114 votes

Pandas: Supprimer les doublons consécutifs

Quel est le moyen le plus efficace de supprimer uniquement les doublons consécutifs dans pandas?

drop_duplicates donne ceci:

In [3]: a = pandas.Series([1,2,2,3,2], index=[1,2,3,4,5])

In [4]: a.drop_duplicates()
Out[4]: 
1    1
2    2
4    3
dtype: int64

Mais je veux ceci:

In [4]: a.something()
Out[4]: 
1    1
2    2
4    3
5    2
dtype: int64

149voto

EdChum Points 10205

Utilisez shift:

a.loc[a.shift(-1) != a]

Out[3]:

1    1
3    2
4    3
5    2
dtype: int64

Donc ce qui précède utilise un critère booléen, nous comparons le dataframe au dataframe décalé de -1 lignes pour créer le masque

Une autre méthode est d'utiliser diff:

In [82]:

a.loc[a.diff() != 0]
Out[82]:
1    1
2    2
4    3
5    2
dtype: int64

Mais c'est plus lent que la méthode originale si vous avez un grand nombre de lignes.

Mise à jour

Merci à Bjarke Ebert d'avoir signalé une erreur subtile, je devrais en fait utiliser shift(1) ou simplement shift() car la valeur par défaut est une période de 1, cela renvoie la première valeur consécutive:

In [87]:

a.loc[a.shift() != a]
Out[87]:
1    1
2    2
4    3
5    2
dtype: int64

Remarquez la différence dans les valeurs d'index, merci @BjarkeEbert!

37voto

johnml1135 Points 1010

Voici une mise à jour qui permettra de le faire fonctionner avec plusieurs colonnes. Utilisez ".any(axis=1)" pour combiner les résultats de chaque colonne :

cols = ["col1","col2","col3"]
de_dup = a[cols].loc[(a[cols].shift() != a[cols]).any(axis=1)]

10voto

Divakar Points 20144

Étant donné que nous recherchons le moyen le plus efficace , c'est-à-dire les performances, utilisons des données de tableau pour tirer parti de NumPy. Nous découperons des tranches uniques et comparerons, comme la méthode de décalage discutée précédemment dans le post de @EdChum. Mais avec NumPy slicing, nous nous retrouverions avec un tableau en moins, nous devons donc concaténer avec un élément Vrai au début pour sélectionner le premier élément et, par conséquent, nous aurions une implémentation comme suit -

 def drop_consecutive_duplicates(a):
     ar = a.values
     retourne a[np.concatenate(([True],ar[:-1]!= ar[1:]))]

Exécution d'exemple -

 Entrée [149]: a
 Sortie [149]:
 1     1
 2     2
 3     2
 4     3
 5     2
 dtype: int64

 En [150]: drop_consecutive_duplicates(a)
 Sortie [150]:
 1     1
 2     2
 4     3
 5     2
 dtype: int64

Chronométrage sur de grands tableaux comparant la solution de @EdChum -

 Entrée [142]: a = pd.Series(np.random.randint(1,5,(1000000)))

 En [143]: %timeit a.loc[a.shift() != a]
 100 boucles, meilleure de 3: 12,1 ms par boucle

 En [144]: %timeit drop_consecutive_duplicates(a)
 100 boucles, meilleure de 3: 11 ms par boucle

 En [145]: a = pd.Series(np.random.randint(1,5,(10000000)))

 En [146]: %timeit a.loc[a.shift() != a]
 10 boucles, meilleure de 3: 136 ms par boucle

 En [147]: %timeit drop_consecutive_duplicates(a)
 10 boucles, meilleure de 3: 114 ms par boucle

Donc, il y a une amélioration!

Obtenez un important boost pour les valeurs uniquement!

Si seules les valeurs sont nécessaires, nous pourrions obtenir un important boost en indexant simplement les données du tableau, comme ceci -

 def drop_consecutive_duplicates(a):
     ar = a.values
     retourne ar[np.concatenate(([True],ar[:-1]!= ar[1:]))]

Exécution d'exemple -

 Entrée [170]: a = pandas.Series([1,2,2,3,2], index=[1,2,3,4,5])

 En [171]: drop_consecutive_duplicates(a)
 Sortie [171]: tableau([1, 2, 3, 2])

Chronométrage -

 Entrée [173]: a = pd.Series(np.random.randint(1,5,(10000000)))

 En [174]: %timeit a.loc[a.shift() != a]
 10 boucles, meilleure de 3: 137 ms par boucle

 En [175]: %timeit drop_consecutive_duplicates(a)
 10 boucles, meilleure de 3: 61,3 ms par boucle

4voto

Arthur D. Howland Points 1078

Pour d'autres explorateurs Stack, en s'appuyant sur la réponse de johnml1135 ci-dessus. Cela supprimera le prochain doublon de plusieurs colonnes sans supprimer toutes les colonnes. Lorsque le dataframe est trié, il conservera la première ligne mais supprimera la deuxième ligne si les "cols" correspondent, même s'il existe d'autres colonnes avec des informations non correspondantes.

cols = ["col1","col2","col3"]
df = df.loc[(df[cols].shift() != df[cols]).any(axis=1)]

3voto

Pablo C Points 4005

Juste une autre façon de le faire:

a.loc[a.ne(a.shift())]

La méthode pandas.Series.ne est l'opérateur différent de, donc a.ne(a.shift()) est équivalent à a != a.shift(). Documentation ici.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X