Python: Pandas系列 - 为什么使用loc?

123

为什么我们在pandas数据框中使用'loc'?似乎以下代码使用或不使用'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 explanation?提到了:

你可以只使用数据框的getitem来检索列:

*

df['time']    # equivalent to df.loc[:, 'time']

虽然它解释了许多loc的特性,但它并没有说明为什么我们要使用loc,我的具体问题是:“为什么不完全省略loc”?我已经接受了下面提供的非常详细的答案。

此外,那篇其他帖子的答案(我认为不是答案)在讨论中非常难以找到,任何搜索我正在寻找的信息的人都会很难找到该答案,并且对于我的问题所提供的答案更好。


可能是[pandas iloc vs ix vs loc explanation?]的重复问题(https://dev59.com/31wZ5IYBdhLWcg3wVO5U)。 - LinkBerest
13
@JGreenwell - 不太对,他们在讨论.loc,.iloc和.ix之间的区别,但我只是想知道为什么要使用.loc,而不是为什么要用它替代.iloc或.ix,我对iloc或ix不感兴趣,我首先想了解loc以及为什么我们使用它,而不是什么都不用替代它。 - Runner Bean
请看 ajcr 的回答 的结尾,其中包括了对 .loc.iloc.ix 使用的一般性说明:如果你只是使用标签或者只是使用整数位置进行索引,请坚持使用 loc 或 iloc 来避免出现意外结果。 - LinkBerest
2
基本上,当你不指定索引技术时,pandas 会采用回退和最佳猜测。因此,它会依次使用它们中的每一个。对于 DataFrame,默认是在列上使用 .loc。对于 Series,默认是在行上使用 .loc,因为没有列。 - Kartik
5
JGreenwell 和 Kartik - 我不理解。我不感兴趣,我再次强调,我不对与 .iloc 有关的任何事情感兴趣,请假装 .iloc 不存在,假装 .ix 不存在。我只想知道为什么我应该使用 .loc,而不是像我问题中的代码一样完全省略它。 - Runner Bean
显示剩余4条评论
3个回答

106
  • 显式优于隐式。

    df [boolean_mask] 会选择 boolean_mask 为 True 的行,但当 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

相比之下,即使 df2 的结构与上面的 df1 几乎相同,以下内容也不会引发 ValueError 异常:

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正在选择行。相比之下,如果不了解有关indexerdf的细节,就不清楚df[indexer]是否会选择行或列(或引发ValueError)。

df.loc[row_indexer, column_index]可以选择行列。根据indexer中的值类型以及df所具有的列值类型(它们是否是布尔值等),df[indexer]只能选择行列。

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

4
为什么你没有在列名周围加上引号?df[['True']]不会正常工作吗? - Mr. Lance E Sloan
2
@LS 在这个例子中,df[['True']] 似乎不能正常工作。列名不是字符串,而是布尔对象。看起来 Pandas 不要求列名为字符串(与例如 R 不同,其中 names(df) 是字符,而 [[]] 强制将输入转换为字符)。 - Richard DiSalvo
10
我认为最后一点是最重要的,应该将其置于首位,但这实际上是非常危险的不同行为。 - kilgoretrout
7
和切片相比,loc[1:2] 是基于索引进行操作的,而 df2[1:2] 则是基于行在数据框中的顺序进行操作的,与索引无关。也就是说,在上面的最后一个例子中,如果索引从 [3,4] 开始,loc[1:2] 将不返回任何内容,但 df2[1:2] 仍将返回第一个索引为3的行。 - NeverStopLearning
另一件事是 loc 始终会复制,而非 loc 方法则不需要:请参见 https://dev59.com/8WIj5IYBdhLWcg3wHhlX, 因此,非 loc 方法在切片数据框时需要明确添加 .copy() 来处理 settingwithcopywarning。 - Richard DiSalvo
在最近的pandas版本中(我使用的是2.0.2),df.loc[[True]]也会失败。它会引发错误:IndexError: 布尔索引的长度错误:应为3而不是1 - AXO

18

多列"链式赋值"在使用和不使用.loc的性能考虑

让我通过对系统性能的考虑来补充已经非常好的答案。

问题本身包括对比两个代码片段使用和不使用.loc的系统性能(执行时间)的比较。这些代码示例的执行时间大致相同。但是,在某些其他代码示例中,使用和不使用.loc可能存在相当大的执行时间差异:例如,差异可能是几倍以上!

pandas数据帧操作的一个常见情况是我们需要创建一个新列,该新列派生自现有列的值。我们可以使用以下代码根据现有列的条件进行过滤,并为新列设置不同的值:

df[df['mark'] >= 50]['text_rating'] = 'Pass'

然而,这种“链式赋值”方式行不通,因为它可能会创建一个“副本”而不是“视图”,基于此“副本”的新列的赋值将不会更新原始数据框。

有两个可选方案:

    1. 我们可以使用 .loc,或者
    1. 以另一种方式编写代码,而不使用 .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的功能差异方面做得非常好。我在这里补充说明系统性能(执行时间)的差异。


5
除了已经提到的问题(使用 loc 选择行和列以及执行行和列选择的切片时,具有 True、False 为列名的问题),另一个重要的区别是可以使用 loc 来为特定的行和列分配值。如果您尝试使用布尔序列选择数据框的子集并尝试更改该子集选择的值,则可能会收到 SettingWithCopy 警告。
假设您想更改薪资大于60000的所有行的 "高级管理" 列。
这个操作:
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],但是我认为先将布尔条件存储在变量中更清晰。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接