理解pandas dataframe的索引

14

概述: 这个不起作用:

df[df.key==1]['D'] = 1
但这个可以:
df.D[df.key==1] = 1

为什么?

复现:

In [1]: import pandas as pd

In [2]: from numpy.random import randn

In [4]: df = pd.DataFrame(randn(6,3),columns=list('ABC'))

In [5]: df
Out[5]: 
          A         B         C
0  1.438161 -0.210454 -1.983704
1 -0.283780 -0.371773  0.017580
2  0.552564 -0.610548  0.257276
3  1.931332  0.649179 -1.349062
4  1.656010 -1.373263  1.333079
5  0.944862 -0.657849  1.526811

In [6]: df['D']=0.0

In [7]: df['key']=3*[1]+3*[2]

In [8]: df
Out[8]: 
          A         B         C  D  key
0  1.438161 -0.210454 -1.983704  0    1
1 -0.283780 -0.371773  0.017580  0    1
2  0.552564 -0.610548  0.257276  0    1
3  1.931332  0.649179 -1.349062  0    2
4  1.656010 -1.373263  1.333079  0    2
5  0.944862 -0.657849  1.526811  0    2

这样不行:

In [9]: df[df.key==1]['D'] = 1

In [10]: df
Out[10]: 
          A         B         C  D  key
0  1.438161 -0.210454 -1.983704  0    1
1 -0.283780 -0.371773  0.017580  0    1
2  0.552564 -0.610548  0.257276  0    1
3  1.931332  0.649179 -1.349062  0    2
4  1.656010 -1.373263  1.333079  0    2
5  0.944862 -0.657849  1.526811  0    2

但这个做法可以:

In [11]: df.D[df.key==1] = 3.4

In [12]: df
Out[12]: 
          A         B         C    D  key
0  1.438161 -0.210454 -1.983704  3.4    1
1 -0.283780 -0.371773  0.017580  3.4    1
2  0.552564 -0.610548  0.257276  3.4    1
3  1.931332  0.649179 -1.349062  0.0    2
4  1.656010 -1.373263  1.333079  0.0    2
5  0.944862 -0.657849  1.526811  0.0    2

笔记本链接

我的问题是:

为什么只有第二种方法可行?我似乎看不出选择/索引逻辑上的区别。

版本号为0.10.0

编辑:不应该再像这样做了。从版本0.11开始,有 .loc。请参见:http://pandas.pydata.org/pandas-docs/stable/indexing.html


正如答案中所说,这似乎是一个numpy问题:请参考这个问题了解类似的问题。我不确定这是否是视图与副本的问题。 - bmu
我现在明白了,这其实很清楚(而且很简单),就是视图和副本的区别。第一种方法只提供一个将被垃圾回收的副本。第二种方法提供了一个视图,因此设置是在原始数据框中完成的。(请参见下面Dougal的评论) - K.-Michael Aye
2个回答

17

pandas文档指出:

返回视图和返回副本的区别

关于何时返回数据视图的规则完全取决于NumPy。每当标签数组或布尔向量参与索引操作时,结果将是一个副本。使用单个标签/标量索引和切片(例如df.ix[3:6]或df.ix[:, 'A'])时,将返回视图。

在代码df[df.key==1]['D']中,首先进行布尔切片(导致Dataframe的复制),然后选择列['D']。

在代码df.D[df.key==1] = 3.4中,首先选择一个列,然后对结果Series进行布尔切片。

这似乎造成了差异,尽管我必须承认这有点反直觉。

编辑:Dougal已经确认了差异,详见他的评论:使用版本1时,在进行布尔切片时调用__getitem__方法会产生副本。对于版本2,只访问__setitem__方法 - 因此不返回副本而是执行赋值操作。


我一开始也是这么想的,但肯定还有其他的事情发生了。df[df.key==1] = 1000 实际上会将所有切片中的值都赋值为1000,所以它不可能是一个副本。我猜在 setattrsetitem 方法中发生了一些魔法。 - cxrodgers
1
但是,由于我在结果系列上进行了布尔切片,那也应该是一个副本,不是吗?那么为什么赋值会以这种方式工作呢? - K.-Michael Aye
看一下Dougal上面的评论。在版本1中,当布尔切片调用__getitem__方法时,会进行复制。对于版本2,只访问__setitem__方法 - 因此不返回副本,而只是分配。 - Thorsten Kranz
4
在第一种方式中,您首先使用__getitem__构建一个副本,然后在该副本上调用__setitem__,随后该副本立即被垃圾回收。在第二种方式中,您使用__getitem__构建一个视图,然后在该视图上调用__setitem__ - Danica

4
我相信你的第一种方式返回的是副本而不是视图,因此对其进行赋值不会改变原始数据。但我不确定为什么会发生这种情况。
似乎与选择行和列的顺序有关,而不是获取列的语法。以下两种方式都可行:
df.D[df.key == 1] = 1
df['D'][df.key == 1] = 1

这两个都不起作用:

df[df.key == 1]['D'] = 1
df[df.key == 1].D = 1

从这些证据来看,我会认为切片 df[df.key == 1] 返回的是一份副本。但事实并非如此!df[df.key == 1] = 0 实际上会更改原始数据,就像它是一个视图一样。
因此,我不确定。我的观感是,这种行为随着 pandas 的版本而改变了。我似乎记得 df.D 曾经返回一个副本,而 df ['D'] 曾经返回一个视图,但这似乎不再是真的了(pandas 0.10.0)。
如果您想要更完整的答案,您应该在 pystatsmodels 论坛上张贴问题:https://groups.google.com/forum/?fromgroups#!forum/pystatsmodels

3
正如Thorsten的答案所指出的那样,df[df.key == 1]实际上返回了一个副本。之所以df[df.key == 1] = 0修改了原始数据,是因为尽管语法有点误导人,但这两个操作并不相同;非赋值版本调用了__getitem__,而赋值版本调用了__setitem__。就像如果我们有l = [0, 1, 2],则l[1]返回整数1,但l[1] = 5会修改原始数据一样。 - Danica

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