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 d'écraser celui existant) qui contient uniquement 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?
Il y a deux solutions possibles:
df.loc[mask]
df[start_date : end_date]
Utiliser un masque booléen:
Assurez-vous que df['date']
est une série avec dtype datetime64[ns]
:
df['date'] = pd.to_datetime(df['date'])
Fabriquez un masque booléen. start_date
et end_date
peuvent être datetime.datetime
s, np.datetime64
s, pd.Timestamp
s ou même des chaînes de date/heure:
mask = (df['date'] > start_date) & (df['date'] <= end_date)
Sélectionnez le sous-DataFrame:
df.loc[mask]
ou réaffecter à 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])
les 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 allez effectuer beaucoup de sélections par date, il peut être plus rapide de définir d'abord la colonne date
comme index. Ensuite, vous pouvez sélectionner des lignes par date à l'aide de 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'])
les 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
Lors de l'indexation de liste Python, par exemple, seq[start:end]
inclut start
mais pas end
; en revanche, les pandas df.loc[start_date : end_date]
incluent les deux extrémités du résultat si elles figurent dans l'index. Cependant, ni start_date
ni end_date
ne doivent être dans l'index.
Notez également que pd.read_csv
A UN PARAM&EGRAVE;TRE parse_dates
que vous pouvez utiliser pour analyser la colonne date
en tant que datetime64
s. Ainsi, si vous utilisez parse_dates
, vous n’auriez pas besoin d’utiliser df['date'] = pd.to_datetime(df['date'])
.
Je pense que la meilleure option sera d'utiliser les contrôles directs 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 KeyError sera généré.
Vous pouvez utiliser la méthode isin
sur la colonne date
comme suit df[df["date"].isin(pd.date_range(start_date, end_date))]
Remarque: Cela fonctionne uniquement avec les dates (comme le demande la question) et non 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
Vous pouvez également utiliser between
:
df[df.some_date.between(start_date, end_date)]
Si vous envisagez de le faire fréquemment, la meilleure solution consiste à définir d'abord la colonne de date comme index, qui convertira la colonne en DateTimeIndex et à utiliser la condition suivante pour découper toute 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')]
Je préfère ne pas modifier le df
.
Une option consiste à récupérer la index
des dates start
et end
:
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]
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
Avec mon test de pandas
version 0.22.0
, vous 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')})
Disons que vous voulez 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
Remarquez l'argument inclusif. very utile lorsque vous voulez être explicite sur votre gamme. remarquez que, lorsqu'il est 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 méthode isin
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, il 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é encore et encore, ma méthode peut est 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)