news 2026/10/24 5:03:36

Pandas DataFrame数据选择全攻略:从loc、iloc到布尔索引

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Pandas DataFrame数据选择全攻略:从loc、iloc到布尔索引

刚开始用pandas处理数据的人,大概率都会在DataFrame这一步犯迷糊——明明数据已经读进来了,想取几行几列却总是报错,要么取出来是个视图改不动,要么直接噼里啪啦一堆警告。别问我怎么知道的,我当年学的时候也是被loc和iloc绕得头晕。今天这篇就专门讲透第2关:DataFrame数据选择方法。

所谓数据选择,说白了就是"怎么从一张表里精准地掏出你想要的那部分数据"。它是一切数据处理、清洗、分析的前提,因为后面你做的所有操作——筛选、分组、透视、画图——本质上都是在"选择"的基础上展开的。这篇文章适合刚接触pandas的初学者,也适合那些已经会df[...]但搞不清loc、iloc区别、经常被SettingWithCopyWarning教训一顿的进阶选手。我会把每一种选择方法的使用场景、背后原理、常见坑位都摊开来讲,全程配代码,你可以直接复制到Jupyter或VS Code里跑。

1. 为什么DataFrame选择方法这么让人头大

1.1 本质原因:表有两个坐标系

先说个最简单的类比。你去图书馆找一本书,有两种方式:一种是按书名找(标签),一种是按书架位置找(位置编号)。同一本书你既可以用"《Python数据分析》"这个名字找到它,也可以用"3楼A区第2排第5本"这个坐标找到它。

DataFrame也一样,每个元素都有两套坐标体系:

  • 标签坐标:行索引(index)和列名(columns),比如行叫"张三"、"李四",列叫"语文"、"数学"。
  • 位置坐标:也就是从0开始数的整数序号,第0行、第1行,第0列、第1列。

pandas为了让这两套坐标都可用,设计出了loc(label-based,按标签)和iloc(integer-based,按位置)两个阵营。初学者最常犯的错,就是拿标签当位置用,或者反过来。我见过有人写df.iloc['a'],结果报错说'a'不是整数——这就是没分清楚两个坐标系。

1.2 选择方法虽多,但各有各的用武之地

除了loc和iloc,DataFrame还有直接方括号df[]、布尔条件筛选、at/iat快速取值、query字符串查询等方法。你可能觉得方法怎么这么多,选一个不就行了?

真不行。举个实际场景:你想按"某列大于某个值的所有行",方括号能做到,但写法别扭;你想取"第3行第5列"这个单值,用loc能做但性能一般,iat才是最优解。每种方法都有自己的舒适区,用对地方效率高、代码清晰,用错地方轻则报错重则静默出错。

我觉得可以把这些方法归纳成一张"选型表":

场景推荐方法为什么
按行名/列名取数据loc标签语义清晰,切片含尾
按行号/列号取数据iloc纯位置定位,切片不含尾
取单列(返回Series)df['列名']或df.列名语法最简
条件筛选多行布尔索引df[df['列'] > 阈值]直观可读
取单个标量值at/iat速度最快,不产生副本
复杂且可复用的查询query()字符串表达式,代码更短

这张表你贴在手边,练习的时候拿不准就回来查,用不了几次就熟了。

2. 核心选择方法逐个拆解

2.1loc:按标签选择的"闭区间"杀手

loc是pandas里最常用也最好理解的标签选择器。它的完整语法是df.loc[行选择器, 列选择器],逗号左边管行,右边管列,中间逗号别漏。

它支持的选择器类型很丰富:

  • 单个标签:df.loc['张三']
  • 标签列表:df.loc[['张三', '李四']]
  • 切片(注意!含尾):df.loc['张三':'王五'],这个切片会同时包含"王五"这一行
  • 布尔数组:df.loc[df['语文'] > 60]
  • 可调用函数:df.loc[lambda df: df['数学'] > 90]

这里最反直觉的就是切片含尾这件事。Python原生的切片比如list[1:3]是不含索引3的,但loc的切片左右都包含。原因是标签本身没有"下一个"的概念——字符串之间没法像整数那样直接算谁是谁的下一个,所以pandas干脆做了闭区间处理。

我第一次用的时候就在这翻过车:想取df.loc['2024-01-01':'2024-01-05']这5天数据,结果取出来发现自己把2024-01-05也包含进去了,还纳闷怎么多了一天。后来才明白,只要你的行索引是日期、字符串这些连续标签,loc切片就默认把两端全部包含。

2.2iloc:按位置选择的"前闭后开"老派风格

iloc走的是纯整数位置路线,和Python原生的切片习惯保持一致:左含右不含。df.iloc[0:3]取的是第0、1、2行,没有第3行。

它的场景很明确:不关心行叫什么名字,只关心它在表格里的物理位置。比如你想看前5行数据,df.iloc[:5];想看最后3行,df.iloc[-3:];想取第2行到第4行、第1列到第2列,df.iloc[1:4, 0:2]。

我自己实际工作中iloc用得比loc少一些,因为真实数据分析任务里,熟人名、熟列名的情况远多于"数第几行"。但有两个场景非它不可:

  • 数据探索阶段,快速看前几行或中间某段,df.iloc[100:120]比df.loc要省事。
  • 处理流水线数据时,行号具有实际物理意义(比如分批数据的前N行),这时候位置定位最靠谱。

还有一个容易踩的坑:iloc不支持布尔数组直接传入(其实是支持的,但要求是位置型的布尔序列)。有些新手把df.iloc[df['语文'] > 60]写出来,pandas会一头雾水,因为iloc只认整数位置,不认条件。条件筛选请去找df[]或loc。

2.3 方括号df[]:最直接的列选择器

df['列名']是最早接触到的选择方式,它的底层语义是"按列名取列"。传入单列返回Series,传入列表返回DataFrame:

# 返回Series df['语文'] # 返回DataFrame,注意有两层方括号 df[['语文', '数学']]

这里有个小细节:取单列时返回的是Series,如果你要的不是Series而是"只有一列的DataFrame",必须用双括号df[['语文']]。这两者在后续操作中行为差别很大——Series很多方法返回的是标量或Series,而DataFrame始终是二维结构。我在写聚合统计的时候经常因为类型搞错,链式调用半天报错,一查发现是单列DataFrame当Series用了。

方括号还有一个隐藏用法,就是传入布尔数组进行行筛选,df[df['语文'] > 60]其实等价于df.loc[df['语文'] > 60]。这个用法在教程里经常和列选择混在一起讲,导致新手搞不清方括号到底是选行还是选列。其实可以这样记:

  • 方括号里是列名或列名列表→ 选列
  • 方括号里是布尔Series/数组→ 选行(按条件过滤)
  • 方括号里是可以解释为布尔数组的表达式→ 也是选行

这个二元性让df[]显得很灵活,但也容易引发混淆。如果拿不准,我建议统一走df.loc,它行列分离,语义清楚,不容易出歧义。

2.4 布尔筛选:条件选择的灵魂

真正体现"选择"威力的,不是按标签或位置取数,而是按条件取数。DataFrame的布尔筛选逻辑是这样的:

  1. 构造一个与DataFrame行数相同的布尔Series,每个元素对应一行数据是否满足条件。
  2. 把这个布尔Series传给df[]或loc,pandas就会保留所有True对应的行。

比如筛选语文成绩大于60且数学成绩大于90的学生:

df[(df['语文'] > 60) & (df['数学'] > 90)]

这里有两个反人类的设计必须提醒:

第一,逻辑与用&而不是and,逻辑或用|而不是or。因为and/or是Python关键字,只对单个布尔值生效,而这里的比较结果是Series(一整套布尔值),必须用按位运算符&和|。新手最容易在这里栽跟头。

第二,多条件必须加括号。df['语文'] > 60 & df['数学'] > 90会报错,因为Python的运算符优先级里&比>高,得写成(df['语文'] > 60) & (df['数学'] > 90)。

布尔筛选还可以配合isin方法做集合匹配,这个在实际工作里非常常见。比如你要找出所有属于"北京、上海、广州"三个城市的记录:

df[df['城市'].isin(['北京', '上海', '广州'])]

这段代码比用多个|连接条件要清爽得多,也更容易维护——条件城市列表可以单独抽出来变成一个变量,后面加城市只改列表就行。

2.5at、iat、query:提升效率和可读性的三件套

at和iat是loc和iloc的"单值特化版"。它们的定位是:当你只需要取或者改一个元素的时候,用它们会比loc/iloc快很多,而且代码语义更明确。

# 按标签取单值 df.at['张三', '语文'] # 按位置取单值 df.iat[0, 1]

实测下来,在大数据框上循环逐元素访问的场景(虽然一般不建议循环,但有时绕不开),at比loc快一个数量级。原因是at做了严格的单元素优化,绕开了切片、复制、索引对齐这一大堆开销。

query则是把查询条件写成字符串,引擎内部解析后执行过滤。它适合条件复杂且需要反复使用的场景,配合f-string还能动态构造查询:

df.query('语文 > 60 and 数学 > 90') df.query('城市 in ["北京", "上海"]') # 动态传入阈值 threshold = 85 df.query(f'数学 > {threshold}')

query不但在代码长度上更短,还有一个隐藏优势:列名可以直接写在字符串里,不用反复用df['列名'],代码读起来更像SQL,团队协作时理解成本低。但注意,query里引用列名时如果列名带空格或特殊字符,需要用反引号包裹。

3. 实操过程:从零到一跑通数据选择

3.1 先造一份测试数据

纸上谈兵没意思,我们来点真能跑的。我建议你边看边在Notebook里敲,先造一份模拟的学生成绩表:

import pandas as pd import numpy as np data = { '姓名': ['张三', '李四', '王五', '赵六', '钱七'], '班级': ['A1', 'A1', 'A2', 'B1', 'B2'], '语文': [78, 92, 66, 58, 88], '数学': [85, 97, 72, 60, 91], '英语': [69, 88, 80, 55, 76], } df = pd.DataFrame(data) df = df.set_index('姓名') print(df)

这里我用set_index('姓名')把姓名列变成行索引,是为了方便演示loc的标签切片。打印出来是这样一个表:

班级 语文 数学 英语 姓名 张三 A1 78 85 69 李四 A1 92 97 88 王五 A2 66 72 80 赵六 B1 58 60 55 钱七 B2 88 91 76

这个数据集只有5行,但足够跑通所有选择方法了。

3.2 按标签和位置取数的完整练习

先来loc系列:

# 取单行 df.loc['张三'] # 取多行(列表) df.loc[['张三', '王五']] # 标签切片,含头含尾! df.loc['张三':'王五'] # 同时选行和选列 df.loc['张三': '王五', ['语文', '数学']]

再来看iloc系列:

# 取前3行 df.iloc[0:3] # 取第1行到第3行、第0列到第1列(不含第3行第1列?注意是前三行前两列) df.iloc[1:3, 0:2] # 用位置取第二行的数学成绩 df.iloc[1, 2] # 输出 97

如果你把df.loc['张三':'王五']和df.iloc[0:3]的结果对比一下,会发现它们都返回了三行,但语义完全不同:前者包含了"王五"这一行,后者恰好取到前3行。当你的索引是连续整数时(比如默认的0、1、2...),这两个写法经常看起来结果一样,于是很多人误以为loc和iloc没区别。一旦索引变成字符串或日期,差别马上就暴露了。

我强烈建议你做一个小实验:把上面的set_index('姓名')去掉,直接用默认整数索引跑一遍df.loc[0:2]和df.iloc[0:2],再观察结果差异。跑过一次之后,你对两种切片的边界规则会有肌肉记忆。

3.3 条件筛选组合实战

接下来是布尔筛选。单独筛、交叉筛、反向筛,全部来一遍:

# 语文及格的同学 df[df['语文'] >= 60] # 语文和数学都超过80的尖子生 df[(df['语文'] > 80) & (df['数学'] > 80)] # 语文不及格或英语不及格 df[(df['语文'] < 60) | (df['英语'] < 60)] # 反向筛选:不是A1班的同学 df[~(df['班级'] == 'A1')]

注意最后那个反向筛选用了波浪号~做取反操作,这对应布尔Series的逐元素取反~True = False。很多新手想写"不等于",直接用df[df['班级'] != 'A1'],这样当然也可以,但在批量条件组合的场景中~更通用。

再来一个loc加布尔条件的混合用法:

# 取数学成绩前两名的同学的姓名和英语成绩 df.loc[df['数学'] > 90, ['姓名', '英语']]

这里loc的行位置传的是布尔Series,列位置传的是列名列表,pandas会自动完成布尔筛选加列选择两个动作。

最后用query重写一遍,你看看代码短了多少:

df.query('语文 > 80 and 数学 > 80') df.query('班级 != "A1" and 英语 < 80')

3.4 真正项目里的组合姿势

上面都是基础招式,但实际工作里很少只用一个方法,多数情况是组合着来。分享一个我处理过的模拟场景,这个"某团队运营数据"的例子很能说明问题:

# 模拟一份订单数据 orders = pd.DataFrame({ '订单号': ['A001', 'A002', 'A003', 'A004', 'A005'], '城市': ['北京', '上海', '广州', '北京', '深圳'], '金额': [150, 3200, 89, 450, 2100], '状态': ['已完成', '已完成', '已取消', '已完成', '已完成'], }) # 找出北京和上海的已完成订单,金额大于100的部分 target = orders[ (orders['城市'].isin(['北京', '上海'])) & (orders['状态'] == '已完成') & (orders['金额'] > 100) ] print(target)

这段代码用到了isin、==和>三个条件,再叠加&连接,一条链就把数据筛出来了。如果你嫌条件太长,可以先逐步构造布尔Series,再统一筛选:

city_ok = orders['城市'].isin(['北京', '上海']) status_ok = orders['状态'] == '已完成' amount_ok = orders['金额'] > 100 filtered = orders[city_ok & status_ok & amount_ok]

这种方式的可读性更好,而且方便你在中间打印city_ok看看布尔Series长什么样。我个人很推荐新手用这种"拆步骤"写法,既能每一步验证结果,又不容易错。

4. 常见问题与排查技巧实录

4.1SettingWithCopyWarning到底在警告什么

pandas里最著名的幽灵警告非它莫属。我第一次遇到的时候,代码能跑、结果也正常,但控制台全是黄字警告,心里很不踏实。这个警告的触发场景一般是链式索引赋值:

# 这种写法容易触发警告 df[df['语文'] > 60]['英语'] = 100

原因很简单:df[df['语文'] > 60]返回的可能是一个视图(view)也可能是一个副本(copy),pandas自己都不确定。如果它是视图,你的修改会改动到原表;如果它是副本,你的修改只作用于临时数据,原表原地不动。两种行为都有可能,所以pandas干脆警告你:"我不保证你改的是哪一份数据。"

解决办法是永远用loc一步到位:

df.loc[df['语文'] > 60, '英语'] = 100

这行代码明确告诉pandas:我要把"语文及格的人"的"英语列"改成100,顺序清晰,pandas也就能确定执行的是原地修改。这个习惯建议从第一天就养成,后面能省大量排查时间。

4.2loc切片含尾、iloc切片不含尾,记不住怎么办

这是新手高频翻车点,有个记忆诀窍:把"含尾的loc"想成"闭区间",把"不含尾的iloc"想成"开区间"。

  • 凡是按名字切的,都是"从头到尾算上"——因为你不知道"王五"的下一个标签是谁,pandas没法偷偷帮你排除"王五"。
  • 凡是按序号切的,都跟Python原生一致——0到3就是0、1、2。

如果你在切片代码里发现结果总比你预想的多一行,第一反应就该检查用的是不是loc;如果总是少一行,检查是不是iloc。

还有一个小技巧:如果你不确定某个取数操作的结果,先打印出来看shape属性。看到行数不对就调整切片边界,这比空想快得多。

4.3 筛选结果全是NaN或空表,多半是条件没匹配上

有的新手写完条件筛选,返回的DataFrame全是一堆NaN,或者行数为0。常见原因有两个:

原因一是列名写错了或者列名有隐藏空格。比如df['城市 '](城市后面多了个空格)匹配不上真实列,返回全NaN。排查方法是df.columns.tolist()把列名原样打出来,肉眼对一遍。

原因二是数据类型不对。你在Excel里看到的"001"可能在pandas里存成了整数1,然后你用字符串'001'去筛,当然筛不中。这时候先用df.dtypes检查列类型,必要时用astype(str)统一转换。

还有一个典型坑是用query时字符串引号嵌套。df.query("城市 == '北京'")外层用双引号内层用单引号,如果你反过来写df.query('城市 == "北京"')其实也可以,但要是条件里既有单引号的内容又有双引号的内容,就很容易在转义上翻车。我建议统一外层单引号、内层双引号的写法,避免混乱。

4.4 修改DataFrame时改了没反应?多半改在副本上

另一个常见现象是:你写df[df['语文'] > 60].loc['张三', '英语'] = 99,运行不报错,但回头看原表还是旧值。原因还是副本问题——筛选那一步就已经产生了一个新DataFrame,后面所有操作都作用在这个临时对象上,原表纹丝不动。

正确的姿势同样是把所有选择合并到一次loc调用里:

df.loc[df['语文'] > 60, '英语'] = 99

如果这个坑你已经踩过,我把这条建议写得再直白一点:任何时候你想"先选再改",都写成"一次loc完成选择+赋值",不要写成两步。这不是风格问题,是pandas内部机制决定的。

5. 卡壳时最有效的几个自测习惯

学数据选择最大的障碍不是语法难,而是"看起来会了但一跑就错"。我自己带过不少人学pandas,发现学得快的人都有一个共同习惯:每写一行选择代码,就立刻打印输出看一眼形状和数据样貌。不要连着噼里啪啦写十行再跑,那样出错都不知道出在哪一步。

比如你写df.loc['张三':'王五', ['语文', '数学']],跑完立刻打印shape,看到(3, 2)就知道行列数对不对;再打印前两行,确认内容是否符合预期。这个习惯能让你在五分钟内排除八成低级错误。

另外,个人强烈建议你养成用copy()的习惯。当你明确知道自己要基于筛选结果做进一步的独立操作时,先复制一份:

sub = df[df['语文'] > 60].copy()

copy()的作用是切断视图与原表的关联,让sub变成一块全新的独立数据。后续你对sub怎么改都不会影响df,也不用担心莫名其妙的警告。虽然多了一次内存复制,但换来的是心智上的绝对安全。

最后再分享一个小技巧,也是我最常用的一个调试姿势:当你搞不清某段选择逻辑对不对时,把里面的条件单独抽出来跑一遍,打印这个布尔Series的value_counts()。比如:

condition = df['语文'] > 60 print(condition.value_counts())

看到True和False的分布,你就能快速判断筛选条件是否符合预期,比直接盯着筛选结果猜快得多。这个习惯我用了很多年,不管是几行的小表格还是几百万行的大数据,排查效率都稳得很。把这第2关的内容练熟,后面处理聚合、分组、透视就都不会再因为"取不出数据"而卡壳了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 5:03:03

基于PCA9422与PIC18F45K22的嵌入式电源管理方案设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 5:03:01

空白字符避坑指南:从ASCII到Unicode的27+种隐形陷阱

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 5:01:42

低功耗便携设备电源管理实战:PCA9422+STM32L151ZD设计解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 4:59:36

机房改造实战:从供配电冗余到动环监控的系统方案拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 4:59:26

C语言字面量后缀U/UL/ULL详解:位操作与跨平台安全基石

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/24 4:58:54

优先审查下OA概率会变高吗?答复期限如何应对?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华