python pandas remove colunas duplicadas

126

Qual é a maneira mais fácil de remover colunas duplicadas de um dataframe?

Estou lendo um arquivo de texto com colunas duplicadas por meio de:

import pandas as pd

df=pd.read_table(fname)

Os nomes das colunas são:

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

Todas as colunas Tempo e Relativo ao tempo contêm os mesmos dados. Eu quero:

Time, Time Relative, N2, H2

Todas as minhas tentativas de descartar, excluir, etc., como:

df=df.T.drop_duplicates().T

Resultar em erros de índice com valor exclusivo:

Reindexing only valid with uniquely valued index objects

Desculpe por ser um Pandas noob. Qualquer sugestão seria apreciada.


detalhes adicionais

Versão do Pandas: 0.9.0
Versão do Python: 2.7.3
Windows 7
(instalado via Pythonxy 2.7.3.0)

arquivo de dados (nota: no arquivo real, as colunas são separadas por tabulações, aqui são separadas por 4 espaços):

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
fonte
Qual versão de pandas você tem? ( import pandas as pd; pd.__version__ )
beardc
1
@BirdJaguarIV, estou usando o pandas versão 0.9.0
Onlyjus de
Você pode querer tentar atualizar para 0.10. Minha versão torna as colunas exclusivas read_tablepara o exemplo que criei.
beardc de
Esteja ciente de que df = df.T.drop_duplicates (). T não considera o nome da coluna. Se você tiver duas colunas com os mesmos dados, mas nomes diferentes, uma será eliminada erroneamente.
Joylove

Respostas:

392

Existe uma solução de uma linha para o problema. Isso se aplica se alguns nomes de coluna estiverem duplicados e você desejar removê-los:

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

Como funciona:

Suponha que as colunas do quadro de dados sejam ['alpha','beta','alpha']

df.columns.duplicated()retorna uma matriz booleana: a Trueou Falsepara cada coluna. Se for False, o nome da coluna será exclusivo até esse ponto; se for True, o nome da coluna será duplicado anteriormente. Por exemplo, usando o exemplo fornecido, o valor retornado seria [False,False,True].

Pandaspermite indexar usando valores booleanos em que seleciona apenas os Truevalores. Uma vez que queremos manter as colunas não duplicadas, precisamos que o array booleano acima seja invertido (ou seja [True, True, False] = ~[False,False,True])

Por fim, df.loc[:,[True,True,False]]seleciona apenas as colunas não duplicadas usando o recurso de indexação mencionado anteriormente.

Nota : o acima verifica apenas nomes de colunas, não valores de coluna.

Gene Burinsky
fonte
16
Uma resposta ideal também funcionaria para valores duplicados, não apenas nomes.
GrimSqueaker
7
@GrimSqueaker: Se você deseja considerar se os valores estão duplicados, você deseja algo como df.T.drop_duplicates().T.
John Zwinck
3
De longe a solução mais rápida
AtotheSiv
2
@ VaidøtasIvøška veja a 2ª resposta para esta pergunta
Gene Burinsky
2
@JohnZwinck: isso só funciona para dataframes pequenos, pois há um limite para o número de colunas que você pode ter. Para mim, falhou para um dataframe com 100.000 linhas, por exemplo, pois isso produz 100.000 colunas após a transposição, o que não é possível
Eelco van Vliet
40

Parece que você já conhece os nomes exclusivos das colunas. Se for esse o caso, então df = df['Time', 'Time Relative', 'N2']funcionaria.

Caso contrário, sua solução deve 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

Você provavelmente tem algo específico para seus dados que está bagunçando tudo. Poderíamos fornecer mais ajuda se você pudesse fornecer mais detalhes sobre os dados.

Edit: Como Andy disse, o problema é provavelmente com os títulos das colunas duplicados.

Para um arquivo de tabela de amostra 'dummy.csv' eu criei:

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

usando read_tablefornece colunas exclusivas e funciona corretamente:

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  

Se sua versão não permitir, você pode criar juntos uma solução para torná-los ú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
fonte
5
Infelizmente, df['Time']seleciona todas as séries temporais (ou seja, retorna um DataFrame), e df['Time', ..]isso retornará todo o DataFrame.
Andy Hayden de
Sim, é muito tedioso ... espero que seja apenas uma diferença de versão.
beardc de
2
O uso de transposições duplas pode ter efeitos colaterais indesejados, como a conversão de tipos numéricos em objetos no caso de você ter um df com tipos mistos. Consulte: stackoverflow.com/questions/24682396/…
Petergavinkin
Esta solução me dá problemas em grandes dataframes: RecursionError: maximum recursion depth exceeded
Scott
A transposição de um grande frame de dados será um processo lento
Kush Patel
13

A transposição é ineficiente para grandes DataFrames. Aqui está uma 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       

Use-o assim:

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

Editar

Uma versão com uso eficiente de memória que trata nans como qualquer outro 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
fonte
3
Funciona como um encanto, muito eficiente! O uso my_df.T.drop_duplicates().Tseria suspenso em grandes dataframes.
Será
1
Ótima solução, mas em 26 de abril de 2017 recebi /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
substituir if array_equivalent(ia, ja):por if np.array_equal(ia, ja):parece produzir os mesmos resultados, mas li que ele não lida bem com NaNs.
George Fisher
@GeorgeFisher O código subjacente de array_equivalentainda está disponível no repositório público, possivelmente em um branch mais antigo?
kalu
@kalu agora existe uma corrente numpy.array_equiv; para pandas, não vejo nenhum branch de lançamento anterior no GitHub, pandas.core.commonmas talvez haja outros lugares para procurar
George Fisher
11

Se não me engano, o seguinte faz o que foi pedido sem os problemas de memória da solução de transposição e com menos linhas que a função de @kalu, mantendo a primeira de quaisquer colunas com nome semelhante.

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
fonte
Sua solução não funciona no meu caso, ela me mostra: "ValueError: labels ['toDROP'] não contidos no eixo" após a execução da última linha
NuValue
4

Parece que você está no caminho certo. Aqui está o one-liner que você estava procurando:

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

Mas, como não existe um quadro de dados de exemplo que produza a mensagem de erro referenciada Reindexing only valid with uniquely valued index objects, é difícil dizer exatamente o que resolveria o problema. se restaurar o índice original for importante para você, faça o seguinte:

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

Primeiro passo: - Leia a primeira linha, ou seja, todas as colunas e remova todas as colunas duplicadas.

Segunda etapa: - Finalmente, leia apenas as colunas.

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

Corri para este problema onde o forro fornecido pela primeira resposta funcionou bem. No entanto, tive a complicação extra em que a segunda cópia da coluna tinha todos os dados. A primeira cópia não.

A solução foi criar dois quadros de dados dividindo um quadro de dados alternando o operador de negação. Depois de ter os dois quadros de dados, executei uma instrução de junção usando olsuffix . Dessa forma, eu poderia fazer referência e excluir a coluna sem os dados.

- E

Eco de Edmund
fonte
0

A maneira abaixo identificará colunas falsas para revisar o que está errado ao construir o dataframe originalmente.

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