【问题标题】:Python: Pandas Series - Why use loc?Python:熊猫系列 - 为什么使用 loc?
【发布时间】:2016-12-17 14:03:36
【问题描述】:

为什么我们对 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”?我已经接受了下面一个非常详细的答案。

另外,其他帖子的答案(我认为不是答案)在讨论中非常隐藏,任何搜索我正在寻找的东西的人都会发现很难找到信息,并且会更好地服务于为我的问题提供的答案。

【问题讨论】:

  • @JGreenwell - 不是真的,他们在讨论 .loc、.iloc 和 .ix 之间的区别,但在这里我只是问为什么要使用 .loc,而不是为什么要在 .iloc 或.ix,我对 iloc 或 ix 不感兴趣,我试图首先了解 loc 以及为什么我们使用它,而不是仅仅将它放在任何地方都没有。
  • 查看answer by ajcr 的末尾,其中包括.loc.iloc.ix: 或相关部分的一般用法,如果您只使用标签进行索引,或者仅使用整数位置进行索引,坚持使用 loc 或 iloc 以避免意外结果。"
  • 本质上,当您不指定索引技术时,pandas 会做出回退和最佳猜测。所以它会通过它们中的每一个。在 DataFrame 上,默认值是在列上使用 .loc。在 Series 上,默认在行上使用 .loc,因为没有列。
  • JGreenwell 和 Kartik - 我不明白。我不是,我再说一遍,我对 .iloc 不感兴趣,让我们假装 .iloc 不存在,假装 .ix 不存在。我只是想知道为什么我应该使用 .loc 而不是像我的问题中的代码那样把它放在一起。

标签: python pandas series pandas-loc


【解决方案1】:
  • 显式优于隐式。

    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 正在选择行。相反,尚不清楚df[indexer] 是否会在不知道indexerdf 的详细信息的情况下选择行或列(或引发ValueError)。

  • 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
    

【讨论】:

  • 为什么不在列名周围使用引号? df[['True']] 不能正常工作吗?
  • @LS df[['True']] 在示例中似乎无法正常工作。列名不是字符串,它们是布尔对象。看起来 pandas 不需要列名是字符串(不同于例如 R 其中 names(df) 是字符并且 [[]] 将输入强制转换为字符)
  • 我会说最后一点是最重要的并将其推到顶部,这实际上是危险的不同行为
  • 与切片的一个重要区别是 loc[1:2] 适用于索引,而 df2[1:2] 适用于简单的行顺序,而与索引无关。在上面的最后一个示例中,如果索引以 [3,4] 开头,则 loc[1:2] 不会返回任何内容,而 df2[1:2] 仍会返回索引为 3 的第一行。跨度>
【解决方案2】:

除了已经说过的(在不使用 loc 的情况下将 True、False 作为列名的问题,以及使用 loc 选择行和列的能力以及对行和列选择进行切片的能力),另一个很大的区别是您可以使用 loc 将值分配给特定的行和列。如果您尝试使用布尔系列选择数据框的子集并尝试更改该子集选择的值,您可能会收到 SettingWithCopy 警告。

假设您要更改所有工资大于 60000 的行的“上层管理”列。

这个:

mask = df["salary"] > 60000
df[mask]["upper management"] = True

抛出警告“正在尝试在数据帧的切片副本上设置一个值”并且无法正常工作,因为 df[mask] 创建了一个副本并尝试更新该副本的“上层管理”对原始df没有影响。

但这成功了:

mask = df["salary"] > 60000
df.loc[mask,"upper management"] = True

请注意,在这两种情况下,您都可以使用df[df["salary"] > 60000]df.loc[df["salary"] > 60000],但我认为首先将布尔条件存储在变量中更简洁。

【讨论】:

    【解决方案3】:

    使用和不使用 .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 倍以上!

    关于“链式赋值”更详细的解释,可以参考另一篇相关帖子How to deal with SettingWithCopyWarning in pandas?,尤其是the answer of cs95。这篇文章很好地解释了使用 .loc 的功能差异。我这里只是补充一下系统性能(执行时间)的差异。

    【讨论】:

      猜你喜欢
      • 2018-09-11
      • 2020-07-09
      • 2020-08-20
      • 1970-01-01
      • 2021-02-07
      • 2016-09-10
      • 2021-12-04
      • 2018-11-24
      • 1970-01-01
      相关资源
      最近更新 更多