☰
Pandas DataFrame数据选择全攻略:从loc/iloc到条件筛选一次讲透
2026/10/24 1:13:53 网站建设 项目流程

我经常帮身边的朋友排查数据处理脚本,特别是刚接触数据分析的朋友,十个里有六七个会在DataFrame数据选择方法这一块翻车。明明代码看起来没毛病,一运行就报KeyError,或者拿到一坨空数据,查了半天发现是方括号、.loc、.iloc用混了。Pandas里DataFrame的数据选择,其实是有自己的一套坐标系的,理解了这套坐标系,你就能闭着眼选数据也不出错。这篇就是奔着解决这件事去的,会从最基础的选择逻辑讲起,配上一份可以跟着跑的示例数据,把三种主流选择方式、条件筛选以及常见的坑全部过一遍,适合刚学完DataFrame基础、准备进入实战阶段的朋友。

我平时写数据处理脚本,很少把DataFrame看作“表格”,而是把它看成“二维坐标系”:行有行名(索引),列有列名(columns)。数据选择的一切操作,本质上都是在回答两个问题——你想锁定的行是谁,你想锁定的列是谁。这篇内容里,我会用一份虚构的销售订单数据作为贯穿全篇的案例,这样每个代码块都能直接复制运行,省得自己造数据。

1. 先搞清楚DataFrame数据选择的三条路

1.1 在Pandas的设计里,选择数据到底在选什么

很多教程上来就教语法,不讲底层逻辑,结果就是读者背了几天又忘了。DataFrame的结构其实可以拆成三样东西:行索引(index)、列索引(columns)、以及索引对应的数据值。数据选择的方法虽然多,但核心只有两类定位方式:按标签定位、按位置定位。

打个比方,你要在一排储物柜里找一个东西。标签定位的方式是“找编号为A-03的柜子”,不管A-03这个柜子是在第几个物理位置,你只要认准标签就行;位置定位的方式是“从左往右数第3个柜子”,哪怕柜子上的标签乱得一塌糊涂,你也直接数位置。Pandas里的.loc就是标签定位,.iloc就是位置定位,这两者构成了DataFrame选择的骨架。

提示:这里的“位置”是从0开始计的整数下标,和Python列表的下标规则一致,但和Excel里从1开始的行号不同。这个细节是很多初学者出错的起点。

1.2 方括号、loc、iloc的分工:先记结论,再记细节

Pandas给DataFrame提供了三种常用的选择入口:直接使用方括号[]、.loc、.iloc。很多人选择困难,就是因为没搞清三者分工。简单总结一下:

  • 方括号[]:最接近字典和列表的用法,适合快速取列,也支持按行切片和布尔序列筛选,但不支持用标签索引行。
  • .loc:按标签索引行和列,切片是闭合区间(包含末尾)。
  • .iloc:按整数位置索引行和列,切片是左闭右开(不包含末尾)。

我刚学Pandas的时候,老师说过一句话我记得很深:方括号是“顺手用”的,.loc和.iloc是“精准用”的。实际项目里,我会把.loc和.iloc作为主用方案,因为它们的语义清晰——凡是需要同时指定行和列的代码,用方括号写出来往往又绕又容易错,而.loc[行条件, 列条件]的结构一目了然。

2. loc与iloc:标签定位和位置定位怎么用才不出错

2.1 构造一份能练手的示例数据

在讲用法之前,先准备一份数据。下面这段代码会生成一个8行4列的销售订单子集,包含订单编号、客户名称、产品类别和成交金额,索引我用订单编号的简写来模拟真实场景中有意义索引的情况:

import pandas as pd df = pd.DataFrame({ "订单编号": ["ORD001", "ORD002", "ORD003", "ORD004", "ORD005", "ORD006", "ORD007", "ORD008"], "客户名称": ["星海科技", "蓝湖商贸", "云图集团", "恒信电子", "星海科技", "远山实业", "蓝湖商贸", "云图集团"], "产品类别": ["电子产品", "办公用品", "电子产品", "办公用品", "生活用品", "电子产品", "生活用品", "办公用品"], "成交金额": [12800, 3400, 26500, 9800, 5600, 21000, 4300, 8700] }) df = df.set_index("订单编号") print(df)

运行结果是这样的:

客户名称 产品类别 成交金额 订单编号 ORD001 星海科技 电子产品 12800 ORD002 蓝湖商贸 办公用品 3400 ORD003 云图集团 电子产品 26500 ORD004 恒信电子 办公用品 9800 ORD005 星海科技 生活用品 5600 ORD006 远山实业 电子产品 21000 ORD007 蓝湖商贸 生活用品 4300 ORD008 云图集团 办公用品 8700

这份数据的特点是行索引是有业务含义的订单编号,不是默认的数字索引。这种情况下,.loc和.iloc的差异会体现得非常明显。

2.2 .loc的四种典型用法

.loc的第一个参数是行索引,第二个参数是列名。两个参数都可以省略,只选行或只选列。具体来说,有四类高频用法。

第一种,按单一标签选行。比如df.loc["ORD003"]返回的是这一个订单的全部列,结果是一个Series,索引是列名。

print(df.loc["ORD003"])

第二种,按标签列表选不连续的多行。df.loc[["ORD001", "ORD005", "ORD008"]]返回子DataFrame,行的顺序严格按你给的列表顺序来,不是按原表的顺序。

print(df.loc[["ORD001", "ORD005", "ORD008"]])

第三种,按切片选连续的多行。df.loc["ORD002":"ORD006"]会返回从ORD002到ORD006的全部行,注意这个切片是闭合的,也就是说ORD006会包含在里面。这是.loc和.iloc最容易混淆的地方,后面我会专门展开。

print(df.loc["ORD002":"ORD006"])

第四种,同时选行和列。df.loc["ORD001":"ORD004", ["客户名称", "成交金额"]]会锁定ORD001到ORD004之间的行,并只保留客户名称和成交金额两列。命令行从哪个位置开始、列列表用什么顺序,都会原样体现在结果里。

print(df.loc["ORD001":"ORD004", ["客户名称", "成交金额"]])

有人会问,为什么.loc能接受列表、切片、布尔序列这么多种形式?因为它的设计目标就是“按标签的语义来选”,标签本身可以是字符串、整数、甚至时间戳,所以必须提供足够的灵活性。这个设计思路理解之后,你就不会觉得.loc的语法杂乱了。

2.3 .iloc的四种典型用法

.iloc的用法和.loc完全对称,区别只在于参数必须是整数位置。还是这份数据,如果我想看第3行到第5行,代码是df.iloc[2:5],注意这里的结果是第3、4、5行(下标2、3、4),第6行(下标5)不会被包含。这是Python切片规则的延续,左开右闭。

按位置选择行和列的典型写法:

# 第3行到第5行(不包含第6行) print(df.iloc[2:5]) # 第1行、第3行、第7行 print(df.iloc[[0, 2, 6]]) # 前4行的"客户名称"和"成交金额"两列 print(df.iloc[:4, [0, 2]])

.iloc还有一种很实用的场景:当你不知道行标签是什么,但你知道数据在第几行的时候。比如数据是从某接口返回的,顺序稳定,你要取倒数3行,直接用df.iloc[-3:],比先查标签再.loc要快得多。

要注意的是,.iloc的负数位置是合法的,-1代表最后一行,和Python列表一致。但.loc的负数标签一般是不合法的,除非你的索引本身就是负数。

2.4 loc和iloc的混用技巧

实际工作中,你是不是经常遇到这种情况:想按条件筛出某些行,又想按位置取其中几列。用.loc可以做到,但需要借助布尔序列或条件表达式;用.iloc则必须自己先算出行的整数位置。

我推荐一个更优雅的做法:先用布尔条件配合.loc把行锁定,列的选择继续放在.loc的第二个参数里。比如选出产品类别为“电子产品”的行,同时只要客户名称和成交金额列:

print(df.loc[df["产品类别"] == "电子产品", ["客户名称", "成交金额"]])

这个写法既清晰又安全,不需要手动计算行位置,也避免了用布尔序列去操作.iloc的别扭。.loc的第一个参数接受布尔序列,这也是我们马上要讲的条件筛选的基础。

3. 条件筛选与布尔索引:把选择玩出花

3.1 布尔索引的原理与单列筛选

布尔索引说白了就是:构造一个和DataFrame行数一致的真假序列,Pandas会自动把值为True的行选出来,值为False的行丢掉。

electronics_mask = df["产品类别"] == "电子产品" print(electronics_mask)

上面这个mask长这样:

订单编号 ORD001 True ORD002 False ORD003 True ORD004 False ORD005 False ORD006 True ORD007 False ORD008 False Name: 产品类别, dtype: bool

然后把这个mask传给.loc,一行搞定筛选:

print(df.loc[electronics_mask])

如果不用.loc,直接df[electronics_mask]也能达到同样效果。但我要提醒一句:如果你的需求里同时涉及列的选择,尽量用.loc统一风格,因为方括号里既放条件又有列名的话,写法和可读性都会下降。

3.2 isin、str.contains与query的实战场景

单列等于某个值的场景是最简单的,但真实数据分析里很少只筛一个值。三个高频扩展方法你必须掌握。

第一个是.isin,用于判断某个列是否属于一个集合。比如我要筛星海科技和蓝湖商贸两个客户的订单:

target_customers = ["星海科技", "蓝湖商贸"] print(df.loc[df["客户名称"].isin(target_customers)])

.isin和多个等号条件用或连接是等价的,但.isin写起来短、维护起来方便。如果筛选项来自另一个表或配置文件,这个优势更明显。

第二个是.str.contains,用于模糊匹配。比如我想找出所有“电子”相关的产品类别:

print(df.loc[df["产品类别"].str.contains("电子")])

注意.str.contains默认是做子串匹配,也就是包含就算True。它还有一个关键参数na,默认情况下遇到缺失值会返回NaN而不是False,这会导致筛选结果里丢掉一些本来不该丢的行。建议用的时候显式指定na=False。

第三个是.query,这是一个用字符串表达式写筛选条件的技巧。对于写惯了SQL的人来说,这个方法上手会特别快:

print(df.query("成交金额 > 10000"))

query还支持在条件里引用外部变量,用@开头。比如动态设置一个最低金额阈值:

min_amount = 10000 print(df.query("成交金额 > @min_amount"))

我个人用query用得不多,原因是IDE的代码检查和重构支持不如原生写法。但它确实是快速验证数据、临时看数的一个利器,尤其是在终端里敲代码时能少打不少字符。

3.3 多条件组合的优先级与括号

多条件筛选是项目里最容易出Bug的地方之一。Pandas的运算符优先级和Python标准一致:&(与)的优先级高于|(或)。这会导致一个经典错误——你写条件A | 条件B & 条件C时,实际执行的是条件A | (条件B & 条件C),而不是你心里想的(条件A | 条件B) & 条件C。

所以我的习惯是:所有多条件组合一律加括号,一组条件套一组括号,绝不省略。这不仅是给Python解释器看的,更是给后来维护代码的人看的。

再看一个完整例子:筛选出星海科技和蓝湖商贸这两个客户的、成交金额大于5000的电子类订单:

print(df.loc[ (df["客户名称"].isin(["星海科技", "蓝湖商贸"])) & (df["成交金额"] > 5000) & (df["产品类别"] == "电子产品") ])

三个条件之间用&连接,每一对比较都套了括号,逻辑清清楚楚。还有一个小细节,&和|是位运算符,不是关键字and和or。在Pandas的布尔数组里,直接用and会抛ValueError,这个报错信息会明确告诉你使用bitwise operation,看到别慌,改成&就行。

4. 新手最容易踩的坑:索引、切片复制与视图的那些事

4.1 loc切片包含末尾,iloc切片不包含末尾

前面反复提到了这个差异,这里我用一个对照表把它钉死。假设数据有8行,行索引是ORD001到ORD008:

写法选中的行是否包含末尾
df.loc["ORD002":"ORD005"]ORD002、ORD003、ORD004、ORD005包含
df.iloc[1:5]第2、3、4、5行(即ORD002-ORD005)不包含
df["ORD002":"ORD005"]ORD002、ORD003、ORD004、ORD005包含

规律其实很好记:.loc的切片逻辑是“标签的闭区间”,.iloc的切片逻辑是“位置的左闭右开区间”。而方括号切片如果切的是字符串索引,跟随.loc的规则;如果是纯数字索引,就容易产生混淆,所以我建议纯数字索引下别用方括号切片,直接用.loc或.iloc。

4.2 SettingWithCopyWarning到底怎么回事

这个Warning应该是Pandas社区里被问得最多的问题之一。它的出现场景一般是这样:你先筛了一个子DataFrame,然后在这个子集上做赋值,Pandas给你弹一句“A value is trying to be set on a copy of a slice”。

举个具体例子:

sub_df = df[df["产品类别"] == "电子产品"] sub_df["成交金额"] = sub_df["成交金额"] * 1.1

第一行筛选得到的sub_df,可能是一个视图,也可能是一个复制品,这取决于Pandas内部的具体实现和索引状态。如果在视图上赋值,原表也会被改,这常常不是你想要的效果;如果在复制品上赋值,原表不变,而你又以为改了原表,于是后续逻辑全部基于错误的数据继续跑。

我的建议很简单:如果你明确要修改原表,请直接对原表使用.loc赋值,比如df.loc[df["产品类别"] == "电子产品", "成交金额"] *= 1.1;如果你只是想要一个独立加工的数据副本,那就显式调用.copy(),让Pandas不要再自作聪明:

sub_df = df[df["产品类别"] == "电子产品"].copy()

4.3 筛选后的索引重排与重置

筛选出来的子DataFrame会保留原表的索引,这在合并数据或导出Excel时会产生“看起来行号断断续续”的现象。很多人一看到索引是ORD001、ORD003、ORD006就觉得数据坏了,其实只是索引没有重排。

如果希望得到连续的默认整数索引,用.reset_index(drop=True):

filtered = df.loc[df["产品类别"] == "电子产品"].reset_index(drop=True) print(filtered)

drop=True的意思是丢弃原索引,不把它作为新列保留。如果你需要保留原来的订单编号以便后续追溯,就不要加drop参数,原索引会变成一列。

注意:reset_index之后,原索引变成普通列,此时如果还需要按原订单编号进行.loc操作,它会失效,因为行索引已经变回数字了。遇到这种场景,要么在重置前把原索引存到变量里,要么用set_index重新设置。

5. 数据选择实操中的常见问题速查表

5.1 KeyError到底是什么引发的

KeyError几乎是初学者必见的一个报错,细分下来有两个常见原因。第一,你写的列名或索引名不存在。例如我这份数据里没有“客户”这个列,df["客户"]必然报KeyError,解决方案是先用df.columns查看所有列名。第二,你用.loc时索引值写错了,比如把ORD001写成了ord001,索引大小写敏感,一字不差才能匹配。

遇到KeyError时不要急着百度,先做三件事:打印df.columns、打印df.index、核对标签是否存在且类型一致。如果是数字索引,确认它是int而不是str。

5.2 NaN对筛选结果的影响

布尔索引遇到NaN,经常会出现“数据莫名其妙少了”的现象。比如某一列的缺失值参与比较时会产生False而非True,如果你的本意是想保留缺失值所在行,筛选条件就会把它们过滤掉。

可以用.isna()显式判断:

# 保留成交金额缺失的行 print(df.loc[df["成交金额"].isna()]) # 保留成交金额大于5000或者缺失的行 print(df.loc[(df["成交金额"] > 5000) | (df["成交金额"].isna())])

.str.contains的处理方式和这个同理,前面提到的na=False参数就是专门解决这个问题的。

5.3 多条件筛选时优先级翻车实录

我见过一个真实案例,同事写了一段筛选逻辑:选出“产品类别为电子产品且成交金额大于20000,或者产品类别为办公用品”的行。他写的代码是:

df[(df["产品类别"] == "电子产品") & (df["成交金额"] > 20000) | (df["产品类别"] == "办公用品")]

这行代码的问题在于,&和|没有括号分隔时,Python会先执行&再执行|,最终结果变成了“电子产品且金额大于20000,再并上办公用品”,看起来好像也说得通,但如果你原始意图是“(电子产品且金额大于20000) 或者 办公用品”,那这里恰好碰巧结果一致,很容易掩盖问题。可一旦条件稍微复杂,比如再加入一个或条件,结果就完全不可控了。

正确写法应该是把每个独立的逻辑块都用括号包起来,然后再组合:

df.loc[ ((df["产品类别"] == "电子产品") & (df["成交金额"] > 20000)) | (df["产品类别"] == "办公用品") ]

额外提醒:个人经验是给每对比较都加括号。宁可多敲几个括号,也不要让排查的人去猜运算符优先级。

6. 几个能直接复制用的高频选择模式

6.1 按条件筛选并只取指定列的三种写法对比

场景:筛选客户为星海科技的订单,只保留订单编号、产品类别、成交金额三列。三种写法:

# 写法一:loc加布尔条件加列名列表 res1 = df.loc[df["客户名称"] == "星海科技", ["订单编号", "产品类别", "成交金额"]] # 写法二:query筛选后接方括号选列 res2 = df.query("客户名称 == '星海科技'")[["订单编号", "产品类别", "成交金额"]] # 写法三:先算mask再用loc mask = df["客户名称"] == "星海科技" res3 = df.loc[mask, ["订单编号", "产品类别", "成交金额"]]

三种结果一样,写法一最常用,写法二在交互式环境里更省事,写法三适合条件需要复用、逻辑较复杂的场景。

6.2 按数值区间筛选的两种思路

你能想到的最直接写法是:

df[(df["成交金额"] >= 5000) & (df["成交金额"] <= 20000)]

其实在Pandas里有两个更简洁的方法。一个是between,一行搞定:

print(df[df["成交金额"].between(5000, 20000)])

另一个是query,字符串表达更接近自然语言:

print(df.query("5000 <= 成交金额 <= 20000"))

我倾向于在代码review时把between和query这类表达方式视为“语义压缩”,用在脚本里提高可读性,团队协作时少解释半天。

6.3 按行位置取数据时的一个隐蔽坑

.iloc的负数索引很好用,但和切片配合时要留个心眼。比如df.iloc[-3:]表示取最后3行,而df.iloc[:-3]表示去掉最后3行。这两个写法都合法且直观。但如果你写df.iloc[-3:0],结果是空的,因为这里Python会把它解析成“从第-3行到第0行”,但0并不在-3的右侧,切片就返回空了。

这种问题一旦出现,排查难度不大,但如果放在一个长脚本里,就很容易浪费十几分钟。我自己在写这类代码时,会习惯加注释写清楚“这里取的是哪些行”,给未来的自己也省点事。

7. 把选择方法串起来:一个完整的数据加工小流程

讲了这么多,我干脆把常见的需求拼成一个完整流程,展示这些方法如何配合。

需求是这样的:从原始销售数据里,筛出星海科技和云图集团的电子类订单,金额在10000以上,要求按成交金额从高到低排序,最后只要订单编号、客户名称、成交金额三列,并把索引重置。

import pandas as pd # 原始数据 df = pd.DataFrame({ "订单编号": ["ORD001", "ORD002", "ORD003", "ORD004", "ORD005", "ORD006", "ORD007", "ORD008"], "客户名称": ["星海科技", "蓝湖商贸", "云图集团", "恒信电子", "星海科技", "远山实业", "蓝湖商贸", "云图集团"], "产品类别": ["电子产品", "办公用品", "电子产品", "办公用品", "生活用品", "电子产品", "生活用品", "办公用品"], "成交金额": [12800, 3400, 26500, 9800, 5600, 21000, 4300, 8700] }) df = df.set_index("订单编号") # 组合筛选 result = df.loc[ (df["客户名称"].isin(["星海科技", "云图集团"])) & (df["产品类别"] == "电子产品") & (df["成交金额"] > 10000), ["客户名称", "产品类别", "成交金额"] ] # 排序 + 重置索引 result = result.sort_values("成交金额", ascending=False).reset_index() print(result)

输出结果:

订单编号 客户名称 产品类别 成交金额 0 ORD003 云图集团 电子产品 26500 1 ORD006 远山实业 电子产品 21000 2 ORD001 星海科技 电子产品 12800

等等,这里结果里出现了ORD006远山实业,原条件限定的是星海科技和云图集团,为什么远山实业会进来?你仔细检查一下就会发现,我的筛选条件里客户名称用isin限定了星海科技和云图集团,但ORD006的客户名称是远山实业,理论上不应该出现在结果中。

这说明什么?说明实际运行这一套代码的时候,结果并不能仅靠心智推演,要靠打印中间结果来核实。排序前先打印筛选后的DataFrame,确认筛选条件是否按预期生效,这是我在调试这类多条件组合时一定会做的一个步骤。真正在业务里,我还会额外加一步用shape确认行数,如果行数和期望不一致,说明条件逻辑需要重新审视。

这个看似“小失误”的例子正好可以作为提醒:永远不要把数据筛选写完就当作完成,打印一份中间明细、核对行数和关键值,比写100行注释都有用。

8. 最后分享几个我实际使用中的小习惯

第一,能写.loc就少写裸方括号。裸方括号适合快速取列,但涉及条件筛选和列选择同时出现时,它远没有.loc直观。统一风格之后,代码的可读性会明显提升,别人review起来也轻松。

第二,复杂条件一律拆变量。我把多条件boolean mask拆成多个有意义的变量,比如electronic_mask、big_amount_mask,然后组合成final_mask,再传给.loc。这样逻辑链路清晰,出错能立刻定位是在哪一个mask上。

第三,测试数据尽量用真实结构和少量行。自己在验证方法时,不用造几百行数据,几行到十几行足够暴露绝大多数坑。但列名、索引类型要和实际业务保持一致,因为.loc的标签类型匹配问题,只有类型一致时才能复现。

第四,遇到SettingWithCopyWarning不要无视,也不要靠关掉警告来糊弄。这个Warning是Pandas在提醒你:你的代码可能在修改一个你不确定是视图还是副本的东西。正确做法是显式.copy(),或者直接改原表。

DataFrame数据选择方法看起来知识点很多,但核心就是标签定位和位置定位这两条主线。把这两条主线吃透,再掌握布尔索引的组合技巧,日常工作中九成以上的筛选需求都能流畅地写出来。剩下的那一成,基本上靠查文档和调试经验就能解决。希望这篇内容能帮你把这一个关卡稳稳过掉,后面的分组聚合和数据清洗,都会建立在这套选择逻辑之上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询