Python pandas eliminan columnas duplicadas

126

¿Cuál es la forma más sencilla de eliminar columnas duplicadas de un marco de datos?

Estoy leyendo un archivo de texto que tiene columnas duplicadas a través de:

import pandas as pd

df=pd.read_table(fname)

Los nombres de las columnas son:

Time, Time Relative, N2, Time, Time Relative, H2, etc...

Todas las columnas Tiempo y Relativo al tiempo contienen los mismos datos. Quiero:

Time, Time Relative, N2, H2

Todos mis intentos de eliminar, eliminar, etc., como:

df=df.T.drop_duplicates().T

Da como resultado errores de índice con valor único:

Reindexing only valid with uniquely valued index objects

Perdón por ser un novato de Pandas. Cualquier sugerencia sera apreciada.


Detalles adicionales

Versión de Pandas: 0.9.0
Versión de Python: 2.7.3
Windows 7
(instalado a través de Pythonxy 2.7.3.0)

archivo de datos (nota: en el archivo real, las columnas están separadas por pestañas, aquí están separadas por 4 espacios):

Time    Time Relative [s]    N2[%]    Time    Time Relative [s]    H2[ppm]
2/12/2013 9:20:55 AM    6.177    9.99268e+001    2/12/2013 9:20:55 AM    6.177    3.216293e-005    
2/12/2013 9:21:06 AM    17.689    9.99296e+001    2/12/2013 9:21:06 AM    17.689    3.841667e-005    
2/12/2013 9:21:18 AM    29.186    9.992954e+001    2/12/2013 9:21:18 AM    29.186    3.880365e-005    
... etc ...
2/12/2013 2:12:44 PM    17515.269    9.991756+001    2/12/2013 2:12:44 PM    17515.269    2.800279e-005    
2/12/2013 2:12:55 PM    17526.769    9.991754e+001    2/12/2013 2:12:55 PM    17526.769    2.880386e-005
2/12/2013 2:13:07 PM    17538.273    9.991797e+001    2/12/2013 2:13:07 PM    17538.273    3.131447e-005
Onlyjus
fuente
¿Qué versión de pandas tienes? ( import pandas as pd; pd.__version__ )
beardc
1
@BirdJaguarIV, estoy usando pandas versión 0.9.0
Onlyjus
Es posible que desee intentar actualizar a 0.10. Mi versión hace que las columnas sean únicas con read_tableel ejemplo que inventé.
beardc
Tenga en cuenta que df = df.T.drop_duplicates (). T no considera el nombre de la columna. Si tiene dos columnas con los mismos datos pero nombres diferentes, una se eliminará por error.
Joylove

Respuestas:

392

Hay una solución de una línea al problema. Esto se aplica si algunos nombres de columna están duplicados y desea eliminarlos:

df = df.loc[:,~df.columns.duplicated()]

Cómo funciona:

Suponga que las columnas del marco de datos son ['alpha','beta','alpha']

df.columns.duplicated()devuelve una matriz booleana: una Trueo Falsede cada columna. Si es Falseasí, el nombre de la columna es único hasta ese momento; si lo es True, el nombre de la columna se ha duplicado anteriormente. Por ejemplo, usando el ejemplo dado, el valor devuelto sería [False,False,True].

Pandaspermite indexar usando valores booleanos por lo que selecciona solo los Truevalores. Como queremos mantener las columnas sin duplicar, necesitamos que la matriz booleana anterior se invierta (es decir [True, True, False] = ~[False,False,True])

Finalmente, df.loc[:,[True,True,False]]selecciona solo las columnas no duplicadas usando la capacidad de indexación antes mencionada.

Nota : lo anterior solo verifica los nombres de las columnas, no los valores de las columnas.

Gene Burinsky
fuente
16
Una respuesta ideal también funcionaría para valores duplicados, no solo para nombres.
GrimSqueaker
7
@GrimSqueaker: si desea considerar si los valores están duplicados, desea algo como df.T.drop_duplicates().T.
John Zwinck
3
Con mucho, la solución más rápida
AtotheSiv
2
@ VaidøtasIvøška, consulte la segunda respuesta para esta pregunta
Gene Burinsky
2
@JohnZwinck: esto solo funciona para marcos de datos pequeños, ya que hay un límite en la cantidad de columnas que puede tener. Para mí, falló para un marco de datos con 100,000 filas, por ejemplo, ya que esto produce 100,000 columnas después de la transposición, lo cual no es posible
Eelco van Vliet
40

Parece que ya conoce los nombres únicos de las columnas. Si ese es el caso, entonces df = df['Time', 'Time Relative', 'N2']funcionaría.

Si no es así, su solución debería funcionar:

In [101]: vals = np.random.randint(0,20, (4,3))
          vals
Out[101]:
array([[ 3, 13,  0],
       [ 1, 15, 14],
       [14, 19, 14],
       [19,  5,  1]])

In [106]: df = pd.DataFrame(np.hstack([vals, vals]), columns=['Time', 'H1', 'N2', 'Time Relative', 'N2', 'Time'] )
          df
Out[106]:
   Time  H1  N2  Time Relative  N2  Time
0     3  13   0              3  13     0
1     1  15  14              1  15    14
2    14  19  14             14  19    14
3    19   5   1             19   5     1

In [107]: df.T.drop_duplicates().T
Out[107]:
   Time  H1  N2
0     3  13   0
1     1  15  14
2    14  19  14
3    19   5   1

Probablemente tenga algo específico en sus datos que lo arruine. Podríamos brindarle más ayuda si hay más detalles que pueda brindarnos sobre los datos.

Editar: Como dijo Andy, el problema probablemente esté en los títulos de columna duplicados.

Para un archivo de tabla de muestra 'dummy.csv' que inventé:

Time    H1  N2  Time    N2  Time Relative
3   13  13  3   13  0
1   15  15  1   15  14
14  19  19  14  19  14
19  5   5   19  5   1

el uso read_tableda columnas únicas y funciona correctamente:

In [151]: df2 = pd.read_table('dummy.csv')
          df2
Out[151]:
         Time  H1  N2  Time.1  N2.1  Time Relative
      0     3  13  13       3    13              0
      1     1  15  15       1    15             14
      2    14  19  19      14    19             14
      3    19   5   5      19     5              1
In [152]: df2.T.drop_duplicates().T
Out[152]:
             Time  H1  Time Relative
          0     3  13              0
          1     1  15             14
          2    14  19             14
          3    19   5              1  

Si su versión no lo permite, puede crear una solución para que sean únicos:

In [169]: df2 = pd.read_table('dummy.csv', header=None)
          df2
Out[169]:
              0   1   2     3   4              5
        0  Time  H1  N2  Time  N2  Time Relative
        1     3  13  13     3  13              0
        2     1  15  15     1  15             14
        3    14  19  19    14  19             14
        4    19   5   5    19   5              1
In [171]: from collections import defaultdict
          col_counts = defaultdict(int)
          col_ix = df2.first_valid_index()
In [172]: cols = []
          for col in df2.ix[col_ix]:
              cnt = col_counts[col]
              col_counts[col] += 1
              suf = '_' + str(cnt) if cnt else ''
              cols.append(col + suf)
          cols
Out[172]:
          ['Time', 'H1', 'N2', 'Time_1', 'N2_1', 'Time Relative']
In [174]: df2.columns = cols
          df2 = df2.drop([col_ix])
In [177]: df2
Out[177]:
          Time  H1  N2 Time_1 N2_1 Time Relative
        1    3  13  13      3   13             0
        2    1  15  15      1   15            14
        3   14  19  19     14   19            14
        4   19   5   5     19    5             1
In [178]: df2.T.drop_duplicates().T
Out[178]:
          Time  H1 Time Relative
        1    3  13             0
        2    1  15            14
        3   14  19            14
        4   19   5             1 
beardc
fuente
5
Desafortunadamente, df['Time']selecciona todas las series de tiempo (es decir, devuelve un DataFrame), y df['Time', ..]esto devolverá el DataFrame completo.
Andy Hayden
Sí, es bastante tedioso ... ojalá sea solo una diferencia de versión.
beardc
2
El uso de transposiciones dobles podría tener efectos secundarios no deseados, como convertir tipos numéricos en objetos en el caso de que tenga un df con tipos mixtos. Ver: stackoverflow.com/questions/24682396/…
Petergavinkin
Esta solución me da problemas en grandes marcos de datos: RecursionError: maximum recursion depth exceeded
Scott
La transposición de un gran marco de datos será un proceso lento
Kush Patel
13

La transposición es ineficaz para DataFrames grandes. Aquí hay una alternativa:

def duplicate_columns(frame):
    groups = frame.columns.to_series().groupby(frame.dtypes).groups
    dups = []
    for t, v in groups.items():
        dcols = frame[v].to_dict(orient="list")

        vs = dcols.values()
        ks = dcols.keys()
        lvs = len(vs)

        for i in range(lvs):
            for j in range(i+1,lvs):
                if vs[i] == vs[j]: 
                    dups.append(ks[i])
                    break

    return dups       

Úselo así:

dups = duplicate_columns(frame)
frame = frame.drop(dups, axis=1)

Editar

Una versión de memoria eficiente que trata nans como cualquier otro valor:

from pandas.core.common import array_equivalent

def duplicate_columns(frame):
    groups = frame.columns.to_series().groupby(frame.dtypes).groups
    dups = []

    for t, v in groups.items():

        cs = frame[v].columns
        vs = frame[v]
        lcs = len(cs)

        for i in range(lcs):
            ia = vs.iloc[:,i].values
            for j in range(i+1, lcs):
                ja = vs.iloc[:,j].values
                if array_equivalent(ia, ja):
                    dups.append(cs[i])
                    break

    return dups
Kalu
fuente
3
¡Funciona a las mil maravillas, muy eficiente! El uso my_df.T.drop_duplicates().Tcolgaría de grandes marcos de datos.
Will el
1
Hermosa solución, pero el 26 de abril de 2017 obtuve/usr/local/lib/python3.5/dist-packages/ipykernel_launcher.py:17: DeprecationWarning: 'pandas.core.common.array_equivalent' is deprecated and is no longer public API
George Fisher
reemplazar if array_equivalent(ia, ja):con if np.array_equal(ia, ja):parece producir los mismos resultados, pero leí que no maneja bien los NaN.
George Fisher
@GeorgeFisher ¿El código subyacente de array_equivalenttodavía está disponible en el repositorio público, posiblemente en una rama más antigua?
Kalu
@kalu ahora hay una corriente numpy.array_equiv; para pandas, no veo ninguna rama de lanzamiento anterior en GitHub, pandas.core.commonpero tal vez haya otros lugares para buscar
George Fisher
11

Si no me equivoco, lo siguiente hace lo que se pidió sin los problemas de memoria de la solución de transposición y con menos líneas que la función de @kalu, manteniendo la primera de las columnas con nombres similares.

Cols = list(df.columns)
for i,item in enumerate(df.columns):
    if item in df.columns[:i]: Cols[i] = "toDROP"
df.columns = Cols
df = df.drop("toDROP",1)
Elliott Collins
fuente
Su solución no funciona en mi caso, me muestra: "ValueError: etiquetas ['toDROP'] no contenidas en el eje" después de ejecutar la última línea
NuValue
4

Parece que estaba en el camino correcto. Aquí está el resumen que estaba buscando:

df.reset_index().T.drop_duplicates().T

Pero como no hay un marco de datos de ejemplo que produzca el mensaje de error al que se hace referencia Reindexing only valid with uniquely valued index objects, es difícil decir exactamente qué resolvería el problema. si restaurar el índice original es importante para usted, haga esto:

original_index = df.index.names
df.reset_index().T.drop_duplicates().reset_index(original_index).T
Tony B
fuente
0

Primer paso: - Leer la primera fila, es decir, todas las columnas, eliminar todas las columnas duplicadas.

Segundo paso: - Finalmente leer solo esas columnas.

cols = pd.read_csv("file.csv", header=None, nrows=1).iloc[0].drop_duplicates()
df = pd.read_csv("file.csv", usecols=cols)
Kamran Kausar
fuente
0

Me encontré con este problema en el que la línea proporcionada por la primera respuesta funcionó bien. Sin embargo, tuve la complicación adicional de que la segunda copia de la columna tenía todos los datos. La primera copia no lo hizo.

La solución fue crear dos marcos de datos dividiendo el único marco de datos alternando el operador de negación. Una vez que tuve los dos marcos de datos, ejecuté una declaración de combinación usando lsuffix. De esta manera, podría hacer referencia y eliminar la columna sin los datos.

- E

Eco de Edmund
fuente
0

La forma a continuación identificará columnas duplicadas para revisar qué está fallando al construir el marco de datos originalmente.

dupes = pd.DataFrame(df.columns)
dupes[dupes.duplicated()]
Joe
fuente