Hapus baris dengan indeks duplikat (Pandas DataFrame dan TimeSeries)

251

Saya membaca beberapa data cuaca otomatis dari web. Pengamatan terjadi setiap 5 menit dan dikompilasi ke dalam file bulanan untuk setiap stasiun cuaca. Setelah saya selesai mengurai file, DataFrame terlihat seperti ini:

                      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

Masalah yang saya alami adalah bahwa kadang-kadang seorang ilmuwan kembali dan mengoreksi pengamatan - bukan dengan mengedit baris yang salah, tetapi dengan menambahkan baris duplikat ke akhir file. Contoh sederhana dari kasus seperti ini diilustrasikan di bawah ini:

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

Jadi saya harus df3menjadi:

                       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

Saya pikir bahwa menambahkan kolom nomor baris ( df3['rownum'] = range(df3.shape[0])) akan membantu saya pilih keluar paling bawah baris untuk setiap nilai DatetimeIndex, tapi saya terjebak pada mencari tahu group_byatau pivotpernyataan (atau ???) untuk membuat pekerjaan itu.

Paul H.
sumber
1
Cara lain untuk mendapatkan duplikat adalah data setiap jam di malam hari ketika jam diatur kembali untuk menghemat waktu siang hari: 1 pagi, 2, 3, 2, 3 lagi, 4 ...
denis

Jawaban:

467

Saya akan menyarankan menggunakan metode duplikat pada Indeks Pandas itu sendiri:

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

Meskipun semua metode lain berfungsi, jawaban yang saat ini diterima adalah yang paling sedikit performan untuk contoh yang diberikan. Selain itu, walaupun metode groupby hanya sedikit performan, saya menemukan metode duplikat lebih mudah dibaca.

Menggunakan data sampel yang disediakan:

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

Perhatikan bahwa Anda dapat menyimpan elemen terakhir dengan mengubah argumen keep.

Juga harus dicatat bahwa metode ini juga berfungsi dengan MultiIndexbaik (menggunakan df1 seperti yang ditentukan dalam contoh Paul ):

>>> %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
sumber
3
locmungkin tidak perlu. Cukup lakukan df3 = df3[~df3.index.duplicated(keep='first')], yang akan menjatuhkan semua baris dengan indeks duplikat kecuali kejadian pertama.
lingjiankong
1
apakah masuk akal untuk menggunakan ini untuk time-series yang sangat besar di mana duplikat biasanya hanya nilai pertama atau terakhir?
cheesus
1
apa yang ~ lakukan di df3 = df3.loc [~ df3.index.duplicated (keep = 'first')] jika ada yang tidak keberatan menjawab?
jsl5703
3
@ jsl5703 Ini membalikkan topeng. Jadi ternyata semua yang Benar Salah dan sebaliknya. Dalam hal ini, itu berarti bahwa kami akan memilih yang tidak digandakan sesuai dengan metode.
n8yoder
115

Jawaban asli saya, yang sekarang sudah usang, disimpan untuk referensi.

Solusi sederhana adalah menggunakan drop_duplicates

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

Bagi saya, ini beroperasi dengan cepat pada set data besar.

Ini mengharuskan 'rownum' menjadi kolom dengan duplikat. Dalam contoh yang dimodifikasi, 'rownum' tidak memiliki duplikat, oleh karena itu tidak ada yang dihilangkan. Yang kami inginkan adalah agar 'cols' disetel ke indeks. Saya belum menemukan cara untuk memberi tahu drop_duplicates untuk hanya mempertimbangkan indeks.

Berikut adalah solusi yang menambahkan indeks sebagai kolom dataframe, menjatuhkan duplikat pada itu, lalu menghapus kolom baru:

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

Dan jika Anda ingin semuanya kembali dalam urutan yang tepat, panggil saja sortkerangka data.

df3 = df3.sort()
DA
sumber
10
Variasi lain dalam hal ini adalah:df.reset_index().drop_duplicates(cols='index',take_last=True).set_index('index')
Luciano
Sementara metode ini berhasil, ia juga membuat dua salinan sementara dari DataFrame dan secara signifikan lebih sedikit performanya daripada menggunakan indeks yang diduplikasi atau metode groupby yang disarankan sebagai jawaban alternatif.
n8yoder
Jika indeks Anda adalah MultiIndex, reset_index()tambahkan kolom level_0, level_1, dll. Dan jika indeks Anda memiliki nama, nama itu akan digunakan sebagai pengganti label "indeks". Itu membuat ini sedikit lebih dari satu-liner untuk melakukannya dengan benar untuk DataFrame apa pun. index_label = getattr(df.index, 'names', getattr(df.index, 'name', 'index'))lalu cols=index_labelkemudian set_index(index_labels)dan bahkan ini tidak mudah (tidak akan bekerja untuk multiindex yang tidak disebutkan namanya).
hobs
1
Memindahkan indeks ke kolom, menghapus duplikat, dan mengatur ulang indeks itu luar biasa, itulah yang saya butuhkan!
mxplusb
Mengingat idx = df.index.name or 'index', orang juga bisa melakukan df2 = df.reset_index(); df2.drop_duplicates(idx, inplace=True); df2.set_index(idx, inplace=True)untuk menghindari salinan perantara (karena inplace=True)
Anakhand
67

Astaga. Ini sebenarnya sangat sederhana!

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

Sunting lanjut 2013-10-29 Dalam kasus di mana saya memiliki yang cukup kompleks MultiIndex, saya pikir saya lebih suka groupbypendekatan. Inilah contoh sederhana untuk anak cucu:

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

dan inilah bagian yang penting

# 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.
sumber
jika mereka memiliki nama, jika tidak (jika satu nama adalah Tidak Ada) katakanlah level=[0,1]akan berfungsi jika ada 2 level df1.groupby(level=[0,1]).last(). Ini harus menjadi bagian dari Panda sebagai drop_duplicates
pelengkap
@ bulu mata ya. Menggunakan df.index.nameshanyalah cara mudah untuk mengelompokkan berdasarkan semua level indeks.
Paul H
Solusi hebat, terima kasih! Saya juga akan menambahkan bahwa ini berfungsi xarrayuntuk berurusan dengan indeks DateTime duplikat juga yang membuat ds.resampledan ds.groupbyoperasi gagal
drg
Amandemen komentar saya sebelumnya: ini berfungsi xarrayselama Anda mengubah grouped = df3.groupby(level=0)to grouped = df3.groupby(dim='time')atau dimensi apa pun yang mengandung duplikat
drg
4

Sayangnya, saya tidak berpikir Pandas memungkinkan seseorang untuk menjatuhkan dups dari indeks. Saya akan menyarankan yang berikut ini:

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!
pengguna128754
sumber
1

Jika ada orang seperti saya suka manipulasi data yang dapat di rantai menggunakan notasi titik panda (seperti perpipaan), maka hal berikut ini mungkin berguna:

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

Ini memungkinkan pernyataan rantai seperti ini:

df3.assign(C=2).query('~index.duplicated()').mean()
bbiegel
sumber
Saya mencoba ini tetapi tidak bisa berfungsi .. Saya mendapatkan kesalahan seperti ini: TypeError: 'Series' objects are mutable, thus they cannot be hashed.. Apakah ini benar-benar bekerja untuk Anda?
Onno Eberhard
1

Hapus duplikat (Keeping First)

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

Hapus duplikat (Menjaga Terakhir)

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

Pengujian: loop 10k menggunakan data 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
sumber