El dataframe de Panda: renombrando columnas con nombres idénticos

Tengo varias columnas con el mismo nombre en un df. Necesito renombrarlos. El cambio de nombre habitual cambia el nombre de todo de todos modos puedo cambiar el nombre de los siguientes bla (s) a bla1, bla4, bla5?

In [6]: df=pd.DataFrame(np.arange(2*5).reshape(2,5)) df.columns=['blah','blah2','blah3','blah','blah'] df Out[6]: blah blah2 blah3 blah blah 0 0 1 2 3 4 1 5 6 7 8 9 

En [7]:

 df.rename(columns = {'blah':'blah1'}) Out[7]: blah1 blah2 blah3 blah1 blah1 0 0 1 2 3 4 1 5 6 7 8 9 

Buscaba encontrar una solución dentro de Pandas más que una solución general de Python. La función get_loc () de la columna devuelve una matriz enmascarada si encuentra duplicados con valores ‘Verdaderos’ que apuntan a las ubicaciones donde se encuentran duplicados. Luego uso la máscara para asignar nuevos valores a esas ubicaciones. En mi caso, sé de antemano cuántos dups obtendré y qué asignaré a ellos, pero parece que df.columns.get_duplicates () devolverá una lista de todos los dups y luego podrá use esa lista junto con get_loc () si necesita una acción de eliminación de datos masiva genérica

 cols=pd.Series(df.columns) for dup in df.columns.get_duplicates(): cols[df.columns.get_loc(dup)]=[dup+'.'+str(d_idx) if d_idx!=0 else dup for d_idx in range(df.columns.get_loc(dup).sum())] df.columns=cols blah blah2 blah3 blah.1 blah.2 0 0 1 2 3 4 1 5 6 7 8 9 

Comenzando con Pandas 0.19.0 pd.read_csv() ha mejorado el soporte para nombres de columna duplicados

Entonces podemos intentar usar el método interno:

 In [137]: pd.io.parsers.ParserBase({'names':df.columns})._maybe_dedup_names(df.columns) Out[137]: ['blah', 'blah2', 'blah3', 'blah.1', 'blah.2'] 

Esta es la función “mágica”:

 def _maybe_dedup_names(self, names): # see gh-7160 and gh-9424: this helps to provide # immediate alleviation of the duplicate names # issue and appears to be satisfactory to users, # but ultimately, not needing to butcher the names # would be nice! if self.mangle_dupe_cols: names = list(names) # so we can index counts = {} for i, col in enumerate(names): cur_count = counts.get(col, 0) if cur_count > 0: names[i] = '%s.%d' % (col, cur_count) counts[col] = cur_count + 1 return names 

Podrías usar esto:

 def df_column_uniquify(df): df_columns = df.columns new_columns = [] for item in df_columns: counter = 0 newitem = item while newitem in new_columns: counter += 1 newitem = "{}_{}".format(item, counter) new_columns.append(newitem) df.columns = new_columns return df 

Entonces

 import numpy as np import pandas as pd df=pd.DataFrame(np.arange(2*5).reshape(2,5)) df.columns=['blah','blah2','blah3','blah','blah'] 

para que df :

  blah blah2 blah3 blah blah 0 0 1 2 3 4 1 5 6 7 8 9 

entonces

 df = df_column_uniquify(df) 

para que df :

  blah blah2 blah3 blah_1 blah_2 0 0 1 2 3 4 1 5 6 7 8 9 

Podrías asignar directamente a las columnas:

 In [12]: df.columns = ['blah','blah2','blah3','blah4','blah5'] df Out[12]: blah blah2 blah3 blah4 blah5 0 0 1 2 3 4 1 5 6 7 8 9 [2 rows x 5 columns] 

Si desea simplemente cambiar el nombre de las columnas duplicadas dinámicamente, podría hacer algo como lo siguiente (código tomado de la respuesta 2: índice de elementos duplicados en una lista de python ):

 In [25]: import collections dups = collections.defaultdict(list) dup_indices=[] col_list=list(df.columns) for i, e in enumerate(list(df.columns)): dups[e].append(i) for k, v in sorted(dups.items()): if len(v) >= 2: dup_indices = v for i in dup_indices: col_list[i] = col_list[i] + ' ' + str(i) col_list Out[25]: ['blah 0', 'blah2', 'blah3', 'blah 3', 'blah 4'] 

Luego puede usar esto para volver a asignar, también podría tener una función para generar un nombre único que no esté presente en las columnas antes de cambiar el nombre.

Dado que la respuesta aceptada (por Lamakaha) no funciona para las versiones recientes de pandas, y debido a que las otras sugerencias parecían un poco torpes, resolví mi propia solución:

 def dedupIndex(idx, fmt=None, ignoreFirst=True): # fmt: A string format that receives two arguments: # name and a counter. By default: fmt='%s.%03d' # ignoreFirst: Disable/enable postfixing of first element. idx = pd.Series(idx) duplicates = idx[idx.duplicated()].unique() fmt = '%s.%03d' if fmt is None else fmt for name in duplicates: dups = idx==name ret = [ fmt%(name,i) if (i!=0 or not ignoreFirst) else name for i in range(dups.sum()) ] idx.loc[dups] = ret return pd.Index(idx) 

Utilice la función de la siguiente manera:

 df.columns = dedupIndex(df.columns) # Result: ['blah', 'blah2', 'blah3', 'blah.001', 'blah.002'] df.columns = dedupIndex(df.columns, fmt='%s #%d', ignoreFirst=False) # Result: ['blah #0', 'blah2', 'blah3', 'blah #1', 'blah #2']