plotly入門 データ可視化

【データ可視化】作ってはいけないグラフ6つ

2021年7月8日

今回は、データを可視化するにあたって、作ってはいけないグラフを6つ紹介したいと思います。

あとで詳しく説明しますが、例えば、以下のような”スパゲッティ・グラフ”と呼ばれるような図です。

ネットの記事でもよく見かけますが、こういうグラフはデータを解読するのに大きなエネルギーを費やすことになるので、大勢の人が時間と労力を割かなくてはなりません。

グラフを理解するための負荷を認知負荷(cognitive load)”と呼び、このようなグラフは認知負荷が非常に高くなります

自分で分析する時点ではいいですが、お客さんや上司に結果を見せる場合や、大勢の前でプレゼンテーションをする際は避けないといけません。

この記事ではそういった例を6つ紹介しています。

ときどきExcelで作った図もありますが、基本的にはPython Plotlyを使用していますので、そのコードも載せています。

グラフは仕事で作成する人も多いと思いますので、参考にしていただければと思います。

では、早速見ていきましょう。

使ってはいけないグラフ

円グラフ(Pie chart)は使わない

円グラフって統計っぽくて好きな人も多いですよね?

一方で、円グラフは使うな!という人も多いかと思います。

もちろん、わかりやすいので使ってもいいんだ、という人もいるかと思います。

色々な考え方があっても良いかと思いますが、私も極力使わない方が良いと考えています。

例えば、このような企業とある製品のマーケットシェアに関するデータがあるとしましょう。

df_pie = pd.DataFrame({'company': ['company A', 'company B', 'company C', 'company D', 'company E', 'company F'],
                       'share': [0.25, 0.21, 0.15, 0.09, 0.17, 0.13]})

円グラフで書くとこのようになります。

fig = go.Figure()
fig.add_trace(go.Pie(labels=df_pie['company'],
                     values=df_pie['share'],
                     textinfo='label+percent'),
)
fig.update_layout(showlegend=False)
fig.show()

わかりやすいと思う一方で、数値を確認しながら目をぐるっと一周回させて理解するのではないでしょうか?

ちなみに、書籍にある悪い例などでは数値が表示されていない例もありますが、さすがにそれは良くありません。

少なくとも数値は書かないと、このようにどの企業が多いのか本当にわからなくなってしまいます。

では、きちんと数値も記載した場合の問題は何でしょうか?

理由は以下の2点です。

  • 大きさが比較しにくいので直観的でない。
  • 他のグラフでよりわかりやすく代替できる場合が多い。

まず1点目は、領域の大きさを比較しながら、細かいところは数値を見ながら、どこが大きいか、どこが小さいかを確認しなければならないということです。

上の図を見てもわかる通り、目をぐるっと順番に回して比較しないといけません。

2点目はこの場合であれば棒グラフなどを使えばよりわかりやすく図示できことです。

少し装飾しますが、以下のようなコードで棒グラフで作成します。

fig = go.Figure()
fig.add_trace(go.Bar(x=df_pie['company'],
                     y=df_pie['share'],
                     width=0.5,
                     texttemplate='%{y:%}',
                     textposition='outside'
            ),
)
fig.update_layout(showlegend=False,
                  title='Market Share of Product XXX',
                  plot_bgcolor='white',
                  xaxis=dict(showline=True,
                             linewidth=1,
                             linecolor='lightgrey'),
                  yaxis=dict(showticklabels=False,
                             ))

fig.show()

作成される図は以下のようになります。

棒グラフの方がわかりやすいのではないでしょうか?

どこの会社のシェアが高いのかが一瞬でわかりますし、シェアが高い順に見るのも簡単です

他にも、特定の会社を強調する場合もあります。

その場合、円グラフだと色と少しずらすことによって、強調することが可能です。

fig = go.Figure()
fig.add_trace(go.Pie(labels=df_pie['company'],
                     values=df_pie['share'],
                     marker_colors=['royalblue', 'royalblue', 'grey', 'grey', 'grey', 'grey'],
                     textinfo='label+percent',
                     pull=[0.1, 0.1, 0, 0, 0, 0]),

)
fig.update_layout(showlegend=False,
                  title='Market Share of Product XXX')
fig.show()

しかし、これも棒グラフの方がわかりやすいです。

コードはこちらです。

fig = go.Figure()
for i, data in df_pie.iterrows():
  if i in [0, 1]:
    color = 'royalblue'
  else:
    color = 'lightgrey'
  text = f"{data['company'][-1]}<br>{data['share']*100}%"
  fig.add_trace(go.Bar(y=[0],
                      x=[data['share']],
                      width=0.3,
                      textposition='inside',
                       text=text,
                      marker_color=color,
                      orientation='h',
              ),
)
fig.update_layout(showlegend=False,
                  title=dict(text='Market Share of Product XXX',
                             y=0.9,
                             x=0.08
                  ),
                  plot_bgcolor='white',
                  xaxis=dict(showticklabels=True,
                             tickformat='%',
                             showline=True,
                             linecolor='lightgrey',
                             linewidth=1,
                             ticklen=5,
                             tickson='boundaries',
                             ticks='outside',
                             side='top'),
                  yaxis=dict(showticklabels=False,
                             ),
                  barmode='stack',
                  height=300
                  )

fig.show()

もし、Company AとCompany Bの合計シェアが重要なのであれば、以下のように、水平棒グラフを使用することも可能です(コードは省略します)

この方法であれば、プロダクトごとに表示したり、以下のように時系列に推移を表示するといったことも可能になりますね。

円グラフで時系列の推移を見た場合はこのようになります。

どこがどう変わったかを見たい場合は、左右を一生懸命比較しないといけません。

棒グラフであれば例えば以下のようにすることが可能です。

これはcompany Aとcompany Bの推移を強調したい場合です。

上図のコードはこちらです。

fig = go.Figure()
for company in df_share.columns:
    if company in ['company A', 'company B']:
      color = 'royalblue'
    else:
      color = 'lightgrey'

    text = [f"<br>{share*100: 0.0f}%" for share in df_share[company].values]
    fig.add_trace(go.Bar(y=[f'{index}年' for index in df_share.index],
                        x=df_share[company],
                        width=0.5,
                       textposition='inside',
                        text=text,
                        marker_color=color,
                        orientation='h',
                         name=company,
                ),
  )

fig.update_layout(showlegend=False,
                  title=dict(text='Market Share of Product XXX',
                            y=.85,
                            x=0.04,
                             font_color='grey'
                  ),
                  
                  plot_bgcolor='white',
                  xaxis=dict(side='bottom',
                             tickformat='%',
                             showline=True,
                             linewidth=2,
                             linecolor='lightgrey',
                             color='grey',
                             ticks="outside",
                            tickson="boundaries",
                              ticklen=5,
                             tickcolor='lightgrey'
                             )
                  barmode='stack',
                  )

pos = df_share.loc[2018]
pos_cumsum = pos.transform('cumsum').values
for i, company in enumerate(df_share.columns):
  if i == 0:
    position = pos[i] / 2
    company_display = company
  else:
    position = pos_cumsum[i-1] + pos[i] / 2
    company_display = company[-1]

  if company in ['company A', 'company B']:
    color = 'royalblue'
  else:
    color = 'grey'
  
  fig.add_annotation(text=f'<b>{company_display}', showarrow=False, x=position, y=3.45,
                  font_color=color,
                  font_size=15)

fig.show()

ということで、基本的に円グラフは使わないようにしましょう

ドーナツ・グラフ(Donut chart)は使わない

円グラフと同様にドーナツチャートも使わない方が良いです。

ドーナツ図は、円グラフの真ん中に穴をあけ、ドーナツ型にしたものです。

中央の穴の角度により大きさが見やすくなるというものです。

fig = go.Figure()
fig.add_trace(go.Pie(labels=df_pie['company'],
                     values=df_pie['share'],
                     textinfo='label',
                     hole=.4),
)
fig.update_layout(showlegend=False,
                  title='Market Share of Product XXX')
fig.show()

しかし、これも伝わりやすさを考えると、棒グラフにはかないません。

ドーナツ図もよっぽど何か理由がない限り、使わないようにしましょう

3D棒グラフは使わない

資料の見栄えが良くなるという理由で3Dの棒グラフにするのはやめましょう。

こちらは先ほどのデータで、Excelを使って作成した3Dです。

company Cのシェアはいくつでしょう?

答えは15%です。

15%より小さく見えますね。

という感じで、3Dの棒グラフは伝わりにくくするだけなので、使用しないようにしましょう。

以下のような、2次元の棒グラフも極力やめましょう。

棒が隠れていたり、水準が比較しづらかったりと、まったく何を伝えたいのかわからなくなりますね。

上の図で、時系列の推移を伝えたいのであれば、線グラフを使うことができます。

線グラフでの表現方法については次で説明します。

スパゲッティ・グラフにしない

上記の例では、3Dの棒グラフにすることで時系列の比較がしづらくなります。

それを回避する一つの手段として、線グラフにすることです。

もちろん、2次元の棒グラフにすることも可能ですが、この場合、線グラフの方がうまく表現できます。

例えば、以下のようなグラフです。

しかしながら、これでは何を言いたいかわかりません。

しかも、青の線はcompany Aで、水色の線はcompany Eで、といった具合に目がデータと凡例を行ったり来たりする必要があります

このようなグラフは“認知負荷”が非常に高くなります

また、このようにたくさんの系列がぐちゃぐちゃに絡み合っている線グラフをスパゲッティ・グラフと呼びます。

このスパゲッティ・グラフでは、グラフを見た人が、それぞれデータを解釈する必要があります。

自分が分析している途中であればこれでもよいですが、分析が終わってお客さんや上司、その他大勢の人々に結果を伝える場合は、もう少し言いたい部分を強調する必要があります

例えば、以下のような表示が一つ考えられます。

まだまだ、見やすくする方法・工夫は多数ありますが、もとのスパゲッティ・グラフよりはだいぶ見やすくなったのではないでしょうか?

コードはこちらです。

df_share = df_share.sort_index(ascending=True)
companies = df_share.columns
fig = make_subplots(rows=3, cols=2, subplot_titles=companies, 
                    shared_yaxes=True,
                    vertical_spacing=0.15)
for i in range(6):
  row, col = divmod(i, 2)
  row += 1
  col += 1
  for company, data in df_share.iteritems():
    
    if company == companies[i]:
      data_highlight = data.copy() 
    # 強調しないデータを描画
    else:
      fig.add_trace(go.Scatter(x=[f'{str(year)}年' for year in  data.index],
                               y=data,
                               mode='lines',
                               marker_color='lightgrey',
                               line_width=1.5,
                              ),
                    col=col, row=row)
  # 強調したいデータを描画(最前面に持ってくるための処理)
  fig.add_trace(go.Scatter(x=[f'{str(year)}年' for year in  data_highlight.index],
                           y=data_highlight,
                           mode='lines',
                           marker_color='royalblue',
                           line_width=3,
                          ),
                col=col, row=row)

  fig.update_xaxes(showline=True,
                    linewidth=1,
                    linecolor='lightgrey',
                    color='grey',
                    tickcolor='lightgrey',
                    ),
  fig.update_yaxes(showline=True,
                    linewidth=1,
                    linecolor='lightgrey',
                    color='grey',
                    tickcolor='lightgrey',
                    tickformat='%'
                    ),

fig.update_layout(showlegend=False,
                  title=dict(text='Market Share of Product XXX',
                            y=.95,
                            x=0.04,
                             font_color='grey'
                  ),
                  plot_bgcolor='white',
                  width=800,
                  height=700
                  )
        
fig.show()

わざわざこんなグラフを作るのは面倒臭いと思う人もいるかもしれませんが、これにより、たくさんの人の時間や労力といったコストを下げることができ、言いたいことが伝わります。

ということで、スパゲッティ・グラフはやめましょう。

2軸のプロットはなるべく避ける

これはこのサイトでも紹介しているプロットですね。

これについては、どうしても使う必要がある場面もあります。

しかしながら、皆さんも感じているかもしれませんが、2軸のプロットはどっちが右軸でどっちが左軸か確認しないといけませんし、かなりじっくり見ないとわかりづらいケースが多くなります。

作成した本人も考えないといけない場合も出てきます。

では、実際に見てみましょう。

以下のコードで、営業利益と営業利益率の推移を見るとします。

years = [2017, 2018, 2019, 2020]
profit = [320, 335, 342, 351]
profit_per_sales = [0.25, 0.27, 0.28, 0.32]
fig = go.Figure()
fig.add_trace(go.Bar(x=[f'{year}年度' for year in years],
                     y=profit,
                     width=0.5,
                     name='営業利益',
                     yaxis='y1'))
fig.add_trace(go.Scatter(x=[f'{year}年度' for year in years],
                         y=profit_per_sales,
                         name='営業利益率',
                         yaxis='y2'))
fig.update_layout(title='営業利益と営業利益率の推移',
                  yaxis1=dict(side='left',
                              title='営業利益(億円)'),
                  yaxis2=dict(side='right',
                              showgrid=False,                              
                              overlaying='y',
                              title='営業利益率',
                              tickformat='%'))
fig.show()

以下のようなグラフが作成されます。

自分がデータを確認するために見る分には良いと思います。

しかしながら、パッと見ると、青の棒は何を示しているか凡例で確認し、左右の目盛りを確認しなければなりません。

そして次に、赤の線は何を示しているかを凡例で確認し、右側に目をやって利益率がいくらなのかを確認しなければなりません。

これは先ほど言った認知負荷がかなり高い状況です。

グラフを紐解くだけでエネルギーを使うことになります。

一つの解決策は、例えば、直接数値を表示することです。

どういった図にするかは、何を伝えたいか?によってきますので、自分が伝えたいことに合わせて最適な表現方法を使うのが重要です。

ただし、どうしても2軸にして比較する必要がある、という場合もあるかと思います。

ですので、2軸のグラフに関しては必要な場合も多々ありますが、うまく回避できるのであれば回避しましょう

棒グラフでゼロ以外を起点にしてはいけない

これもよくある例ですね。

特徴量を工夫して、機械学習の精度が75%から82%、87%と改善したとしましょう(あくまで例です)。

差を強調したいがために、このようなグラフにすることがあります。

y軸の開始位置が70%になっています。

「差がわかりやすくなるのだからいいのではないか?」という意見もあるかもしれませんが、これではあたかも精度が何倍にもなったように見えます

じっくり見れば当然わかりますが、ぱっと見たときに誤認してしまいます。

色や太字、文字などで強調するのは良いですが、これでは明らかに実際の大きさと長さが違います。

ですので、シンプルに0%からスタートさせましょう。

改善を強調したいのであれば、文字の色や大きさで調整します。

これで十分伝わりますね。

同様の理由で、バブルチャートで量を半径で変えるというのも避けるべき事項の一つです。

大きさを半径で表してしまうと、実際の面積は2乗になってしまいます。

5倍の量のものが25倍の面積になってしまうので、量を誤認させてしまう可能性があります。

ということで、棒グラフは基本的にゼロを開始位置にしましょう

まとめ

さて、今回は使ってはいけないグラフということで、グラフを作成する際の注意点を解説しました。

すべてが必ずしも悪いというわけではありませんが、極力避けて、より伝わりやすい図を作るようにしましょう。

このあたりを意識していると、ネットにあるニュース記事のグラフを見て、わかりにくいなぁと思うようになると思います。

レイアウトの設定が多少凝っていますが、plotlyの細かいレイアウト設定については以下の記事で解説しています。

では!

-plotly入門, データ可視化
-,