每周分组 python pandas 数据框(从星期一开始)
Group python pandas dataframe per weeks (starting on Monday)
我有一个包含每天值的数据框(请参见下面的 df)。
我想每周对 "Forecast" 字段进行分组,但将星期一作为一周的第一天。
目前我可以通过 pd.TimeGrouper('W')(见下面的 df_final)来完成,但它将一周从星期日开始分组(见下面的 df_final)
import pandas as pd
data = [("W1","G1",1234,pd.to_datetime("2015-07-1"),8),
("W1","G1",1234,pd.to_datetime("2015-07-30"),2),
("W1","G1",1234,pd.to_datetime("2015-07-15"),2),
("W1","G1",1234,pd.to_datetime("2015-07-2"),4),
("W1","G2",2345,pd.to_datetime("2015-07-5"),5),
("W1","G2",2345,pd.to_datetime("2015-07-7"),1),
("W1","G2",2345,pd.to_datetime("2015-07-9"),1),
("W1","G2",2345,pd.to_datetime("2015-07-11"),3)]
labels = ["Site","Type","Product","Date","Forecast"]
df = pd.DataFrame(data,columns=labels).set_index(["Site","Type","Product","Date"])
df
Forecast
Site Type Product Date
W1 G1 1234 2015-07-01 8
2015-07-30 2
2015-07-15 2
2015-07-02 4
G2 2345 2015-07-05 5
2015-07-07 1
2015-07-09 1
2015-07-11 3
df_final = (df
.reset_index()
.set_index("Date")
.groupby(["Site","Product",pd.TimeGrouper('W')])["Forecast"].sum()
.astype(int)
.reset_index())
df_final["DayOfWeek"] = df_final["Date"].dt.dayofweek
df_final
Site Product Date Forecast DayOfWeek
0 W1 1234 2015-07-05 12 6
1 W1 1234 2015-07-19 2 6
2 W1 1234 2015-08-02 2 6
3 W1 2345 2015-07-05 5 6
4 W1 2345 2015-07-12 5 6
使用W-MON
代替W
,检查anchored offsets:
df_final = (df
.reset_index()
.set_index("Date")
.groupby(["Site","Product",pd.Grouper(freq='W-MON')])["Forecast"].sum()
.astype(int)
.reset_index())
df_final["DayOfWeek"] = df_final["Date"].dt.dayofweek
print (df_final)
Site Product Date Forecast DayOfWeek
0 W1 1234 2015-07-06 12 0
1 W1 1234 2015-07-20 2 0
2 W1 1234 2015-08-03 2 0
3 W1 2345 2015-07-06 5 0
4 W1 2345 2015-07-13 5 0
针对这个问题,我有以下三种解决方案。首先,我应该声明前接受的答案是不正确的。原因如下:
# let's create an example df of length 9, 2020-03-08 is a Sunday
s = pd.DataFrame({'dt':pd.date_range('2020-03-08', periods=9, freq='D'),
'counts':0})
> s
dt
counts
0
2020-03-08 00:00:00
0
1
2020-03-09 00:00:00
0
2
2020-03-10 00:00:00
0
3
2020-03-11 00:00:00
0
4
2020-03-12 00:00:00
0
5
2020-03-13 00:00:00
0
6
2020-03-14 00:00:00
0
7
2020-03-15 00:00:00
0
8
2020-03-16 00:00:00
0
这九天跨越三个星期一至星期日的星期。 3 月 2 日、9 日和 16 日这几周。让我们试试接受的答案:
# the accepted answer
> s.groupby(pd.Grouper(key='dt',freq='W-Mon')).count()
dt
counts
2020-03-09 00:00:00
2
2020-03-16 00:00:00
7
这是错误的,因为 OP 希望在生成的数据框中将“星期一作为一周的第一天”(而不是一周的最后一天)。让我们看看当我们尝试 freq='W'
时会得到什么
> s.groupby(pd.Grouper(key='dt', freq='W')).count()
dt
counts
2020-03-08 00:00:00
1
2020-03-15 00:00:00
7
2020-03-22 00:00:00
1
这条石斑鱼实际上按照我们的意愿分组(周一到周日),但将 'dt' 标记为一周的结束,而不是一周的开始。所以,为了得到我们想要的,我们可以将索引移动 6 天,例如:
w = s.groupby(pd.Grouper(key='dt', freq='W')).count()
w.index -= pd.Timedelta(days=6)
或者我们可以这样做:
s.groupby(pd.Grouper(key='dt',freq='W-Mon',label='left',closed='left')).count()
第三种解决方案,可以说是最易读的解决方案,首先将 dt
转换为句点,然后分组,最后(如果需要)转换回时间戳:
s.groupby(s.dt.dt.to_period('W'))['counts'].count().to_timestamp()
# a variant of this solution is: s.set_index('dt').to_period('W').groupby(pd.Grouper(freq='W')).count().to_timestamp()
所有这些解决方案return OP 的要求:
dt
counts
2020-03-02 00:00:00
1
2020-03-09 00:00:00
7
2020-03-16 00:00:00
1
说明:当 freq
提供给 pd.Grouper
时,closed
和 label
kwargs 都默认为 right
。将 freq
设置为 W
(W-Sun
的缩写)是可行的,因为我们希望我们的一周在星期日结束(包括星期日,g.closed == 'right'
处理这个)。不幸的是,pd.Grouper
文档字符串不显示默认值,但您可以这样查看它们:
g = pd.Grouper(key='dt', freq='W')
print(g.closed, g.label)
> right right
我有一个包含每天值的数据框(请参见下面的 df)。 我想每周对 "Forecast" 字段进行分组,但将星期一作为一周的第一天。
目前我可以通过 pd.TimeGrouper('W')(见下面的 df_final)来完成,但它将一周从星期日开始分组(见下面的 df_final)
import pandas as pd
data = [("W1","G1",1234,pd.to_datetime("2015-07-1"),8),
("W1","G1",1234,pd.to_datetime("2015-07-30"),2),
("W1","G1",1234,pd.to_datetime("2015-07-15"),2),
("W1","G1",1234,pd.to_datetime("2015-07-2"),4),
("W1","G2",2345,pd.to_datetime("2015-07-5"),5),
("W1","G2",2345,pd.to_datetime("2015-07-7"),1),
("W1","G2",2345,pd.to_datetime("2015-07-9"),1),
("W1","G2",2345,pd.to_datetime("2015-07-11"),3)]
labels = ["Site","Type","Product","Date","Forecast"]
df = pd.DataFrame(data,columns=labels).set_index(["Site","Type","Product","Date"])
df
Forecast
Site Type Product Date
W1 G1 1234 2015-07-01 8
2015-07-30 2
2015-07-15 2
2015-07-02 4
G2 2345 2015-07-05 5
2015-07-07 1
2015-07-09 1
2015-07-11 3
df_final = (df
.reset_index()
.set_index("Date")
.groupby(["Site","Product",pd.TimeGrouper('W')])["Forecast"].sum()
.astype(int)
.reset_index())
df_final["DayOfWeek"] = df_final["Date"].dt.dayofweek
df_final
Site Product Date Forecast DayOfWeek
0 W1 1234 2015-07-05 12 6
1 W1 1234 2015-07-19 2 6
2 W1 1234 2015-08-02 2 6
3 W1 2345 2015-07-05 5 6
4 W1 2345 2015-07-12 5 6
使用W-MON
代替W
,检查anchored offsets:
df_final = (df
.reset_index()
.set_index("Date")
.groupby(["Site","Product",pd.Grouper(freq='W-MON')])["Forecast"].sum()
.astype(int)
.reset_index())
df_final["DayOfWeek"] = df_final["Date"].dt.dayofweek
print (df_final)
Site Product Date Forecast DayOfWeek
0 W1 1234 2015-07-06 12 0
1 W1 1234 2015-07-20 2 0
2 W1 1234 2015-08-03 2 0
3 W1 2345 2015-07-06 5 0
4 W1 2345 2015-07-13 5 0
针对这个问题,我有以下三种解决方案。首先,我应该声明前接受的答案是不正确的。原因如下:
# let's create an example df of length 9, 2020-03-08 is a Sunday
s = pd.DataFrame({'dt':pd.date_range('2020-03-08', periods=9, freq='D'),
'counts':0})
> s
dt | counts | |
---|---|---|
0 | 2020-03-08 00:00:00 | 0 |
1 | 2020-03-09 00:00:00 | 0 |
2 | 2020-03-10 00:00:00 | 0 |
3 | 2020-03-11 00:00:00 | 0 |
4 | 2020-03-12 00:00:00 | 0 |
5 | 2020-03-13 00:00:00 | 0 |
6 | 2020-03-14 00:00:00 | 0 |
7 | 2020-03-15 00:00:00 | 0 |
8 | 2020-03-16 00:00:00 | 0 |
这九天跨越三个星期一至星期日的星期。 3 月 2 日、9 日和 16 日这几周。让我们试试接受的答案:
# the accepted answer
> s.groupby(pd.Grouper(key='dt',freq='W-Mon')).count()
dt | counts |
---|---|
2020-03-09 00:00:00 | 2 |
2020-03-16 00:00:00 | 7 |
这是错误的,因为 OP 希望在生成的数据框中将“星期一作为一周的第一天”(而不是一周的最后一天)。让我们看看当我们尝试 freq='W'
> s.groupby(pd.Grouper(key='dt', freq='W')).count()
dt | counts |
---|---|
2020-03-08 00:00:00 | 1 |
2020-03-15 00:00:00 | 7 |
2020-03-22 00:00:00 | 1 |
这条石斑鱼实际上按照我们的意愿分组(周一到周日),但将 'dt' 标记为一周的结束,而不是一周的开始。所以,为了得到我们想要的,我们可以将索引移动 6 天,例如:
w = s.groupby(pd.Grouper(key='dt', freq='W')).count()
w.index -= pd.Timedelta(days=6)
或者我们可以这样做:
s.groupby(pd.Grouper(key='dt',freq='W-Mon',label='left',closed='left')).count()
第三种解决方案,可以说是最易读的解决方案,首先将 dt
转换为句点,然后分组,最后(如果需要)转换回时间戳:
s.groupby(s.dt.dt.to_period('W'))['counts'].count().to_timestamp()
# a variant of this solution is: s.set_index('dt').to_period('W').groupby(pd.Grouper(freq='W')).count().to_timestamp()
所有这些解决方案return OP 的要求:
dt | counts |
---|---|
2020-03-02 00:00:00 | 1 |
2020-03-09 00:00:00 | 7 |
2020-03-16 00:00:00 | 1 |
说明:当 freq
提供给 pd.Grouper
时,closed
和 label
kwargs 都默认为 right
。将 freq
设置为 W
(W-Sun
的缩写)是可行的,因为我们希望我们的一周在星期日结束(包括星期日,g.closed == 'right'
处理这个)。不幸的是,pd.Grouper
文档字符串不显示默认值,但您可以这样查看它们:
g = pd.Grouper(key='dt', freq='W')
print(g.closed, g.label)
> right right