Python Pandas Drop Duplicates mantienen el segundo al último

¿Cuál es la forma más eficiente de seleccionar el segundo al último de cada conjunto duplicado en un dataframe de pandas?

Por ejemplo básicamente quiero hacer esta operación:

df = df.drop_duplicates(['Person','Question'],take_last=True) 

Pero esto:

 df = df.drop_duplicates(['Person','Question'],take_second_last=True) 

Pregunta resumida: ¿cómo elegir qué duplicado conservar si el duplicado no es el máximo ni el mínimo?

Con groupby.apply:

 df = pd.DataFrame({'A': [1, 1, 1, 1, 2, 2, 2, 3, 3, 4], 'B': np.arange(10), 'C': np.arange(10)}) df Out: ABC 0 1 0 0 1 1 1 1 2 1 2 2 3 1 3 3 4 2 4 4 5 2 5 5 6 2 6 6 7 3 7 7 8 3 8 8 9 4 9 9 (df.groupby('A', as_index=False).apply(lambda x: x if len(x)==1 else x.iloc[[-2]]) .reset_index(level=0, drop=True)) Out: ABC 2 1 2 2 5 2 5 5 7 3 7 7 9 4 9 9 

Con un DataFrame diferente, subcontrata dos columnas:

 df = pd.DataFrame({'A': [1, 1, 1, 1, 2, 2, 2, 3, 3, 4], 'B': [1, 1, 2, 1, 2, 2, 2, 3, 3, 4], 'C': np.arange(10)}) df Out: ABC 0 1 1 0 1 1 1 1 2 1 2 2 3 1 1 3 4 2 2 4 5 2 2 5 6 2 2 6 7 3 3 7 8 3 3 8 9 4 4 9 (df.groupby(['A', 'B'], as_index=False).apply(lambda x: x if len(x)==1 else x.iloc[[-2]]) .reset_index(level=0, drop=True)) Out: ABC 1 1 1 1 2 1 2 2 5 2 2 5 7 3 3 7 9 4 4 9 

Puede groupby/tail(2) para tomar los últimos 2 elementos, luego por groupby/head(1) para tomar el primer elemento de la cola:

 df.groupby(['A','B']).tail(2).groupby(['A','B']).head(1) 

Si solo hay un elemento en el grupo, tail(2) devuelve solo un elemento.


Por ejemplo,

 import numpy as np import pandas as pd df = pd.DataFrame(np.random.randint(10, size=(10**2, 3)), columns=list('ABC')) result = df.groupby(['A','B']).tail(2).groupby(['A','B']).head(1) expected = (df.groupby(['A', 'B'], as_index=False).apply(lambda x: x if len(x)==1 else x.iloc[[-2]]).reset_index(level=0, drop=True)) assert expected.sort_index().equals(result) 

Los métodos integrados de groupby (como tail y head ) son a menudo mucho más rápidos que groupby/apply con funciones de Python personalizadas. Esto es especialmente cierto si hay muchos grupos:

 In [96]: %timeit df.groupby(['A','B']).tail(2).groupby(['A','B']).head(1) 1000 loops, best of 3: 1.7 ms per loop In [97]: %timeit (df.groupby(['A', 'B'], as_index=False).apply(lambda x: x if len(x)==1 else x.iloc[[-2]]).reset_index(level=0, drop=True)) 100 loops, best of 3: 17.9 ms per loop 

Alternativamente, ayhan sugiere una buena mejora:

 alt = df.groupby(['A','B']).tail(2).drop_duplicates(['A','B']) assert expected.sort_index().equals(alt) In [99]: %timeit df.groupby(['A','B']).tail(2).drop_duplicates(['A','B']) 1000 loops, best of 3: 1.43 ms per loop