2016-04-01 15 views
3

Ich habe einen Datenrahmen wie folgt aus:Wie die Zuordnung zu einer hierarchischen Spalte von Pandas Datenrahmen mit Boolean Maske?

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 

Ich mag würde einige Werte, die einen Booleschen Filter entsprechen ändern. z.B .:

ix = df.val1 > 20 
print ix 

site  a  b 
time    
1  False True 
2  False True 

Eine natürliche Sache, zu versuchen df.val1[ix] = 50 wäre. Dies führt die erwartete Zuweisung durch, gibt jedoch eine Warnung: 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.

So jetzt versuche ich etwas ähnliches mit df.loc zu erreichen. Aber ich kann keine Möglichkeit finden, df.loc mit dieser Art von booleschen Maske zu verwenden. Dies scheint zu sein, weil ich hierarchische Spalten verwende, d. H. Ich habe keine großen Probleme, wenn ich nur einen Satz von Werten (val1) habe. Leider sind Zuordnungen mit booleschen Filtern für hierarchische Spalten in docs nicht sehr gut abgedeckt.

Ich habe versucht, bezogen auf df.loc[:,'val1',ix], aber das gibt IndexingError: Too many indexers. Ich habe versucht df.loc[:,'val1'][ix] = 50, und das funktioniert, aber gibt die SettingWithCopyWarning.

Ich kann df.val1 = df.val1.where(~ix, other=50) verwenden, aber das scheint nicht intuitiv, ineffizient und unflexibel (z. B. könnte es nicht einfach erweitert werden, 10 zu den vorhandenen Werten hinzuzufügen).

Gibt es eine andere Indexierungsmethode, die ich verwenden sollte, um Werte einer hierarchischen Spalte eines Datenrahmens basierend auf einer booleschen Maske zuzuordnen?

Ich wusste nicht, das wäre ein Problem, aber ich würde wirklich filtern wie basierend auf den Werten in den beiden val1 und val2 Spalten und Änderungswerte in beiden:

Edited die Frage erweitern Sätze von Spalten, etwa wie folgt:

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

gibt es eine einfache Indizierung Ansatz, dies zu tun? Es ist ziemlich einfach mit numpy ndarrays, aber scheint mit einem Pandas Datenrahmen viel kniffliger zu sein.

+0

wäre es eine Option für Sie, Ihre Spalten zu reduzieren? – MaxU

Antwort

3

Sie können nur eine Liste verwenden Sie Spalte auszuwählen

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

Danke, das ist eine großartige Antwort auf die Frage, die ich gestellt habe. Leider habe ich vergessen zu erwähnen, dass ich auch die entsprechenden Einträge in der Spalte val2 ändern möchte, etwa wie 'ix = (df.val1> 20) | (df.val2> 200); df.val1 [ix] = 50; df.val2 [ix] = 150'. Irgendeine Idee, wie man das macht? Es ist ziemlich einfach mit standard-numpy ndarrays, aber scheint in Pandas knifflig zu sein. –

0

Dieses Problem tritt auf, wenn Sie zum ersten Mal eine Serie von einem Datenrahmen von Spaltennamen auswählen und dann versuchen, eine boolean Maske zu verwenden und Werte zuweisen. Insbesondere wird die Zuweisung mit einer booleschen Maske intern in extracted_data.where (-mask, other = Wert, inplace = True) konvertiert, wodurch das SettingWithCopyWarning ausgelöst wird.

Es wäre sehr nett, wenn Pandas nur garantieren könnten, dass diese Art von Operation den ursprünglichen Datenrahmen ändert, anstatt diese Warnung zu erhöhen. (Und wenn die Reihenfolge der verketteten Operationen umgekehrt wird, geben df[ix]["val1"] = 500 oder df[ix][["val1", "val2"]] = 500 keine Warnung aus, aber aktualisieren den ursprünglichen Datenrahmen nicht). Bis dies gelöst ist, gibt es einige Problemumgehungen.

(1) Inspiriert von der Antwort von @cncggvg: Konstruieren Sie einen einzelnen Index, der alle Elemente angibt, die aktualisiert werden müssen, anstatt zwei Indexierungsvorgänge miteinander zu verketten.

# 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) Vermeiden Sie die impliziten series.where(..., inplace=True) mit meiner mithilfe von eigenen .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) 

Dies sind beide umständlicher als Ich mag würde, so dass ich kann nur kopieren Sie die entsprechenden Abschnitte meines Datenrahmen in numply arrays, dann arbeiten sie von dort aus. Das sollte laut http://penandpants.com/2014/09/05/performance-of-pandas-series-vs-numpy-arrays/ ohnehin eine bessere Performance haben.