les pandas python suppriment les colonnes en double

126

Quel est le moyen le plus simple de supprimer les colonnes en double d'un dataframe?

Je lis un fichier texte contenant des colonnes en double via:

import pandas as pd

df=pd.read_table(fname)

Les noms des colonnes sont:

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

Toutes les colonnes Time et Time Relative contiennent les mêmes données. Je voudrais:

Time, Time Relative, N2, H2

Toutes mes tentatives de suppression, de suppression, etc. telles que:

df=df.T.drop_duplicates().T

Il en résulte des erreurs d'index à valeur unique:

Reindexing only valid with uniquely valued index objects

Désolé d'être un noob Pandas. Toute suggestion serait appréciée.


Détails supplémentaires

Version Pandas: 0.9.0
Version Python: 2.7.3
Windows 7
(installé via Pythonxy 2.7.3.0)

fichier de données (note: dans le fichier réel, les colonnes sont séparées par des tabulations, ici elles sont séparées par 4 espaces):

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
la source
Quelle version de pandas avez-vous? ( import pandas as pd; pd.__version__ )
beardc
1
@BirdJaguarIV, j'utilise pandas version 0.9.0
Onlyjus
Vous pouvez essayer de passer à la version 0.10. Ma version rend les colonnes uniques avec read_tablel'exemple que j'ai composé.
beardc
Attention, df = df.T.drop_duplicates (). T ne considère pas le nom de la colonne. Si vous avez deux colonnes avec les mêmes données mais des noms différents, l'une sera supprimée par erreur.
Joylove

Réponses:

392

Il existe une solution en une seule ligne au problème. Cela s'applique si certains noms de colonnes sont dupliqués et que vous souhaitez les supprimer:

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

Comment ça fonctionne:

Supposons que les colonnes du bloc de données soient ['alpha','beta','alpha']

df.columns.duplicated()renvoie un tableau booléen: a Trueou Falsepour chaque colonne. Si c'est le cas, Falsele nom de la colonne est unique jusqu'à ce point, si c'est le cas, Truele nom de la colonne est dupliqué plus tôt. Par exemple, en utilisant l'exemple donné, la valeur renvoyée serait[False,False,True] .

Pandas permet d'indexer en utilisant des valeurs booléennes, par lesquelles il ne sélectionne que le True valeurs. Puisque nous voulons conserver les colonnes non dupliquées, nous avons besoin du tableau booléen ci-dessus pour être retourné (ie [True, True, False] = ~[False,False,True])

Finalement, df.loc[:,[True,True,False]] sélectionne uniquement les colonnes non dupliquées à l'aide de la capacité d'indexation susmentionnée.

Remarque : ce qui précède ne vérifie que les noms de colonnes, pas les valeurs de colonne.

Gene Burinsky
la source
16
Une réponse idéale fonctionnerait également pour les valeurs dupliquées, pas seulement les noms.
GrimSqueaker
7
@GrimSqueaker: Si vous voulez déterminer si les valeurs sont dupliquées, vous voulez quelque chose comme df.T.drop_duplicates().T.
John Zwinck
3
De loin la solution la plus rapide
AtotheSiv
2
@ VaidøtasIvøška s'il vous plaît voir la 2ème réponse à cette question
Gene Burinsky
2
@JohnZwinck: cela ne fonctionne que pour les petites dataframes, car il y a une limite au nombre de colonnes que vous pouvez avoir. Pour moi, cela a échoué pour une trame de données avec 100000 lignes par exemple, car cela donne 100000 colonnes après la transposition, ce qui n'est pas possible
Eelco van Vliet
40

Il semble que vous connaissez déjà les noms de colonnes uniques. Si tel est le cas, df = df['Time', 'Time Relative', 'N2']cela fonctionnerait.

Sinon, votre solution devrait fonctionner:

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

Vous avez probablement quelque chose de spécifique à vos données qui gâche tout. Nous pourrions vous aider davantage si vous pouviez nous donner plus de détails sur les données.

Edit: Comme Andy l'a dit, le problème vient probablement des titres de colonne en double.

Pour un exemple de fichier de table 'dummy.csv', j'ai créé:

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

l'utilisation read_tabledonne des colonnes uniques et fonctionne correctement:

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 votre version ne le permet pas, vous pouvez pirater ensemble une solution pour les rendre uniques:

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 
barbe
la source
5
Malheureusement, df['Time']sélectionne toutes les séries temporelles (c'est-à-dire renvoie un DataFrame), et df['Time', ..]cela renverra le DataFrame entier.
Andy Hayden
Ouais, c'est assez fastidieux ... j'espère que ce n'est qu'une différence de version.
beardc
2
L'utilisation de doubles transpositions peut avoir des effets secondaires indésirables tels que la conversion de types numériques en objets dans le cas où vous avez un df avec des types mixtes. Voir: stackoverflow.com/questions/24682396/…
Petergavinkin
Cette solution me pose des problèmes sur les grands dataframes: RecursionError: maximum recursion depth exceeded
Scott
La transposition d'une grande trame de données sera un processus lent
Kush Patel
13

La transposition est inefficace pour les grands DataFrames. Voici une alternative:

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       

Utilisez-le comme ceci:

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

Éditer

Une version à mémoire efficace qui traite nans comme toute autre valeur:

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
la source
3
Fonctionne comme un charme, très efficace! L'utilisation my_df.T.drop_duplicates().Taccrocherait des cadres de données volumineux.
Sera
1
Belle solution mais le 26 avril 2017 j'ai eu /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
remplacer if array_equivalent(ia, ja):par if np.array_equal(ia, ja):semble produire les mêmes résultats mais j'ai lu qu'il ne gère pas bien les NaN.
George Fisher
@GeorgeFisher Le code sous-jacent est-il array_equivalenttoujours disponible dans le référentiel public, éventuellement sur une ancienne branche?
kalu
@kalu il y a maintenant un courant numpy.array_equiv; pour les pandas, je ne vois aucune branche de version antérieure sur GitHub, pandas.core.commonmais il y a peut-être d'autres endroits à chercher
George Fisher
12

Si je ne me trompe pas, ce qui suit fait ce qui a été demandé sans les problèmes de mémoire de la solution de transposition et avec moins de lignes que la fonction de @kalu, en conservant la première de toutes les colonnes de nom similaire.

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
la source
Votre solution ne fonctionne pas dans mon cas, elle me montre: "ValueError: labels ['toDROP'] not contents in axis" après l'exécution de la dernière ligne
NuValue
4

Il semble que vous étiez sur la bonne voie. Voici le one-liner que vous recherchiez:

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

Mais comme il n'y a pas de trame de données d'exemple qui produit le message d'erreur référencé Reindexing only valid with uniquely valued index objects, il est difficile de dire exactement ce qui résoudrait le problème. si la restauration de l'index d'origine est importante pour vous, procédez comme suit:

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

Première étape: - Lire la première ligne, c'est-à-dire toutes les colonnes, supprimer toutes les colonnes en double.

Deuxième étape: - Enfin, ne lisez que les colonnes.

cols = pd.read_csv("file.csv", header=None, nrows=1).iloc[0].drop_duplicates()
df = pd.read_csv("file.csv", usecols=cols)
kamran kausar
la source
0

Je suis tombé sur ce problème où la seule doublure fournie par la première réponse fonctionnait bien. Cependant, j'ai eu la complication supplémentaire où la deuxième copie de la colonne avait toutes les données. La première copie ne l'a pas fait.

La solution consistait à créer deux trames de données en divisant la trame de données en basculant l'opérateur de négation. Une fois que j'ai eu les deux trames de données, j'ai exécuté une instruction de jointure en utilisant lelsuffix . De cette façon, je pourrais alors référencer et supprimer la colonne sans les données.

- E

Écho d'Edmund
la source
0

La méthode ci-dessous identifiera les colonnes de dupe pour examiner ce qui ne va pas lors de la création du dataframe à l'origine.

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