はじめに
Icons8 Teamラベル別にデータの分布を確認するためヒストグラムを重ねてプロットすることは多いが、データ次第ではビン幅の違いが目立つケースがある。
TableauをはじめとしたBIツールを使っていても発生しないが、matplotlibやseabornは勝手に調整してくれないので自分で対応する必要がある。
方法
引数のbins
を利用する。
bins : int or sequence or str, optional
matplotlib.pyplot.hist
bins
は整数値だけでなくsequence
も受け取れるので、
range関数に最大値最小値を指定して好みの分割数を設定するだけでいい。
import numpy as np
import matplotlib.pyplot as plt
# ラベルが2種類でデータの分布が異なるDataFrameを用意
df_1st = pd.DataFrame(np.random.normal(loc=20, scale=10, size=100), columns=["val"])
df_1st["target"] = "class_1"
df_2nd = pd.DataFrame(np.random.normal(loc=15, scale=20, size=100), columns=["val"])
df_2nd["target"] = "class_2"
df = pd.concat([df_1st, df_2nd])
ビン幅補正前
import matplotlib as plt
import seaborn as sns
# target毎にプロット
for val in df["target"].unique():
ax = sns.distplot(df.query('target == @val')["val"], kde=False, label=f"target is {val}")
ax.legend()
ビン幅補正後
# 最小値
x_min = int(df["val"].min())
# 最大値
x_max = int(df["val"].max())
# 最小値から最大値の範囲で5間隔
range_bin_width = range(x_min, x_max, 5)
# target毎にプロット
for val in df["target"].unique():
ax = sns.distplot(df.query('target == @val')["val"], bins=range_bin_width, kde=False, label=f"target is {val}")
ax.legend()
補足
bins
を設定しない場合、Freedman-Diaconis ruleと呼ばれる手法でビンの数が決定されている。
この手法は中々優秀で単一のデータをプロットする場合、概ね問題なくプロットされる。
distributions.py
def _freedman_diaconis_bins(a):
"""Calculate number of hist bins using Freedman-Diaconis rule."""
# From https://stats.stackexchange.com/questions/798/
a = np.asarray(a)
if len(a) < 2:
return 1
h = 2 * iqr(a) / (len(a) ** (1 / 3))
# fall back to sqrt(a) bins if iqr is 0
if h == 0:
return int(np.sqrt(a.size))
else:
return int(np.ceil((a.max() - a.min()) / h))
おわりに
プロットは美しくないプロットは見せる相手に失礼なので、
最低限キレイに整えるするのは礼儀だと思う。