Xóa các hàng có chỉ số trùng lặp (Pandas DataFrame và TimeSeries)


251

Tôi đang đọc một số dữ liệu thời tiết tự động từ web. Các quan sát xảy ra cứ sau 5 phút và được tổng hợp thành các tệp hàng tháng cho mỗi trạm thời tiết. Khi tôi đã phân tích cú pháp một tệp, DataFrame trông giống như thế này:

                      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

Vấn đề tôi gặp phải là đôi khi một nhà khoa học quay lại và sửa chữa các quan sát - không phải bằng cách chỉnh sửa các hàng sai, mà bằng cách nối một hàng trùng lặp vào cuối tệp. Ví dụ đơn giản về trường hợp như vậy được minh họa dưới đây:

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

Và vì vậy tôi cần phải df3trở thành đồng đều:

                       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

Tôi nghĩ rằng việc thêm một cột số hàng ( df3['rownum'] = range(df3.shape[0])) sẽ giúp tôi chọn ra hàng dưới cùng cho bất kỳ giá trị nào của giá trị DatetimeIndex, nhưng tôi bị mắc kẹt trong việc tìm ra các câu lệnh group_byhoặc pivot(hoặc ???) để thực hiện công việc đó.


1
Một cách khác để nhận được các bản sao là dữ liệu hàng giờ vào ban đêm khi đồng hồ được đặt lại để tiết kiệm thời gian ban ngày: 1 giờ sáng, 2, 3, 2, 3 lần nữa, 4 ...
chối

Câu trả lời:


467

Tôi sẽ đề nghị sử dụng phương pháp trùng lặp trên chính Chỉ số Pandas:

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

Trong khi tất cả các phương pháp khác hoạt động, câu trả lời hiện được chấp nhận cho đến nay là ít hiệu quả nhất cho ví dụ được cung cấp. Hơn nữa, trong khi phương thức nhóm chỉ có hiệu suất thấp hơn một chút, tôi thấy phương thức trùng lặp dễ đọc hơn.

Sử dụng dữ liệu mẫu được cung cấp:

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

Lưu ý rằng bạn có thể giữ phần tử cuối cùng bằng cách thay đổi đối số keep.

Cũng cần lưu ý rằng phương pháp này cũng hoạt động với MultiIndex(sử dụng df1 như được chỉ định trong ví dụ của 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

3
loccó thể không cần thiết Đơn giản chỉ cần làm df3 = df3[~df3.index.duplicated(keep='first')], sẽ bỏ tất cả các hàng có chỉ mục trùng lặp ngoại trừ lần xuất hiện đầu tiên.
lingjiankong

1
nó sẽ có ý nghĩa để sử dụng điều này cho chuỗi thời gian rất lớn trong đó các bản sao thường chỉ là giá trị đầu tiên hoặc cuối cùng?
pho mát

1
~ làm gì trong df3 = df3.loc [~ df3.index.d repeatated (keep = 'first')] nếu có ai không ngại trả lời?
jsl5703

3
@ jsl5703 Nó đảo ngược mặt nạ. Vì vậy, nó biến mọi thứ là Đúng Sai và ngược lại. Trong trường hợp này, điều đó có nghĩa là chúng tôi sẽ chọn ra những cái không trùng lặp theo phương pháp.
n8yoder

115

Câu trả lời ban đầu của tôi, hiện đã lỗi thời, được giữ lại để tham khảo.

Một giải pháp đơn giản là sử dụng drop_duplicates

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

Đối với tôi, điều này hoạt động nhanh chóng trên các tập dữ liệu lớn.

Điều này đòi hỏi 'rownum' là cột có các bản sao. Trong ví dụ sửa đổi, 'rownum' không có bản sao, do đó không có gì bị loại bỏ. Điều chúng tôi thực sự muốn là đặt 'cols' thành chỉ mục. Tôi không tìm thấy cách nào để nói với drop_d repeatate chỉ xem xét chỉ mục.

Đây là một giải pháp thêm chỉ mục dưới dạng cột dataframe, loại bỏ trùng lặp trên đó, sau đó xóa cột mới:

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

Và nếu bạn muốn mọi thứ trở lại theo đúng thứ tự, chỉ cần gọi sortvào khung dữ liệu.

df3 = df3.sort()

10
Một biến thể khác về điều này là:df.reset_index().drop_duplicates(cols='index',take_last=True).set_index('index')
Luciano

Mặc dù phương thức này hoạt động nhưng nó cũng tạo ra hai bản sao tạm thời của DataFrame và ít hiệu quả hơn đáng kể so với việc sử dụng chỉ mục trùng lặp hoặc phương thức nhóm được đề xuất làm câu trả lời thay thế.
n8yoder

Nếu chỉ mục của bạn là Đa chỉ số, hãy reset_index()thêm các cột level_0, level_1, v.v. Và nếu chỉ mục của bạn có tên thì tên đó sẽ được sử dụng thay cho nhãn "chỉ mục". Điều đó làm cho điều này nhiều hơn một chút so với một lớp lót để thực hiện đúng cho bất kỳ DataFrame nào. index_label = getattr(df.index, 'names', getattr(df.index, 'name', 'index'))sau cols=index_labelđó set_index(index_labels)và thậm chí điều này không thể đánh lừa được (sẽ không hoạt động đối với các đa diện không tên).
hobs

1
Di chuyển chỉ mục vào một cột, xóa các bản sao và đặt lại chỉ mục là tuyệt vời, đó chính xác là những gì tôi cần!
mxplusb

Cho idx = df.index.name or 'index', người ta cũng có thể làm df2 = df.reset_index(); df2.drop_duplicates(idx, inplace=True); df2.set_index(idx, inplace=True)để tránh các bản sao trung gian (do inplace=True)
Anakhand

67

Ôi trời. Điều này thực sự rất đơn giản!

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

Theo dõi chỉnh sửa 2013-10-29 Trong trường hợp tôi có một vấn đề khá phức tạp MultiIndex, tôi nghĩ rằng tôi thích groupbycách tiếp cận hơn. Đây là ví dụ đơn giản cho hậu thế:

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

và đây là phần quan trọng

# 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

nếu họ có tên, nếu không (nếu một tên là Không), hãy nói level=[0,1]sẽ hoạt động nếu có 2 cấp độ df1.groupby(level=[0,1]).last(). Đây phải là một phần của Pandas dưới dạng miễn phídrop_duplicates
bảnh bao

@dashy yeah. Sử dụng df.index.nameschỉ là một cách dễ dàng để nhóm theo tất cả các cấp của chỉ mục.
Paul H

Giải pháp tuyệt vời, cảm ơn bạn! Tôi cũng sẽ thêm rằng điều này hoạt động xarrayđể xử lý các chỉ số DateTime trùng lặp cũng khiến việc thực hiện ds.resampleds.groupbyhoạt động không thành công
drg

Sửa đổi nhận xét trước đó của tôi: nó hoạt động xarraymiễn là bạn thay đổi grouped = df3.groupby(level=0)thành grouped = df3.groupby(dim='time')hoặc bất kỳ thứ nguyên nào có chứa các bản sao
drg

4

Thật không may, tôi không nghĩ Pandas cho phép một người thả dups khỏi các chỉ số. Tôi muốn đề nghị như sau:

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!

1

Nếu bất cứ ai như tôi thích thao tác dữ liệu có thể xâu chuỗi bằng cách sử dụng ký hiệu dấu chấm gấu trúc (như đường ống), thì những điều sau đây có thể hữu ích:

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

Điều này cho phép xâu chuỗi các câu như thế này:

df3.assign(C=2).query('~index.duplicated()').mean()

Tôi đã thử điều này nhưng không thể làm cho nó hoạt động .. Tôi gặp một lỗi như thế này: TypeError: 'Series' objects are mutable, thus they cannot be hashed.. Điều này có thực sự hiệu quả với bạn không?
Onno Eberhard

1

Loại bỏ trùng lặp (Giữ đầu tiên)

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

Xóa các bản sao (Giữ cuối cùng)

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

Kiểm tra: 10k vòng sử dụng dữ liệu của 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.