Удалить строки с дублирующимися индексами (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 выяснилось, что в одном из двух столбцов с одинаковым именем вы упомянули, что вы отбрасываете некоторые столбцы, возможно, это может быть причиной.

Другие вопросы по тегам