クロス表・2次元配列データの視覚化
特異値分解(SVD)と内積による表現
クロス表(対応表)・2次元配列データは、内積で表すことができる。
内積で表すことにより、データの関係が視覚的に分かり易く表される。象徴語と色の適合関係のデータ、SD法(セマンティックディファレンシャル法)のデータの分析例を考え方の説明の後で扱う。
なお、これらの分析例では配列データをそのまま直接分析しているが、データの関係が内積で表現するのに適した値に変換(前処理)した上で分析することも考えられる。
ここで、2つのベクトルと
の内積は、それぞれのベクトルの対応する成分の積の和で表されるが、ベクトルの長さとベクトルのなす角
の余弦との積でも表される。
内積は、ベクトルの長さが長いほど大きい値となる。2つのベクトルが同じ方向()に向いていると正の値となり、反対方向に向いているときは負の値となる。ベクトルのなす角が
(直交する)であれば内積は0である。
内積による表現は、特異値分解により簡単に得ることができる。なお、特異値分解から内積モデルへの展開は、岡本安晴(2022)で例を示した特異値分解について頂いたコメント(岡太彬訓、2022)に触発されたものである。
行列Xの特異値分解は、次式で与えられる。
ここで、
,
,
である。、・・・、
は互いに直交する長さ1のベクトルであり、
、・・・、
は互いに直交する長さ1のベクトルであり、
は行列
の階数(rank)である。
は左特異ベクトル(left singular vector)、
は右特異ベクトル(right singular vector)、
は特異値(singular value)と呼ばれている。行列
に対してこれらを求める関数がライブラリ(Numpy、Scipyなど)に用意されており、プログラムで求めるのは簡単である。
式(1)の特異値分解の式を、式(3)のように書き変える。
いま、
,
と置くと、
である。すなわち、
を得る。特異値の分解は、
も考えられるが、この方法は匿名の方から問題点が指摘された。
クロス表の分析法に対応分析などがあるが、これらの分析法ではデータに含まれる情報を低次元の空間に表現することが目指され、その解が2次元空間に図示される。これに対して式(3)では特異値分解によりr次元空間における内積によるデータの表現が行われている。次元数rは配列データ行列の階数であり、階数が高いときは高次元空間での表現になる。データ分析において高次元空間に埋め込むことは、機械学習のサポートデータマシンで行われているが、高次元空間に埋め込むことにより、線形式による分離が可能になる例が示されている(Brunton & Kutz, 2019, Figure 5.23)。対応表や2次元配列データも、データ行列の階数rに対してr次元空間を用いるとデータの内積による表現が可能になる。得られた内積による表現を視覚化するときは2次元空間を選べばよい。次元の選択においては、特異値が参考になる。特異値は、データの説明量を表していると解釈できる(エッカート・ヤングの定理と呼ばれることがある;Brunton & Kutz, 2019, pp. 7-8)。
行項目と列項目の分布を描いたとき、2つの分布の広がりが同じようになると関係が見やすい。特異ベクトルは、長さが1という制約があるので、項目の数が多くなると特異ベクトルの成分の絶対値が小さくなる傾向がる。
いま、それぞれの分布の座標値の2乗の平均が等しいという条件を置く。
これと式(2)より、
を得る。
行項目の布置
と行項目
の布置
の距離とデータとの関係は以下のようになる。
データの行目のベクトルを
と置けば
と表され,
と書ける。同様に第行のベクトル
は
で与えられる。したがって,次式が成り立つ。
ここで、
=
である。すなわち,データの第行の値のベクトル
と第
行の値のベクトル
の距離の2乗は,行項目
の布置
と行項目
の布置
の重み
付き距離の2乗に等しい。
同様に、列項目の布置
と列項目
の布置
の距離とデータとの関係は以下のようになる。
データの第列の値のベクトル
は
と表され,
と書くことができる。第列のベクトルも同様に
と表せる。したがって,次式が成り立つ。
ここで、
=
である。すなわち,データの第列のベクトル
と第
列のベクトル
の距離の2乗は,列項目
の布置
と列項目
の布置
の重み
付き距離の2乗に等しい。
いま、直交回転を考える。
より
が成り立ち、内積は直交回転に対して不変であり、直交回転可能である。
例えば、下図のデータを分析してみる。
図1
図2に示す結果を得る。
図2
第1次元の値が同じで変数の違い/関係を表す情報が含まれていない。
図2の布置を図3のように回転する。この回転は3Dグラフィックにおいてマウスによるドラッグで回転している。
第
図3
行項目と列項目の親近関係と行項目間および列項目間の関係が、円環状の繋がりにおいてよく表されている。
回転を以下のように考える。
行項目のベクトルの座標行列と列項目のベクトルの座標行列
をまとめて
とおく。
である。
の行ベクトルで表される点の,
の分布の中心(平均)からの変動を最大(極大)にする方向を求めるために,
から
の平均
を引いたものを求め
と置く。すなわち
である。このの分布における変動の最大(極大)方向を与えるベクトル
,・・・,
を主成分分析の考え方(Timm, 1975)により求める。
これらのは,
の固有ベクトル(eigen
vector)として与えられる(Timm, 1975)。
これより,直交回転変換行列を
とおく。このとき
である。
クラスター分析
内積を与えるベクトルの座標値を用いて、クラスター分析ができる。ベクトルのなす角を用いたクラスタ分析も可能である。クラスタ分析は、次元による制約を受けない。
特異値分解は、適用できる2次元配列に制約はない。そこから内積表現が導かれるが、これは数学的に表現されるということであり、心理学的に自然な解釈ができることとは別である。例えば、色と象徴語の適合度のデータにおいて、不適合である方に注目して不適合であれば1とコーディングした場合、不適合であればベクトルの内積は大きくなりベクトルは近付くという表現が予想されるが、これは不自然であろう。また、セマンティックディファレンシャル法では双極性の尺度を用いることが標準であるが、尺度値をー3から3までの数値で表すとき、合う関係は内積が正になりベクトルは近付き、合わないときは内積は負になりベクトルは反対方向に向くので、自然な解釈になる。しかし、これを尺度値として1から7までの数値を用いると、反対方向の関係が内積が0に近い値で表されるので不都合と考えられる。特異値分解とそこから導かれる内積表現はデータを選ばないが、得られた解の解釈はデータに依存している。
図C1に示すデータは、大山(1994)に説明されている色と象徴語の関係を、適合するものを1で、そうでないものを0で表した岡本(2014)の表14.1.1をExcelファイルとしたものである。
図C1
図C1では、適合するかどうかが1と0で表されているが、データが適合すると答えた人の人数、あるいは適合度を100点満点で評価したときの平均値であれば、図C1の数値は0以上の実数となる。また、適合しないを負の数で表すデータも考えられる。いずれにせよ、1と0で適合するかしないかを表すという大まかなデータでどのような分析結果となるかは興味深いと思われる。
図C1のデータをリスト1のプログラムで分析する。スクリプトファイルなどは圧縮ファイルsamplefiles.zipにまとめている。Jupyter Notebook用のファイルもsamplefiles_jn.zipにまとめている。
リスト1のプログラムを実行すると、入力ファイル名を聞いてくるので、入力データファイル名を入力する。入力データファイルは、図C!に示す形式のExcelファイルである。
入力データファイル名が設定されると、ファイルが読み込まれて計算が始まる。
先ず、特異値のグラフ(スクリープロット)が表示される(図C2)。
図C2
3つ目までの特異値が大きいが、1番目の特異値はデータの全体的な様子を表すので、2番目と3番目の特異値が個々の変数の関係を表していると予想できる。
図C2のグラフを閉じると、図C3のグラフが表示される。
図C3
布置の回転を行うときの主成分分析の固有値のグラフである。1番目と2番目の固有値が大きいので、布置はほぼ2次元空間に配置されていると考えられる。
図C3のグラフを閉じると、回転後の布置が表示される(図C4)。
図C4
色が色相環に沿う形で配置され、それぞれの色に適合する象徴語が色に沿う形で配置されている。原点(星印)から色(列項目:下向き3角形)あるいは象徴語(行項目:上向き3角形)の位置までがベクトルを表す。
図C5のフォームを閉じると、次の描画における次元の設定が求められるので、次元の他の組み合わせにおける布置が得られる。次元として0以下の値を設定すると、プログラムは終了する。
図S1は、大山ら(2005)の図4-4に示されているセマンティック・ディファレンシャル法のデータ(「幸福」のイメージに対する評定形容詞対の平均値)を読んで、Excelファイルとしたものである。形容詞対は、対の一方のみを示している。
図S1 「幸福」のイメージに対する平均値
図S1のデータをリスト1のプログラムで分析する。プログラムなどは圧縮ファイルsamplefiles.zipにまとめている。Jupyter
Notebook用のプログラムファイルなどは圧縮ファイルsamplefiles_jn.zipにまとめた。
プログラムを実行すると、入力データファイル名の設定が求められる。入力データファイルは、図S1の形式で、Excelファイルとして用意する。
入力データファイル名を設定すると、データは読み込まれて計算が始まる。
まず、特異値のグラフ(スクリープロット)が表示される(図S2)。
図S2
1番目と2番目の特異値が大きいので、データ配列はほぼ2次元の布置で表されると考えられる。
図S2のグラフを閉じると、固有値のスクリープロットが表示される(図S3)。
図S3
固有値のスクリープロットから、第1固有値と第2固有値が大きく、布置はほぼ2次元空間に配置されていると考えられる。
図S3のフォームを閉じると図S4の布置のグラフが表示される。
図S4
原点(星印)から国(行項目:上向き3角形)あるいは形容詞対(列項目:下向き3角形)の位置までがベクトルを表す。
「幸福」のイメージとして、「美しい」、「好きな」、「良い」、「明るい」、「陽気な」があげられ、「緊張した」とは反対のイメージであることが表されている。「日本」と「台湾」のグループと、「米国」と「セルビア」のグループにまとまっていて、「米国」と「セルビア」のグループでは「幸福」は「動的な」と「派手な」のイメージに近いが、「日本」と「台湾」ではそうではないことが表されている。
Brunton, S. L., & Kutz, J. N. (2019). Data-driven science and engineering. New York: Cambridge University Press.
岡太彬訓(私信、2022.11.02)
岡本安晴(2019) いまさら聞けないPythonでデータ分析.丸善出版
岡本安晴(2022) 官能評価に使える統計解析ツール Python編、日本官能評価学会誌、第26巻第2号、102-106.
大山 正(1994) 色彩心理学入門――ニュートンとゲーテの流れを追って――.中公新書.
大山 正・岩脇三良・宮埜嘉夫(2005)「心理学研究法」サイエンス社
Timm, N. H.
(1975). Multivariate analysis with
applications in education and psychology. Brooks/Cole Publishing Company.
import pandas as pd
import numpy as np
import
matplotlib.pyplot as plt
import
japanize_matplotlib
# ExcelファイルをPandasのDataFrame型として読み込む
fl_nm = input('Excel
Data File(*.xlsx) = ')
xlfile =
pd.ExcelFile(fl_nm)
df =
pd.read_excel(xlfile)
# 1行目の値(列変数名)
col_names =
df.keys()
# Excelの1行目
c00 =
col_names[0]
# 行変数名の列のキー名
col_names =
col_names[1:]
# 2番目以降を取り出す
print('col_names...\n',
col_names)
# 行変数名
row_names = df[c00]
print('row_names...\n',
row_names)
# セル内の数値
df_data =
df.values
# 2行目以降が取り出される
F =
df_data[:,1:]
# 取り出した各行の2列目以降を取り出す
data = np.asfarray(F)
# dataの特異値分解SVD
u, s, vh =
np.linalg.svd(data, full_matrices = False)
print('data =\n',
data)
p = sum([1 if v >
1.0E-9 else 0 for v in s])
s = s[:p]
print('\ns =', s)
print('p =', p)
u = u[:,:p] # (u.T[:p]).T
vh = vh[:p]
plt.title('Scree Plot
of Singular Values', fontsize=14)
plt.plot(range(1,
p+1), s, c = 'k')
plt.xticks(np.arange(1,
p+1, 1))
plt.ylabel('Value',
fontsize = 14)
plt.tight_layout()
plt.savefig('Fig_Singular.png')
plt.show()
print('\n',
'Fig_Singular.png', 'was saved.')
M = len(data)
N = len(data.T)
s1 = np.empty(p)
s2 = np.empty(p)
for t in range(p):
s1[t] = (s[t]**0.5) *
((M/N)**0.25)
s2[t] = (s[t]**0.5) *
((N/M)**0.25)
# 特異値による重み付け
row_coord = u @
np.diag(s1)
col_coord =
np.diag(s2) @ vh
#
# 行情報と列情報の積み重ね
#
mrg_coord =
np.concatenate((row_coord, col_coord.T), axis = 0)
mrg_label =
np.concatenate((row_names, col_names), axis = 0)
#
# 積み重ね座標の中心化と主成分分析(標準偏差の調整無し)
#
mean_X =
np.mean(mrg_coord, axis = 0)
Z = mrg_coord -
mean_X
w, v =
np.linalg.eigh(Z.T @ Z)
sidx = np.argsort(-w)
w = w[sidx]
v = v.T[sidx].T
print('w =\n', w)
plt.title('Scree Plot
of w', fontsize=16)
plt.plot(np.arange(len(w))+1,w)
plt.ylabel('Eigen value',
fontsize=14)
plt.tight_layout()
plt.savefig('Fig_Eigen.png')
plt.show()
print('\n',
'Fig_Eigen.png', 'was saved.')
#
# 座標軸の回転
#
Proj = v.T
Proj_mrg_coord =
(Proj @ mrg_coord.T).T
p_dim = 5 if p > 5
else p
fout =
open('Coord.txt', 'w')
fout.write(f'\n\n{"Names":<15s}')
for i in
range(p_dim):
fout.write(f'{f"Dim-{i+1}":<15s}')
fout.write('\n')
for i in range(M):
fout.write(f'{row_names[i]:<15s}')
for j in range(p_dim):
fout.write(f'{Proj_mrg_coord[i][j]:<15.5f}')
fout.write('\n')
fout.write('\n')
for i in range(N):
fout.write(f'{col_names[i]:<15s}')
for j in range(p_dim):
fout.write(f'{Proj_mrg_coord[M+i][j]:<15.5f}')
fout.write('\n')
fout.close()
print('\n',
'Coord.txt', 'was saved.')
#
# 布置の描画
#
max_coord =
np.max(np.abs(Proj_mrg_coord))
dimx = 0
dimy = 1
while True:
plt.figure(figsize=(7,7))
# figsize = (5, 4))
plt.xticks([-max_coord,
max_coord])
plt.yticks([-max_coord,
max_coord])
plt.plot(Proj_mrg_coord.T[dimx][:M],
Proj_mrg_coord.T[dimy][:M], '^', c = 'g', label = 'Row')
plt.plot(Proj_mrg_coord.T[dimx][M:], Proj_mrg_coord.T[dimy][M:], 'v', c
= 'b', label = 'Col')
for i in range(M):
plt.text(Proj_mrg_coord.T[dimx][i], Proj_mrg_coord.T[dimy][i] - 0.05,
row_names[i], rotation = 0, fontsize = 14, color='g')
for i in range(N):
plt.text(Proj_mrg_coord.T[dimx][M + i], Proj_mrg_coord.T[dimy][M + i] +
0.05,
col_names[i], rotation = 0, fontsize = 14, color='b')
plt.plot([0], [0], '*', c =
'm', markersize = 12, label = 'Origin')
plt.plot([-max_coord,
max_coord], [0, 0], c = 'k', ls = ':')
plt.plot([0, 0],
[-max_coord, max_coord], c = 'k', ls = ':')
plt.legend()#fontsize = 14)
plt.title('Orthogonally
Rotated Configulation', fontsize = 16)
plt.xlabel(f'Axis-{dimx+1}',
fontsize = 14)
plt.ylabel(f'Axis-{dimy+1}',
fontsize = 14)
plt.tight_layout()
plt.savefig(f'FigMapRotatedAixes_{dimx+1}_{dimy+1}.png')
plt.show()
print('\n',
f'FigMapRotatedAixes_{dimx+1}_{dimy+1}.png', 'was saved.')
print(f'\nMaximum dimension
= {p}')
dimx = int(input('Dimension
for x-axis = '))
if dimx < 1:
break
if dimx > p:
dimx
= p
dimx -= 1
dimy = int(input('Dimension
for y-axis = '))
if dimy < 1:
break
if dimy > p:
dimy
= p
dimy -= 1