はじめに:なぜデータ可視化が重要なのか
データサイエンスの世界では、どんなに優れた分析結果でも、適切に可視化しないとその価値は半減してしまいます。正直な話、数値がずらっと並んだスプレッドシートを見せられても、ほとんどの人はピンときませんよね。それがグラフ一枚になった途端、パターンやトレンド、異常値が直感的に見えてくる。これがデータ可視化の力です。
Pythonはこの分野で最も充実したエコシステムを持つ言語の一つで、matplotlib、seaborn、Plotlyといった強力なライブラリが揃っています。
この記事では、2026年現在の最新バージョンであるmatplotlib 3.10とseabornを中心に、Pythonで美しく効果的なデータ可視化を行うための実践的なテクニックを紹介します。新機能の活用法からプロフェッショナルなグラフ作成まで、コード例付きでしっかり解説していきますので、ぜひ手を動かしながら読んでみてください。
環境構築と基本セットアップ
必要なライブラリのインストール
まずは環境構築から。本記事で使用するライブラリをインストールしましょう。Python 3.10以上を推奨します。
pip install matplotlib==3.10.8 seaborn==0.13.2 pandas numpy plotly
Jupyter NotebookやJupyterLab環境で作業する場合は、インライン表示を有効にするマジックコマンドも忘れずに。
%matplotlib inline
基本的なインポートとスタイル設定
すべての可視化プロジェクトで一貫したスタイルを適用するために、ファイルの冒頭で以下のように設定しておくのがおすすめです。個人的には、この初期設定をテンプレートとして保存しておくと、毎回のプロジェクト開始がかなり楽になります。
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
import pandas as pd
import numpy as np
# グローバルスタイル設定
plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['figure.dpi'] = 100
plt.rcParams['font.size'] = 12
plt.rcParams['axes.titlesize'] = 16
plt.rcParams['axes.labelsize'] = 14
# 日本語フォントの設定(macOS / Windows / Linux対応)
import platform
system = platform.system()
if system == 'Darwin':
plt.rcParams['font.family'] = 'Hiragino Sans'
elif system == 'Windows':
plt.rcParams['font.family'] = 'MS Gothic'
else:
plt.rcParams['font.family'] = 'IPAGothic'
# マイナス記号の文字化け防止
plt.rcParams['axes.unicode_minus'] = False
日本語環境でmatplotlibを使うとき、フォント設定はかなり重要です。上のコードではOS別に適切な日本語フォントを自動選択するようにしていて、これで軸ラベルやタイトルに日本語を使っても文字化けが起きません。(初めてmatplotlibで日本語を表示しようとして豆腐文字だらけになった経験、ありませんか?あれは本当に焦りますよね。)
matplotlib 3.10の新機能
2024年12月にリリースされたmatplotlib 3.10には、多くの改善と新機能が含まれています。ここでは実務で特に役立つものを見ていきましょう。
petroff10カラーサイクル:アクセシビリティ対応の新配色
matplotlib 3.10で個人的に一番うれしかったのが、このpetroff10カラーサイクルです。色覚多様性に配慮しながらも、見た目にも美しい配色になっています。機械学習ベースの美的モデルとクラウドソーシングによる色彩選好調査を組み合わせて開発されたという、なかなか凝ったアプローチで作られたものです。
# petroff10カラーサイクルの適用
plt.style.use('petroff10')
# サンプルデータで効果を確認
categories = ['A', 'B', 'C', 'D', 'E']
fig, ax = plt.subplots()
for i, cat in enumerate(categories):
data = np.random.randn(50).cumsum() + i * 10
ax.plot(data, label=f'カテゴリ {cat}')
ax.set_title('petroff10カラーサイクルのデモ')
ax.set_xlabel('時間')
ax.set_ylabel('値')
ax.legend()
plt.tight_layout()
plt.show()
ユニバーサルデザインの観点から、レポートやプレゼン資料ではpetroff10の使用をおすすめします。色覚に個人差がある方でも区別しやすい配色なので、より多くの人にとって読みやすいグラフが作れます。
新しいダイバージングカラーマップ:berlin、managua、vanimo
ダークモード対応のダイバージングカラーマップとして、berlin、managua、vanimoの3つが追加されました。中央が暗く、両端が明るいという特徴的なデザインです。
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
# サンプルデータ
data = np.random.randn(20, 20)
cmaps = ['berlin', 'managua', 'vanimo']
for ax, cmap_name in zip(axes, cmaps):
im = ax.imshow(data, cmap=cmap_name, aspect='auto')
ax.set_title(f'カラーマップ: {cmap_name}')
plt.colorbar(im, ax=ax, shrink=0.8)
plt.suptitle('matplotlib 3.10 新規ダイバージングカラーマップ', fontsize=18, y=1.02)
plt.tight_layout()
plt.show()
これらのカラーマップは、正と負の値を持つヒートマップや地理データの可視化で特に威力を発揮します。科学論文向けの配色としても広く認められているので、研究用途にもばっちりです。
Colorizerクラス:カラーマッピングの再利用
matplotlib 3.10で導入されたColorizerクラスは、複数のプロットで同じカラーマッピングを簡単に共有できるようにする新機能です。これ、地味に便利なんですよね。
from matplotlib.colorizer import Colorizer
from matplotlib.colors import Normalize
# Colorizerの作成
colorizer = Colorizer(norm=Normalize(vmin=-3, vmax=3), cmap='berlin')
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
# 同じColorizerを複数のプロットで使用
data1 = np.random.randn(15, 15)
data2 = np.random.randn(15, 15)
im1 = ax1.imshow(data1, colorizer=colorizer)
ax1.set_title('データセットA')
im2 = ax2.imshow(data2, colorizer=colorizer)
ax2.set_title('データセットB')
# 共有カラーバーの追加
fig.colorbar(im1, ax=[ax1, ax2], shrink=0.8, label='値')
plt.tight_layout()
plt.show()
複数のサブプロットで同じスケールのカラーマッピングを使いたい場面って意外と多いんですが、Colorizerを使うとコードがシンプルになって、スケールの一貫性も自然に保てます。
orientationパラメータ:箱ひげ図とバイオリンプロットの改善
従来のvertパラメータに代わり、より直感的なorientationパラメータが導入されました。名前から何をするか一目瞭然で、コードの可読性が上がります。
# 水平箱ひげ図の作成
data = [np.random.normal(loc, 1, 100) for loc in range(5)]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
# 新しいorientationパラメータを使用
ax1.boxplot(data, orientation='horizontal',
tick_labels=[f'グループ{i+1}' for i in range(5)])
ax1.set_title('水平箱ひげ図(orientation使用)')
ax1.set_xlabel('値')
ax2.violinplot(data, orientation='horizontal')
ax2.set_title('水平バイオリンプロット')
ax2.set_xlabel('値')
ax2.set_yticks(range(1, 6))
ax2.set_yticklabels([f'グループ{i+1}' for i in range(5)])
plt.tight_layout()
plt.show()
seabornによる統計的データ可視化
seabornはmatplotlibの上に構築された高レベルの可視化ライブラリです。統計的なグラフを少ないコードで美しく描けるので、探索的データ分析(EDA)にはもってこいのツールですね。
テーマ設定とスタイル管理
seabornのテーマ設定は非常に柔軟で、matplotlibの見た目をかなり改善できます。
# seabornのテーマ設定
sns.set_theme(
style='whitegrid', # 背景スタイル
palette='husl', # カラーパレット
font_scale=1.2, # フォントスケール
rc={
'figure.figsize': (10, 6),
'axes.titlesize': 16,
}
)
# コンテキスト設定(出力先に応じた調整)
# 'paper', 'notebook', 'talk', 'poster' から選択
sns.set_context('notebook', font_scale=1.1)
set_contextを使えば、論文用、ノートブック用、プレゼン用、ポスター用と、出力先に応じた最適なサイズ設定をワンライナーで切り替えられます。発表直前にフォントサイズを一つずつ変更する作業から解放されるのは、本当にありがたいです。
分布の可視化
データの分布を理解することは分析の基本中の基本。seabornはそのための強力な関数を揃えています。
# サンプルデータの作成
np.random.seed(42)
df = pd.DataFrame({
'年齢': np.concatenate([
np.random.normal(30, 5, 200),
np.random.normal(50, 8, 150)
]),
'年収(万円)': np.concatenate([
np.random.normal(450, 100, 200),
np.random.normal(700, 150, 150)
]),
'グループ': ['若手'] * 200 + ['中堅'] * 150
})
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. ヒストグラム + KDE
sns.histplot(data=df, x='年齢', hue='グループ', kde=True,
ax=axes[0, 0], alpha=0.6)
axes[0, 0].set_title('年齢分布(ヒストグラム + KDE)')
# 2. KDEプロット
sns.kdeplot(data=df, x='年収(万円)', hue='グループ',
fill=True, ax=axes[0, 1], alpha=0.5)
axes[0, 1].set_title('年収分布(KDEプロット)')
# 3. バイオリンプロット
sns.violinplot(data=df, x='グループ', y='年収(万円)',
ax=axes[1, 0], inner='box')
axes[1, 0].set_title('グループ別年収(バイオリンプロット)')
# 4. ストリッププロット + 箱ひげ図の重ね合わせ
sns.boxplot(data=df, x='グループ', y='年齢',
ax=axes[1, 1], width=0.4, fliersize=0)
sns.stripplot(data=df, x='グループ', y='年齢',
ax=axes[1, 1], alpha=0.3, size=3, color='black')
axes[1, 1].set_title('グループ別年齢(箱ひげ図 + ストリップ)')
plt.suptitle('seabornによる分布の可視化', fontsize=18, y=1.02)
plt.tight_layout()
plt.show()
ちなみに、ストリッププロットと箱ひげ図を重ねるテクニックは、分布の概要と個々のデータポイントの密度を同時に確認できるので、実務のEDAでかなり重宝します。見た目のインパクトもあって、レビュー会でウケがいいんですよね。
関係性の可視化
変数間の関係を把握するためのグラフも、seabornなら驚くほど簡潔に作れます。
# ペアプロットによる変数間の関係性把握
iris = sns.load_dataset('iris')
# 日本語カラム名に変換
iris.columns = ['がく片の長さ', 'がく片の幅', '花弁の長さ', '花弁の幅', '品種']
iris['品種'] = iris['品種'].map({
'setosa': 'セトサ',
'versicolor': 'バーシカラー',
'virginica': 'バージニカ'
})
g = sns.pairplot(iris, hue='品種', diag_kind='kde',
plot_kws={'alpha': 0.6},
height=2.5)
g.figure.suptitle('アイリスデータセットのペアプロット', y=1.02, fontsize=16)
plt.show()
# 相関行列のヒートマップ
numeric_cols = iris.select_dtypes(include=[np.number])
corr_matrix = numeric_cols.corr()
fig, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(corr_matrix, annot=True, fmt='.2f', cmap='RdBu_r',
center=0, square=True, linewidths=1, ax=ax,
vmin=-1, vmax=1,
cbar_kws={'label': '相関係数'})
ax.set_title('特徴量の相関行列')
plt.tight_layout()
plt.show()
相関行列のヒートマップは、特徴量選択やマルチコリニアリティの検出に非常に便利です。center=0を指定することで正の相関と負の相関が色で直感的にわかるようになるので、このオプションはぜひ覚えておいてください。
カテゴリカルデータの可視化
ビジネスレポートでは、カテゴリカルデータの可視化を求められる場面がとにかく多いです。seabornはこの用途でもバリエーション豊かなプロットを用意してくれています。
# サンプルの売上データを作成
np.random.seed(42)
sales_data = pd.DataFrame({
'地域': np.repeat(['東京', '大阪', '名古屋', '福岡', '札幌'], 50),
'四半期': np.tile(np.repeat(['Q1', 'Q2', 'Q3', 'Q4'], 50 // 4 + 1)[:50], 5),
'売上(万円)': np.concatenate([
np.random.normal(1000, 200, 50),
np.random.normal(800, 150, 50),
np.random.normal(600, 100, 50),
np.random.normal(500, 120, 50),
np.random.normal(400, 80, 50)
])
})
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
# 1. 棒グラフ(平均値 + 信頼区間)
sns.barplot(data=sales_data, x='地域', y='売上(万円)',
hue='四半期', ax=axes[0], errorbar='ci')
axes[0].set_title('地域別・四半期別平均売上')
axes[0].tick_params(axis='x', rotation=45)
# 2. ポイントプロット(トレンド比較に最適)
sns.pointplot(data=sales_data, x='四半期', y='売上(万円)',
hue='地域', ax=axes[1], dodge=True, markers=['o', 's', 'D', '^', 'v'])
axes[1].set_title('四半期推移(地域別)')
# 3. カウントプロット
sns.countplot(data=sales_data, x='地域', hue='四半期', ax=axes[2])
axes[2].set_title('地域・四半期別データ数')
axes[2].tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
実践的なグラフ作成テクニック
サブプロットとレイアウト制御
複数のグラフを一つの図にまとめるとき、matplotlibのGridSpecを使うと自由度の高いレイアウトが実現できます。均等分割では物足りない、というケースでは特に重宝します。
from matplotlib.gridspec import GridSpec
# 非対称レイアウトの作成
fig = plt.figure(figsize=(14, 8))
gs = GridSpec(2, 3, figure=fig, hspace=0.3, wspace=0.3)
# メインプロット(上段、横幅2カラム)
ax_main = fig.add_subplot(gs[0, :2])
# 右上(小さいプロット)
ax_right = fig.add_subplot(gs[0, 2])
# 下段3つ
ax_bottom1 = fig.add_subplot(gs[1, 0])
ax_bottom2 = fig.add_subplot(gs[1, 1])
ax_bottom3 = fig.add_subplot(gs[1, 2])
# サンプルデータの描画
x = np.linspace(0, 10, 100)
ax_main.plot(x, np.sin(x), label='sin(x)', linewidth=2)
ax_main.plot(x, np.cos(x), label='cos(x)', linewidth=2)
ax_main.set_title('メインチャート')
ax_main.legend()
ax_right.pie([35, 25, 20, 15, 5],
labels=['A', 'B', 'C', 'D', 'E'],
autopct='%1.0f%%', startangle=90)
ax_right.set_title('構成比')
for ax, title in zip(
[ax_bottom1, ax_bottom2, ax_bottom3],
['ヒストグラム', '散布図', '棒グラフ']
):
if title == 'ヒストグラム':
ax.hist(np.random.randn(500), bins=30, alpha=0.7, color='steelblue')
elif title == '散布図':
ax.scatter(np.random.randn(100), np.random.randn(100),
alpha=0.5, c='coral')
else:
ax.bar(range(5), np.random.randint(10, 50, 5), color='seagreen')
ax.set_title(title)
plt.suptitle('GridSpecによるカスタムレイアウト', fontsize=18, y=1.02)
plt.show()
アノテーションとテキストの活用
グラフにアノテーション(注釈)を追加すると、重要なポイントが一目でわかるようになります。特に上司やクライアントへの報告資料では、「ここを見てほしい」というメッセージを明確にできるのが大きなメリットです。
# 株価風の時系列データ
np.random.seed(42)
dates = pd.date_range('2025-01-01', periods=365, freq='D')
prices = 100 + np.cumsum(np.random.randn(365) * 1.5)
fig, ax = plt.subplots(figsize=(14, 6))
ax.plot(dates, prices, color='#2196F3', linewidth=1.5)
ax.fill_between(dates, prices, alpha=0.1, color='#2196F3')
# 最高値と最安値にアノテーション
max_idx = np.argmax(prices)
min_idx = np.argmin(prices)
ax.annotate(f'最高値: {prices[max_idx]:.0f}',
xy=(dates[max_idx], prices[max_idx]),
xytext=(dates[max_idx] + pd.Timedelta(days=30), prices[max_idx] + 10),
arrowprops=dict(arrowstyle='->', color='red', lw=2),
fontsize=12, color='red', fontweight='bold')
ax.annotate(f'最安値: {prices[min_idx]:.0f}',
xy=(dates[min_idx], prices[min_idx]),
xytext=(dates[min_idx] + pd.Timedelta(days=30), prices[min_idx] - 15),
arrowprops=dict(arrowstyle='->', color='green', lw=2),
fontsize=12, color='green', fontweight='bold')
# 移動平均線の追加
ma_30 = pd.Series(prices).rolling(30).mean()
ax.plot(dates, ma_30, color='orange', linewidth=2,
linestyle='--', label='30日移動平均')
ax.set_title('2025年 株価推移シミュレーション')
ax.set_xlabel('日付')
ax.set_ylabel('価格')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
カスタムスタイルシートの作成
チームやプロジェクト全体で一貫したデザインを保ちたいなら、カスタムスタイルシートが欠かせません。
# カスタムスタイルの定義(辞書形式)
custom_style = {
'figure.figsize': (12, 7),
'figure.facecolor': '#fafafa',
'axes.facecolor': '#ffffff',
'axes.edgecolor': '#cccccc',
'axes.grid': True,
'grid.color': '#e0e0e0',
'grid.linestyle': '--',
'grid.alpha': 0.7,
'axes.spines.top': False,
'axes.spines.right': False,
'font.size': 12,
'axes.titlesize': 16,
'axes.titleweight': 'bold',
'axes.labelsize': 13,
'lines.linewidth': 2.0,
'lines.markersize': 8,
}
# スタイルの適用
with plt.style.context(custom_style):
fig, ax = plt.subplots()
x = np.linspace(0, 2 * np.pi, 100)
ax.plot(x, np.sin(x), label='sin')
ax.plot(x, np.cos(x), label='cos')
ax.set_title('カスタムスタイルの適用例')
ax.legend()
plt.show()
plt.style.contextをコンテキストマネージャとして使えば、スタイルの適用範囲を限定できます。同じスクリプト内で異なるスタイルのグラフを安全に作り分けたいときに便利ですよ。
pandasとの連携による効率的なデータ可視化
pandas DataFrameから直接グラフを作るワークフローは、データ分析の現場では定番中の定番です。seabornはpandasとネイティブに統合されていて、カラム名を指定するだけでグラフが生成できるのがうれしいところ。
実務データの可視化パイプライン
ここでは、ECサイトの売上データを例に、実務で使える可視化パイプラインを組んでみます。
# ECサイトの売上データをシミュレーション
np.random.seed(42)
n_records = 1000
ec_data = pd.DataFrame({
'購入日': pd.date_range('2025-01-01', periods=n_records, freq='8h'),
'カテゴリ': np.random.choice(
['電子機器', '書籍', '衣料品', '食品', 'スポーツ'], n_records,
p=[0.25, 0.2, 0.3, 0.15, 0.1]
),
'購入金額': np.random.lognormal(mean=8, sigma=0.8, size=n_records).astype(int),
'顧客年齢': np.random.normal(35, 10, n_records).clip(18, 70).astype(int),
'リピート購入': np.random.choice([True, False], n_records, p=[0.35, 0.65])
})
# 月別集計
ec_data['月'] = ec_data['購入日'].dt.to_period('M').astype(str)
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
# 1. カテゴリ別売上の箱ひげ図
sns.boxplot(data=ec_data, x='カテゴリ', y='購入金額',
ax=axes[0, 0], palette='Set2')
axes[0, 0].set_title('カテゴリ別購入金額の分布')
axes[0, 0].set_ylabel('購入金額(円)')
axes[0, 0].tick_params(axis='x', rotation=30)
# 2. 年齢 vs 購入金額の散布図
sns.scatterplot(data=ec_data, x='顧客年齢', y='購入金額',
hue='カテゴリ', alpha=0.5, ax=axes[0, 1])
axes[0, 1].set_title('年齢と購入金額の関係')
axes[0, 1].set_ylabel('購入金額(円)')
# 3. カテゴリ別リピート率
repeat_rate = ec_data.groupby('カテゴリ')['リピート購入'].mean() * 100
axes[1, 0].barh(repeat_rate.index, repeat_rate.values, color='steelblue')
axes[1, 0].set_xlabel('リピート率(%)')
axes[1, 0].set_title('カテゴリ別リピート購入率')
for i, v in enumerate(repeat_rate.values):
axes[1, 0].text(v + 0.5, i, f'{v:.1f}%', va='center', fontweight='bold')
# 4. 月別売上推移
monthly = ec_data.groupby('月')['購入金額'].sum() / 10000
axes[1, 1].plot(range(len(monthly)), monthly.values,
marker='o', linewidth=2, color='#E91E63')
axes[1, 1].fill_between(range(len(monthly)), monthly.values,
alpha=0.1, color='#E91E63')
axes[1, 1].set_xticks(range(len(monthly)))
axes[1, 1].set_xticklabels(monthly.index, rotation=45, ha='right')
axes[1, 1].set_title('月別売上推移')
axes[1, 1].set_ylabel('売上合計(万円)')
plt.suptitle('ECサイト売上ダッシュボード', fontsize=20, y=1.02)
plt.tight_layout()
plt.show()
このように、pandasのgroupbyやdt属性と組み合わせることで、複雑なビジネスデータの可視化が驚くほどスムーズに進みます。
FacetGridによる条件別比較
seabornのFacetGridは、データをカテゴリ別に分割して同じ種類のグラフを並べて比較するための機能です。条件ごとの分布の違いを一発で把握できるので、分析初期段階のスクリーニングに最適です。
# FacetGridによるカテゴリ別分布の比較
g = sns.FacetGrid(ec_data, col='カテゴリ', col_wrap=3,
height=4, aspect=1.2)
g.map_dataframe(sns.histplot, x='購入金額', kde=True, bins=25)
g.set_titles(col_template='{col_name}')
g.set_axis_labels('購入金額(円)', '頻度')
g.figure.suptitle('カテゴリ別購入金額の分布', y=1.02, fontsize=16)
plt.show()
# jointplotによる二変量解析
g = sns.jointplot(data=ec_data, x='顧客年齢', y='購入金額',
hue='リピート購入', kind='scatter',
alpha=0.4, height=8)
g.figure.suptitle('年齢×購入金額のジョイントプロット', y=1.02)
plt.show()
グラフの保存とエクスポート
せっかく作ったグラフも、適切な形式で保存しないとレポートやプレゼンに活かせません。用途に応じたエクスポート方法を押さえておきましょう。
高品質な画像出力
# PNG形式(Webやプレゼンテーション向け)
fig.savefig('chart.png', dpi=300, bbox_inches='tight',
facecolor='white', edgecolor='none')
# SVG形式(ベクター形式、論文やレポート向け)
fig.savefig('chart.svg', format='svg', bbox_inches='tight')
# PDF形式(印刷向け)
fig.savefig('chart.pdf', format='pdf', bbox_inches='tight')
# 透過背景のPNG
fig.savefig('chart_transparent.png', dpi=300,
bbox_inches='tight', transparent=True)
Web向けにはPNG(300dpi以上が望ましい)、論文やレポートにはSVGかPDF、印刷物にはPDFが適しています。bbox_inches='tight'を指定しておくと余白が自動で最適化されるので、ほぼ必須のオプションと言っていいでしょう。
プロフェッショナルなグラフ作成のベストプラクティス
1. 目的に応じたグラフ種類の選択
まず大前提として、何を伝えたいかによって使うべきグラフの種類は決まります。
- トレンドの表示:折れ線グラフ、面グラフ
- 比較:棒グラフ、グループ化棒グラフ
- 分布:ヒストグラム、箱ひげ図、バイオリンプロット
- 関係性:散布図、バブルチャート
- 構成比:円グラフ(項目が少ない場合のみ)、積み上げ棒グラフ
- 相関:ヒートマップ、ペアプロット
2. デザイン原則
見た目で大事なのは「引き算」の発想です。
- シンプルさを保つ:3D効果や過度な色使いなど、不要な装飾は避ける
- データインク比を最大化:グラフの要素はデータを表現するものに限定する
- 色の使用には意味を持たせる:カテゴリの区別、値の大小など明確な理由がある場合だけ色を使う
- アクセシビリティへの配慮:色覚多様性に対応した配色(petroff10の活用)
- 適切なフォントサイズ:読み手の環境に応じたサイズ設定を心がける
3. よくあるアンチパターンと改善策
実は、よく見かけるグラフの中にも改善の余地があるものは多いです。典型的な例を見てみましょう。
# アンチパターン:3D円グラフ(歪みが生じて比較が困難)
# 改善策:水平棒グラフを使用
categories = ['商品A', '商品B', '商品C', '商品D', '商品E']
values = [35, 25, 20, 12, 8]
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
# 悪い例:円グラフ(項目が多い場合は読みにくい)
ax1.pie(values, labels=categories, autopct='%1.0f%%', startangle=90)
ax1.set_title('円グラフ(改善前)')
# 良い例:水平棒グラフ(ソート済み)
sorted_idx = np.argsort(values)
ax2.barh([categories[i] for i in sorted_idx],
[values[i] for i in sorted_idx],
color='steelblue')
for i, idx in enumerate(sorted_idx):
ax2.text(values[idx] + 0.5, i, f'{values[idx]}%',
va='center', fontweight='bold')
ax2.set_xlabel('割合(%)')
ax2.set_title('水平棒グラフ(改善後)')
plt.suptitle('グラフ選択の改善例', fontsize=16)
plt.tight_layout()
plt.show()
4. 色彩設計のガイドライン
色の使い方一つでグラフの印象はガラッと変わります。ブランドカラーに合わせたり、データの性質に応じたパレットを選ぶことで、よりプロフェッショナルな仕上がりになります。
# カスタムカラーパレットの作成
# ブランドカラーに合わせたパレット例
brand_colors = ['#1a73e8', '#ea4335', '#fbbc04', '#34a853', '#ff6d01']
brand_palette = sns.color_palette(brand_colors)
# 連続的なカラーパレット(データの大小を表現)
sequential_palette = sns.color_palette('Blues', n_colors=7)
# ダイバージングパレット(正負の値を表現)
diverging_palette = sns.color_palette('RdBu_r', n_colors=11)
fig, axes = plt.subplots(1, 3, figsize=(18, 3))
for ax, palette, title in zip(
axes,
[brand_colors, sequential_palette, diverging_palette],
['ブランドカラー', '連続パレット', 'ダイバージングパレット']
):
sns.palplot(palette, ax=ax)
ax.set_title(title)
plt.tight_layout()
plt.show()
機械学習モデルの評価グラフ
モデルを構築したら、次はその性能を可視化して評価するステップです。ここでは分類モデルの代表的な評価グラフを4つまとめて作ってみます。
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, learning_curve
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (
confusion_matrix, ConfusionMatrixDisplay,
roc_curve, auc, precision_recall_curve
)
# サンプルの分類データ生成
X, y = make_classification(n_samples=1000, n_features=20,
n_informative=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
y_proba = clf.predict_proba(X_test)[:, 1]
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
# 1. 混同行列
cm = confusion_matrix(y_test, y_pred)
ConfusionMatrixDisplay(cm, display_labels=['陰性', '陽性']).plot(
ax=axes[0, 0], cmap='Blues', colorbar=False
)
axes[0, 0].set_title('混同行列')
# 2. ROC曲線
fpr, tpr, _ = roc_curve(y_test, y_proba)
roc_auc = auc(fpr, tpr)
axes[0, 1].plot(fpr, tpr, linewidth=2, label=f'ROC曲線 (AUC = {roc_auc:.3f})')
axes[0, 1].plot([0, 1], [0, 1], 'k--', alpha=0.5)
axes[0, 1].set_xlabel('偽陽性率')
axes[0, 1].set_ylabel('真陽性率')
axes[0, 1].set_title('ROC曲線')
axes[0, 1].legend(loc='lower right')
# 3. 適合率-再現率曲線
precision, recall, _ = precision_recall_curve(y_test, y_proba)
axes[1, 0].plot(recall, precision, linewidth=2, color='#E91E63')
axes[1, 0].fill_between(recall, precision, alpha=0.1, color='#E91E63')
axes[1, 0].set_xlabel('再現率(Recall)')
axes[1, 0].set_ylabel('適合率(Precision)')
axes[1, 0].set_title('適合率-再現率曲線')
# 4. 学習曲線
train_sizes, train_scores, val_scores = learning_curve(
clf, X_train, y_train, cv=5,
train_sizes=np.linspace(0.1, 1.0, 10),
scoring='accuracy', n_jobs=-1
)
train_mean = train_scores.mean(axis=1)
train_std = train_scores.std(axis=1)
val_mean = val_scores.mean(axis=1)
val_std = val_scores.std(axis=1)
axes[1, 1].plot(train_sizes, train_mean, label='訓練スコア', marker='o')
axes[1, 1].fill_between(train_sizes, train_mean - train_std,
train_mean + train_std, alpha=0.15)
axes[1, 1].plot(train_sizes, val_mean, label='検証スコア', marker='s')
axes[1, 1].fill_between(train_sizes, val_mean - val_std,
val_mean + val_std, alpha=0.15)
axes[1, 1].set_xlabel('訓練データ数')
axes[1, 1].set_ylabel('精度')
axes[1, 1].set_title('学習曲線')
axes[1, 1].legend()
plt.suptitle('機械学習モデルの評価ダッシュボード', fontsize=18, y=1.02)
plt.tight_layout()
plt.show()
混同行列、ROC曲線、適合率-再現率曲線、学習曲線――この4つは分類モデルの評価で最も基本的かつ重要な可視化です。特に学習曲線は、モデルが過学習しているのか、それともデータが足りないのかの判断材料として欠かせません。
特徴量重要度の可視化
どの特徴量がモデルの予測に貢献しているかを把握するのは、モデルの解釈性を高めるうえで非常に大切です。
# 特徴量重要度の可視化
feature_names = [f'特徴量{i+1}' for i in range(20)]
importances = clf.feature_importances_
sorted_idx = np.argsort(importances)
fig, ax = plt.subplots(figsize=(10, 8))
ax.barh(range(len(sorted_idx)), importances[sorted_idx], color='steelblue')
ax.set_yticks(range(len(sorted_idx)))
ax.set_yticklabels([feature_names[i] for i in sorted_idx])
ax.set_xlabel('重要度')
ax.set_title('ランダムフォレストの特徴量重要度')
ax.axvline(x=np.mean(importances), color='red',
linestyle='--', label=f'平均: {np.mean(importances):.4f}')
ax.legend()
plt.tight_layout()
plt.show()
インタラクティブ可視化:Plotlyの基本
静的なグラフだけでは伝えきれない情報もあります。そんなときはインタラクティブなグラフの出番です。Plotlyを使えば、ズーム、パン、ホバーによる詳細情報の表示など、対話的な操作が可能なグラフを手軽に作れます。
import plotly.express as px
import plotly.graph_objects as go
# Plotlyによるインタラクティブ散布図
fig = px.scatter(
ec_data,
x='顧客年齢',
y='購入金額',
color='カテゴリ',
size='購入金額',
hover_data=['リピート購入'],
title='ECサイト:年齢×購入金額(インタラクティブ)',
template='plotly_white',
opacity=0.6,
size_max=15
)
fig.update_layout(
xaxis_title='顧客年齢',
yaxis_title='購入金額(円)',
font=dict(size=14)
)
fig.show()
# Plotlyによるサンバーストチャート
category_summary = ec_data.groupby(
['カテゴリ', 'リピート購入']
)['購入金額'].sum().reset_index()
category_summary['リピート'] = category_summary['リピート購入'].map(
{True: 'リピート', False: '初回'}
)
fig = px.sunburst(
category_summary,
path=['カテゴリ', 'リピート'],
values='購入金額',
title='カテゴリ別・リピート状況別の売上構成',
color='購入金額',
color_continuous_scale='Blues'
)
fig.show()
Plotlyは特にJupyter Notebook環境やWebアプリケーション(DashやStreamlit)との相性が抜群です。データの探索的分析フェーズでは、ホバーでデータポイントの詳細をその場で確認できるのが想像以上に便利ですよ。
まとめと次のステップ
この記事では、matplotlib 3.10の最新機能(petroff10カラーサイクル、新しいダイバージングカラーマップ、Colorizerクラス、orientationパラメータ)から始まり、seabornによる統計的可視化、pandasとの連携、機械学習モデル評価のグラフ、そしてPlotlyによるインタラクティブ可視化まで、Pythonのデータ可視化を幅広くカバーしました。
最後に、効果的なデータ可視化のポイントを改めてまとめておきます。
- 目的を明確にする:何を伝えたいかを先に決めて、それに最適なグラフ種類を選ぶ
- シンプルさを優先する:不要な装飾を排除して、データそのものを際立たせる
- アクセシビリティを考慮する:色覚多様性への対応(petroff10やカラーバリアフリーパレット)を忘れない
- 一貫性を保つ:スタイルシートやColorizerを活用して、プロジェクト全体で統一感を出す
- 適切なツールを選ぶ:静的な分析にはmatplotlib/seaborn、インタラクティブな探索にはPlotly
ここで紹介したテクニックは、pandasによるデータ操作スキルやscikit-learnによる機械学習パイプラインと組み合わせることで、データサイエンスワークフロー全体をカバーできます。ぜひ実際のデータで試して、自分なりの可視化スタイルを見つけてみてください。