Seleccione filas de DataFrame entre dos fechas


196

Estoy creando un DataFrame desde un csv de la siguiente manera:

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

El DataFrame tiene una columna de fecha. ¿Hay alguna manera de crear un nuevo DataFrame (o simplemente sobrescribir el existente) que solo contiene filas con valores de fecha que se encuentran dentro de un rango de fechas especificado o entre dos valores de fechas específicos?

Respuestas:


399

Hay dos posibles soluciones:

  • Use una máscara booleana, luego use df.loc[mask]
  • Establezca la columna de fecha como DatetimeIndex, luego use df[start_date : end_date]

Usando una máscara booleana :

Asegúrese de que df['date']sea ​​una serie con dtype datetime64[ns]:

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

Haz una máscara booleana. start_datey end_datepueden ser cadenas datetime.datetimes, np.datetime64s, pd.Timestamps o incluso datetime:

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

Seleccione el sub-DataFrame:

df.loc[mask]

o reasignar a df

df = df.loc[mask]

Por ejemplo,

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])

rendimientos

            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

Usando un DatetimeIndex :

Si va a hacer muchas selecciones por fecha, puede ser más rápido establecer dateprimero la columna como índice. Luego puede seleccionar filas por fecha usando 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'])

rendimientos

                   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

Mientras que la indexación de la lista de Python, por ejemplo, seq[start:end]incluye startpero no end, en contraste, Pandas df.loc[start_date : end_date]incluye ambos puntos finales en el resultado si están en el índice. Sin embargo, start_dateni end_datetiene que estar en el índice.


También tenga en cuenta que pd.read_csvtiene un parse_datesparámetro que puede usar para analizar la datecolumna como datetime64s. Por lo tanto, si usa parse_dates, no necesitaría usar df['date'] = pd.to_datetime(df['date']).


Establecer la columna de fecha como el índice funciona bien, pero no está claro en la documentación que he visto que uno puede hacer eso. Gracias.
Faheem Mitha

@FaheemMitha: agregué un enlace arriba donde se documenta la "indexación parcial de cadenas".
unutbu

La parte que quizás sea menos clara es que un índice tiene que ser creado explícitamente. Y sin crear explícitamente el índice, un rango restringido devuelve un conjunto vacío, no un error.
Faheem Mitha

8
Después del df = df.set_index(['date'])paso, descubrí que el índice también debe ordenarse (a través de df.sort_index(inplace=True, ascending=True)), ya que de lo contrario puede obtener resultados de DataFrame menos que completos o incluso vacíos df.loc['2000-6-1':'2000-6-10']. Y si lo usa ascending=False, eso no funcionará en absoluto, incluso si lo revierte condf.loc['2000-6-10':'2000-6-1']
bgoodr

Si desea mantener la columna 'fecha' mientras sigue dando su valor al índice del marco de datos, puede hacerlo df.index = df ['fecha']
Richard Liang

64

Creo que la mejor opción será usar las verificaciones directas en lugar de usar la función loc:

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

Esto funciona para mi.

El problema principal con la función loc con un segmento es que los límites deben estar presentes en los valores reales, de lo contrario esto dará como resultado KeyError.


Creo que las rodajas locson geniales. Y me parece que, como dice unutbu, ni start_date ni end_date tienen que estar en el índice .
nealmcb

cómo filtrar la fecha como (14 días antes hasta la fecha actual) ... si la fecha de hoy es 2019-01-15 ... necesito los datos desde (2019-01-01 hasta 2019-01-15)
Praveen Snowy

Simple y elegante Gracias Christin, esto es lo que estaba tratando de hacer. Funciona para mi.
Brohjoe


19

Puedes usar el isinmétodo en la datecolumna así df[df["date"].isin(pd.date_range(start_date, end_date))]

Nota: Esto solo funciona con fechas (como se pregunta en la pregunta) y no con marcas de tiempo.

Ejemplo:

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

lo que da

           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

9

Manteniendo la solución simple y pitónica, te sugiero que pruebes esto.

En caso de que vaya a hacer esto con frecuencia, la mejor solución sería establecer primero la columna de fecha como índice, que convertirá la columna en DateTimeIndex y usará la siguiente condición para cortar cualquier rango de fechas.

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')]

4

Con mi prueba de pandasversión 0.22.0, ahora puede responder esta pregunta más fácilmente con un código más legible simplemente usando 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')})

Supongamos que desea obtener las fechas entre el 27 de noviembre de 2018 y el 15 de enero de 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

Observe el argumento inclusivo. muy útil cuando quieres ser explícito sobre tu rango. cuando se establece en True, también devolvemos el 27 de noviembre de 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

Este método también es más rápido que el isinmétodo mencionado anteriormente :

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

Sin embargo, es no más rápido que la respuesta aceptada actualmente, proporcionada por unutbu, sólo si la máscara está ya creado . pero si la máscara es dinámica y necesita reasignarse una y otra vez, mi método puede ser más eficiente:

# 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)

3

Prefiero no alterar el df.

Una opción es recuperar la indexde la starty endfechas:

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]

lo que resulta en:

     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

3

Otra opción, cómo lograr esto, es mediante el pandas.DataFrame.query()método. Déjame mostrarte un ejemplo en el siguiente marco de datos llamado 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

Como argumento, use la condición para filtrar así:

>>> 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 no desea incluir límites, simplemente cambie la condición de la siguiente manera:

>>> print(df.query('date > @start_date and date < @end_date'))
      col_1       date
2  0.374775 2020-01-03
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.