loc基于标签选择数据,iloc基于整数位置;前者切片包含结束值,适用于有业务含义的索引,后者切片不包含结束值,适合按位置批量操作。

Pandas中的
loc和
iloc都是用于数据选择的强大工具,但它们的根本区别在于
loc是基于标签(label-based)进行选择的,而
iloc则是基于整数位置(integer-location based)进行选择。简单来说,如果你想通过行名和列名来选取数据,就用
loc;如果你想通过行号和列号(就像Python列表的索引一样)来选取,那就用
iloc。理解这一点,能帮你避开很多在使用Pandas时可能遇到的坑。
解决方案
要深入理解
loc和
iloc,最好的办法就是看它们在不同场景下的具体行为。它们就像是Pandas数据框的两把不同钥匙,分别对应不同的锁。
loc
:基于标签的选择
loc操作符主要通过行索引标签和列名来选取数据。它的语法是
df.loc[行标签, 列标签]。这里有几个关键点:
立即学习“Python免费学习笔记(深入)”;
-
行标签和列标签可以是单个值,也可以是列表。 比如,
df.loc['row_label_1', 'col_A']
或者df.loc[['row_label_1', 'row_label_2'], ['col_A', 'col_B']]
。 -
切片操作是包含结束值的。 这一点和Python原生的切片(不包含结束值)非常不同。如果你写
df.loc['start_label':'end_label']
,那么end_label
对应的那一行数据也会被包含在结果中。 -
布尔索引。
loc
非常适合结合布尔条件进行筛选,例如df.loc[df['col_A'] > 10]
会返回col_A
列值大于10的所有行。
举个例子:
import pandas as pd
import numpy as np
# 创建一个带有自定义索引和列名的数据框
data = {
'A': [10, 20, 30, 40, 50],
'B': [1, 2, 3, 4, 5],
'C': [100, 200, 300, 400, 500]
}
index_labels = ['r1', 'r2', 'r3', 'r4', 'r5']
df = pd.DataFrame(data, index=index_labels)
print("原始DataFrame:\n", df)
# 1. 选择单行单列
print("\nloc: 选择'r2'行和'B'列:\n", df.loc['r2', 'B']) # 输出:2
# 2. 选择多行多列
print("\nloc: 选择'r1'到'r3'行和'A'到'C'列:\n", df.loc['r1':'r3', 'A':'C'])
# 注意,'r3'和'C'都被包含在内
# 3. 使用布尔索引
print("\nloc: 选择'A'列大于20的所有行:\n", df.loc[df['A'] > 20])iloc
:基于整数位置的选择
iloc操作符则完全依赖于数据的整数位置,和Python列表的索引方式一模一样。它的语法是
df.iloc[行位置, 列位置]。
- 行位置和列位置都是从0开始的整数。 负数索引也有效,例如-1表示最后一行或最后一列。
-
切片操作是不包含结束值的。 这和Python原生的切片行为一致。
df.iloc[0:3]
会选择第0、1、2行,不包含第3行。 -
不支持布尔索引。
iloc
不能直接接受布尔数组作为索引,因为它只认整数位置。
继续上面的例子:
# 1. 选择单行单列
print("\niloc: 选择第1行(索引为0)和第2列(索引为1):\n", df.iloc[0, 1]) # 输出:10 (df.iloc[0,1] 对应 'r1', 'B')
# 2. 选择多行多列
print("\niloc: 选择第0到2行(不包含第3行)和第0到1列(不包含第2列):\n", df.iloc[0:3, 0:2])
# 对应 'r1', 'r2', 'r3' 行 和 'A', 'B' 列
# 3. 使用负数索引
print("\niloc: 选择最后一行和最后一列:\n", df.iloc[-1, -1]) # 输出:500我个人觉得,当你拿到一个新数据集,想快速预览前几行或者某个特定位置的数据时,
iloc会更顺手。但如果你在做报表或者处理有明确业务含义的列名和行索引的数据,
loc的表达力会强得多,也更不容易出错。
为什么loc
和iloc
在处理非默认整数索引时表现不同?
这确实是很多初学者会感到困惑的地方,我自己也曾在这里栽过跟头。问题核心在于,当你的DataFrame索引本身就是整数时,比如
0, 1, 2, ...,
loc和
iloc看起来似乎能达到同样的效果,但这只是表象。一旦你的索引不是默认的
0, 1, 2, ...,或者你故意将索引设置为非连续的整数,甚至重复的整数,它们的行为差异就会立刻显现出来。
loc始终将你传入的数字视为标签。如果你的DataFrame索引是
[10, 20, 30],那么
df.loc[20]会去寻找标签为
20的那一行。而
iloc则始终将你传入的数字视为位置。
df.iloc[2]会去寻找DataFrame中的第3行(因为是从0开始计数)。
来看一个具体的例子,这能更好地说明问题:
data_custom_index = {
'Value': [100, 200, 300, 400]
}
custom_index = [1, 3, 5, 7] # 自定义整数索引
df_custom = pd.DataFrame(data_custom_index, index=custom_index)
print("带有自定义整数索引的DataFrame:\n", df_custom)
# 尝试用loc和iloc选择
print("\ndf_custom.loc[3]:\n", df_custom.loc[3]) # 选择标签为3的行
# print(df_custom.loc[2]) # 这一行会报错,因为没有标签为2的行
print("\ndf_custom.iloc[1]:\n", df_custom.iloc[1]) # 选择位置为1(即第二行)的行
# 看看它们的不同
print("\ndf_custom.loc[3]['Value']:", df_custom.loc[3]['Value']) # 输出 200
print("df_custom.iloc[1]['Value']:", df_custom.iloc[1]['Value']) # 输出 200
# 结果一样是因为碰巧了,但它们的逻辑完全不同
# 如果我们想取第0行的值(标签为1)
print("\ndf_custom.loc[1]['Value']:", df_custom.loc[1]['Value']) # 输出 100
print("df_custom.iloc[0]['Value']:", df_custom.iloc[0]['Value']) # 输出 100
# 重点来了:
# 如果我想要取标签为 5 的那一行,我用 loc[5]
print("\nloc选择标签为5的行:\n", df_custom.loc[5])
# 如果我想要取第 2 个位置(从0开始计数,也就是第三行)的那一行,我用 iloc[2]
print("\niloc选择位置为2的行:\n", df_custom.iloc[2])
# 它们在数值上是等价的,但概念上完全不同。
# loc 找的是索引值等于 5 的那一行,而 iloc 找的是 DataFrame 内部存储顺序上的第三行。这种区别在数据被重新排序后尤为明显。如果你的DataFrame被排序了,
iloc仍然会按照新的物理顺序来取数据,而
loc则会根据标签找到它最初对应的那一行,无论它现在在物理上处于哪个位置。在我看来,这是理解两者最核心的钥匙。
在实际数据分析中,我应该如何选择使用loc
还是iloc
?
选择
loc还是
iloc,真的取决于你当前的需求和数据的特性。这不像是一个非此即彼的硬性规定,更像是一种策略选择。
-
当你的索引有明确的业务含义时,坚决使用
loc
。 比如,你的DataFrame索引是日期、用户ID、产品SKU等。使用loc
能让你的代码更具可读性,也更健壮。即使数据顺序被打乱,loc
也能准确地找到你想要的数据,因为它依赖的是标签而不是物理位置。想象一下,你有一个按日期索引的销售数据,你想查看2023年10月15日的数据,写df.loc['2023-10-15']
比去算它是第几行要直观和安全得多。 -
当你需要基于位置进行批量操作时,
iloc
是首选。 比如,你只想处理前10行数据,或者去除最后两列。这时候,iloc
的简洁性就体现出来了。df.iloc[0:10]
或者df.iloc[:, :-2]
这种写法,既清晰又高效。在进行数据清洗或特征工程时,经常需要这种基于位置的批量处理。 -
进行布尔索引筛选时,通常与
loc
结合使用。 虽然布尔数组本身可以直接作为索引,但当你想在筛选的同时也指定列时,loc
的优势就出来了。例如,df.loc[df['Sales'] > 1000, ['Product', 'Price']]
这种组合非常强大,它能帮你精确地定位到满足特定条件的行和特定的列。 -
如果你需要对数据进行迭代,或者在循环中按顺序处理数据,
iloc
可能更合适。 比如,你可能需要遍历DataFrame的每一行,或者每隔N行取一次数据。在这种场景下,基于整数位置的iloc
会让你写出更简洁的循环逻辑。
我个人的经验是,在大部分业务分析场景下,我会倾向于使用
loc。因为数据往往带有丰富的语义信息,利用这些标签能让代码更“说人话”,也更不容易因数据结构变化而失效。只有在确实需要操作数据物理位置,或者进行一些通用性的数据切片时,我才会转向
iloc。两者结合使用,才是Pandas数据选择的真正艺术。
使用loc
和iloc
时有哪些常见的陷阱和最佳实践?
即便你已经理解了
loc和
iloc的基本区别,在使用过程中还是有一些“坑”需要注意,以及一些能让你的代码更健壮、更高效的最佳实践。
常见陷阱:
-
切片行为的差异: 这是最常见的陷阱之一。
loc
的切片是包含结束标签的。df.loc['A':'C']
会包含'A'、'B'、'C'三行(如果它们存在)。iloc
的切片是不包含结束位置的。df.iloc[0:3]
会包含第0、1、2行,不包含第3行。 这种不一致性经常让人犯错,尤其是在从Python列表操作习惯过渡到Pandas时。
-
SettingWithCopyWarning
: 这是一个让无数Pandas用户头疼的问题。当你通过链式索引(例如df['col_A'][df['col_B'] > 10] = value
)来修改DataFrame时,Pandas可能会发出这个警告。它告诉你,你可能正在修改一个副本,而不是原始DataFrame的视图,导致你的修改没有实际反映到原始数据上。-
解决方案: 始终使用
loc
或iloc
进行链式赋值操作,并确保它们是单次操作。例如,df.loc[df['col_B'] > 10, 'col_A'] = value
。这能明确告诉Pandas你想要修改原始DataFrame的特定部分。如果实在需要修改一个副本,就明确地使用.copy()
。
-
解决方案: 始终使用
整数索引的模糊性: 当DataFrame的索引恰好是
0, 1, 2, ...
这样的整数时,df.loc[0]
和df.iloc[0]
都会返回第一行。这会给人一种错觉,认为它们是等价的。然而,一旦你改变了索引,或者索引不再是连续的整数,这种等价性就会立刻消失。我见过太多人因此而写出在特定数据集上能跑,换个数据集就崩溃的代码。单元素选择返回Series: 当你使用
loc
或iloc
选择单行或单列时,Pandas默认会返回一个Series。如果你期望得到一个DataFrame,可能需要进行额外的处理,例如使用df.loc[['label']]
(注意双层列表)来确保返回的是一个DataFrame。
最佳实践:
优先使用
loc
处理带有业务含义的索引和列名。 这不仅能提高代码的可读性,还能增加代码的健壮性。你的代码会更清楚地表达“我要选择销售额大于1000的客户的姓名和地址”,而不是“我要选择满足某个条件的第0列和第2列”。明确指定行和列。 即使你只想选择所有行或所有列,也建议明确使用
:
。例如,df.loc[:, 'col_A']
比df['col_A']
更明确地表达了你是在使用loc
的行/列选择语法。避免链式索引进行赋值。 这一点和
SettingWithCopyWarning
紧密相关。总是尝试将选择和赋值操作合并到一次loc
或iloc
调用中。理解你的索引。 在处理数据之前,花点时间检查DataFrame的索引类型和内容。
df.index
和df.columns
是你最好的朋友。了解你的索引是标签还是位置,能帮你避免很多不必要的错误。在需要时使用
.copy()
。 如果你确实需要创建一个DataFrame的独立副本进行操作,而不是修改原始数据,请务必使用.copy()
。例如,df_copy = df.loc[df['col_A'] > 10].copy()
。这能明确你的意图,并避免潜在的副作用。
通过遵循这些原则,你不仅能更有效地利用
loc和
iloc,还能写出更清晰、更可靠的Pandas代码,这在复杂的数据分析项目中是至关重要的。











