首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Python的pandas把我坑惨了,原来loc和iloc的区别这么大

Python的pandas把我坑惨了,原来loc和iloc的区别这么大

原创
作者头像
风一样的男子
发布于 2026-09-21 14:48:09
发布于 2026-09-21 14:48:09
1230
举报
文章被收录于专栏:编程教程编程教程

上个月,同事让我帮他看一段数据处理的代码。逻辑很简单:从一份销售表里取出前 100 行,按地区筛选,然后更新某列的数值。他写完之后跑了一遍,结果对不上,但代码没报错,数据也没缺,就是数字不对。

他把代码发给我,我扫了一眼,问题出在两行上:

代码语言:javascript
复制
df.loc[0:100, 'amount'] = df.loc[0:100, 'amount'] * 1.1

他说:“取前 100 行,乘个 1.1,有什么问题?”

我说:“在 loc 里,0:100 取的是 101 行,不是 100 行。”

他愣了一下:“切片不都是左闭右开吗?”

在 Python 里是,在 pandas 的 loc 里不是。

这就是 loc 和 iloc 最容易让人栽跟头的地方。它们的语法看起来几乎一样,都是 df[行, 列] 的形式,但底层逻辑完全不同。一个是按标签找,一个是按位置找。一个切片包含右端点,一个不包含。一个接受任意标签类型,一个只接受整数。

混用它们,代码不会报错,但结果会悄悄偏掉。

最根本的区别:标签 vs 位置

loc 的全称是 location by label,按标签定位。iloc 的全称是 integer location,按整数位置定位。

用一个具体的 DataFrame 来看:

代码语言:javascript
复制
import pandas as pd

df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie', 'David'],
    'score': [85, 92, 78, 95]
}, index=['a', 'b', 'c', 'd'])

这个 DataFrame 的索引是 'a'、'b'、'c'、'd',位置是 0、1、2、3。

df.loc['b'] 取的是索引为 'b' 的那一行,也就是 Bob。df.iloc[1] 取的是位置为 1 的那一行,也是 Bob。这里两者恰好指向同一行,因为索引标签和位置碰巧对上了。

但如果索引不是默认的 0、1、2、3 呢?比如把索引改成 [10, 20, 30, 40]:

代码语言:javascript
复制
df.index = [10, 20, 30, 40]

现在 df.loc[10] 取的是索引为 10 的那一行,也就是第一行 Alice。但 df.iloc[10] 会直接报 IndexError,因为只有 4 行,位置 10 不存在。

反过来,df.iloc[0] 取第一行 Alice,df.loc[0] 会报 KeyError,因为索引里没有 0 这个标签。

这就是核心差异:loc 认的是索引里真实存在的标签,iloc 认的是从 0 开始数的位置编号。两者只在索引恰好是 0、1、2、3……的时候才会重合。一旦索引被设置成日期、字符串、或者乱序的整数,它们就分道扬镳了。

切片行为:loc 包含右端点,iloc 不包含

Python 的切片 list[0:3] 取的是索引 0、1、2,不包含 3。这是大多数人的肌肉记忆。

iloc 遵循这个规则:

代码语言:javascript
复制
df.iloc[0:3]  # 取位置 0、1、2,共 3 行

但 loc 不一样:

代码语言:javascript
复制
df.loc['a':'c']  # 取 'a'、'b'、'c',共 3 行,包含 'c'

loc 的切片是闭区间,左右都包含。这个设计其实有它的道理:标签是离散的、有意义的,你说“从 a 到 c”,自然应该把 c 也包括进去。而位置是连续的编号,Python 传统上就是左闭右开。

但问题在于,很多人写代码时不会刻意去想这个差异。尤其是当索引是整数的时候:

代码语言:javascript
复制
df = pd.DataFrame({'x': range(10)})
df.loc[0:5]   # 取 0、1、2、3、4、5,共 6 行
df.iloc[0:5]  # 取 0、1、2、3、4,共 5 行

一个取 6 行,一个取 5 行。如果你在循环里用 loc 做分批处理,每批的大小就会比你预期多一行。批次之间还会重叠,因为上一批的末尾和下一批的开头是同一行。

我见过有人在训练模型时用 loc 做数据切分,训练集和验证集有重叠样本,模型评估结果虚高,排查了很久才发现是切片包含右端点导致的。

布尔索引:loc 支持,iloc 不支持

loc 可以直接接受布尔 Series 或布尔数组:

代码语言:javascript
复制
df.loc[df['score'] > 80]

这行代码选出 score 大于 80 的所有行。loc 会把布尔序列对齐到索引上,True 的位置保留,False 的位置丢弃。

iloc 不接受布尔序列。df.iloc[df['score'] > 80] 会报错,因为它期望的是整数位置,不是布尔值。

想在 iloc 里实现同样的效果,得先算出满足条件的行位置:

代码语言:javascript
复制
import numpy as np
positions = np.where(df['score'] > 80)[0]
df.iloc[positions]

麻烦得多,而且容易出错。所以条件筛选基本都用 loc,这是它的主场。

整数索引的歧义

loc 和 iloc 最危险的场景,是索引本身就是整数,而且不是从 0 开始的连续整数。

代码语言:javascript
复制
df = pd.DataFrame({'x': [10, 20, 30]}, index=[1, 2, 3])

df.loc[1] 取的是索引为 1 的那一行,也就是 x=10。df.iloc[1] 取的是位置为 1 的那一行,也就是 x=20。

同样写 [1],一个拿第一行,一个拿第二行。如果代码里混用了 loc 和 iloc,或者从别处复制了一段代码但没改定位方式,结果就会错位。

更隐蔽的是 df[1] 这种写法。方括号直接索引在 pandas 里行为很复杂:对列名来说,它取列;对切片来说,它取行;对整数来说,它可能按位置也可能按标签,取决于索引类型。这种模糊性正是 pandas 官方建议永远明确使用 loc 或 iloc 的原因。

赋值时的坑

loc 和 iloc 在读取时的差异已经够多了,赋值时更危险。

用 loc 赋值:

代码语言:javascript
复制
df.loc[df['score'] > 80, 'grade'] = 'A'

这是标准做法,安全、清晰。

用 iloc 赋值:

代码语言:javascript
复制
df.iloc[0:3, 1] = 0

把位置 0 到 2 的行的第 1 列设为 0。这也有效。

但如果链式使用,比如 df['score'].iloc[0] = 100,pandas 会抛 SettingWithCopyWarning,因为 df['score'] 返回的是一个副本还是一个视图是不确定的,修改可能不会反映到原 DataFrame 上。

loc 在链式赋值时同样有这个问题:df['score'].loc[0] = 100 也会警告。

正确的做法始终是在一次 loc 或 iloc 调用里完成行列定位:

代码语言:javascript
复制
df.loc[0, 'score'] = 100
df.iloc[0, 1] = 100

不要先取列再取行,也不要先取行再取列。

一个真实场景

我之前处理一份订单表,索引是订单号,是一串不连续的整数,比如 1001、1003、1007、1012……。我需要做两件事:一是取出前 50 条记录做快速检查,二是把金额大于 1000 的订单标记为高价值。

第一件事我写了:

代码语言:javascript
复制
sample = df.loc[0:50]

结果取出来 51 条,而且因为索引不是从 0 开始的,loc[0:50] 实际匹配的是标签在 0 到 50 之间的行——但这个范围里可能一条都没有,因为订单号是从 1001 开始的。最后返回的是一个空 DataFrame。

正确的写法是:

代码语言:javascript
复制
sample = df.iloc[0:50]

按位置取前 50 条,和索引标签无关。

第二件事我写了:

代码语言:javascript
复制
df.loc[df['amount'] > 1000, 'level'] = 'high'

这个没问题,因为 loc 天然支持布尔索引。

但如果我错误地写成 df.iloc[df['amount'] > 1000, ...],就会直接报错。iloc 不接受布尔序列。

这两个操作放在一起,恰好展示了 loc 和 iloc 各自最适合的场景:按条件筛选用 loc,按位置取数用 iloc。

性能差异

loc 和 iloc 在性能上也有区别,虽然大多数时候可以忽略,但在大数据集上值得注意。

iloc 按位置定位,底层直接操作 numpy 数组的整数索引,速度最快。

loc 按标签定位,需要先在索引里查找标签对应的位置,多了一步哈希查找或二分查找。对于普通规模的 DataFrame,这个开销可以忽略。但对于几百万行的数据,频繁用 loc 做逐行访问会比 iloc 慢不少。

不过这个差异不应该成为选择依据。正确性永远优先于性能。该用 loc 的地方用 loc,该用 iloc 的地方用 iloc。如果确实遇到性能瓶颈,再考虑把索引转成位置、用 iloc 批量操作,或者直接上 numpy。

一张表说清楚

维度

loc

iloc

定位依据

索引标签

整数位置

切片右端点

包含

不包含

布尔索引

支持

不支持

接受类型

标签、标签列表、切片、布尔序列

整数、整数列表、整数切片

索引为整数时

按标签解释

按位置解释

赋值

支持

支持

越界行为

KeyError

IndexError

怎么避免踩坑

第一,永远明确写 loc 或 iloc,不要用 df[...] 做模糊索引。方括号的直接索引在 pandas 里行为太多变,读代码的人无法一眼判断是按列还是按行、是按标签还是按位置。

第二,索引不是默认整数时,优先用 iloc 做位置操作。只要你想表达的是“第几行”“前几行”“某几行”,就用 iloc。只要你想表达的是“索引为某个值的行”,就用 loc。

第三,切片时想一想右端点。用 loc 切片,问自己是否真的想包含右端点。用 iloc 切片,确认自己用的是左闭右开的习惯。

第四,条件筛选只用 loc。iloc 不支持布尔序列,硬要绕路只会让代码更难读。

第五,注意索引类型。如果索引是整数,务必清楚 loc 和 iloc 会给出不同结果。可以在代码里加注释说明索引的含义,或者干脆用 reset_index() 把索引变成默认的 0、1、2、3,消除歧义。

最后

loc 和 iloc 的区别,本质上是标签语义和位置语义的区别。一个回答“哪个标签”,一个回答“第几个”。

它们长得像,但规则不同:切片右端点一个包含一个不包含,布尔索引一个支持一个不支持,整数索引下同一个数字指向不同的行。

这些差异不会让代码报错,只会让结果偏掉。而数据处理的 bug,最难查的就是这种“不报错但不对”的问题。

写 pandas 的时候,每次敲下 loc 或 iloc,花一秒钟想一下:我要的是标签还是位置?这一秒钟,能省下后面几个小时的排查。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 最根本的区别:标签 vs 位置
  • 切片行为:loc 包含右端点,iloc 不包含
  • 布尔索引:loc 支持,iloc 不支持
  • 整数索引的歧义
  • 赋值时的坑
  • 一个真实场景
  • 性能差异
  • 一张表说清楚
  • 怎么避免踩坑
  • 最后
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档