📊

【Python】matplotlibで時系列データの積み上げ棒グラフを描く

2021/08/02に公開

はじめに

タイトル通りです。業務中、時系列データの積み上げ棒グラフを描画したくなる時が多々あります。
都度、描画の方法をググっていて効率が悪いなぁと感じたので、簡単な関数を実装してみました。
備忘録としてZennで公開します。n番煎じだと思いますが、温かい目で見ていただけると幸いです。。。

コード

使用ライブラリ

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib

データセット

いわゆる「縦持ち」「横持ち」の両方に対応しています。

# 縦持ち

# 期間
dts = pd.date_range('2021-01-01', '2021-01-31', freq='D')
durations = len(dts)

# 種類
groups = ['Type A', 'Type B', 'Type C', 'Type D']

df = pd.DataFrame()
for group in groups:
    tmp_df = pd.DataFrame(dts, columns=['dt'])
    tmp_df['group'] = group
    tmp_df['uu'] = np.random.randint(low=100, high=1000, size=durations)
    df = df.append(tmp_df)

df = df.sort_values(['dt', 'group'])
df = df.reset_index(drop=True)

df.head()
dt group uu
0 2021-01-01 Type A 736
1 2021-01-01 Type B 232
2 2021-01-01 Type C 776
3 2021-01-01 Type D 597
4 2021-01-02 Type A 960
# 横持ち

# 期間
dts = pd.date_range('2021-01-01', '2021-01-31', freq='D')
durations = len(dts)

# 種類
groups = ['Type A', 'Type B', 'Type C', 'Type D']

df = pd.DataFrame(dts, columns=['dt'])
for group in groups:
    df[group] = np.random.randint(low=100, high=1000, size=durations)

df.head()
Type A Type B Type C Type D Type D
0 2021-01-01 470 288 992 332
1 2021-01-02 878 391 993 920
2 2021-01-03 244 454 736 128
3 2021-01-04 193 357 846 435
4 2021-01-05 136 377 487 866

描画関数


import itertools

def plot_stacked_bar_chart(data, x, y='y', hue='hue', rational=False, axis=0):
    # 横持ちのデータは縦持ちに変換
    if axis:
        data = data.set_index(x)
        data = data.stack().reset_index().rename(columns={'level_1': hue, 0: y})

    groups = np.sort(data[hue].unique())
    x_vals = np.sort(data[x].unique())
    pair = pd.DataFrame(list(map(list, itertools.product(x_vals, groups))), columns=[x, hue])
    data = pair.merge(data, on=[x, hue], how='left').fillna(0)

    # 割合を算出
    if rational:
        col_name = f'__sum_{y}'
        sum_y_by_x = data.groupby(x)[y].sum().reset_index().rename(columns={y: col_name})
        data = data.merge(sum_y_by_x, on=x, how='left')
        data[f'{y}_ratio'] = data[y] / data[col_name]
        target_col_name = f'{y}_ratio'
    else:
        target_col_name = y

    n = len(data[x].unique())
    bottom_values = np.zeros(shape=(n,))
    for group in groups:
        tmp_df = data[data[hue] == group]
        plt.bar(tmp_df[x], tmp_df[target_col_name], bottom=bottom_values, label=group)
        bottom_values += tmp_df[target_col_name].values

seabornっぽい感じで書いてみました。rational で割合の積み上げ棒グラフにするかどうかを選択するようにします。
積み上げ棒グラフは、matplotlib.pyplot.barbottomにベースにしたい値を設定することで実現します。今回、for文でbottom_valuesに各グループの値を足していくことで、イイ感じに積み上げ棒グラフが実現されます。

使い方

plt.figure(figsize=(12, 6))
plot_stacked_bar_chart(data=df, x='dt', y='uu', hue='group', rational=False)
plt.title('2021年1月のUU数推移')
plt.legend()

plt.figure(figsize=(12, 6))
plot_stacked_bar_chart(data=df, x='dt', y='uu', hue='group', rational=True)
plt.title('2021年1月のUU割合推移')
plt.legend()

さいごに

最低限、自分が業務で使う上で困らない程度に実装しました。どなたかのお役に立てれば嬉しいです。

Discussion