2次元配列データの特異値分解による内積表現
クロス表(対応表)は2次元配列データの特殊な場合(セル値が非負整数値)である。2次元配列データは、行列として特異値分解を適用することができる。特異値分解から内積による所与の行列の表現が導かれ、これによりデータの直感的把握、情報の視覚化が容易になる(岡本、2023)。特異値分解から内積表現の導き方、情報教育調査の分析例、色と象徴語の関係の分析例、セマンティックディファレンシャルデータの分析例の説明を行った後、内積表現の解釈について論じる。ファイルは、圧縮ファイルinner_product.zipにまとめている。
型行列のランクがであるとき、次のように特異値分解(singular value decomposition (SVD) (Magnus & Neudecker, 1999)ができる。
ここで、
であり、はランクの単位行列、は次式の対角行列である。
.
内積表現は、(1)式を次のように書き直すことにより得られる。
,
上式より
すなわち、次の内積表現を得る。
内積は、ベクトルとの長さの積にその挟む角のコサインを掛けたものである(Figure 1).
Figure 1 Vectors and , drawn from the origin .
式(2)のとは、座標の平均2乗和のバランスがとれるように決める、すなわち、
である。次式
より、
となり、次式を得る。
とが式 (2)を満たすことより、次式を得る。
これらのとに対して、次式が成り立つ。
布置の距離とデータの距離
データの第行をで表す。
すなわち、
である。このとき、ベクトルとの距離は次式で与えられる。
ここで、
=
である。すなわち、との距離は、座標ベクトルとの重み付距離で与えられる。.
列ベクトルとの場合も同様に考える。
と置けば、
より、次式を得る。
ここで、
=
である。列ベクトルとの距離は、布置ベクトルとの重み付距離で表される。.
布置の直交回転
内積は、布置の直交回転に対して不変であるので、得られた布置を見やすく直交回転することができる。
いま、Table 1のデータについて考える。行項目がR1,…,R3,であり、列項目がC1,…,C3.である。
Table 1 Frequencies for rows R1,..,R3 and
columns C1,…,C3
(artificial data). |
|||
|
C1 |
C2 |
C3 |
R1 |
10 |
1 |
0 |
R2 |
0 |
10 |
1 |
R3 |
0 |
0 |
10 |
Table 1のデータに対して、特異値分解を行い、布置を求めるとFigure 2のような布置を得る。
Figure 2
座標軸1上の値が重なっていて、座標軸1は項目間の差を表す情報が含まれていない。
Figure 2の布置を回転すると、Figure 3のようになる。
Figure 3
布置を回転することにより、座標軸1が項目間の差異を表すことが期待される。
直交回転を求めるために、求めた布置とをまとめてとする。
の主要な分散の方向を求めるために中心化する。
の分散を極大化する方向,…, を次式で与える。
これらの,…, は、次式を満たす(Timm, 1975)
すなわち、はの固有ベクトルであり、は固有値である (Magnus & Neudecker, 1999)。
次式
より、はの方向の分散に対応している。
分散の方向を最大(極大)にするベクトル,…, により、直交回転を次式で与える。
回転後の座標は次式で与えられる。
回転後の布置は、Figure 4のようになる。ベクトルの座標がプロットされているので、ベクトルは原点(星印)から座標点までの矢印で表される。ただし、矢印は描画されていない。
Figure 4
クラスター分析
内積を与えるベクトルの布置が得られると、クラスター分析を行うことができる。布置の座標に対してはWard法を適用することができる。この場合、ベクトルの向きと長さの観点からのクラスターが得られる。ベクトルの挟む角度に基づいて分析すると、向きのみの観点からのクラスター分析となる。
分析例
情報教育調査結果、色と象徴語の適合度データ、セマンティックディファレンシャルデータの分析例を示す。
データはCSV形式のファイルでプログラムファイルと同じフォルダ内に用意されているものとする。CSV形式のファイルについては、このウェブサイトで説明している。
分析は、リスト1に示すスクリプトで行った。実行は、Anaconda/Windows10のPython3.12.4仮想環境で行った。Windows10にインストールしたPython3.12.7で実行すると、モジュールjapanize_matplotlibに関係したエラーが出た(2024.10.19)。モジュールjapanize_matplotlibのimportを行わなければエラーは出ない。このとき、日本語を用いるときは、rcParamsを用いればよい。すなわち、
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#import japanize_matplotlib
from scipy.cluster.hierarchy import
linkage
from scipy.cluster.hierarchy import
dendrogram
plt.rcParams['font.family'] = 'Microsoft
Yahei' # フォント名'Microsoft Yahei'は、使用PC依存
とする。この方法を用いれば、Python 3.12.7でも実行可能であった(2024.10.19)。
ファイルは、圧縮ファイルinner_product.zipにまとめた。
入力データファイルはCSV形式のファイルの形式で用意する。図I.1は、政府統計ポータルサイトe-Statからダウンロードした「学校における教育の情報化の実態等に関する調査(令和3年度)」のファイルをリスト1のスクリプト用に編集したものである。
図I.1 「学校における教育の情報化の実態等に関する調査(令和3年度)」(e-Statより)を編集
1行目に列項目名、1列目に行項目名が設定されている。セル内の数値はPC(デバイス)の設置台数である。
リスト1のスクリプトを実行すると、入力データファイル名の設定が求められる。
図I.1のCSVファイル名情報調査結果.csvを設定する。
CSV File(*.csv)
= 情報調査結果.csv
入力ファイル名を設定すると計算が始まり、まず特異値分解の特異値のスクリープロットが表示される(図I.2)。
図I.2
布置の分布は、ほぼ1次元であることが分かる
図I.2のWindowを閉じると、図I.3のグラフが表示される。
図I.3
回転を求めるための固有分解の固有値のスクリープロットである。このスクリープロットから、布置はほぼ1次元空間に分布していることが分かる。
図I.3のWindowを閉じると、図I.4のマップが表示される。
図I.4
小学校が、iOS、Chrome、Windows 10と、中学校と高等学校がChrome、Windiows 10と近いことが表されている。
図I.4のWindowを閉じると、次に表示するマップの座標軸の設定が求められる。
0以下の値を設定すると、マップの表示が終わり、次に進む。
Maximum dimension = 7
Dimension for x-axis = 0
マップの表示が終わると、クラスター分析が行われる。まず、布置座標によるクラスター分析の結果が表示される(図I.5)。
図I.5
小学校、中学校、Chrome、iOS、Windows 10がクラスターとしてまとまっている。
図I.5のWindowを閉じると、図I.6のグラフが表示される。
図I.6
ベクトルの角度によるクラスター分析である。長さは無視される。小学校、中学校、Chrome、Windows 10、iOSがクラスターとしてまとまっている。高等学校がWindows 11とまとまっているが、これは角度によるクラスターリングなので、絶対数ではなく相対度数によるまとまりと考えられる。
図I.6のWindowを閉じると、スクリプトの実行終了である。
図C1に示すデータは、大山(1994)に説明されている色と象徴語の関係を、適合するものを1で、そうでないものを0で表した岡本(2014)の表14.1.1をCSV形式のファイルとしたものである。
図C1
図C1では、適合するかどうかが1と0で表されているが、データが適合すると答えた人の人数、あるいは適合度を100点満点で評価したときの平均値であれば、図C1の数値は0以上の実数となる。また、適合しないを負の数で表すデータも考えられる。いずれにせよ、1と0で適合するかしないかを表すという大まかなデータでどのような分析結果となるかは興味深いと思われる。
図C1のデータをリスト1のプログラムで分析する。
プログラムを実行すると、入力ファイル名の設定が求められる。
図C1のファイル名を設定する。
CSV File(*.csv) = CncptClr.csv
ファイル名を設定すると計算が始まる。
先ず、特異値のグラフ(スクリープロット)が表示される(図C2)。
図C2
3つ目までの特異値が大きいが、1番目の特異値はデータの全体的な様子を表すので、2番目と3番目の特異値が個々の変数の関係を表していると予想できる。
図C2のWindowを閉じると、図C3のグラフが表示される。
図C3
布置の回転を行うときの主成分分析の固有値のグラフである。1番目と2番目の固有値が大きいので、布置はほぼ2次元空間に配置されていると考えられる。
図C3のWindowを閉じると、回転後の布置が表示される(図C4)。
図C4
色が色相環に沿う形で配置され、それぞれの色に適合する象徴語が色に沿う形で配置されている。原点(星印)から色(列項目:下向き3角形)あるいは象徴語(行項目:上向き3角形)の位置までがベクトルを表す。
図C5のWindowを閉じると、次の描画における次元の設定が求められるので、次元の他の組み合わせにおける布置が得られる。次元として0以下の値を設定すると、クラスター分析に進む。
まず、座標値によるクラスター分析の結果が表示される(図C5)。
図C5
図C5のWindowを閉じると、角度によるクラスター分析の結果が示される(図C6)。
図C6
図C6のWindowを閉じると、プログラムは終了する。
図S1は、大山ら(2005)の図4-4に示されているセマンティック・ディファレンシャルのデータ(「幸福」のイメージに対する評定形容詞対の平均値)を読んで、CSV形式のファイルとしたものである。形容詞対は、対の一方のみを示している。
図S1 「幸福」のイメージに対する平均値
図S1のデータをリスト1のプログラムで分析する。
プログラムを実行すると、入力データファイル名の設定が求められる。
図S1のファイル名を設定する。
CSV File(*.csv) = SDHp.csv
入力データファイル名を設定すると、データは読み込まれて計算が始まる。
まず、特異値のグラフ(スクリープロット)が表示される(図S2)。
図S2
1番目と2番目の特異値が大きいので、データ配列はほぼ2次元の布置で表されると考えられる。
図S2のグラフを閉じると、固有値のスクリープロットが表示される(図S3)。
図S3
固有値のスクリープロットから、第1固有値と第2固有値が大きく、布置はほぼ2次元空間に配置されていると考えられる。図S1のデータの場合、データ値が正の値と負の値の量符号があるので、ベクトルの位置関係は同方向(内積が正)と反対方向(内積が負)の2通りがあるので、分布は原点を中心にして両方向に広がっていると考えられる。このため、特異値分解の特異値のスクリープロットの反映する次元数は固有分解の固有値のスクリープロットが反映する次元数と同じになっていると考えられる。
図S3のWindowを閉じると図S4の布置のグラフが表示される。
図S4
原点(星印)から国(行項目:上向き3角形)あるいは形容詞対(列項目:下向き3角形)の位置までがベクトルを表す。
「幸福」のイメージとして、「美しい」、「好きな」、「良い」、「明るい」、「陽気な」があげられ、「緊張した」とは反対のイメージであることが表されている。「日本」と「台湾」のグループと、「米国」と「セルビア」のグループにまとまっていて、「米国」と「セルビア」のグループでは「幸福」は「動的な」と「派手な」のイメージに近いが、「日本」と「台湾」ではそうではないことが表されている。
図S4のWindowを閉じると、次に表示する布置の次元の設定が求められる。
Dimension for x-axis = 0
0以下の数値を設定すると、布置の描画は終わり、クラスター分析が行われる。
まず、座標によるクラスター分析が行われる(図S5)。
図S5
日本と台湾が同じクラスタ―、米国とセルビアが同じクラスターにまとめられている。
図S5のWindowを閉じると、挟む角によるクラスター分析が行われる(図S6)。
図S6
図S6では、米国はセルビアから離れて別々のクラスターにまとめられている。
布置を、第1次元を横軸に、第3次元を縦軸にとって描いてみると、図S7のようになる。
図S7
「動的な」などに、米国とセルビアに違いがあるようである。
図S6のWindowを閉じると、プログラムの終了である。
特異値分解は、適用できる2次元配列に制約はない。そこから内積表現が導かれるが、これは数学的に表現されるということであり、心理学的に自然な解釈ができることとは別である。例えば、色と象徴語の適合度のデータにおいて、不適合である方に注目して不適合であれば1とコーディングした場合、不適合であればベクトルの内積は大きくなりベクトルは近付くという表現が予想されるが、これは不自然であろう。また、セマンティックディファレンシャル法では双極性の尺度を用いることが標準であるが、尺度値をー3から3までの数値で表すとき、合う関係は内積が正になりベクトルは近付き、合わないときは内積は負になりベクトルは反対方向に向くので、自然な解釈になる。しかし、これを尺度値として1から7までの数値を用いると、反対方向の関係が内積が0に近い値で表されるので不都合と考えられる。単極性の尺度のとき、その尺度に合う場合に7,関係がない場合に0を与えるときは、尺度に合うときは同じ向きになり、無関係のときは内積が0、すなわち直交関係になるので、自然な表現となる。
特異値分解とそこから導かれる内積表現はデータを選ばないが、得られた解の解釈はデータに依存している。
Borg, U. & Groenen, P. J. F. (2005) Modern multidimensional scaling: Theory and applications, second edition. Springer.
Greenacre, M. (2017) Correspondence analysis in practice, third edition. CRC Press.
Magnus, J. R. & Neudecker, H. (1999) Matrix differential calculus: with applications in statistics and econometrics, revised edition. John Wiley & Sons.
岡本安晴(2023)「クロス表のSVDおよび内積による表現と座標変換――グラフ図示による例示――」日本行動計量学会第51回大会発表.
https://doi.org/10.20742/pbsj.51.0_58
Okamoto, Y.(2024) Inner Product Representation of Two-dimensional Array of Data Derived from Singular Value Decomposition.
https://osf.io/ye5jf/ DOI 10.17605/OSF.IO/YE5JF
大山 正(1994) 色彩心理学入門――ニュートンとゲーテの流れを追って――.中公新書.
大山 正・岩脇三良・宮埜嘉夫(2005)「心理学研究法」サイエンス社
Timm, N. H. (1975) Multivariate analysis: with applications in education and psychology. Brooks/Cole Publishing Company.
"""
Yasuharu Okamoto, 2024.08,2024.10
"""
import pandas as pd
import numpy as np
import
matplotlib.pyplot as plt
import
japanize_matplotlib
from
scipy.cluster.hierarchy import linkage
from scipy.cluster.hierarchy
import dendrogram
#
Read CSV file
fl_nm = input('CSV
File(*.csv) = ')
df =
pd.read_csv(fl_nm, encoding='shift-jis')
# Names of
column items
col_names =
df.keys()
col_names =
col_names[1:]
#
Names of row items
row_names =
df.values.T[0]
#
# Values in
the two dimensional array data
#
df_data =
df.values
F =
df_data[:,1:]
data = np.array(F,
dtype=float)
print(data)
# Singular
Value Decomposition of data
u, s, vh = np.linalg.svd(data,
full_matrices = False)
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]
vh = vh[:p]
#
#
Scree plot of the singular values
#
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.show()
plt.close()
#
#
Weighting
#
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
#
#
Concatenation of the row and column coordinates
#
mrg_coord = np.concatenate((row_coord,
col_coord.T), axis = 0)
mrg_label =
np.concatenate((row_names, col_names), axis = 0)
#
#
Eigendecomposition with centering
#
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
# Scree plot of
the eigen values
plt.title('Scree Plot
for w', fontsize=16)
plt.plot(np.arange(len(w))+1,w,
c='k')
plt.xticks(np.arange(len(w))+1)
plt.ylabel('Eigen
value', fontsize=14)
plt.tight_layout()
plt.show()
plt.close()
#
# Rotation
of the coordinates
#
Proj = v.T
Proj_mrg_coord =
(Proj @ mrg_coord.T).T
#
# Output the
coordinates to the file Coord.txt
#
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.')
#
# Plot
of coordinates
#
max_coord =
np.max(np.abs(Proj_mrg_coord))
dimx = 0
dimy = 1
while True:
plt.close()
plt.figure(figsize=(7,7))
plt.xticks([-max_coord,
max_coord])
plt.yticks([-max_coord,
max_coord])
# Plot the row
items
plt.plot(Proj_mrg_coord.T[dimx][:M],
Proj_mrg_coord.T[dimy][:M],
'^', c = 'k', label = 'Row')
# Plot the column
itens
plt.plot(Proj_mrg_coord.T[dimx][M:], Proj_mrg_coord.T[dimy][M:],
'v', c = 'k', label = 'Col')
# Write the row
item names
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='k')
# Write the column
item names
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='k')
plt.plot([0], [0], '*', c =
'k', 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()
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.show()
#
# Selection of the
axes for the next plot
#
print(f'\nMaximum dimension
= {p}')
dimx = int(input('Dimension
for x-axis = '))
if dimx < 1: # End this plotting
break
if dimx > p:
dimx
= p
dimx -= 1
dimy = int(input('Dimension
for y-axis = '))
if dimy < 1: # End this plotting
break
if dimy > p:
dimy
= p
dimy -= 1
#
# Concatenate the row
and column coordinates for clustering
#
mrg_coord =
np.concatenate((row_coord, col_coord.T), axis = 0)
#
# Concatenate the row
and column names for clustering
#
mrg_label =
np.concatenate((row_names, col_names), axis = 0)
#
# Clustering
by the coordinates
#
row_clusters =
linkage(mrg_coord, method = 'ward')
fig =
plt.figure(figsize = (7, 5), facecolor = 'white')
row_dendr =
dendrogram(row_clusters, labels = mrg_label,
orientation = 'right', leaf_font_size = 14,
link_color_func = lambda k: 'k')
plt.title('Clustering
by Coordinates', fontsize=14)
plt.tight_layout()
plt.show()
#
# Calculate the
angle between two vectors
#
def calc_angle(x, y):
v = np.dot(x, y) /
((np.dot(x,x))*np.dot(y,y))**0.5
v1 = 1.0 if v > 1.0 else
-1.0 if v < -1.0 else v
return np.arccos(v1)
#
# Calculation of
angles between vectors of items
#
Angles =
np.empty((M+N, M+N))
for i in range(M+N):
for j in range(M+N):
if i
> j:
Angles[i][j]
= Angles[j][i]
elif
i == j:
Angles[i][j] = 0.0
else:
Angles[i][j] = calc_angle(mrg_coord[i], mrg_coord[j])
#
# Cluster
Analysis by angles
#
dists = []
for i in
range(len(Angles)-1):
for j in range(i+1, len(Angles)):
dists.append(Angles[i][j])
row_clusters =
linkage(dists, method = 'complete')
fig =
plt.figure(figsize = (7, 5), facecolor = 'white')
row_dendr =
dendrogram(row_clusters, labels = mrg_label,
orientation = 'right',
leaf_font_size = 14, link_color_func = lambda k: 'k')
plt.title('Clustering
by Angles', fontsize = 14)
plt.tight_layout()
plt.show()