2016-08-02 23 views
3

Ich möchte eine Gruppe per Operation auf einen Pandas DataFrame anwenden, ohne eine Aggregation durchzuführen. Stattdessen möchte ich nur, dass die hierarchische Struktur im MultiIndex widergespiegelt wird.Pandas DataFrame-Gruppierung zum Generieren eines numerischen Multiindex

import pandas as pd 

def multi_index_group_by(df, columns): 
    # TODO: How to write this? (Hard-coded to give the desired result for the example.) 
    if columns == ["b"]: 
     df.index = pd.MultiIndex(levels=[[0,1],[0,1,2]], labels=[[0,1,0,1,0],[0,0,1,1,2]]) 
     return df 
    if columns == ["c"]: 
     df.index = pd.MultiIndex(levels=[[0,1],[0,1],[0,1]], labels=[[0,1,0,1,0],[0,0,0,1,1],[0,0,1,0,0]]) 
     return df 

if __name__ == '__main__': 
    df = pd.DataFrame({ 
     "a": [0,1,2,3,4], 
     "b": ["b0","b1","b0","b1","b0"], 
     "c": ["c0","c0","c0","c1","c1"], 
    }) 
    print(df.index.values) # [0,1,2,3,4] 


    # Add level of grouping 
    df = multi_index_group_by(df, ["b"]) 
    print(df.index.values) # [(0, 0) (1, 0) (0, 1) (1, 1) (0, 2)] 

    # Examples 
    print(df.loc[0]) # Group 0 
    print(df.loc[1,1]) # Group 1, Item 1 


    # Add level of grouping 
    df = multi_index_group_by(df, ["c"]) 
    print(df.index.values) # [(0, 0, 0) (1, 0, 0) (0, 0, 1) (1, 1, 0) (0, 1, 0)] 

    # Examples 
    print(df.loc[0]) # Group 0 
    print(df.loc[0,0]) # Group 0, Sub-Group 0 
    print(df.loc[0,0,1]) # Group 0, Sub-Group 0, Item 1 

Was wäre der beste Weg multi_index_group_by zu implementieren? Die folgenden fast funktioniert, aber der resultierende Index ist nicht numerisch:

index_columns = [] 
# Add level of grouping 
index_columns += ["b"] 
print(df.set_index(index_columns, drop=False)) 
# Add level of grouping 
index_columns += ["c"] 
print(df.set_index(index_columns, drop=False)) 

Edit: Um zu klären, in dem Beispiel, sollte die endgültige Indizierung äquivalent zu:

[ 
    [ #b0 
     [ #c0 
      {"a": 0, "b": "b0", "c": "c0"}, 
      {"a": 2, "b": "b0", "c": "c0"}, 
     ], 
     [ #c1 
      {"a": 4, "b": "b0", "c": "c1"}, 
     ] 
    ], 
    [ #b1 
     [ #c0 
      {"a": 1, "b": "b1", "c": "c0"}, 
     ], 
     [ #c1 
      {"a": 3, "b": "b1", "c": "c1"}, 
     ] 
    ] 
] 

Edit: Hier ist das beste, was ich bisher habe:

def autoincrement(value=0): 
    def _autoincrement(*args, **kwargs): 
     nonlocal value 
     result = value 
     value += 1 
     return result 
    return _autoincrement 

def swap_levels(df, i, j): 
    order = list(range(len(df.index.levels))) 
    order[i], order[j] = order[j], order[i] 
    return df.reorder_levels(order) 

def multi_index_group_by(df, columns): 
    new_index = df.groupby(columns)[columns[0]].aggregate(autoincrement()) 

    result = df.join(new_index.rename("_new_index"), on=columns) 
    result.set_index('_new_index', append=True, drop=True, inplace=True) 
    result.index.name = None 
    result = swap_levels(result, -2, -1) 
    return result 

Es gibt das korrekte Ergebnis, mit Ausnahme des letzten leve l, das ist unverändert. Ich habe immer noch das Gefühl, dass es noch einiges zu verbessern gibt.

+0

Was die Bedeutung Ihrer fest einprogrammiert ist Multiindex? – desiato

+0

@desiato Es ist ein stetig wachsender nd-Index (siehe Beispiele). Zum Beispiel (0, 1, 2, ...) bedeutet: Gruppe 0, Untergruppe 1, Untergruppe 2 usw. – kloffy

+0

Im Wesentlichen möchte ich in der Lage sein, Zeilen auf dieselbe Weise zu adressieren, in die Sie indizieren würden eine verschachtelte Liste – kloffy

Antwort

2

, wenn Sie die sklearn Paket bereit zu verwenden sind, könnten Sie die Verwendung LabelEncoder:

from sklearn.preprocessing import LabelEncoder 
le = LabelEncoder() 

def multi_index_group_by(df, columns): 
    df.index = pd.MultiIndex.from_tuples(zip(*[ le.fit_transform(df[col]) for col in columns ])) 
    return df 

Es kodiert für Etiketten jeder Spalte mit einem Wert zwischen 0 und n_klassen-1

aufrufen

multi_index_group_by(['b','c']) 

gibt Ihnen

 a b c 
0 0 0 b0 c0 
1 0 1 b1 c0 
0 0 2 b0 c0 
1 1 3 b1 c1 
0 1 4 b0 c1 
+0

Wow, ja, das scheint ziemlich nah an dem zu sein, mit dem ich gelandet bin (siehe die letzte Bearbeitung meiner Frage). Nicht sicher, ob es die Abhängigkeit von sklearn wert ist, aber interessanter Vorschlag, danke! – kloffy

+0

Ich werde diese Antwort akzeptieren, da es meistens macht was ich will. Wenn jemand eine Alternative ohne Abhängigkeit von sklearn benötigt, sehen Sie sich die Änderungen meiner Frage an. – kloffy

1

Dieser Code macht, was Sie wollen:

index_columns = [] 
replace_values = {} 

index_columns += ["b"] 
replace_values.update({'b0':0, 'b1':1}) 

df[['idx_{}'.format(i) for i in index_columns]] = df[index_columns].replace(replace_values) 
print(df.set_index(['idx_{}'.format(i) for i in index_columns], drop=True)) 

index_columns += ["c"] 
replace_values.update({'c0':0, 'c1':1}) 

df[['idx_{}'.format(i) for i in index_columns]] = df[index_columns].replace(replace_values) 
print(df.set_index(['idx_{}'.format(i) for i in index_columns], drop=True)) 

# If you want the 3rd ('c') level MultiIndex: 
df['d'] = [0,0,1,0,0] 
print(df.set_index(['idx_{}'.format(i) for i in index_columns] + ['d'], drop=True)) 
+0

Ja, ich spielte mit etwas ähnlichem, aber es wäre schön, wenn ich die Indizes nicht manuell nachverfolgen müsste. Danke für den Vorschlag, wenn nichts Besseres kommt, werde ich es akzeptieren. – kloffy