在 pandas 中加快过去 60 天的平均值
Speeding up past-60-day mean in pandas
我使用过去 kaggle 挑战赛的数据,该挑战赛基于跨越 2.5 年时间跨度多个商店的面板数据。每个观察值都包括给定商店日期的顾客数量。对于每个商店日期,我的 objective 是计算过去 60 天内光顾这家商店的平均顾客数量。
下面的代码完全符合我的需要。然而,它会永远持续下去——处理 c.800k 行需要一个晚上。我正在寻找一种聪明的方法来更快地实现相同的 objective。
我已经包含了对初始数据集的 5 个观察结果以及相关变量:商店 ID (Store)、日期和客户数量 ("Customers")。
注:
- 对于迭代中的每一行,我最终使用 .loc 而不是例如row["Lagged No of customers"] 因为 "row" 没有在单元格中写入任何内容。我想知道为什么会这样。
- 我通常使用 "apply, axis = 1" 填充新列,所以我非常感谢基于此的任何解决方案。我发现 "apply" 对于每一行都可以正常工作,计算是使用同一行级别的值跨列完成的。但是,我不知道 "apply" 函数如何涉及不同的行,而这正是这个问题所需要的。到目前为止我看到的唯一例外是"diff",它在这里没有用。
谢谢。
示例数据:
pd.DataFrame({
'Store': {0: 1, 1: 1, 2: 1, 3: 1, 4: 1},
'Customers': {0: 668, 1: 578, 2: 619, 3: 635, 4: 785},
'Date': {
0: pd.Timestamp('2013-01-02 00:00:00'),
1: pd.Timestamp('2013-01-03 00:00:00'),
2: pd.Timestamp('2013-01-04 00:00:00'),
3: pd.Timestamp('2013-01-05 00:00:00'),
4: pd.Timestamp('2013-01-07 00:00:00')
}
})
有效但速度极慢的代码:
import pandas as pd
import numpy as np
data = pd.read_csv("Rossman - no of cust/dataset.csv")
data.Date = pd.to_datetime(data.Date)
data.Customers = data.Customers.astype(int)
for index, row in data.iterrows():
d = row["Date"]
store = row["Store"]
time_condition = (d - data["Date"]<np.timedelta64(60, 'D')) & (d > data["Date"])
sub_df = data.loc[ time_condition & (data["Store"] == store), :]
data.loc[ (data["Date"]==d) & (data["Store"] == store), "Lagged No customers"] = sub_df["Customers"].sum()
data.loc[ (data["Date"]==d) & (data["Store"] == store), "No of days"] = len(sub_df["Customers"])
if len(sub_df["Customers"]) > 0:
data.loc[ (data["Date"]==d) & (data["Store"] == store), "Av No of customers"] = int(sub_df["Customers"].sum()/len(sub_df["Customers"]))
考虑到你的小样本数据,我使用了两天滚动平均值而不是 60 天。
>>> (pd.rolling_mean(data.pivot(columns='Store', index='Date', values='Customers'), window=2)
.stack('Store'))
Date Store
2013-01-03 1 623.0
2013-01-04 1 598.5
2013-01-05 1 627.0
2013-01-07 1 710.0
dtype: float64
通过将日期数据作为索引并将存储作为列,您可以简单地获取滚动平均值。然后,您需要堆叠存储以使数据恢复到正确的形状。
这是最终堆栈之前原始数据的一些示例输出:
Store 1 2 3
Date
2015-07-29 541.5 686.5 767.0
2015-07-30 534.5 664.0 769.5
2015-07-31 550.5 613.0 822.0
.stack('Store')
之后,变成:
Date Store
2015-07-29 1 541.5
2 686.5
3 767.0
2015-07-30 1 534.5
2 664.0
3 769.5
2015-07-31 1 550.5
2 613.0
3 822.0
dtype: float64
假设上面的命名为df
,然后您可以将其合并回您的原始数据,如下所示:
data.merge(df.reset_index(),
how='left',
on=['Date', 'Store'])
编辑:
数据中存在明显的季节性模式,您可能希望对其进行调整。无论如何,您可能希望滚动平均值是七的倍数以表示偶数周。我在下面的示例(9 周)中使用了 63 天的时间 window。
为了避免丢失刚刚开业的商店(以及时间段开始时的商店)的数据,您可以在滚动平均函数中指定 min_periods=1
。这将为您提供给定时间内所有可用观测值的平均值 window
df = data.loc[data.Customers > 0, ['Date', 'Store', 'Customers']]
result = (pd.rolling_mean(df.pivot(columns='Store', index='Date', values='Customers'),
window=63, min_periods=1)
.stack('Store'))
result.name = 'Customers_63d_mvg_avg'
df = df.merge(result.reset_index(), on=['Store', 'Date'], how='left')
>>> df.sort_values(['Store', 'Date']).head(8)
Date Store Customers Customers_63d_mvg_avg
843212 2013-01-02 1 668 668.000000
842103 2013-01-03 1 578 623.000000
840995 2013-01-04 1 619 621.666667
839888 2013-01-05 1 635 625.000000
838763 2013-01-07 1 785 657.000000
837658 2013-01-08 1 654 656.500000
836553 2013-01-09 1 626 652.142857
835448 2013-01-10 1 615 647.500000
为了更清楚地了解发生了什么,这里有一个玩具示例:
s = pd.Series([1,2,3,4,5] + [np.NaN] * 2 + [6])
>>> pd.concat([s, pd.rolling_mean(s, window=4, min_periods=1)], axis=1)
0 1
0 1 1.0
1 2 1.5
2 3 2.0
3 4 2.5
4 5 3.5
5 NaN 4.0
6 NaN 4.5
7 6 5.5
window 是四个观察值,但请注意,5.5 的最终值等于 (5 + 6) / 2。4.0 和 4.5 的值是 (3 + 4 + 5) / 3 和 (4 + 5) / 2, 分别.
在我们的示例中,枢轴 table 的 NaN 行不会合并回 df
,因为我们进行了左连接并且 df
中的所有行都有一个或更多客户。
滚动数据图表如下:
df.set_index(['Date', 'Store']).unstack('Store').plot(legend=False)
我使用过去 kaggle 挑战赛的数据,该挑战赛基于跨越 2.5 年时间跨度多个商店的面板数据。每个观察值都包括给定商店日期的顾客数量。对于每个商店日期,我的 objective 是计算过去 60 天内光顾这家商店的平均顾客数量。
下面的代码完全符合我的需要。然而,它会永远持续下去——处理 c.800k 行需要一个晚上。我正在寻找一种聪明的方法来更快地实现相同的 objective。
我已经包含了对初始数据集的 5 个观察结果以及相关变量:商店 ID (Store)、日期和客户数量 ("Customers")。
注:
- 对于迭代中的每一行,我最终使用 .loc 而不是例如row["Lagged No of customers"] 因为 "row" 没有在单元格中写入任何内容。我想知道为什么会这样。
- 我通常使用 "apply, axis = 1" 填充新列,所以我非常感谢基于此的任何解决方案。我发现 "apply" 对于每一行都可以正常工作,计算是使用同一行级别的值跨列完成的。但是,我不知道 "apply" 函数如何涉及不同的行,而这正是这个问题所需要的。到目前为止我看到的唯一例外是"diff",它在这里没有用。
谢谢。
示例数据:
pd.DataFrame({
'Store': {0: 1, 1: 1, 2: 1, 3: 1, 4: 1},
'Customers': {0: 668, 1: 578, 2: 619, 3: 635, 4: 785},
'Date': {
0: pd.Timestamp('2013-01-02 00:00:00'),
1: pd.Timestamp('2013-01-03 00:00:00'),
2: pd.Timestamp('2013-01-04 00:00:00'),
3: pd.Timestamp('2013-01-05 00:00:00'),
4: pd.Timestamp('2013-01-07 00:00:00')
}
})
有效但速度极慢的代码:
import pandas as pd
import numpy as np
data = pd.read_csv("Rossman - no of cust/dataset.csv")
data.Date = pd.to_datetime(data.Date)
data.Customers = data.Customers.astype(int)
for index, row in data.iterrows():
d = row["Date"]
store = row["Store"]
time_condition = (d - data["Date"]<np.timedelta64(60, 'D')) & (d > data["Date"])
sub_df = data.loc[ time_condition & (data["Store"] == store), :]
data.loc[ (data["Date"]==d) & (data["Store"] == store), "Lagged No customers"] = sub_df["Customers"].sum()
data.loc[ (data["Date"]==d) & (data["Store"] == store), "No of days"] = len(sub_df["Customers"])
if len(sub_df["Customers"]) > 0:
data.loc[ (data["Date"]==d) & (data["Store"] == store), "Av No of customers"] = int(sub_df["Customers"].sum()/len(sub_df["Customers"]))
考虑到你的小样本数据,我使用了两天滚动平均值而不是 60 天。
>>> (pd.rolling_mean(data.pivot(columns='Store', index='Date', values='Customers'), window=2)
.stack('Store'))
Date Store
2013-01-03 1 623.0
2013-01-04 1 598.5
2013-01-05 1 627.0
2013-01-07 1 710.0
dtype: float64
通过将日期数据作为索引并将存储作为列,您可以简单地获取滚动平均值。然后,您需要堆叠存储以使数据恢复到正确的形状。
这是最终堆栈之前原始数据的一些示例输出:
Store 1 2 3
Date
2015-07-29 541.5 686.5 767.0
2015-07-30 534.5 664.0 769.5
2015-07-31 550.5 613.0 822.0
.stack('Store')
之后,变成:
Date Store
2015-07-29 1 541.5
2 686.5
3 767.0
2015-07-30 1 534.5
2 664.0
3 769.5
2015-07-31 1 550.5
2 613.0
3 822.0
dtype: float64
假设上面的命名为df
,然后您可以将其合并回您的原始数据,如下所示:
data.merge(df.reset_index(),
how='left',
on=['Date', 'Store'])
编辑: 数据中存在明显的季节性模式,您可能希望对其进行调整。无论如何,您可能希望滚动平均值是七的倍数以表示偶数周。我在下面的示例(9 周)中使用了 63 天的时间 window。
为了避免丢失刚刚开业的商店(以及时间段开始时的商店)的数据,您可以在滚动平均函数中指定 min_periods=1
。这将为您提供给定时间内所有可用观测值的平均值 window
df = data.loc[data.Customers > 0, ['Date', 'Store', 'Customers']]
result = (pd.rolling_mean(df.pivot(columns='Store', index='Date', values='Customers'),
window=63, min_periods=1)
.stack('Store'))
result.name = 'Customers_63d_mvg_avg'
df = df.merge(result.reset_index(), on=['Store', 'Date'], how='left')
>>> df.sort_values(['Store', 'Date']).head(8)
Date Store Customers Customers_63d_mvg_avg
843212 2013-01-02 1 668 668.000000
842103 2013-01-03 1 578 623.000000
840995 2013-01-04 1 619 621.666667
839888 2013-01-05 1 635 625.000000
838763 2013-01-07 1 785 657.000000
837658 2013-01-08 1 654 656.500000
836553 2013-01-09 1 626 652.142857
835448 2013-01-10 1 615 647.500000
为了更清楚地了解发生了什么,这里有一个玩具示例:
s = pd.Series([1,2,3,4,5] + [np.NaN] * 2 + [6])
>>> pd.concat([s, pd.rolling_mean(s, window=4, min_periods=1)], axis=1)
0 1
0 1 1.0
1 2 1.5
2 3 2.0
3 4 2.5
4 5 3.5
5 NaN 4.0
6 NaN 4.5
7 6 5.5
window 是四个观察值,但请注意,5.5 的最终值等于 (5 + 6) / 2。4.0 和 4.5 的值是 (3 + 4 + 5) / 3 和 (4 + 5) / 2, 分别.
在我们的示例中,枢轴 table 的 NaN 行不会合并回 df
,因为我们进行了左连接并且 df
中的所有行都有一个或更多客户。
滚动数据图表如下:
df.set_index(['Date', 'Store']).unstack('Store').plot(legend=False)