Tengo un dataframe con un índice jerárquico en el eje 1 (columnas) (de una operación 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
Quiero aplanarlo, para que se vea así (los nombres no son críticos, podría cambiar el nombre):
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
¿Cómo hago esto? (He intentado mucho, en vano.)
Por una sugerencia, aquí está la cabeza en forma 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}}
Creo que la forma más fácil de hacer esto sería establecer las columnas en el nivel superior:
df.columns = df.columns.get_level_values(0)
Nota: si el nivel tiene un nombre, también puede acceder a él por este, en lugar de 0.
.
Si desea combinar / join
su índice múltiple en un índice (asumiendo que solo tiene entradas de cadena en sus columnas) , podría:
df.columns = [' '.join(col).strip() for col in df.columns.values]
Nota: debemos strip
el espacio en blanco para cuando no haya un segundo índice.
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 respuesta de Andy Hayden es sin duda la forma más fácil: si desea evitar las tags de columnas duplicadas, debe ajustar un poco
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]
Y si desea conservar cualquier información de agregación del segundo nivel del índice múltiple, puede intentar esto:
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
Quizás un poco tarde, pero si no te preocupan los nombres de columna duplicados:
df.columns = df.columns.tolist()
Después de leer todas las respuestas, se me ocurrió esto:
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
Dado un dataframe:
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étodo de agregación individual : las variables resultantes se denominan igual que la fuente
df.groupby(by="grouper").agg("min").my_flatten_cols()
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
Variable de origen único, agregaciones múltiples : variables resultantes nombradas después de las estadísticas :
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()
a = df.groupby(..).agg(..); a.columns = a.columns.droplevel(0); a.reset_index()
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
Múltiples variables, múltiples agregaciones : variables resultantes llamadas (varname) _ (statname) :
df.groupby(by="grouper").agg({"val1": min, 2:[sum, "size"]}).my_flatten_cols() # you can combine the names in other ways too, eg 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]
bajo el capó (ya que esta forma de agg()
da como resultado MultiIndex
en columnas) . my_flatten_cols
, puede ser más fácil escribir la solución sugerida por @Seigi : a.columns = ["_".join(t).rstrip("_") for t in a.columns.values]
, que funciona de manera similar en este caso (pero falla si tiene tags numéricas en las columnas) a.columns = ["_".join(tuple(map(str, t))).rstrip("_") for t in a.columns.values]
), pero no entiendo por qué es necesaria la llamada tuple()
, y creo que rstrip()
solo es necesario si algunas columnas tienen un descriptor como ("colname", "")
(lo que puede suceder si reset_index()
antes de intentar arreglar .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
Desea nombrar las variables resultantes manualmente: (está en desuso desde pandas 0.20.0 sin una alternativa adecuada a 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")
res.columns = ['A_sum', 'B_sum', 'count']
o .join()
ing varias declaraciones groupby
. ----- 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
devuelve los nombres ( str
, no tuplas) .agg()
es posible que deba mantener la etiqueta de la parte inferior de la columna o concatenar varias tags. reset_index()
pueda trabajar con las columnas reset_index()
forma regular, por lo que lo hace de forma predeterminada En caso de que quiera tener un separador en el nombre entre niveles, esta función funciona 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)
Una solución general que maneja múltiples niveles y tipos mixtos:
df.columns = ['_'.join(tuple(map(str, t))) for t in df.columns.values]
La forma más pythonica de hacer esto para usar la función de map
.
df.columns = df.columns.map(' '.join).str.strip()
print(df.columns)
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')
df.columns = [f'{f} {s}' if s != '' else f'{f}' for f, s in df.columns] print(df.columns)
Salida:
Index(['USAF', 'WBAN', 'day', 'month', 's_CD sum', 's_CL sum', 's_CNT sum', 's_PC sum', 'tempf amax', 'tempf amin', 'year'], dtype='object')
Siguiendo a @jxstanford y @ tvt173, escribí una función rápida que debería hacer el truco, independientemente de los nombres de columna de cadena / int:
def flatten_cols(df): df.columns = [ '_'.join(tuple(map(str, t))).rstrip('_') for t in df.columns.values ] return df
También puedes hacer lo siguiente. Considere df
como su dataframe y un índice de dos niveles (como es el caso en su ejemplo)
df.columns = [(df.columns[i][0])+'_'+(datadf_pos4.columns[i][1]) for i in range(len(df.columns))]
Voy a compartir una manera directa que funcionó para mí.
[" ".join([str(elem) for elem in tup]) for tup in df.columns.tolist()] #df = df.reset_index() if needed
Para aplanar un MultiIndex dentro de una cadena de otros métodos DataFrame, defina una función como esta:
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()
Luego use el método pipe
para aplicar esta función en la cadena de métodos DataFrame, después de groupby
y agg
pero antes de cualquier otro método en la cadena:
my_df \ .groupby('group') \ .agg({'value': ['count']}) \ .pipe(flatten_index) \ .sort_values('value_count')