はじめに
データ分析と聞くと、売上予測やマーケティングなどビジネスの現場で使われるものという印象があるかもしれません。一方で、相関分析のような基本的な手法は、個人的な記録の整理や意思決定の補助としても役立ちます。
たとえば、長く付き合う持病がある場合、検査値の動きと体調の関係を、感覚だけでなく客観的な数値で把握したいと考えることがあります。そうした場面で、自分のデータを可視化しておくと、傾向の把握や医師との対話の材料として活用しやすくなります。
本記事では、Python を使った相関分析の実践的な手法を解説します。相関係数の算出からヒートマップ・散布図による可視化に加え、相関の統計的有意性(p 値)の確認、カテゴリ変数の相関の扱い、結果を解釈する際の注意点までを扱います。
- 相関分析と相関係数の基礎知識
- Pandas / Seaborn を使った算出と可視化のコード
- scipy による p 値(有意性)の確認と、Pearson / Spearman の使い分け
- カテゴリ変数の相関(Cramér’s V・相関比)の求め方
- 観察データを解釈する際の注意点(小標本・交絡・相関と因果)
相関分析は数行のコードで実行できますが、結果の読み取りには注意も必要です。本記事では、相関係数とヒートマップの基本に加えて、有意性の確認やカテゴリ変数の扱いといった一歩進んだ手法と、観察データを誤読しないための視点までをまとめます。
免責事項
本記事で使用するデータは、データ分析の手法を解説するための事例です。検査値の動きや治療への反応には大きな個人差があり、本記事の数値や分析結果が他の方に当てはまるとは限りません。また、後述のとおり少数の個人データから治療効果を結論づけることはできません。治療方針については、必ず専門医の診断に従ってください。
相関分析とは
相関分析とは、2 つのデータの間にある関係性の強さ(連動性)を計算し、数値化する手法です。片方の数値が変化したとき、もう片方の数値がどう動くのかを統計的に把握します。
この関係性の強さを表す数値を相関係数(Correlation Coefficient)と呼び、一般にrという記号で表します。相関係数は必ず -1 から 1 の範囲に収まり、次のように読み取ります。

相関係数の見方
1.0(完全な正の相関):
片方が増えれば、もう片方も増える関係(正比例)。例として、身長と体重、気温とアイスの売上など。
-1.0(完全な負の相関):
片方が増えれば、もう片方は減る関係(反比例)。例として、暖房の設定温度と電気代、運動量と体脂肪率など。
0(無相関):
片方が変化しても、もう片方には連動しない関係。例として、身長とサイコロの出目など。
一般的な目安として、絶対値が 0.7 以上で強い相関、0.4〜0.7 で中程度の相関、0.2〜0.4 で弱い相関とされることが多いですが、これは分野や標本サイズによって解釈が変わります。後述するように、標本が小さい場合は相関係数だけで判断せず、有意性(p 値)やグラフの形も併せて確認することをおすすめします。
相関関係と因果関係は異なる(擬似相関)
相関分析で注意したいのは、相関関係があるからといって因果関係があるとは限らない点です。
たとえば、アイスクリームの売上と水難事故の件数には正の相関が見られます。しかし、これはアイスを食べたことが事故の原因なのではなく、両方の背後に「気温が高い(夏である)」という別の要因が隠れているためです。このように、共通の要因によって見かけ上の関係が現れることを擬似相関と呼びます。
分析結果を見る際は、本当に A が B の原因なのかを冷静に検討する視点が必要です。とくに、観察された記録(実験的に条件を統制していないデータ)では、時期や別の要因が交絡しやすいため、相関の有無だけで結論を急がないことが大切です。
Python で相関分析を行う準備
まずは必要なライブラリのインストールと、分析に使うデータの準備を行います。
必要なライブラリ
相関分析では、次のライブラリを使用します。グラフ内で日本語を表示するためのフォント対応ライブラリも併せて導入します。
- pandas: データの読み込みや加工(Excel のような表操作)を行う
- matplotlib: グラフを描画する基本ライブラリ
- seaborn: matplotlib をベースに、ヒートマップなどの統計グラフを簡潔に描く
- scipy: 相関係数の p 値(有意性)を算出する
- matplotlib-fontja: グラフの日本語文字化けを防ぐ
なお、日本語フォント対応にはかつてjapanize-matplotlibが広く使われていましたが、Python 3.12 系で動作しない事象が報告され、更新も停滞しています。現在は後継で Python 3.13 まで対応するmatplotlib-fontjaをおすすめします。
ターミナル(コマンドプロンプト)で次のコマンドを実行します。
pip install pandas matplotlib seaborn scipy matplotlib-fontja使用するデータ(血液検査結果)
例として、ある期間の血液検査の結果をまとめたsample.csvを使用します(Excel で作成し、CSV 形式で保存したものを想定)
日付,WBC,HGB,PLT,T-P,ALB,CRP,治療
2020/10/13,7.4,11.8,348,7,4,2.81,アザニン
2020/10/19,5,11.3,418,6.9,3.7,3.5,レミケード
2021/12/3,6.7,12.3,239,7.5,4.3,0.17,レミケード&漢方
...(以下続く)各項目の意味は次のとおりです。今回の分析で特に注目するのは CRP と ALB です。
| 項目名 | 正式名称 | 意味・役割 |
|---|---|---|
| WBC | 白血球数 | 体の防御反応。細菌感染や炎症で増加する傾向がある |
| HGB | ヘモグロビン | 貧血の指標。低いと貧血状態を示す |
| PLT | 血小板数 | 止血作用。炎症が強いと増加する傾向がある |
| T-P | 総蛋白 | 血液中のタンパク質の総量 |
| ALB | アルブミン | 栄養状態の指標。炎症が長く続くと低下する傾向がある |
| CRP | C 反応性蛋白 | 炎症反応の指標。炎症が起きていると上昇する |
| 治療 | (カテゴリ) | その時期に使用していた治療薬等の名称(アザニン, レミケード, 漢方など) |
このデータを用いて、CRP(炎症)が高いときに他の数値がどう動くか、どの治療の時期に数値が安定しているかを分析していきます。
相関係数の算出とヒートマップ作成
読み込んだデータから数値項目を抽出し、相関係数を計算してヒートマップで表示します。
データの読み込み
pandas で CSV ファイルを読み込みます。日本語を含むため、文字化けを防ぐencodingを指定します。
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib_fontja # 日本語表示用
# データの読み込み
# index_col="日付": 1 列目の「日付」を行ラベル(インデックス)として扱う
# encoding="utf-8-sig": 日本語を含む CSV の文字化け対策
df = pd.read_csv("sample.csv", index_col="日付", encoding="utf-8-sig")index_col="日付"を指定する理由は、日付を分析対象の数値に含めないためです。日付を行ラベルにしておくと、純粋な検査数値だけを相関の計算対象にできます。encoding="utf-8-sig"は、特に Windows 環境で作成した CSV を読み込む際の文字化けを防ぎます。
相関係数の算出
データフレームに対して相関係数を計算します。
# 相関係数の算出
# numeric_only=True: 文字列(治療薬名など)を除外し、数値列だけで計算する
df_corr = df.corr(numeric_only=True)df.corr()は、各列の組み合わせについて相関係数(-1〜1)を一括で計算します。numeric_only=Trueを指定すると、文字列の「治療」列を除外し、数値列のみを対象にできます。なおdf.corr()は既定で Pearson の相関係数を計算します。Spearman を使う方法は後述します。
ヒートマップで可視化
計算した相関行列を、seaborn で色分けして表示します。
# グラフのサイズ設定
plt.figure(figsize=(10, 8))
# ヒートマップの作成
# annot=True: マスの中に数値を表示
# fmt=".2f": 数値を小数点第 2 位まで表示
# cmap="coolwarm": 青(負)〜赤(正)の配色
sns.heatmap(df_corr, annot=True, fmt=".2f", cmap="coolwarm", square=True, vmin=-1, vmax=1)
plt.tight_layout()
plt.show()cmap="coolwarm"は、正の相関(赤)と負の相関(青)を直感的に区別しやすい配色です。Bluesのような単色系では負の相関が見えにくくなるため、相関行列の可視化では発散系の配色が向きます。vminとvmaxを -1〜1 に固定すると、色の濃さを相関係数の大きさと正しく対応させられます。
ヒートマップにより、どの項目同士が強く関係しているかを把握できます。色が濃い赤いマス(正の相関)や青いマス(負の相関)ほど、関係性が強いことを示します。

相関の有意性(p 値)を確認する
ヒートマップで相関係数の大きさは把握できますが、df.corr()が返すのは相関係数だけで、その相関が統計的に意味のあるものか(偶然ではないか)はわかりません。標本データを扱う場合は、相関係数とあわせて p 値(有意性)を確認することをおすすめします。p 値はscipy.statsのpearsonrを使うと、相関係数と同時に算出できます。
from scipy.stats import pearsonr
import pandas as pd
# 数値列のみを取り出す
num_df = df.select_dtypes(include="number")
cols = num_df.columns
p_matrix = pd.DataFrame(index=cols, columns=cols, dtype=float)
# 各ペアについて相関係数と p 値を算出する
for c1 in cols:
for c2 in cols:
# 欠損値を含む行を除外してから計算する
pair = num_df[[c1, c2]].dropna()
if len(pair) >= 2 and c1 != c2:
r, p = pearsonr(pair[c1], pair[c2])
p_matrix.loc[c1, c2] = p
else:
p_matrix.loc[c1, c2] = 0.0 if c1 == c2 else None
print(p_matrix)pearsonrは相関係数rと p 値pをまとめて返します。一般に p 値が 0.05 未満であれば、その相関は統計的に有意である(偶然では生じにくい)と判断されることが多いです。ヒートマップで相関係数が大きくても p 値が大きい場合は、偶然による見かけの相関である可能性を考慮します。
ここで重要な注意があります。p 値の信頼性は標本サイズに依存します。標本が小さい場合、p 値の精度は低く、相関係数も不安定になりやすいため、有意性の判断は慎重に行う必要があります。標本が少ないケースでは、並べ替え検定(permutation test)を併用する方法もあります。少数の個人データを扱う本記事の例では、p 値はあくまで参考程度にとどめ、後述の「解釈上の注意」もあわせて検討することをおすすめします。
参考: scipy.stats.pearsonr(SciPy 公式)
“The p-value roughly indicates the probability of an uncorrelated system producing datasets that have a Pearson correlation at least as extreme as the one computed”
(p 値は、無相関なデータがこれと同等以上に極端な相関を偶然生む確率の目安を示します。)
https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.pearsonr.html
Pearson と Spearman の使い分け
df.corr()が既定で計算する Pearson の相関係数は、2 変数の線形関係の強さを測ります。一方、Spearman の相関係数は、値そのものではなく順位の関係(単調性)を測ります。曲線的だが一貫して増減する関係や、外れ値・歪んだ分布を含むデータでは、Spearman の方が実態に合うことがあります。
Spearman に切り替える場合は、methodを指定します。
# Spearman の相関係数(順位相関)
df_corr_spearman = df.corr(method="spearman", numeric_only=True)検査値のように分布が偏りやすいデータや、標本が小さいケースでは、Pearson と Spearman の両方を確認し、結果が大きく異なる場合はグラフの形(次の散布図行列)で関係性を目視することをおすすめします。
参考: NumPy, SciPy, and pandas: Correlation With Python(Real Python)
“Pearson’s coefficient measures linear correlation, while the Spearman and Kendall coefficients compare the ranks of data”
(Pearson は線形の相関を、Spearman と Kendall は順位の関係を測ります。)
https://realpython.com/numpy-scipy-pandas-correlation-python/
散布図行列(Pairplot)での詳細分析
相関係数を確認したら、次はグラフの形(散布図)を目で見て確認します。相関係数は関係の強さを 1 つの数値に要約しますが、その背後にある分布の形や外れ値までは表しません。seaborn のpairplotを使うと、全変数の組み合わせの散布図を一度に描画できます。
さらにhueを指定すると、治療列の内容ごとに点を色分けできます。
# 散布図行列の作成
# hue="治療": 「治療」列の種類ごとに点を色分けする
# height=2.5: 1 つのグラフのサイズ調整
sns.pairplot(df, hue="治療", height=2.5)
plt.show()hue="治療"には CSV の列名を正確に指定します。これにより、点が治療の種類ごとに色分けされ、どの治療の時期にどの数値帯へ分布しているかを把握しやすくなります。
対角線には各項目のヒストグラム(分布)が表示されます。たとえば特定の治療期間のグループが、CRP の低い位置に分布が偏っていれば、その時期は炎症が低めに保たれていた傾向が読み取れます。散布図側では、CRP が低く ALB が高い領域に点が集まっているグループがあれば、その時期は数値が安定していた傾向として確認できます。

数値(相関係数)で全体の傾向をつかみ、グラフ(散布図)で分布の形やグループごとの違いを確認する。この 2 段階で見ることが、相関分析を誤読しないための基本になります。
カテゴリ変数の相関を扱う
ここまでは数値項目同士の相関を扱いました。一方で、治療のようなカテゴリ変数(文字列)は、Pearson や Spearman では相関を計算できません。カテゴリ変数の関連の強さを測るには、別の指標を用います。
カテゴリ変数同士: Cramér’s V
2 つのカテゴリ変数の関連の強さには、カイ二乗検定をもとにした Cramér’s V を使います。0(関連なし)から 1(完全な関連)の範囲をとり、相関係数と同じ感覚で関連の強さを把握できます。pandasのクロス集計表とscipyのカイ二乗検定で算出できます。
import numpy as np
import pandas as pd
from scipy.stats import chi2_contingency
def cramers_v(x, y):
# クロス集計表(分割表)を作成する
confusion_matrix = pd.crosstab(x, y)
chi2 = chi2_contingency(confusion_matrix)[0]
n = confusion_matrix.sum().sum()
phi2 = chi2 / n
r, k = confusion_matrix.shape
# バイアス補正
phi2corr = max(0, phi2 - ((k - 1) * (r - 1)) / (n - 1))
rcorr = r - ((r - 1) ** 2) / (n - 1)
kcorr = k - ((k - 1) ** 2) / (n - 1)
denom = min((kcorr - 1), (rcorr - 1))
return np.sqrt(phi2corr / denom) if denom > 0 else np.nan
# 例: 「治療」と、別のカテゴリ列(例として「服薬遵守」など)の関連を測る
# v = cramers_v(df["治療"], df["別のカテゴリ列"])
# print(v)なおscipyには、Cramér’s V を直接算出するscipy.stats.contingency.associationも用意されています。複数のカテゴリ変数の組み合わせをまとめて行列化し、ヒートマップで可視化すれば、数値項目と同じ感覚でカテゴリ変数の関連も俯瞰できます。
参考: scipy.stats.contingency.association(SciPy 公式)
“A value of 1.0 indicates perfect association, and 0.0 means the variables … are independent”
(1.0 は完全な関連、0.0 は変数が独立であることを示します。)
https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.contingency.association.html
カテゴリ変数と数値変数: 相関比(η)
治療(カテゴリ)と CRP(数値)のように、カテゴリ変数と数値変数の関連を測りたい場合は、相関比(η, イータ)や一元配置分散分析(one-way ANOVA)が適します。相関比は、カテゴリ間で数値の平均がどれだけ分かれているかを 0〜1 で表す指標です。
import numpy as np
def correlation_ratio(categories, values):
# 欠損値を除外する
mask = values.notna()
categories = categories[mask]
values = values[mask]
cat_groups = values.groupby(categories)
y_mean = values.mean()
# 群間平方和と全体平方和の比から相関比を求める
ss_between = sum(len(g) * (g.mean() - y_mean) ** 2 for _, g in cat_groups)
ss_total = ((values - y_mean) ** 2).sum()
return np.sqrt(ss_between / ss_total) if ss_total > 0 else np.nan
# 例: 治療(カテゴリ)と CRP(数値)の関連の強さ
# eta = correlation_ratio(df["治療"], df["CRP"])
# print(eta)これにより、治療の違いによって CRP の水準がどの程度分かれているかを定量化できます。ただし、後述のとおり、関連が見られても治療そのものの効果とは限らない点に注意が必要です。
解釈上の注意(観察データの限界)
ここまでの手法で、検査値同士の相関や、治療ごとの分布の違いを可視化できます。一方で、こうした個人の記録(観察データ)から結論を導く際には、いくつかの限界を踏まえる必要があります。
- 標本サイズが小さいと、相関係数は偶然の変動に強く影響され、数値が大きく見えても安定しない。p 値の信頼性も低下する。
- 検査値は時系列データであり、観測が互いに独立していない。治療は時期とともに変化するため、「治療ごとの差」は時期や病勢の変化と交絡し、治療単独の効果として切り分けにくい。
- 治療を変更した経緯(調子が悪い時期に治療を強化した、など)があると、見かけの関連が逆向きに現れることもある。
- CRP と ALB の負の相関は、炎症(急性期反応)に伴う一般的な生理的関係として知られており、特定の治療の優位性を示すものではない。
相関やクラスターは、あくまで仮説を見つけるための手がかりです。「効果が証明された」と断定するものではなく、要因をさらに検討するための出発点として扱うのが適切です。
分析から見えること(仮説として)
以上の手法を踏まえ、サンプルデータからは次のような傾向が読み取れます。いずれも仮説であり、結論ではない点に留意してください。
CRP と ALB の負の相関
ヒートマップでは、CRP(炎症反応)と ALB(栄養状態)の間に強い負の相関が見られます。炎症が高まると栄養状態の指標が下がる、という関係が数値として可視化された形です。前述のとおりこれは生理的に知られた関係ですが、自分のデータでも同様の傾向が確認できることは、炎症のコントロールが他の数値にも波及しうるという見立ての参考になります。
治療時期ごとの分布の違い
散布図行列で治療時期ごとの分布を見ると、特定の併用期間のグループが、CRP が低く ALB が高い領域に比較的まとまって分布している様子が確認できます。一方で、これは治療の効果と断定できるものではなく、時期や病勢との交絡を含む観察にとどまります。
医師との対話の材料として
体調の変化は主観的な感覚で語られがちですが、データを可視化しておくと、客観的な記録として医師との対話に持ち込めます。たとえば「この期間は数値が安定していたので、方針を継続したい」といった相談の補足材料になります。データは結論を出すものではなく、対話と判断を助ける材料として活用するのが現実的です。
まとめ
本記事では、Python を使った相関分析の基礎から、ヒートマップ・散布図による可視化、有意性やカテゴリ変数の扱い、そして観察データを解釈する際の注意点までを解説しました。
df.corr()とsns.heatmap()で相関係数を一括で可視化できる- 標本データでは scipy で p 値を併記し、有意性も確認する
- 線形なら Pearson、順位なら Spearman と使い分ける
- カテゴリ変数には Cramér’s V や相関比を用いる
- 小標本・時系列の交絡があると、相関は不安定で誤読しやすい
- 相関やクラスターは仮説であり、効果の証明ではない
以上、最後までお読みいただきありがとうございました。
