J'ai un bloc de données avec un index hiérarchique dans l'axe 1 (colonnes) (à partir d'une opération 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
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 puis-je faire cela? (J'ai essayé beaucoup, en vain.)
Par suggestion, voici la tête sous 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}}
Je pense que le moyen le plus simple de procéder est de définir les colonnes au niveau supérieur:
df.columns = df.columns.get_level_values(0)
Remarque: si le niveau de destination a un nom, vous pouvez également y accéder par cette adresse plutôt que par 0.
.
Si vous souhaitez combiner/ join
votre MultiIndex dans un seul index (en supposant que vous n'ayez que des entrées de chaîne dans vos colonnes) , vous pourriez :
df.columns = [' '.join(col).strip() for col in df.columns.values]
Remarque: nous devons strip
les espaces pour l'absence d'un 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 solution la plus simple. Si vous souhaitez éviter les libellés de colonnes 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
Toutes les réponses actuelles sur ce fil doivent être un peu datées. A partir de pandas
version 0.24.0, la .to_flat_index()
fait ce dont vous avez besoin.
De panda's propre documentation :
MultiIndex.to_flat_index ()
Convertir un MultiIndex en un Index de nuplets contenant les valeurs de niveau.
Un exemple simple tiré de sa documentation:
import pandas as pd
print(pd.__version__) # '0.23.4'
index = pd.MultiIndex.from_product(
[['foo', 'bar'], ['baz', 'qux']],
names=['a', 'b'])
print(index)
# MultiIndex(levels=[['bar', 'foo'], ['baz', 'qux']],
# codes=[[1, 1, 0, 0], [0, 1, 0, 1]],
# names=['a', 'b'])
Appliquer to_flat_index()
:
index.to_flat_index()
# Index([('foo', 'baz'), ('foo', 'qux'), ('bar', 'baz'), ('bar', 'qux')], dtype='object')
pandas
existanteUn exemple d'utilisation de celui-ci sur dat
, qui est un DataFrame avec une colonne MultiIndex
:
dat = df.loc[:,['name','workshop_period','class_size']].groupby(['name','workshop_period']).describe()
print(dat.columns)
# MultiIndex(levels=[['class_size'], ['count', 'mean', 'std', 'min', '25%', '50%', '75%', 'max']],
# codes=[[0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 2, 3, 4, 5, 6, 7]])
dat.columns = dat.columns.to_flat_index()
print(dat.columns)
# Index([('class_size', 'count'), ('class_size', 'mean'),
# ('class_size', 'std'), ('class_size', 'min'),
# ('class_size', '25%'), ('class_size', '50%'),
# ('class_size', '75%'), ('class_size', 'max')],
# dtype='object')
df.columns = ['_'.join(tup).rstrip('_') for tup in df.columns.values]
Et si vous souhaitez conserver les informations d'agrégation du deuxième niveau du multi-index, 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
La façon la plus pythonique de faire cela consiste à utiliser la fonction map
.
df.columns = df.columns.map(' '.join).str.strip()
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')
df.columns = [f'{f} {s}' if s != '' else f'{f}'
for f, s in df.columns]
print(df.columns)
Sortie:
Index(['USAF', 'WBAN', 'day', 'month', 's_CD sum', 's_CL sum', 's_CNT sum',
's_PC sum', 'tempf amax', 'tempf amin', 'year'],
dtype='object')
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
Étant donné un bloc 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 unique : variables résultantes nommées identiques à la source :
df.groupby(by="grouper").agg("min").my_flatten_cols()
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
Variable source unique, agrégations multiples : variables résultantes nommées d'après des statistiques :
df.groupby(by="grouper").agg({"val1": [min,max]}).my_flatten_cols("last")
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 résultantes nommées (nomvar) _ (nomstat) :
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)
a.columns = ["_".join(filter(None, map(str, levels))) for levels in a.columns.values]
sous le capot (étant donné que cette forme de agg()
aboutit à MultiIndex
sur des colonnes).my_flatten_cols
, il sera peut-ê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)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()
n'est requis que si certaines colonnes ont un descripteur tel que ("colname", "")
(ce qui peut arriver si vous reset_index()
avant d'essayer de corriger .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 souhaitez nommer les variables résultantes manuellement: (c'est obsolète depuis pandas 0.20. avec pas d'alternative adéquate à partir de 0.2 )
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")
res.columns = ['A_sum', 'B_sum', 'count']
ou .join()
ing plusieurs groupby
instructions.----- 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
map(str, ..)
filter(None, ..)
columns.values
renvoie les noms (str
, pas les tuples).agg()
, vous devrez peut-être conserver l'étiquette la plus basse pour une colonne ou concaténer plusieurs étiquettes.reset_index()
soit capable de travailler avec les colonnes group-by de la manière habituelle, donc il le fait par défautUn peu en retard peut-être, mais si vous n'êtes pas inquiet au sujet des noms de colonnes en double:
df.columns = df.columns.tolist()
Une solution générale qui gère plusieurs niveaux et types mélangés:
df.columns = ['_'.join(Tuple(map(str, t))) for t in df.columns.values]
Si 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)
Après @jxstanford et @ tvt173, j’ai écrit une fonction rapide qui devrait faire l'affaire, quels que soient les 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
Je vais partager un moyen simple qui a fonctionné pour moi.
[" ".join([str(elem) for elem in tup]) for tup in df.columns.tolist()]
#df = df.reset_index() if needed
Vous pouvez également faire comme ci-dessous. Considérez df
comme votre cadre de données et supposez un index à deux niveaux (comme dans votre exemple)
df.columns = [(df.columns[i][0])+'_'+(datadf_pos4.columns[i][1]) for i in range(len(df.columns))]
Pour aplatir un MultiIndex dans une chaîne d'autres méthodes DataFrame, définissez une fonction comme celle-ci:
def flatten_index(df):
df_copy = df.copy()
df_copy.columns = ['_'.join(col).rstrip('_') for col in df_copy.columns.values]
return df_copy.reset_index()
Utilisez ensuite la méthode pipe
pour appliquer cette fonction dans la chaîne de méthodes DataFrame, après groupby
et agg
mais avant toute autre méthode de la chaîne:
my_df \
.groupby('group') \
.agg({'value': ['count']}) \
.pipe(flatten_index) \
.sort_values('value_count')
La solution la plus simple et la plus intuitive pour moi consistait à combiner les noms de colonne à l'aide de get_level_values . Cela évite les noms de colonnes en double lorsque vous effectuez plusieurs agrégations sur la même colonne:
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
Si vous voulez un séparateur entre les colonnes, vous pouvez le faire. Cela retournera la même chose que le commentaire de Seiji Armstrong sur la réponse acceptée qui n'inclut que les traits de soulignement pour les colonnes avec des valeurs dans les deux niveaux d'index:
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
Je sais que cela fait la même chose que l'excellente réponse d'Andy Hayden ci-dessus, mais je pense que c'est un peu plus intuitif de cette façon et qu'il est plus facile à retenir (donc je n'ai pas à continuer à faire référence à ce fil), en particulier pour les novices pandas utilisateurs.
Cette méthode est également plus extensible dans le cas où vous pouvez avoir 3 niveaux de colonne.
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