【问题标题】:Create Pandas TimeSeries from Data, Period-range and aggregation function从数据、周期范围和聚合函数创建 Pandas TimeSeries
【发布时间】:2020-10-01 23:50:56
【问题描述】:

上下文

我想创建一个时间序列(使用 pandas),如果开始日期和结束日期在考虑的日期内,则计算 Id 的不同值。

为了便于阅读,这是问题的简化版本。

数据

让我们这样定义数据:

df = pd.DataFrame({
    'customerId': [
        '1', '1', '1', '2', '2'
    ],
    'id': [
        '1', '2', '3', '1', '2'
    ],
    'startDate': [
        '2000-01', '2000-01', '2000-04', '2000-05', '2000-06',
    ],
    'endDate': [
        '2000-08', '2000-02', '2000-07', '2000-07', '2000-08',
    ],
})

这样的周期范围:

period_range = pd.period_range(start='2000-01', end='2000-07', freq='M')

目标

对于每个 customerId,有几个不同的 id。 最终目标是,对于周期范围的每个date,对于每个customerId,其start_dateend_date 与函数my_date_predicate 匹配的不同id 的计数。

my_date_predicate的简化定义:

unset_date = pd.to_datetime("1900-01")


def my_date_predicate(date, row):
    return row.startDate <= date and \
           (row.endDate.equals(unset_date) or row.endDate > date)

等待结果

我想要这样的时间序列结果:

        date customerId customerCount
0   2000-01          1             2
1   2000-01          2             0
2   2000-02          1             1
3   2000-02          2             0
4   2000-03          1             1
5   2000-03          2             0
6   2000-04          1             2
7   2000-04          2             0
8   2000-05          1             2
9   2000-05          2             1
10  2000-06          1             2
11  2000-06          2             2
12  2000-07          1             1
13  2000-07          2             0

问题

我如何使用 pandas 来获得这样的结果?

【问题讨论】:

  • my_date_predicate是什么聚合函数?
  • 刚刚添加了my_date_predicate的定义。
  • 有人可以帮忙吗?
  • 您的样本数据中没有任何日期未设置的记录。你想添加一些,以便人们可以看到自己的代码运行良好吗?
  • 是的,例如,我们可以将最后一个 end_date 替换为 unset_date 作为测试(并且您的解决方案不起作用,但如果它之前被“最大日期”替换)。

标签: python pandas time-series


【解决方案1】:

这里有一个解决方案:

df.startDate = pd.to_datetime(df.startDate)
df.endDate = pd.to_datetime(df.endDate)
df["month"] = df.apply(lambda row: pd.date_range(row["startDate"], row["endDate"], freq="MS", closed = "left"), axis=1)
df = df.explode("month")

period_range = pd.period_range(start='2000-01', end='2000-07', freq='M')

t = pd.DataFrame(period_range.to_timestamp(), columns=["month"])
customers_df = pd.DataFrame(df.customerId.unique(), columns = ["customerId"])
t = pd.merge(t.assign(dummy=1), customers_df.assign(dummy=1), on = "dummy").drop("dummy", axis=1)
t = pd.merge(t, df, on = ["customerId", "month"], how = "left")
t.groupby(["month", "customerId"]).count()[["id"]].rename(columns={"id": "count"})

结果是:

                       count
month      customerId       
2000-01-01 1               2
           2               0
2000-02-01 1               1
           2               0
2000-03-01 1               1
           2               0
2000-04-01 1               2
           2               0
2000-05-01 1               2
           2               1
2000-06-01 1               2
           2               2
2000-07-01 1               1
           2               1

注意:

  • 对于未设置的日期,请在开始计算之前将结束日期替换为您感兴趣的最后一个日期。

【讨论】:

  • 感谢您非常有趣的回答,它与一个小样本完美配合。我今天将在真正的大样本上挑战它,以确保一切正常;)
  • 太棒了。请注意我关于未设置日期的最后一条说明。
  • 谢谢,您的解决方案运行良好,即使有更多数据。我怎样才能创建一个漂亮的绘图图 => 每个客户一条线,Y 轴上的员工数量和 X 轴上的月份?
  • 你有各种绘图库。有非常受欢迎的matplotlib。我过去使用过 plotly,效果很好。
  • 如果它回答了原始问题,您介意接受它作为答案吗?
【解决方案2】:

您可以使用 2 pivot_table 来获取索引中每个开始日期(和结束日期)列中每个客户的 ID countreindex 每个都带有您感兴趣的 period_date。从开始的枢轴中减去结束的枢轴。使用cumsum 获取每个客户 ID 的累积部分 ID。最后使用stackreset_index 来达到想要的形状。

#convert to period columns like period_date
df['startDate'] = pd.to_datetime(df['startDate']).dt.to_period('M')
df['endDate'] = pd.to_datetime(df['endDate']).dt.to_period('M')

#create the pivots
pvs = (df.pivot_table(index='startDate', columns='customerId', values='id', 
                      aggfunc='count', fill_value=0)
         .reindex(period_range, fill_value=0)
      )
pve = (df.pivot_table(index='endDate', columns='customerId', values='id', 
                      aggfunc='count', fill_value=0)
         .reindex(period_range, fill_value=0)
      )
print (pvs)
customerId  1  2
2000-01     2  0 #two id for customer 1 that start at this month
2000-02     0  0
2000-03     0  0
2000-04     1  0
2000-05     0  1 #one id for customer 2 that start at this month
2000-06     0  1
2000-07     0  0

现在您可以将一个减去另一个并使用cumsum 来获得每个日期所需的金额。

res = (pvs - pve).cumsum().stack().reset_index()
res.columns = ['date', 'customerId','customerCount']
print (res)
       date customerId  customerCount
0   2000-01          1              2
1   2000-01          2              0
2   2000-02          1              1
3   2000-02          2              0
4   2000-03          1              1
5   2000-03          2              0
6   2000-04          1              2
7   2000-04          2              0
8   2000-05          1              2
9   2000-05          2              1
10  2000-06          1              2
11  2000-06          2              2
12  2000-07          1              1
13  2000-07          2              1

请务必注意如何处理 unset_date,因为我看不到它的用途

【讨论】:

  • 谢谢,今天应该有时间测试一下。同时,不要犹豫,为这个问题投票;)
猜你喜欢
  • 2019-07-04
  • 1970-01-01
  • 2021-03-11
  • 2020-12-13
  • 2018-11-14
  • 2021-09-18
  • 2018-02-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多