第1章

x <- c(5.0, 4.9, 5.3, 5.2, 3.9, 4.5, 6.1, 4.8, 5.2, 4.3)
mu <- 5
sigma <- 3
n <- length(x)
sqrt(n) * (mean(x)-mu) / sigma   ## Zの値
## [1] -0.0843274
qnorm(0.025)                     ## 棄却域との境界(下限)
## [1] -1.959964
qnorm(0.975)                     ## 棄却域との境界(上限)
## [1] 1.959964

第2章

kruskal <- function(w) {     # Prim法
    # 行列の次元取得
    p <- ncol(w)
    # 上三角行列を対称行列に変換
    for (i in 1:(p - 1)) {
        for (j in (i + 1):p) {
            w[j, i] <- w[i, j]
        }
    }
    # 対角要素を 0 に設定
    for (i in 1:p) {
        w[i, i] <- 0
    }
    # 初期化
    parent <- array(dim = p)  # 各ノードの親を記録する配列
    u <- rep(0, p)  # 訪問済みノードを記録 (0: 未訪問, 1: 訪問済み)
    v <- rep(-Inf, p)  # 各ノードの最大重み (最大極大木のため)
    # Uに最初に含まれる頂点jの設定
    j <- 1
    parent[1] <- -1  # 根の親を -1 に設定 (ルート)
    # Prim法による最大極大森の構築
    while (j <= p) {
        u[j] <- 1  # ノード j を訪問済みに設定
        # 隣接ノードの重みを更新
        for (i in 1:p) {
            if (u[i] == 0 && w[j, i] > v[i]) {
                v[i] <- w[j, i]
                parent[i] <- j
            }
        }
        # Uの中にある親iとの重みw[i,j]が最大のV\Uの要素jを見出す
        max_weight <- 0
        for (i in 1:p) {
            if (u[i] == 0 && v[i] > max_weight) {
                j <- i
                max_weight <- v[i]
            }
        }
        # 極大木にはならない場合、次のjを他の連結部分から見出す
        if (max_weight <= 0) {
            j <- 1
            while (j <= p && u[j] == 1) {
                j <- j + 1
            }
            # すべてu[j]=1であれば、ここでjがpを超えている。
            # 他に連結部分があれば、そのjを新しい根とする。
            if (j <= p) {
                parent[j] <- -1
            }
        }
    }
    # 結果のエッジペアを作成
    pair.1 <- NULL
    pair.2 <- NULL
    for (j in 1:p) {
        if (parent[j] != -1) {
            pair.1 <- c(pair.1, parent[j])
            pair.2 <- c(pair.2, j)
        }
    }
    return(cbind(pair.1, pair.2))  # エッジリストを返す
}

library(igraph)
p <- 10
w <- matrix(rnorm(p*p)**2, p, p)
edgelist <- kruskal(w)
plot(graph_from_edgelist(edgelist), directed=FALSE)

第6章

AIC.1 <- function(k){
  m <- length(k)
  n <- sum(k)
  return(sum(k*log (n/k))+m-1)
}

BIC.1 <- function(k){
  m <- length(k)
  n <- sum(k)
  return(sum(k*log (n/k))+(m-1)/2*log(n))
}

n <- 200
p <- 0.25
x <- rbinom(n, 1, p)
k <- as.vector(table(x))
AIC.1(k)
## [1] 107.6328
BIC.1(k)
## [1] 109.2819
sigma2 <- function(X, y){
  n <- length(y)
  beta <- solve(t(X)%*%X) %*% t(X) %*% y
  return( sum((y-X%*%beta)^2) / n)
}
AIC.2 <- function(X, y){
  n <- length(y)
  p <- ncol(X)
  sigma2 <- sigma2(X, y)
  # AICの公式に代入する
  value <- (n/2)*log(sigma2) + n/2*log(2*pi*exp(1)) + p + 1
  return(value)
}
BIC.2 <- function(X, y){
  n <- length(y)
  p <- ncol(X)
  # 行列演算でbetaの値を得る
  sigma2 <- sigma2(X,y)
  # BICの公式に代入する
  value <- (n/2)*log(sigma2) + n/2*log(2*pi*exp(1)) + (p+1)/2*log(n)
  return(value)
}
AIC.3 <- function(X, y) length(y) * log(sigma2(X, y)) + 2*ncol(X)
BIC.3 <- function(X, y) length(y) * log(sigma2(X, y)) + ncol(X)*log(length(y))

n <- 100
p <- 3
X <- matrix(rnorm(n*p), n, p)
y <- rnorm(n)
mu <- rnorm(p)
Sigma <- diag(p)
AIC.2(X, y)
## [1] 147.9021
BIC.2(X, y)
## [1] 153.1124
AIC.3(X, y)
## [1] 10.01642
BIC.3(X, y)
## [1] 17.83193
library(MASS)
df <- Boston
# 14変数あるうちの離散の説明変数と目的う変数を除外した
X <-as.matrix(df[,c(1,3,5,6,7,8,10,11,12,13)])
y <- df[[14]]
n <- nrow(X)
p <- ncol(X)
X <- cbind(rep(1,n),X) # 切片を含めた
AIC.seq <- NULL
BIC.seq <- NULL;
for(k in 1:p){
  # combn(S,k)で集合Sの中の大きさkの部分集合を列にもつ行列を生成
  T <- combn(1:p,k)
  m <- ncol(T)
  # 各kで、sigma2を最小にする変数の組合わせを選択して、それに対してのAIC/BICを計算。最小値の初期値として S.minに無限大をおいた。
  S.min <- Inf
  for(j in 1:m){
    q <- c(1, T[,j]+1) # 切片(1列目)と選択された変数の列(1列右にずらした)
    S <- sigma2(X[,q],y)
    if(S < S.min) {
      S.min <- S
      q.min <- q
    }
  }
  AIC.seq <- c(AIC.seq, AIC.3(X[,q.min],y))
  BIC.seq <- c(BIC.seq, BIC.3(X[,q.min],y))
}
plot(1:p, ylim=c(min(AIC.seq),max(BIC.seq)), type="n",
     xlab="変数の個数", ylab="AIC/BICの値")
lines(AIC.seq,col="red"); lines(BIC.seq,col="blue")
legend("topright",legend=c("AIC","BIC"), col=c("red","blue"), lwd=1, cex =.8)

Q.1 <- function(k,a){
  m <- length(k)
  log.q <- 0
  for(j in 2:m) {
    log.q <- log.q + log( beta(k[j-1]+a[j-1], sum(k[j:m]+a[j:m])) )
                   - log( beta(a[j-1], sum(a[j:m])))
  }
  return(-log.q)
}

n <- 100
m <- 4
prob <- rep(1/m, m)
k <- as.vector(rmultinom(1, n, prob))
a <- rep(1,m)
Q.1(k,a)
## [1] 144.8654
n <- 100
x <- rbinom(n, 1, 0.5)
y <- rbinom(n, 1, 0.25 )
k <- as.vector(table(x,y))
a <- rep(0.5, 4)
Q.1(k,a)
## [1] 123.5134
# 必要なパッケージを読み込み
library(CholWishart)  # lmvgamma 関数を利用するために必要

# 入力:
#   x       : 観測データ(n行p列の行列形式)
#   kappa.0 : 事前分布のスケールパラメータ
#   nu.0    : 事前分布の自由度
#   mu.0    : 事前分布の平均ベクトル
#   Lambda.0: 事前分布の共分散行列
# 出力:
#   value   : 周辺尤度の対数値

Q.3 <- function(x, kappa.0, nu.0, mu.0, Lambda.0) {
  x <- as.matrix(x)
  n <- nrow(x)
  d <- ncol(x)

  kappa.n <- kappa.0 + n
  nu.n <- nu.0 + n

  x.bar <- colMeans(x)
  S <- t(x - matrix(x.bar, n, d, byrow = TRUE)) %*%
        (x - matrix(x.bar, n, d, byrow = TRUE))
  mu.diff <- matrix(x.bar - mu.0, nrow = d)
  M <- (kappa.0 * n) / (kappa.0 + n) * (mu.diff %*% t(mu.diff))
  Lambda.n <- Lambda.0 + S + M
  value <- n * d / 2 * log(pi) -
    lmvgamma(nu.n / 2, d) + lmvgamma(nu.0 / 2, d) -
    (nu.0 / 2) * log(det(Lambda.0)) + (nu.n / 2) * log(det(Lambda.n)) -
    (d / 2) * log(kappa.0 / kappa.n)
  return(value)
}

library(MASS)
library(CholWishart)
kappa.0 <- 1
nu.0 <- 2
mu.0 <- c(0, 0)
Lambda.0 <- diag(2)
n <- 100
x <- mvrnorm(n, c(0,0), matrix(c(1,0,0,1), 2, 2))
a <- rnorm(2)
A <- a %*% t(a)
y <- mvrnorm(n, c(0,0), A)
Q.3(x, kappa.0, nu.0, mu.0, Lambda.0)
## [1] 266.1074
Q.3(y, kappa.0, nu.0, mu.0, Lambda.0)
## [1] 70.9682
# Bostonデータを準備
df <- Boston
X <- as.matrix(df[, c(1, 3, 5, 6, 7, 8, 10, 11, 12, 13)])  # 説明変数10個
Y <- as.matrix(df[, 14])  # 目的変数
n <- nrow(X)
X <- X[1:n, ]
Y <- Y[1:n, ]
p <- ncol(X)

# スコア計算 (Q3(X,Y) - Q3(X)) を変数数ごとに最小選択
Q3.seq <- numeric(p)
for (k in 1:p) {
  T <- combn(1:p, k)
  m <- ncol(T)
  min_score <- Inf
  for (j in 1:m) {
    idx <- T[, j]
    X.sub <- X[, idx, drop = FALSE]
    mu.0 <- rep(0, ncol(X.sub))
    Lambda.0 <- diag(ncol(X.sub))
    q3_x <- Q.3(X.sub, 1, ncol(X.sub) + 2, mu.0, Lambda.0)
    X.sub.y <- cbind(X.sub, Y)
    mu.0.y <- rep(0, ncol(X.sub.y))
    Lambda.0.y <- diag(ncol(X.sub.y))
    q3_xy <- Q.3(X.sub.y, 1, ncol(X.sub.y) + 2, mu.0.y, Lambda.0.y)
    score <- q3_xy - q3_x
    if (score < min_score) {
      min_score <- score
    }
  }
  Q3.seq[k] <- min_score
}
# プロット
plot(1:p, Q3.seq, type = "b", col = "forestgreen", lwd = 2,
     xlab = "選択された変数の数", ylab = expression(Q[3](X,Y) - Q[3](X)),
     main = "周辺尤度差 Q.3(X,Y)-Q.3(X) の変数選択評価")
grid()

第7章

library(bnlearn)
library(igraph)
# alarmデータの読み込み
df <- alarm
p <- ncol(df)
# ノード名を "1", "2", ..., "p" に置き換える(bnlearn用)
colnames(df) <- as.character(1:p)
# 構造学習(Hill Climbing + BIC)
alarm.hc <- hc(df, score = "bic")
# arcs 関数で (from, to) 辺の情報を取得(文字列型)
mat <- arcs(alarm.hc)
# 文字列を整数に変換(igraphで使うため)
u <- as.integer(mat[,1])  # 親ノード
v <- as.integer(mat[,2])  # 子ノード
edges <- cbind(u, v)      # 辺の行列

X.1 <- c(2, 2, 2, 2, 1, 2, 2, 2, 1, 2)
X.2 <- c(2, 2, 1, 1, 2, 1, 1, 2, 2, 2)
X.3 <- c(1, 1, 2, 2, 3, 1, 3, 1, 1, 1)

tab.1 <- ftable(X.1)
tab.2 <- ftable(X.2)
tab.3 <- ftable(X.3)
tab.1.2 <- ftable(X.1, X.2)
tab.1.3 <- ftable(X.1, X.3)
tab.2.3 <- ftable(X.2, X.3)
tab.1.2.3 <- ftable(X.1, X.2, X.3)

h <- function(tab) {
    m <- nrow(tab)   # 行数(例: 条件変数 X の取りうる値の個数)
    r <- ncol(tab)   # 列数(例: 目的変数 Y の取りうる値の個数)
    h <- 0           # エントロピーの初期化
    for(i in 1:m) {
        ss <- sum(tab[i, ])  # 行 i の合計(= P(X = i) に比例)
        if(ss > 0) {
            for(j in 1:r) {
                if(tab[i, j] > 0) {
                    # 条件付き確率 p(Y = j | X = i) に対する項
                    h <- h - tab[i, j] * log(tab[i, j] / ss)
                }
            }
        }
    }
    return(h)
}

c(h(tab.1), h(tab.2), h(tab.3), h(tab.1.2), h(tab.1.3), h(tab.2.3), h(tab.1.2.3))
## [1] 5.004024 6.730117 9.502705 5.545177 8.588343 6.862250 5.545177
IC <- function(X, y, proc= "AIC"){
    n <- nrow(X)
    tab <- ftable(data.frame(X,y))
    m <- nrow(tab)
    r <- ncol(tab)
    d <- m*(r-1)
    if(proc=="AIC")    IC <- h(tab)+d else
        IC <- h(tab)+d*log(n)/2
    return(IC)
}

IC.min <- function(X, y, proc = "AIC") {
    # X: 説明変数の行列(n × p)
    # y: 応答変数のベクトル(長さ n)
    # proc: "AIC" または "BIC" のどちらを使用するか(文字列)
    p <- ncol(X)      # 説明変数の数
    n <- length(y)    # データの個数(サンプル数)
    L <- 2^p          # 説明変数の全部分集合の数(2^p 通り)
    # 全変数を使わないモデル(定数項のみのモデル)の IC(AIC/BIC の共通部分)
    IC.min <- n * log(sum((y - mean(y))^2) / n)  # 定数項のみのモデルの対数尤度から計算
    set.min <- NULL   # 最小ICを与える変数集合(初期値)
    if (p == 0) {
        # 説明変数がない場合、定数モデルしかないので即座に返す
        return(list(value = IC.min, set = set.min))
    }
    # i は 2 から 2^p まで(i = 1 は空集合に対応するのでスキップ)
    for (i in 2:L) {
        set <- decimal.to.set(i - 1)  # i-1 を2進数に変換し、どの変数を選ぶかを取得
        # 部分集合に対応する説明変数を使ってモデルを適合させ、ICを計算
        if (proc == "AIC") {
            value <- AIC.3(as.matrix(X[, set]), y)
        } else {
            value <- BIC.3(as.matrix(X[, set]), y)
        }
        # ICがこれまでの最小より小さい場合は、値と集合を更新
        if (value < IC.min) {
            IC.min <- value
            set.min <- set
        }
    }
    # 最小ICとそのときの変数集合を返す
    return(list(value = IC.min, set = set.min))
}

library(MASS)
df <- Boston
index <- c(1,3,5,6,7,8,10,11,12,13)
X <- as.matrix(df[, index])
y <- df[[14]]

decimal.to.set <- function(x) if(x==0) NULL else
    c(decimal.to.set(x-2^floor(log2(x))), floor(log2(x))+1)

IC.min(X, y, "AIC")
## $value
## [1] 1655.203
## 
## $set
## [1]  1  3  4  6  8  9 10
p <- ncol(X)
parent <- list()
parent[[1]] <- NULL
for(j in 2:p)parent[[j]] <- IC.min(as.matrix(X[, 1:(j-1)]), X[,j], "BIC")$set
parent
## [[1]]
## NULL
## 
## [[2]]
## NULL
## 
## [[3]]
## NULL
## 
## [[4]]
## NULL
## 
## [[5]]
## [1] 2 3 4
## 
## [[6]]
## [1] 2 4 5
## 
## [[7]]
## [1] 1 2 3 6
## 
## [[8]]
## NULL
## 
## [[9]]
## [1] 1 3 4 6 7 8
## 
## [[10]]
## [1] 1 3 4 5 6 8
# グラフ描画のためのライブラリ
library(igraph)
# 有向辺を格納する行列(最大1000個分を確保しておく)
edge <- matrix(ncol = 2, nrow = 1000)
# 辺のカウント用
k <- 0
p <- 10
# 各変数とその親変数との間に有向辺を張る(j → i)
for (i in 1:p) {
  for (j in parent[[i]]) {
    k <- k + 1
    edge[k, ] <- c(j, i)  # j → i の有向辺を登録
  }
}
# 有向辺のリストから igraph オブジェクトを作成
g <- graph_from_edgelist(edge[1:k, ], directed = TRUE)
# 各ノード(変数)にラベルを付ける
# ラベルは変数名の頭文字(例: crim → "C", indus → "I", ...)
V(g)$label <- c("C", "I", "N", "R", "A", "R", "T", "P", "B", "L")
# グラフの描画(デフォルトスタイル)
layout <- layout_with_fr(g)
plot(g,
     layout = layout,
     vertex.label.cex = 1,    # ラベルの文字サイズ
     vertex.size = 30,          # ノードの円のサイズ(単位: ピクセル程度)
     vertex.label.color = "black", # ラベルの色
     vertex.color = "yellow",     # ノードの円の色
     edge.arrow.size = 0.5          # 矢印の大きさ
)

# データ生成
n <- 100 # 標本数
# xは1/4の確率で1になる二項分布から生成
x <- rbinom(n, 1, 1/4)
k.x <- as.vector(table(x)) # xのカテゴリごとの出現回数
# yは1/2の確率で1になる二項分布から生成
y <- rbinom(n, 1, 1/2)
k.y <- as.vector(table(y)) # yのカテゴリごとの出現回数
# zはxとyの組み合わせ(カテゴリ数増加)
z <- x + 2 * y
k.z <- as.vector(table(z)) # zのカテゴリごとの出現回数
# 独立性の判定
# xとyの独立性が成り立つ場合、結果は負の値になるはず
result <- Q.1(k.x, rep(1/2, 2)) + Q.1(k.y, rep(1/2, 2)) - Q.1(k.z, rep(1/2, 4))
# 結果を出力
print(result)
## [1] -0.2424068
x <- rbinom(n, 1, 1/2)
k.x <- as.vector(table(x))
y <- (rbinom(n, 1, 1/3) + x) %% 2
k.y <- as.vector(table(y))
z <- x + 2 * y
k.z <- as.vector(table(z))

# データの生成
n <- 100  # サンプル数
a <- rnorm(2) * 10              # ランダムな係数
A <- a %*% t(a)                 # 共分散行列の生成
z <- mvrnorm(n, c(0, 0), A)     # 多変量正規分布データの生成
# x, yに分解
x <- z[, 1]  # zの1列目
y <- z[, 2]  # zの2列目
# 関数の実行例
Q.3(x, 2, 2, c(0), matrix(1, 1, 1))
## [1] 376.8391
Q.3(y, 2, 2, c(0), matrix(1, 1, 1))
## [1] 361.0308
Q.3(z, 2, 2, c(0, 0), diag(2))
## [1] 318.1479
Q.1(k.x, rep(1/2, 2)) + Q.1(k.y, rep(1/2, 2)) - Q.1(k.z, rep(1/2, 4))
## [1] 1.320075
z <- mvrnorm(n, c(0,0), diag(2))

Q.3(x, 2, 2, c(0), matrix(1, 1, 1)) +
Q.3(y, 2, 2, c(0), matrix(1, 1, 1)) -
Q.3(z, 2, 2, c(0, 0), diag(2))
## [1] 431.1244
I.n <- function(x, y) {
    z <- table(x, y)  # X, Y の同時度数表
    size <- dim(z)  # 表のサイズを取得
    n <- length(x)  # サンプルサイズ
    S <- 0  # 相互情報量の初期値
    for (i in 1:size[1]) {
        u <- sum(z[i,])  # X = i の頻度
        for (j in 1:size[2]) {
            v <- sum(z[, j])  # Y = j の頻度
            if (z[i, j] != 0) {
                S <- S + z[i, j] / n * log( z[i, j] * n / (u * v) )
                # 相互情報量の計算
            }
        }
    }
    return(S)
}

library(igraph)
library(bnlearn)
# データの取得
df <- asia
n <- nrow(df)  # サンプル数
p <- ncol(df)  # 変数の個数
# データを行列形式に変換
x <- matrix(nrow = n, ncol = p)
for (i in 1:p) {
    x[, i] <- df[[i]]
}
# 相互情報量行列の作成
w <- matrix(0, nrow = p, ncol = p)  # 初期化
for (i in 1:(p - 1)) {
    for (j in (i + 1):p) {
        w[i, j] <- I.n(x[, i], x[, j])  # 相互情報量を計算
        w[j, i] <- w[i, j]
    }
}
# Chow-Liu 木の構築(最大重み全域木)
edge.list <- kruskal(w)  # Kruskalアルゴリズム
print(edge.list)  # 結果のエッジリストを表示
##      pair.1 pair.2
## [1,]      4      2
## [2,]      1      3
## [3,]      6      4
## [4,]      2      5
## [5,]      3      6
## [6,]      6      7
## [7,]      5      8
# グラフを描画
g <- graph_from_edgelist(edge.list, directed = FALSE)  # igraph を使用
V(g)$label <- colnames(df)  # ノードにラベルを付与
plot(g, main = "Chow-Liu アルゴリズム(I.n)")  # グラフを描画

J.n <- function(x,y){
  x <- as.numeric(x)
  alpha <- length(table(x))
  y <- as.numeric(y)
  beta <- length(table(y))
  z <- (y-1) * alpha + x
  value <- Q.4(x, alpha) + Q.4(y, beta) - Q.4(z, alpha*beta)
  return(max(value,0))
}

Q.4 <- function(x, alpha){
  x <- as.numeric(x)
  n <- length(x)
  nc <- 0
  cc <- rep(0, alpha)
  log.q <- 0
  for(i in 1:n){
    log.q <- log.q - log ( (cc[x[i]]+0.5) / (nc + alpha/2))
    cc[x[i]] <- cc[x[i]] + 1
    nc <- nc +1
  }
  return(log.q / n)
}

J.n <- function(x,y){
  n <- length (x)
  size <- dim(table(x,y))
  value <- I.n(x,y) - (size[1]-1) * (size[2]-1) / 2 / n * log(n)
  return(max(value, 0))
}

I.n <- function(x,y) {
  rho <- cov(x, y) / sqrt(var(x) * var(y))
  return(-0.5 * log(1-rho^2))
}

J.n <- function(x,y){
  n <- length(x)
  value <- I.n(x,y) - 0.5 * log (n) / n
  return (max(value, 0))
}