Chọn hàng DataFrame giữa hai ngày


195

Tôi đang tạo DataFrame từ csv như sau:

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

DataFrame có một cột ngày. Có cách nào để tạo một DataFrame mới (hoặc chỉ ghi đè lên khung hiện có) chỉ chứa các hàng có giá trị ngày nằm trong phạm vi ngày được chỉ định hoặc giữa hai giá trị ngày được chỉ định không?

Câu trả lời:


397

Đây là hai phương pháp khả thi:

  • Sử dụng mặt nạ boolean, sau đó sử dụng df.loc[mask]
  • Đặt cột ngày làm Datetime Index, sau đó sử dụng df[start_date : end_date]

Sử dụng mặt nạ boolean :

Đảm bảo df['date']là một Series với dtype datetime64[ns]:

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

Làm mặt nạ boolean. start_dateend_datecó thể là chuỗi datetime.datetimes, np.datetime64s, pd.Timestamps hoặc thậm chí chuỗi thời gian:

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

Chọn khung dữ liệu phụ:

df.loc[mask]

hoặc gán lại cho df

df = df.loc[mask]

Ví dụ,

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

sản lượng

            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

Sử dụng một Datetime Index :

Nếu bạn định thực hiện nhiều lựa chọn theo ngày, có thể nhanh hơn để đặt datecột làm chỉ mục trước. Sau đó, bạn có thể chọn hàng theo ngày bằng cách sử dụng 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'])

sản lượng

                   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

Trong khi lập chỉ mục danh sách Python, ví dụ seq[start:end]bao gồm startnhưng không end, ngược lại, Pandas df.loc[start_date : end_date]bao gồm cả hai điểm cuối trong kết quả nếu chúng nằm trong chỉ mục. Không start_datephải cũng không end_datephải trong chỉ số.


Cũng lưu ý rằng pd.read_csvcó một parse_datestham số mà bạn có thể sử dụng để phân tích datecột là datetime64s. Vì vậy, nếu bạn sử dụng parse_dates, bạn sẽ không cần sử dụng df['date'] = pd.to_datetime(df['date']).


Đặt cột ngày làm chỉ mục hoạt động tốt, nhưng không rõ ràng từ tài liệu tôi đã thấy rằng người ta có thể làm điều đó. Cảm ơn.
Faheem Mitha

@FaheemMitha: Tôi đã thêm một liên kết ở trên vào nơi "lập chỉ mục chuỗi một phần" được ghi lại.
unutbu

Phần có lẽ ít rõ ràng hơn là một chỉ mục phải được tạo rõ ràng. Và không rõ ràng tạo chỉ mục, một phạm vi bị hạn chế trả về một tập hợp trống, không phải là một lỗi.
Faheem Mitha

8
Sau df = df.set_index(['date'])bước này, tôi đã tìm thấy chỉ mục cũng cần được sắp xếp (thông qua df.sort_index(inplace=True, ascending=True)), vì nếu không, bạn có thể nhận được ít hơn kết quả DataFrame đầy đủ hoặc thậm chí trống từ đó df.loc['2000-6-1':'2000-6-10']. Và nếu bạn sử dụng ascending=False, nó sẽ không hoạt động, ngay cả khi bạn đảo ngược nó vớidf.loc['2000-6-10':'2000-6-1']
bgoodr 22/03/19

Nếu bạn muốn giữ cột 'ngày' trong khi vẫn đưa giá trị của nó vào chỉ mục của khung dữ liệu, bạn có thể thực hiện điều này df.index = df ['date']
Richard Liang

64

Tôi cảm thấy tùy chọn tốt nhất sẽ là sử dụng kiểm tra trực tiếp thay vì sử dụng chức năng loc:

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

Nó làm việc cho tôi.

Vấn đề chính với hàm loc với một lát là các giới hạn phải có trong các giá trị thực, nếu không điều này sẽ dẫn đến KeyError.


Tôi nghĩ rằng lát qua loclà tuyệt vời. Và dường như đối với tôi, như unutbu nói, cả start_date và end_date đều không có trong chỉ mục .
nealmcb

làm cách nào để lọc ngày là (14 ngày trước ngày hiện tại) .. nếu ngày hôm nay là 2019-01-15 ... tôi cần dữ liệu từ (2019-01-01 đến 2019-01-15)
Praveen Snowy

Đơn giản và thanh lịch. Cảm ơn Christin, đây là những gì tôi đã cố gắng làm. Làm việc cho tôi.
brohjoe


19

Bạn có thể sử dụng isinphương thức trên datecột như vậy df[df["date"].isin(pd.date_range(start_date, end_date))]

Lưu ý: Điều này chỉ hoạt động với ngày (như câu hỏi yêu cầu) và không có dấu thời gian.

Thí dụ:

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

cái nào cho

           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

Giữ cho giải pháp đơn giản và pythonic, tôi sẽ đề nghị bạn thử điều này.

Trong trường hợp nếu bạn định làm điều này thường xuyên, giải pháp tốt nhất là trước tiên hãy đặt cột ngày làm chỉ mục sẽ chuyển đổi cột trong DateTime Index và sử dụng điều kiện sau để cắt bất kỳ phạm vi ngày nào.

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

Với thử nghiệm pandasphiên bản của tôi, 0.22.0bây giờ bạn có thể trả lời câu hỏi này dễ dàng hơn với mã dễ đọc hơn bằng cách sử dụng đơn giản 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')})

Giả sử bạn muốn lấy ngày từ ngày 27 tháng 11 năm 2018 đến ngày 15 tháng 1 năm 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

Lưu ý các đối số bao gồm. rất hữu ích khi bạn muốn rõ ràng về phạm vi của bạn. thông báo khi được đặt thành True, chúng tôi cũng sẽ trả lại vào ngày 27 tháng 11 năm 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

Phương pháp này cũng nhanh hơn isinphương pháp đã đề cập trước đó :

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

Tuy nhiên, nó không nhanh hơn câu trả lời hiện được chấp nhận, được cung cấp bởi unutbu, chỉ khi mặt nạ đã được tạo . nhưng nếu mặt nạ động và cần được gán lại nhiều lần, phương pháp của tôi thể hiệu quả hơn:

# 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

Tôi không muốn thay đổi df.

Một lựa chọn là để lấy indexcủa startendngày:

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]

kết quả là:

     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

2

Một lựa chọn khác, làm thế nào để đạt được điều này, là bằng cách sử dụng pandas.DataFrame.query()phương pháp. Hãy để tôi chỉ cho bạn một ví dụ trên khung dữ liệu sau đây được gọi 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

Để làm đối số, hãy sử dụng điều kiện để lọc như thế này:

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

Nếu bạn không muốn bao gồm các ranh giới, chỉ cần thay đổi điều kiện như sau:

>>> print(df.query('date > @start_date and date < @end_date'))
      col_1       date
2  0.374775 2020-01-03
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.