Pilih baris DataFrame antara dua tanggal

198

Saya membuat DataFrame dari csv sebagai berikut:

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

DataFrame memiliki kolom tanggal. Apakah ada cara untuk membuat DataFrame baru (atau hanya menimpa yang sudah ada) yang hanya berisi baris dengan nilai tanggal yang termasuk dalam rentang tanggal yang ditentukan atau antara dua nilai tanggal yang ditentukan?

darkpool
sumber

Jawaban:

404

Ada dua solusi yang mungkin:

  • Gunakan topeng boolean, lalu gunakan df.loc[mask]
  • Atur kolom tanggal sebagai DatetimeIndex, lalu gunakan df[start_date : end_date]

Menggunakan topeng boolean :

Pastikan df['date']adalah Seri dengan dtype datetime64[ns]:

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

Buat topeng boolean. start_datedan end_datedapat berupa string datetime.datetimes, np.datetime64s, pd.Timestamps, atau bahkan datetime:

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

Pilih sub-DataFrame:

df.loc[mask]

atau ditugaskan kembali df

df = df.loc[mask]

Sebagai contoh,

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

hasil panen

            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

Menggunakan DatetimeIndex :

Jika Anda akan melakukan banyak pilihan berdasarkan tanggal, mungkin lebih cepat untuk mengatur datekolom sebagai indeks terlebih dahulu. Kemudian Anda dapat memilih baris berdasarkan tanggal menggunakan 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'])

hasil panen

                   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

Sementara pengindeksan daftar Python, misalnya seq[start:end]termasuk starttetapi tidak end, sebaliknya, Pandas df.loc[start_date : end_date]menyertakan kedua titik akhir dalam hasil jika mereka berada dalam indeks. Namun, keduanya start_datetidak end_dateharus berada dalam indeks.


Perhatikan juga bahwa pd.read_csvmemiliki parse_datesparameter yang dapat Anda gunakan untuk mem-parsing datekolom sebagai datetime64s. Jadi, jika Anda menggunakan parse_dates, Anda tidak perlu menggunakannya df['date'] = pd.to_datetime(df['date']).

unutbu
sumber
Mengatur kolom tanggal sebagai indeks berfungsi dengan baik, tetapi tidak jelas dari dokumentasi yang saya lihat bahwa orang dapat melakukannya. Terima kasih.
Faheem Mitha
@FaheemMitha: Saya menambahkan tautan di atas ke tempat "pengindeksan string parsial" didokumentasikan.
unutbu
Bagian yang mungkin kurang jelas adalah bahwa indeks harus dibuat secara eksplisit. Dan tanpa secara eksplisit membuat indeks, rentang terbatas mengembalikan set kosong, bukan kesalahan.
Faheem Mitha
8
Setelah df = df.set_index(['date'])langkah tersebut, saya telah menemukan indeks juga perlu disortir (via df.sort_index(inplace=True, ascending=True)), karena jika tidak, Anda bisa mendapatkan hasil DataFrame kurang dari penuh atau bahkan kosong df.loc['2000-6-1':'2000-6-10']. Dan jika Anda menggunakan ascending=False, itu tidak akan berhasil sama sekali, bahkan jika Anda membalikkannya dengandf.loc['2000-6-10':'2000-6-1']
bgoodr
1
Jika Anda ingin menyimpan kolom 'tanggal' sambil tetap memberikan nilainya ke indeks kerangka data, Anda dapat melakukan ini df.index = df ['date']
Richard Liang
64

Saya merasa pilihan terbaik adalah menggunakan cek langsung daripada menggunakan fungsi loc:

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

Ini bekerja untuk saya.

Masalah utama dengan fungsi loc dengan slice adalah batas harus ada pada nilai aktual, jika tidak ini akan menghasilkan KeyError.

Christin Jose
sumber
1
Saya pikir irisan via locsangat bagus. Dan menurut saya seperti yang dikatakan unutbu, baik start_date maupun end_date tidak harus ada dalam indeks .
nealmcb
cara memfilter tanggal sebagai (14 hari sebelum tanggal saat ini) .. jika tanggal todays adalah 2019-01-15 ... saya memerlukan data dari (2019-01-01 hingga 2019-01-15)
Praveen Snowy
Sederhana dan elegan. Terima kasih Christin, inilah yang saya coba lakukan. Bekerja untukku.
brohjoe
36

Anda juga dapat menggunakan between:

df[df.some_date.between(start_date, end_date)]
pomber
sumber
2
Juga periksa between_time: pandas.pydata.org/pandas-docs/version/0.20.3/generated/…
Anton Tarasenko
1
@ AntonTarasenko Anehnya, tidak bekerja dengan datetimes , melainkan hanya kali . Butuh waktu beberapa saat untuk menyadari perbedaan ini. Begitulah akhirnya saya berkonsultasi dengan utas ini.
rotton
19

Anda dapat menggunakan isinmetode pada datekolom seperti itu df[df["date"].isin(pd.date_range(start_date, end_date))]

Catatan: Ini hanya berfungsi dengan tanggal (saat pertanyaan diajukan) dan bukan cap waktu.

Contoh:

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

pemberian yang mana

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

Menjaga solusinya sederhana dan pythonic, saya sarankan Anda untuk mencoba ini.

Jika Anda akan sering melakukan ini, solusi terbaik adalah dengan terlebih dahulu mengatur kolom tanggal sebagai indeks yang akan mengkonversi kolom di DateTimeIndex dan menggunakan kondisi berikut untuk mengiris berbagai tanggal.

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

Dengan pengujian pandasversi 0.22.0saya sekarang Anda dapat menjawab pertanyaan ini dengan lebih mudah dengan kode yang lebih mudah dibaca hanya dengan menggunakan 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')})

Katakanlah Anda ingin mengambil tanggal antara 27 November 2018 dan 15 Januari 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

Perhatikan argumen inklusif. sangat membantu ketika Anda ingin menjadi eksplisit tentang jangkauan Anda. perhatikan ketika disetel ke True, kami juga mengembalikan 27 November 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

Metode ini juga lebih cepat daripada isinmetode yang disebutkan sebelumnya :

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

Namun, itu tidak lebih cepat dari jawaban yang saat ini diterima, disediakan oleh unutbu, hanya jika topeng sudah dibuat . tetapi jika topeng itu dinamis dan perlu dipindahkan ulang berulang kali, metode saya mungkin lebih efisien:

# 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
sumber
4

Pilihan lain, bagaimana mencapainya, adalah dengan menggunakan pandas.DataFrame.query()metode. Izinkan saya menunjukkan kepada Anda sebuah contoh pada kerangka data berikut yang disebut 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

Sebagai argumen, gunakan kondisi untuk memfilter seperti ini:

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

Jika Anda tidak ingin menyertakan batasan, cukup ubah kondisi seperti berikut:

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

Saya lebih suka tidak mengubah df.

Pilihan adalah untuk mengambil indexdari startdan endtanggal:

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]

yang mengakibatkan:

     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
sumber