Python Pandas - comment aplatir un index hiérarchique en colonnes

j'ai un bloc de données avec un index hiérarchique dans l'axe 1 (colonnes) (à partir d'un groupby.apa fonctionnement):

     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

je veux l'aplatir, pour qu'il ressemble à ceci (les noms ne sont pas critiques - je pourrais renommer):

     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

Comment faire? (J'ai essayé beaucoup de choses, en vain.)

pour une suggestion, voici la tête en forme de 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
demandé sur Mark Byers 2013-01-24 22:03:11

12 réponses

je pense que la façon la plus facile de faire cela serait de placer les colonnes au niveau supérieur:

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

Note: si le niveau to a un nom, vous pouvez également y accéder par ceci, plutôt que par 0.

.

si vous voulez combiner/ join votre MultiIndex dans un Index (en supposant que vous avez juste des entrées de chaîne dans vos colonnes) vous pourriez:

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

Note: Nous devons strip l'espace blanc pour quand il n'y a pas de deuxième index.

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
répondu Andy Hayden 2014-01-17 19:36:17
pd.DataFrame(df.to_records()) # multiindex become columns and new index is integers only
53
répondu Gleb Yarnykh 2015-12-14 08:00:21

la réponse D'Andy Hayden est certainement la façon la plus facile -- si vous voulez éviter les étiquettes de colonne en double, vous devez modifier un peu

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
répondu Theodros Zelleke 2013-01-24 18:54:14
df.columns = ['_'.join(tup).rstrip('_') for tup in df.columns.values]
6
répondu tvt173 2017-05-31 00:25:38

et si vous voulez conserver n'importe quelle information d'agrégation du deuxième niveau du multiindex vous pouvez essayer ceci:

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
répondu Zelazny7 2018-02-12 16:19:27

Après avoir lu toutes les réponses, j'ai trouvé ceci:

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

Utilisation:

donne une base de données:

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
  • méthode d'agrégation simple : variables résultantes nommées même que la source :

    df.groupby(by="grouper").agg("min").my_flatten_cols()
    
    • identique à df.groupby(by="grouper", as_index=False ) ou .agg(...) .reset_index ()
    • ----- before -----
                 val1  2
        grouper         
      
      ------ after -----
        grouper  val1  2
      0       x     0  1
      1       y     4  5
      
  • source Unique variable, plusieurs agrégations : il résulte des variables nommé d'après les statistiques :

    df.groupby(by="grouper").agg({"val1": [min,max]}).my_flatten_cols("last")
    
    • identique à 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
      
  • Plusieurs variables, plusieurs agrégations : variables obtenues nommé (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)
    
    • exécute a.columns = ["_".join(filter(None, map(str, levels))) for levels in a.columns.values] sous la hotte (puisque cette forme de agg() donne MultiIndex sur les colonnes).
    • si vous n'avez pas l'aide my_flatten_cols , il pourrait être plus facile de taper la solution suggérée par @Seigi : a.columns = ["_".join(t).rstrip("_") for t in a.columns.values] , qui fonctionne de la même manière dans ce cas (mais échoue si vous avez des étiquettes numériques sur les colonnes)
    • pour manipuler les étiquettes numériques sur les colonnes, vous pourriez utiliser la solution suggérée par @jxstanford et @Nolan Conaway ( a.columns = ["_".join(tuple(map(str, t))).rstrip("_") for t in a.columns.values] ), mais je ne comprends pas pourquoi l'appel tuple() est nécessaire, et je crois que rstrip() est seulement requis si certaines colonnes ont un descripteur comme ("colname", "") (qui peut arriver si vous reset_index() avant d'essayer de réparer .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
      
  • vous voulez nommer manuellement les variables résultantes: (c'est déprécié depuis pandas 0.20.0 avec aucune alternative adéquate à partir de 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")
    
    • autres suggestions inclure : réglage de l'colonnes manuellement: res.columns = ['A_sum', 'B_sum', 'count'] ou .join() ing plusieurs groupby des déclarations.
    • ----- 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
      

cas traités par la fonction d'aide

  • les noms de niveau peuvent être non-string, par exemple Index pandas DataFrame par les numéros de colonne, quand les noms de colonne sont des entiers , donc nous devons convertir avec map(str, ..)
  • ils peuvent aussi être vides, donc nous devons filter(None, ..)
  • pour les colonnes à un niveau (c.-à-d. tout sauf MultiIndex), columns.values renvoie les noms ( str , pas de tuples)
  • selon la façon dont vous avez utilisé .agg() vous pourriez avoir besoin de conserver l'étiquette la plus basse pour une colonne ou de concaténer plusieurs étiquettes
  • (depuis que je suis nouveau aux pandas?) le plus souvent, je veux reset_index() pour être en mesure de travailler avec le groupe de colonnes dans la façon régulière, de sorte qu'il n'par défaut,
4
répondu Nickolay 2018-07-26 16:58:46

Dans le cas où vous voulez avoir un séparateur dans le nom entre les niveaux, cette fonction fonctionne bien.

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
répondu agartland 2015-04-03 18:12:37

un peu tard peut-être, mais si vous n'êtes pas inquiet au sujet des noms de colonne dupliquer:

df.columns = df.columns.tolist()
3
répondu Niels 2016-11-30 12:29:41

Une solution générale qui gère plusieurs niveaux et les types mixtes:

df.columns = ['_'.join(tuple(map(str, t))) for t in df.columns.values]
2
répondu jxstanford 2017-07-20 12:23:44

suite à @jxstanford et @tvt173, j'ai écrit une fonction rapide qui devrait faire l'affaire, indépendamment des noms de colonne string/int:

def flatten_cols(df):
    df.columns = [
        '_'.join(tuple(map(str, t))).rstrip('_') 
        for t in df.columns.values
        ]
    return df
2
répondu Nolan Conaway 2017-08-10 20:35:18

, Vous pouvez aussi faire comme ci-dessous. Considérez df comme votre dataframe et supposez un index à deux niveaux (comme c'est le cas dans votre exemple)

df.columns = [(df.columns[i][0])+'_'+(datadf_pos4.columns[i][1]) for i in range(len(df.columns))]
0
répondu Holy cow 2016-10-28 00:30:12

la façon la plus pythonique de faire ceci pour utiliser la fonction map .

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

sortie 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
répondu Scott Boston 2018-08-07 21:23:13