为什么我们使用' loc'对于pandas数据帧?似乎以下代码使用或不使用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?
修改:这已被标记为重复的问题。但是虽然pandas iloc vs ix vs loc explanation?确实提到了*
您只需使用数据框即可进行列检索 的的GetItem :
*
df['time'] # equivalent to df.loc[:, 'time']
它没有说明为什么我们使用loc,虽然它确实解释了loc的许多功能,但我的具体问题是“为什么不完全省略loc”'?我已经在下面接受了一个非常详细的答案。
此外,其他帖子的答案(我认为不是答案)在讨论中非常隐蔽,任何寻找我正在寻找的人都会发现很难找到信息并且会更好地服务于答案提供给我的问题。
答案 0 :(得分:43)
明确比隐含更好。
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
相反,即使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
答案 1 :(得分:5)
让我在考虑系统性能的情况下补充已经非常好的答案。
问题本身包括对使用和不使用 .loc 的 2 段代码的系统性能(执行时间)的比较。引用的代码示例的执行时间大致相同。但是,对于其他一些代码示例,使用和不使用 .loc 的执行时间可能会有很大差异:例如数倍甚至更多!
pandas 数据框操作的一个常见情况是我们需要创建一个从现有列的值派生的新列。我们可以使用下面的代码来过滤条件(基于现有列)并为新列设置不同的值:
df[df['mark'] >= 50]['text_rating'] = 'Pass'
但是,这种“链式分配”不起作用,因为它可以创建“副本”而不是“视图”,并且基于此“副本”对新列的分配不会更新原始数据框。
>2 个可用选项:
第二种情况,例如:
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 的功能差异。我这里只是补充一下系统性能(执行时间)差异。
答案 2 :(得分:2)
除了已经说过的(使用 True、False 作为列名而不使用 loc 以及使用 loc 选择行和列的能力以及对行和列选择进行切片的能力的问题)之外,另一个很大的区别是您可以使用 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]
,但我认为首先将布尔条件存储在变量中更清晰。