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}}
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']
pd.DataFrame(df.to_records()) # multiindex become columns and new index is integers only
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
df.columns = ['_'.join(tup).rstrip('_') for tup in df.columns.values]
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
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
- identique à
-
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
- identique à
-
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 deagg()
donneMultiIndex
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'appeltuple()
est nécessaire, et je crois querstrip()
est seulement requis si certaines colonnes ont un descripteur comme("colname", "")
(qui peut arriver si vousreset_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
- exécute
-
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 plusieursgroupby
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
- autres suggestions inclure : réglage de l'colonnes manuellement:
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,
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)
un peu tard peut-être, mais si vous n'êtes pas inquiet au sujet des noms de colonne dupliquer:
df.columns = df.columns.tolist()
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]
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
, 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))]
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')