Wählen Sie DataFrame-Zeilen zwischen zwei Daten aus

195

Ich erstelle einen DataFrame aus einer CSV wie folgt:

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

Der DataFrame hat eine Datumsspalte. Gibt es eine Möglichkeit, einen neuen DataFrame zu erstellen (oder nur den vorhandenen zu überschreiben), der nur Zeilen mit Datumswerten enthält, die innerhalb eines bestimmten Datumsbereichs oder zwischen zwei angegebenen Datumswerten liegen?

Darkpool
quelle

Antworten:

397

Es gibt zwei mögliche Lösungen:

  • Verwenden Sie eine boolesche Maske und dann df.loc[mask]
  • Legen Sie die Datumsspalte als DatetimeIndex fest und verwenden Sie sie df[start_date : end_date]

Verwenden einer Booleschen Maske :

Stellen Sie sicher, dass df['date']es sich um eine Serie mit dem Typ dtype handelt datetime64[ns]:

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

Machen Sie eine boolesche Maske. start_dateund end_datekann datetime.datetimes, np.datetime64s, pd.Timestamps oder sogar datetime-Zeichenfolgen sein:

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

Wählen Sie den Sub-DataFrame aus:

df.loc[mask]

oder neu zuweisen df

df = df.loc[mask]

Beispielsweise,

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

ergibt

            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

Verwenden eines DatetimeIndex :

Wenn Sie viele Auswahlen nach Datum vornehmen, ist es möglicherweise schneller, die dateSpalte zuerst als Index festzulegen. Dann können Sie Zeilen nach Datum mit auswählen 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'])

ergibt

                   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

Während die Python-Listenindizierung, z. B. seq[start:end]enthält, startaber nicht end, df.loc[start_date : end_date]enthält Pandas beide Endpunkte im Ergebnis, wenn sie im Index enthalten sind. Weder start_datenoch end_datemuss es jedoch im Index sein.


Beachten Sie auch, dass pd.read_csves einen parse_datesParameter gibt, mit dem Sie die dateSpalte als datetime64s analysieren können . Wenn Sie also verwenden parse_dates, müssen Sie nicht verwenden df['date'] = pd.to_datetime(df['date']).

unutbu
quelle
Das Einstellen der Datumsspalte als Index funktioniert gut, aber aus der Dokumentation geht nicht hervor, dass dies möglich ist. Vielen Dank.
Faheem Mitha
@FaheemMitha: Ich habe oben einen Link hinzugefügt , über den "Partial String Indexing" dokumentiert ist.
Unutbu
Der Teil, der vielleicht weniger klar ist, ist, dass ein Index explizit erstellt werden muss. Und ohne den Index explizit zu erstellen, gibt ein eingeschränkter Bereich eine leere Menge zurück, keinen Fehler.
Faheem Mitha
8
Nach dem df = df.set_index(['date'])Schritt habe ich festgestellt, dass der Index auch sortiert werden muss (via df.sort_index(inplace=True, ascending=True)), da Sie sonst weniger als vollständige oder sogar leere DataFrame-Ergebnisse erhalten können df.loc['2000-6-1':'2000-6-10']. Und wenn Sie verwenden ascending=False, wird das überhaupt nicht funktionieren, selbst wenn Sie es mitdf.loc['2000-6-10':'2000-6-1']
bgoodr
Wenn Sie die Spalte 'Datum' beibehalten möchten, während Sie dem Index des Datenrahmens ihren Wert zuweisen, können Sie dies tun. Df.index = df ['Datum']
Richard Liang
64

Ich bin der Meinung, dass die beste Option darin besteht, die direkten Überprüfungen anstelle der loc-Funktion zu verwenden:

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

Für mich geht das.

Das Hauptproblem bei der loc-Funktion mit einem Slice besteht darin, dass die Grenzwerte in den tatsächlichen Werten vorhanden sein sollten. Andernfalls führt dies zu KeyError.

Christin Jose
quelle
Ich finde Slices via locgroßartig. Und es scheint mir, dass, wie unutbu sagt, weder start_date noch end_date im Index enthalten sein müssen .
Nealmcb
So filtern Sie das Datum als (14 Tage vor bis zum aktuellen Datum). Wenn das heutige Datum 2019-01-15 ist. Ich benötige die Daten von (2019-01-01 bis 2019-01-15.)
Praveen Snowy
Einfach und elegant. Danke Christin, das habe ich versucht. Funktioniert bei mir.
Brohjoe
35

Sie können auch verwenden between:

df[df.some_date.between(start_date, end_date)]
pomber
quelle
2
Überprüfen Sie auch between_time: pandas.pydata.org/pandas-docs/version/0.20.3/generated/…
Anton Tarasenko
@AntonTarasenko Seltsamerweise funktioniert nicht mit Datumszeiten , sondern nur mit Zeiten . Ich habe eine Weile gebraucht, um diesen Unterschied zu erkennen. So habe ich diesen Thread konsultiert.
Rotton
19

Sie können die isinMethode für die dateSpalte wie folgt verwenden df[df["date"].isin(pd.date_range(start_date, end_date))]

Hinweis: Dies funktioniert nur mit Datumsangaben (wie in der Frage gestellt) und nicht mit Zeitstempeln.

Beispiel:

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

was gibt

           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
quelle
9

Um die Lösung einfach und pythonisch zu halten, würde ich Ihnen empfehlen, dies zu versuchen.

Wenn Sie dies häufig tun, besteht die beste Lösung darin, zuerst die Datumsspalte als Index festzulegen, der die Spalte in DateTimeIndex konvertiert, und die folgende Bedingung zu verwenden, um einen beliebigen Datumsbereich aufzuteilen.

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
quelle
4

Mit meinem pandasVersionstest können 0.22.0Sie diese Frage jetzt einfacher mit besser lesbarem Code beantworten, indem Sie einfach verwenden 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')})

Angenommen, Sie möchten die Daten zwischen dem 27. November 2018 und dem 15. Januar 2019 erfassen:

# 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

Beachten Sie das inklusive Argument. Sehr hilfreich, wenn Sie Ihre Reichweite explizit angeben möchten. Beachten Sie, wenn wir auf True gesetzt sind, kehren wir auch am 27. November 2018 zurück:

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

Diese Methode ist auch schneller als die zuvor erwähnte isinMethode:

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

Es ist jedoch nicht schneller als die aktuell akzeptierte Antwort von unutbu, nur wenn die Maske bereits erstellt wurde . Wenn die Maske jedoch dynamisch ist und immer wieder neu zugewiesen werden muss, ist meine Methode möglicherweise effizienter:

# 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
quelle
3

Ich ziehe es vor, das nicht zu ändern df.

Eine Option ist das Abrufen indexder startund end-Daten:

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]

was in ... endet:

     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
quelle
2

Eine andere Möglichkeit, dies zu erreichen, ist die Verwendung der pandas.DataFrame.query()Methode. Lassen Sie mich Ihnen ein Beispiel für den folgenden Datenrahmen zeigendf .

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

Verwenden Sie als Argument die Bedingung zum Filtern wie folgt:

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

Wenn Sie keine Grenzen einschließen möchten, ändern Sie die Bedingung wie folgt:

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