「データ視覚化のデザイン #3」をpandasとmatplotlibで実装する

# データ視覚化のデザインって？
UXやUIを突き詰めたサービスで有名なTHE GUILDのGo Andoさんがnoteで公開した、データの視覚化のポイントをまとめたもの。

https://note.mu/goando/n/n99f6c395ae8a

# #1, #2 は？

*  #1は以下を参照
    * https://qiita.com/skotaro/items/cdb0732ad1ad2a4b6236

*  #2は以下を参照
    * https://qiita.com/okd46/items/ee27aeee714f54a8a6c5
 
# 注意事項
* フォントについて、macでは動きますが他OSだと多分動かないので別のフォントを指定してください。

```python
plt.rcParams['font.family'] = 'Hiragino Sans'  
```
の部分です。


# 13. 表にグラフを入れると効果的
![picture_pc_1bd02bdb34795599c91bc4e22a5657e2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/196857/8ad8b591-4f06-fbc2-72c1-54b39329f874.png)



pandasを利用。

```python
import pandas as pd
import numpy as np

%matplotlib inline
# data
apple_products = pd.DataFrame({"プロダクト":["iPhone","iPad","Mac","Services","Other"],
                              "売上(Mドル)":[141319,19222,25859,29980,12863],
                             "ユニット":[216756,43753,19251,np.nan,np.nan]})

# 値のフォーマット
format_dict = {'売上(Mドル)':'{0:,.0f}', 'ユニット':'{0:,.0f}'}

# グラフの設定をしながら表示
(apple_products
 .style
 .format(format_dict)
 .hide_index()
 .bar(color="#99ceff", vmin=0, subset=['売上(Mドル)'], align='zero')
 .bar(color="#ff999b", vmin=0, subset=['ユニット'], align='zero'))

```

<img width="332" alt="スクリーンショット 2019-12-03 21.55.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/196857/7a67435c-0716-088e-1838-7bf17814dc7f.png">

* 値のフォーマットの部分では3桁ごとにカンマ区切りに[^1]
* 表についてはpandasのstyling機能を使用[^2]
* nanについて、0で埋めても良かったがユニットにservicesとotherは該当しないだろうという理由からnanのままにした
* .bar()の部分について、align='zero'と指定することで、グラフが適度な大きさになる

# 15. 割合の比較には積み上げグラフが便利
![picture_pc_ef8b0b193e70633750354d6f86d444df.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/196857/45e9b40a-6d7c-8f7e-6ee6-755825f5af40.png)

```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates


# pandasでのエラー回避用
from pandas.plotting import register_matplotlib_converters
register_matplotlib_converters()

# フォント設定
plt.rcParams['font.family'] = 'Hiragino Sans'  
plt.rcParams['font.weight'] = 'heavy'

# データ
music_env_df = pd.DataFrame({"ラジオ":[0.4,0.21],"CD・ダウンロード":[0.22,0.44],"動画配信":[0.2,0.3],"音楽配信":[0.18,0.05]},
            index=["GLOBAL","JAPAN"])
# 積み上げグラフ用
music_env_df.T.cumsum()
# グラフの色
bar_colors = ["#3B7780","#98C550","#7FC2CB","#E9C645"]

# x ticklabel を取得
x = music_env_df.index
# グラフの各項目名
keys = music_env_df.keys()

fig,ax = plt.subplots(figsize=(7,7))

# 1. 左右の枠を消去
sides = ['left','right']
[ax.spines[side].set_visible(False) for side in sides] 

# 2. 左軸メモリ、メモリラベル削除
ax.tick_params(left=False, labelleft=False)

# 3. 上下枠線の色変更
ax.spines['bottom'].set_color("dimgray")
ax.spines['top'].set_color("dimgray")

# 4. x軸メモリ設定
ax.tick_params(axis='x', labelsize='x-large',color="dimgray",labelcolor="dimgray")

# 5. 積み上げグラフをプロットおよびプロットの情報を格納
bar_info = []
for i in range(len(keys)):
    if i == 0:
        bar_info.append(ax.bar(x, music_env_df.T.iloc[i],width=0.5,color=bar_colors[i]))
    else:
        bar_info.append(ax.bar(x, music_env_df.T.iloc[i], bottom=music_env_df.T.cumsum().iloc[i-1],width=0.5,color=bar_colors[i]))

# 6. 各グラフの項目
for i,one in enumerate(bar_info):
    # %の数値を格納
    bar_center = [[0,0],[0,0]]
    #  棒グラフ同士の差を強調する線の座標を格納
    bar_line = [[0,0],[0,0]]
    for j,one_bar in enumerate(one):
        bar_center[j][0] =  one_bar.xy[0]+one_bar.get_width()/2
        bar_center[j][1] =  one_bar.xy[1]+one_bar.get_height()/2
        # 項目名を表示
        if j == 0:    
            ax.annotate(keys[i],xy=(0,0),xycoords="data",
                       xytext=(-0.4,bar_center[j][1]),
                       ha='right',color=bar_colors[i],fontsize=16)
            bar_line[j][0] = one_bar.xy[0]+one_bar.get_width()
            bar_line[j][1] = one_bar.xy[1]
        else:
            bar_line[j][0] = one_bar.xy[0] - bar_line[0][0]
            bar_line[j][1] = one_bar.xy[1] - bar_line[0][1]
        # 割合の数値（％）を表示
        ax.annotate(f'{one_bar.get_height():.0%}',xy=(0,0),xycoords="data",
                   xytext=(bar_center[j][0],bar_center[j][1]),
                   ha="center",va="center",color="white",fontsize=16)
        # 強調の線を表示
        ax.arrow(bar_line[0][0],bar_line[0][1], bar_line[1][0], bar_line[1][1], head_width=0, head_length=0, ec='dimgray')
        
# 7. 縦軸の領域を設定
ax.set_ylim(0,1)
       
```

![tmp9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/196857/8f39f2fb-dac6-4b2d-57e4-22934e5e8d32.png)

* 積み上げ用に累和データを作成(`music_env_df.T.cumsum()`)
* #6の強調の線については、GLOBAL棒グラフの右上からJAPAN棒グラフの左上を結ぶようにしている

# 番外編 #1

![DjHmYHNUcAE0dto.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/196857/2af667a3-68c7-eef0-e5f0-1fb8a063df64.jpeg)



```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as mdates


# pandasでのエラー回避用
from pandas.plotting import register_matplotlib_converters
register_matplotlib_converters()

# フォント設定
plt.rcParams['font.family'] = 'Hiragino Sans'  
plt.rcParams['font.weight'] = 'heavy'

# データ
icing_method = pd.DataFrame([0.35, 0.19, 0.13,0.05,0.03,0.02],
                           index=['アイスバス 2℃', 'アイスバス 8℃', '水掛け+アイスマッサージ','水掛け','扇風機','静脈アイシング'],
                           columns=['冷却スピード'])

icing_detail = ["2℃のアイスバスに全身浸かる","8℃のアイスバスに全身浸かる","12℃の水掛け+アイスマッサージ",
                "15℃の水道水を全身にかけ続ける","室温22℃で扇風機の風にあたる","（頸部・腋部・鼠径部）"]

# バーの色だけオリジナルで指定
ori_blue = "#71C0F9"

fig, ax = plt.subplots(figsize=(12, 6))

icing_method.plot.barh(legend=False, ax=ax, width=0.8,color=ori_blue)

# 1. タイトル設定
plt.title("冷却方法と冷却スピード",fontsize=24,fontweight='bold',color="dimgray")

# 2. 左に余白を多めに作る
plt.subplots_adjust(left=0.35)

# 3. y軸の順番を逆に
ax.invert_yaxis()

# 4. 左枠以外を消す
sides = ['right', 'top', 'bottom']
[ax.spines[side].set_visible(False) for side in sides] 

# 5. y軸x軸のtick, y軸のtick labelを消す
ax.tick_params(bottom=False, left=False,labelleft=False)

# 6. x軸値ラベルの設定
ax.set_xticks([i*0.1 for i in range(5)])
ax.tick_params(axis='x', labelcolor="silver")

# 7. x軸の範囲設定（0.4までにするとx=0.4のグリッドが出てこないため）
ax.set_xlim(0,0.41)

# 8. x軸グリッドの設定
ax.grid(axis="x")

# 9. x軸のラベル設定
ax.set_xlabel("10秒あたりに下がる体温（℃）",fontsize="x-large",fontweight="bold",color="silver")

# 10. バーの右側に実際の値、右側に項目と補足説明を表示
vmax = icing_method['冷却スピード'].max()
for i, (value,main_label,sub_label) in enumerate(zip(icing_method['冷却スピード'],icing_method.index,icing_detail)):
    ax.text(value+vmax*0.02, i, f'{value:,} ℃', fontsize='x-large', va='center', color=ori_blue)
    ax.text(-0.01, i-0.1,main_label  , fontsize='xx-large', va='center',ha='right',color="dimgray")
    ax.text(-0.01,i+0.25, sub_label, fontsize='x-large' ,va='center',ha='right', color="silver")
```

![tmp12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/196857/1eb09287-72e1-4aac-4739-cabc5d7f0cff.png)

* 項目と補足説明のように、同じtext内でフォントサイズを変えることが出来ないので、別々に分けて表示している



[^1]: https://pbpython.com/styling-pandas.html
[^2]: https://speakerdeck.com/komofr/pyconjp-2019