Pythonのseabornで手軽なのに美しいヒストグラムを作成する方法

こんにちは、データサイエンティストのたぬ(@tanuhack)です!

Pythonでグラフを描画するときによく使われているライブラリとして『Matplotlib』が挙げられます。

しかし、このMatplotlibは、多機能であるが故に設定が面倒だったり、美しいグラフが描けたとしても無駄にコードが長くなってしまったりと何かと問題が付きまとうと思いませんか?

ヒストグラムで確認したいことと言えば、データの全体的な『ばらつき具合』を確認したいだけなのに、たかだかグラフの設定にそこまで時間を掛けられない…。

そこで今回はMatplotlibより、もっと簡単に美しいグラフが描ける『seaborn(シーボーン)』を使って、ヒストグラムを作成する方法を紹介します!

準備

まず、seabornをインストールします。

ターミナル
pip install seaborn

次に、Pythonプログラムで必須ライブラリをインポートします。

今回のseabornによるグラフの描画は『Jupyter Notebook』内とします。

# 数値計算に用いるライブラリ
import math
import numpy as np
import pandas as pd

# グラフを描画するライブラリ
from matplotlib import pyplot as plt
import seaborn as sns; sns.set() # sns.set() ==> グラフの見た目をseabornに合わせる
%matplotlib inline

データセットは、Rでお馴染みの『アヤメの統計データ』をPandasのDataFrameに格納させます。

アヤメのデータセットは、seabornに予め用意されているのでそれを使います。ありがたや〜。

iris = sns.load_dataset('iris') 
# type(iris) ==> pandas.core.frame.DataFrame
preprocessing-extraction1
150 rows × 5 columns
レコード数 150
カラム数 5
カラム名 説明 データの尺度名
sepal_length がく片の長さ:4.3~7.9(cm) 間隔尺度(量的変量)
sepal_width がく片の幅:2.0~4.4(cm) 間隔尺度(量的変量)
petal_length 花びらの長さ:1.3~6.9(cm) 間隔尺度(量的変量)
petal_width 花びらの幅:0.1~2.5(cm) 間隔尺度(量的変量)
species アヤメの種類(setosa, versicolor, virginica) 名義尺度(質的変量)

ヒストグラムを描画する

seaborn-histgram1

sns.distplot(
    iris['sepal_width'], bins=13, color='#123456', label='data',
    kde=False,
    rug=False
)
plt.legend() # 凡例を表示
plt.show()   # ヒストグラムを表示

seabornのdistplotメソッドでヒストグラムを描画します。

オプション 説明
data Seriesまたは1d-array、listのみ
bins 等級値(x軸の刻み目)の数。スタージェスの公式(※後述)で最適化可能
color 色の指定
label 凡例の指定。plt.legend()必須。
kde True:密度近似関数の描画
rug True:実数値の描画
fit norm:正規分布の描画

とりあえず、必須級のオプションはこれだけ。

もっと詳しくオプションを知りたい人は公式ライブラリ(英語)も合わせてどうぞ。

スタージェスの公式で階級幅(bins)を最適化する

ヒストグラムは階級幅のとり方によって、同じデータから作成しても形状が変わることが知られています。

seaborn-histgram2

ヒストグラムで実現したいことと言えば、サンプルデータの分布から、データの背景にある『真の分布』を知りたい、ということになります。

この目的を達成するためには、描画するヒストグラムの適切な階級値を設定しなければいけません。

その代表例が『スタージェスの公式』です

サンプル数をnとすると、適切な階級数kは2の累乗をとって初めてサンプルサイズ以上となる数にさらに1プラスしたものになる
# スタージェスの公式で適切なbinsの値を求める
sturges = lambda n: math.ceil(math.log2(n*2))

sturges(len(iris['sepal_width']))
# ==> 9

よってサンプル数150に対して、適切な階級数は9ということがわかりました。

サンプルサイズ スタージェスの公式で求めた階級数
20 6
50 7
100 8
250 9
500 10

カーネル密度推定(kde)を描画する

seaborn-histgram4

seaborn.distplotメソッドのkdeオプションをTrueにすると、ヒストグラムに滑らかな線(いまあるデータから、全体の分布の推定)を上から重ねて描画します。

(わかりやすいようにbinの値を極端に3にしています。)

sns.distplot(
    iris['sepal_width'], bins=3, color='#123456', label='data',
    kde=True
)
plt.legend()
plt.show()

kdeのグラフに対しても色やラベルを指定したい場合は、kde_kws={'color':'yellow','label':'kde'}のように記述します。

seaborn-histgram5

kdeの他にも、hist, rug, fitオプションに関しては、{hist, kde, rug, fit}_kwsのようにグラフ自体に他のオプションを付与できます。

正規分布を描画させる

seaborn.distplotメソッドのfitオプションをnormにすると、正規分布を描くことができます。

seaborn-histgram7

from scipy.stats import norm

sns.distplot(
    iris['sepal_width'],bins=sturges(len(iris['sepal_width'])),color='black',label='data',
    kde_kws={'label': 'kde','color':'k'},
    fit=norm,fit_kws={'label': 'norm','color':'red'},
    rug=False
)

plt.legend()
plt.show()

ヒストグラムをカテゴリ毎に描画する

次に、irisデータには『品種(species)』の項目があるので、品種カテゴリごとにsepal_widthのヒストグラムを描写してみます。

まとめて描画する

アヤメの品種別の『sepal_width』を重ねず、それぞれ独立した状態でまとめてヒストグラムを描画させたい場合は、seabornのFacetGridメソッドを使います。

seaborn-histgram6

grid = sns.FacetGrid(iris, col='species', hue='species', col_wrap=3, size=5)
grid.map(sns.distplot, 'sepal_width', bins=7, kde=True)
plt.show()

seaborn.FacetGrid|公式ライブラリ(英語)

各品種それぞれ、正規分布っぽい形をしていることが視認できますね。

重ねて描画する

次は、1つ1つのヒストグラムを独立させず、1つのグラフとして重ねたものになります。

sns.distplot(x)
sns.distplot(y)
sns.distplot(z)

こんな感じでdistplotメソッドを連続で使用します。

seaborn-histgram3

sns.distplot(
    iris.query('species=="setosa"')['sepal_width'],
    bins=sturges(len(iris.query('species=="setosa"')['sepal_width'])),
    color='red', 
    kde=True,
    label='setosa'
)
sns.distplot(
    iris.query('species=="versicolor"')['sepal_width'],
    bins=sturges(len(iris.query('species=="versicolor"')['sepal_width'])),
    color='blue',
    kde=True,
    label='versicolor'
)
sns.distplot(
    iris.query('species=="virginica"')['sepal_width'],
    bins=sturges(len(iris.query('species=="virginica"')['sepal_width'])),
    color='green',
    kde=True,
    label='virginica'
)

plt.legend()
plt.show()

スマートにいきたければ、forループを使いましょう。

iris_species_arr = iris['species'].unique()
color_dict = {0:'red', 1:'blue', 2:'green'}

for index, item in enumerate(iris_species_arr):
    data = iris.query('species=="'+item+'"')['sepal_width']
    sns.distplot(
        data,
        bins=sturges(len(data)),
        color=color_dict[index],
        kde=True,
        label=item
    )
    
plt.legend()
plt.show()

全ての列のヒストグラムを描画する

さいごはseabornの機能ではありませんが、DataFrameのhist()を使って、各列ごとのヒストグラムを一気に描画出来る方法があるので、それを紹介します。

irisデータは全部で5つ列がありますが、それぞれの列の値に1つでも質的データが含まれる場合、描画されません。

つまり、今回はspecies以外の列が全てヒストグラムで描画される対象になるので、合計4つプロットされます。

カテゴリを指定しない場合

seaborn-histgram8

from pylab import rcParams
rcParams['figure.figsize'] = 10, 10 # グラフのサイズを大きくする
iris.hist(bins=9);
plt.tight_layout() # グラフ同士が重ならないようにする
plt.show()

カテゴリを指定する場合

seaborn-histgram9

from pylab import rcParams
rcParams['figure.figsize'] = 10, 10
iris.query('species=="setosa"').hist(bins=7)
plt.tight_layout() # グラフ同士が重ならないようにする
plt.show()

DataFrameのqueryで条件指定するだけです。

さいごに

今回は、Pythonのseabornでヒストグラムを描画する方法を紹介しました。

自分が確認するときは、ササッと手短に。誰かに共有するときは、認知不可が無いようにわかりやすいグラフ作成を心がけましょう〜!

それでは