为什么在 Pandas 中使用 loc?
- 2025-02-21 08:50:00
- admin 原创
- 94
问题描述:
为什么我们要使用loc
pandas 数据框?似乎以下代码无论是否使用,loc
编译和运行的速度都差不多:
%timeit df_user1 = df.loc[df.user_id=='5561']
100 loops, best of 3: 11.9 ms per loop
或者
%timeit df_user1_noloc = df[df.user_id=='5561']
100 loops, best of 3: 12 ms per loop
那么为什么要使用loc
?
编辑:这已被标记为重复问题。但尽管pandas iloc vs ix vs loc 解释?确实提到了这一点
您只需使用数据框即可进行列检索
__getitem__
:df['time'] # equivalent to df.loc[:, 'time']
它没有说明为什么我们使用loc
,尽管它确实解释了 的许多特性loc
。但我的具体问题是:为什么不干脆loc
完全省略?对于这个问题,我接受了下面的一个非常详细的答案。
另外,在上面的帖子中,答案(我不认为是一个答案)在讨论中隐藏得很好,任何搜索我所问问题的人都会发现很难找到信息,而这里提供的答案会对我的问题更有帮助。
解决方案 1:
明确优于隐含。
df[boolean_mask]
选择为 True 的行boolean_mask
,但是有一种特殊情况您可能不希望这样做:当df
具有布尔值的列标签时:
In [229]: df = pd.DataFrame({True:[1,2,3],False:[3,4,5]}); df
Out[229]:
False True
0 3 1
1 4 2
2 5 3
您可能想要使用df[[True]]
选择True
列。相反,它会引发ValueError
:
In [230]: df[[True]]
ValueError: Item wrong length 1 instead of 3.
与使用相比loc
:
In [231]: df.loc[[True]]
Out[231]:
False True
0 3 1
相反,下面的ValueError
虽然结构df2
与上面的几乎相同,但并不成立df1
:
In [258]: df2 = pd.DataFrame({'A':[1,2,3],'B':[3,4,5]}); df2
Out[258]:
A B
0 1 3
1 2 4
2 3 5
In [259]: df2[['B']]
Out[259]:
B
0 3
1 4
2 5
因此,df[boolean_mask]
并不总是表现得与 相同df.loc[boolean_mask]
。尽管这可以说是一个不太可能的用例,但我建议始终使用df.loc[boolean_mask]
而不是,因为的语法df[boolean_mask]
含义是明确的。使用 时,您会自动知道正在选择行。相比之下,如果不知道和 的详细信息,则不清楚 是否会选择行或列(或提高)。df.loc
`df.loc[indexer]df.loc
df[indexer]ValueError
indexer`df
df.loc[row_indexer, column_index]
可以选择行和列。df[indexer]
只能根据值的类型和列值的类型来选择行或列(再次,它们是布尔值吗?)。indexer
`df`
In [237]: df2.loc[[True,False,True], 'B']
Out[237]:
0 3
2 5
Name: B, dtype: int64
当切片被传递到 时,
df.loc
端点包含在范围内。当切片被传递到 时df[...]
,切片被解释为半开区间:
In [239]: df2.loc[1:2]
Out[239]:
A B
1 2 4
2 3 5
In [271]: df2[1:2]
Out[271]:
A B
1 2 4
解决方案 2:
使用和不使用 .loc 对多列“链式赋值”的性能考虑
让我从系统性能的角度来补充一下已经非常好的答案。
问题本身包括对使用和不使用 .loc 的两段代码的系统性能(执行时间)的比较。对于引用的代码示例,执行时间大致相同。但是,对于其他一些代码示例,使用和不使用 .loc 的执行时间可能会有相当大的差异:例如相差几倍甚至更多!
操作 pandas dataframe 的一个常见情况是我们需要创建一个从现有列的值派生的新列。我们可以使用以下代码来过滤条件(基于现有列)并为新列设置不同的值:
df[df['mark'] >= 50]['text_rating'] = 'Pass'
但是,这种“链式分配”不起作用,因为它可以创建“副本”而不是“视图”,并且基于此“副本”对新列的分配不会更新原始数据框。
有 2 个选项:
我们可以使用.loc,或者
不使用 .loc,以其他方式编码
第二种情况例如:
df['text_rating'][df['mark'] >= 50] = 'Pass'
通过将过滤放在最后(指定新列名之后),分配可以很好地与更新的原始数据框配合使用。
使用.loc的解决方案如下:
df.loc[df['mark'] >= 50, 'text_rating'] = 'Pass'
现在,让我们看看它们的执行时间:
不使用 .loc:
%%timeit
df['text_rating'][df['mark'] >= 50] = 'Pass'
2.01 ms ± 105 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
使用 .loc:
%%timeit
df.loc[df['mark'] >= 50, 'text_rating'] = 'Pass'
577 µs ± 5.13 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
我们可以看到,使用 .loc 后,执行时间提高了 3 倍以上!
有关“链式赋值”的更详细解释,您可以参考另一篇相关文章如何处理 pandas 中的 SettingWithCopyWarning?特别是cs95 的答案。该帖子很好地解释了使用 .loc 的功能差异。我只是在这里补充一下系统性能(执行时间)差异。
解决方案 3:
除了已经说过的内容(不使用 loc 时将 True、False 作为列名的问题,以及使用 loc 选择行和列的能力以及对行和列选择进行切片的能力),另一个很大的区别是您可以使用 loc 为特定的行和列分配值。如果您尝试使用布尔系列选择数据框的子集并尝试更改该子集选择的值,您可能会收到 SettingWithCopy 警告。
假设您要更改所有工资大于 60,000 的行的“高层管理人员”列。
这:
mask = df["salary"] > 60000
df[mask]["upper management"] = True
抛出警告“正尝试在 Dataframe 切片的副本上设置一个值”,并且不起作用,因为 df[mask] 创建了一个副本,而尝试更新该副本的“上层管理”对原始 df 没有影响。
但这成功了:
mask = df["salary"] > 60000
df.loc[mask,"upper management"] = True
请注意,在两种情况下您都可以执行df[df["salary"] > 60000]
或df.loc[df["salary"] > 60000]
,但我认为首先将布尔条件存储在变量中更清晰。
扫码咨询,免费领取项目管理大礼包!