【问题标题】:Convert Date Ranges into a Time Series将日期范围转换为时间序列
【发布时间】:2021-08-26 12:50:03
【问题描述】:

我有以下格式的数据:

    BEG_DT      END_DT      REGION
0   2020-01-01  2021-06-09  region_a
1   2020-06-29  2021-06-09  region_a
2   2020-01-01  2020-06-29  region_a
3   2020-01-01  2021-06-09  region_b
4   2020-01-01  2021-06-09  region_b
5   2020-01-01  2021-06-09  region_a
6   2020-01-01  2021-06-09  region_a
7   2020-07-08  2021-06-09  region_a
8   2020-01-01  2020-07-08  region_a
9   2021-05-10  2021-06-09  region_a
10  2020-01-01  2021-05-10  region_a
11  2020-01-01  2021-06-09  region_a
12  2020-01-01  2021-06-09  region_a
13  2020-01-01  2021-06-09  region_a
14  2020-01-01  2021-06-09  region_a
15  2020-01-01  2021-06-09  region_a
16  2020-01-01  2021-06-09  region_a
17  2020-01-01  2021-06-09  region_b
18  2020-01-01  2021-06-09  region_a
19  2020-02-10  2021-06-09  region_a
20  2020-01-01  2020-02-10  region_a
21  2020-01-01  2021-06-09  region_a
22  2020-01-01  2021-06-09  region_b
23  2020-01-01  2021-06-09  region_a
24  2020-05-31  2021-06-09  region_b
25  2020-01-01  2020-05-31  region_b
26  2020-07-31  2021-06-09  region_a
27  2020-03-01  2020-07-31  region_a
28  2020-01-01  2020-03-01  region_a
29  2021-03-08  2021-06-09  region_a
30  2020-03-31  2021-03-08  region_a
31  2020-01-01  2020-03-31  region_a
32  2020-01-01  2021-06-09  region_a
33  2020-01-01  2021-06-09  region_a
34  2020-12-31  2021-06-09  region_a
35  2020-01-01  2020-12-31  region_a
36  2020-01-01  2021-06-09  region_a
37  2021-03-17  2021-06-09  region_a
38  2020-01-01  2021-03-17  region_a
39  2020-01-01  2021-06-09  region_a
40  2021-03-31  2021-06-09  region_b
41  2020-01-01  2021-03-31  region_b
42  2020-01-01  2021-06-09  region_a
43  2020-05-31  2021-06-09  region_b
44  2020-01-01  2020-05-31  region_b
45  2021-05-08  2021-06-09  region_c
46  2021-03-31  2021-05-08  region_c
47  2020-12-31  2021-03-31  region_c
48  2020-01-01  2020-12-31  region_a
49  2020-01-01  2021-06-09  region_a

每一行代表给定 id 在给定区域中存在的持续时间。

如何从日期范围转换为时间序列?

应将日期范围重新采样到单个时间序列索引中。

REGION 列应转为三列('region_a'、'region_b'、'region_c')

这些值应该是新索引位于原始记录的 BEG_DT 和 END_DT 之间的记录数。

             region_b    region_a   region_c
2020-01-01   9000        8000       1000
2020-01-02   8940        7932       1128
...
2021-06-09   8067        7062       2871

【问题讨论】:

    标签: python pandas time-series pandas-groupby


    【解决方案1】:

    我不确定我是否正确理解了您的意图,但也许以下内容可以帮助您找到解决方案。

    使用您提供的数据中的 DataFrame df

            BEG_DT      END_DT    REGION
    0   2020-01-01  2021-06-09  region_a
    1   2020-06-29  2021-06-09  region_a
    2   2020-01-01  2020-06-29  region_a
    ... ...         ...         ...
    47  2020-12-31  2021-03-31  region_c
    48  2020-01-01  2020-12-31  region_a
    49  2020-01-01  2021-06-09  region_a
    

    这个

    def dates_range(row):
        return pd.date_range(start=row.iat[0], end=row.iat[1], freq='D')
    
    df['DATE'] = df.apply(dates_range, axis='columns')
    df = df[['DATE', 'REGION']].explode('DATE')
    df['COUNT'] = 1
    df = df.pivot_table(index='DATE', columns='REGION', aggfunc='sum')
    

    为您提供以下结果 (print(df)):

                  COUNT                  
    REGION     region_a region_b region_c
    DATE                                 
    2020-01-01     27.0      7.0      NaN
    2020-01-02     27.0      7.0      NaN
    2020-01-03     27.0      7.0      NaN
    2020-01-04     27.0      7.0      NaN
    2020-01-05     27.0      7.0      NaN
    ...             ...      ...      ...
    2021-06-05     26.0      7.0      1.0
    2021-06-06     26.0      7.0      1.0
    2021-06-07     26.0      7.0      1.0
    2021-06-08     26.0      7.0      1.0
    2021-06-09     26.0      7.0      1.0
    

    发生了什么:applydf 中的每一行创建一个范围,其日期包含在区间[BEG_DT, END_DT] 中,并将其存储在DATE 列中。在下一步中,DATE 中的范围会“爆炸”。生成的长 DataFrame 有 2 列,一列 - DATE - 包含列表中的所有日期,另一列 - REGION - 包含相应区域。然后添加一列 - COUNT - 仅用于在下一个 pivot_table 步骤中进行计数。

    这有意义吗?

    编辑

    如果explode 的使用太占用内存那么这个

    from collections import Counter
    
    def count_dates(row):
        return Counter(pd.date_range(row.iat[0], row.iat[1], freq='D'))
    
    df['COUNT'] = df.apply(count_dates, axis='columns')
    df = df[['REGION', 'COUNT']].groupby('REGION').sum()
    df = pd.DataFrame.from_dict({
                region: count
                for region, count in df.itertuples()
            })
    

    或者更短一点

    ...
    df['COUNT'] = df.apply(count_dates, axis='columns')
    df = pd.DataFrame.from_dict({
                region: sum(group.COUNT, Counter({}))
                for region, group in df[['REGION', 'COUNT']].groupby('REGION')
            })
    

    可能是另一种选择。结果应该是一样的。

    上次修改:更改了替代解决方案中的一些名称,以更好地反映调整后的机制。

    【讨论】:

    • 我想是的。 Explode 是一个简洁的功能。我知道我遇到过它,但这是我第一次看到它有什么用处。
    • 我认为这可能会过度放大 DataFrame 的大小。
    • @JoshuaFarina 如果 DataFrame 的大小变得太大,那么我刚刚添加的版本可能是一个选项。
    • 感谢您的帮助。我认为通过一些调整,这可能会奏效。
    • 我想出了一些不同的方法,但我打算在有时间的时候比较方法。让我知道你的想法。
    【解决方案2】:

    我想出了这个:

    periods =  pd.date_range(df.BEG_DT.min(), df.END_DT.max(), freq="W-MON")
    pd.DataFrame({p: df.query("BEG_DT < @p & @p <= END_DT").REGION.value_counts() for p in periods}).T 
    

    我需要测试一下它是否更快。

    对于一些额外的上下文,这个数据集包含 1300 万条记录。

    【讨论】:

    • 不错!到目前为止,我还没有与query 合作过,但现在我确信我需要调查一下。期待您的计时测量。
    • 它与 df[(df["BEG_DT"]
    • 嗯......呃......到目前为止,它是三分钟,而不是三分钟。
    • 我觉得是时候取消了。
    • 天哪,太糟糕了:((
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-10
    • 1970-01-01
    • 2013-08-11
    • 1970-01-01
    相关资源
    最近更新 更多