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

horizontal bargraph1

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

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

https://www.amazon.co.jp/Google%E6%B5%81%E8%B3%87%E6%96%99%E4%BD%9C%E6%88%90%E8%A1%93-%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%BB%E3%83%8C%E3%83%83%E3%82%B9%E3%83%90%E3%82%A6%E3%83%9E%E3%83%BC%E3%83%BB%E3%83%8A%E3%83%95%E3%83%AA%E3%83%83%E3%82%AF/dp/4534054726/ref=sr_1_1?s=books&ie=UTF8&qid=1541212776&sr=1-1&keywords=google%E6%B5%81%E8%B3%87%E6%96%99%E4%BD%9C%E6%88%90%E8%A1%93

この本ではデータ分析に置ける指針やストーリーを伝えることの重要性、グラフの見せ方といった各種スキルが紹介されています。実際に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 acidityvolatile aciditycitric acidresidual sugarchloridesfree sulfur dioxidetotal sulfur dioxidedensitypHsulphatesalcoholquality
07.40.700.001.90.07611.034.00.99783.510.569.45
17.80.880.002.60.09825.067.00.99683.200.689.85
27.80.760.042.30.09215.054.00.99703.260.659.85
311.20.280.561.90.07517.060.00.99803.160.589.86
47.40.700.001.90.07611.034.00.99783.510.569.45

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

count_df = data.groupby("quality").agg("count")[data.columns.tolist()[0]].reset_index().rename(columns={data.columns.tolist()[0]:"counts"})
count_df
qualitycounts
0310
1453
25681
36638
47199
5818

シンプルな棒グラフ

では、何も考えずにシンプルな棒グラフを表示しましょう。棒グラフは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()
C0C1C2C3C4C5C6C7C8C9
A15.65836317.53731319.75308625.37878819.5906435.48780541.0891097.47331018.65285028.712871
B24.91103232.83582127.1604944.54545516.95906439.63414619.30693130.96085423.83419729.042904
C28.82562313.8059707.71604929.16666721.0526325.4878059.90099028.46975135.75129526.072607
D16.72597923.88059725.30864237.50000025.73099429.87804914.3564366.7615669.8445604.620462
E13.87900411.94029920.0617283.40909116.66666719.51219515.34653526.33452011.91709811.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

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

D10D9D8D7D6D5D4D3D2D1
A25.37878819.5906437.47331015.65836318.6528505.48780519.75308617.53731328.71287141.089109
B4.54545516.95906430.96085424.91103223.83419739.63414627.16049432.83582129.04290419.306931
C29.16666721.05263228.46975128.82562335.7512955.4878057.71604913.80597026.0726079.900990
D37.50000025.7309946.76156616.7259799.84456029.87804925.30864223.8805974.62046214.356436
E3.40909116.66666726.33452013.87900411.91709819.51219520.06172811.94029911.55115515.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