「入門はじめての多変量解析」をPythonで写経 Vol.29 ~ 8章「はじめての数量化Ⅱ類」数量化Ⅱ類と判別スコア
8章「はじめての数量化Ⅱ類」
書籍の著者 石村貞夫 先生、石村光資郎 先生
書籍「入門はじめての多変量解析」8章「はじめての数量化Ⅱ類」の Python写経活動記録 です。
多変量解析の入門を Python と一緒に学ぶ写経シリーズです。
この記事は、数量化Ⅱ類によるアンケート分析を実践します。
数量化Ⅱ類は「説明変数と目的変数が質的変数」のデータに対応します。
外的基準(目的変数)の判別(分類)を予測する「判別式」を推定します。
ChatGPT 活用型学習で進めてまいります!
では書籍を開いて多変量解析の旅に出かけましょう🚀

はじめに
このブログシリーズは、書籍「入門はじめての多変量解析」(東京図書、「テキスト」と呼びます)の Python 写経を通じて得た「多変量解析の楽しさ」をご紹介します。
書籍の紹介と引用表記はリンク先の記事に掲載しています。
8章 はじめての数量化Ⅱ類
この記事は8章の以下のSectionを取り扱います。
8.1 判別に役立つ数量化Ⅱ類
8.2 数量化Ⅱ類の判別式の求め方
8.3 数量化Ⅱ類の判別式の利用法
8.4 カテゴリ数量の基準化
8.5 数量化Ⅱ類で・・・を当てよう
記事に用いるデータは、テキストに掲載されたデータそのものを引用しています。
データ件数の少ないものはコード上でデータを登録し、データ件数の多いものはCSVファイル化してデータを読み込みしています。
この記事で用いるライブラリをインポートします。
### インポート
# 数値計算
import numpy as np
import pandas as pd
import sympy
# LaTeX表示
from IPython.display import Math
# 描画
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.family'] = 'Meiryo' # または import japanize_matplotlib
数量化理論と数量化Ⅱ類
数量化理論について、テキストは「アンケート調査の回答のような質的データに対しても、最適な数量や評点を与えて分析しようとするのが、数量化理論です」と説明します。
テキストの例題データはすべて、アンケート・回答形式になっています。
数量化理論はざっくり、説明変数などに含まれる質的変数(カテゴリ変数)を数値化して分析を進める手法 です。
アンケートの回答選択肢「はい、いいえ」や「そう思わない、どちらとも言えない、そう思う」などは質的変数です。
これまでの記事で実践した解析手法と比較すると…
$$
\begin{array}{lll}
目的変数 & 数量化理論 & 類似解析手法 \\
\hline
\\
量的変数 & 数量化Ⅰ類 & 重回帰分析 \\
質的変数 & 数量化Ⅱ類 & 判別分析 \\
なし & 数量化Ⅲ類 & 主成分分析 \\
\end{array}
$$
数量化Ⅱ類に「説明変数・目的変数が質的変数の場合の判別分析」を重ねるとイメージしやすいかもしれません。

■ 数量化理論の用語
例題データを見ながら、数量化理論の用語を確認しましょう。
テキスト p.257 表 8.1.1 の例題データを引用します。
### アンケート調査の結果 p.257 表8.1.2
# データフレームの作成
data1 = pd.DataFrame(
np.array([['A', 'A', 'A', 'A', 'B', 'B', 'B'],
[0, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 0, 0, 0, 0],
[1, 0, 1, 0, 0, 1, 0],
[0, 1, 0, 1, 1, 0, 1]]).T,
columns=[['血液型', '楽天家', '楽天家', '堅実派', '堅実派'],
['', 'はい', 'いいえ', 'はい', 'いいえ']],
index=['A1', 'A2', 'A3', 'A4', 'B1', 'B2', 'B3'],)
data1.index.name = '被験者No.'
data1[data1.columns[1:]] = data1[data1.columns[1:]].astype(int)
data1【実行結果】

数量化理論の用語を一覧化します。
$$
\begin{array}{lll}
数量化理論の用語 & 言い換え & 例題データ \\
\hline
\\
外的基準 & 目的変数 & 血液型\\
アイテム & 説明変数 & 楽天家・慎重派 \\
カテゴリ & カテゴリ値 & はい・いいえ \\
カテゴリ数量 & 係数 & (a_0, a_{11}, a_{12}, a_{21}. a_{22}) \\
\end{array}
$$

数量化Ⅱ類の判別式
外的基準(血液型): $${Y}$$
アイテム $${i}$$ のカテゴリ $${j}$$ のダミー変数: $${x_{ij}}$$
取りうる値は $${0}$$ か $${1}$$
アイテム $${i}$$ のカテゴリ $${j}$$ のカテゴリ数量: $${a_{ij}}$$
とすると、数量化Ⅱ類の判別式は次の1次式で表現できます。
📊 数量化Ⅱ類の判別式(アイテムは2つ、カテゴリは各2つ)
$$
Y = a_{11} x_{11} + a_{12} x_{12} + a_{21} x_{21} + a_{22} x_{22} + a_0
$$
係数に記号 $${a}$$ を用いるのは、おそらく、線形判別関数の係数 $${a}$$ と合わせる意図があるように感じます。
また、判別式 $${Y}$$ にデータ $${x_{ij}}$$を代入した値を 判別スコア と呼びます。
例題データの列見出しを $${y}$$、$${x_{ij}}$$ に変換しましょう。
### 判別スコアと平均 p.258 表8.2.1
data1_cat = data1.copy()
data1_cat.columns = ['y', 'x11', 'x12', 'x21', 'x22']
data1_cat【実行結果】


基準化前のカテゴリ数量を求める
■ $${\eta^2=S_W/S_T}$$ の最大化
線形判別分析(線形判別関数)では、判別得点の全変動 $${S_T}$$ と グループ間変動 $${S_B}$$ の比 $${S_B/S_T}$$ を最大化する係数 $${a_i}$$ を求めました。
数量化Ⅱ類の場合は、判別スコアの全変動とグループ間変動の比=相関比 $${\eta^2 = S_B / S_T}$$ を最大化する係数を求めます。
なお $${S_W}$$ はグループ内変動です。
テキストの例題データに関する変動は以下の式で示されます。
テキストの数式をお借りします。
$$
\begin{align*}
S_T &= \sum_{i=1}^4 (Y_i^{(1)} - \bar{Y})^2 + \sum_{i=1}^3 (Y_i^{(2)} - \bar{Y})^2 \\
\\
S_B &= 4 (\bar{Y}^{(1)} - \bar{Y})^2 + 3 (\bar{Y}^{(2)} - \bar{Y})^2 \\
\\
S_W &= \sum_{i=1}^4(Y_i^{(1)} - \bar{Y}^{(1)})^2 + \sum_{i=1}^3(Y_i^{(2)} - \bar{Y}^{(2)})^2 \\
\end{align*}
$$
$${Y}$$ に関する記号の一覧です。
$${\bar{Y}}$$:判別スコアの全平均
$${\bar{Y}^{(1)}}$$:判別スコアのグループ1平均
$${\bar{Y}^{(2)}}$$:判別スコアのグループ2平均
$${Y_i^{(1)}}$$:グループ1の $${i}$$ 番目の外的基準(目的変数)
$${Y_i^{(2)}}$$:グループ1の $${i}$$ 番目の外的基準(目的変数)

🖥️ Python で解く
ここからは sympy ライブラリを利用して、次の4ステップで「基準化前のカテゴリ数量」を推定します。
$${S_T, S_B}$$ の算出
$${\eta^2}$$ の算出
$${\eta^2}$$ を $${a_{12}, a_{22}}$$ で偏微分して0と置く
条件式 $${a_{12}^2 + a_{22}^2 = 1}$$ を加えた連立方程式を求解

1️⃣ $${S_T, S_B}$$ の算出
テキスト p.259 の数式に相当します。
$${a_{11}=0, a_{21}=0}$$ とおいています。
### 数量化Ⅱ類の予測式の求め方 p.259~
# sympy利用
# 変数の定義
a0, a12, a22 = sympy.symbols('a0 a12 a22')
# 判別スコアの算出 ※a11=0, a21=0の代入を反映
Y1 = [a12*x12 + a22*x22 + a0 for _, x12, _, x22
in data1_cat[data1_cat['y']=='A'].iloc[:, 1:].values]
Y2 = [a12*x12 + a22*x22 + a0 for _, x12, _, x22
in data1_cat[data1_cat['y']=='B'].iloc[:, 1:].values]
# 判別スコアの平均の算出
Y1_bar = sum(Y1) / len(Y1)
Y2_bar = sum(Y2) / len(Y2)
Y_bar = sum(Y1 + Y2) / len(Y1 + Y2)
# 全変動ST, グループ間変動SBの算出
ST = sum([(y1 - Y_bar)**2 for y1 in Y1]) + sum([(y2 - Y_bar)**2 for y2 in Y2])
SB = (Y1_bar - Y_bar)**2 * len(Y1) + (Y2_bar - Y_bar)**2 * len(Y2)
display(Math(f'S_T = {sympy.latex(ST.simplify().factor())}'))
display(Math(f'S_B = {sympy.latex(SB.simplify())}'))【実行結果】


2️⃣ $${\eta^2}$$ の算出
テキスト p.259 の数式に相当します。
## η2の算出
η2 = SB / ST
display(Math(f'\eta^2 = \cfrac{{S_B}}{{S_T}} = {sympy.latex(η2.factor())}'))【実行結果】


3️⃣ $${\eta^2}$$ を $${a_{12}, a_{22}}$$ で偏微分して0とおく
テキスト p.260 の数式に相当します、言いたいところですが…
偏微分結果に $${\eta^2}$$ を入れ込むことができませんでした。
このあたりから、テキストの計算方法と違う道(未知?)を進み始めます。
## η2をa12, a22で偏微分
expr_a12 = sympy.diff(η2, a12)
expr_a22 = sympy.diff(η2, a22)
display(Math(f'\cfrac{{\partial \eta^2}}{{\partial a_{{12}}}}'
f' = {sympy.latex(expr_a12.factor())} = 0'))
display(Math(f'\cfrac{{\partial \eta^2}}{{\partial a_{{22}}}}'
f' = {sympy.latex(expr_a22.factor())} = 0'))【実行結果】


4️⃣ 条件式 $${a_{12}^2 + a_{22}^2 = 1}$$ を加えた連立方程式を求解
とにかく連立方程式を解きます。
その後、各解を用いて $${\eta^2}$$ を算出します。
## 連立方程式を解く
# 条件式の追加
cond = a12**2 + a22**2 - 1
# 連立方程式の計算
solved = sympy.solve([expr_a12, expr_a22, cond])
# 連立方程式の解の表示 ★テキストと違うアプローチです…(適切なのか不明です。)
# 解から選択 2番目にしよう: η2が最大、かつ、値が正
for i, dic in enumerate(solved, 1):
print(f'【解 {i}】')
subs_list = []
for k, v in dic.items():
subs_list.append((k, float(v)))
display(Math(f'{sympy.latex(subs_list[0][0])} = {subs_list[0][1]},'
f'\quad {sympy.latex(subs_list[1][0])} = {subs_list[1][1]}'))
display(Math(f'\eta^2 = {sympy.latex(η2.subs(subs_list).simplify())}'))
print('-'*50)【実行結果】
4つの解が得られました…。
最大の $${\eta^2}$$ で係数が正の値の「解2」を採用しましょう!(勘)

選択した「解2」の計算結果を取り出します。
## 選択した解 a12, a22の取得
vals = [v for v in solved[1].values()]
a12 = float(vals[0])
a22 = float(vals[1])
display(Math(f'a_{{12}} = {a12}, \quad a_{{22}} = {a22}'))【実行結果】
カテゴリ数量 $${a_{12}, a_{22}}$$ が求まりました。


5️⃣ 切片 $${a_0}$$ の算出
テキスト p.261 の $${a_0}$$ 値に相当します。
## a0の算出
a0 = -a12**2 * data1_cat['x12'].mean() - a22**2 * data1_cat['x22'].mean()
display(Math(f'a_0 = {a0}'))【実行結果】


6️⃣ 判別式の表示
テキスト p.261 の判別式 $${Y}$$ の完成です。
## 判別式Y p.261
display(Math(f'Y = {a12:.4f} x_{{12}} {a22:+.4f} x_{{22}} {a0:+.4f}'))【実行結果】

今回はなんとか方程式を解くことができました(勘をふくめて)。
ところで、このままでは $${x_{11}, x_{21}}$$ に対応するカテゴリ数量 $${a_{11}, a_{21}}$$ が 0 のままです…
カテゴリ数量の基準化を通じて、判別式を仕上げていきましょう!
と行きたいところですが…
その前に、基準化前のカテゴリ数量を用いてテキストの「判別式の利用法」を解きます。

カテゴリ数量基準化前の判別式の活用
■ 判別スコアの算出(学習データ版)
ここまでで作成できた判別式(カテゴリ数量は基準化前)で判別スコアを算出しましょう。
判別スコアが正の値ならばA型判定、負の値ならばB型判定です。
判別スコア算出のヘルパー関数を定義して、判別スコアを計算します。
### 判別式による判別 p.262
## 判別スコア算出関数の定義
def calc_type2(data, a_list):
data_array = np.column_stack([data, np.ones(len(data))])
return data_array @ a_list
## 判別スコアの算出
# カテゴリ数量のリスト化(最後の要素にa0を配置)
a_list = [0, a12, 0, a22, a0]
# 判別スコアの算出
result = calc_type2(data1_cat.drop(columns='y'), a_list)
result【実行結果】

読みにくいですね…
テキスト p.262 表 8.3.1 の形式でまとめてみます。
### 判別スコアと平均 p.262 表8.3.1 ※判別スコアがプラスの場合A型
# data1に判別スコア列を追加
data1['判別スコア'] = result
# 平均列を追加
data1.loc['A1', '平均'] = result[data1['血液型']=='A'].mean()
data1.loc['B1', '平均'] = result[data1['血液型']=='B'].mean()
# 結果の表示
data1.round(4)【実行結果】
判別スコアが正の値がA型判定ですので、A4さんは誤判別です。


■ 判別スコアの算出(未知データ版)
テキストの「Kさん」のアンケート結果データを判別式に投入して、Kさんの血液型を推定します。
### Kさんの判別スコア p.262
# アンケート調査の結果 ※2次元にすること
data_K = [[0, 1, 1, 0]]
# カテゴリ数量のリスト化(最後の要素にa0を配置)
a_list = [0, a12, 0, a22, a0]
# 判別スコアの算出
res_K = calc_type2(data_K, a_list)
print('A型' if res_K > 0 else 'B型', f' 判別スコア: {res_K[0]:.4f}')【実行結果】
判別スコアが正の値ですので、A型判定です。


■ 判別式の判別性能
「全変動とグループ間変動の比=相関比 $${\eta^2}$$ の最大化」で判別式の係数を推定しました。
相関比 $${\eta^2}$$ は $${0}$$ から $${1}$$ の値をとります。
相関比が $${1}$$ に近いほど、グループ間変動が大きいのです。
テキストによると、相関比 $${\eta^2}$$ の値が1に近いほど判別の程度が良い、ということになるようです。
判別式の相関比 $${\eta}$$ を算出しましょう。
相関比算出関数を定義します。
## 相関比算出関数の定義
def calc_eta2(X, y, score):
## 準備
# データフレームのコピー
df = X.copy()
# 目的変数をdfに結合
df['y'] = y
# 判別スコアをdfに結合
df['score'] = score
## 計算要素の算出
# 全平均の算出
Y_bar = df['score'].mean()
# グループ単位の平均値の算出
Yg_bar = df.groupby(['y'])['score'].mean().values
# グループ単位のデータ個数の算出
Yg_count = df.groupby(['y'])['score'].count().values
## 全変動、グループ間変動、相関比の算出
ST = sum((df['score'] - Y_bar)**2) # 偏差二乗和
SB = sum((Yg_bar - Y_bar)**2 * Yg_count) # グループ平均と全平均の差の二乗和
eta2 = SB / ST # 相関比
## 戻り値: 相関比η²、全変動ST、グループ間変動SB、グループ内変動SW
return {'eta2': eta2, 'ST': ST, 'SB': SB, 'SW': ST - SB}【実行結果】なし
判別式の相関比を算出しましょう。
# 相関比の算出
res1_eta2 = calc_eta2(data1_cat.iloc[:, 1:], data1_cat['y'], result)
print(f"η²: {res1_eta2['eta2']:.5f}")【実行結果】
相関比 $${\eta^2 = 0.5884}$$ です。
テキストによると「外的基準の判別の程度は悪くないと考えられます」です。

判別スコアの分布を可視化しましょう。
テキスト p.263 図 8.3.1 に相当します。
### 判別スコアによるグラフ表現 p.263 図8.3.1
# 追加インポート
from matplotlib.ticker import AutoMinorLocator
# 描画領域の設定
plt.figure(figsize=(7, 2))
# 血液型別の判別スコアのヒストグラムの描画
sns.histplot(data=data1[['判別スコア', '血液型']], x='判別スコア', hue='血液型',
bins=np.arange(-1, 1.1, 0.1), multiple='stack', ec='white')
# x軸目盛りの値の設定
plt.xticks(np.arange(-1, 1.1, 0.5))
# x軸の補助目盛りの設定
plt.gca().xaxis.set_minor_locator(AutoMinorLocator(5));【実行結果】
青色のA型とオレンジ色のB型の位置は割と離れている感じがします。


カテゴリ数量の基準化
■ 数量化Ⅱ類の判別式の完成
テキストによると「カテゴリ数量の基準化は、各アイテム内のカテゴリ数量の平均が0となるように、カテゴリ数量を変換すること」です。
変換のイメージはぜひテキストをお読み下さい!
ここでは、数量化Ⅱ類関数を作って、基準化済みのカテゴリ数量等の計算を実施しましょう。
## 数量化Ⅱ類関数の定義 p.258~265
# num_catsの設定方法:各アイテムのカテゴリの数を設定する
# [アイテム1のカテゴリ1, ・・・, カテゴリn], [アイテム2のカテゴリ1, ・・・, カテゴリm], …
# のとき、[n, m, …]と設定
def quantification_method_type1(X, y, num_cats):
## 設定と準備
# 全体の標本サイズ
N = len(X)
# ダミー変数のnum_cats[2, 3, ...]を[0, 1, 0, 1, 1,...]に変換
cats_bit = np.array([0 if i==0 else 1 for n in num_cats for i in range(n)])
# 説明変数のダミー変数化:各アイテムの最初のカテゴリを削除
X_dummy = X[X.columns[cats_bit == 1]]
## 分散の算出
# 総分散行列STの算出:説明変数の分散共分散行列
S_T = X_dummy.cov(ddof=0)
# 群内分散行列SWの算出:各群の分散共分散行列を標本サイズで重み付け
S_W = sum([len(X_dummy[y==group]) * X_dummy[y==group].cov(ddof=0)
for group in y.unique()]) / N
# 群間分散行列SBの算出:ST - SW = SB
S_B = S_T - S_W
## 分散比行列SB/STに関する固有値・固有ベクトルの算出&固有値の降順で並び替え
eig_vals, eig_vecs = np.linalg.eig(np.linalg.inv(S_T) @ S_B)
sort_idx = eig_vals.argsort()[::-1]
eig_vals = eig_vals[sort_idx] # 固有値
eig_vecs = eig_vecs[:, sort_idx] # 固有ベクトル
## 基準化前のカテゴリ数量の算出:最大固有値に対応する固有ベクトル=カテゴリ数量
coef_ax1 = eig_vecs[:, 0]
coefs_before_normal = np.zeros_like(cats_bit).astype(float)
coefs_before_normal[cats_bit==1] = coef_ax1
## 切片の算出(基準化前のカテゴリ数量に対応)
intercept = -(X_dummy * coef_ax1).sum(axis=0) @ coef_ax1 / N
## カテゴリ数量の基準化
# 基準化されたカテゴリ数量を格納するリストの初期化
coefs_normalized = np.zeros_like(coefs_before_normal)
# アイテム単位のカテゴリ累積数をリスト化
pos_list = np.cumsum(num_cats).tolist()
# アイテム単位、カテゴリ単位でカテゴリ数量の基準化を繰り返し処理
for start, end in zip([0] + pos_list[:-1], pos_list):
# アイテム単位の基準化前カテゴリ数量を取得
coefs = coefs_before_normal[start:end]
# カテゴリ単位でカテゴリ数量を基準化してリストに格納
for i, coef in enumerate(coefs):
coefs_normalized[start + i] = \
coef - sum(X.iloc[:, start:end] @ coefs) / N
## 範囲の算出:アイテム単位の基準化後のカテゴリ数量の最大値-最小値
ranges = np.zeros(len(pos_list))
# アイテム単位でカテゴリ数量の基準化を繰り返し処理
for i, (start, end) in enumerate(zip([0] + pos_list[:-1], pos_list)):
# アイテム単位のカテゴリ数量を昇順ソート
vals = sorted(coefs_normalized[start:end])[::-1]
# 範囲の算出:最初の値(最大値)-最後の値(最小値)
ranges[i] = vals[0] - vals[-1]
## 判別スコアの算出
scores = X @ coefs_normalized
scores = scores.rename('判別スコア').to_frame()
## 相関比の算出
eta2_res = calc_eta2(X, y, scores)
## 戻り値: 基準化されたカテゴリ数量(np)、範囲(np)、判別スコア(df), 相関比
return {'coefs': coefs_normalized, 'ranges': ranges, 'scores': scores,
'eta2': eta2_res['eta2'],
'coefs_non_normed': coefs_before_normal, 'intercept': intercept}【実行結果】なし
【参考サイト】
分散・共分散を用いて基準化前のカテゴリ数量を算出するロジックは、次のサイトのコードを参考にしました。
ありがとうございます!
【注意事項】
この数量化Ⅱ類関数は一癖ありまして、テキストの1つ目の例題データ(表 8.1.2)の結果はテキストと合致しますが、テキストの2つ目の例題データ(表 8.5.2)の結果はテキストと相違します。
ですので、1つ目の例題データの分析にこの数量化Ⅱ類関数を使います。
この関数で、テキスト p.248 のカテゴリ数量の基準化と同じ結果を算出しましょう。
### 基準化されたカテゴリ数量の算出 p.264
# データセットの作成
target_col = 'y'
X = data1_cat.drop(columns=[target_col])
y = data1_cat[target_col]
# 設定:アイテム単位のカテゴリ数
num_cats = [2, 2]
## 数量化Ⅱ類の実行
res1 = quantification_method_type1(X, y, num_cats)
# 結果の表示
print('基準化されたカテゴリ数量: ', coefs:=res1['coefs'])
print('範囲: ', ranges:=res1['ranges'])
print('相関比: ', eta2:=res1['eta2'])
# 判別式の表示
x11, x12, x21, x22 = sympy.symbols('x11 x12 x21 x22')
expr1_y = sympy.Matrix([[x11, x12, x21, x22]]) @ res1['coefs'].round(4)
display(Math(f'Y = {sympy.latex(expr1_y[0])}'))【実行結果】
無事に判別式 $${Y}$$ を算出できました!

基準化前のカテゴリ数量も出力しています!
# 基準化前のカテゴリ数量と切片
print('基準化前のカテゴリ数量: ', res1['coefs_non_normed'].round(4))
print('切片(基準化前) : ', res1['intercept'].round(4))【実行結果】


■ アイテムの影響の度合い「範囲」の確認
テキスト p.264 表 8.4.1 に相当する形式でまとめてみます。
## 基準化されたカテゴリ数量と範囲 p.264 表8.4.1
# データフレームの枠を作成
data1_coefs = data1[['楽天家', '堅実派']].sum(axis=0).to_frame().drop(columns=[0])
# カテゴリ数量列の追加
data1_coefs['カテゴリ数量'] = coefs
# 範囲列の追加
data1_coefs.loc[(slice(None), 'はい'), '範囲'] = ranges
# マルチインデックスの列名の追加
data1_coefs.index.names = ['アイテム', 'カテゴリ'] # names "s"がつく
# 結果の表示
data1_coefs.round(4)【実行結果】

## 相関比
pd.DataFrame({'相関比': [eta2]}).set_axis(['カテゴリ数量'], axis=0).T.round(4)【実行結果】

各アイテム(説明変数)が外的基準(目的変数)の判別に与える影響の大きさを「範囲」という指標で確認します。
範囲は各アイテム(説明変数)のカテゴリ数量(係数)の最大値・最小値の差です。

■ 基準化された判別スコア
基準化された判別スコアを表示しましょう。
数量化Ⅱ類関数の出力結果に含まれている判別スコアを利用します。
テキスト p.265 表 8.4.2 に相当します。
### 基準化された判別スコアと平均 p.265 表8.4.1 ※判別スコアがプラスの場合A型
# data1のコピーを取得
data1_scores = data1[['血液型']].copy()
# 基準化された判別スコア列を追加
data1_scores['判別スコア'] = res1['scores'].values
# 平均列を追加
data1_scores.loc['A1', '平均'] = res1['scores'].values[:4].mean()
data1_scores.loc['B1', '平均'] = res1['scores'].values[4:].mean()
# 結果の表示
data1_scores.round(4)【実行結果】


例題を分析
テキスト p.266~の例題データを数量化Ⅱ類で分析します。
1️⃣ データの準備
テキスト p.268 表 8.5.2 の例題データを引用します。
### 性格と血液型のデータ p.268 表8.5.2
# カテゴリ: 1=はい、2=いいえ
# データの登録
data2 = pd.DataFrame(
{'合理': [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0,
1, 1, 0, 1, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0,
1, 0, 0, 0],
'気分': [1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1],
'他人': [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 1, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0],
'型': [0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
1, 1, 0, 1, 1, 0, 0, 1,
0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
0, 1, 0, 1],
'クール': [0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1,
0, 1, 1, 1, 0, 0, 1, 1,
1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0,
0, 0, 1, 0],
'個性': [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1,
0, 0, 1, 0, 0, 1, 1, 1,
0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0,
0, 0, 1, 0],
'現実': [1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 0, 0, 1, 0,
0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0],
'保守': [1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0,
1, 0, 0, 1, 1, 1, 0, 0,
1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1,
1, 1, 0, 1],
'ロマン': [0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1,
0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0,
0, 1, 0, 0],
'楽天': [0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1,
0, 0, 1, 0, 1, 0, 0, 1,
0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0,
1, 0, 0, 0],
'堅実': [0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0,
1, 1, 1, 1, 1, 0, 1, 1,
1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1,
1, 0, 1, 0],
'仲間': [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 1, 1, 0, 1,
1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1,
1, 1, 0, 1],
'血液型': ['A']*16 + ['B']*8 + ['O']*12 + ['AB']*4},
index=[f'A{i+1:02}' for i in range(16)] + [f'B{i+1:02}' for i in range(8)] \
+ [f'O{i+1:02}' for i in range(12)] + [f'AB{i+1}' for i in range(4)]
)
data2.index.name = '被験者No.'
data2.iloc[:, :-1] = data2.iloc[:, :-1] + 1
# 結果の表示
data2
# 結果をcsvファイルに保存
# data2.to_csv('./data/personality.csv')【実行結果】
アンケートで得た性格と血液型をまとめたデータです。
標本サイズ $${40}$$、外的基準(目的変数)が「4値の血液型」、その他の 12 個の変数がアイテム(説明変数)です。
カテゴリは「はい:1」「いいえ:2」です。



💡 数量化Ⅱ類の実践的コード
テキストの分析結果に合わせるため…
青木繁伸 先生の数量化Ⅱ類関数をお借りします。
ありがとうございます!
このWebサイトよりソースコードをダウンロードできます。
関数使用法については、サイトの「XVIII. 多変量解析」「26. 数量化 II 類」でpdfファイルを閲覧できます。
ボリュームが多いので、記事末に引用コードを掲載させていただきます。
以降の分析処理の前に引用コードを実行する必要があります。
【注意事項】
上記サイトは https 通信に対応していません。
テキストの1つ目の例題データに関しては、この関数による分析結果とテキストの結果は一致しません。謎です…

2️⃣ 数量化Ⅱ類のモデル構築
数量化Ⅱ類の関数を実行します。
## 数量化Ⅱ類の実行
res2_qt2 = qt2(data2, verbose=False)【実行結果】なし

3️⃣ 基準化されたカテゴリ数量の表示
テキスト p.269 表 8.5.3 に相当します。
### 基準化されたカテゴリ数量 p.269 表8.5.3 ※軸2はテキストの結果と正負反転している
## データフレームの作成
# 度数列のみのデータフレームの作成
result2_df = (data2.iloc[:, :-1].apply(lambda x: x.value_counts(), axis=0)
.T.stack().rename('度数').to_frame().reset_index())
# カテゴリ数量列の追加
result2_df = pd.concat([result2_df, res2_qt2['cs'].reset_index()], axis=1)
# インデックス、列名の一次整理
result2_df = result2_df.drop(columns=['index'])
result2_df = result2_df.rename(
columns={'level_0': 'アイテム', 'level_1': 'カテゴリ'})
# 3つの軸の範囲列を追加
result2_df.loc[result2_df['カテゴリ']==1, 'ax1_範囲'] = (
[max(x, y) - min(x, y) for x, y
in zip(result2_df['Axis 1'].values[::2], result2_df['Axis 1'].values[1::2])])
result2_df.loc[result2_df['カテゴリ']==1, 'ax2_範囲'] = (
[max(x, y) - min(x, y) for x, y
in zip(result2_df['Axis 2'].values[::2], result2_df['Axis 2'].values[1::2])])
result2_df.loc[result2_df['カテゴリ']==1, 'ax3_範囲'] = (
[max(x, y) - min(x, y) for x, y
in zip(result2_df['Axis 3'].values[::2], result2_df['Axis 3'].values[1::2])])
# 列の並び替え
result2_df = result2_df[['アイテム', 'カテゴリ', '度数', 'Axis 1', 'ax1_範囲',
'Axis 2', 'ax2_範囲', 'Axis 3', 'ax3_範囲']]
# アイテム・カテゴリをマルチインデックス化
result2_df = result2_df.set_index(['アイテム', 'カテゴリ'])
# 列をマルチインデックス化
result2_df.columns = [['度数', '軸1', '軸1', '軸2', '軸2', '軸3', '軸3'],
['', 'カテゴリ数量', '範囲', 'カテゴリ数量', '範囲',
'カテゴリ数量', '範囲']]
## 結果の表示
display(result2_df.round(3))
## 相関比 p.269 表外
display(res2_qt2['eta'].round(4))【実行結果】
3軸のカテゴリ数量が算出されました(テキストは2軸)。
テキストのカテゴリ数量と正負の符号が逆転しています。


4️⃣ 判別スコアによる傾向分析
テキスト p.270 表 8.5.4 に相当します。
## 判別スコア p.270 表8.5.4 ※軸2はテキストの結果と正負反転している
# 判別スコアデータフレームの作成
score2_df = pd.concat([data2['血液型'], res2_qt2['ss'].set_index(data2.index)],
axis=1)
score2_df = score2_df.rename(
columns={'Axis 1': '軸1_判別スコア', 'Axis 2': '軸2_判別スコア',
'Axis 3': '軸3_判別スコア'})
# 血液型のごとに表示
display(score2_df[score2_df['血液型']=='A'].round(2).style
.background_gradient(axis=None, cmap='RdBu_r', vmin=-2, vmax=2)
.format(precision=2))
display(score2_df[score2_df['血液型']=='B'].round(2).style
.background_gradient(axis=None, cmap='RdBu_r', vmin=-2, vmax=2)
.format(precision=2))
display(score2_df[score2_df['血液型']=='O'].round(2).style
.background_gradient(axis=None, cmap='RdBu_r', vmin=-2, vmax=2)
.format(precision=2))
display(score2_df[score2_df['血液型']=='AB'].round(2).style
.background_gradient(axis=None, cmap='RdBu_r', vmin=-2, vmax=2)
.format(precision=2))【実行結果】
負の値:青、正の値:赤のグラデーションを付けてみました。
テキストとほぼ一致しています。


判別スコアできっちりと判別できるわけでは無いのですね!
そこで傾向を探ると…
1~2軸の判別スコアが次のような「血液型別の傾向」がありそうです。
$$
\begin{array}{ccc}
血液型 & 軸1 & 軸2 \\
\hline
A & 負 & 負 \\
B & 1未満(負寄り) & 正 \\
O & 正 & 負 \\
AB & -1以上(正寄り) & 正 \\
\end{array}
$$
1~2軸の平面で判別スコアをプロットします。
テキスト p.271 図 8.5.1 に相当します。
## 性格による血液型の散布図 ~サンプルスコアの可視化 p.271 図8.5.1
plt.figure(figsize=(8, 6))
qt2_plot(res2_qt2, type='ss', yval=data2['血液型'].values, color2='black',
alpha=0.8)【実行結果】
血液型別にデータ点の色を揃えています。
可視化することで、表にまとめた傾向を直感的に把握できそうです。


5️⃣ 数量化Ⅱ類モデルによる予測
「Iさん」のアンケート結果を判別式に入れて、血液型を予測します。
テキスト p.272 の回答データ(未知データ)を引用します。
## Iさんのアンケート調査の回答 p.272 表8.5.5
# 回答
ans_I = [1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0]
# 軸1,2,3の判別スコア
score_I = res2_qt2['cs'].T @ ans_I
score_I.rename('score').to_frame().round(3)【実行結果】
軸1が「1未満(負寄り)」、軸2が「正」ですので、B型かもです。

判別スコアのプロットにIさんの判別スコアを追加して可視化しましょう。
テキスト p.273 図 8.5.2 に相当します。
## 性格による血液型の散布図 ~Iさんの血液型は p.273 図8.5.2
# 描画領域の設定
plt.figure(figsize=(8, 6))
# Iさんの判別スコアの描画
plt.plot([score_I.iloc[0]], [score_I.iloc[1]], '*', ms=20, color='tomato')
# Iさんのテキストの描画
plt.text(x=score_I.iloc[0], y=score_I.iloc[1]+0.2, s='Iさん', ha='center')
# 学習データの判別スコアの散布図の描画
qt2_plot(res2_qt2, type='ss', yval=data2['血液型'].values, color2='black',
alpha=0.8)【実行結果】
IさんはB型(オレンジ)、っぽいです。


6️⃣ 軸1・軸2の意味づけ
p.273 の「アイヤ しばらく!」で軸1・軸2の意味づけの検討をしています。
カテゴリ数量を可視化して、直感的に意味づけを探ってみましょう。
## カテゴリ数量:カテゴリスコアの可視化 p.273 アイヤしばらく
plt.figure(figsize=(8, 6))
qt2_plot(res2_qt2, color='tab:red', color2='black', alpha=0.8)【実行結果】
「性格 - 1」というように末尾「1」が「はい」、「2」が「いいえ」を示しています。
「現実 - 1」は「現実的である」に対応しています。

■ 軸の解釈
横軸「軸1」は正の方向(右側)に
・現実的である
・気分屋でない
・型にはまっていない
・個性的である
などが揃ってます。まとめると値が大きいほど
「現実派であって、気分屋ではなく型にはまりにくい。また個性的」
でしょう(まとまってない感じ?)
縦軸「軸2」は正の方向(上側)に
・合理的である
・クールである
・現実的でない
・堅実的でない
などが揃ってます。まとめると値が大きいほど
「合理的でクールであるが、現実的・堅実ではない」
でしょう()

青木繁伸 先生の「数量化Ⅱ類コード」の引用
「multi.py」スクリプトから3つの関数を引用いたします。
★印の部分を改造しました。
### 数量化Ⅱ類関数 by 青木繁伸 教授
# http://aoki2.si.gunma-u.ac.jp/Python/index.html
# ★印の箇所を変更した
from scipy.linalg import inv, eigh
def qt2(dat, verbose=True, mute=True):
if isinstance(dat, list):
dat = np.array(dat)
if isinstance(dat, np.ndarray):
name = ["x" + str(i + 1) for i in range(dat.shape[1])]
name[-1] = "group"
dat = pd.DataFrame(dat, columns = name)
names = list(dat.columns)
nc, item = dat.shape
# gvar = dat.iloc[:, -1] # ★ 削除
gvar = dat.iloc[:, -1].copy() # ★ 変更追加: 元データフレームの書き換えを防止
gname = np.unique(gvar)
dat2 = dat.groupby(gvar)
n = dat2.size()
ng = len(n)
cname = [list(np.unique(dat.iloc[:, i]))
for i in range(item)]
cat = [len(i) for i in cname]
junjo = np.hstack((0, np.cumsum(cat)[:-1]))
nobe2 = sum(cat)
nobe = nobe2 - ng
dat2 = np.zeros((nc, nobe2))
for i in range(nc):
for j in range(item):
k = cname[j].index(dat.iloc[i, j])
dat2[i, junjo[j] + k] = 1
x = np.zeros((nobe2, nobe2))
for i in range(nc):
x += np.outer(dat2[i, :], dat2[i, :])
pcros = x[0:nobe, 0:nobe]
gcros = x[nobe:, 0:nobe]
w = np.outer(n, n) / nc
w[np.eye(ng) == 1] = 0
grgr = np.diag(n - n**2 / nc) - w
w = np.diag(pcros)
grpat = gcros - np.outer(n, w) / nc
pat = pcros - np.outer(w, w) / nc
select = junjo[0:item - 1]
include = [not x for x in np.in1d(range(nobe), select)]
pat = pat[include, :][:, include]
grpat = grpat[1:, :][:, include]
r = grgr[1:, :][:, 1:]
ndim = ng - 1
m = pat.shape[1]
pat = inv(pat)
c = grpat@pat@ np.reshape(grpat.T, (m, ndim))
val, vec = eigh(c, r)
val = val[::-1]
vec = vec[:, ::-1]
w = (pat@grpat.T@vec) / np.sqrt(val)
a = np.zeros((nobe, ndim))
end = -1
for j in range(item - 1):
begin = end + 1
end = begin + cat[j] - 2
offset = junjo[j] + 1
a[offset:(offset + end - begin) + 1, :] = w[begin:end + 1, :]
w = (np.diag(pcros) * a.T).T
for j in range(item - 1):
begin = junjo[j]
end = begin + cat[j] - 1
s = (w[begin:end + 1, :]).sum(axis=0) / nc
a[begin:end + 1, :] = a[begin:end + 1, :] - s
a /= np.sqrt(np.diag(a.T @ pcros @ a) / nc)
sample_score = dat2[:, 0:nobe] @ a
temp = np.vstack((np.zeros((1, vec.shape[1])), vec))
centroid = (np.array(n) * temp.T).T
centroid = temp - centroid.sum(axis=0) / nc
temp2 = ((centroid**2).T * np.array(n)).sum(axis=1)
centroid /= np.sqrt(temp2 / nc / val)
item1 = item
partial_cor = np.zeros((item - 1, ndim))
for l in range(ndim):
pat = np.zeros((item1, item1))
pat[0, 0] = sum(n * centroid[:, l]**2)
temp = (a[:, l] * (gcros.T * centroid[:, l]).T).sum(axis=0)
begin = junjo[:-1]
end = junjo[:-1] + cat[:-1]
pat[0, 1:(item + 1)] = [sum(temp[begin[k]:end[k]])
for k in range(len(begin))]
temp = np.outer(a[:, l], a[:, l]) * pcros
for i in range(item - 1):
begin = junjo[i]
end = begin + cat[i] - 1
for j in range(i + 1):
pat[j + 1, i + 1] = np.sum(temp[begin:end + 1,
(junjo[j]):(junjo[j] + cat[j])])
d = np.diag(pat)
pat /= np.sqrt(np.outer(d, d))
pat = pat + pat.T
pat[np.eye(item1) == 1] = 1
pat = inv(pat)
partial_cor[:, l] = -pat[1:item1, 0] / np.sqrt(pat[0, 0] * np.diag(pat)[1:])
gname = [names[-1] + "-" + str(cname[-1][j]) for j in range(cat[-1])]
vname = names[:-1]
name = np.array([])
for i in range(item - 1):
name = np.hstack(
(name, [vname[i] + "-" + str(cname[i][j]) for j in range(cat[i])]))
axis = ["Axis " + str(i + 1) for i in range(ndim)]
category_score = pd.DataFrame(a, columns=axis, index=name)
partial_cor = pd.DataFrame(partial_cor, columns=axis, index=vname)
centroid = pd.DataFrame(centroid, columns=axis, index=gname)
val = pd.DataFrame(np.reshape(val, (1, ndim)), columns=axis, index=["Eta"])
sample_score = pd.DataFrame(sample_score, columns=axis)
if verbose:
print("Quantification theory type II\n\nCategory score")
print(category_score)
print("\nPartial correlation coefficient")
print(partial_cor)
print("\nCentroid")
print(centroid)
print("\nEta")
print(val)
if not mute:
print("\nSample score")
print(sample_score)
return dict(ndim=ndim, ng=ng, dat2=dat2, cs=category_score, partialCor=partial_cor,
centroid=centroid, eta=val, ss=sample_score, name=name, gvar=gvar, n=nc)
def qt2_plot(obj, type="cs", ax1=1, ax2=2, color="black", color2="blue", alpha=0.5, yval=None):
n = obj["n"]
ndim = obj["ndim"]
if type == "cs":
title = "Category Score"
txt = obj["name"]
elif type == "ss":
title = "Sample Score"
txt = [str(i + 1) for i in range(n)]
gvar = np.ravel(obj["gvar"])
unique = list(np.unique(gvar))
for i in range(n):
gvar[i] = unique.index(gvar[i])
else:
raise Exception("only 'cs' or 'ss' is allowed")
if ndim == 1:
x = np.ravel(obj[type])
if type == "cs":
for i in range(len(x)):
plt.barh(i, x[i], color=color, alpha=alpha)
plt.text(0, i, " " + txt[i] + " ", color=color2,
verticalalignment="center",
horizontalalignment="left" if x[i] < 0 else "right")
plt.axvline(0, color=color, linewidth=0.5)
plt.yticks([])
else:
k, min2, width = pretty(min(x), max(x), 20)
freq = np.zeros((2, k))
for i in range(n):
freq[gvar[i], int((x[i]-min2)/width)] += 1
for i in range(k):
plt.bar(min2+(i-0.5)*width, freq[0, i], width=width/2, color="red", alpha=alpha)
plt.bar(min2+i*width, freq[1, i], width=width/2, color="blue", alpha=alpha)
else:
ax1 = int(ax1)
if ax1 < 1 or ax1 > ndim:
raise Exception("value must be 1 to " + str(ndim))
x = np.array(obj[type])[:, ax1 - 1]
ax2 = int(ax2)
if ax2 < 1 or ax2 > ndim:
raise Exception("value must be 1 to " + str(ndim))
y = np.array(obj[type])[:, ax2 - 1]
if type == "cs":
plt.scatter(x, y, color=color, s=40, alpha=alpha) # ★修正s=16⇒40
else:
# plt.scatter(x, y, c=gvar, s=16, alpha=alpha) # ★削除
# ★変更追加ここから: 凡例表示を追加
if yval is None:
plt.scatter(x, y, c=gvar, s=16, alpha=alpha)
else:
sns.scatterplot(x=x, y=y, hue=yval, s=60, palette='tab10')
plt.legend(bbox_to_anchor=(1, 1))
# ★変更追加ここまで
for i in range(len(txt)):
plt.text(x[i], y[i], " " + txt[i], color=color2, alpha=alpha)
plt.axhline(0, color="black", linewidth=0.5)
plt.axvline(0, color="black", linewidth=0.5)
plt.xlabel("Axis " + str(ax1))
plt.ylabel("Axis " + str(ax2))
plt.title(title)
plt.show()
def pretty(min0, max0, n=5):
width = (max0 - min0) / n
k = 0
while width >= 10:
width /= 10
k += 1
while width < 1:
width *= 10
k -= 1
if width < 1.5:
width = 1
elif width < 2.25:
width = 2
elif width < 3.75:
width = 2.5
else:
width = 5
width *= 10**k
min2 = np.floor(min0 / width) * width
max2 = np.ceil(max0 / width) * width
n = int((max2 - min2) / width + 1)
return n, min2, width記事の最後はChatGPTが締めくくります。
さまざまなシーンを回想して。
📘 ChatGPTのひとこと:
今回の記事では、数量化Ⅱ類を通じて「カテゴリデータを最適に数値化する視点」を身につけました。
まるで手作りのスパイスセットを用意して、お好みの風味を見つけるように――「はい」「いいえ」といったシンプルな選択肢が、最適な得点としてデータの中で輝き始めたのではないでしょうか😊
カテゴリの背後に隠れた微妙なニュアンスを数値としてすくい取り、回帰や判別の土俵にきれいに並べることで、データに新しい奥行きが生まれました。
次回はその先のステップ、数量化Ⅲ類に進みます。ここでは「複数のカテゴリ変数を同時に扱い、相互の関係性を探る」手法を学びますので、また一緒にゆったりとデータの海を渡る時間を楽しみましょう✨
今回の写経は以上です。
シリーズの記事
次の記事
前の記事
目次
ブログの紹介
note で7つのシリーズ記事を書いています。
ぜひ覗いていってくださいね!
1.のんびり統計
統計検定2級の問題集を手がかりにして、確率・統計をざっくり掘り下げるブログです。
雑談感覚で大丈夫です。ぜひ覗いていってくださいね。
統計検定2級公式問題集CBT対応版に対応しています。
Python、EXCELのサンプルコードの配布もあります。
2.実験!たのしいベイズモデリング1&2をPyMC Ver.5で
書籍「たのしいベイズモデリング」・「たのしいベイズモデリング2」の心理学研究に用いられたベイズモデルを PyMC Ver.5で描いて分析します。
この書籍をはじめ、多くのベイズモデルは R言語+Stanで書かれています。
PyMCの可能性を探り出し、手軽にベイズモデリングを実践できるように努めます。
身近なテーマ、イメージしやすいテーマですので、ぜひぜひPyMCで動かして、一緒に楽しみましょう!
3.実験!岩波データサイエンス1のベイズモデリングをPyMC Ver.5で
書籍「実験!岩波データサイエンスvol.1」の4人のベイジアンによるベイズモデルを PyMC Ver.5で描いて分析します。
この書籍はベイズプログラミングのイロハをざっくりと学ぶことができる良書です。
楽しくPyMCモデルを動かして、ベイズと仲良しになれた気がします。
みなさんもぜひぜひPyMCで動かして、一緒に遊んで学びましょう!
4.楽しい写経 ベイズ・Python等
ベイズ、Python、その他の「書籍の写経活動」の成果をブログにします。
主にPythonへの翻訳に取り組んでいます。
写経に取り組むお仲間さんのサンプルコードになれば幸いです🍀
5.RとStanではじめる心理学のための時系列分析入門 を PythonとPyMC Ver.5 で
書籍「RとStanではじめる心理学のための時系列分析入門」の時系列分析をPythonとPyMC Ver.5 で実践します。
この書籍には時系列分析のテーマが盛りだくさん!
時系列分析の懐の深さを実感いたしました。
大好きなPythonで楽しく時系列分析を学びます。
6.データサイエンスっぽいことを綴る
統計、データ分析、AI、機械学習、Pythonのコラムを不定期に綴っています。
統計・データサイエンス書籍にまつわる記事が多いです。
「統計」「Python」「数学とPython」「R」のシリーズが生まれています。
7.Python機械学習プログラミング実践記
書籍「Python機械学習プログラミング PyTorch & scikit-learn編」を学んだときのさまざまな思いを記事にしました。
この書籍は、scikit-learnとPyTorchの教科書です。
よかったらぜひ、お試しくださいませ。
最後までお読みいただきまして、ありがとうございました。
いいなと思ったら応援しよう!
応援ありがとうございます。これからもがんばって記事を作成します!