Up

偏相関係数

疑似相関

CmdStanPy対応:2025.11

 

 

2つの変数XYの相関係数が、第3の変数Zの影響を除くと相関が無くなることがある。この疑似相関(spurious correlation)に対して、第3の変数Zの影響を単回帰モデルによって変数XYから除き、その残りの部分について相関係数を求めたものは偏相関係数(partial correlation coefficient)と呼ばれている(Mardia ei al., 1979)。すなわち、2つの単回帰モデル

 

 

において、の相関係数を偏相関係数と呼び、で表す。

 

 

である。

XYYZXZの間の相関係数をで表すと次式が成り立つ。

 

 

岡本安晴「Delphiで学ぶデータ分析法」CQ出版社pp32-35に証明がある。

式(4)を導く考え方については、<岡本安晴「データを使いこなすための統計入門」22世紀アート、電子書籍>のp.40-41でも説明している。

 

ここでは、式(4)により代数的に偏相関係数を求めるのではなく、ベイズ分析により偏相関係数の事後分布を求める。事後分布から、偏相関係数がどの範囲にあるかの推測を行うことができる。

 

定義より、偏相関係数をと置くとき、式(1)と式(2)に対して

 

 

と置くことができる。

上式に基づき、リスト1に示すStanスクリプトを用意した。

リスト1のStanスクリプトを用いて偏相関係数を求めるPythonスクリプトをリスト2に用意した。

ファイルはSpurCorrFiles.zipにまとめた。

 

偏相関係数を求めるプログラムの実行例を以下に示すが、データは図1と図2の2通りを用意した。

 

グラフィカル ユーザー インターフェイス, アプリケーション, テーブル, Excel

AI 生成コンテンツは誤りを含む可能性があります。

図1 DataXYZ.csv

 

グラフィカル ユーザー インターフェイス, アプリケーション, テーブル, Excel

AI 生成コンテンツは誤りを含む可能性があります。

図2 DataXYZW.csv

 

図1、図2のデータはともにCSV形式のファイルである。CSV形式のファイルは、Excelで図1、図2のようにデータを用意して、保存時にファイル拡張子として「.csv」を選ぶとCSV形式のファイルとして保存される。データは1行目に変数名を書き、2行目からデータ値を書く。1列目はデータの識別用で計算には用いられないが、入力データの形式として必要である。図1のデータは、第3の変数Zの影響を変数XYから除くと相関係数(偏相関係数)は0であると考えられる値になる。図2のデータは、変数XYの相関係数をそのまま算出すると正の値であるが、第3の変数Zの効果を除くと、隠されていた負の相関関係が現れ、偏相関係数は負の値になる。

 

 

まず、図1のデータDataXYZ.csvを分析する。

 

リスト1のStanスクリプトファイル(pcor.stan)、リスト2のPythonスクリプトファイル(main_calc.py)、および入力データファイ(DataXYZ.csv)を同じフォルダに置く。そのフォルダにカレントディレクトリ(フォルダ)を移して、CmdStanPyのインストールされた環境で、次のコマンドを実行する。CmdStanPyの簡単な説明を、このウェブサイトに用意した。

 

(stan) *****/SpurCorrFiles$ python main_calc.py

 

上のコマンドを実行すると、入力データファイル名の設定が求められる。

 

(stan) *****/SpurCorrFiles$ python main_calc.py

Input file name (*.csv) = DataXYZ.csv

 

上の例では、図1のファイル名が設定されている。

ファイル名が設定されると、Stanスクリプトのコンパイルが始まる。コンパイルには、多少時間が掛かる。コンパイルが終了すると、MCMCサンプリングが始まり、終了すると、トレース図が表示される(図3)。

 

図3

 

図3のWindowを閉じると、図4のグラフが表示される。

 

図4

 

回帰直線のパラメータの事後分布のグラフである。

図4のWindowを閉じると、図5のグラフが表示される。

 

図5

 

偏相関係数の事後分布のグラフである。偏相関係数が正である事後確率は、0.30925であると表示されている。事後分布は、ほぼ原点を中心にして分布していると言える。

図5のWindowを閉じると、図6のグラフが表示される。

 

図6

 

点(Z,X)の散布図が左上、点(X.ZY.Z)の散布図が右上に示されている。X.ZY.Zは、式(1)における残差式(2)における残差を表している。下段の散布図は、点(X,Y)の散布図が左側、点(Z,Y)の散布図が右側に示されている。散布図における赤の実線は、回帰直線である。散布図の上に表示されているrの値は相関係数である。

変量XYの相関係数はr=0.496であるが、偏相関係数はr=0.036であり、図5の事後分布から、偏相関係数は0であると言えそうである。

 

図6のWindowを閉じると、スクリプトの実行終了である。

 

 

次に、図2のデータを分析してみる。

 

リスト2のスクリプトを実行して、入力データファイルを以下のように設定する。

 

(stan) *****/SpurCorrFiles$ python main_calc.py

Input file name (*.csv) = DataXYZW.csv

 

上記ファイル設定で、図7のトレース図が表示される。

 

図7

 

図7のWindowを閉じると、図8のグラフが表示される。

 

図8

 

図8のWindowを閉じると、図9のグラフが表示される。

 

図9

 

偏相関係数の事後分布のグラフであるが、偏相関係数が正である事後確率は、0.00000と表示されている。事後分布の中央値は、―0.511である。偏相関係数は、負であると言えそうである。

図9のWindowを閉じると、図10のグラフが表示される。

 

10

 

左下の(X,Y)の散布図では、相関係数は0.629であり、回帰直線は右上がりの直線である。変量Zの影響を除いた点(X.ZY.Z)の散布図では、偏相関係数はー0.511で、回帰直線は右下がりの直線である。

 

10Windowを閉じると、スクリプトの実行終了である。

 

 

 

リスト1 偏相関係数のStanスクリプト(pcor.stan

 

data {

    int N;

    array[N] real X;

    array[N] real Y;

    array[N] real Z;

}

parameters {

    real<lower = 0.001, upper=1000> sgm1;

    real<lower = 0.001, upper=1000> sgm2;

    real<lower = -0.999, upper = 0.999> rho;

    real Ax;

    real Bx;

    real Ay;

    real By;

}

transformed parameters {

    array[N] vector[2] XY;

    for (i in 1:N) {

        XY[i][1] = X[i] - (Bx + Ax * Z[i]);

        XY[i][2] = Y[i] - (By + Ay * Z[i]);  

    }

}

model {

    sgm1 ~ uniform(0.001, 1000);

    sgm2 ~ uniform(0.001, 1000); 

    rho ~ uniform(-0.999, 0.999);

    Ax ~ normal(0.0, 1000.0);

    Ay ~ normal(0.0, 1000.0);

    Bx ~ normal(0.0, 1000.0);

    By ~ normal(0.0, 1000.0);

    for (i in 1:N) {

        XY[i] ~ multi_normal([0.0, 0.0],

                            [[sgm1*sgm1, rho*sgm1*sgm2],

                             [rho*sgm2*sgm1, sgm2*sgm2]]);

 

    }

}

 

 

 

リスト2 リスト1のStanスクリプトを用いて偏相関係数を求めるPythonスクリプト()

 

import csv

import numpy as np

from cmdstanpy import CmdStanModel

import matplotlib.pyplot as plt

import seaborn as sb

import scipy.stats as ss

import arviz as az

 

 

def calc_b_a(y, x):

    """

             単回帰分析

    """

    X = np.vstack((np.ones(len(y)), x)).T

    ba = np.linalg.inv(X.T@X)@X.T@np.reshape(y,(len(y),1))

    return ba[0][0], ba[1][0]

 

def calc_r(x, y):

    """

             相関係数の計算

    """

    num = np.sum((x - np.mean(x)) * (y - np.mean(y)))

    den = np.sum((x - np.mean(x))**2) * np.sum((y - np.mean(y))**2)

    return num / (den**0.5)

 

 

 

fin_nm = input('Input file name (*.csv) = ')

with open(fin_nm, 'r') as f:

    csv_data = [d for d in csv.reader(f)]

 

savefig_nm = 'Figure.png'

fout_nm = 'Results.txt'

fout = open(fout_nm, 'w')

fout.write('Input data file = {}'.format(fin_nm))

 

N = len(csv_data) - 1

print('N =', N)

X = []

Y = []

Z = []

for i in range(N):

    X.append(float(csv_data[i+1][1]))

    Y.append(float(csv_data[i+1][2]))

    Z.append(float(csv_data[i+1][3]))

 

fout.write('\n\nData =\n')

for x, y, z in zip(X, Y, Z):

    fout.write(f'{x}  {y}  {z}\n')

 

Data = {'N': len(X), 'X':X, 'Y':Y, 'Z':Z}

 

model = CmdStanModel(stan_file='pcor.stan')

fit = model.sample(data=Data)

print(fit.summary())

 

infdata = az.from_cmdstanpy(fit)  # Arviz InfereceData型へ変換

az.plot_trace(infdata, var_names=['rho', 'sgm1', 'sgm2'])

plt.tight_layout()

plt.show()

 

fit = fit.draws_pd()              #  Pandas DataFrame型への変換

 

print(fit.keys())

 

 

plt.figure(figsize=(8,6))

plt.subplot(221)

sb.kdeplot(fit['Ax'])

plt.title('Posterior distribution of Ax')

 

plt.subplot(222)

sb.kdeplot(fit['Bx'])

plt.title('Posterior distiribution of Bx')

 

plt.subplot(223)

sb.kdeplot(fit['Ay'])

plt.title('Posterior distribution of Ay')

 

plt.subplot(224)

sb.kdeplot(fit['By'])

plt.title('Posterior distribution of By')

 

plt.tight_layout()

plt.show()

 

sb.kdeplot(fit['rho'])

plt.xlabel('Partial Correlation', fontsize=12)

p_pos = np.mean(fit['rho']>0)

q1, q2, q3 = np.percentile(fit['rho'], [25, 50, 75])

plt.title('Posterior distribution of Partial Correlation' +

          f'\nP(partial corr>0)={p_pos:.5f},' +

          f'\nQ1={q1:.3f}, Med.={q2:.3f}, Q3={q3:.3f}',

          fontsize=14)

plt.tight_layout()

plt.show()

 

 

XZ = []

YZ = []

for i in range(len(X)):

    XZ.append(np.median(fit[f'XY[{i+1},1]']))

    YZ.append(np.median(fit[f'XY[{i+1},2]']))

 

 

plt.figure(figsize=(9,8))

plt.subplot(221)

plt.scatter(Z,X)

plt.xlabel('Z', fontsize=14)

plt.ylabel('X', fontsize=14)

min_Z = np.min(Z)

max_Z = np.max(Z)

Bx_hat = np.median(fit['Bx'])

Ax_hat = np.median(fit['Ax'])

plt.plot([min_Z, max_Z],

         [Bx_hat+Ax_hat*min_Z, Bx_hat+Ax_hat*max_Z], c='r')

r = calc_r(Z,X)

plt.title(f'r={r:.3f}', fontsize=14)

 

plt.subplot(223)

plt.scatter(X,Y)

plt.xlabel('X', fontsize=14)

plt.ylabel('Y', fontsize=14)

b, a = calc_b_a(Y,X)

min_X = np.min(X)

max_X = np.max(X)

plt.plot([min_X, max_X],

         [b+a*min_X, b+a*max_X], c='r')

r = calc_r(Y,X)

plt.title(f'r={r:.3f}', fontsize=14)

 

plt.subplot(224)

plt.scatter(Z,Y)

plt.xlabel('Z', fontsize=14)

plt.ylabel('Y', fontsize=14)

By_hat = np.median(fit['By'])

Ay_hat = np.median(fit['Ay'])

plt.plot([min_Z, max_Z],

         [By_hat+Ay_hat*min_Z, By_hat+Ay_hat*max_Z], c='r')

r = calc_r(Z,Y)

plt.title(f'r={r:.3f}', fontsize=14)

 

plt.subplot(222)

plt.scatter(XZ, YZ)

plt.xlabel('X.Z', fontsize=14)

plt.ylabel('Y.Z', fontsize=14)

b, a = calc_b_a(YZ, XZ)

min_XZ = np.min(XZ)

max_XZ = np.max(XZ)

plt.plot([min_XZ, max_XZ],

         [b+a*min_XZ, b+a*max_XZ], c='r')

r = np.median(fit['rho'])

plt.title(f'r={r:.3f}', fontsize=14)

 

plt.tight_layout()

plt.show()

 

fout.close()

print('Results.txt was saved.')

 

 

 

 

参考文献

Mardia, K.V., Kent, J.T., & Bibby, J.M. (1979) Multivariate analysis. Academic Press.

岡本安晴(2019) いまさら聞けないPythonでデータ分析.丸善出版

岡本安晴(1998) Delphiで学ぶデータ分析法.CQ出版社

 

 

 

Up