Entfernen Sie Zeilen mit doppelten Indizes (Pandas DataFrame und TimeSeries).

251

Ich lese einige automatisierte Wetterdaten aus dem Internet. Die Beobachtungen erfolgen alle 5 Minuten und werden für jede Wetterstation in monatlichen Dateien zusammengestellt. Sobald ich eine Datei analysiert habe, sieht der DataFrame ungefähr so ​​aus:

                      Sta  Precip1hr  Precip5min  Temp  DewPnt  WindSpd  WindDir  AtmPress
Date                                                                                      
2001-01-01 00:00:00  KPDX          0           0     4       3        0        0     30.31
2001-01-01 00:05:00  KPDX          0           0     4       3        0        0     30.30
2001-01-01 00:10:00  KPDX          0           0     4       3        4       80     30.30
2001-01-01 00:15:00  KPDX          0           0     3       2        5       90     30.30
2001-01-01 00:20:00  KPDX          0           0     3       2       10      110     30.28

Das Problem, das ich habe, ist, dass ein Wissenschaftler manchmal zurückgeht und Beobachtungen korrigiert - nicht indem er die fehlerhaften Zeilen bearbeitet, sondern indem er eine doppelte Zeile an das Ende einer Datei anfügt. Ein einfaches Beispiel für einen solchen Fall ist unten dargestellt:

import pandas 
import datetime
startdate = datetime.datetime(2001, 1, 1, 0, 0)
enddate = datetime.datetime(2001, 1, 1, 5, 0)
index = pandas.DatetimeIndex(start=startdate, end=enddate, freq='H')
data1 = {'A' : range(6), 'B' : range(6)}
data2 = {'A' : [20, -30, 40], 'B' : [-50, 60, -70]}
df1 = pandas.DataFrame(data=data1, index=index)
df2 = pandas.DataFrame(data=data2, index=index[:3])
df3 = df2.append(df1)
df3
                       A   B
2001-01-01 00:00:00   20 -50
2001-01-01 01:00:00  -30  60
2001-01-01 02:00:00   40 -70
2001-01-01 03:00:00    3   3
2001-01-01 04:00:00    4   4
2001-01-01 05:00:00    5   5
2001-01-01 00:00:00    0   0
2001-01-01 01:00:00    1   1
2001-01-01 02:00:00    2   2

Und so muss ich df3gleich werden:

                       A   B
2001-01-01 00:00:00    0   0
2001-01-01 01:00:00    1   1
2001-01-01 02:00:00    2   2
2001-01-01 03:00:00    3   3
2001-01-01 04:00:00    4   4
2001-01-01 05:00:00    5   5

Ich dachte , dass eine Spalte mit Zeilennummern hinzugefügt ( df3['rownum'] = range(df3.shape[0])) würde mir helfen , die unterste Zeile für jeden Wert der Auswahl aus DatetimeIndex, aber ich bin fest auf die herauszufinden , group_byoder pivot(oder ???) Anweisungen , um diese Arbeit zu machen.

Paul H.
quelle
1
Eine andere Möglichkeit, Duplikate zu erhalten, sind stündliche Daten in der Nacht, wenn die Uhren auf Sommerzeit zurückgesetzt werden: 1 Uhr morgens, 2, 3, 2, 3 Uhr wieder, 4 ...
denis

Antworten:

467

Ich würde vorschlagen, die duplizierte Methode im Pandas-Index selbst zu verwenden:

df3 = df3.loc[~df3.index.duplicated(keep='first')]

Während alle anderen Methoden funktionieren, ist die derzeit akzeptierte Antwort für das angegebene Beispiel bei weitem die am wenigsten leistungsfähige. Während die Groupby-Methode nur geringfügig weniger performant ist, finde ich die duplizierte Methode besser lesbar.

Verwendung der bereitgestellten Beispieldaten:

>>> %timeit df3.reset_index().drop_duplicates(subset='index', keep='first').set_index('index')
1000 loops, best of 3: 1.54 ms per loop

>>> %timeit df3.groupby(df3.index).first()
1000 loops, best of 3: 580 µs per loop

>>> %timeit df3[~df3.index.duplicated(keep='first')]
1000 loops, best of 3: 307 µs per loop

Beachten Sie, dass Sie das letzte Element behalten können, indem Sie das Argument keep ändern.

Es sollte auch beachtet werden, dass diese Methode auch funktioniert MultiIndex(unter Verwendung von df1, wie in Pauls Beispiel angegeben ):

>>> %timeit df1.groupby(level=df1.index.names).last()
1000 loops, best of 3: 771 µs per loop

>>> %timeit df1[~df1.index.duplicated(keep='last')]
1000 loops, best of 3: 365 µs per loop
n8yoder
quelle
3
locmöglicherweise nicht notwendig. Tun Sie dies einfach df3 = df3[~df3.index.duplicated(keep='first')], wodurch alle Zeilen mit doppeltem Index bis auf das erste Vorkommen gelöscht werden.
Lingjiankong
1
Wäre es sinnvoll, dies für sehr große Zeitreihen zu verwenden, bei denen die Duplikate normalerweise nur der erste oder der letzte Wert sind?
Cheesus
1
Was macht ~ in df3 = df3.loc [~ df3.index.duplicated (keep = 'first')], wenn es jemandem nichts ausmacht zu antworten?
jsl5703
3
@ jsl5703 Es kehrt die Maske um. Also dreht es alles, was wahr war, falsch und umgekehrt. In diesem Fall bedeutet dies, dass wir diejenigen auswählen, die nicht gemäß der Methode dupliziert wurden.
n8yoder
115

Meine ursprüngliche Antwort, die jetzt veraltet ist, wurde als Referenz aufbewahrt.

Eine einfache Lösung ist zu verwenden drop_duplicates

df4 = df3.drop_duplicates(subset='rownum', keep='last')

Für mich funktionierte dies schnell bei großen Datenmengen.

Dies erfordert, dass 'rownum' die Spalte mit Duplikaten ist. In dem modifizierten Beispiel hat 'rownum' keine Duplikate, daher wird nichts eliminiert. Was wir wirklich wollen, ist, dass die 'cols' auf den Index gesetzt werden. Ich habe keine Möglichkeit gefunden, drop_duplicates anzuweisen, nur den Index zu berücksichtigen.

Hier ist eine Lösung, die den Index als Datenrahmenspalte hinzufügt, Duplikate darauf löscht und dann die neue Spalte entfernt:

df3 = df3.reset_index().drop_duplicates(subset='index', keep='last').set_index('index')

Und wenn Sie die Dinge wieder in der richtigen Reihenfolge haben möchten, rufen Sie einfach sortden Datenrahmen auf.

df3 = df3.sort()
DA
quelle
10
Eine andere Variation davon ist:df.reset_index().drop_duplicates(cols='index',take_last=True).set_index('index')
Luciano
Diese Methode funktioniert zwar, erstellt jedoch auch zwei temporäre Kopien des DataFrame und ist erheblich weniger leistungsfähig als die Verwendung der als alternative Antworten vorgeschlagenen Methoden für duplizierten Index oder Groupby.
n8yoder
Wenn Ihr Index ein MultiIndex ist, werden reset_index()die Spalten level_0, level_1 usw. hinzugefügt. Wenn Ihr Index einen Namen hat, wird dieser Name anstelle der Bezeichnung "index" verwendet. Das macht dies zu etwas mehr als einem Einzeiler, um es für jeden DataFrame richtig zu machen. index_label = getattr(df.index, 'names', getattr(df.index, 'name', 'index'))dann cols=index_labeldann set_index(index_labels)und auch das ist nicht kinderleicht (funktioniert nicht für unbenannte Multiindizes).
Kochfelder
1
Das Verschieben des Index in eine Spalte, das Löschen von Duplikaten und das Zurücksetzen des Index war fantastisch, genau das brauchte ich!
mxplusb
Gegeben idx = df.index.name or 'index', man könnte auch tun df2 = df.reset_index(); df2.drop_duplicates(idx, inplace=True); df2.set_index(idx, inplace=True), um die Zwischenkopien (aufgrund der inplace=True) zu vermeiden
Anakhand
67

Oh mein. Das ist eigentlich so einfach!

grouped = df3.groupby(level=0)
df4 = grouped.last()
df4
                      A   B  rownum

2001-01-01 00:00:00   0   0       6
2001-01-01 01:00:00   1   1       7
2001-01-01 02:00:00   2   2       8
2001-01-01 03:00:00   3   3       3
2001-01-01 04:00:00   4   4       4
2001-01-01 05:00:00   5   5       5

Follow-up-Bearbeitung 29.10.2013 Wenn ich einen ziemlich komplexen Ansatz MultiIndexhabe, bevorzuge ich den groupbyAnsatz. Hier ist ein einfaches Beispiel für die Nachwelt:

import numpy as np
import pandas

# fake index
idx = pandas.MultiIndex.from_tuples([('a', letter) for letter in list('abcde')])

# random data + naming the index levels
df1 = pandas.DataFrame(np.random.normal(size=(5,2)), index=idx, columns=['colA', 'colB'])
df1.index.names = ['iA', 'iB']

# artificially append some duplicate data
df1 = df1.append(df1.select(lambda idx: idx[1] in ['c', 'e']))
df1
#           colA      colB
#iA iB                    
#a  a  -1.297535  0.691787
#   b  -1.688411  0.404430
#   c   0.275806 -0.078871
#   d  -0.509815 -0.220326
#   e  -0.066680  0.607233
#   c   0.275806 -0.078871  # <--- dup 1
#   e  -0.066680  0.607233  # <--- dup 2

und hier ist der wichtige Teil

# group the data, using df1.index.names tells pandas to look at the entire index
groups = df1.groupby(level=df1.index.names)  
groups.last() # or .first()
#           colA      colB
#iA iB                    
#a  a  -1.297535  0.691787
#   b  -1.688411  0.404430
#   c   0.275806 -0.078871
#   d  -0.509815 -0.220326
#   e  -0.066680  0.607233
Paul H.
quelle
Wenn sie Namen haben, level=[0,1]funktioniert dies andernfalls (wenn ein Name None ist), wenn es zwei Ebenen gibt df1.groupby(level=[0,1]).last(). Dies sollte ein Teil von Pandas sein, als Ergänzung zudrop_duplicates
schneidigen
@ Dashesy ja. Die Verwendung df.index.namesist nur eine einfache Möglichkeit, nach allen Ebenen des Index zu gruppieren.
Paul H
Tolle Lösung, danke! Ich werde auch hinzufügen, dass dies auch xarrayfür den Umgang mit doppelten DateTime-Indizes funktioniert, bei denen make ds.resampleund ds.groupbyOperationen fehlschlagen
drg
Geänderte meinen früheren Kommentar: Es funktioniert in xarrayso lange , wie Sie die Änderung grouped = df3.groupby(level=0)zu grouped = df3.groupby(dim='time')oder was auch immer die Dimension ist , dass enthält Duplikate
drg
4

Leider glaube ich nicht, dass Pandas es einem erlaubt, Dups von den Indizes zu streichen. Ich würde folgendes vorschlagen:

df3 = df3.reset_index() # makes date column part of your data
df3.columns = ['timestamp','A','B','rownum'] # set names
df3 = df3.drop_duplicates('timestamp',take_last=True).set_index('timestamp') #done!
user128754
quelle
1

Wenn jemand wie ich eine verkettbare Datenmanipulation mit der Pandas-Punktnotation (wie Piping) mag, kann Folgendes nützlich sein:

df3 = df3.query('~index.duplicated()')

Dies ermöglicht die Verkettung von Anweisungen wie folgt:

df3.assign(C=2).query('~index.duplicated()').mean()
bbiegel
quelle
Ich habe es versucht, konnte es aber nicht zum Laufen bringen. Ich erhalte die folgende Fehlermeldung: TypeError: 'Series' objects are mutable, thus they cannot be hashedHat das tatsächlich bei Ihnen funktioniert?
Onno Eberhard
1

Duplikate entfernen (Keeping First)

idx = np.unique( df.index.values, return_index = True )[1]
df = df.iloc[idx]

Duplikate entfernen (Keeping Last)

df = df[::-1]
df = df.iloc[ np.unique( df.index.values, return_index = True )[1] ]

Tests: 10k-Schleifen unter Verwendung der OP-Daten

numpy method - 3.03 seconds
df.loc[~df.index.duplicated(keep='first')] - 4.43 seconds
df.groupby(df.index).first() - 21 seconds
reset_index() method - 29 seconds
Mott das Tupel
quelle