Pandas:按日历周分组,然后为实际日期时间绘制分组条形图

Posted

技术标签:

【中文标题】Pandas:按日历周分组,然后为实际日期时间绘制分组条形图【英文标题】:Pandas: Group by calendar-week, then plot grouped barplots for the real datetime 【发布时间】:2014-08-03 21:10:48 【问题描述】:

编辑

我找到了一个很好的解决方案,并将其发布在下面作为答案。 结果将如下所示:


您可以为这个问题生成一些示例数据:

codes = list('ABCDEFGH'); 
dates = pd.Series(pd.date_range('2013-11-01', '2014-01-31')); 
dates = dates.append(dates)
dates.sort()
df = pd.DataFrame('amount': np.random.randint(1, 10, dates.size), 'col1': np.random.choice(codes, dates.size), 'col2': np.random.choice(codes, dates.size), 'date': dates)

导致:

In [55]: df
Out[55]:
    amount col1 col2       date
0        1    D    E 2013-11-01
0        5    E    B 2013-11-01
1        5    G    A 2013-11-02
1        7    D    H 2013-11-02
2        5    E    G 2013-11-03
2        4    H    G 2013-11-03
3        7    A    F 2013-11-04
3        3    A    A 2013-11-04
4        1    E    G 2013-11-05
4        7    D    C 2013-11-05
5        5    C    A 2013-11-06
5        7    H    F 2013-11-06
6        1    G    B 2013-11-07
6        8    D    A 2013-11-07
7        1    B    H 2013-11-08
7        8    F    H 2013-11-08
8        3    A    E 2013-11-09
8        1    H    D 2013-11-09
9        3    B    D 2013-11-10
9        1    H    G 2013-11-10
10       6    E    E 2013-11-11
10       6    F    E 2013-11-11
11       2    G    B 2013-11-12
11       5    H    H 2013-11-12
12       5    F    G 2013-11-13
12       5    G    B 2013-11-13
13       8    H    B 2013-11-14
13       6    G    F 2013-11-14
14       9    F    C 2013-11-15
14       4    H    A 2013-11-15
..     ...  ...  ...        ...
77       9    A    B 2014-01-17
77       7    E    B 2014-01-17
78       4    F    E 2014-01-18
78       6    B    E 2014-01-18
79       6    A    H 2014-01-19
79       3    G    D 2014-01-19
80       7    E    E 2014-01-20
80       6    G    C 2014-01-20
81       9    H    G 2014-01-21
81       9    C    B 2014-01-21
82       2    D    D 2014-01-22
82       7    D    A 2014-01-22
83       6    G    B 2014-01-23
83       1    A    G 2014-01-23
84       9    B    D 2014-01-24
84       7    G    D 2014-01-24
85       7    A    F 2014-01-25
85       9    B    H 2014-01-25
86       9    C    D 2014-01-26
86       5    E    B 2014-01-26
87       3    C    H 2014-01-27
87       7    F    D 2014-01-27
88       3    D    G 2014-01-28
88       4    A    D 2014-01-28
89       2    F    A 2014-01-29
89       8    D    A 2014-01-29
90       1    A    G 2014-01-30
90       6    C    A 2014-01-30
91       6    H    C 2014-01-31
91       2    G    F 2014-01-31

[184 rows x 4 columns]

我想按日历周和col1 的值进行分组。像这样:

kw = lambda x: x.isocalendar()[1]
grouped = df.groupby([df['date'].map(kw), 'col1'], sort=False).agg('amount': 'sum')

导致:

In [58]: grouped
Out[58]:
           amount
date col1
44   D          8
     E         10
     G          5
     H          4
45   D         15
     E          1
     G          1
     H          9
     A         13
     C          5
     B          4
     F          8
46   E          7
     G         13
     H         17
     B          9
     F         23
47   G         14
     H          4
     A         40
     C          7
     B         16
     F         13
48   D          7
     E         16
     G          9
     H          2
     A          7
     C          7
     B          2
...           ...
1    H         14
     A         14
     B         15
     F         19
2    D         13
     H         13
     A         13
     B         10
     F         32
3    D          8
     E         18
     G          3
     H          6
     A         30
     C          9
     B          6
     F          5
4    D          9
     E         12
     G         19
     H          9
     A          8
     C         18
     B         18
5    D         11
     G          2
     H          6
     A          5
     C          9
     F          9

[87 rows x 1 columns]

然后我想生成这样的情节: 这意味着:x 轴上的日历周和年(日期时间)以及每个分组的 col1 一个条形。

我面临的问题是:我只有整数描述日历周(图中的 KW),但我必须以某种方式合并其上的日期才能获得按年份标记的刻度。此外,我不能只绘制分组日历周,因为我需要正确的项目顺序(kw 47、kw 48(2013 年)必须在 kw 1 的左侧(因为这是 2014 年))。


编辑

我从这里发现: http://pandas.pydata.org/pandas-docs/stable/visualization.html#visualization-barplot 分组条需要是列而不是行。于是我想了想如何转换数据,发现了pivot这个方法,原来是个很棒的功能。需要reset_index 将多索引转换为列。最后我用零填充NaNs:

A = grouped.reset_index().pivot(index='date', columns='col1', values='amount').fillna(0)

将数据转换成:

col1   A   B   C   D   E   F   G   H
date
1      4  31   0   0   0  18  13   8
2      0  12  13  22   1  17   0   8
3      3  10   4  13  12   8   7   6
4     17   0  10   7   0  25   7   4
5      7   0   7   9   8   6   0   7
44     0   0   2  11   7   0   0   2
45     9   3   2  14   0  16  21   2
46     0  14   7   2  17  13  11   8
47     5  13   0  15  19   7   5  10
48    15   8  12   2  20   4   7   6
49    20   0   0  18  22  17  11   0
50     7  11   8   6   5   6  13  10
51     8  26   0   0   5   5  16   9
52     8  13   7   5   4  10   0  11

这看起来像文档中的示例数据以分组条形绘制:

A. plot(kind='bar')

得到这个:

而我的轴有问题,因为它现在已排序(从 1 到 52),这实际上是错误的,因为在这种情况下,日历周 52 属于 2013 年......关于如何合并回来的任何想法日历周的真实日期时间并将它们用作 x 轴刻度?

【问题讨论】:

评论真的应该放在 cmets 中。我知道当没有足够的人回答问题时很容易气馁,但最好的办法是从问题中删除不相关的部分,并尝试让问题尽可能快速- 尽可能点。 那不是评论,真的。只是为用户提供的信息,对所有读者都非常有帮助...... 【参考方案1】:

我认为resample('W') 是一种更好的方法 - 默认情况下,它按星期天结束的星期分组('W' 与 'W-SUN' 相同),但你可以指定任何你想要的。

在你的例子中,试试这个:

grouped = (df
    .groupby('col1')                
    .apply(lambda g:               # work on groups of col1
        g.set_index('date')        
        [['amount']]
        .resample('W').agg('sum')  # sum the amount field across weeks
    )
    .unstack(level=0)              # pivot the col1 index rows to columns
    .fillna(0)
)
grouped.columns=grouped.columns.droplevel()   # drop the 'col1' part of the multi-index column names
print grouped
grouped.plot(kind='bar')

它应该打印您的数据表并绘制与您的相似的图,但带有“真实”日期标签:

col1         A   B   C   D   E   F   G   H
date                                      
2013-11-03  18  0   9   0   8   0   0   4 
2013-11-10  4   11  0   1   16  2   15  2 
2013-11-17  10  14  19  8   13  6   9   8 
2013-11-24  10  13  13  0   0   13  15  10
2013-12-01  6   3   19  8   8   17  8   12
2013-12-08  5   15  5   7   12  0   11  8 
2013-12-15  8   6   11  11  0   16  6   14
2013-12-22  16  3   13  8   8   11  15  0 
2013-12-29  1   3   6   10  7   7   17  15
2014-01-05  12  7   10  11  6   0   1   12
2014-01-12  13  0   17  0   23  0   10  12
2014-01-19  10  9   2   3   8   1   18  3 
2014-01-26  24  9   8   1   19  10  0   3 
2014-02-02  1   6   16  0   0   10  8   13

【讨论】:

how 在 0.19 版之后被弃用。请改用.resample('W').agg('sum')【参考方案2】:

好的,我自己回答这个问题,因为我终于弄明白了。关键是不要按日历周分组(因为您会丢失有关年份的信息),而是按包含日历周和年份的字符串分组。

然后使用pivot 更改问题中提到的布局(重塑)。日期将是索引。使用reset_index() 使当前date-index 成为一列,而不是获取一个整数范围作为索引(然后按正确的顺序绘制(最低年份/日历周是索引0,最高年份/日历周是最大整数)。

选择date-column 作为新变量ticks 作为列表并从DataFrame 中删除该列。现在绘制条形图并简单地将 xticks 的标签设置为ticks。 Completey 解决方案非常简单,在这里:

codes = list('ABCDEFGH'); 
dates = pd.Series(pd.date_range('2013-11-01', '2014-01-31')); 
dates = dates.append(dates)
dates.sort()
df = pd.DataFrame('amount': np.random.randint(1, 10, dates.size), 'col1': np.random.choice(codes, dates.size), 'col2': np.random.choice(codes, dates.size), 'date': dates)

kw = lambda x: x.isocalendar()[1]; 
kw_year = lambda x: str(x.year) + ' - ' + str(x.isocalendar()[1])
grouped = df.groupby([df['date'].map(kw_year), 'col1'], sort=False, as_index=False).agg('amount': 'sum')
A = grouped.pivot(index='date', columns='col1', values='amount').fillna(0).reset_index()

ticks = A.date.values.tolist()
del A['date']
ax = A.plot(kind='bar')
ax.set_xticklabels(ticks)

结果:

【讨论】:

哇,非常好的、简单且干净的解决方案。我喜欢。关于 matplotlib-axisticks 没有太多麻烦 :)【参考方案3】:

将一周添加到一年中的 52 次,以便“按年”对周进行排序。将勾选labels,which might be nontrivial,设置为你想要的。


你想要的是这样增加几周

nth week → (n+1)th week → (n+2)th week → etc.

但是当你有新的一年时,它会下降 51 (52 → 1)。

要抵消这一点,请注意年份增加一。所以加上当年的增长乘以 52,总变化将是-51 + 52 = 1

【讨论】:

“将一周添加到一年中的 52 次”是什么意思?当我按日历周分组时,日期时间会丢失,这是我的绘图所需要的:( 又名。不要按周分组,按week + 52*year 分组。虽然要小心那第365天…… 不知何故我还是不明白。但也许我不得不重新考虑一下为什么要按返回值添加年份的 52 的倍数来分组 啊,现在我明白了,您的意思是 delta_year*52,表示自开始以来的真实周数。 是的。只是年份是否从 2013 年开始并不重要,因为它只会使数字变大(而不是它们之间的差异)。【参考方案4】:

KeyError: '日期'

上述异常是以下异常的直接原因:

KeyError Traceback(最近一次调用最后一次) 在 10 kw_year = lambda x: str(x.year) + ' - ' + str(x.isocalendar()[1]) 11 分组 = df.groupby([df['date'].map(kw_year), 'col1'], sort=False, as_index=False).agg('amount': 'sum') ---> 12 A = grouped.pivot(index='date', columns='col1', values='amount').fillna(0).reset_index()

【讨论】:

以上是关于Pandas:按日历周分组,然后为实际日期时间绘制分组条形图的主要内容,如果未能解决你的问题,请参考以下文章

按日期对 Pandas DataFrame 进行分组

熊猫日期时间周与预期不符

Python Pandas 使用日期时间数据按日期分组

Python Pandas:按日期分组,并按时间戳访问每个组

Pandas groupby 多列基础日期列按纪元周

在多列上分组时如何绘制条形图?