双対尺度法
分割表の分析
表1(大山「色彩心理学入門」、1994、pp. 220-222 における記述を基に分割表の形にまとめ直したもの)のような分割表を双対尺度法(<岡本安晴「心理学データ分析法と測定」勁草書房、第14章 質的データの可視化:双対尺度法>で説明している)によって分析するPythonスクリプトを作成した。双対尺度法により「怒り」などの行項目と「赤」などの列項目に数値が割り当てられ、この数値を座標値として行項目と列項目の関係をビジュアルに表すことができる(図1)。
| 
   表1 象徴語と色の適合関係。適合するときを1、適合しないときを0で表す。  | 
 ||||||||||||||
| 
   | 
  
   赤  | 
  
   橙  | 
  
   黒  | 
  
   紫  | 
  
   灰  | 
  
   青紫  | 
  
   白  | 
  
   緑味青  | 
  
   ピンク  | 
  
   黄橙  | 
  
   青  | 
  
   緑  | 
  
   黄緑  | 
  
   黄  | 
 
| 
   怒り  | 
  
   1  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
 
| 
   嫉妬  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
 
| 
   罪  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
 
| 
   永遠  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
 
| 
   幸福  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
 
| 
   孤独  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
 
| 
   平静  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   0  | 
 
| 
   郷愁  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   1  | 
  
   1  | 
  
   1  | 
  
   0  | 
 
| 
   家庭  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
 
| 
   愛  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
 
| 
   純潔  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
 
| 
   夢  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   1  | 
 
| 
   不安  | 
  
   0  | 
  
   0  | 
  
   1  | 
  
   1  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
 
| 
   恐怖  | 
  
   1  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   1  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
  
   0  | 
 

図1
表1のデータは、図2の形式で入力データファイルとして用意している。

図2
スラッシュ / で始まる行の次の行に、行概念(angerからfearまで)の数と列概念(redからyellowまで)の数を書く。
次の行に、列概念のラベルを並べる。
その次の行から、各行に行概念のラベルとそれに続けてクロス表の頻度値を書いていく。表1と図2の対応が確認できる。
プログラムはこのウェブサイトの後半にメインスクリプトを載せてあるが、プログラムファイルとサンプルデータファイルをまとめて圧縮ファイルPrgsDS.zipとしても用意した。ファイル名をPrgsDS.zipクリックすればダウンロードできる。
プログラムを実行すると、まず、入力データファイル名の設定が求められる(図3)。

図3
入力データファイル名を設定すると、次に計算結果の出力用テキストファイル名の設定が求まられるので、適当なテキストファイル名を設定する(図4)。

図4
ファイル名の設定の次に、解の次元数が求められる。双対尺度法の解は、次元数を増やしても次元数が小さいときの解は影響を受けないので、多い目に設定しておけばよい。設定した次元数が大きすぎるときは、プログラムの実行時に調整される。
次元数を設定すると計算が始まり、計算が終了すると解の描画のx軸とy軸の次元の設定が求められる(図5)。

図5
x軸とy軸の次元を設定すると、それに対応して解の布置が描画される(図6)。なお、双対尺度法の解は、相関係数を極大にするという条件で求められており、各次元のペアの解は同時に方向を逆転したものも解になる。どちらの方向の解が与えられるかは、プログラムのアルゴリズムに依存している。いずれの解も正しい解である。

図6
次元の値が大きすぎるときは最大値に調整される。
右上のX印をクリックすると描画フォームは閉じられる。
次の描画用x軸とy軸の次元の設定が求められる(図7)。

図7
希望する次元を設定すれば、それに対応した図6のようなマップが表示される。次元数として0以下の値を設定するとプログラムの実行終了となる(図8)。

図8
プログラムの実行終了後、出力ファイルを開くと、図2のデータの場合、以下のようになっている。
Input data file =
Data.txt
n_dim = 2
Input Data...
LabelCol...
 red orange black purple gray violet white
grnble pink yorange blue green ygrn yellow
LabelRow and F-data
anger       1  1 
1  0  0 
0  0  0 
0  0  0 
0  0  0
jealousy    1  1 
0  1  0 
0  0  0 
0  0  0 
0  0  0
sin        
0  0  1 
0  1  1 
0  0  0 
0  0  0 
0  0
eternity    0  0 
0  0  0 
0  1  1 
0  0  1 
0  0  0
happy       0  1 
0  0  0 
0  0  0 
1  1  0 
0  0  0
lonely      0  0 
1  0  1 
0  0  0 
0  0  1 
0  0  0
calm       
0  0  0 
0  0  0 
0  1  0 
0  1  1 
0  0
nostalgia   0  0 
0  0  0 
0  0  0 
0  1  1 
1  1  0
home       
0  1  0 
0  0  0 
0  0  1 
1  0  0 
0  0
love       
1  1  0 
0  0  0 
0  0  1 
0  0  0 
0  0
purity      1  0 
0  0  0 
0  1  1 
0  0  0 
0  0  0
dream       0  0 
0  0  0 
0  0  1 
1  0  0 
0  0  1
anxiety     0  0 
1  1  1 
0  0  0 
0  0  0 
0  0  0
fear       
1  0  1 
0  1  0 
0  0  0 
0  0  0 
0  0
              
sq-cor-ratio       
coorelation             
Prop.     
Cum-Prop.
1:            
0.79550            
0.89191                
22.77%        
22.77%
2:         
   0.69651            
0.83457                
19.94%        
42.71%
3:            
0.58080            
0.76210                
16.63%        
59.34%
4:            
0.44823            
0.66950                
12.83%        
72.17%
5:            
0.31479             0.56106                 
9.01%        
81.18%
6:            
0.20005            
0.44727                 
5.73%        
86.91%
7:            
0.18970            
0.43554                 
5.43%        
92.34%
8:            
0.14835            
0.38516                 
4.25%        
96.59%
9:            
0.06270            
0.25041                 
1.80%        
98.38%
10:           
0.04255            
0.20628                 
1.22%        
99.60%
11:           
0.01388            
0.11781                 
0.40%       
100.00%
Row categories
anger            
0.64729  -0.61905
jealousy         
0.58478  -0.93674
sin              
1.79765   0.82609
eternity        
-0.98094   1.27341
happy        
   -0.54496  -1.52207
lonely           
0.78469   0.95314
calm            
-1.13363   1.35036
nostalgia       
-1.14923   0.78936
home            
-0.54496  -1.52207
love            
-0.08410  -1.36867
purity          
-0.57312   0.54473
dream           
-1.08906  -0.52765
anxiety          
1.47619   0.27158
fear             
1.19250   0.22445
Column categories
red              
0.39631  -0.51650
orange           
0.01301  -1.43033
black            
1.32263   0.39690
purple           
1.15537  -0.39850
gray             
1.47186   0.68156
violet           
2.01552   0.98983
white           
-0.87121   1.08926
grnble          
-1.05862   0.79108
pink            
-0.63434  -1.47993
yorange         
-0.83685  -0.90057
blue            
-0.69490   1.30793
green           
-1.27977   1.28192
ygrn            
-1.28851   0.94582
yellow          
-1.22104  -0.63224
Row categories
(weighted)
anger            
0.57732  -0.51664
jealousy         
0.52156  -0.78178
sin              
1.60334   0.68943
eternity         -0.87491   1.06276
happy           
-0.48606  -1.27028
lonely           
0.69986   0.79547
calm            
-1.01110   1.12698
nostalgia       
-1.02501   0.65878
home            
-0.48606  -1.27028
love            
-0.07501  -1.14225
purity         
 -0.51117   0.45461
dream           
-0.97133  -0.44037
anxiety          
1.31662   0.22665
fear             
1.06360   0.18732
Column categorie
(weighted)s
red              
0.35347  -0.43106
orange           
0.01161  -1.19372
black            
1.17966   0.33124
purple           
1.03048  -0.33258
gray             
1.31276   0.56881
violet           
1.79765   0.82609
white           
-0.77703   0.90907
grnble          
-0.94419   0.66021
pink            
-0.56577  -1.23511
yorange         
-0.74639  -0.75159
blue            
-0.61978   1.09157
green           
-1.14143   1.06986
ygrn            
-1.14923   0.78936
yellow          
-1.08906  -0.52765
これらの出力の解釈は、岡本安晴「心理学データ分析と測定」の「第14章 質的データの可視化:双対尺度法」を参照されたい。
プログラムのPythonスクリプトのメイン部は以下のようになっている。変数名は岡本安晴「心理学データ分析と測定」の「第14章 質的データの可視化:双対尺度法」における説明に合わせてある。
import numpy as np
import matplotlib as
plt
from mySVD import
my_svd
from PlotDS import
plot_ds
#       Input Data
File Name
fn_in =
input("Input data file = ")
f_in = open(fn_in,
"r")
data_f =
f_in.readlines()
f_in.close()
#       Output
File Name
fn_out =
input("Output data file = ")
f_out = open(fn_out,
"w")
f_out.write("Input
data file = " + fn_in + "\n\n")
n_dim =
int(input('\nNumber of dimensions = '))
f_out.write('n_dim =
{}\n'.format(n_dim))
pos = 0
while True:
    if (len(data_f[pos]) >
0):
        if
data_f[pos][0] == '/':
           
break
    pos += 1
pos += 1
s =
data_f[pos].split()
n_row = int(s[0])
n_col = int(s[1])
print('\nn_row =',
n_row, '   n_col =', n_col)
pos += 1
s =
data_f[pos].split()
LabelCol = []
for v in s:
    LabelCol.append(v)
pos += 1
LabelRow = []
F = []
for i in range(n_row):
    F.append([])
    s = data_f[pos].split()
    LabelRow.append(s[0])
    for j in range(n_col):
       
F[i].append(int(s[j + 1]))
    pos += 1
f_out.write('\nInput
Data...\n')
f_out.write('\nLabelCol...\n')
for i in range(n_col):
    f_out.write('
{}'.format(LabelCol[i]))
f_out.write('\n\nLabelRow
and F-data\n')
for i in range(n_row):
   
f_out.write('{0:<10s}'.format(LabelRow[i]))
    for j in range(n_col):
       
f_out.write('{0:3d}'.format(F[i][j]))
    f_out.write('\n')
fc = []
for j in range(n_col):
    sum = 0
    for i in range(n_row):
        sum
+= F[i][j]
    fc.append(sum)
ft = 0
for j in range(n_col):
    ft += fc[j]
fr = []
for i in range(n_row):
    sum = 0
    for j in range(n_col):
        sum
+= F[i][j]
    fr.append(sum)
Dc_invsqrt =
np.zeros((n_col, n_col))
for j in range(n_col):
    Dc_invsqrt[j, j] = 1.0 /
(fc[j] ** 0.5)
Dr_invsqrt =
np.zeros((n_row, n_row))
for i in range(n_row):
    Dr_invsqrt[i, i] = 1.0 /
(fr[i] ** 0.5)
Fmat = np.matrix(F)
G = Dr_invsqrt * Fmat
* Dc_invsqrt
rnk, Lambda, v, u =
my_svd(G)
if (rnk - 1) <
n_dim:
    n_dim = rnk - 1
    f_out.write('\nrank of G =
{0}\n'.format(rnk))
    f_out.write('n_dim was set
to be {0}\n'.format(n_dim))
if rnk <= 1:
    print('\nRank of G is less
than 2')
else:
    cor = []
    corr_sqr = []
    cum_corr_sqr = []
    sum = 0.0
    for i in range(1, rnk, 1):
       
cor.append(Lambda[i])
       
corr_sqr.append(Lambda[i]**2)
        sum
+= Lambda[i] ** 2
       
cum_corr_sqr.append(sum)
   
f_out.write('\n{0:<15}{1:<20}{2:<15}{3:>15}{4:>15}\n'.\
               
format(' ', 'sq-cor-ratio', \
                      
'coorelation', 'Prop.', 'Cum-Prop.'))
    for i in range(rnk - 1):
       
f_out.write('{0:<15}{1:<20.5f}{2:<15.5f}{3:>15}{4:>15}\n'.\
                   
format(('{0}:'.format(i + 1)),\
                          
corr_sqr[i], cor[i],\
                          
('{0:.2f}%'.format(100.0 * corr_sqr[i] / cum_corr_sqr[rnk - 2])),\
                          
('{0:.2f}%'.format(100.0 * cum_corr_sqr[i] / cum_corr_sqr[rnk - 2]))))
    for t in range(n_dim):
        for
j in range(n_col):
           
u[t + 1][j] *= ft ** 0.5
        for
i in range(n_row):
           
v[t + 1][i] *= ft ** 0.5
    x = []
    y = []
    x_coord = []
    y_coord = []
    for t in range(n_dim):
       
x.append([])
       
x_coord.append([])
        for
j in range(n_col):
           
x[t].append(Dc_invsqrt[j, j] * u[t + 1][j])
           
x_coord[t].append(Lambda[t + 1] * Dc_invsqrt[j, j] * u[t + 1][j])
       
y.append([])
       
y_coord.append([])
        for
i in range(n_row):
           
y[t].append(Dr_invsqrt[i, i] * v[t + 1][i])
           
y_coord[t].append(Lambda[t + 1] * Dr_invsqrt[i, i] * v[t + 1][i])
    f_out.write('\n\nRow
categories\n')
    for i in range(n_row):
       
f_out.write('{0:<15}'.format(LabelRow[i]))
        for
t in range(n_dim):
           
f_out.write('{0:>10.5f}'.format(y[t][i]))
       
f_out.write('\n')
    f_out.write('\n\nColumn
categories\n')
    for j in range(n_col):
       
f_out.write('{0:<15}'.format(LabelCol[j]))
        for
t in range(n_dim):
           
f_out.write('{0:>10.5f}'.format(x[t][j]))
       
f_out.write('\n')
    f_out.write('\n\nRow
categories (weighted)\n')
    for i in range(n_row):
       
f_out.write('{0:<15}'.format(LabelRow[i]))
        for
t in range(n_dim):
           
f_out.write('{0:>10.5f}'.format(y_coord[t][i]))
       
f_out.write('\n')
        
    f_out.write('\n\nColumn
categorie (weighted)s\n')
    for j in range(n_col):
       
f_out.write('{0:<15}'.format(LabelCol[j]))
        for
t in range(n_dim):
           
f_out.write('{0:>10.5f}'.format(x_coord[t][j]))
       
f_out.write('\n')
    v_scale = 0.0
    for t in range(n_dim):
        for
i in range(n_row):
        
   if v_scale <
abs(y_coord[t][i]):
               
v_scale = abs(y_coord[t][i])
        for
j in range(n_col):
           
if v_scale < abs(x_coord[t][j]):
               
v_scale = abs(x_coord[t][j])
    v_scale *= 1.3
    if v_scale < 1.3:
        v_scale
= 1.3                             
    while True:
       
dim_x = int(input('\ndim-x = '))
        if
dim_x < 1:
           
break
       
dim_y = int(input('dim-y = '))
        if
dim_y < 1:
           
break
       
plot_ds( v_scale, dim_x, dim_y, n_dim, x_coord, LabelCol, \
                
n_col, y_coord, LabelRow, n_row, "Bi-Plot")
f_out.close()
print('\n', fn_out,
'was saved.')