web-dev-qa-db-fra.com

Supprimer les lignes avec des index en double (Pandas DataFrame et Time Series)

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 je rencontre est qu’un scientifique retourne parfois corriger les observations - non pas en modifiant les lignes erronées, mais en ajoutant une ligne en double à la fin du 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 j’ai donc besoin de df3 pour devenir:

                       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 rangées (df3['rownum'] = range(df3.shape[0])) m'aiderait à sélectionner la rangée la plus basse pour toute valeur de la variable DatetimeIndex, mais je suis coincé pour déterminer le group_by ou pivot (ou ???) pour que cela fonctionne.

196
Paul H

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

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

Alors que toutes les autres méthodes fonctionnent, le réponse actuellement acceptée est de loin le moins performant pour l'exemple fourni. En outre, 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 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
360
n8yoder

Ma réponse originale, qui est maintenant obsolète, est 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 avec 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 "colonnes" soient définies dans 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 dataframe, supprime les doublons, puis supprime la nouvelle colonne:

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

Et si vous souhaitez que les choses reviennent dans le bon ordre, appelez simplement sort sur le cadre de données.

df3 = df3.sort()
111
D. A.

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

Follow up edit 2013-10-29 Dans le cas où j'ai une MultiIndex assez complexe, je pense que je préfère l'approche groupby. 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
60
Paul H

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!
4
user128754

Si quelqu'un comme moi aime la manipulation de données chaînable à l'aide de la notation pandas (comme la tuyauterie), alors ce qui suit peut être utile:

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

Cela permet d’enchaîner des énoncés comme celui-ci:

df3.assign(C=2).query('~index.duplicated()').mean()
1
bbiegel

Supprimer les doublons (Keeping First)

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

Supprimer les doublons (Keeping Last)

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

Tests: 10k boucles utilisant les données de l'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
0
Mott The Tuple