每周分组 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 时,closedlabel kwargs 都默认为 right。将 freq 设置为 WW-Sun 的缩写)是可行的,因为我们希望我们的一周在星期日结束(包括星期日,g.closed == 'right' 处理这个)。不幸的是,pd.Grouper 文档字符串不显示默认值,但您可以这样查看它们:

g = pd.Grouper(key='dt', freq='W')
print(g.closed, g.label)
> right right