如何使用布尔掩码分配到pandas数据帧的分层列?

时间:2016-04-01 08:03:51

标签: python pandas boolean-expression hierarchical

我有一个这样的数据框:

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

我想更改一些与布尔过滤器匹配的值。 e.g:

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数据帧似乎更加棘手。

2 个答案:

答案 0 :(得分:3)

您只需使用列表选择列

即可
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

答案 1 :(得分:0)

当您首先按列名从数据框中选择一个系列然后尝试使用布尔掩码并为其赋值时,会出现此问题。具体来说,使用布尔掩码的赋值在内部转换为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)通过使用我自己的series.where(..., inplace=True)

避免使用隐式.where(..., inplace=False)
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数组中,然后从那里开始处理它们。根据{{​​3}},这应该会有更好的表现。