中学社会670081 views
MathPython501700 views
小学算数1203731 views
数学講師2913151 views
中学理科1633649 views
高校化学2933198 views
高校倫理1444503 views
ヒストリア294981 views
教育150077 views
雑学1474218 views
Help
Tools

English

点を 1 つ足すと相関が 0.98 から -0.29 へ……Python で散布図を描く

10 個の点から相関係数を出すと 0.980238 でした。ここに点を 1 つ足しただけで、値が になります。

正の強い関係が、負の弱い関係に変わりました。データは 10 個から 11 個になっただけです。

数字を出す前に散布図を描く理由が、この 1 例に入っています。

相関係数を出す

NumPy には相関行列を返す関数があります。

import numpy as np

x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
y = np.array([38, 38, 42, 54, 53, 58, 69, 67, 73, 83])

print(np.corrcoef(x, y))
[[1.       0.980238]
 [0.980238 1.      ]]

の行列が返ります。対角線は自分自身との相関なので 、知りたい値は非対角の位置にあります。

print(round(float(np.corrcoef(x, y)[0, 1]), 6))       # 0.980238
print(round(float(np.corrcoef(x, y)[0, 1]) ** 2, 6))  # 0.960867

2 乗すると決定係数になります。この 10 点では、 のばらつきの 96% が で説明できる形です。

pandas でも同じ値

表として持っているなら、pandas のメソッドが使えます。

import pandas as pd

frame = pd.DataFrame({'x': x, 'y': y})

print(frame.corr())
x         y
x  1.000000  0.980238
y  0.980238  1.000000

列が 3 つ以上あっても、すべての組み合わせが一度に出ます。列名がそのまま行と列の見出しになるので、読みやすい。

順位で測る相関

値そのものではなく順位で計算する方法もあります。引数で切りかえます。

print(frame.corr(method='spearman'))
x         y
x  1.000000  0.972649
y  0.972649  1.000000

0.972649 で、ふつうの相関係数 0.980238 とわずかに違います。

順位に直してから計算するので、外れ値の影響が小さくなります。値が 200 でも 2000 でも、順位が同じなら結果は変わらない。

ふつうの相関係数

値の大きさをそのまま使う。直線の当てはまり具合を測る

順位相関

順位だけを使う。単調に増えているかどうかを測る

曲がっていても単調に増えていれば、順位相関は に近くなります。直線かどうかを問わない形です。

差がはっきり出る例を作ってみます。 を 1 から 10、 を とすると、順位はぴったり対応しているのに、値は右へ行くほど急に増える。

x = np.arange(1, 11)
y = 2 ** x
f = pd.DataFrame({'x': x, 'y': y})

print(round(float(f.corr().loc['x', 'y']), 4))                    # 0.7988
print(round(float(f.corr(method='spearman').loc['x', 'y']), 4))   # 1.0

ふつうの相関係数は 0.7988、順位相関は 1.0 でした。順位で見れば完全に対応しているので になります。

0.7988 という値だけを見ると「そこそこの関係」に見えますが、実際は が決まれば が一意に決まる関係です。どちらの数字も嘘ではなく、測っているものが違う。

散布図を描く

matplotlib で点を打ちます。

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(x, y)
ax.set_xlabel('x')
ax.set_ylabel('y')
fig.savefig('scatter.png', dpi=100)

matplotlib.use(‘Agg’) は、画面を持たない環境で画像だけを作るための指定です。ファイルに保存するだけなら、これで足ります。

figsize は横と縦の長さをインチで、dpi は 1 インチあたりの画素数を指定します。 画素の横幅になる。

回帰直線を重ねる

最小二乗の直線は np.polyfit で出せます。次数に 1 を渡すと 1 次式になります。

slope, cut = np.polyfit(x, y, 1)

print(round(slope, 4), round(cut, 4))    # 5.0 30.0

ax.plot(x, cut + slope * x)

傾き 5.0、切片 30.0 でした。散布図の上にこの直線を重ねると、点が直線のまわりにどう散っているかが見えます。

相関係数 0.980238 という 1 つの数は、この散らばり方を要約したものです。要約する前の形は、図でしか確かめられません。

点が多いときの描き方

数千点を打つと、点が重なって形が見えなくなります。大きさと透明度を下げると、密度が読めるようになる。

rng = np.random.default_rng(0)
x = rng.normal(50, 10, 2000)
y = 0.6 * x + rng.normal(0, 8, 2000)

fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(x, y, s=8, alpha=0.2)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.grid(True, linewidth=0.3)
fig.tight_layout()
fig.savefig('dense.png', dpi=100)

print(round(float(np.corrcoef(x, y)[0, 1]), 4))   # 0.593

s は点の面積、alpha は透明度です。alpha を 0.2 にすると、5 個重なったところでちょうど濃くなります。

default_rng(0) の 0 は乱数の種で、これを固定すると毎回同じデータが出ます。結果を再現できるようにしておくと、あとで確かめ直せる。

grid の線は 0.3 と細くしています。目盛りの線が太いと、点より線のほうが目立ってしまう。

例:1 点で符号が変わる

さきほどの 10 点に を足してみます。

x3 = np.append(x, 30)
y3 = np.append(y, 20)

print(round(float(np.corrcoef(x3, y3)[0, 1]), 4))   # -0.2882

0.9802 が になりました。 が大きいのに が小さい点を 1 つ置いたので、全体の傾きが引きずられています。

同じ位置に を足すと、こんどは 0.9968 まで上がります。 の平均から遠い点ほど、傾きを決める力が強い。

相関が でも関係はある

反対に、関係があるのに相関係数が になる場合もあります。

curve = np.array([-3, -2, -1, 0, 1, 2, 3])

print(round(float(np.corrcoef(curve, curve ** 2)[0, 1]), 4))   # 0.0

なので、 は から完全に決まります。それでも相関係数はちょうど 。

左半分の負の積と右半分の正の積が打ち消し合うためです。直線としてはどちらへも傾いていない、というのが相関係数の言い分になります。

散布図を描けば放物線が見えます。数字だけを見て「関係なし」と結論すると、この形を見のがす。

相関係数が測っているのは直線の当てはまり具合だけです。関係の有無ではありません。

に近い値が出たら、まず図を見る。放物線でも周期的な形でも、直線でなければ に近づきます。

描いてから数える

手順としては、図が先です。

散布図を描く

形と外れ値を目で確かめる

そのうえで相関係数を出す

外れ値が見つかったら、それが測定の誤りなのか、本当にそういう対象があるのかを調べます。落とすかどうかは、その判断のあとで決める。

図を描かずに数字だけを出すと、 という値をそのまま受けとることになります。1 点のせいだと分かるのは、図を見たときだけです。

見るべきところは決まっています。まず全体の形が直線に近いかどうか。次に、群れから外れた点があるかどうか。最後に、縦の散らばりが横の位置によって変わっていないか。

3 つ目は見のがしやすい。 が大きいところだけ縦に大きく散っている形は、回帰の前提が崩れている合図です。

この 3 つを目で確かめてから相関係数を出せば、その数字が何を要約しているかが分かった状態になります。

散布図を描いたら放物線の形をしていて、相関係数が 0.02 でした。どう読みますか。

  • と には関係がない
  • 直線の関係は弱いが、曲線の関係はある
  • 計算をまちがえている
__RESULT__

相関係数が測るのは直線の当てはまり具合だけです。対称な放物線では正と負の寄与が打ち消し合い、 に近い値になります。関係がないという結論は出せません。

np.corrcoef で 1 行、matplotlib で数行。手間はほとんど同じなので、両方を出す習慣にしておくほうが安全です。

参考

numpy.corrcoef. NumPy Reference.
pandas.DataFrame.corr. pandas documentation.
F. J. Anscombe, *Graphs in Statistical Analysis*. The American Statistician 27(1), 17-21, 1973.
10 点で np.corrcoef が返した 0.980238 は、$(30, 20)$ を 1 つ足すだけで $-0.2882$ になります。$(30, 200)$ を足せば 0.9968。$x$ の平均から遠い点ほど直線を強く引っぱるためです。pandas の .corr() は列の総当たりを一度に返し、method='spearman' で順位相関に切りかえられます。$y = 2^x$ ではふつうの相関が 0.7988、順位相関が 1.0。matplotlib で散布図を出し、np.polyfit の傾き 5.0 と切片 30.0 を重ねるところ、2000 点を alpha=0.2 で描いて密度を見る書き方まで、実行して確かめた出力とともに並べます。