Python Pandas - jak spłaszczyć indeks hierarchiczny w kolumnach

Mam ramkę danych z hierarchicznym indeksem w osi 1 (kolumny) (z groupby."AGG"): {]}

     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

Chcę go spłaszczyć, aby wyglądał tak (nazwy nie są krytyczne - mogę zmienić nazwę):

     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
Jak mam to zrobić? (Próbowałem wiele, bezskutecznie.)

Zgodnie z sugestią, oto głowa w formie dict

{('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}}
 159
Author: Mark Byers, 2013-01-24

12 answers

Myślę, że najprostszym sposobem, aby to zrobić, byłoby ustawienie kolumn na najwyższym poziomie:

df.columns = df.columns.get_level_values(0)

Uwaga: Jeśli poziom to ma nazwę, Możesz również uzyskać do niego dostęp poprzez to, a nie 0.

.

Jeśli chcesz połączyć/join Twój MultiIndex w jednym indeksie (zakładając, że masz tylko wpisy w kolumnach) możesz:

df.columns = [' '.join(col).strip() for col in df.columns.values]

Uwaga: musimy strip Biała SPACJA, gdy nie ma drugiego indeksu.

In [11]: [' '.join(col).strip() for col in df.columns.values]
Out[11]: 
['USAF',
 'WBAN',
 'day',
 'month',
 's_CD sum',
 's_CL sum',
 's_CNT sum',
 's_PC sum',
 'tempf amax',
 'tempf amin',
 'year']
 260
Author: Andy Hayden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-17 19:36:17
pd.DataFrame(df.to_records()) # multiindex become columns and new index is integers only
 53
Author: Gleb Yarnykh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-14 08:00:21

ODPOWIEDŹ Andy ' ego Haydena jest z pewnością najprostszym sposobem-jeśli chcesz uniknąć powielania etykiet kolumn, musisz trochę poprawić

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


In [35]: mi = df.columns

In [36]: mi
Out[36]: 
MultiIndex
[(USAF, ), (WBAN, ), (day, ), (month, ), (s_CD, sum), (s_CL, sum), (s_CNT, sum), (s_PC, sum), (tempf, amax), (tempf, amin), (year, )]


In [37]: mi.tolist()
Out[37]: 
[('USAF', ''),
 ('WBAN', ''),
 ('day', ''),
 ('month', ''),
 ('s_CD', 'sum'),
 ('s_CL', 'sum'),
 ('s_CNT', 'sum'),
 ('s_PC', 'sum'),
 ('tempf', 'amax'),
 ('tempf', 'amin'),
 ('year', '')]

In [38]: ind = pd.Index([e[0] + e[1] for e in mi.tolist()])

In [39]: ind
Out[39]: Index([USAF, WBAN, day, month, s_CDsum, s_CLsum, s_CNTsum, s_PCsum, tempfamax, tempfamin, year], dtype=object)

In [40]: df.columns = ind




In [46]: df
Out[46]: 
     USAF   WBAN  day  month  s_CDsum  s_CLsum  s_CNTsum  s_PCsum  tempfamax  tempfamin  \
0  702730  26451    1      1       12        0        13        1      30.92      24.98   
1  702730  26451    2      1       13        0        13        0      32.00      24.98   
2  702730  26451    3      1        2       10        13        1      23.00       6.98   
3  702730  26451    4      1       12        0        13        1      10.04       3.92   
4  702730  26451    5      1       10        0        13        3      19.94      10.94   




   year  
0  1993  
1  1993  
2  1993  
3  1993  
4  1993
 24
Author: Theodros Zelleke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-24 18:54:14
df.columns = ['_'.join(tup).rstrip('_') for tup in df.columns.values]
 6
Author: tvt173,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-31 00:25:38

I jeśli chcesz zachować informacje o agregacji z drugiego poziomu multiindex, możesz spróbować tego:

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
 5
Author: Zelazny7,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-12 16:19:27

Po przeczytaniu wszystkich odpowiedzi, wpadłem na to:

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

Użycie:

Podano ramkę danych:

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
  • Metoda pojedynczej agregacji : zmienne wynikowe nazwane tak samo jak źródło:

    df.groupby(by="grouper").agg("min").my_flatten_cols()
    
    • tak samo jak df.groupby(by="grouper", as_index = False) lub .agg(...).reset_index ()
    • ----- before -----
                 val1  2
        grouper         
      
      ------ after -----
        grouper  val1  2
      0       x     0  1
      1       y     4  5
      
  • Jedna zmienna źródłowa, wiele agregacji : wynikająca zmienne nazwane statystykami :

    df.groupby(by="grouper").agg({"val1": [min,max]}).my_flatten_cols("last")
    
    • tak samo jak 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
      
  • Wiele zmiennych, wiele agregacji: zmienne wynikowe nazwane (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)
    
    • Działa a.columns = ["_".join(filter(None, map(str, levels))) for levels in a.columns.values] pod maską (ponieważ ta forma agg() daje MultiIndex na kolumnach).
    • jeśli nie masz my_flatten_cols helpera, może być łatwiej wpisać rozwiązanie zasugerowane przez @Seigi : W tym przypadku nie jest to jednak możliwe, jeśli na kolumnach znajdują się numeryczne etykiety.]}
    • do obsługi numerycznych etykiet na kolumnach, możesz użyć rozwiązania zaproponowanego przez @jxstanford i @ Nolan Conaway (a.columns = ["_".join(tuple(map(str, t))).rstrip("_") for t in a.columns.values]), ale nie rozumiem, dlaczego wywołanie tuple() jest potrzebne i wierzę, że {[21] } jest wymagane tylko wtedy, gdy niektóre kolumny mają deskryptor, taki jak ("colname", "") (co może się zdarzyć, jeśli reset_index() zanim spróbujesz naprawić .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
      
  • Chcesz ręcznie nazwać zmienne wynikowe: (jest to przestarzałe od pandas 0.20.0 Z brak odpowiedniej alternatywy od 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")
    
    • Inne sugestie obejmują : ręczne ustawianie kolumn: res.columns = ['A_sum', 'B_sum', 'count'] lub .join() ING wiele groupby poleceń.
    • ----- 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
      

Sprawy obsługiwane przez pomocnika function

  • nazwy poziomów mogą być nie-łańcuchowe, np. indeksowe ramki danych według numerów kolumn, gdy nazwy kolumn są liczbami całkowitymi , więc musimy przekonwertować za pomocą map(str, ..)
  • Mogą być również puste, więc musimy]}
  • dla kolumn jednopoziomowych (tj. cokolwiek poza MultiIndex), columns.values zwraca nazwy (str, nie krotki)
  • w zależności od sposobu użycia .agg() może być konieczne zachowanie dolnej większości etykiet dla kolumny lub połączenie wielu etykiety
  • (odkąd jestem nowy na pandach? z tego powodu nie jest to możliwe, ponieważ nie jest to możliwe, ponieważ nie jest to możliwe.]}
 4
Author: Nickolay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-26 16:58:46

Jeśli chcesz mieć separator w nazwie między poziomami, ta funkcja działa dobrze.

def flattenHierarchicalCol(col,sep = '_'):
    if not type(col) is tuple:
        return col
    else:
        new_col = ''
        for leveli,level in enumerate(col):
            if not level == '':
                if not leveli == 0:
                    new_col += sep
                new_col += level
        return new_col

df.columns = df.columns.map(flattenHierarchicalCol)
 3
Author: agartland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-03 18:12:37

Może trochę za późno, ale jeśli nie martwisz się duplikatami nazw kolumn:

df.columns = df.columns.tolist()
 3
Author: Niels,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-30 12:29:41

Ogólne rozwiązanie, które obsługuje wiele poziomów i typów mieszanych:

df.columns = ['_'.join(tuple(map(str, t))) for t in df.columns.values]
 2
Author: jxstanford,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-20 12:23:44

Podążając za @jxstanford i @tvt173, napisałem szybką funkcję, która powinna załatwić sprawę, niezależnie od nazw kolumn string/int:

def flatten_cols(df):
    df.columns = [
        '_'.join(tuple(map(str, t))).rstrip('_') 
        for t in df.columns.values
        ]
    return df
 2
Author: Nolan Conaway,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-10 20:35:18

Można również zrobić jak poniżej. Rozważ df jako ramkę danych i przyjmij dwupoziomowy indeks (jak to ma miejsce w twoim przykładzie)

df.columns = [(df.columns[i][0])+'_'+(datadf_pos4.columns[i][1]) for i in range(len(df.columns))]
 0
Author: Holy cow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-28 00:30:12

Najbardziej pythoniczny sposób, aby to zrobić, aby użyć funkcji map.

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

Wyjście 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')
 0
Author: Scott Boston,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-07 21:23:13