今回は主にmatplotlibでの棒グラフと積み上げ棒グラフ、100%積み上げ棒グラフの基本的な作成方法と、特定部分の色を変える方法を勉強します。
このグラフを作成するきっかけは、最近読み直した「Google流資料作成術」という本です。
この本ではデータ分析に置ける指針やストーリーを伝えることの重要性、グラフの見せ方といった各種スキルが紹介されています。実際にGoogle流なのかどうかはさておき、内容としては勉強になるものでした。本自体はよかったのですが、自分が持っているものは表紙と表紙裏が擦れてキュッキュッという音がしたので、苦手な人は確認か工夫が必要かと思います。(自分は表紙裏の紙を切り取りました)。
本書の中には、「私は通常、図やグラフをグレーで作り、注意をひきたいところに目立つ色を1つ使います。」(p. 117より引用)という一節があります。実際、筆者は自分の主張を相手に伝えるための手法として、この方法を用いていきます。カラフルなグラフではなく、メッセージに重きを置くために色を効果的に使用することは、本書籍の中で繰り返し強調される部分です。
よし、ではPythonでもやってみるか、という訳で、今回取り組んでみることにした次第です。
ちなみに、今回の記事での最終ゴールはこちらのグラフです。
こちらは、上で紹介した書籍の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");
簡単ですね。最後のセミコロンは、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");
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);
青を強調したいということを考えると、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);
内容はコメントに記載した通りですが、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);
色を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);
このコードではグラフの色の境界がないため、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);
これで一段落です。次は横棒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);
色を一部変更した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);
一応このグラフが作成できれば今回扱う内容は完了です。
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);
青が際立つのはやはり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);
おまけ(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);
すでにあるデータフレームを活用したので必要以上に煩雑になってしまいました。新しくデータを作り直した方が早かったかもしれません。
今回は行いませんでしたが、汎用性を高めるために関数化することも考えておくと良いかと思います。
まとめ
matplotlibでの棒グラフと積み上げ棒グラフの基本的な使い方と、特定部分の色を変える方法を整理しました。
ある程度慣れてくるとその時々で作りたいグラフを作成できるようになってくるかと思います。描画方法自体は結局は手段に過ぎないので、どういったストーリーで何を見せるべきかを見極めるスキルの重要性を改めて感じたので、そちらの方についてもまた改めて整理したいと思います。
参考文献
コール・ヌッスバウマー・ナフリック著、村井瑞枝訳、Google流資料作成術、2017年2月20日初版、日本実業出版社
matplotlib.pyplot.bar
Stacked Bar Graph