Удалить строки с дублирующимися индексами (Pandas DataFrame и TimeSeries)
Я читаю некоторые автоматические данные о погоде из Интернета. Наблюдения происходят каждые 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
(или???) заявления, чтобы сделать эту работу.
7 ответов
Я бы предложил использовать дублированный метод на самом Индексе Панд:
df3 = df3[~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
Обратите внимание, что вы можете сохранить последний элемент, изменив аргумент keep.
Следует также отметить, что этот метод работает с MultiIndex
также (используя df1, как указано в примере с Полом):
>>> %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
Обратите внимание, что есть лучший ответ (ниже), основанный на последних Пандах
Это должен быть принятый ответ.
Мой оригинальный ответ, который сейчас устарел, хранится для справки.
Простое решение заключается в использовании drop_duplicates
df4 = df3.drop_duplicates(subset='rownum', keep='last')
Для меня это работало быстро на больших наборах данных.
Для этого необходимо, чтобы столбец с дубликатами был "rownum". В модифицированном примере "rownum" не имеет дубликатов, поэтому ничего не удаляется. То, что мы действительно хотим, это чтобы столбцы были установлены в индекс. Я не нашел способ указать drop_duplicates только рассматривать индекс.
Вот решение, которое добавляет индекс в виде столбца данных, удаляет дубликаты, а затем удаляет новый столбец:
df3 = df3.reset_index().drop_duplicates(subset='index', keep='last').set_index('index')
И если вы хотите, чтобы все вернулось в нужном порядке, просто позвоните sort
на кадре данных.
df3 = df3.sort()
Боже мой Это на самом деле так просто!
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
Follow up edit 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
Удалить дубликаты (сохранить первым)
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
К сожалению, я не думаю, что Pandas позволяет сбрасывать спады с индексов. Я бы предложил следующее:
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!
Если кому-то, как я, нравится цепное манипулирование данными с использованием точечной нотации панд (например, конвейерная обработка), то может быть полезно следующее:
df3 = df3.query('~index.duplicated()')
Это позволяет создавать цепочки операторов следующим образом:
df3.assign(C=2).query('~index.duplicated()').mean()
У меня был опыт с этой же ошибкой, и после погружения в каждый df выяснилось, что в одном из двух столбцов с одинаковым именем вы упомянули, что вы отбрасываете некоторые столбцы, возможно, это может быть причиной.