からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

【R】三角グラフを作図したい【ggplot2】

はじめに

 素直なやり方ではできなかったのでむりくりなんとかする黒魔術シリーズです。もっといい方法があれば教えてください。

 この記事では、R言語で三角図を作成します。

【他の内容】

www.anarchive-beta.com

【目次】

ggplot2で三角グラフを作図したい

 三角図や三角ダイアグラム(ternary diagram)などと呼ばれるグラフのアニメーションを作成したい。そこで、gganimateパッケージを利用できるようにggplot2パッケージを利用して三角図を作成する。

 次のパッケージを利用する。

# 利用パッケージ
library(tidyverse)
library(ggrepel)
library(gganimate)
library(MCMCpack)

 基本的にパッケージ名::関数名()の記法を使うので、パッケージを読み込む必要はない。ただし、作図コードがごちゃごちゃしないようにパッケージ名を省略しているので、ggplot2を読み込む必要がある。
 また、magrittrパッケージのパイプ演算子%>%ではなく、ネイティブパイプ演算子|>を使っている。%>%に置き換えても処理できるが、その場合はmagrittrを読み込む必要がある。

三角座標への変換式の確認

 まずは、3次元座標上の点$\mathbf{x}$から三角座標(2次元座標)上の点$\mathbf{y}$に変換する計算式を確認する。

 総和が1の3次元の変数(点)

$$ \mathbf{x} = (x_1, x_2, x_3) ,\ 0 \leq x_i \leq 1 ,\ \sum_{i=1}^3 x_i = 1 $$

に対して、次の式で2次元の変数(点)$\mathbf{y}$に変換できる。

$$ \mathbf{y} = (y_1, y_2) ,\ \begin{cases} y_1 = x_2 + \frac{x_3}{2} \\ y_2 = \frac{\sqrt{3} x_3}{2} \end{cases} $$

 $\mathbf{y}$は、正三角形の座標上の点になる。2次元座標上の点に変換することで、2次元のグラフで可視化できる。

 総和が1でない定数の場合は、各成分(各次元の値)を総和(定数)で割り、総和が1になるように正規化することで、同様に計算(処理)できる。

$$ \tilde{\mathbf{x}} = (\tilde{x}_1, \tilde{x}_2, \tilde{x}_3) ,\ \tilde{x}_i = \frac{x_i}{\sum_{i'=1}^3 x_{i'}} $$


 この記事では、元の3次元の座標におけるx軸・y軸・z軸をそれぞれ$x_1$軸・$x_2$軸・$x_3$軸、変換後の2次元の座標におけるx軸・y軸をそれぞれ$y_1$軸・$y_2$軸と呼ぶことにする。

三角図の座標

 次は、三角図の基となる軸やグリッド線を用意する。

 軸目盛用の値を設定する。

# 軸目盛の位置を指定
axis_vals <- seq(from = 0, to = 1, by = 0.1)
axis_vals
##  [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0

 3つの軸の目盛ラベルやグリッド線を描画する位置として、0から1の範囲の値をaxis_valsに指定する。
 axis_valsを使って、描画に利用するデータフレームを作成する。

 正三角形の枠線(2次元座標上の$x_1$軸・$x_2$軸・$x_3$軸)を描画する用のデータフレームを作成する。

# 枠線用の値を作成
ternary_axis_df <- tibble::tibble(
  y_1_start = c(0.5, 0, 1),         # 始点のx軸の値
  y_2_start = c(0.5*sqrt(3), 0, 0), # 始点のy軸の値
  y_1_end = c(0, 1, 0.5),           # 終点のx軸の値
  y_2_end = c(0, 0, 0.5*sqrt(3)),   # 終点のy軸の値
  axis = c("x_1", "x_2", "x_3")     # 元の軸
)
ternary_axis_df
## # A tibble: 3 × 5
##   y_1_start y_2_start y_1_end y_2_end axis 
##       <dbl>     <dbl>   <dbl>   <dbl> <chr>
## 1       0.5     0.866     0     0     x_1  
## 2       0       0         1     0     x_2  
## 3       1       0         0.5   0.866 x_3

 3つの軸をそれぞれ線分として描画するため、各軸の始点の$y_1$軸・$y_2$軸の値をy_1_start列・y_2_start列、終点の$y_1$軸・$y_2$軸の値をy_1_end列・y_2_end列とする。
 また、各行に対応する軸を示すaxis列を作成する。

 グリッド線を描画する用のデータフレームを作成する。

# グリッド線用の値を作成
ternary_grid_df <- tibble::tibble(
  y_1_start = c(
    0.5 * axis_vals, 
    axis_vals, 
    0.5 * axis_vals + 0.5
  ), # 始点のx軸の値
  y_2_start = c(
    sqrt(3) * 0.5 * axis_vals, 
    rep(0, times = length(axis_vals)), 
    sqrt(3) * 0.5 * (1 - axis_vals)
  ), # 始点のy軸の値
  y_1_end = c(
    axis_vals, 
    0.5 * axis_vals + 0.5, 
    0.5 * rev(axis_vals)
  ), # 終点のx軸の値
  y_2_end = c(
    rep(0, times = length(axis_vals)), 
    sqrt(3) * 0.5 * (1 - axis_vals), 
    sqrt(3) * 0.5 * rev(axis_vals)
  ), # 終点のy軸の値
  axis = c("x_1", "x_2", "x_3") |> 
    rep(each = length(axis_vals)) # 元の軸
)
ternary_grid_df
## # A tibble: 33 × 5
##    y_1_start y_2_start y_1_end y_2_end axis 
##        <dbl>     <dbl>   <dbl>   <dbl> <chr>
##  1      0       0          0         0 x_1  
##  2      0.05    0.0866     0.1       0 x_1  
##  3      0.1     0.173      0.2       0 x_1  
##  4      0.15    0.260      0.3       0 x_1  
##  5      0.2     0.346      0.4       0 x_1  
##  6      0.25    0.433      0.5       0 x_1  
##  7      0.3     0.520      0.6       0 x_1  
##  8      0.35    0.606      0.7       0 x_1  
##  9      0.4     0.693      0.8       0 x_1  
## 10      0.45    0.779      0.9       0 x_1  
## # … with 23 more rows

 $x_1$軸と$x_2$軸、$x_2$軸と$x_3$軸・$x_3$軸と$x_1$軸の対応する目盛を結ぶ線分(平行な線分)を描画するため、各グリッド線の始点の$y_1$軸・$y_2$軸の値をy_1_start列・y_2_start列、終点の$y_1$軸・$y_2$軸の値をy_1_end列・y_2_end列とする。

 軸ラベルを描画する用のデータフレームを作成する。

# 軸ラベル用の値を作成
ternary_axislabel_df <- tibble::tibble(
  y_1 = c(0.25, 0.5, 0.75),               # x軸の値
  y_2 = c(0.25*sqrt(3), 0, 0.25*sqrt(3)), # y軸の値
  label = c("x[1]", "x[2]", "x[3]"),      # 軸ラベル
  h = c(3, 0.5, -2),  # 水平方向の調整用の値
  v = c(0.5, 3, 0.5), # 垂直方向の調整用の値
  axis = c("x_1", "x_2", "x_3") # 元の軸
)
ternary_axislabel_df
## # A tibble: 3 × 6
##     y_1   y_2 label     h     v axis 
##   <dbl> <dbl> <chr> <dbl> <dbl> <chr>
## 1  0.25 0.433 x[1]    3     0.5 x_1  
## 2  0.5  0     x[2]    0.5   3   x_2  
## 3  0.75 0.433 x[3]   -2     0.5 x_3

 3つの軸それぞれの中点に軸ラベルを描画するために、各軸の中点の$y_1$軸・$y_2$軸の値をy_1列・y_2列とする。
 軸ラベルとして描画する文字列をlabel列に指定する。この例では、expression()の表記を利用する。
 ラベルを表示する際の水平・垂直方向の調整値をh列・v列として値を指定する。

 各軸の目盛ラベルを描画する用のデータフレームを作成する。

# 軸目盛ラベル用の値を作成
ternary_ticklabel_df <- tibble::tibble(
  y_1 = c(
    0.5 * axis_vals, 
    axis_vals, 
    0.5 * axis_vals + 0.5
  ), # x軸の値
  y_2 = c(
    sqrt(3) * 0.5 * axis_vals, 
    rep(0, times = length(axis_vals)), 
    sqrt(3) * 0.5 * (1 - axis_vals)
  ), # y軸の値
  label = c(
    rev(axis_vals), 
    axis_vals, 
    rev(axis_vals)
  ), # 軸目盛ラベル
  h = c(
    rep(1.5, times = length(axis_vals)), 
    rep(1.5, times = length(axis_vals)), 
    rep(-0.5, times = length(axis_vals))
  ), # 水平方向の調整用の値
  v = c(
    rep(0.5, times = length(axis_vals)), 
    rep(0.5, times = length(axis_vals)), 
    rep(0.5, times = length(axis_vals))
  ), # 垂直方向の調整用の値
  angle = c(
    rep(-60, times = length(axis_vals)), 
    rep(60, times = length(axis_vals)), 
    rep(0, times = length(axis_vals))
  ), # ラベルの表示角度
  axis = c("x_1", "x_2", "x_3") |> 
    rep(each = length(axis_vals)) # 元の軸
)
ternary_ticklabel_df
## # A tibble: 33 × 7
##      y_1    y_2 label     h     v angle axis 
##    <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl> <chr>
##  1  0    0        1     1.5   0.5   -60 x_1  
##  2  0.05 0.0866   0.9   1.5   0.5   -60 x_1  
##  3  0.1  0.173    0.8   1.5   0.5   -60 x_1  
##  4  0.15 0.260    0.7   1.5   0.5   -60 x_1  
##  5  0.2  0.346    0.6   1.5   0.5   -60 x_1  
##  6  0.25 0.433    0.5   1.5   0.5   -60 x_1  
##  7  0.3  0.520    0.4   1.5   0.5   -60 x_1  
##  8  0.35 0.606    0.3   1.5   0.5   -60 x_1  
##  9  0.4  0.693    0.2   1.5   0.5   -60 x_1  
## 10  0.45 0.779    0.1   1.5   0.5   -60 x_1  
## # … with 23 more rows

 3つの軸それぞれに目盛ラベルを描画するために、各軸のaxis_valsの位置に対応する$y_1$軸・$y_2$軸の値をy_1列・y_2列とする。この列はternary_grid_dfy_1_start, y_2_start列と一致する。
 目盛ラベルとして描画する値をlabel列として、ラベルの水平・垂直方向の調整値をh列・v列、角度をangle列として値を指定する。この例では、各軸の目盛ラベルとグリッド線が同じ角度になるように設定している。

 作成した4つのデータフレームを使って、三角図を作成する。

# 三角図の枠を作成
ggplot() + 
  geom_segment(data = ternary_grid_df, 
               mapping = aes(x = y_1_start, y = y_2_start, xend = y_1_end, yend = y_2_end), 
               color = "gray50", linetype = "dashed") + # 三角図のグリッド線
  geom_segment(data = ternary_axis_df, 
               mapping = aes(x = y_1_start, y = y_2_start, xend = y_1_end, yend = y_2_end), 
               color = "gray50") + # 三角図の枠線
  geom_text(data = ternary_ticklabel_df, 
            mapping = aes(x = y_1, y = y_2, label = label, hjust = h, vjust = v, angle = angle)) + # 三角図の軸目盛ラベル
  geom_text(data = ternary_axislabel_df, 
            mapping = aes(x = y_1, y = y_2, label = label, hjust = h, vjust = v), 
            parse = TRUE, size = 6) + # 三角図の軸ラベル
  scale_x_continuous(breaks = c(0, 0.5, 1), labels = NULL) + # x軸
  scale_y_continuous(breaks = c(0, 0.25*sqrt(3), 0.5*sqrt(3)), labels = NULL) + # y軸
  coord_fixed(ratio = 1, clip = "off") + # アスペクト比
  theme(
    axis.ticks = element_blank(), # 目盛の指示線
    panel.grid.minor = element_blank() # 補助目盛のグリッド線
  ) + # 図の体裁
  labs(title = "Ternary Plot", 
       subtitle = parse(text = "x==(list(x[1], x[2], x[3]))"), 
       x = "", y = "")

三角図の座標

 geom_segment()で軸線とグリッド線を描画する。始点の値の引数x, yy_1_start, y_2_start列、終点の値の引数xend, yendy_1_end, y_2_end列を指定して、線分を描画する。
 geom_text()で軸ラベルと目盛ラベルを描画する。プロット位置の引数x, yy_1, y_2列、表示する文字列の引数labellabel列、水平・垂直方向の調整用の引数hjust, vjusth, j列、ラベルの表示角度の引数angleangle列を指定して、文字列を描画する。
 ここまでで、三角図の座標を描画できた。続いて、グラフ全体の体裁を整える。

 グラフ全体における(連続値をとる)x軸とy軸については、scale_*_continuous()で設定できる。主目盛(のラベルや補助線)の位置をbreaks引数に指定する。この例では、三角図の最小値・中央値・最大値とした。
 正三角形になるようにcoord_fixed()ratio = 1を指定して、アスペクト比を1に設定する。clip = "off"を指定すると、グラフ領域内のラベルなどを余白領域にはみ出して表示できる。
 theme()の目盛の指示線の引数axis.ticksと補助目盛のグリッド線の引数panel.grid.minorelement_blank()を指定して非表示にする。

 各軸と対応するグリッド線の関係が分かりにくいので、色分けして確認する。

# 三角図の各軸を可視化
ggplot() + 
  geom_segment(data = ternary_axis_df, 
               mapping = aes(x = y_1_start, y = y_2_start, xend = y_1_end, yend = y_2_end), 
               color = "gray50") + # 三角図の枠線
  geom_segment(data = ternary_grid_df, 
               mapping = aes(x = y_1_start, y = y_2_start, xend = y_1_end, yend = y_2_end, color = axis), 
               linetype = "dashed") + # 三角図のグリッド線
  geom_text(data = ternary_ticklabel_df, 
            mapping = aes(x = y_1, y = y_2, label = label, hjust = h, vjust = v, angle = angle, color = axis)) + # 三角図の軸目盛ラベル
  geom_text(data = ternary_axislabel_df, 
            mapping = aes(x = y_1, y = y_2, label = label, hjust = h, vjust = v, color = axis), 
            parse = TRUE, size = 6) + # 三角図の軸ラベル
  scale_x_continuous(breaks = c(0, 0.5, 1), labels = NULL) + # x軸
  scale_y_continuous(breaks = c(0, 0.25*sqrt(3), 0.5*sqrt(3)), labels = NULL) + # y軸
  scale_color_manual(breaks = c("x_1", "x_2", "x_3"), values = c("red", "green4", "blue")) + # 線と文字の色
  coord_fixed(ratio = 1, clip = "off") + # アスペクト比
  theme(
    axis.ticks = element_blank(), # 目盛の指示線
    panel.grid.minor = element_blank(), # 補助目盛のグリッド線
    legend.position = "none" # 凡例の位置
  ) + # 図の体裁
  labs(title = "Ternary Plot", 
       subtitle = parse(text = "x==(list(x[1], x[2], x[3]))"), 
       x = "", y = "")

三角図の座標

 各関数のcolor引数にaxis列を指定し、scale_color_manual()breaks引数にaxis列の値、values引数に色を指定する。

 以上で、三角図における座標を作成できた。次は、三角座標上にグラフを描画する。

散布図

 三角座標上の散布図を作成する。

 例として利用するために、一様分布の乱数を生成して正規化する。

# データ数を指定
N <- 9

# 一様分布の乱数を生成
x_nk <- runif(n = N*3) |> 
  matrix(nrow = N, ncol = 3)

# 正規化
x_nk <- x_nk / rowSums(x_nk)
head(x_nk)
##           [,1]       [,2]      [,3]
## [1,] 0.5113079 0.38762507 0.1010671
## [2,] 0.2528108 0.48058910 0.2666001
## [3,] 0.4133917 0.27691043 0.3096978
## [4,] 0.1532591 0.16899243 0.6777485
## [5,] 0.1002832 0.36689829 0.5328185
## [6,] 0.3382020 0.09033587 0.5714621

 runif()でN×3個の一様乱数を生成して、N3列のマトリクスを作成する。各行が1つのサンプルに対応する。
 行(サンプル)ごとに、総和で割ることで総和が1になるように正規化する。行ごとの和は、rowSums()で計算できる。

 あるいは、ディリクレ分布の乱数を生成する。

# ディリクレ分布のパラメータを指定
alpha_k <- c(1, 1, 1)

# ディリクレ分布の乱数を生成
x_nk <- MCMCpack::rdirichlet(n = N, alpha = alpha_k)
head(x_nk)
##            [,1]        [,2]       [,3]
## [1,] 0.60554844 0.080815344 0.31363621
## [2,] 0.31504549 0.002726359 0.68222815
## [3,] 0.01359573 0.924689031 0.06171524
## [4,] 0.18793568 0.075148453 0.73691587
## [5,] 0.35656396 0.313215561 0.33022048
## [6,] 0.19848203 0.524209065 0.27730891

 ディリクレ分布の乱数(確率変数)は総和が1の値をとるので、正規化の必要がない。ディリクレ乱数は、MCMCpackパッケージのrdirichlet()で生成できる。サンプルサイズの引数nN、パラメータの引数alphaに設定したパラメータalpha_kを指定する。

 サンプルを三角座標に変換してデータフレームに格納する。

# 三角座標に変換して格納
data_df <- tibble::tibble(
  x_1 = x_nk[, 1], # 元のx軸の値
  x_2 = x_nk[, 2], # 元のy軸の値
  x_3 = x_nk[, 3], # 元のz軸の値
  y_1 = x_2 + 0.5 * x_3,     # 変換後のx軸の値
  y_2 = sqrt(3) * 0.5 * x_3, # 変換後のy軸の値
  label = paste0("(", round(x_1, 2), ", ", round(x_2, 2), ", ", round(x_3, 2), ")") # データラベル
)
data_df
## # A tibble: 9 × 6
##      x_1     x_2    x_3   y_1    y_2 label             
##    <dbl>   <dbl>  <dbl> <dbl>  <dbl> <chr>             
## 1 0.606  0.0808  0.314  0.238 0.272  (0.61, 0.08, 0.31)
## 2 0.315  0.00273 0.682  0.344 0.591  (0.32, 0, 0.68)   
## 3 0.0136 0.925   0.0617 0.956 0.0534 (0.01, 0.92, 0.06)
## 4 0.188  0.0751  0.737  0.444 0.638  (0.19, 0.08, 0.74)
## 5 0.357  0.313   0.330  0.478 0.286  (0.36, 0.31, 0.33)
## 6 0.198  0.524   0.277  0.663 0.240  (0.2, 0.52, 0.28) 
## 7 0.0927 0.502   0.405  0.705 0.351  (0.09, 0.5, 0.41) 
## 8 0.338  0.289   0.373  0.476 0.323  (0.34, 0.29, 0.37)
## 9 0.335  0.146   0.519  0.405 0.449  (0.34, 0.15, 0.52)

 x_nkの各列の値と変換後の値、またサンプルラベルとして値を文字列結合する。

 元の値を確認する必要がなければ、次のように格納する。

# 三角座標に変換して格納
data_df <- tibble::tibble(
  y_1 = x_nk[, 2] + 0.5 * x_nk[, 3], # 三角座標のx軸の値
  y_2 = sqrt(3) * 0.5 * x_nk[, 3],   # 三角座標のy軸の値
  label = paste0(
    "(", round(x_nk[, 1], 2), ", ", round(x_nk[, 2], 2), ", ", round(x_nk[, 3], 2), ")"
  ) # データラベル
)
data_df
## # A tibble: 9 × 3
##     y_1    y_2 label             
##   <dbl>  <dbl> <chr>             
## 1 0.238 0.272  (0.61, 0.08, 0.31)
## 2 0.344 0.591  (0.32, 0, 0.68)   
## 3 0.956 0.0534 (0.01, 0.92, 0.06)
## 4 0.444 0.638  (0.19, 0.08, 0.74)
## 5 0.478 0.286  (0.36, 0.31, 0.33)
## 6 0.663 0.240  (0.2, 0.52, 0.28) 
## 7 0.705 0.351  (0.09, 0.5, 0.41) 
## 8 0.476 0.323  (0.34, 0.29, 0.37)
## 9 0.405 0.449  (0.34, 0.15, 0.52)


 パラメータの値を数式で表示するための文字列を作成する。

# パラメータラベル用の文字列を作成
param_text <- paste0(
  "list(", 
  "alpha==(list(", paste0(alpha_k, collapse = ", "), "))", 
  ", N==", N, 
  ", x==(list(x[1], x[2], x[3]))", 
  ")"
)
param_text
## [1] "list(alpha==(list(1, 1, 1)), N==9, x==(list(x[1], x[2], x[3])))"

 ギリシャ文字などの記号を使った数式を表示する場合は、expression()の記法を使う。等号は"=="、複数の(数式上の)変数を並べるには"list(変数1, 変数2)"とする。(プログラム上の)変数の値を使う場合は、parse()text引数に指定する。

 三角図上に散布図を描画する。

# 散布図を作成
ggplot() + 
  geom_segment(data = ternary_grid_df, 
               mapping = aes(x = y_1_start, y = y_2_start, xend = y_1_end, yend = y_2_end), 
               color = "gray50", linetype = "dashed") + # 三角図のグリッド線
  geom_segment(data = ternary_axis_df, 
               mapping = aes(x = y_1_start, y = y_2_start, xend = y_1_end, yend = y_2_end), 
               color = "gray50") + # 三角図の枠線
  geom_text(data = ternary_ticklabel_df, 
            mapping = aes(x = y_1, y = y_2, label = label, hjust = h, vjust = v, angle = angle)) + # 三角図の軸目盛ラベル
  geom_text(data = ternary_axislabel_df, 
            mapping = aes(x = y_1, y = y_2, label = label, hjust = h, vjust = v), 
            parse = TRUE, size = 6) + # 三角図の軸ラベル
  geom_point(data = data_df, 
             mapping = aes(x = y_1, y = y_2), 
             color = "orange", size = 3) + # 観測データ
  ggrepel::geom_label_repel(data = data_df, 
                            mapping = aes(x = y_1, y = y_2, label = label), 
                            color = "orange", alpha = 0.9, size = 3) + # データラベル
  scale_x_continuous(breaks = c(0, 0.5, 1), labels = NULL) + # x軸
  scale_y_continuous(breaks = c(0, 0.25*sqrt(3), 0.5*sqrt(3)), labels = NULL) + # y軸
  coord_fixed(ratio = 1, clip = "off") + # アスペクト比
  theme(axis.ticks = element_blank(), 
        panel.grid.minor = element_blank()) + # 図の体裁
  labs(title = "Scatter Ternary Plot", 
       subtitle = parse(text = param_text), 
       x = "", y = "")

三角座標上の散布図

 データラベルの描画にはggrepelパッケージのgeom_label_repel()を利用する。

 同様に、色分けした座標に描画する。

# 三角図の各軸を可視化
ggplot() + 
  geom_segment(data = ternary_axis_df, 
               mapping = aes(x = y_1_start, y = y_2_start, xend = y_1_end, yend = y_2_end), 
               color = "gray50") + # 三角図の枠線
  geom_segment(data = ternary_grid_df, 
               mapping = aes(x = y_1_start, y = y_2_start, xend = y_1_end, yend = y_2_end, color = axis), 
               linetype = "dashed") + # 三角図のグリッド線
  geom_text(data = ternary_ticklabel_df, 
            mapping = aes(x = y_1, y = y_2, label = label, hjust = h, vjust = v, angle = angle, color = axis)) + # 三角図の軸目盛ラベル
  geom_text(data = ternary_axislabel_df, 
            mapping = aes(x = y_1, y = y_2, label = label, hjust = h, vjust = v, color = axis), 
            parse = TRUE, size = 6) + # 三角図の軸ラベル
  geom_point(data = data_df, 
             mapping = aes(x = y_1, y = y_2), 
             color = "orange", size = 3) + # 観測データ
  ggrepel::geom_label_repel(data = data_df, 
                            mapping = aes(x = y_1, y = y_2, label = label), 
                            color = "orange", alpha = 0.9, size = 3) + # データラベル
  scale_x_continuous(breaks = c(0, 0.5, 1), labels = NULL) + # x軸
  scale_y_continuous(breaks = c(0, 0.25*sqrt(3), 0.5*sqrt(3)), labels = NULL) + # y軸
  scale_color_manual(breaks = c("x_1", "x_2", "x_3"), values = c("red", "green4", "blue")) + # 線と文字の色
  coord_fixed(ratio = 1, clip = "off") + # アスペクト比
  theme(
    axis.ticks = element_blank(), # 軸目盛の指示線
    panel.grid.minor = element_blank(), # 軸目盛の補助線
    legend.position = "none" # 凡例の位置
  ) + # 図の体裁
  labs(title = "Scatter Ternary Plot", 
       subtitle = parse(text = param_text), 
       x = "", y = "")

三角座標上の散布図

 以上で、私が欲しいグラフが得られた。

散布図のアニメーション

 最後に、散布図のアニメーション(gif画像)を作成して、三角図の座標について確認する。

 $x_2$軸方向に点が移動するように値を指定する。

# フレーム数を指定
frame_num <- 61

# 3次元変数の値を指定
x_1_vals <- seq(from = 0.2, to = 0.8, length.out = frame_num)
x_2_vals <- rep(0.2, times = length(frame_num))
x_3_vals <- 1 - x_1_vals - x_2_vals

# 三角座標に変換して格納
anime_data_df <- tibble::tibble(
  y_1 = x_2_vals + 0.5 * x_3_vals, # x軸の値
  y_2 = sqrt(3) * 0.5 * x_3_vals, # y軸の値
  frame = paste0("(", round(x_1_vals, 2), ", ", round(x_2_vals, 2), ", ", round(x_3_vals, 2), ")") |> 
    factor(levels = paste0("(", round(x_1_vals, 2), ", ", round(x_2_vals, 2), ", ", round(x_3_vals, 2), ")")) # フレーム切替用ラベル
)
anime_data_df
## # A tibble: 61 × 3
##      y_1   y_2 frame            
##    <dbl> <dbl> <fct>            
##  1 0.5   0.520 (0.2, 0.2, 0.6)  
##  2 0.495 0.511 (0.21, 0.2, 0.59)
##  3 0.49  0.502 (0.22, 0.2, 0.58)
##  4 0.485 0.494 (0.23, 0.2, 0.57)
##  5 0.48  0.485 (0.24, 0.2, 0.56)
##  6 0.475 0.476 (0.25, 0.2, 0.55)
##  7 0.47  0.468 (0.26, 0.2, 0.54)
##  8 0.465 0.459 (0.27, 0.2, 0.53)
##  9 0.46  0.450 (0.28, 0.2, 0.52)
## 10 0.455 0.442 (0.29, 0.2, 0.51)
## # … with 51 more rows

 散布図のときと同様に値を変換してデータフレームに格納し、フレーム切替用のラベルを作成する。ラベルが文字列型だと文字列の基準で順序が決まるので、因子型にしてレベル(順序)を設定する。

 三角図のアニメーションを作成する。

# 散布図のアニメーションを作図
anime_graph <- ggplot() + 
  geom_segment(data = ternary_axis_df, 
               mapping = aes(x = y_1_start, y = y_2_start, xend = y_1_end, yend = y_2_end), 
               color = "gray50") + # 三角図の枠線
  geom_segment(data = ternary_grid_df, 
               mapping = aes(x = y_1_start, y = y_2_start, xend = y_1_end, yend = y_2_end, color = axis), 
               linetype = "dashed") + # 三角図のグリッド線
  geom_text(data = ternary_ticklabel_df, 
            mapping = aes(x = y_1, y = y_2, label = label, hjust = h, vjust = v, angle = angle, color = axis)) + # 三角図の軸目盛ラベル
  geom_text(data = ternary_axislabel_df, 
            mapping = aes(x = y_1, y = y_2, label = label, hjust = h, vjust = v, color = axis), 
            parse = TRUE, size = 6) + # 三角図の軸ラベル
  geom_point(data = anime_data_df, 
             mapping = aes(x = y_1, y = y_2), 
             color = "orange", size = 6) + 
  geom_label(data = anime_data_df, 
             mapping = aes(x = y_1, y = y_2, label = frame), 
             hjust = 0, vjust = -1, color = "orange", alpha = 0.8, size = 5) + 
  gganimate::transition_manual(frame) + # フレーム
  scale_x_continuous(breaks = c(0, 0.5, 1), labels = NULL) + # x軸
  scale_y_continuous(breaks = c(0, 0.25*sqrt(3), 0.5*sqrt(3)), labels = NULL) + # y軸
  scale_color_manual(breaks = c("x_1", "x_2", "x_3"), values = c("red", "green4", "blue")) + # 線と文字の色
  coord_fixed(ratio = 1, clip = "off") + # アスペクト比
  theme(axis.ticks = element_blank(), 
        panel.grid.minor = element_blank(), 
        legend.position = "none") + # 図の体裁
  labs(title = "Ternary Plot", 
       subtitle = parse(text = "x==(list(x[1], x[2], x[3]))"), 
       x = "", y = "")

# gif画像を作成
gganimate::animate(plot = anime_graph, nframes = frame_num, fps = 10, width = 600, height = 600)

三角座標上を一方向に移動する点

 gganimateパッケージのtransition_manual()にフレームを制御する列を指定して、animate()でgif画像に変換する。

 続いて、3つの軸の方向を順番に点が移動するように値を指定する。

# 3次元変数の値を指定
x_1_vals <- c(
  seq(0.2, 0.4, by = 0.01), 
  rev(seq(0.2, 0.39, by = 0.01)), 
  rep(0.2, times = 19)
)
x_2_vals <- c(
  rep(0.2, times = 21), 
  seq(0.21, 0.4, by = 0.01), 
  rev(seq(0.21, 0.39, by = 0.01))
)
x_3_vals <- c(
  rev(seq(0.4, 0.6, by = 0.01)), 
  rep(0.4, times = 20), 
  seq(from = 0.41, to = 0.59, by = 0.01)
)

# フレーム数を設定
frame_num <- length(x_1_vals)

 作図については同じコードで処理できる。

三角座標上を三方向に移動する点

 以上で、三角図上のデータ点と座標の関係を掴めた気がする。次は、等高線の作図を考える。

おわりに

 思ったよりも苦労したので記事にしておきます。普通の(動かない)グラフであれば普通に専用のパッケージを使えばいいと思います。

【次の内容】

www.anarchive-beta.com