簡體   English   中英

根據另一個 dataframe 的日期范圍對多個 Dask dataframe 進行切片的最快方法

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

筆記

  1. 這使用 3M 行。 您正在使用 30M 行,因此如果您想檢查時間等,則必須修改開始時生成的虛擬數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM