Sélectionnez les lignes DataFrame entre deux dates

199

Je crée un DataFrame à partir d'un csv comme suit:

stock = pd.read_csv('data_in/' + filename + '.csv', skipinitialspace=True)

Le DataFrame a une colonne de date. Existe-t-il un moyen de créer un nouveau DataFrame (ou simplement de remplacer celui existant) qui ne contient que des lignes avec des valeurs de date comprises dans une plage de dates spécifiée ou entre deux valeurs de date spécifiées?

Darkpool
la source

Réponses:

405

Il y a deux solutions possibles:

  • Utilisez un masque booléen, puis utilisez df.loc[mask]
  • Définissez la colonne de date comme DatetimeIndex, puis utilisez df[start_date : end_date]

Utilisation d'un masque booléen :

S'assurer df['date']est une série avec dtype datetime64[ns]:

df['date'] = pd.to_datetime(df['date'])  

Faites un masque booléen. start_dateet end_datepeut être datetime.datetimes, np.datetime64s, pd.Timestamps ou même des chaînes datetime:

#greater than the start date and smaller than the end date
mask = (df['date'] > start_date) & (df['date'] <= end_date)

Sélectionnez le sous-DataFrame:

df.loc[mask]

ou réattribuer à df

df = df.loc[mask]

Par exemple,

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.random((200,3)))
df['date'] = pd.date_range('2000-1-1', periods=200, freq='D')
mask = (df['date'] > '2000-6-1') & (df['date'] <= '2000-6-10')
print(df.loc[mask])

rendements

            0         1         2       date
153  0.208875  0.727656  0.037787 2000-06-02
154  0.750800  0.776498  0.237716 2000-06-03
155  0.812008  0.127338  0.397240 2000-06-04
156  0.639937  0.207359  0.533527 2000-06-05
157  0.416998  0.845658  0.872826 2000-06-06
158  0.440069  0.338690  0.847545 2000-06-07
159  0.202354  0.624833  0.740254 2000-06-08
160  0.465746  0.080888  0.155452 2000-06-09
161  0.858232  0.190321  0.432574 2000-06-10

Utilisation d'un DatetimeIndex :

Si vous comptez effectuer de nombreuses sélections par date, il peut être plus rapide de définir d'abord la datecolonne comme index. Ensuite, vous pouvez sélectionner des lignes par date en utilisant df.loc[start_date:end_date].

import numpy as np
import pandas as pd

df = pd.DataFrame(np.random.random((200,3)))
df['date'] = pd.date_range('2000-1-1', periods=200, freq='D')
df = df.set_index(['date'])
print(df.loc['2000-6-1':'2000-6-10'])

rendements

                   0         1         2
date                                    
2000-06-01  0.040457  0.326594  0.492136    # <- includes start_date
2000-06-02  0.279323  0.877446  0.464523
2000-06-03  0.328068  0.837669  0.608559
2000-06-04  0.107959  0.678297  0.517435
2000-06-05  0.131555  0.418380  0.025725
2000-06-06  0.999961  0.619517  0.206108
2000-06-07  0.129270  0.024533  0.154769
2000-06-08  0.441010  0.741781  0.470402
2000-06-09  0.682101  0.375660  0.009916
2000-06-10  0.754488  0.352293  0.339337

Alors que l'indexation de liste Python, par exemple seq[start:end]inclut startmais pas end, en revanche, Pandas df.loc[start_date : end_date]inclut les deux points d'extrémité dans le résultat s'ils sont dans l'index. Cependant, ni l'un start_dateni l' autre ne end_datedoivent figurer dans l'index.


Notez également que pd.read_csvpossède un parse_datesparamètre que vous pouvez utiliser pour analyser la datecolonne en tant que datetime64s. Ainsi, si vous utilisez parse_dates, vous n'aurez pas besoin d'utiliser df['date'] = pd.to_datetime(df['date']).

unutbu
la source
Définir la colonne de date comme index fonctionne bien, mais il n'est pas clair d'après la documentation que j'ai vue que l'on puisse le faire. Merci.
Faheem Mitha
@FaheemMitha: J'ai ajouté un lien ci - dessus vers où "l'indexation de chaîne partielle" est documentée.
unutbu
La partie qui est peut-être moins claire est qu'un index doit être créé explicitement. Et sans créer explicitement l'index, une plage restreinte renvoie un ensemble vide, pas une erreur.
Faheem Mitha
8
Après l' df = df.set_index(['date'])étape, j'ai trouvé que l'index doit également être trié (via df.sort_index(inplace=True, ascending=True)), sinon vous pouvez obtenir des résultats DataFrame moins que pleins ou même vides df.loc['2000-6-1':'2000-6-10']. Et si vous utilisez ascending=False, cela ne fonctionnera pas du tout, même si vous l'inversez avecdf.loc['2000-6-10':'2000-6-1']
bgoodr
1
Si vous souhaitez conserver la colonne 'date' tout en donnant sa valeur à l'index de la dataframe, vous pouvez faire ceci df.index = df ['date']
Richard Liang
64

Je pense que la meilleure option sera d'utiliser les vérifications directes plutôt que d'utiliser la fonction loc:

df = df[(df['date'] > '2000-6-1') & (df['date'] <= '2000-6-10')]

Ça marche pour moi.

Le problème majeur avec la fonction loc avec une tranche est que les limites doivent être présentes dans les valeurs réelles, sinon cela entraînera KeyError.

Christin Jose
la source
1
Je pense que les tranches via locsont excellentes. Et il me semble que, comme le dit unutbu, ni start_date ni end_date ne doivent cependant figurer dans l'index .
nealmcb
comment filtrer la date comme (14 jours avant jusqu'à la date actuelle) .. si la date d'aujourd'hui est le 15/01/2019 ... j'ai besoin des données du (01/01/2019 au 15/01/2019)
Praveen Snowy
Simple et élégant. Merci Christin, c'est ce que j'essayais de faire. Travaille pour moi.
brohjoe le
36

Vous pouvez également utiliser between:

df[df.some_date.between(start_date, end_date)]
pomber
la source
2
Vérifiez également between_time: pandas.pydata.org/pandas-docs/version/0.20.3/generated
Anton Tarasenko
1
@AntonTarasenko Étrangement, ne fonctionne pas avec les datetimes , mais plutôt uniquement avec les temps . Il m'a fallu un certain temps pour réaliser cette distinction. C'est ainsi que j'ai fini par consulter ce fil.
rotton le
19

Vous pouvez utiliser la isinméthode sur la datecolonne comme ceci df[df["date"].isin(pd.date_range(start_date, end_date))]

Remarque: cela ne fonctionne qu'avec les dates (comme le demande la question) et non avec les horodatages.

Exemple:

import numpy as np   
import pandas as pd

# Make a DataFrame with dates and random numbers
df = pd.DataFrame(np.random.random((30, 3)))
df['date'] = pd.date_range('2017-1-1', periods=30, freq='D')

# Select the rows between two dates
in_range_df = df[df["date"].isin(pd.date_range("2017-01-15", "2017-01-20"))]

print(in_range_df)  # print result

qui donne

           0         1         2       date
14  0.960974  0.144271  0.839593 2017-01-15
15  0.814376  0.723757  0.047840 2017-01-16
16  0.911854  0.123130  0.120995 2017-01-17
17  0.505804  0.416935  0.928514 2017-01-18
18  0.204869  0.708258  0.170792 2017-01-19
19  0.014389  0.214510  0.045201 2017-01-20
Jonny Brooks
la source
9

En gardant la solution simple et pythonique, je vous suggère d'essayer ceci.

Dans le cas où vous allez faire cela fréquemment, la meilleure solution serait de définir d'abord la colonne de date comme index qui convertira la colonne en DateTimeIndex et utilisera la condition suivante pour découper n'importe quelle plage de dates.

import pandas as pd

data_frame = data_frame.set_index('date')

df = data_frame[(data_frame.index > '2017-08-10') & (data_frame.index <= '2017-08-15')]
Abhinav Anand
la source
4

Avec mes tests de pandasversion, 0.22.0vous pouvez maintenant répondre à cette question plus facilement avec un code plus lisible en utilisant simplement between.

# create a single column DataFrame with dates going from Jan 1st 2018 to Jan 1st 2019
df = pd.DataFrame({'dates':pd.date_range('2018-01-01','2019-01-01')})

Supposons que vous souhaitiez saisir les dates entre le 27 novembre 2018 et le 15 janvier 2019:

# use the between statement to get a boolean mask
df['dates'].between('2018-11-27','2019-01-15', inclusive=False)

0    False
1    False
2    False
3    False
4    False

# you can pass this boolean mask straight to loc
df.loc[df['dates'].between('2018-11-27','2019-01-15', inclusive=False)]

    dates
331 2018-11-28
332 2018-11-29
333 2018-11-30
334 2018-12-01
335 2018-12-02

Notez l'argument inclusif. très utile lorsque vous souhaitez être explicite sur votre gamme. notez que lorsque défini sur True, nous retournons également le 27 novembre 2018:

df.loc[df['dates'].between('2018-11-27','2019-01-15', inclusive=True)]

    dates
330 2018-11-27
331 2018-11-28
332 2018-11-29
333 2018-11-30
334 2018-12-01

Cette méthode est également plus rapide que la isinméthode mentionnée précédemment :

%%timeit -n 5
df.loc[df['dates'].between('2018-11-27','2019-01-15', inclusive=True)]
868 µs ± 164 µs per loop (mean ± std. dev. of 7 runs, 5 loops each)


%%timeit -n 5

df.loc[df['dates'].isin(pd.date_range('2018-01-01','2019-01-01'))]
1.53 ms ± 305 µs per loop (mean ± std. dev. of 7 runs, 5 loops each)

Cependant, ce n'est pas plus rapide que la réponse actuellement acceptée, fournie par unutbu, uniquement si le masque est déjà créé . mais si le masque est dynamique et doit être réaffecté à plusieurs reprises, ma méthode peut être plus efficace:

# already create the mask THEN time the function

start_date = dt.datetime(2018,11,27)
end_date = dt.datetime(2019,1,15)
mask = (df['dates'] > start_date) & (df['dates'] <= end_date)

%%timeit -n 5
df.loc[mask]
191 µs ± 28.5 µs per loop (mean ± std. dev. of 7 runs, 5 loops each)
MattR
la source
4

Une autre option, comment y parvenir, consiste à utiliser la pandas.DataFrame.query()méthode. Permettez-moi de vous montrer un exemple sur le bloc de données suivant appelé df.

>>> df = pd.DataFrame(np.random.random((5, 1)), columns=['col_1'])
>>> df['date'] = pd.date_range('2020-1-1', periods=5, freq='D')
>>> print(df)
      col_1       date
0  0.015198 2020-01-01
1  0.638600 2020-01-02
2  0.348485 2020-01-03
3  0.247583 2020-01-04
4  0.581835 2020-01-05

En tant qu'argument, utilisez la condition de filtrage comme ceci:

>>> start_date, end_date = '2020-01-02', '2020-01-04'
>>> print(df.query('date >= @start_date and date <= @end_date'))
      col_1       date
1  0.244104 2020-01-02
2  0.374775 2020-01-03
3  0.510053 2020-01-04

Si vous ne souhaitez pas inclure de limites, modifiez simplement la condition comme suit:

>>> print(df.query('date > @start_date and date < @end_date'))
      col_1       date
2  0.374775 2020-01-03
Jaroslav Bezděk
la source
3

Je préfère ne pas modifier le fichier df.

Une option est de récupérer les indexdes startet les enddates:

import numpy as np   
import pandas as pd

#Dummy DataFrame
df = pd.DataFrame(np.random.random((30, 3)))
df['date'] = pd.date_range('2017-1-1', periods=30, freq='D')

#Get the index of the start and end dates respectively
start = df[df['date']=='2017-01-07'].index[0]
end = df[df['date']=='2017-01-14'].index[0]

#Show the sliced df (from 2017-01-07 to 2017-01-14)
df.loc[start:end]

ce qui se traduit par:

     0   1   2       date
6  0.5 0.8 0.8 2017-01-07
7  0.0 0.7 0.3 2017-01-08
8  0.8 0.9 0.0 2017-01-09
9  0.0 0.2 1.0 2017-01-10
10 0.6 0.1 0.9 2017-01-11
11 0.5 0.3 0.9 2017-01-12
12 0.5 0.4 0.3 2017-01-13
13 0.4 0.9 0.9 2017-01-14
Arraval
la source