カテゴリカルデータの主成分分析
単純2段法
質問項目がカテゴリカル変量である場合は多くみられる。カテゴリカル変量を量的変量に変換すると量的変量の分析法を用いることが出来て便利である。
カテゴリカル変量の数量化を行った後、それに基づいて主成分分析を行う方法を以下のように試みた。まず、考え方について簡潔に説明した後、分析例を示す。分析例では、村上(1999)に掲載されているTable 1の作曲家の特性データを利用した。
考え方
まず、カテゴリカル変量の数量化を行い(第1段)、それを利用して主成分分析を行う(第2段)。第1段の共通数量化後の第2段における分析として主成分分析以外も用いた例は岡本(2015a)に報告されている。
第1段(共通数量化)
共通数量化について簡単に説明する。詳しくは、Okamoto (2015b)を参照されたい。
カテゴリ変数が個のカテゴリ値をとるものとする。カテゴリ値を1からまでの自然数で表す。の番目(人目)のデータをで表す。を行列(ベクトル)として表せば
となる。
いま、カテゴリ変数の数量化変数をとおき、カテゴリに数値を与えるものとする。の番目の値に対するの値をとおけば、
のとき
である。
カテゴリ変数の番目の値に対してベクトルを
とおけば、式(1)は
と書ける。したがって、データ数が人分であるとき、
、 、
とおけば
と書ける。はダミー変数のように機能しており、indicator行列と呼ばれている。は数量化ベクトルである。
いま、数量化変数の分散が大きい程、カテゴリ変数に含まれる情報が数量化変数によく反映されていると考え、数量化ベクトルを、の長さが1の制約条件の下で数量化変数の分散を最大(極大)にするものとして求める。このは、次式を満たすものとして求められる(Okamoto, 2015b).
ここで
であり、はを要素とする型行列
である。
式(2)を満たすとは、固有値とそれに対する固有ベクトルと呼ばれているが(岡本、2009、pp. 41-45;岡本、2019、pp. 61-67)、式(2)の場合は、一般に0でない固有値を個もつ(Okamoto, 2015b)。式式(2)に対しての分散は
で与えられる。数量化変数の平均を0、分散を1に標準化した値を共通数量化変数とする。
である。
カテゴリ数のカテゴリ変数に対して、一般には個の数量化変数(式(3))が与えられるが、それらはお互いに独立である(Okamoto、2015b)。
第2段(主成分分析)
主成分分析については、岡本安晴(2014)、pp. 73−77、および岡本(2019)、pp. 115-129で説明している。
分析例を、村上(1999)に掲載されているTable 1のデータを用いて示す。用いたデータファイルとプログラムファイルは圧縮ファイルPCACatVarPy.zipにまとめた。ダウンロード展開すれば自由に利用できる。
村上(1999)のTable 1をExcelファイルデータとして図1のように用意した。
図1
図1のデータは、作曲家Beethoven、・・・、Stravinskyについて、3つの特性、生国、作品の性格、時期のカテゴリ値を示すものである。カテゴリ値は、カテゴリ変数のカテゴリ値がK個である場合は、1からKまでの整数値で表す。図1の場合は、以下のカテゴリ値である。
Var1: 生国; ドイツ・オーストリア1、フランス2、ロシア3
Var2: 作品の性格; 絶対音楽1、中間的2、表題・劇音楽3
Var3: 時期; 19世紀前半1、19世紀後半2、20世紀前半3
図1において2行目の数値は、各カテゴリ変数のカテゴリ値の総数を示している。図1の場合はすべて3個である。
Excelの表において、1行目に変数名、2行目にカテゴリ数を書き、データは3行目から書く。1列目は、各データの識別値、図1の場合は作曲者名が書かれている。
すなわち、
1行目に、変数のラベルを置く。
2行目に、各カテゴリ変数のカテゴリ数を設定する。1列目の列は、各データの識別名の変数であるので、カテゴリ数は書かずに空セルとする。
3行目以降に、データ値を書く。1列目はデータの識別名である。2列目以降に、各カテゴリー変数における選択カテゴリーの値を並べて書く。上のデータの場合、すべてカテゴリー数は3個である。カテゴリー値は、カテゴリー数がK個の場合は、1からKまでの整数値を用いる。カテゴリー値は名義尺度(もちろん、順序尺度でもよい)のものとして扱うので、どの選択カテゴリーにどの数値を対応させるかは任意である。
第1段 数量化
図1のデータに対して数量化を行う。プログラムをリスト1に示す。
リスト1のプログラムを実行すると、入力データファイルを聞いてくる。
Excel Input
Data File(*.xlsx) =
図1の形式で用意したExcelファイルを指定する。次の例では、図1のファイルDataMurakami.xlsxを指定している。
Excel Input Data File(*.xlsx) =
DataMurakami.xlsx
Output File (*.txt) = Results.txt
入力データファイル名を設定すると、次に出力データファイル名を聞いてくる。適当なテキストファイル名を設定する。上の例ではResults.txtと設定している。
出力ファイル名を設定すると、データが読み込まれて数量化の計算が始まる。
計算が終了すると、数量化の計算結果を格納するExcelファイル名の設定が求められる。
Excel File
Name (*.xlsx) = qdata.xlsx
上の例では、qdata.xlsxと設定している。
出力用のExcelファイル名を設定すると、数量化のデータがExcelファイルとして書き出されて、プログラムの終了となる。端末には以下の出力が表示されている。
Excel File Name (*.xlsx) = qdata.xlsx
qdata.xlsx was saved.
Results.txt was saved.
出力されたExcelファイルqdata.xlsxを開くと図2のようになっている。
図2
カテゴリ変数の数量化された値が変数名「カテゴリ変数名_0」などの値として表されている。各カテゴリ変数の数量化された変数は、カテゴリ変数名に「_0」などと「_」に数値を付けて区別している。
数量化において用いられたカテゴリ値の値(重み)は出力ファイルResults.txtに記されている。
出力ファイルResults.txtを開くと以下のようである。
Data file = DataMurakami.xlsx
data...
Data_ID Country Character period
0
NaN
3
3
3
1 Beethoven
1
1
1
2 Schubert
1
2
1
3
Wagner
1 3 2
4 Schoenberg
1
1
3
5
Berlioz
2
3
1
6
Bizet
2
3
2
7
Debussy
2
2
3
8 Tchaikovsky
3
2
2
9 Mussorgsky 3
3
2
10 Stravinsky
3
2
3
Country
G =
[[1. 0. 0.]
[1. 0. 0.]
[1. 0. 0.]
[1. 0. 0.]
[0. 1. 0.]
[0. 1. 0.]
[0. 1. 0.]
[0. 0. 1.]
[0. 0. 1.]
[0. 0. 1.]]
f =
[[4. 3. 3.]]
Lambda...
3.600 3.000
Omega...
0.816
0.000
-0.408
-0.707
-0.408
0.707
Country_0
Country_1
Character
G =
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]
[1. 0. 0.]
[0. 0. 1.]
[0. 0. 1.]
[0. 1. 0.]
[0. 1. 0.]
[0. 0. 1.]
[0. 1. 0.]]
f =
[[2. 4. 4.]]
Lambda...
4.000
2.400
Omega...
-0.000
0.816
-0.707
-0.408
0.707 -0.408
Character_0
Character_1
period
G =
[[1. 0. 0.]
[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]
[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]
[0. 1. 0.]
[0. 1. 0.]
[0. 0. 1.]]
f =
[[3. 4. 3.]]
Lambda...
3.600
3.000
Omega...
0.408
0.707
-0.816
0.000
0.408
-0.707
period_0
period_1
Quant. Vars...
Country_0
Country_1
Character_0 Character_1
period_0 period_1
Beethoven
1.22474
0.00000
-0.00000
2.00000 0.81650
1.29099
Schubert
1.22474 0.00000
-1.11803
-0.50000 0.81650
1.29099
Wagner
1.22474
0.00000
1.11803
-0.50000
-1.22474
0.00000
Schoenberg
1.22474
0.00000
-0.00000
2.00000 0.81650 -1.29099
Berlioz
-0.81650 -1.29099 1.11803
-0.50000 0.81650
1.29099
Bizet
-0.81650
-1.29099
1.11803
-0.50000
-1.22474
0.00000
Debussy
-0.81650 -1.29099
-1.11803
-0.50000
0.81650 -1.29099
Tchaikovsky
-0.81650
1.29099
-1.11803
-0.50000 -1.22474
0.00000
Mussorgsky
-0.81650
1.29099
1.11803
-0.50000 -1.22474
0.00000
Stravinsky -0.81650
1.29099
-1.11803 -0.50000
0.81650 -1.29099
カテゴリ値に対する数量化の値(重み)をまとめると以下の表のようになる。
Countryのカテゴリ値の重み
|
Country_0 |
Contry_1 |
ドイツ・オーストリア |
0.816 |
0.000 |
フランス |
-0.408 |
-0.707 |
ロシア |
-0.408 |
0.707 |
Characterのカテゴリ値の重み
|
Character_0 |
Character_1 |
絶対音楽 |
-0.000 |
0.816 |
中間的 |
-0.707 |
-0.408 |
表題・劇音楽 |
0.707 |
-0.408 |
Periodのカテゴリ値の重み
|
Period_0 |
Period_1 |
19世紀前半 |
0.408 |
0.707 |
19世紀後半 |
-0.816 |
0.000 |
20世紀前半 |
0.404 |
-0.707 |
数量化の結果は図2のExcelファイルにまとめられている。このExcelファイルのデータを次の主成分分析で分析する。
リスト1 数量化プログラム(quantification.py)
"""
Quantification of Categorical Data
Yasuharu Okamoto,
2023.08, 2023.11
"""
import pandas as pd
import numpy as np
fl_nm = input('Excel Input Data
File(*.xlsx) = ')
xlfile = pd.ExcelFile(fl_nm)
df = pd.read_excel(xlfile)
fout_nm = input('Output File (*.txt) =
')
fout = open(fout_nm, 'w')
fout.write(f'Data file = {fl_nm}\n')
print('DataFrame =\n', df)
fout.write(f'data...\n{df}\n')
case_id = df.values.T[0][1:]
var_names = np.array(df.keys()[1:])
var_types = df.values[0][1:]
X = df.values[1:,1:]
#
#
Quantification
#
genvars = []
genX = []
n = len(case_id)
for i in range(len(var_names)):
k = var_types[i]
G = np.zeros((n, k))
for j in range(n):
G[j][X[j][i] - 1] = 1
print('\n', var_names[i])
print('G =\n', G)
fout.write(f'\n{var_names[i]}')
fout.write(f'\nG =\n')
fout.write(f'{G}\n')
f = np.reshape(np.sum(G,
axis=0), (1, k))
print('f =\n', f)
fout.write(f'f =\n{f}\n')
H = G - np.ones((n,1)) @ f /
n
vals, vecs =
np.linalg.eig(H.T @ H)
idxsort = np.argsort(-vals)
vals = vals[idxsort]
vecs = vecs.T[idxsort].T
ck_k = np.sum([vals >
1.0e-9])
print('ck_k =', ck_k)
print(f'\n{var_names[i]}:')
print('\nLambda...')
for t in range(ck_k):
print(f'{vals[t]:>20.3f}', end = '')
print()
fout.write('Lambda...\n')
for t in range(ck_k):
fout.write(f'{vals[t]:>20.3f}')
fout.write('\n')
print('Omega...\n')
fout.write('Omega...\n')
for t in range(k):
for
j in range(ck_k):
print(f'{vecs[t][j]:>20.3f}', end = '')
fout.write(f'{vecs[t][j]:>20.3f}')
print()
fout.write('\n')
for t in range(ck_k):
v =
f'{var_names[i]}_{t}'
genvars.append(v)
fout.write(f"{v:>20s}")
lmbd
= vals[t]
omg
= np.reshape(vecs[:, t], (k, 1))
z =
H @ omg / ((lmbd/n)**0.5)
z =
np.reshape(z, len(z))
genX.append(z)
fout.write('\n')
genX = np.array(genX).T
fout.write('\nQuant. Vars...\n')
fout.write(f'{" ":>15s}')
for i in range(len(genvars)):
fout.write(f'{genvars[i]:>15s}')
fout.write('\n')
for i in range(len(genX)):
v = f'{case_id[i]:>15s}'
fout.write(v)
for v in genX[i]:
fout.write(f'{v:>15.5f}')
fout.write('\n')
QVars = {'case_id': case_id}
for i in range(len(genvars)):
QVars[genvars[i]] =
genX.T[i]
f_excel_nm = input('\nExcel File Name
(*.xlsx) = ')
with pd.ExcelWriter(f_excel_nm) as
writer:
df = pd.DataFrame(QVars)
df.to_excel(writer, index =
False)
print(f_excel_nm, 'was saved.')
fout.close()
print(f'\n{fout_nm} was saved.\n')
図2のデータに対して主成分分析を行う。図2の形で得られた数量化データは、主成分分析以外の分析にも使えるが、数量化後に主成分分析以外の分析を行った報告(岡本、2015a)もある。
主成分分析をリスト2に示すプログラムで行った。プログラムファイルなどはまとめて圧縮ファイルPCACatVarPy.zipとした。ダウンロード展開して自由に利用できる。
リスト2のプログラムを実行すると、入力データファイル名を聞いてくる。次の例では、図2のファイル名を設定している。
Excel Data
File(*.xlsx) = qdata.xlsx
入力データセット名を設定すると、次に出力データファイル名の設定が求められる。
Excel Data File(*.xlsx) = qdata.xlsx
Output Data file(*.txt) = Results.txt
出力ファイル名を設定すると、計算が始まる。
主成分分析は、次の特異値分解のコードに基づいて行われている。
U, S, Vh =
np.linalg.svd(genX/(n**0.5))
これは、岡本(2014)における式(5.2.2)に基づくものである。
上のコードによる特異値分解が終わると、まず、スクリープロットが表示される。
図3
図3より、主成分数として2ぐらいと判断してみる。
図3のフォームを閉じると、主成分分析の主成分の個数の設定が求められるので、2と設定してみる。
Number of
components = 2
主成分の個数2で計算が始まる。
まず、主成分の値を座標値とする散布図が表示される(図4)。
図4
図4は、第1主成分を横座標、第2主成分を縦座標とする散布図である。Beethoven、Schoenberg、Schubertのグループが左側に、Berlioz、Wagner、Bizet、Mussorgskyのグループが右上側に、Debussy、Tchaikovsky、Stravinskyのグループが右下にまとまっている。
図4のフォームを閉じると、次に描く散布図の横軸の主成分の設定が求められる。
X-Axis =
希望する主成分を設定すると、次に縦軸の主成分の設定が求められる。
主成分値の散布図の描画は終えて、次に進むときは、横軸の主成分として0以下の値を設定する。
X-Axis = 0
0以下の値を入力すると、次に構造行列の値(変数と主成分の相関係数)を座標値とする散布図が表示される(図5)。
図5
変数Contry_0は、カテゴリ値「ドイツ・オーストリア」に対して大きい値が与えられている。変数Character_1は、カテゴリ値「絶対音楽」に対して大きい値が与えられている。主成分comp_1は、負の値がカテゴリ値「ドイツ・オーストリア」およびカテゴリ値「絶対音楽」に対応していると考えられる。
変数Character_0は、カテゴリ値「表題・劇音楽」に対して大きい値が与えられている。主成分comp_2は、正の値がカテゴリ値「表題・劇音楽」に関係していると考えられる。
図5のフォームを閉じると、次の表示における主成分の設定が求められる。
X-Axis =
横軸(X-Axis)の主成分を設定すると、次に縦軸の主成分の設定が求められる。
横軸(X-Axis)の設定において、0以下の値を設定すると構造行列の描画が終了する。
構造行列の描画が終了すると、プログラムの実行終了である。
端末には、次のメッセージが表示される。
Results.txt
was saved.
ファイルResults.txtを開くと、以下のようになっている。
Data file = qdata.xlsx
Data =
Beethoven
1.225
0.000
-0.000
2.000
0.816
1.291
Schubert
1.225
0.000
-1.118
-0.500
0.816
1.291
Wagner
1.225 0.000 1.118 -0.500 -1.225 0.000
Schoenberg
1.225
0.000
-0.000
2.000
0.816 -1.291
Berlioz
-0.816 -1.291 1.118 -0.500 0.816 1.291
Bizet
-0.816 -1.291 1.118 -0.500 -1.225 0.000
Debussy
-0.816 -1.291 -1.118 -0.500 0.816 -1.291
Tchaikovsky
-0.816
1.291
-1.118
-0.500
-1.225 0.000
Mussorgsky
-0.816
1.291
1.118
-0.500
-1.225
0.000
Stravinsky
-0.816
1.291
-1.118 -0.500 0.816 -1.291
means =
[-4.44089210e-17 0.00000000e+00 -4.44089210e-17 0.00000000e+00
-3.33066907e-17 -1.55431223e-16]
sds =
[1. 1. 1. 1. 1. 1.]
Standardized Data =
Beethoven
1.225
0.000
-0.000
2.000
0.816 1.291
Schubert
1.225
0.000
-1.118
-0.500
0.816
1.291
Wagner
1.225 0.000 1.118 -0.500 -1.225 0.000
Schoenberg
1.225
0.000
-0.000
2.000
0.816 -1.291
Berlioz -0.816 -1.291 1.118 -0.500 0.816 1.291
Bizet
-0.816 -1.291 1.118 -0.500 -1.225 0.000
Debussy
-0.816 -1.291 -1.118 -0.500 0.816 -1.291
Tchaikovsky
-0.816
1.291
-1.118 -0.500 -1.225 0.000
Mussorgsky
-0.816
1.291
1.118
-0.500
-1.225
0.000
Stravinsky
-0.816
1.291
-1.118
-0.500
0.816 -1.291
Singular values...
[1.39311266 1.25179426 1.05662675
0.89510525 0.61414187 0.44430229]
Rank of genX = 6
n_comp = 2
Principal components...
comp_1
comp_2
Beethoven
-1.76034
0.67795
Schubert
-0.84579 -0.00930
Wagner
0.33143
1.00592
Schoenberg
-1.53814 -0.45344
Berlioz
0.16427
1.26576
Bizet
1.04611
1.08558
Debussy
0.09110 -1.06104
Tchaikovsky
0.95855 -0.91854
Mussorgsky
1.25392
0.27686
Stravinsky
0.29890 -1.86975
Structure matrix...
comp_1
comp_2
Country_0
-0.77829
0.24926
Country_1
0.15620 -0.49080
Character_0
0.25636
0.83772
Character_1
-0.82462
0.05613
period_0
-0.73281 -0.29594
period_1
-0.16702
0.68664
重みや主成分の値は、使用するライブラリにより符号が反転することがある。これはライブラリにより特異値分解における特異ベクトルの反転したものが得られることによる。いずれの解も正しい。
"""
Principal Components Analysis
Yasuharu Okamoto
"""
import pandas as pd
import numpy as np
import scipy.stats as ss
import matplotlib.pyplot as plt
fl_nm = input('Excel Data File(*.xlsx)
= ')
xlfile = pd.ExcelFile(fl_nm)
df = pd.read_excel(xlfile)
fout_nm = input('Output Data
file(*.txt) = ')
fout = open(fout_nm, 'w')
fout.write(f'Data file = {fl_nm}\n')
genvars = np.array(df.keys())[1:]
print('genvars =\n', genvars)
case_id = df.values.T[0]
n = len(case_id)
print('case_id =\n', case_id)
genX = np.array(df.values[:, 1:],
dtype = float)
print('genX =\n', genX)
fout.write('\nData =\n')
for i in range(n):
fout.write(f'{case_id[i]:>20}')
for v in genX[i]:
fout.write(f'{v:>10.3f}')
fout.write('\n')
fout.write('\n')
means = np.mean(genX, axis=0)
sds = np.std(genX, axis=0, ddof=0)
fout.write(f'\nmeans =\n{means}')
fout.write(f'\nsds =\n{sds}\n')
genX = (genX - means) / sds # Standardization
fout.write('\n\nStandardized Data
=\n')
for i in range(n):
fout.write(f'{case_id[i]:>20}')
for v in genX[i]:
fout.write(f'{v:>10.3f}')
fout.write('\n')
fout.write('\n')
#
#
Principal Components Analysis
# 式(5.2.2), 岡本安晴(2014)心理学データ分析と測定.勁草書房
#
U, S, Vh =
np.linalg.svd(genX/(n**0.5))
print('\nSingular values...\n', S)
fout.write(f'\nSingular
values...\n{S}\n')
plt.plot(range(len(S)), S)
plt.ylabel('Singular value')
plt.xticks(range(len(S)), np.arange(len(S))+1)
plt.show()
n_comp = int(input('Number of
components = '))
rnk_S = np.sum([S > 1.0e-9])
print('\nrnk_S =', rnk_S)
print('\nRank of genX =', rnk_S)
fout.write(f'\nRank of genX =
{rnk_S}\n')
if rnk_S < n_comp:
n_comp = rnk_S
print('\nn_comp =', n_comp)
fout.write(f'\nn_comp = {n_comp}\n')
C = U * (n**0.5)
# Principal Components
print('\nPrincipal components...')
fout.write('\nPrincipal
components...\n')
print(f'{" ":>15s}', end
= '')
fout.write(f'{" ":>15s}')
for i in range(n_comp):
v = f'comp_{i+1}'
print(f'{v:>15s}', end =
'')
fout.write(f'{v:>15s}')
print()
fout.write('\n')
for i in range(len(case_id)):
print(f'{case_id[i]:>15s}', end = '')
fout.write(f'{case_id[i]:>15s}')
for j in range(n_comp):
print(f'{C[i][j]:>15.5f}', end = '')
fout.write(f'{C[i][j]:>15.5f}')
print()
fout.write('\n')
max_c = np.max(C)
min_c = np.min(C)
if max_c < -min_c:
max_c = -min_c
if min_c > -max_c:
min_c = -max_c
dimx = 1
dimy = 2 if n_comp > 1 else 1
while True:
for i in
range(len(case_id)):
plt.plot(C[i][dimx-1], C[i][dimy-1], 'o', c = 'b')
plt.text(C[i][dimx-1], C[i][dimy-1], case_id[i])
plt.xlabel(f'comp_{dimx}')
plt.ylabel(f'comp_{dimy}')
plt.plot([min_c, max_c],
[0,0], lw = 1, c ='k', ls = ':')
plt.plot([0,0], [min_c,
max_c], lw = 1, c = 'k', ls = ':')
plt.show()
dimx = int(input('X-Axis =
'))
if dimx < 1:
break
if dimx > n_comp:
dimx
= n_comp
dimy = int(input('Y-Axis =
'))
if dimy < 1:
break
if dimy > n_comp:
dimy
= n_comp
Strctr = genX.T @ C / n
# Structure Matrix
dimx = 1
dimy = 2 if n_comp > 1 else 1
while True:
for i in
range(len(genX[0])):
plt.plot(Strctr[i][dimx-1],
Strctr[i][dimy-1], 'o', c = 'b')
plt.text(Strctr[i][dimx-1], Strctr[i][dimy-1], genvars[i])
plt.plot([-1, 1], [0,0], lw
= 1, c ='k', ls = ':')
plt.plot([0,0], [-1, 1], lw
= 1, c = 'k', ls = ':')
plt.xlabel(f'comp_{dimx}')
plt.ylabel(f'comp_{dimy}')
plt.show()
dimx = int(input('X-Axis =
'))
if dimx < 1:
break
if dimx > n_comp:
dimx
= n_comp
dimy = int(input('Y-Axis =
'))
if dimy < 1:
break
if dimy > n_comp:
dimy
= n_comp
print('\nStructure matrix...')
fout.write('\nStructure matrix...\n')
print(f'{" ":>15s}', end
= '')
fout.write(f'{" ":>15s}')
for i in range(n_comp):
v = f'comp_{i+1}'
print(f'{v:>15s}', end =
'')
fout.write(f'{v:>15s}')
print()
fout.write('\n')
for i in range(len(genvars)):
print(f'{genvars[i]:>15s}', end = '')
fout.write(f'{genvars[i]:>15s}')
for j in range(n_comp):
print(f'{Strctr[i][j]:>15.5f}', end = '')
fout.write(f'{Strctr[i][j]:15.5f}')
print()
fout.write('\n')
fout.close()
print(f'\n{fout_nm} was saved.\n')
fout.close()
村上 隆(1999)カテゴリカル・データの主成分分析の心理計量学的研究.科学研究費補助金(課題番号09610114)研究成果報告書.
岡本安晴(2015a)2段共通数量化分析法による分析例.日本行動計量学会大会抄録集、43、298-301.
岡本安晴(2017)共通数量化の応用例.日本女子大学大学院人間社会研究科紀要、23、69-82.
岡本安晴(2019)いまさら聞けないPythonでデータ分析.丸善出版.