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

Python: Pandas Series - Why use loc?

为什么我们对 pandas 数据帧使用 'loc'?似乎以下代码使用或不使用 loc 都以相同的速度编译 anr 运行

%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?

编辑: 这已被标记为重复问题。但是尽管 确实提到了 *

you can do column retrieval just by using the data frame's getitem:

*

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

它没有说我们为什么使用loc,虽然它确实解释了loc的很多特性,我的具体问题是'why not just omit loc altogether'?为此,我在下面接受了一个非常详细的答案。

另外 post 答案(我认为这不是答案)在讨论中非常隐蔽,任何搜索我正在寻找的东西的人都会发现很难找到信息和为我的问题提供的答案会更好。

  • 显式优于隐式。

    df[boolean_mask] selects 行,其中 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]] 到 select 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 是 selecting 行。相比之下,在不知道 indexerdf.[=43 的详细信息的情况下,不清楚 df[indexer] 是否会 select 行或列(或提高 ValueError) =]

  • df.loc[row_indexer, column_index] 可以 select 行 列。 df[indexer] 只能 select 行 列,具体取决于 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
    

除了已经说过的问题(在不使用 loc 的情况下将 True、False 作为列名的问题以及 select 具有 loc 的行和列的能力以及对行和列进行切片的能力 selections),另一个很大的区别是你可以使用 loc 为特定的行和列赋值。如果您尝试 select 使用布尔系列的数据框子集并尝试更改该子集的值 selection 您可能会收到 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],但我认为首先将布尔条件存储在变量中更清晰。

使用和不使用 .loc 的多列“链式分配”的性能考虑

考虑到系统性能,让我补充一下已经很好的答案。

问题本身包括对使用和不使用.loc 的 2 段代码的系统性能(执行时间)的比较。引用的代码示例的执行时间大致相同。但是,对于其他一些代码示例,使用和不使用 .loc 在执行时间上可能存在 相当大的差异:例如几倍甚至更多!

pandas 数据框操作的常见情况是我们需要创建一个从现有列的值派生的新列。我们可以使用下面的代码来过滤条件(基于现有列)并为新列设置不同的值:

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

但是,这种“链式赋值”不起作用,因为它可以创建“副本”而不是“视图”,并且基于此“副本”对新列的赋值不会更新原始数据框。

2 个可用选项:

    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 倍多!

关于“Chained Assignment”更详细的解释,可以参考另一篇相关的postHow to deal with SettingWithCopyWarning in pandas? and in particular the answer of cs95。 post 很好地解释了使用 .loc 的功能差异。我这里只是补充一下系统性能(执行时间)的差异。