Usuń wiersze ze zduplikowanymi indeksami (Pandas DataFrame i TimeSeries)

252

Czytam niektóre automatyczne dane pogodowe z Internetu. Obserwacje odbywają się co 5 minut i są zestawiane w miesięczne pliki dla każdej stacji pogodowej. Po zakończeniu analizowania pliku DataFrame wygląda mniej więcej tak:

                      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

Problemem jest to, że czasami naukowiec cofa się i koryguje obserwacje - nie poprzez edycję błędnych wierszy, ale przez dodanie duplikatu wiersza na końcu pliku. Prosty przykład takiego przypadku pokazano poniżej:

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

A więc muszę df3równomiernie zostać:

                       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

Myślałem, że dodanie kolumny liczb wierszy ( df3['rownum'] = range(df3.shape[0])) pomogłoby mi wybrać najniższy wiersz dla dowolnej wartości DatetimeIndex, ale utknąłem przy ustalaniu instrukcji group_bylub pivot(lub ???) instrukcji, aby to zadziałało.

Paul H.
źródło
1
Innym sposobem uzyskania duplikatów są dane godzinowe w nocy, kiedy zegary są cofane w celu zmiany czasu na letni: 1 AM, 2, 3, 2, 3 ponownie, 4 ...
den

Odpowiedzi:

467

Sugerowałbym użycie zduplikowanej metody na samym indeksie Pandas:

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

Podczas gdy wszystkie inne metody działają, aktualnie akceptowana odpowiedź jest zdecydowanie najmniej wydajna dla podanego przykładu. Ponadto, chociaż metoda groupby jest tylko nieco mniej wydajna, uważam, że powielona metoda jest bardziej czytelna.

Korzystając z podanych przykładowych danych:

>>> %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

Zauważ, że możesz zachować ostatni element, zmieniając argument keep.

Należy również zauważyć, że ta metoda również działa MultiIndex(przy użyciu df1, jak podano w przykładzie Paula ):

>>> %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
źródło
3
locmoże nie być konieczne. Po prostu zrób df3 = df3[~df3.index.duplicated(keep='first')], co spowoduje usunięcie wszystkich wierszy ze zduplikowanym indeksem z wyjątkiem pierwszego wystąpienia.
lingjiankong
1
czy miałoby to sens w przypadku bardzo dużych szeregów czasowych, w których duplikaty są zwykle tylko pierwszą lub ostatnią wartością?
cheesus
1
co ~ robi w df3 = df3.loc [~ df3.index.duplicated (keep = 'first')], jeśli ktoś nie ma nic przeciwko odpowiadaniu?
jsl5703
3
@ jsl5703 Odwraca maskę. Tak więc zamienia wszystko, co było Prawdą Fałsz i odwrotnie. W tym przypadku oznacza to, że wybieramy te, które nie są duplikowane zgodnie z metodą.
n8yoder
115

Moja pierwotna odpowiedź, która jest obecnie nieaktualna, została zachowana w celach informacyjnych.

Prostym rozwiązaniem jest użycie drop_duplicates

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

Dla mnie działało to szybko na dużych zestawach danych.

Wymaga to, aby „rownum” było kolumną z duplikatami. W zmodyfikowanym przykładzie „rownum” nie ma duplikatów, dlatego nic nie jest eliminowane. Naprawdę chcemy, aby „cols” były ustawione na indeks. Nie znalazłem sposobu, aby powiedzieć drop_duplicates, aby uwzględniało tylko indeks.

Oto rozwiązanie, które dodaje indeks jako kolumnę ramki danych, upuszcza na nim duplikaty, a następnie usuwa nową kolumnę:

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

A jeśli chcesz odzyskać wszystko w odpowiedniej kolejności, po prostu wywołaj sortramkę danych.

df3 = df3.sort()
DA
źródło
10
Inną odmianą tego jest:df.reset_index().drop_duplicates(cols='index',take_last=True).set_index('index')
Luciano
Chociaż ta metoda działa, tworzy również dwie tymczasowe kopie DataFrame i jest znacznie mniej wydajna niż użycie duplikatu indeksu lub metod grupowania sugerowanych jako alternatywne odpowiedzi.
n8yoder,
Jeśli indeks jest indeksem MultiIndex, reset_index()dodaje kolumny poziom_0, poziom_1 itd. A jeśli indeks ma nazwę, nazwa ta zostanie użyta zamiast etykiety „indeks”. To sprawia, że ​​jest to coś więcej niż jedno-liniowy, aby zrobić to dobrze dla dowolnej ramki DataFrame. index_label = getattr(df.index, 'names', getattr(df.index, 'name', 'index'))wtedy cols=index_labelwtedy set_index(index_labels)i nawet to nie jest niezawodny (nie będzie pracować dla nienazwanych multiindexes).
płyty grzejne
1
Przeniesienie indeksu do kolumny, usunięcie duplikatów i zresetowanie indeksu było niesamowite, właśnie tego potrzebowałem!
mxplusb
Biorąc pod uwagę idx = df.index.name or 'index', można również df2 = df.reset_index(); df2.drop_duplicates(idx, inplace=True); df2.set_index(idx, inplace=True)uniknąć pośrednich kopii (z powodu inplace=True)
Anakhand
67

O mój. To jest naprawdę takie proste!

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

Kontynuuj edycję 2013-10-29 W przypadku, gdy mam dość skomplikowane MultiIndex, myślę, że wolę takie groupbypodejście. Oto prosty przykład dla potomności:

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

i oto ważna część

# 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.
źródło
jeśli mają nazwy, w przeciwnym razie (jeśli jedna nazwa to Brak), powiedzmy level=[0,1], że będzie działać, jeśli istnieją 2 poziomy df1.groupby(level=[0,1]).last(). To powinno być częścią Pand jako drop_duplicates
dodatek
@dashesy yeah. Używanie df.index.namesjest tylko łatwym sposobem grupowania według wszystkich poziomów indeksu.
Paul H
Świetne rozwiązanie, dziękuję! Dodam też, że działa to również w xarrayprzypadku duplikatów indeksów DateTime, które powodują, że operacje ds.resamplei ds.groupbyoperacje kończą się niepowodzeniem
drg
Poprawka do mojego wcześniejszego komentarza: to działa xarraytak długo, jak zmienić grouped = df3.groupby(level=0)się grouped = df3.groupby(dim='time')albo cokolwiek wymiar jest to, że zawiera duplikaty
DRG
4

Niestety nie sądzę, aby Pandy pozwalały na zrzucanie duplikatów z indeksów. Sugerowałbym następujące:

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!
użytkownik128754
źródło
1

Jeśli ktoś taki jak ja lubi manipulowanie danymi łańcuchowymi za pomocą notacji kropek pandy (np. Potokowanie), przydatne mogą być następujące:

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

Umożliwia to tworzenie łańcuchów takich instrukcji:

df3.assign(C=2).query('~index.duplicated()').mean()
bbiegel
źródło
Próbowałem tego, ale nie udało mi się go uruchomić .. Mam taki błąd: TypeError: 'Series' objects are mutable, thus they cannot be hashed.. Czy to naprawdę działało dla ciebie?
Onno Eberhard
1

Usuń duplikaty (na początku)

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

Usuń duplikaty (Keep Last Last)

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

Testy: 10 000 pętli z wykorzystaniem danych OP

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 The Tuple
źródło