ลบแถวที่มีดัชนีซ้ำกัน (Pandas DataFrame และ TimeSeries)


252

ฉันกำลังอ่านข้อมูลสภาพอากาศอัตโนมัติจากเว็บ การสังเกตเกิดขึ้นทุก 5 นาทีและรวบรวมเป็นไฟล์รายเดือนสำหรับแต่ละสถานีตรวจอากาศ เมื่อฉันแยกไฟล์เสร็จแล้ว DataFrame จะมีลักษณะดังนี้:

                      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

ปัญหาที่ฉันมีอยู่คือบางครั้งนักวิทยาศาสตร์กลับไปและแก้ไขข้อสังเกต - ไม่ใช่โดยการแก้ไขแถวที่ผิดพลาด แต่ด้วยการต่อแถวที่ซ้ำกันไปยังส่วนท้ายของไฟล์ ตัวอย่างง่ายๆของกรณีเช่นนี้แสดงไว้ด้านล่าง:

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

ดังนั้นฉันจำเป็นต้องdf3เป็นอย่างเท่าเทียมกัน:

                       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

ฉันคิดว่าการเพิ่มคอลัมน์หมายเลขแถว ( df3['rownum'] = range(df3.shape[0])) จะช่วยให้ฉันเลือกแถวล่างสุดสำหรับค่าใด ๆ ของDatetimeIndexแต่ฉันติดอยู่กับการหาคำสั่งgroup_byหรือpivot(หรือ ???) เพื่อให้งานนั้น


1
วิธีการรับซ้ำกันก็คือข้อมูลรายชั่วโมงในเวลากลางคืนเมื่อนาฬิกาเป็นชุดกลับมาบันทึกเวลา: 01:00, 2, 3, 2, 3 อีกครั้ง 4 ...
เดนิส

คำตอบ:


467

ฉันขอแนะนำให้ใช้วิธีการซ้ำซ้อนในดัชนี Pandas เอง:

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

ในขณะที่วิธีการอื่น ๆ ทั้งหมดทำงานได้คำตอบที่ได้รับการยอมรับในขณะนี้คือตัวอย่างที่มีประสิทธิภาพน้อยที่สุด นอกจากนี้ในขณะที่วิธีการ groupbyมีประสิทธิภาพน้อยกว่าเล็กน้อย แต่ฉันพบว่าวิธีที่ทำซ้ำนั้นสามารถอ่านได้มากขึ้น

การใช้ข้อมูลตัวอย่างที่มีให้:

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

โปรดทราบว่าคุณสามารถเก็บองค์ประกอบสุดท้ายโดยเปลี่ยนอาร์กิวเมนต์เก็บ

ควรสังเกตว่าวิธีนี้ใช้ได้กับMultiIndexเช่นกัน (ใช้ df1 ตามที่ระบุในตัวอย่างของ 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
locอาจไม่จำเป็น เพียงแค่ทำdf3 = df3[~df3.index.duplicated(keep='first')]ซึ่งจะลดลงทุกแถวที่มีดัชนีซ้ำกันยกเว้นการเกิดขึ้นครั้งแรก
lingjiankong

1
มันจะสมเหตุสมผลไหมที่จะใช้สิ่งนี้สำหรับอนุกรมเวลาที่มีขนาดใหญ่มากซึ่งการทำซ้ำมักจะเป็นเพียงค่าแรกหรือค่าสุดท้าย
ชีส

1
~ ทำอะไรใน df3 = df3.loc [~ df3.index.duplicated (keep = 'first')] ถ้าใครไม่สนใจที่จะตอบ?
jsl5703

3
@ jsl5703 มันกลับมาสก์ ดังนั้นจึงเปลี่ยนทุกอย่างที่เป็นเท็จจริงและในทางกลับกัน ในกรณีนี้หมายความว่าเราจะเลือกรายการที่ไม่ซ้ำตามวิธีการ
n8yoder

115

คำตอบเดิมของฉันซึ่งตอนนี้ล้าสมัยเก็บไว้เพื่อการอ้างอิง

ทางออกที่ง่ายคือการใช้ drop_duplicates

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

สำหรับฉันมันทำงานได้อย่างรวดเร็วในชุดข้อมูลขนาดใหญ่

สิ่งนี้ต้องการให้ 'rownum' เป็นคอลัมน์ที่มีรายการซ้ำ ในตัวอย่างที่แก้ไขแล้ว 'rownum' ไม่มีการซ้ำซ้อนดังนั้นจึงไม่มีสิ่งใดถูกกำจัด สิ่งที่เราต้องการจริงๆคือให้ตั้งค่า 'cols' เป็นดัชนี ฉันไม่พบวิธีบอก drop_duplicates ให้พิจารณาเฉพาะดัชนี

นี่คือวิธีการแก้ปัญหาที่เพิ่มดัชนีเป็นคอลัมน์ dataframe ปล่อยรายการซ้ำจากนั้นลบคอลัมน์ใหม่:

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

และถ้าคุณต้องการสิ่งต่าง ๆ กลับมาในลำดับที่ถูกต้องเพียงโทรหาsortdataframe

df3 = df3.sort()

10
การเปลี่ยนแปลงอื่น ๆ ในเรื่องนี้คือ:df.reset_index().drop_duplicates(cols='index',take_last=True).set_index('index')
Luciano

ในขณะที่วิธีการนี้ใช้งานได้มันยังสร้างสำเนาชั่วคราวสองชุดของ DataFrame และมีประสิทธิภาพน้อยกว่าการใช้ดัชนีที่ซ้ำกันหรือวิธีกลุ่มโดยแนะนำเป็นคำตอบทางเลือก
n8yoder

หากดัชนีของคุณคือ MultiIndex ให้reset_index()เพิ่มคอลัมน์ level_0, level_1 ฯลฯ และหากดัชนีของคุณมีชื่อที่จะใช้ชื่อแทนป้าย "index" ทำให้นี่เป็นมากกว่าซับไลน์หนึ่งเดียวที่จะทำ DataFrame ให้ถูกต้อง index_label = getattr(df.index, 'names', getattr(df.index, 'name', 'index'))จากcols=index_labelนั้นset_index(index_labels)และแม้กระทั่งนี้ก็ไม่สามารถป้องกันได้ (จะไม่ทำงานสำหรับ multiindexes ที่ไม่มีชื่อ)
hobs

1
การย้ายดัชนีไปยังคอลัมน์การล้างข้อมูลที่ซ้ำกันและการรีเซ็ตดัชนีนั้นยอดเยี่ยมนั่นคือสิ่งที่ฉันต้องการ!
mxplusb

ให้idx = df.index.name or 'index'ใครสามารถทำdf2 = df.reset_index(); df2.drop_duplicates(idx, inplace=True); df2.set_index(idx, inplace=True)เพื่อหลีกเลี่ยงสำเนากลาง (เนื่องจากinplace=True)
Anakhand

67

พุทโธ่. อันนี้ง่ายจริงๆ!

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

ติดตามการแก้ไข 2013-10-29 ในกรณีที่ฉันมีความซับซ้อนค่อนข้างMultiIndexฉันคิดว่าฉันชอบgroupbyวิธีการ นี่เป็นตัวอย่างง่ายๆสำหรับคนรุ่นหลัง:

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

และนี่คือส่วนสำคัญ

# 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

ถ้าพวกเขามีชื่อเป็นอย่างอื่น (ถ้ามีชื่อหนึ่งคือไม่มี) ช่วยบอกlevel=[0,1]จะทำงานถ้ามี 2 df1.groupby(level=[0,1]).last()ระดับ นี้ควรจะเป็นส่วนหนึ่งของนุ่นเป็นบริการฟรีแก่drop_duplicates
dashesy

@dashesy ใช่ การใช้df.index.namesเป็นวิธีง่ายๆในการจัดกลุ่มตามดัชนีทุกระดับ
พอลเอช

ทางออกที่ดีขอบคุณ! ฉันยังจะเพิ่มว่างานนี้ในxarrayการจัดการกับดัชนี DateTime ซ้ำกันเช่นเดียวกับที่ทำds.resampleและds.groupbyการดำเนินงานล้มเหลว
DRG

แก้ไขความคิดเห็นก่อนหน้าของฉัน: มันทำงานได้xarrayตราบใดที่คุณเปลี่ยนgrouped = df3.groupby(level=0)ไปเป็นgrouped = df3.groupby(dim='time')หรืออะไรก็ตามมิติที่มีซ้ำกัน
drg

4

โชคไม่ดีที่ฉันไม่คิดว่านุ่นจะอนุญาตให้คนหนึ่งวางดัชนีออกจากดัชนี ฉันอยากจะแนะนำต่อไปนี้:

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

หากใครชอบฉันชอบการจัดการข้อมูลแบบลูกโซ่โดยใช้เครื่องหมายจุดแพนด้า (เช่น piping) ดังนั้นสิ่งต่อไปนี้อาจเป็นประโยชน์:

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

สิ่งนี้เปิดใช้งานการโยงแบบนี้:

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

ฉันพยายาม แต่ไม่สามารถใช้งานได้ .. ฉันได้รับข้อผิดพลาดเช่นนี้: TypeError: 'Series' objects are mutable, thus they cannot be hashed.. มันใช้งานได้จริงสำหรับคุณหรือไม่?
Onno Eberhard

1

ลบรายการซ้ำ (การรักษาก่อน)

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

ลบรายการซ้ำ (การรักษาล่าสุด)

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

การทดสอบ: 10k ลูปโดยใช้ข้อมูลของ 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.