如果您的 DataFrame 有一个简单的列索引,则没有区别。
例如,
In [8]: df = pd.DataFrame(np.arange(12).reshape(4,3), columns=list('ABC'))
In [9]: df.loc[:, ['A','B']]
Out[9]:
A B
0 0 1
1 3 4
2 6 7
3 9 10
In [10]: df.loc[:, ('A','B')]
Out[10]:
A B
0 0 1
1 3 4
2 6 7
3 9 10
但如果 DataFrame 具有 MultiIndex,则可能会有很大差异:
df = pd.DataFrame(np.random.randint(10, size=(5,4)),
columns=pd.MultiIndex.from_arrays([['foo']*2+['bar']*2,
list('ABAB')]),
index=pd.MultiIndex.from_arrays([['baz']*2+['qux']*3,
list('CDCDC')]))
# foo bar
# A B A B
# baz C 7 9 9 9
# D 7 5 5 4
# qux C 5 0 5 1
# D 1 7 7 4
# C 6 4 3 5
In [27]: df.loc[:, ('foo','B')]
Out[27]:
baz C 9
D 5
qux C 0
D 7
C 4
Name: (foo, B), dtype: int64
In [28]: df.loc[:, ['foo','B']]
KeyError: 'MultiIndex Slicing requires the index to be fully lexsorted tuple len (1), lexsort depth (0)'
KeyError 表示必须对 MultiIndex 进行词法排序。如果我们这样做,那么我们仍然会得到不同的结果:
In [29]: df.sortlevel(axis=1).loc[:, ('foo','B')]
Out[29]:
baz C 9
D 5
qux C 0
D 7
C 4
Name: (foo, B), dtype: int64
In [30]: df.sortlevel(axis=1).loc[:, ['foo','B']]
Out[30]:
foo
A B
baz C 7 9
D 7 5
qux C 5 0
D 1 7
C 6 4
这是为什么?df.sortlevel(axis=1).loc[:, ('foo','B')]
正在选择第一个列级别等于的列foo
,第二列级别是B
.
相比之下,df.sortlevel(axis=1).loc[:, ['foo','B']]
正在选择第一个列级别为的列foo
or B
。对于第一列级别,没有B
列,但有两个foo
列。
我认为 Pandas 的操作原理是,如果你使用df.loc[...]
as
an 表达,你应该假设df.loc
可能会返回副本或视图。 Pandas 文档没有指定任何您应该期望的规则。
然而,如果你做了一个任务形式的
df.loc[...] = value
那么你就可以相信 Pandas 会改变df
itself.
文档警告视图和副本之间的区别的原因是为了让您意识到使用以下形式的链分配的陷阱
df.loc[...][...] = value
在这里,Pandas 评价df.loc[...]
首先,它可以是视图或副本。现在如果它是一个副本,那么
df.loc[...][...] = value
正在更改某些部分的副本df
,因此对df
本身。雪上加霜的是,对副本的影响也会丢失,因为没有对副本的引用,因此在赋值语句完成后无法访问副本,并且(至少在 CPython 中)因此很快- 待垃圾收集。
我不知道有什么实用的万无一失的方法a priori的方法来确定是否df.loc[...]
将返回视图或副本。
然而,有一些经验法则可能有助于指导您的直觉(但请注意,我们在这里讨论的是实现细节,因此不能保证 Pandas 将来需要这样做):
- 如果生成的 NDFrame 不能表示为 NDFrame 的基本切片
底层 NumPy 数组,那么它可能是一个副本。因此,选择任意行或列将产生一个副本。对连续行和/或连续列(其可以表示为切片)的选择可以返回视图。
- 如果生成的 NDFrame 具有不同 dtypes 的列,则
df.loc
可能会再次返回一份副本。
然而,有一个简单的方法可以确定是否x = df.loc[..]
是一个视图事后:简单地看看是否改变了一个值x
影响df
。如果有,那就是一个视图,如果没有,x
是一个副本。