it-swarm.com.de

Wählen Sie DataFrame-Zeilen zwischen zwei Datumsangaben aus

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 einfach den vorhandenen zu überschreiben), der nur Zeilen mit Datumswerten enthält, die innerhalb eines angegebenen Datumsbereichs oder zwischen zwei angegebenen Datumswerten liegen?

116
darkpool

Es gibt zwei mögliche Lösungen:

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

Verwenden einer booleschen Maske:

Stellen Sie sicher, dass df['date'] eine Serie mit dtype datetime64[ns] ist:

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

Mache eine boolesche Maske. start_date und end_date können datetime.datetimes, np.datetime64s, pd.Timestamps oder sogar datetime-Zeichenfolgen sein:

mask = (df['date'] > start_date) & (df['date'] <= end_date)

Wählen Sie den Sub-DataFrame aus:

df.loc[mask]

oder df neu zuweisen

df = df.loc[mask]

Zum Beispiel,

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

erträge

            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

DatetimeIndex verwenden:

Wenn Sie nach Datum viele Auswahlmöglichkeiten treffen, ist es möglicherweise schneller, die Spalte date zuerst als Index festzulegen. Dann können Sie mit df.loc[start_date:end_date] Zeilen nach Datum auswählen.

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

erträge

                   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 Indizierung der Python-Liste, z. seq[start:end] enthält start, jedoch nicht end. Im Gegensatz dazu enthält Pandas df.loc[start_date : end_date]beide Endpunkte im Ergebnis, wenn sie im Index enthalten sind. Weder start_date noch end_date müssen jedoch im Index enthalten sein.


Beachten Sie auch, dass pd.read_csv einen parse_dates-Parameter hat , mit dem Sie die date-Spalte als datetime64s analysieren können. Wenn Sie parse_dates verwenden, müssen Sie df['date'] = pd.to_datetime(df['date']) nicht verwenden. 

256
unutbu

Ich glaube, dass die beste Option die direkte Prüfung ist, anstatt die 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 ist, dass die Grenzwerte in den tatsächlichen Werten vorhanden sein sollten. Andernfalls führt dies zu KeyError.

32
Christin Jose

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

Hinweis: Dies funktioniert nur mit Datumsangaben (wie es die Frage verlangt) 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
18
Jonny Brooks

Sie können auch between verwenden:

df[df.some_date.between(start_date, end_date)]
13
pomber

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

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

Ich möchte die df nicht ändern.

Eine Option ist das Abrufen der index der start und 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 ... resultiert:

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

Mit meinem Test von pandas Version 0.22.0 können Sie diese Frage jetzt mit lesbarerem Code einfacher beantworten, indem Sie einfach between verwenden.

# 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 Termine zwischen dem 27. November 2018 und dem 15. Januar 2019 einholen:

# 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 Ihren Bereich explizit angeben möchten. Beachten Sie, wenn True eingestellt ist, wir geben auch den 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 isin-Methode:

%%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, die von unutbu bereitgestellt wird, nur, wenn die Maske bereits erstellt ist . 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)
0
MattR