Supprimer des lignes avec des index en double (Pandas DataFrame et TimeSeries)

251

Je lis des données météorologiques automatisées sur le Web. Les observations ont lieu toutes les 5 minutes et sont compilées dans des fichiers mensuels pour chaque station météorologique. Une fois que j'ai fini d'analyser un fichier, le DataFrame ressemble à ceci:

                      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

Le problème que j'ai, c'est que parfois un scientifique revient en arrière et corrige des observations - non pas en modifiant les lignes erronées, mais en ajoutant une ligne en double à la fin d'un fichier. Un exemple simple d'un tel cas est illustré ci-dessous:

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

Et donc j'ai besoin df3de devenir de façon équilibrée:

                       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

Je pensais que l'ajout d'une colonne de numéros de ligne ( df3['rownum'] = range(df3.shape[0])) m'aiderait à sélectionner la ligne la plus basse pour n'importe quelle valeur de DatetimeIndex, mais je suis coincé à trouver les instructions group_byou pivot(ou ???) pour que cela fonctionne.

Paul H
la source
1
Une autre façon d'obtenir des doublons est la donnée horaire dans la nuit lorsque les horloges sont mises à l'heure d'été: 1h du matin, 2, 3, 2, 3 à nouveau, 4 ...
denis

Réponses:

467

Je suggère d'utiliser la méthode dupliquée sur l'index Pandas lui-même:

df3 = df3.loc[~df3.index.duplicated(keep='first')]

Bien que toutes les autres méthodes fonctionnent, la réponse actuellement acceptée est de loin la moins performante pour l'exemple fourni. De plus, bien que la méthode groupby ne soit que légèrement moins performante, je trouve que la méthode dupliquée est plus lisible.

En utilisant les exemples de données fournis:

>>> %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

Notez que vous pouvez conserver le dernier élément en modifiant l'argument keep.

Il convient également de noter que cette méthode fonctionne également avec MultiIndex(en utilisant df1 comme spécifié dans l'exemple 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
n8yoder
la source
3
locpourrait ne pas être nécessaire. Faites simplement df3 = df3[~df3.index.duplicated(keep='first')], ce qui supprimera toutes les lignes avec un index en double, sauf la première occurrence.
lingjiankong du
1
serait-il sensé de l'utiliser pour de très grandes séries chronologiques où les doublons ne sont généralement que les premières ou dernières valeurs?
cheesus
1
que fait ~ dans df3 = df3.loc [~ df3.index.duplicated (keep = 'first')] si quelqu'un ne veut pas répondre?
jsl5703
3
@ jsl5703 Il inverse le masque. Donc, cela transforme tout ce qui était vrai faux et vice-versa. Dans ce cas, cela signifie que nous sélectionnerons ceux qui ne sont pas dupliqués selon la méthode.
n8yoder
115

Ma réponse originale, désormais dépassée, a été conservée pour référence.

Une solution simple consiste à utiliser drop_duplicates

df4 = df3.drop_duplicates(subset='rownum', keep='last')

Pour moi, cela a fonctionné rapidement sur de grands ensembles de données.

Cela nécessite que «rownum» soit la colonne contenant les doublons. Dans l'exemple modifié, 'rownum' n'a pas de doublons, donc rien n'est éliminé. Ce que nous voulons vraiment, c'est que les «cols» soient mis à l'index. Je n'ai pas trouvé de moyen de dire à drop_duplicates de ne considérer que l'index.

Voici une solution qui ajoute l'index en tant que colonne de trame de données, supprime les doublons à ce sujet, puis supprime la nouvelle colonne:

df3 = df3.reset_index().drop_duplicates(subset='index', keep='last').set_index('index')

Et si vous voulez que les choses sortsoient remises dans le bon ordre, appelez simplement la trame de données.

df3 = df3.sort()
DA
la source
10
Une autre variation à ce sujet est:df.reset_index().drop_duplicates(cols='index',take_last=True).set_index('index')
Luciano
Bien que cette méthode fonctionne, elle crée également deux copies temporaires du DataFrame et est beaucoup moins performante que l'utilisation de l'index dupliqué ou des méthodes groupby suggérées comme réponses alternatives.
n8yoder
Si votre index est un MultiIndex, reset_index()ajoute les colonnes level_0, level_1, etc. Et si votre index a un nom, ce nom sera utilisé à la place de l'étiquette "index". Cela en fait un peu plus qu'une ligne pour le faire correctement pour n'importe quel DataFrame. index_label = getattr(df.index, 'names', getattr(df.index, 'name', 'index'))puis cols=index_labelalors set_index(index_labels)et même ce n'est pas infaillible (ne fonctionnera pas pour les multi-index sans nom).
plaques
1
Déplacer l'index dans une colonne, effacer les doublons et réinitialiser l'index était génial, c'était exactement ce dont j'avais besoin!
mxplusb
Étant donné idx = df.index.name or 'index', on pourrait aussi faire df2 = df.reset_index(); df2.drop_duplicates(idx, inplace=True); df2.set_index(idx, inplace=True)pour éviter les copies intermédiaires (à cause du inplace=True)
Anakhand
67

Oh mon. C'est en fait si 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

Édition de suivi 2013-10-29 Dans le cas où j'ai un assez complexe MultiIndex, je pense que je préfère l' groupbyapproche. Voici un exemple simple pour la postérité:

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

et voici la partie 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
Paul H
la source
s'ils ont des noms, sinon (si un nom est Aucun), disons level=[0,1]fonctionnera s'il y a 2 niveaux df1.groupby(level=[0,1]).last(). Cela devrait faire partie des Pandas en complément dedrop_duplicates
dashesy
@dashesy ouais. L'utilisation df.index.namesn'est qu'un moyen simple de regrouper tous les niveaux de l'index.
Paul H
Excellente solution, merci! J'ajouterai également que cela fonctionne xarraypour traiter les index DateTime en double ainsi que la fabrication ds.resampleet les ds.groupbyopérations échouent
drg
Amendement à mon commentaire précédent: il fonctionne xarrayaussi longtemps que vous changez le grouped = df3.groupby(level=0)en grouped = df3.groupby(dim='time')ou quelle que soit la dimension qui contient des doublons
drg
4

Malheureusement, je ne pense pas que Pandas permette de supprimer les doublons des indices. Je suggérerais ce qui suit:

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!
user128754
la source
1

Si quelqu'un comme moi aime la manipulation de données chaînables en utilisant la notation de point pandas (comme la tuyauterie), alors ce qui suit peut être utile:

df3 = df3.query('~index.duplicated()')

Cela permet de chaîner des instructions comme celle-ci:

df3.assign(C=2).query('~index.duplicated()').mean()
bbiegel
la source
J'ai essayé mais je n'ai pas réussi à le faire fonctionner .. J'ai une erreur comme celle-ci: TypeError: 'Series' objects are mutable, thus they cannot be hashed.. Est-ce que cela a fonctionné pour vous?
Onno Eberhard
1

Supprimer les doublons (continuer en premier)

idx = np.unique( df.index.values, return_index = True )[1]
df = df.iloc[idx]

Supprimer les doublons (Garder le dernier)

df = df[::-1]
df = df.iloc[ np.unique( df.index.values, return_index = True )[1] ]

Tests: boucles 10k utilisant les données 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
Mott The Tuple
la source