对MultiIndex的pandas.DataFrame列应用函数

5 投票
1 回答
7639 浏览
提问于 2025-04-18 01:50

我有一个多重索引的 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.1NumPy v.1.8.0

1 个回答

5

df['L']['Five'] 这个操作是先选择数据框中第0级的值为'L'的部分,然后再选择其中的'Five'这一列,最后返回的是一个系列。

在数据框中,__getitem__ 这个访问器(就是用 [] 这个符号)会尽量做正确的选择,给你返回正确的列。不过,这种方式叫做链式索引,可以看看这里

如果你想访问多重索引,可以用元组的方式,比如 ('a','b').loc,这样就不会产生歧义,比如 df.loc[:,('a','b')]。而且,这样可以同时对多维度进行索引(比如同时选择行和列)。

那么,为什么链式索引和赋值操作不管用,比如 df['L']['Five'] = value 这样?

df['L'] 返回的是一个单层索引的数据框。接着,另一个操作 df_with_L['Five'] 选择了'Five'这一列。为了说明这一点,我用了另一个变量。因为 pandas 会把这些操作看作是独立的事件(比如分别调用 __getitem__),所以它必须把这些操作当作线性操作,一个接一个地执行。

对比一下 df.loc[:,('L','Five')],这个操作是把一个嵌套的元组 (:,('L','Five')) 传给 __getitem__ 的一次调用。这样 pandas 就可以把它当作一个整体来处理(顺便说一下,这样会快很多,因为它可以直接索引到数据框中)。

这有什么重要性呢?因为链式索引是两次调用,所以可能会有其中一次返回的是数据的副本,这和切片的方式有关。因此,当你进行赋值时,实际上是在设置一个副本,而不是原始的数据框。pandas 无法判断这一点,因为这两次操作是独立的。

SettingWithCopy 的警告是一种“启发式”方法,用来检测这种情况(意思是它能捕捉到大多数情况,但只是一个轻量级的检查)。真正搞清楚这一点是非常复杂的。

.loc 操作是一次完整的 python 操作,因此可以选择一个切片(虽然这仍然可能是副本),但允许 pandas 在修改后将这个切片重新赋值回数据框,从而按照你的想法设置值。

警告的原因就是这样。有时候,当你切片一个数组时,你会得到一个视图,这样你就可以随意设置了。然而,即使是一个单一的数据类型数组,在特定的切片方式下也可能生成一个副本。一个多数据类型的数据框(比如同时包含浮点数和对象数据)几乎总是会返回一个副本。是否创建视图取决于数组的内存布局。

注意:这与数据的来源无关。

撰写回答