资讯动态

Pandas DataFrame数据选择全攻略:从loc/iloc到条件筛选一次讲透

发布时间:2026/10/24 1:14:00 来源:尧图企业网站定制
我经常帮身边的朋友排查数据处理脚本特别是刚接触数据分析的朋友十个里有六七个会在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这会导致筛选结果里丢掉一些本来不该丢的行。建议用的时候显式指定naFalse。第三个是.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(dropTrue)filtered df.loc[df[产品类别] 电子产品].reset_index(dropTrue) print(filtered)dropTrue的意思是丢弃原索引不把它作为新列保留。如果你需要保留原来的订单编号以便后续追溯就不要加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的处理方式和这个同理前面提到的naFalse参数就是专门解决这个问题的。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(成交金额, ascendingFalse).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数据选择方法看起来知识点很多但核心就是标签定位和位置定位这两条主线。把这两条主线吃透再掌握布尔索引的组合技巧日常工作中九成以上的筛选需求都能流畅地写出来。剩下的那一成基本上靠查文档和调试经验就能解决。希望这篇内容能帮你把这一个关卡稳稳过掉后面的分组聚合和数据清洗都会建立在这套选择逻辑之上。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑