İki tarih arasındaki DataFrame satırlarını seçme


198

Aşağıdaki gibi bir csv bir DataFrame oluşturuyorum:

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

DataFrame'in bir tarih sütunu vardır. Yalnızca belirli bir tarih aralığında veya belirtilen iki tarih değeri arasında tarih değerleri olan satırlar içeren yeni bir DataFrame oluşturmanın (veya yalnızca varolan dosyanın üzerine yazmanın) bir yolu var mı?

Yanıtlar:


403

İki olası çözüm vardır:

  • Boole maskesi kullanın, ardından şunu kullanın: df.loc[mask]
  • Tarih sütununu DatetimeIndex olarak ayarlayın, ardından df[start_date : end_date]

Boole maskesi kullanma :

Emin olun df['date']dtype ile bir Seri datetime64[ns]:

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

Boole maskesi yapın. start_dateve end_dateolabilir datetime.datetimeler, np.datetime64lar, pd.Timestampler, hatta tarih saat dizeleri:

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

Alt DataFrame'i seçin:

df.loc[mask]

veya yeniden ata df

df = df.loc[mask]

Örneğin,

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

verim

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

Tarihe göre çok sayıda seçim yapacaksanız, datesütunu önce dizin olarak ayarlamak daha hızlı olabilir . Ardından kullanarak satırları tarihe göre seçebilirsiniz 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'])

verim

                   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

Python listesi indeksleme, örneğin seq[start:end], içerir, startancak endiçermez df.loc[start_date : end_date], buna karşılık, Pandalar dizindeyse sonuçta her iki bitiş noktasını da içerir . Bununla birlikte, ne start_datede ne de end_datedizinde bulunmalıdır.


Ayrıca , sütunu s olarak ayrıştırmak için kullanabileceğiniz pd.read_csvbir parse_datesparametreye sahip olduğunu unutmayın . Böylece, kullanırsanız , kullanmanız gerekmez .datedatetime64parse_datesdf['date'] = pd.to_datetime(df['date'])


Tarih sütununu dizin olarak ayarlamak iyi çalışıyor, ancak bunun yapabileceğini gördüğüm belgelerden net değil. Teşekkürler.
Faheem Mitha

@FaheemMitha: Yukarıda "kısmi dize dizine ekleme" nin belgelendiği bir bağlantı ekledim .
unutbu

Belki de daha az net olan kısım, bir endeksin açıkça oluşturulması gerektiğidir. Ve dizini açıkça oluşturmadan, sınırlı bir aralık hata değil boş bir küme döndürür.
Faheem Mitha

8
df = df.set_index(['date'])Adımdan sonra, dizinin de (üzerinden df.sort_index(inplace=True, ascending=True)) sıralanması gerektiğini buldum, aksi takdirde dolu veya boş DataFrame sonuçlarından daha az alabilirsiniz df.loc['2000-6-1':'2000-6-10']. Ve kullanırsanız ascending=False, tersine df.loc['2000-6-10':'2000-6-1']
çevirseniz

1
Veri çerçevesi dizinine yine de değerini verirken 'tarih' sütununu korumak istiyorsanız, bunu yapabilirsiniz df.index = df ['tarih']
Richard Liang

64

En iyi seçenek loc işlevini kullanmak yerine doğrudan denetimleri kullanmak olacağını hissediyorum:

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

Benim için çalışıyor.

Bir dilimle loc işlevi ile ilgili temel sorun, sınırların gerçek değerlerde bulunması gerektiğidir, aksi takdirde KeyError ile sonuçlanır.


1
Bence dilimleri locharika. Ve bana öyle geliyor ki unutbu'nun dediği gibi, ne başlangıç_tarihi ne de bitiş_tarihi dizinde olmak zorunda değil .
nealmcb

tarih olarak nasıl filtrelenir (geçerli tarihe kadar 14 gün önce) .. eğer bugünün tarihi 2019-01-15 ise ... (2019-01-01 - 2019-01-15 arası)
Praveen Snowy

Basit ve zarif. Teşekkürler Christin, ben de bunu yapmaya çalışıyordum. Benim için çalışıyor.
brohjoe


19

Sütundaki isinyöntemi şöyle kullanabilirsinizdatedf[df["date"].isin(pd.date_range(start_date, end_date))]

Not: Bu yalnızca tarihlerle (sorunun sorduğu gibi) çalışır, zaman damgalarıyla değil.

Misal:

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

hangi verir

           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

Çözümü basit ve pitonik tutarak, bunu denemenizi öneririm.

Bunu sık sık yapacaksanız, en iyi çözüm ilk olarak DateTimeIndex içindeki sütunu dönüştürecek ve herhangi bir tarih aralığını dilimlemek için aşağıdaki koşulu kullanacak şekilde tarih sütununu dizin olarak ayarlamak olacaktır.

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

pandasSürüm testlerimle 0.22.0artık bu soruyu daha okunabilir kodlarla daha kolay bir şekilde kullanarak cevaplayabilirsiniz 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')})

Diyelim ki 27 Kasım 2018 ile 15 Ocak 2019 arasındaki tarihleri ​​almak istiyorsunuz:

# 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

Kapsayıcı argümana dikkat edin. aralığınız hakkında açık olmak istediğinizde çok yararlı. True olarak ayarlandığında 27 Kasım 2018'de de geri dönüyoruz:

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

Bu yöntem daha önce belirtilen isinyöntemden daha hızlıdır :

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

Ancak, sadece maske zaten oluşturulmuşsa , unutbu tarafından sağlanan şu anda kabul edilen cevaptan daha hızlı değildir . ancak maske dinamikse ve tekrar tekrar atanması gerekiyorsa yöntemim daha verimli olabilir :

# 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

Ben değiştirmemeyi tercih ederim df.

Bir seçenek almak için indexbir startve endtarihleri:

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]

sonuç:

     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

Bunu başarmak için başka bir seçenek, pandas.DataFrame.query()yöntem kullanmaktır . Aşağıdaki veri çerçevesinde size bir örnek göstereyim 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

Bağımsız değişken olarak, filtreleme koşulunu şu şekilde kullanın:

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

Sınırları dahil etmek istemiyorsanız, durumu aşağıdaki gibi değiştirin:

>>> print(df.query('date > @start_date and date < @end_date'))
      col_1       date
2  0.374775 2020-01-03
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.