Eliminar filas de pandas con índices duplicados
¿Cómo eliminar filas con valores de índice duplicados?
En el marco de datos meteorológico a continuación, a veces un científico regresa y corrige las observaciones, no editando las filas erróneas, sino agregando una fila duplicada al final de un archivo.
Estoy leyendo algunos datos meteorológicos automatizados de la web (las observaciones se realizan cada 5 minutos y se compilan en archivos mensuales para cada estación meteorológica). Después de analizar un archivo, el DataFrame se ve así:
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
Ejemplo de un caso duplicado:
import pandas as pd
import datetime
startdate = datetime.datetime(2001, 1, 1, 0, 0)
enddate = datetime.datetime(2001, 1, 1, 5, 0)
index = pd.date_range(start=startdate, end=enddate, freq='H')
data1 = {'A' : range(6), 'B' : range(6)}
data2 = {'A' : [20, -30, 40], 'B' : [-50, 60, -70]}
df1 = pd.DataFrame(data=data1, index=index)
df2 = pd.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
Y entonces necesito df3
llegar a ser:
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
Pensé que agregar una columna de números de fila ( df3['rownum'] = range(df3.shape[0])
) me ayudaría a seleccionar la fila inferior para cualquier valor de DatetimeIndex
, pero no puedo descubrir las declaraciones group_by
o pivot
(o ???) para que eso funcione.
Sugeriría utilizar el método duplicado en el propio Índice Pandas:
df3 = df3[~df3.index.duplicated(keep='first')]
Si bien todos los demás métodos funcionan, .drop_duplicates
es, con diferencia, el de menor rendimiento para el ejemplo proporcionado. Además, aunque el método groupby tiene un rendimiento ligeramente menor, encuentro que el método duplicado es más legible.
Usando los datos de muestra proporcionados:
>>> %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
Tenga en cuenta que puede conservar el último elemento cambiando el argumento keep a 'last'
.
También cabe señalar que este método MultiIndex
también funciona (usando df1 como se especifica en el ejemplo de 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
Esto agrega el índice como una columna de DataFrame, elimina duplicados y luego elimina la nueva columna:
df = (df.reset_index()
.drop_duplicates(subset='index', keep='last')
.set_index('index').sort_index())
Tenga en cuenta que el uso de lo .sort_index()
anterior al final es según sea necesario y es opcional.
Oh mi. ¡Esto es realmente tan simple!
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
Edición de seguimiento 2013-10-29
En el caso de que tenga un problema bastante complejo MultiIndex
, creo que prefiero el groupby
enfoque. He aquí un ejemplo sencillo para la posteridad:
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
y aquí está la parte importante
# 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
Eliminar duplicados (mantener primero)
idx = np.unique( df.index.values, return_index = True )[1]
df = df.iloc[idx]
Eliminar duplicados (mantenerse al final)
df = df[::-1]
df = df.iloc[ np.unique( df.index.values, return_index = True )[1] ]
Pruebas: bucles de 10k utilizando datos de 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