Как сделать назначение иерархическому столбцу фрейма данных pandas, используя логическую маску?

У меня есть такой кадр данных:

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). К сожалению, назначения с логическими фильтрами для иерархических столбцов не очень хорошо описаны в документы.

Я пытался сослаться на df.loc[:,'val1',ix], но это дает IndexingError: Too many indexers. Я пробовал df.loc[:,'val1'][ix] = 50, и это работает, но дает SettingWithCopyWarning.

Я могу использовать df.val1 = df.val1.where(~ix, other=50), но это кажется неинтуитивным, неэффективным и негибким (например, его нельзя легко расширить, чтобы добавить 10 к существующим значениям).

Есть ли какой-то другой метод индексации, который я должен использовать для присвоения значений иерархическому столбцу фрейма данных на основе логической маски?

Отредактировано для расширения вопроса:

Я не осознавал, что это будет проблемой, но на самом деле я хотел бы фильтровать значения в столбцах val1 и val2 и изменять значения в обоих наборах столбцов, примерно так:

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

Есть ли простой подход к индексации, который может это сделать? Это довольно просто с numpy ndarrays, но с кадром данных pandas, кажется, все сложнее.


person Matthias Fripp    schedule 01.04.2016    source источник
comment
не могли бы вы сгладить свои столбцы?   -  person MaxU    schedule 01.04.2016


Ответы (2)


вы можете просто использовать список, чтобы выбрать столбец

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
person cncggvg    schedule 01.04.2016
comment
Спасибо, это отличный ответ на вопрос, который я задал. К сожалению, я забыл упомянуть, что я также хотел бы изменить соответствующие записи в столбце val2, что-то вроде ix = (df.val1 > 20) | (df.val2 > 200); df.val1[ix] = 50; df.val2[ix] = 150. Любая идея, как это сделать? Это довольно просто со стандартными numpy ndarrays, но с пандами все сложнее. - person Matthias Fripp; 01.04.2016

Эта проблема возникает, когда вы сначала выбираете серию из фрейма данных по имени столбца, а затем пытаетесь использовать логическую маску и присваивать ей значения. В частности, присваивание с логической маской преобразуется внутренне в Extracted_data.where(-mask, other=value, inplace=True), что приводит к возникновению SettingWithCopyWarning.

Было бы очень хорошо, если бы панды могли просто гарантировать, что такая операция изменит исходный фрейм данных, а не выдаст это предупреждение. (И кстати, если порядок связанных операций обратный, df[ix]["val1"] = 500 или df[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, а затем работать с ними оттуда. В любом случае, согласно http://penandpants.com/2014/09/05/performance-of-pandas-series-vs-numpy-arrays/ .

person Matthias Fripp    schedule 04.04.2016