Python-Pandas entfernen doppelte Spalten


125

Was ist der einfachste Weg, um doppelte Spalten aus einem Datenrahmen zu entfernen?

Ich lese eine Textdatei mit doppelten Spalten über:

import pandas as pd

df=pd.read_table(fname)

Die Spaltennamen sind:

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

Alle Spalten "Zeit" und "Zeitrelativ" enthalten dieselben Daten. Ich will:

Time, Time Relative, N2, H2

Alle meine Versuche zu löschen, zu löschen usw. wie:

df=df.T.drop_duplicates().T

Ergebnis zu eindeutig bewerteten Indexfehlern:

Reindexing only valid with uniquely valued index objects

Tut mir leid, dass ich ein Pandas Noob bin. Anregungen wäre dankbar.


Weitere Details

Pandas-Version: 0.9.0
Python-Version: 2.7.3
Windows 7
(installiert über Pythonxy 2.7.3.0)

Datendatei (Hinweis: In der realen Datei werden Spalten durch Tabulatoren getrennt, hier durch 4 Leerzeichen):

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

Welche Version von Pandas hast du? ( import pandas as pd; pd.__version__ )
Bart

1
@BirdJaguarIV, ich benutze Pandas Version 0.9.0
Onlyjus

Möglicherweise möchten Sie ein Upgrade auf 0.10 versuchen. Meine Version macht die Spalten read_tablefür das Beispiel, das ich erstellt habe, einzigartig .
Bart

Beachten Sie, dass df = df.T.drop_duplicates (). T den Spaltennamen nicht berücksichtigt. Wenn Sie zwei Spalten mit denselben Daten, aber unterschiedlichen Namen haben, wird eine fälschlicherweise gelöscht.
Joylove

Antworten:


389

Es gibt eine einzeilige Lösung für das Problem. Dies gilt, wenn einige Spaltennamen dupliziert sind und Sie sie entfernen möchten:

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

Wie es funktioniert:

Angenommen, die Spalten des Datenrahmens sind ['alpha','beta','alpha']

df.columns.duplicated()Gibt ein boolesches Array zurück: a Trueoder Falsefür jede Spalte. Wenn dies Falseder Fall ist, ist der Spaltenname bis zu diesem Punkt eindeutig. Wenn dies der Fall ist, wird Trueder Spaltenname früher dupliziert. In dem angegebenen Beispiel wäre der zurückgegebene Wert beispielsweise [False,False,True].

Pandasermöglicht die Indizierung mit booleschen Werten, wobei nur die TrueWerte ausgewählt werden. Da wir die nicht duplizierten Spalten behalten möchten, muss das obige boolesche Array gespiegelt werden (dh [True, True, False] = ~[False,False,True])

Schließlich werden df.loc[:,[True,True,False]]nur die nicht duplizierten Spalten unter Verwendung der oben genannten Indizierungsfunktion ausgewählt.

Hinweis : Das obige prüft nur Spaltennamen , keine Spaltenwerte.


15
Eine ideale Antwort würde auch für doppelte Werte funktionieren, nicht nur für Namen.
GrimSqueaker

7
@GrimSqueaker: Wenn Sie prüfen möchten, ob die Werte dupliziert sind, möchten Sie so etwas wie df.T.drop_duplicates().T.
John Zwinck

3
Mit Abstand die schnellste Lösung
AtotheSiv

2
@ VaidøtasIvøška siehe die 2. Antwort auf diese Frage
Gene Burinsky

2
@JohnZwinck: Dies funktioniert nur für kleine Datenrahmen, da die Anzahl der Spalten begrenzt ist. Für mich ist es beispielsweise bei einem Datenrahmen mit 100.000 Zeilen fehlgeschlagen, da dies nach dem Transponieren 100.000 Spalten ergibt, was nicht möglich ist
Eelco van Vliet

40

Es hört sich so an, als ob Sie die eindeutigen Spaltennamen bereits kennen. Wenn das der Fall ist, df = df['Time', 'Time Relative', 'N2']würde es funktionieren.

Wenn nicht, sollte Ihre Lösung funktionieren:

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

Sie haben wahrscheinlich etwas Spezielles für Ihre Daten, das es durcheinander bringt. Wir könnten mehr Hilfe geben, wenn Sie uns mehr Details zu den Daten geben könnten.

Bearbeiten: Wie Andy sagte, liegt das Problem wahrscheinlich bei den doppelten Spaltentiteln.

Für eine Beispieltabellendatei 'dummy.csv' habe ich Folgendes zusammengestellt:

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

using read_tablegibt eindeutige Spalten und funktioniert ordnungsgemäß:

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  

Wenn Ihre Version dies nicht zulässt, können Sie eine Lösung zusammenstellen, um sie einzigartig zu machen:

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 

5
df['Time']Wählt leider alle Zeitreihen aus (dh gibt einen DataFrame zurück), und df['Time', ..]dies gibt den gesamten DataFrame zurück.
Andy Hayden

Ja, es ist ziemlich langweilig ... hoffentlich ist es nur ein Versionsunterschied.
Bart

2
Die Verwendung von doppelten Transponierungen kann unbeabsichtigte Nebenwirkungen haben, z. B. das Konvertieren numerischer Typen in Objekte, falls Sie eine df mit gemischten Typen haben. Siehe: stackoverflow.com/questions/24682396/…
Petergavinkin

Diese Lösung gibt mir Probleme auf großen Datenrahmen: RecursionError: maximum recursion depth exceeded
Scott

Die Transponierung eines großen
Kush Patel

13

Das Transponieren ist für große DataFrames ineffizient. Hier ist eine 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       

Verwenden Sie es so:

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

Bearbeiten

Eine speichereffiziente Version, die Nans wie jeden anderen Wert behandelt:

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

3
Funktioniert wie ein Zauber, sehr effizient! Die Verwendung my_df.T.drop_duplicates().Twürde an großen Datenrahmen hängen.
Will

1
Schöne Lösung, aber am 26. April 2017 bekam ich /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

Das Ersetzen if array_equivalent(ia, ja):durch if np.array_equal(ia, ja):scheint die gleichen Ergebnisse zu liefern, aber ich habe gelesen, dass es NaNs nicht gut handhabt.
George Fisher

@GeorgeFisher Ist der zugrunde liegende Code für array_equivalentnoch im öffentlichen Repo verfügbar, möglicherweise in einer älteren Filiale?
Kalu

@kalu gibt es jetzt eine Strömung numpy.array_equiv; Für Pandas sehe ich keine früheren Release-Zweige auf GitHub, pandas.core.commonaber vielleicht gibt es noch andere Orte, an denen man suchen kann
George Fisher,

11

Wenn ich mich nicht irre, wird im Folgenden das getan, was ohne die Speicherprobleme der Transponierungslösung und mit weniger Zeilen als die Funktion von @kalu gefragt wurde, wobei die erste von ähnlich benannten Spalten beibehalten wird.

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)

Ihre Lösung funktioniert in meinem Fall nicht, sie zeigt mir: "ValueError: Labels ['toDROP'] nicht in Achse enthalten" nach Ausführung der letzten Zeile
NuValue

4

Es sieht so aus, als wären Sie auf dem richtigen Weg. Hier ist der Einzeiler, den Sie gesucht haben:

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

Da es jedoch keinen Beispieldatenrahmen gibt, der die referenzierte Fehlermeldung erzeugt Reindexing only valid with uniquely valued index objects, ist es schwierig, genau zu sagen, was das Problem lösen würde. Wenn es Ihnen wichtig ist, den ursprünglichen Index wiederherzustellen, gehen Sie wie folgt vor:

original_index = df.index.names
df.reset_index().T.drop_duplicates().reset_index(original_index).T

0

Erster Schritt: - Lesen Sie die erste Zeile, dh alle Spalten, und entfernen Sie alle doppelten Spalten.

Zweiter Schritt: - Lesen Sie schließlich nur diese Spalten.

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

0

Ich bin auf dieses Problem gestoßen, bei dem der von der ersten Antwort bereitgestellte Einzeiler gut funktioniert hat. Ich hatte jedoch die zusätzliche Komplikation, dass die zweite Kopie der Spalte alle Daten enthielt. Die erste Kopie nicht.

Die Lösung bestand darin, zwei Datenrahmen durch Teilen des einen Datenrahmens durch Umschalten des Negationsoperators zu erstellen. Sobald ich die beiden Datenrahmen hatte, führte ich eine Join-Anweisung mit dem aus lsuffix. Auf diese Weise konnte ich dann die Spalte ohne die Daten referenzieren und löschen.

- E.


0

Auf die folgende Weise werden betrogene Spalten identifiziert, um zu überprüfen, was beim ursprünglichen Erstellen des Datenrahmens falsch läuft.

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.