Yinelenen dizinlere sahip satırları kaldırma (Pandalar DataFrame ve TimeSeries)


252

Web'den bazı otomatik hava durumu verilerini okuyorum. Gözlemler her 5 dakikada bir gerçekleşir ve her hava istasyonu için aylık dosyalarda derlenir. Bir dosyayı ayrıştırmayı bitirdikten sonra DataFrame şöyle görünür:

                      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

Yaşadığım sorun bazen bir bilim insanının geri dönüp gözlemleri düzeltmesidir - hatalı satırları düzenleyerek değil, dosyanın sonuna yinelenen bir satır ekleyerek. Böyle bir vakanın basit bir örneği aşağıda gösterilmiştir:

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

Ve böylece df3eşit olarak olmalıyım:

                       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

Satır numaraları ( df3['rownum'] = range(df3.shape[0])) sütun eklemenin bana herhangi bir değeri için en alt satır seçmek yardımcı olacağını düşündüm DatetimeIndex, ama bu işi yapmak için group_byveya pivot(veya ???) ifadeleri anlamaya takılıp .


1
Çiftleri almanın bir başka yolu, saatlerin gün ışığından yararlanma saati için geri ayarlandığı gece saatlik verilerdir: 1 AM, 2, 3, 2, 3 tekrar, 4 ...
denis

Yanıtlar:


467

Pandalar endeksi kendisi çoğaltılan yöntemi kullanarak öneririz :

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

Diğer tüm yöntemler çalışırken, şu anda kabul edilen cevap verilen örnek için açık ara en düşük performanstır. Ayrıca, groupby yöntemi sadece biraz daha az performans gösterirken, yinelenen yöntemin daha okunabilir olduğunu düşünüyorum.

Sağlanan örnek verileri kullanarak:

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

Keep bağımsız değişkenini değiştirerek son öğeyi koruyabileceğinizi unutmayın.

Ayrıca, bu yöntemin MultiIndexde ( Paul örneğinde belirtildiği gibi df1 kullanılarak) çalıştığı belirtilmelidir :

>>> %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
locgerekli olmayabilir. Yapmanız gereken tek şey df3 = df3[~df3.index.duplicated(keep='first')], ilk tekrarlama dışında tüm satırları yinelenen dizinle bırakacaktır.
lingjiankong

1
kopyaların genellikle sadece ilk veya son değerler olduğu çok büyük zaman serileri için bunu kullanmak anlamlı olur mu?
cheesus

1
df3 = df3.loc [~ df3.index.duplicated (keep = 'first')] içinde kimse cevap vermezse ne yapar?
jsl5703

3
@ jsl5703 Maskeyi tersine çevirir. Bu yüzden Doğru Yanlış olan herşeyi tersine çevirir. Bu durumda, yönteme göre çoğaltılamayanları seçeceğimiz anlamına gelir.
n8yoder

115

Şimdi eskimiş olan orijinal cevabım referans olarak saklandı.

Basit bir çözüm kullanmaktır drop_duplicates

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

Benim için bu, büyük veri kümelerinde hızlı bir şekilde çalıştı.

Bu, 'rownum'un yinelenen sütun olmasını gerektirir. Değiştirilmiş örnekte, 'rownum' kopyaları yoktur, bu nedenle hiçbir şey ortadan kaldırılmaz. Gerçekten istediğimiz şey, 'sütunların' dizine ayarlanmasıdır. Ben sadece endeksi dikkate drop_duplicates anlatmak için bir yol bulamadım.

Dizini veri çerçevesi sütunu olarak ekleyen, bu kopyaları yineleyen, ardından yeni sütunu kaldıran bir çözüm:

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

Ve işleri uygun sırada geri almak istiyorsanız sort, veri çerçevesini arayın.

df3 = df3.sort()

10
Bu konuda başka bir varyasyon:df.reset_index().drop_duplicates(cols='index',take_last=True).set_index('index')
Luciano

Bu yöntem işe yarar olsa da, DataFrame'in iki geçici kopyasını oluşturur ve yinelenen dizin veya alternatif yanıtlar olarak önerilen groupby yöntemlerini kullanmaktan önemli ölçüde daha az performans gösterir.
n8yoder

Dizininiz bir MultiIndex ise, reset_index()düzey_0, düzey_1 vb. Sütunlar ekler. Ve dizininizde bir ad varsa, "dizin" etiketi yerine bu ad kullanılır. Bu, bunu herhangi bir DataFrame için doğru yapmak için bir astardan biraz daha fazla yapar. index_label = getattr(df.index, 'names', getattr(df.index, 'name', 'index'))o cols=index_labelzaman set_index(index_labels)ve bu bile kusursuz değildir (adlandırılmamış çok endeksler için çalışmaz).
ocaklar

1
Dizini bir sütuna taşımak, kopyaları silmek ve dizini sıfırlamak harikaydı, tam da ihtiyacım olan buydu!
mxplusb

Verilen idx = df.index.name or 'index', df2 = df.reset_index(); df2.drop_duplicates(idx, inplace=True); df2.set_index(idx, inplace=True)ara kopyaları önlemek için de yapılabilir (nedeniyle inplace=True)
Anakhand

67

Aman. Bu aslında çok basit!

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

İzlem düzenleme 2013-10-29 Oldukça karmaşık olduğum durumda MultiIndex, groupbyyaklaşımı tercih ettiğimi düşünüyorum . İşte gelecek nesiller için basit bir örnek:

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

ve işte önemli kısım

# 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

isimleri varsa, aksi takdirde (bir isim Yok ise) level=[0,1]2 seviye varsa çalışacağını söyleyelim df1.groupby(level=[0,1]).last(). Bu pandaların bir parçası olarak ücretsiz olmalıdrop_duplicates
dashesy

@dashesy evet. Kullanmak df.index.namessadece endeksin tüm seviyelerine göre gruplamak için kolay bir yoludur.
Paul H

Harika bir çözüm, teşekkür ederim! Bunun, aynı tarih ve işlem başarısız xarrayolan DateTime endeksleri ile uğraşmak için işe yaradığını da ekleyeceğimds.resampleds.groupby
drg

Benim yorumuna Ek: o çalışır xarraydeğiştirmek sürece grouped = df3.groupby(level=0)etmek grouped = df3.groupby(dim='time')ya da bu kopyalar içeriyor olursa olsun boyuttur
DRG

4

Ne yazık ki, Pandaların birinin endekslerden dups bırakmasına izin verdiğini sanmıyorum. Aşağıdakileri öneririm:

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

Benim gibi biri panda nokta gösterimini (borulama gibi) kullanarak zincirlenebilir veri manipülasyonunu seviyorsa, aşağıdakiler yararlı olabilir:

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

Bu, şöyle ifadelerin zincirlenmesini sağlar:

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

Bunu denedim ama işe yaramadı .. Böyle bir hata alıyorum: TypeError: 'Series' objects are mutable, thus they cannot be hashed.. Bu aslında sizin için işe yaradı mı?
Onno Eberhard

1

Yinelenenleri kaldır (İlk önce)

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

Kopyaları kaldır (Sonuncuyu Koru)

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

Testler: OP verilerini kullanarak 10 bin döngü

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.