📊
【Python】matplotlibで時系列データの積み上げ棒グラフを描く
はじめに
タイトル通りです。業務中、時系列データの積み上げ棒グラフを描画したくなる時が多々あります。
都度、描画の方法をググっていて効率が悪いなぁと感じたので、簡単な関数を実装してみました。
備忘録として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.bar
でbottom
にベースにしたい値を設定することで実現します。今回、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