2016-04-01 4 views
3

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

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

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

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

Edited расширить вопрос:

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

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

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

+0

Возможно ли это, чтобы вы могли сгладить свои столбцы? – MaxU

ответ

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 
+0

Спасибо, это отличный ответ на вопрос, который я задал. К сожалению, я забыл упомянуть, что также хотел бы изменить соответствующие записи в столбце val2, что-то вроде 'ix = (df.val1> 20) | (df.val2> 200); df.val1 [ix] = 50; df.val2 [ix] = 150'. Любая идея, как это сделать? Это довольно просто со стандартными ndarrays numpy, но в пандах это кажется более сложным. –

0

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

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

Они являются более громоздкими, чем хотелось бы, чтобы я мог просто скопировать соответствующие разделы моего dataframe в numpy массивы, а затем работать над ними оттуда. В любом случае это должно иметь лучшую производительность, согласно http://penandpants.com/2014/09/05/performance-of-pandas-series-vs-numpy-arrays/.