对多级索引的 pandas.DataFrame 列应用函数

5

我有一个MultiIndex的pandas DataFrame,我想对其中的一列应用某个函数,并将结果赋值给该列。

In [1]:
    import numpy as np
    import pandas as pd
    cols = ['One', 'Two', 'Three', 'Four', 'Five']
    df = pd.DataFrame(np.array(list('ABCDEFGHIJKLMNO'), dtype='object').reshape(3,5), index = list('ABC'), columns=cols)
    df.to_hdf('/tmp/test.h5', 'df')
    df = pd.read_hdf('/tmp/test.h5', 'df')
    df
Out[1]:
         One     Two     Three  Four    Five
    A    A       B       C      D       E
    B    F       G       H      I       J
    C    K       L       M      N       O
    3 rows × 5 columns

In [2]:
    df.columns = pd.MultiIndex.from_arrays([list('UUULL'), ['One', 'Two', 'Three', 'Four', 'Five']])
    df['L']['Five'] = df['L']['Five'].apply(lambda x: x.lower())
    df
-c:2: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead 
Out[2]:
         U                      L
         One    Two     Three   Four    Five
    A    A      B       C       D       E
    B    F      G       H       I       J
    C    K      L       M       N       O
    3 rows × 5 columns

In [3]:
    df.columns = ['One', 'Two', 'Three', 'Four', 'Five']
    df    
Out[3]:
         One    Two     Three   Four    Five
    A    A      B       C       D       E
    B    F      G       H       I       J
    C    K      L       M       N       O
    3 rows × 5 columns

In [4]:
    df['Five'] = df['Five'].apply(lambda x: x.upper())
    df
Out[4]:
         One    Two     Three   Four    Five
    A    A      B       C       D       E
    B    F      G       H       I       J
    C    K      L       M       N       O
    3 rows × 5 columns

如您所见,该函数未应用于该列,我猜测是因为我收到了以下警告:

-c:2: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead

奇怪的是,这个错误有时候才会发生,我一直没能弄明白它为什么会发生或不发生。
我按照警告建议使用了 .loc 函数对数据框进行切片操作。
In [5]:
    df.columns = pd.MultiIndex.from_arrays([list('UUULL'), ['One', 'Two', 'Three', 'Four', 'Five']])
    df.loc[:,('L','Five')] = df.loc[:,('L','Five')].apply(lambda x: x.lower())
    df

Out[5]:
         U                      L
         One    Two     Three   Four    Five
    A    A      B       C       D       e
    B    F      G       H       I       j
    C    K      L       M       N       o
    3 rows × 5 columns

但我想了解为什么在进行类字典切片时会出现这种行为(例如,df['L']['Five']),而使用.loc切片则不会出现。请注意:数据框来自未多重索引的HDF文件,这可能是奇怪行为的原因吗?编辑:我正在使用Pandas v.0.13.1和NumPy v.1.8.0。
1个回答

5

df['L']['Five'] 选择第0级别为'L'的数据并返回一个DataFrame,然后选择'Five'这一列,返回该序列。

Dataframe([])的__getitem__访问器会尝试进行正确的操作并给出正确列。但是,这是链式索引,请参见此处

要访问多级索引,请使用元组表示法 ('a','b')和不含歧义的.loc,例如: df.loc[:,('a','b')],同时允许多轴索引(例如行和列)。

那么,当你执行链式索引和赋值时(例如df['L']['Five'] = value),为什么它不起作用呢?

df['L'] 返回的是单索引的DataFrame。接着,另一个Python操作df_with_L['Five']选择了由“Five”索引的序列。我用另一个变量来表示这一点。因为pandas将这些操作视为单独的事件(例如对__getitem__的单独调用),所以必须将它们视为线性运算,一个接一个地发生。

与此形成对比的是df.loc[:,('L','Five')],它将一个嵌套元组(:,('L','Five')) 传递给单个__getitem__调用。这使得pandas可以将其视为单个实体处理(并且因为可以直接索引到帧中,所以更快一些)。

为什么这很重要?由于链式索引是2次调用,因此由于切片的方式可能会返回数据的副本。因此,当进行设置时,实际上是设置了一个副本,而不是原始框架。Pandas无法弄清楚这一点,因为它们是2个独立的Python操作,彼此没有关联。

SettingWithCopy警告是检测这种情况的'启发式'方法(即它通常通过轻量级检查来捕获大多数情况)。真正理解这个问题则相当复杂。

.loc操作是单个Python操作,因此可以选择一个切片(仍然可能是副本),但允许pandas在修改后将该切片重新分配回帧中,从而设置您认为的值。

警告的原因是这样的。有时候当你切片一个数组时,你只会得到一个视图,这意味着你可以毫无问题地设置它。然而,即使是一个单一的数据类型数组,在特定的方式下进行切片也可能会生成一个副本。一个多数据类型的DataFrame(表示它包含浮点数和对象数据),几乎总是会生成一个副本。是否创建视图取决于数组的内存布局。
注意:这与数据源无关。

1
我真的非常喜欢这个解释... 我知道我们在文档中有一个类似的,但这个更好。我们能做一个 pd.merge(orig_doc, this_explanation, how='right') 吗? :) - Phillip Cloud
更新:http://pandas-docs.github.io/pandas-docs-travis/indexing.html#returning-a-view-versus-a-copy - Jeff

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