Pythonの積み上げ棒グラフの基本と特定部分の色を変える方法

horizontal bargraph1

今回は主にmatplotlibでの棒グラフと積み上げ棒グラフ、100%積み上げ棒グラフの基本的な作成方法と、特定部分の色を変える方法を勉強します。

このグラフを作成するきっかけは、最近読み直した「Google流資料作成術」という本です。

Amazon.co.jp

この本ではデータ分析に置ける指針やストーリーを伝えることの重要性、グラフの見せ方といった各種スキルが紹介されています。実際にGoogle流なのかどうかはさておき、内容としては勉強になるものでした。本自体はよかったのですが、自分が持っているものは表紙と表紙裏が擦れてキュッキュッという音がしたので、苦手な人は確認か工夫が必要かと思います。(自分は表紙裏の紙を切り取りました)。

本書の中には、「私は通常、図やグラフをグレーで作り、注意をひきたいところに目立つ色を1つ使います。」(p. 117より引用)という一節があります。実際、筆者は自分の主張を相手に伝えるための手法として、この方法を用いていきます。カラフルなグラフではなく、メッセージに重きを置くために色を効果的に使用することは、本書籍の中で繰り返し強調される部分です。

よし、ではPythonでもやってみるか、という訳で、今回取り組んでみることにした次第です。

ちなみに、今回の記事での最終ゴールはこちらのグラフです。

horizontal bargraph1

こちらは、上で紹介した書籍の227ページに記載されているようなグラフを目指して作りました。細かい部分はさておきという感じではありますが。積み上げグラフ自体も使う場面は時々あると思うので、とりあえず見ていきましょう。

本記事の流れ

本記事で扱うのは単純な棒グラフと積み上げ棒グラフです。どちらもまず基本的な作成方法を確認してから部分的に色を変えていきます。基本的に、ベースの色はlightgray、強調したい部分はblueにしています。

単純な棒グラフ

下準備

まずは単純な棒グラフを書いてみます。このグラフにはwine-qualityデータセットを利用することにします。データセットは事前にダウンロードしたものを使用しました。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
np.random.seed(0)

data = pd.read_csv("../input/wine-quality/winequality-red.csv", sep = ";")
data.head()
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5
1 7.8 0.88 0.00 2.6 0.098 25.0 67.0 0.9968 3.20 0.68 9.8 5
2 7.8 0.76 0.04 2.3 0.092 15.0 54.0 0.9970 3.26 0.65 9.8 5
3 11.2 0.28 0.56 1.9 0.075 17.0 60.0 0.9980 3.16 0.58 9.8 6
4 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5

次に、カウントデータを作成しておきます。ここはやり方はなんでもいいと思います。自分のはそこそこ面倒かと思うので…。

count_df = data.groupby("quality").agg("count")[data.columns.tolist()[0]].reset_index().rename(columns={data.columns.tolist()[0]:"counts"})
count_df
quality counts
0 3 10
1 4 53
2 5 681
3 6 638
4 7 199
5 8 18

シンプルな棒グラフ

では、何も考えずにシンプルな棒グラフを表示しましょう。棒グラフはplt.bar()なり、axes.bar()を使えば作成できます。今回は後のことを考えて、axes.bar()を使用しています。

fig, ax = plt.subplots(figsize=(7, 4))
ax.bar(count_df["quality"], count_df["counts"], width=0.7)
ax.set_xlabel("quality")
ax.set_ylabel("counts")
ax.set_title("counts of quality");

simple bar graph

簡単ですね。最後のセミコロンは、Jupyter Notebook上で余計な出力を出させないためのものです。以降でも使用していきます。

1色だけ色を変えた棒グラフ

次は’quality’が’5’の棒だけ色を変えます。これ自体は、plt.bar()やax.bar()の引数’color’に色のリストを渡すだけでOKだとは思いますが、今回はplt.bar()とax.bar()の戻り値を利用する方法にしました。

fig, ax = plt.subplots(figsize=(7, 4))
bar_list = ax.bar(count_df["quality"], count_df["counts"], width=0.7, color="lightgray")
bar_list[2].set_color("blue")
ax.set_xlabel("quality")
ax.set_ylabel("counts")
ax.set_title("counts of quality");

simple bar with blue

bar_listの中身は、

bar_list
<BarContainer object of 6 artists>
bar_list[0]
出力結果
<matplotlib.patches.Rectangle at 0x1024ea400>

というように、描画された長方形一つ一つの情報になっているようです。

ライトグレーとグレーの比較

最後に、lightgrayとgrayの比較をしておきました。

color_list = ["lightgray", "gray"]
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(14, 4))
for c_color, ax in zip(color_list, axes.flatten()):
    bar_list = ax.bar(count_df["quality"], count_df["counts"], width=0.7, color=c_color)
    bar_list[2].set_color("blue")
    ax.set_xlabel("quality")
    ax.set_ylabel("counts")
    ax.set_title(c_color);

compare gray and lightgray

青を強調したいということを考えると、grayよりlightgrayの方が良さそうです。これがこの記事でlightgrayを基本色にした理由です。もちろん、#c0c0c0とかの方がよければそちらでも良いかと思います。

積み上げ棒グラフ

次は積み上げ棒グラフです。ここでは基本的な積み上げ棒グラフと、100%積み上げ棒グラフを作成します。

下準備

都合の良いデータが手元になかったので、numpyでデータを作成しました。

sample_df = pd.DataFrame()
sample_data = np.random.randint(0, 100, size=(5, 10))
columns = ["C"+str(i) for i in range(10)]
sample_df = pd.DataFrame(data=sample_data, columns=columns)
sample_df

100%積み上げ棒グラフを作成するために、データを100%での割合に変換しておきます。

for each_col in sample_df.columns.tolist():
    sample_df[each_col] = 100 * sample_df[each_col] / sample_df[each_col].sum()
    sample_df.index = [val for val in "ABCDE"]
    sample_df.head()
C0 C1 C2 C3 C4 C5 C6 C7 C8 C9
A 15.658363 17.537313 19.753086 25.378788 19.590643 5.487805 41.089109 7.473310 18.652850 28.712871
B 24.911032 32.835821 27.160494 4.545455 16.959064 39.634146 19.306931 30.960854 23.834197 29.042904
C 28.825623 13.805970 7.716049 29.166667 21.052632 5.487805 9.900990 28.469751 35.751295 26.072607
D 16.725979 23.880597 25.308642 37.500000 25.730994 29.878049 14.356436 6.761566 9.844560 4.620462
E 13.879004 11.940299 20.061728 3.409091 16.666667 19.512195 15.346535 26.334520 11.917098 11.551155

念のため確認ですが、indexは下記の方法で作成しています。

[val for val in "ABCDE"]
['A', 'B', 'C', 'D', 'E']

以上で下準備が完了です。

シンプルな積み上げ棒グラフ

まずは積み上げ棒グラフ作成方法の確認のため、2段だけのグラフを作成します。積み上げ棒グラフは公式ドキュメントに作成方法が記載されていました。

fig, ax = plt.subplots()
data_points = np.arange(len(sample_df.columns))
# 一番下の層のbarplot
ax.bar(data_points, sample_df.iloc[0])
# 二番目の層のbarplot。bottomに1番目の層の値を指定している。
ax.bar(data_points, sample_df.iloc[1], bottom=sample_df.iloc[0])
ax.set_xticks(data_points)
ax.set_xticklabels(sample_df.columns);

stack plot 1

内容はコメントに記載した通りですが、bottomを一つ前のデータ列で指定してあげればOKです。

100%にした積み上げ棒グラフ

100%の積み上げ棒グラフです。事前にデータは作成しているので、先ほどの単純な棒グラフを応用するだけです。

fig, ax = plt.subplots()
data_points = np.arange(len(sample_df.columns))
bottom_data = pd.Series(np.zeros(len(sample_df.columns)), index=sample_df.columns.tolist())
for i in range(len(sample_df.index)):
    ax.bar(data_points, sample_df.iloc[i], bottom=bottom_data)
    bottom_data += sample_df.iloc[i]
    ax.set_xticks(data_points)
    ax.set_xticklabels(sample_df.columns);

stack plot 2

色を2色にした積み上げグラフ

今度は、1番目と2番目だけ青にし、他をlightgrayに変更します。今回は色を変える対象のインデックスのリストを準備する方式にしましたが、色のリストを使うなど、様々な方法があるかと思います。

# シンプルな積み上げグラフ
fig, ax = plt.subplots()
data_points = np.arange(len(sample_df.columns))
bottom_data = pd.Series(np.zeros(len(sample_df.columns)), index=sample_df.columns.tolist())
blue_index_list = [0, 1]
for i in range(len(sample_df.index)):
    bar_list = ax.bar(data_points, sample_df.iloc[i], bottom=bottom_data)
    for j in range(len(bar_list)):
        if i in blue_index_list:
            bar_list[j].set_color("blue")
        else:
            bar_list[j].set_color("lightgray")
        bottom_data += sample_df.iloc[i]
ax.set_xticks(data_points)
ax.set_xticklabels(sample_df.columns);

color plot 1

このコードではグラフの色の境界がないため、AとB、CとDとEの境目が見えなくなってしまいました。回避するには、棒グラフ描画時に’edgecolor’を指定することと、色の指定で’set_color’ではなく’set_facecolor’を使用する必要があります。

fig, ax = plt.subplots()
data_points = np.arange(len(sample_df.columns))
bottom_data = pd.Series(np.zeros(len(sample_df.columns)), index=sample_df.columns.tolist())
blue_index_list = [0, 1]
for i in range(len(sample_df.index)):
    bar_list = ax.bar(data_points, sample_df.iloc[i], bottom=bottom_data, edgecolor="white", linewidth=0.5)
    for j in range(len(bar_list)):
        if i in blue_index_list:
            bar_list[j].set_facecolor("blue")
        else:
            bar_list[j].set_facecolor("lightgray")
     bottom_data += sample_df.iloc[i]
ax.set_xticks(data_points)
ax.set_xticklabels(sample_df.columns);

color plot 2

これで一段落です。次は横棒100%積み上げグラフを作成します。

100%積み上げ横棒グラフ

縦から横に変えるのは簡単です。基本的にはplt.bar()の代わりにplt.hbar()を利用するだけ。但し、オプションに細かな変更があるので注意が必要です。

とりあえず色の指定はせずに描画してみましょう。

fig, ax = plt.subplots()
data_points = np.arange(len(sample_df.columns))
left_data = pd.Series(np.zeros(len(sample_df.columns)), index=sample_df.columns.tolist())
for i in range(len(sample_df.index)):
    bar_list = ax.barh(data_points, sample_df.iloc[i], left=left_data)
    left_data += sample_df.iloc[i]
ax.set_xlim([0,100])
ax.set_yticks(data_points)
ax.set_yticklabels(sample_df.columns);

h color plot 1

色を一部変更した100%積み上げ横棒グラフ

大体予想通りのグラフです。次に、色の指定を入れます。右上に凡例も表示するようにします。

fig, ax = plt.subplots()
data_points = np.arange(len(sample_df.columns))
left_data = pd.Series(np.zeros(len(sample_df.columns)), index=sample_df.columns.tolist())
blue_index_list = [0, 1]
    for i in range(len(sample_df.index)):
    bar_list = ax.barh(data_points, sample_df.iloc[i], left=left_data,edgecolor="white", linewidth=0.5)
    for j in range(len(bar_list)):
        if i in blue_index_list:
            bar_list[j].set_facecolor("blue")
        else:
            bar_list[j].set_facecolor("lightgray")
    left_data += sample_df.iloc[i]
ax.legend(sample_df.index.tolist(), loc="upper right")
ax.set_xlim([0,100])
ax.set_yticks(data_points)
ax.set_yticklabels(sample_df.columns);

h two color plot

一応このグラフが作成できれば今回扱う内容は完了です。

100%積み上げ横棒グラフのlightgrayとgrayの比較

100%積み上げ横棒グラフでも念のためlightgrayとgrayの比較をしておきます。

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(14, 4))
data_points = np.arange(len(sample_df.columns))
color_list = ["lightgray", "gray"]
blue_index_list = [0, 1]
for c_color, ax in zip(color_list, axes.flatten()):
    left_data = pd.Series(np.zeros(len(sample_df.columns)), index=sample_df.columns.tolist())
    for i in range(len(sample_df.index)):
        bar_list = ax.barh(data_points, sample_df.iloc[i], left=left_data,edgecolor="white", linewidth=0.5)
        for j in range(len(bar_list)):
            if i in blue_index_list:
                bar_list[j].set_facecolor("blue")
            else:
                bar_list[j].set_facecolor(c_color)
        left_data += sample_df.iloc[i]
    ax.legend(sample_df.index.tolist(), loc="upper right")
    ax.set_xlim([0,100])
    ax.set_yticks(data_points)
    ax.set_yticklabels(sample_df.columns);

compare gray and lightgray 2

青が際立つのはやはりlightgrayの方でしょうか。今回はnumpyで適当に作成したグラフということもあり、色を変えたからといってあまり何も主張がないため、まあこんなもんかといった感じになりました。

おまけ(1)凡例を上部に移動した100%積み上げ横棒グラフ

冒頭で紹介したグラフです。凡例は右上の方が個人的には好きですが、こういう表示もあるということで。

# シンプルな積み上げグラフ
fig, ax = plt.subplots()
data_points = np.arange(len(sample_df.columns))
left_data = pd.Series(np.zeros(len(sample_df.columns)), index=sample_df.columns.tolist())
blue_index_list = [0, 1]
for i in range(len(sample_df.index)):
    bar_list = ax.barh(data_points, sample_df.iloc[i], left=left_data,edgecolor="white", linewidth=0.5)
    for j in range(len(bar_list)):
        if i in blue_index_list:
            bar_list[j].set_facecolor("blue")
        else:
            bar_list[j].set_facecolor("lightgray")
    left_data += sample_df.iloc[i]
ax.legend(sample_df.index.tolist(), loc="upper center", ncol=5, bbox_to_anchor=(0.5, 1), frameon=False, borderaxespad=-2, columnspacing=3.5)
ax.set_xlim([0,100])
ax.set_yticks(data_points)
ax.set_yticklabels(sample_df.columns);

hplot with legend is top

おまけ(2)AとBの和でソートしたグラフ

参考元のグラフはAとBの和でソートした結果をグラフにしていたので、同じように作成してみました。

まずはデータフレームを準備します。データ自体は先ほどのものと同じものを使いますが、ラベルを新たに付け直しています。

target_df = sample_df.copy()
ab_s = target_df.loc["A"] + target_df.loc["B"]
ab_df = pd.DataFrame(ab_s).T
ab_df.index=["A_plus_B"]
target_df = pd.concat([target_df, ab_df])
target_df.sort_values(by="A_plus_B", axis=1)
target_df=target_df.sort_values(by="A_plus_B", axis=1)
target_df = target_df.drop("A_plus_B", axis=0)
rename_col = {base_col: "D" + str(i) for (i, base_col) in zip(range(10, 0, -1), target_df.columns.tolist())}
target_df = target_df.rename(columns=rename_col)
target_df

作成し直したデータフレームがこちら。

D10 D9 D8 D7 D6 D5 D4 D3 D2 D1
A 25.378788 19.590643 7.473310 15.658363 18.652850 5.487805 19.753086 17.537313 28.712871 41.089109
B 4.545455 16.959064 30.960854 24.911032 23.834197 39.634146 27.160494 32.835821 29.042904 19.306931
C 29.166667 21.052632 28.469751 28.825623 35.751295 5.487805 7.716049 13.805970 26.072607 9.900990
D 37.500000 25.730994 6.761566 16.725979 9.844560 29.878049 25.308642 23.880597 4.620462 14.356436
E 3.409091 16.666667 26.334520 13.879004 11.917098 19.512195 20.061728 11.940299 11.551155 15.346535

読みやすさのために一つのブロックに固めていますが、実際はNotebook上で色々と試行錯誤しながら作成しました。

このデータフレームを使用して作成したグラフがこちらです。

# シンプルな積み上げグラフ
fig, ax = plt.subplots()
data_points = np.arange(len(target_df.columns))
left_data = pd.Series(np.zeros(len(target_df.columns)), index=target_df.columns.tolist())
blue_index_list = [0, 1]
for i in range(len(target_df.index)):
    bar_list = ax.barh(data_points, target_df.iloc[i], left=left_data,edgecolor="white", linewidth=0.5)
    for j in range(len(bar_list)):
        if i in blue_index_list:
            bar_list[j].set_facecolor("blue")
        else:
            bar_list[j].set_facecolor("lightgray")
    left_data += target_df.iloc[i]
ax.legend(target_df.index.tolist(), loc="upper center", ncol=5, bbox_to_anchor=(0.5, 1), frameon=False, borderaxespad=-2, columnspacing=3.5)
ax.set_xlim([0,100])
ax.set_yticks(data_points)
ax.set_yticklabels(target_df.columns);

sorted_graph

すでにあるデータフレームを活用したので必要以上に煩雑になってしまいました。新しくデータを作り直した方が早かったかもしれません。

今回は行いませんでしたが、汎用性を高めるために関数化することも考えておくと良いかと思います。

まとめ

matplotlibでの棒グラフと積み上げ棒グラフの基本的な使い方と、特定部分の色を変える方法を整理しました。

ある程度慣れてくるとその時々で作りたいグラフを作成できるようになってくるかと思います。描画方法自体は結局は手段に過ぎないので、どういったストーリーで何を見せるべきかを見極めるスキルの重要性を改めて感じたので、そちらの方についてもまた改めて整理したいと思います。

参考文献

コール・ヌッスバウマー・ナフリック著、村井瑞枝訳、Google流資料作成術、2017年2月20日初版、日本実業出版社
matplotlib.pyplot.bar
Stacked Bar Graph