541 Stimmen

Wie man einen hierarchischen Index in Spalten reduziert

Ich habe einen Datenrahmen mit einem hierarchischen Index in Achse 1 (Spalten) (aus einer groupby.agg Betrieb):

     USAF   WBAN  year  month  day  s_PC  s_CL  s_CD  s_CNT  tempf       
                                     sum   sum   sum    sum   amax   amin
0  702730  26451  1993      1    1     1     0    12     13  30.92  24.98
1  702730  26451  1993      1    2     0     0    13     13  32.00  24.98
2  702730  26451  1993      1    3     1    10     2     13  23.00   6.98
3  702730  26451  1993      1    4     1     0    12     13  10.04   3.92
4  702730  26451  1993      1    5     3     0    10     13  19.94  10.94

Ich möchte sie abflachen, so dass sie wie folgt aussieht (die Namen sind nicht entscheidend - ich könnte sie umbenennen):

     USAF   WBAN  year  month  day  s_PC  s_CL  s_CD  s_CNT  tempf_amax  tmpf_amin   
0  702730  26451  1993      1    1     1     0    12     13  30.92          24.98
1  702730  26451  1993      1    2     0     0    13     13  32.00          24.98
2  702730  26451  1993      1    3     1    10     2     13  23.00          6.98
3  702730  26451  1993      1    4     1     0    12     13  10.04          3.92
4  702730  26451  1993      1    5     3     0    10     13  19.94          10.94

Wie kann ich das tun? (Ich habe schon viel versucht, ohne Erfolg.)

Auf Anregung ist hier der Kopf in Diktatform

{('USAF', ''): {0: '702730',
  1: '702730',
  2: '702730',
  3: '702730',
  4: '702730'},
 ('WBAN', ''): {0: '26451', 1: '26451', 2: '26451', 3: '26451', 4: '26451'},
 ('day', ''): {0: 1, 1: 2, 2: 3, 3: 4, 4: 5},
 ('month', ''): {0: 1, 1: 1, 2: 1, 3: 1, 4: 1},
 ('s_CD', 'sum'): {0: 12.0, 1: 13.0, 2: 2.0, 3: 12.0, 4: 10.0},
 ('s_CL', 'sum'): {0: 0.0, 1: 0.0, 2: 10.0, 3: 0.0, 4: 0.0},
 ('s_CNT', 'sum'): {0: 13.0, 1: 13.0, 2: 13.0, 3: 13.0, 4: 13.0},
 ('s_PC', 'sum'): {0: 1.0, 1: 0.0, 2: 1.0, 3: 1.0, 4: 3.0},
 ('tempf', 'amax'): {0: 30.920000000000002,
  1: 32.0,
  2: 23.0,
  3: 10.039999999999999,
  4: 19.939999999999998},
 ('tempf', 'amin'): {0: 24.98,
  1: 24.98,
  2: 6.9799999999999969,
  3: 3.9199999999999982,
  4: 10.940000000000001},
 ('year', ''): {0: 1993, 1: 1993, 2: 1993, 3: 1993, 4: 1993}}

19voto

odedbd Punkte 2045

Ein weiterer kurzer Bericht, der nur Pandas Methoden verwendet:

df.columns = df.columns.to_flat_index().str.join('_')

Ausbeute als Ausgabe:

    USAF_  WBAN_  day_  month_  ...  s_PC_sum  tempf_amax  tempf_amin  year_
0  702730  26451     1       1  ...       1.0       30.92       24.98   1993
1  702730  26451     2       1  ...       0.0       32.00       24.98   1993
2  702730  26451     3       1  ...       1.0       23.00        6.98   1993
3  702730  26451     4       1  ...       1.0       10.04        3.92   1993
4  702730  26451     5       1  ...       3.0       19.94       10.94   1993

Sie werden den nachgestellten Unterstrich für Spalten bemerken, die nicht Teil eines MultiIndexes waren. Sie haben erwähnt, dass Sie sich nicht um den Namen kümmern, also könnte das für Sie funktionieren. In meinem eigenen ähnlichen Anwendungsfall hatten alle Spalten zwei Ebenen, so dass dieser einfache Befehl schöne Namen erzeugte.

19voto

bodily11 Punkte 584

Die einfachste und intuitivste Lösung für mich war die Kombination der Spaltennamen mit get_level_values . Dadurch werden doppelte Spaltennamen vermieden, wenn Sie mehr als eine Aggregation für dieselbe Spalte durchführen:

level_one = df.columns.get_level_values(0).astype(str)
level_two = df.columns.get_level_values(1).astype(str)
df.columns = level_one + level_two

Wenn Sie ein Trennzeichen zwischen den Spalten wünschen, können Sie dies tun. Dies ergibt das Gleiche wie der Kommentar von Seiji Armstrong zur akzeptierten Antwort, die nur Unterstriche für Spalten mit Werten in beiden Indexebenen enthält:

level_one = df.columns.get_level_values(0).astype(str)
level_two = df.columns.get_level_values(1).astype(str)
column_separator = ['_' if x != '' else '' for x in level_two]
df.columns = level_one + column_separator + level_two

Ich weiß, dass dies dasselbe tut wie die großartige Antwort von Andy Hayden oben, aber ich denke, dass es auf diese Weise etwas intuitiver und leichter zu merken ist (so dass ich nicht ständig auf diesen Thread verweisen muss), besonders für unerfahrene Pandabenutzer.

Diese Methode ist auch dann besser erweiterbar, wenn Sie 3 Spaltenebenen haben.

level_one = df.columns.get_level_values(0).astype(str)
level_two = df.columns.get_level_values(1).astype(str)
level_three = df.columns.get_level_values(2).astype(str)
df.columns = level_one + level_two + level_three

18voto

Zelazny7 Punkte 38896

Und wenn Sie die Aggregationsinformationen der zweiten Ebene des Multiindex beibehalten wollen, können Sie dies versuchen:

In [1]: new_cols = [''.join(t) for t in df.columns]
Out[1]:
['USAF',
 'WBAN',
 'day',
 'month',
 's_CDsum',
 's_CLsum',
 's_CNTsum',
 's_PCsum',
 'tempfamax',
 'tempfamin',
 'year']

In [2]: df.columns = new_cols

18voto

Scott Boston Punkte 136420

Der pythonischste Weg, dies zu tun, ist die Verwendung von map Funktion.

df.columns = df.columns.map(' '.join).str.strip()

Salida print(df.columns) :

Index(['USAF', 'WBAN', 'day', 'month', 's_CD sum', 's_CL sum', 's_CNT sum',
       's_PC sum', 'tempf amax', 'tempf amin', 'year'],
      dtype='object')

Aktualisierung mit Python 3.6+ mit f string:

df.columns = [f'{f} {s}' if s != '' else f'{f}' 
              for f, s in df.columns]

print(df.columns)

出力します:

Index(['USAF', 'WBAN', 'day', 'month', 's_CD sum', 's_CL sum', 's_CNT sum',
       's_PC sum', 'tempf amax', 'tempf amin', 'year'],
      dtype='object')

11voto

Nickolay Punkte 29488

Nachdem ich mir alle Antworten durchgelesen hatte, kam ich zu diesem Ergebnis:

def __my_flatten_cols(self, how="_".join, reset_index=True):
    how = (lambda iter: list(iter)[-1]) if how == "last" else how
    self.columns = [how(filter(None, map(str, levels))) for levels in self.columns.values] \
                    if isinstance(self.columns, pd.MultiIndex) else self.columns
    return self.reset_index() if reset_index else self
pd.DataFrame.my_flatten_cols = __my_flatten_cols

Verwendung:

Gegeben ein Datenrahmen:

df = pd.DataFrame({"grouper": ["x","x","y","y"], "val1": [0,2,4,6], 2: [1,3,5,7]}, columns=["grouper", "val1", 2])

  grouper  val1  2
0       x     0  1
1       x     2  3
2       y     4  5
3       y     6  7
  • Einzelne Aggregationsmethode : Die daraus resultierenden Variablen mit dem Namen wie die Quelle :

    df.groupby(by="grouper").agg("min").my_flatten_cols()
    • Dasselbe wie df.groupby(by="grouper", as_index=False ) o .agg(...) .reset_index()
    • ----- before ----- val1 2 grouper

      ------ after -----
        grouper  val1  2
      0       x     0  1
      1       y     4  5
  • Einzelne Quellvariable, mehrere Aggregate resultierende Variablen benannt nach Statistiken :

    df.groupby(by="grouper").agg({"val1": [min,max]}).my_flatten_cols("last")
    • Dasselbe wie a = df.groupby(..).agg(..); a.columns = a.columns.droplevel(0); a.reset_index() .
    • ----- before ----- val1
      min max grouper

      ------ after -----
        grouper  min  max
      0       x    0    2
      1       y    4    6
  • Mehrere Variablen, mehrere Aggregate : Die resultierenden Variablen heißen (varname)_(statname) :

    df.groupby(by="grouper").agg({"val1": min, 2:[sum, "size"]}).my_flatten_cols()
    # you can combine the names in other ways too, e.g. use a different delimiter:
    #df.groupby(by="grouper").agg({"val1": min, 2:[sum, "size"]}).my_flatten_cols(" ".join)
    • Läuft a.columns = ["_".join(filter(None, map(str, levels))) for levels in a.columns.values] unter der Haube (da diese Form der agg() führt zu MultiIndex auf Spalten).
    • Wenn Sie nicht über die my_flatten_cols Helper zu verwenden, könnte es einfacher sein, die von @Seigi : a.columns = ["_".join(t).rstrip("_") for t in a.columns.values] was in diesem Fall ähnlich funktioniert (aber fehlschlägt, wenn Sie numerische Beschriftungen in Spalten haben)
    • Um die numerischen Beschriftungen der Spalten zu handhaben, können Sie die Lösung verwenden, die von @jxstanford und @Nolan Conaway ( a.columns = ["_".join(tuple(map(str, t))).rstrip("_") for t in a.columns.values] ), aber ich verstehe nicht, warum die tuple() Anruf erforderlich ist, und ich glaube rstrip() ist nur erforderlich, wenn einige Spalten einen Deskriptor wie ("colname", "") (was passieren kann, wenn Sie reset_index() bevor Sie versuchen, etwas zu reparieren .columns )
    • ----- before ----- val1 2
      min sum size grouper

      ------ after -----
        grouper  val1_min  2_sum  2_size
      0       x         0      4       2
      1       y         4     12       2
  • Sie möchten die resultierenden Variablen manuell benennen: (dies ist veraltet seit Pandas 0.20.0 con keine angemessene Alternative ab 0,23 )

    df.groupby(by="grouper").agg({"val1": {"sum_of_val1": "sum", "count_of_val1": "count"},
                                       2: {"sum_of_2":    "sum", "count_of_2":    "count"}}).my_flatten_cols("last")
    • Andere Vorschläge einschließen. : Manuelle Einstellung der Spalten: res.columns = ['A_sum', 'B_sum', 'count'] o .join() mehrere groupby Erklärungen.
    • ----- before ----- val1 2
      count_of_val1 sum_of_val1 count_of_2 sum_of_2 grouper

      ------ after -----
        grouper  count_of_val1  sum_of_val1  count_of_2  sum_of_2
      0       x              2            2           2         4
      1       y              2           10           2        12

Fälle, die von der Hilfsfunktion behandelt werden

  • Ebenennamen können Nicht-Strings sein, z. B. Index pandas DataFrame durch Spaltennummern, wenn Spaltennamen ganze Zahlen sind also müssen wir konvertieren mit map(str, ..)
  • sie können auch leer sein, also müssen wir filter(None, ..)
  • für einstufige Spalten (d. h. alles außer MultiIndex), columns.values gibt die Namen ( str , nicht Tupel)
  • je nachdem, wie Sie die .agg() Sie müssen möglicherweise die unterste Beschriftung für eine Spalte beibehalten oder mehrere Beschriftungen miteinander verknüpfen.
  • (da ich mich noch nicht mit Pandas beschäftigt habe?), möchte ich öfters reset_index() in der Lage sein, mit den Gruppierungsspalten auf die übliche Art und Weise zu arbeiten, so dass dies standardmäßig geschieht

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X