HOME/Articles/

いろいろ可視化の備忘録

Article Outline

いろいろ可視化の備忘録

普通にヒストグラムを書いた時に、外れ値のせいで横軸のレンジが広くなりすぎて、左隅に1本棒が立っているだけの意味のない可視化になる時が多い。

それを避けるために、クリッピングで数値丸めてからヒストグラム書くと、意味のある可視化になりやすい。

# クリッピングして2クラスのヒストグラム作成
fig=plt.figure(figsize=(18,14))
dim=len(plot_cols)
for i, col in tqdm(enumerate(plot_cols)):
    # クリッピング時のminmax取得
    vmin=df01[col].clip(df01[col].quantile(0.0), df01[col].quantile(0.99)).min()
    vmax=df01[col].clip(df01[col].quantile(0.0), df01[col].quantile(0.99)).max()
    # ヒストグラムのwidth決める
    if vmax>=10000:
        width=round(vmax/50)
    elif round(vmax/20)==0:
        width=1
    else:
        width=round(vmax/20)

    ax = plt.subplot(round(np.ceil(dim/np.sqrt(dim))), round(np.ceil(dim/np.sqrt(dim))), i+1)
    ax2 = ax.twinx()

    VAL = df01[df01[obj_col]==0][col].copy()
    # クリッピング
    if width==1:
        pass
    else:
        VAL = VAL.clip(VAL.quantile(0.0), VAL.quantile(0.99))

    VAL2 = df01[df01[obj_col]==1][col].copy()
    # クリッピング
    if width==1:
        pass
    else:
        VAL2 = VAL2.clip(VAL2.quantile(0.0), VAL2.quantile(0.99))

    # ヒストグラム
    sns.histplot(VAL.to_numpy(), binwidth=width, binrange=(0,max(VAL.max(), VAL2.max())), ax=ax, kde=False, label='0', color ='b', alpha=1.)
    sns.histplot(VAL2.to_numpy(), binwidth=width, binrange=(0,max(VAL.max(), VAL2.max())), ax=ax2, kde=False, label='1', color ='r', alpha=0.5)
    ax.set_title(col+', binwidth:'+str(width))
    ax2.grid(False)
    h1, l1 = ax.get_legend_handles_labels()
    h2, l2 = ax2.get_legend_handles_labels()
    ax.legend(h1+h2, l1+l2, loc='upper right', fontsize=10)
plt.tight_layout()
plt.show()

回帰予測の時の、実測値と予測値の散布図作成関数。

実測値±rateの割合と実測値±small_thresholdの範囲に網掛けして、範囲内のサンプル数もカウントする。

def obs_pred_plot(zissoku, yosoku# zissoku, yosoku:numpy array
                  , rate=0.1, small_threshold=5, model_name=''):
    # 閾値範囲を示すarray
    zissoku_lower = zissoku*(1-rate)
    zissoku_upper = zissoku*(1+rate)
    zissoku_lower_small = zissoku-small_threshold
    zissoku_upper_small = zissoku+small_threshold

    # 閾値範囲内だけのarrayのIndex
    inrange_ind = np.where(((yosoku>=zissoku_lower)&(yosoku<=zissoku_upper))|((yosoku>=zissoku_lower_small)&(yosoku<=zissoku_upper_small)))[0]

    ## 精度指標計算(一般的なものと、こちらで定義したものの2種)
    # 一般的なもの
    r2 = sklearn.metrics.r2_score(zissoku, yosoku) # r2 score
    rmse = np.sqrt(sklearn.metrics.mean_squared_error(zissoku, yosoku)) # rmse
    mape = np.mean(np.abs((yosoku - zissoku) / (zissoku+0.0001))) # mape
    # こちらで定義したもの
    good_ratio = (inrange_ind.shape[0])/(len(yosoku)) # 予測結果が閾値内に入る割合
    print('R2 score: {:.3f}'.format(r2))
    print('RMSE: {:.3f}'.format(rmse))
    print('MAPE: {:.3f}'.format(mape))
    print('Inside threshold ratio: {:.3f}'.format(good_ratio))

    # グラフの上限指定
    lim=max(zissoku.max(), yosoku.max())
    # グラフの上限指定
    limmin=min(zissoku.min(), yosoku.min())
    # 理想線引くためのlinear
    linear=np.linspace(limmin,lim,11)
    # figsize
    fig = plt.figure(figsize=(8,8))
    ax=plt.subplot(1,1,1)
    # 散布図(すべて)
    ax.scatter(zissoku, yosoku, alpha=0.5, label='Outside threshold')
    # 散布図(範囲内)
    ax.scatter(zissoku[inrange_ind]
               , yosoku[inrange_ind], edgecolors="k", alpha=1.
               , label='Inside threshold')
    # 範囲を可視化
    ax.fill_between(linear,linear*(1-rate),linear*(1+rate)
                    , facecolor='r',  alpha=0.3
                    , label='Threshold range(ratio):±{:.3f}'.format(rate))
    # 範囲を可視化(実測値が小さいところは±%の範囲が極小なので±実数で判断)
    ax.fill_between(linear, linear-small_threshold, linear+small_threshold
                    , facecolor='g', alpha=0.3
                    , label='Threshold range(small range):±{:.3f}'.format(small_threshold))
    # 理想的な予測結果の線を可視化
    ax.plot(linear,linear, label='Perfecrt predict line', ls='dotted', c='k', lw=3.)
    # 各指標を記す
    ax.annotate("R2 score: {:.3f}".format(r2), xy = (lim*0.5, lim*0.15), size = 12, color = "k")
    ax.annotate('RMSE: {:.3f}'.format(rmse), xy = (lim*0.5, lim*0.11), size = 12, color = "k")
    ax.annotate('MAPE: {:.3f}'.format(mape), xy = (lim*0.5, lim*0.07), size = 12, color = "k")
    ax.annotate('Inside threshold ratio: {:.3f}'.format(good_ratio), xy = (lim*0.5, lim*0.03), size = 12, color = "k")

    ax.legend()
    ax.set_xlim(limmin,lim)
    ax.set_ylim(limmin,lim)
    ax.set_xlabel('observation')
    ax.set_ylabel('prediction')
    ax.set_title(model_name+' Observation-Prediction plot')
    plt.show()

ただの棒グラフ作成用関数。

# 棒グラフ作成
def plots_bar(df02, obj_col, key_col, xlabel='', flg=0):
    # 棒グラフにする数値を集計
    tmp1 = df02[df02[obj_col]==flg].groupby([key_col])[['col01']].count().reset_index()
    #display(tmp1)
    tmp2 = df02[df02[obj_col]==flg].groupby([key_col])[['col02']].sum().reset_index()
    #display(tmp2)
    tmp3 = df02[df02[obj_col]==flg].groupby([key_col])[['col03']].nunique().reset_index()
    #display(tmp3)
    tmp = pd.merge(tmp1, tmp2, on=[key_col], how='left')
    tmp = pd.merge(tmp, tmp3, on=[key_col], how='left')

    fig=plt.figure(figsize=(15,15))
    ax=plt.subplot(3,1,1)
    ax.bar(tmp[key_col], tmp['col01'],color='blue',alpha=0.6)
    ax.set_xlabel(xlabel)
    ax.set_ylabel('col01')
    plt.setp(ax.get_xticklabels(), rotation=30, ha='right')

    ax=plt.subplot(3,1,2)
    ax.bar(tmp[key_col], tmp['col02'],color='blue',alpha=0.6)
    ax.set_xlabel(xlabel)
    ax.set_ylabel('col02')
    plt.setp(ax.get_xticklabels(), rotation=30, ha='right')

    ax=plt.subplot(3,1,3)
    ax.bar(tmp[key_col], tmp['col03'],color='blue',alpha=0.6)
    ax.set_xlabel(xlabel)
    ax.set_ylabel('col03')
    plt.setp(ax.get_xticklabels(), rotation=30, ha='right')

    plt.tight_layout()
    plt.show()

箱ひげ図。ただし、外れ値が大きい場合、箱ひげがつぶれて見えないので、外れ値は無しにする。

その代わり、stripplotも同時にplotして外れ値まだ可視化する。

# 箱ひげとstripplot作成
def plots_box_strip(df02, df_obj, obj_col, col, key_col, xlabel=''):
    tmp1 = df02.copy()
    tmp1 = tmp1.groupby([key_col, col])[['col02']].sum().reset_index()
    tmp1 = pd.merge(tmp1.reset_index(), df_obj, on=[key_col], how='left')
    tmp1[obj_col].fillna(0, inplace=True)

    tmp2 = df02.copy()
    tmp2 = tmp2.groupby([key_col,col])[['col01']].count().reset_index()

    tmp3 = pd.merge(tmp1, tmp2, on=[key_col, col], how='left')

    fig=plt.figure(figsize=(25,20))
    ax=plt.subplot(2,1,1)
    ax2=ax.twinx()
    # 箱ひげ
    sns.boxplot(x=col, y='col01', data=tmp3, hue=obj_col, ax=ax, boxprops=dict(alpha=.5), dodge=True, sym="")
    #sns.violinplot(x=col, y='col02', data=tmp3, hue=obj_col, ax=ax, jitter=True, dodge=True, alpha=0.5)#, boxprops=dict(alpha=.5)
    # stripplot
    sns.stripplot(x=col, y='col01', data=tmp3, hue=obj_col, jitter=True, dodge=True, ax=ax2, alpha=0.3, marker='o')
    ax2.grid(False)
    ax.set_ylim(None, tmp3['col01'].max()/10)
    ax.set_xlabel(xlabel)
    plt.setp(ax.get_xticklabels(), rotation=30, ha='right')

    ax=plt.subplot(2,1,2)
    ax2=ax.twinx()
    sns.boxplot(x=col, y='col02', data=tmp3, hue=obj_col, ax=ax, boxprops=dict(alpha=.5), dodge=True, sym="")
    #sns.violinplot(x=col, y='col02', data=tmp3, hue=obj_col, ax=ax, jitter=True, dodge=True, alpha=0.5)#, boxprops=dict(alpha=.5)
    sns.stripplot(x=col, y='col02', data=tmp3, hue=obj_col, jitter=True, dodge=True, ax=ax2, alpha=0.3, marker='o')
    ax2.grid(False)
    ax.set_ylim(None, tmp3['col02'].max()/10)
    ax.set_xlabel(xlabel)
    plt.setp(ax.get_xticklabels(), rotation=30, ha='right')

    plt.tight_layout()
    plt.show()

ただの円グラフ作成関数。

# 円グラフ作成関数
def pct_abs(pct, raw_data):
    absolute = int(np.sum(raw_data)*(pct/100.))
    return '{:d}\n({:.0f}%)'.format(absolute, pct) if pct > 5 else ''

def plot_chart(y_km):
    km_label=pd.DataFrame(y_km).rename(columns={0:'cluster'})
    km_label['val']=1
    km_label=km_label.groupby('cluster')[['val']].count().reset_index()
    fig=plt.figure(figsize=(5,5))
    ax=plt.subplot(1,1,1)
    ax.pie(km_label['val'],labels=km_label['cluster'], autopct=lambda p: pct_abs(p, km_label['val']))#, autopct="%1.1f%%")
    ax.axis('equal')
    ax.set_title('Cluster Chart (ALL Records:{})'.format(km_label['val'].sum()),fontsize=14)
    plt.show()

(予測 - 実測) ÷ 実測の計算。計算後はsns.scatterplot()で可視化したり、表として出力したりご自由に。

# (予測 - 実測) ÷ 実測
period_ = list(np.round(np.linspace(-0.5,0.5,11), 3))
dfresults = pd.read_csv("<FILE NAME>")  # 実測と予測のdf
dfresults.columns=['true', 'pred']
dfresults['diff'] = dfresults['pred'] - dfresults['true']  # 実測予測の差分
dfresults['ratio'] = dfresults['diff'] / dfresults['true']  # 実測予測の差分を実測値で割る
s_cut, bins = pd.cut(dfresults['ratio'], period_, right=False, retbins=True)  # 区切る
labels=bins[:-1]  # 区切る
s_cut = pd.cut(dfresults['ratio'], period_, right=False, labels=labels)  # 区切る
dfresults['period']=s_cut.values  # 区切った区間を追加
dfresults['period']=dfresults['period'].astype(str)
dfresults['period']=dfresults['period'].astype(float)  # float型へ
dfresults.loc[(dfresults['ratio']>=period_[-1])&(~(np.isinf(dfresults['ratio']))), 'period'] = period_[-1]+0.01
dfresults.loc[(dfresults['ratio']<period_[0])&(~(np.isinf(dfresults['ratio']))), 'period'] = period_[0]-0.01
dfresults.loc[(np.isinf(dfresults['ratio'])), 'period'] = 9999
#dfresults.loc[(np.isposinf(dfresults['ratio'])), 'period'] = 9999
#dfresults.loc[(np.isneginf(dfresults['ratio'])), 'period'] = -9999

dfresultsPlot = dfresults.copy()
rep = {i:str(n+1).zfill(2)+'_[{}, {})_DiffRatio'.format(round(i,3), round(i+0.1,3)) for n, i in enumerate(labels)}
rep[period_[-1]+0.01] = str(len(labels)+1).zfill(2)+'_[{}, )_DiffRatio'.format(period_[-1])
rep[period_[0]-0.01] = str(0).zfill(2)+'_[ , {})_DiffRatio'.format(period_[0])
rep[9999] = 'inf'
dfresultsPlot = dfresultsPlot.replace({'period':rep})

分布見るとき、バイオリンプロットの方が見やすいかも。

# num_cols:見たい連続値データのカラム名リスト
dim = len(num_cols)
fig=plt.figure(figsize=(18,18))
for i, col in tqdm(enumerate(num_cols)):
    tmp = df.copy()
    tmp[col] = tmp[col].clip(tmp[col].quantile(0.0), tmp[col].quantile(0.99))
    ax = plt.subplot(round(np.ceil(dim/np.sqrt(dim))), round(np.ceil(dim/np.sqrt(dim))), i+1)
    sns.violinplot(data=tmp, y=col, ax=ax, hue=obj_col, split=True, inner="quart")  # , hue="alive", split=True, x='union_category_name'
    ax.set_title(col, fontsize=8)
    plt.setp(ax.get_xticklabels(), fontsize=8)
    plt.setp(ax.get_yticklabels(), fontsize=8)
    ax.set_xlabel('', fontsize=8)
    ax.set_ylabel('dist', fontsize=8)
    ax.legend(fontsize=8)
plt.tight_layout()
plt.show()

'''
fig = plt.figure(figsize=(15,5))
# plt.rcParams['font.family'] = prop.get_name() #全体のフォントを設定
ax = plt.subplot(1,1,1)
sns.violinplot(data=df, y='column1', x='column2', ax=ax, hue='column2')  # hueカテゴリーごとに横軸に分布見られる
plt.setp(ax.get_xticklabels(), rotation=30, ha='right')
plt.show()
'''