如何使用布尔掩码对 pandas 数据框的分层列进行分配?

How to make assignment to hierarchical column of pandas dataframe using boolean mask?

我有一个这样的数据框:

import pandas as pd
df = pd.DataFrame({
    "time": [1, 2, 1, 2], 
    "site": ['a', 'a', 'b', 'b'], 
    "val1": [11, 12, 21, 22],
    "val2": [101, 102, 201, 202]
})
df.set_index(['time', 'site'], inplace=True, append=False)
df = df.unstack("site")
print df

     val1     val2     
site    a   b    a    b
time                   
1      11  21  101  201
2      12  22  102  202

我想更改一些匹配布尔过滤器的值。例如:

ix = df.val1 > 20
print ix

site      a     b
time             
1     False  True
2     False  True

自然而然的尝试是 df.val1[ix] = 50。这会执行预期的分配,但会发出警告:SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead.

所以现在我正在尝试使用 df.loc 实现类似的效果。但是我找不到任何方法将 df.loc 与这种布尔掩码一起使用。这似乎是因为我使用的是分层列,也就是说,如果我只有一组值 (val1),我不会遇到太多麻烦。不幸的是,在 docs.

中没有很好地涵盖分层列上的布尔过滤器分配。

我试过参考 df.loc[:,'val1',ix],但结果是 IndexingError: Too many indexers。我已经尝试了 df.loc[:,'val1'][ix] = 50,这有效但给出了 SettingWithCopyWarning.

我可以使用 df.val1 = df.val1.where(~ix, other=50) 但这似乎不直观、低效且不灵活(例如,它不能轻易扩展到现有值加 10)。

我应该使用其他一些索引方法根据布尔掩码将值分配给数据框的分层列吗?

编辑以扩展问题:

我没有意识到这会是一个问题,但实际上我想根据 val1val2 列中的值进行过滤并更改两组列中的值,像这样:

ix = (df.val1 > 20) | (df.val2 < 102)
df.val1[ix] = 50
df.val2[ix] = 150

是否有一种简单的索引方法可以做到这一点?使用 numpy ndarrays 非常容易,但使用 pandas 数据帧似乎要复杂得多。

您可以只使用列表来select您的列

idx = df[['val1']] > 20

idx
Out[39]: 
       val1      
site      a     b
time             
1     False  True
2     False  True

df[idx] = 50

df
Out[41]: 
     val1     val2     
site    a   b    a    b
time                   
1      11  50  101  201
2      12  50  102  202

当您首先 select 按列名从数据框中提取一个系列,然后尝试使用布尔掩码并为其赋值时,就会出现此问题。具体来说,带有布尔掩码的赋值在内部转换为 extracted_data.where(-mask, other=value, inplace=True),这会引发 SettingWithCopyWarning。

如果 pandas 可以保证这种操作会改变原始数据帧,而不是引发此警告,那就太好了。 (顺便说一下,如果链式操作的顺序颠倒了,df[ix]["val1"] = 500df[ix][["val1", "val2"]] = 500 不会发出警告但无法更新原始数据帧)。在解决此问题之前,有几个解决方法。

(1) 受@cncggvg 回答的启发:构造一个指定所有需要更新的元素的索引,而不是将两个索引操作链接在一起。

# create a partial index for the boolean operation
# note: this specifies the second-level columns it will act on, but not 
# the first level, since that was given unambiguously in the df[col] expression
ix = (df["val1"] > 20) | (df["val2"] < 102)
# build an index that specifies both the first and second-level columns
ix2 = pd.concat({"val1": ix}, axis=1)
# or, to do the same assignment on multiple first-level columns:
ix2 = pd.concat({"val1": ix, "val2": ix}, axis=1)
# do the assignment in one step, with no chaining
df[ix2] = 50
# or derive new values from current values
df[ix2] = df[ix2]+50

(2) 通过使用我自己的 .where(..., inplace=False):

避免使用隐式 series.where(..., inplace=True)
ix = (df["val1"] > 20) | (df["val2"] < 102)
df["val1"] = df["val1"].where(~ix, other=50)
df["val2"] = df["val2"].where(~ix, other=50)

# or to assign both columns at once:
# note: this should work with df[["val1", "val2"]] = ..., but pandas 0.18
# doesn't realize that that gives the same set of columns as cols.columns
cols = df[["val1", "val2"]]
df[cols.columns] = cols.where(~ix, other=50)
# or with a calculation:
df[cols.columns] = cols.where(~ix, other=cols+50)

这些都比我想要的更麻烦,所以我可能只是将我的数据框的相关部分复制到 numpy 数组中,然后从那里开始处理它们。根据 http://penandpants.com/2014/09/05/performance-of-pandas-series-vs-numpy-arrays/ .

的说法,这应该有更好的性能