![](/img/trans.png)
[英]Fastest way to filter results from one dataframe into another dataframe based on multiple conditions (including date range)
[英]Fastest way to slice multiple Dask dataframe based on the date ranges from another dataframe
select 的最快方法是什么,只有 df1 日期范圍內的日期來自 ddf2 Dask dataframe? 應刪除所有超出范圍的日期。
df1 - Pandas dataframe 與開始結束日期范圍
start end
01 2018-06-25 2018-06-29
02 2019-05-06 2019-05-13
...
dd2 - Dask dataframe(30M 行)
(*) 必須選擇標記的行
date value1
2018-01-01 23
2018-01-01 24
2018-01-02 545
2018-01-03 433
2018-01-04 23
*2018-06-25 234
*2018-06-25 50
*2018-06-25 120
*2018-06-26 22
*2018-06-27 32
*2018-06-27 123
*2018-06-28 603
*2018-06-29 625
2019-01-01 734
2019-01-01 241
2019-01-01 231
2019-01-02 211
2019-01-02 214
2019-05-05 234
2019-05-05 111
*2019-05-06 846
*2019-05-06 231
*2019-05-07 654
*2019-05-07 119
*2019-05-08 212
*2019-05-08 122
*2019-05-06 765
*2019-05-13 231
*2019-05-13 213
*2019-05-13 443
2019-05-14 321
2019-05-14 231
2019-05-15 123
...
Output:Dask dataframe 需要附加切片
date value1
2018-06-25 234
2018-06-25 50
2018-06-25 120
2018-06-26 22
2018-06-27 32
2018-06-27 123
2018-06-28 603
2018-06-29 625
2019-05-06 846
2019-05-06 231
2019-05-07 654
2019-05-07 119
2019-05-08 212
2019-05-08 122
2019-05-06 765
2019-05-13 231
2019-05-13 213
2019-05-13 443
此代碼有效,但我需要在 df1 中傳遞開始和結束日期范圍以過濾 dd2,而無需手動硬編碼日期。
dd2 = dd2[
(dd2['date'] >= '2018-06-25') & (dd2['date'] <= '2018-06-29') |
(dd2['date'] >= '2019-05-06') & (dd2['date'] <= '2019-05-13')
]
這看起來可能有效:
from itertools import starmap
date_ddf = ddf.set_index("date")
slices = starmap(slice, df.values)
# There might be a more "Pandas-esque" way to do this, but I
# don't know it yet.
sliced = map(date_ddf.__getitem__, slices)
# We have to reify the `map` object into a `list` for Dask.
concat_ddf = dd.concat(list(sliced))
concat_ddf.compute()
每次通過map
date_ddf.__getitem__
上的 map 都會返回原始幀的剪輯,因此需要dd.concat
將其重新組合在一起。
這是另一種方法,但使用列表推導按索引進行切片,並驗證(最后)切片是否正確完成。
進口
from datetime import datetime
import dask.dataframe as dd
import numpy as np
import pandas as pd
from dask import compute
指定可調輸入
# Start date from which to create dummy data to use
data_start_date = "1700-01-01"
# Frequency of dummy data created (hourly)
data_freq = "H"
# number of rows of data to generate
nrows = 3_000_000
# Dask DataFrame chunk size; will be used later to determine how many files
# (of the dummy data generated here) will be exported to disk
chunksize = 75_000
生成帶有切片邊界日期的df1
df1 = pd.DataFrame.from_records(
[
{"start": datetime(1850, 1, 6, 0, 0, 0), "end": datetime(1870, 9, 4, 23, 0, 0)},
{"start": datetime(1880, 7, 6, 0, 0, 0), "end": datetime(1895, 4, 9, 23, 0, 0)},
{"start": datetime(1910, 11, 25, 0, 0, 0), "end": datetime(1915, 5, 5, 23, 0, 0)},
{"start": datetime(1930, 10, 8, 0, 0, 0), "end": datetime(1940, 2, 8, 23, 0, 0)},
{"start": datetime(1945, 9, 9, 0, 0, 0), "end": datetime(1950, 1, 3, 23, 0, 0)},
]
)
print(df1)
start end
0 1850-01-06 1870-09-04 23:00:00
1 1880-07-06 1895-04-09 23:00:00
2 1910-11-25 1915-05-05 23:00:00
3 1930-10-08 1940-02-08 23:00:00
4 1945-09-09 1950-01-03 23:00:00
創建虛擬數據
wanted
將在這里分配一個名為 Want 的列,所有行都為False
df = pd.DataFrame(
np.random.rand(nrows),
index=pd.date_range(data_start_date, periods=nrows, freq="h"),
columns=["value1"],
)
df.index.name = "date"
df["wanted"] = False
print(df.head())
value1 wanted
date
1700-01-01 00:00:00 0.504119 False
1700-01-01 01:00:00 0.582796 False
1700-01-01 02:00:00 0.383905 False
1700-01-01 03:00:00 0.995389 False
1700-01-01 04:00:00 0.592130 False
現在,如果行的日期與df1
中的日期相同,我們會將所需的行更改為True
wanted
的列在您的實際用例中不是必需的,但只需要檢查我們的工作for _, row in df1.iterrows():
df.loc[row['start']: row['end'], "wanted"] = True
df = df.reset_index()
print(df.head())
print(df["wanted"].value_counts().to_frame())
date value1 wanted
0 1700-01-01 00:00:00 0.504119 False
1 1700-01-01 01:00:00 0.582796 False
2 1700-01-01 02:00:00 0.383905 False
3 1700-01-01 03:00:00 0.995389 False
4 1700-01-01 04:00:00 0.592130 False
wanted
False 2530800
True 469200
請注意,在wanted
列上調用.value_counts()
會顯示此列中的True
值的數量,如果我們正確地對數據進行切片,我們應該期望這些值。 這是使用pandas.DataFrame
中的數據完成的,但稍后我們將在dask.DataFrame
中使用相同的數據來完成此操作。
現在,我們將數據導出到本地的多個.parquet
文件中
dask
的數據開始.parquet
蒼蠅,我們會將pandas.DataFrame
轉換為dask.DataFrame
,然后設置將在每個導出文件中放置多少個塊大小文件(將創建chunksize
個源文件)塊chunksize
參數ddf = dd.from_pandas(df, chunksize=chunksize)
ddf.to_parquet("data", engine="auto")
現在將所有.parquet
文件直接加載到單個dask.DataFrame
並將date
列設置為索引
dask.DataFrame
時才指定它,之后不再更改它ddf = dd.read_parquet(
"data/",
dtype={"value1": "float64"},
index="date",
parse_dates=["date"],
)
print(ddf)
Dask DataFrame Structure:
value1 wanted
npartitions=40
1700-01-01 00:00:00 float64 bool
1708-07-23 00:00:00 ... ...
... ... ...
2033-09-07 00:00:00 ... ...
2042-03-28 23:00:00 ... ...
Dask Name: read-parquet, 40 tasks
現在,我們准備使用df1
中的日期進行切片。 我們將使用列表理解來遍歷df1
中的每一行,使用該行對數據進行切片(在dask.DataFrame
中),然后調用dd.concat
(就像@joebeeson 所做的那樣)
slices = dd.concat([ddf.loc[row['start']: row['end']] for _, row in df1.iterrows()])
最后,在此延遲dask
對象列表上進行計算,以獲得單個pandas.DataFrame
切片以提供所需的日期
ddf_sliced_computed = compute(slices)[0].reset_index()
print(ddf_sliced_computed.head())
print(ddf_sliced_computed["wanted"].value_counts().to_frame())
date value1 wanted
0 1850-01-06 00:00:00 0.671781 True
1 1850-01-06 01:00:00 0.455022 True
2 1850-01-06 02:00:00 0.490212 True
3 1850-01-06 03:00:00 0.240171 True
4 1850-01-06 04:00:00 0.162088 True
wanted
True 469200
如您所見,我們已經在wanted
列中切出了具有正確數量的True
值的行。 我們可以使用pandas.DataFrame
明確驗證這一點,我們之前使用它來生成稍后寫入磁盤的虛擬數據
assert all(ddf_sliced_computed["wanted"] == True)
assert (
df[df["wanted"] == True]
.reset_index(drop=True)
.equals(ddf_sliced_computed[ddf_sliced_computed["wanted"] == True])
)
筆記
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.