からっぽのしょこ

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

【R】cos関数の可視化

はじめに

 R言語で三角関数の定義や公式を可視化しようシリーズです。

 この記事では、cos関数のグラフを作成します。

【前の内容】

www.anarchive-beta.com

【他の記事一覧】

www.anarchive-beta.com

【この記事の内容】

cos関数の可視化

 単位円における角度とcos関数(コサイン波・余弦波)の関係をグラフで可視化します。

 利用するパッケージを読み込みます。

# 利用パッケージ
library(tidyverse)
library(patchwork)
library(magick)

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

グラフの作成

 角度を固定したグラフを作成します。

変数の設定

 まずは、cos関数の変数である角度(ラジアン)を設定します。

 角度を指定します。

# 角度を指定
alpha <- -120

# ラジアンに変換
theta <- alpha / 180 * pi
theta
## [1] -2.094395

 度数法における角度$\alpha$を指定して、弧度法における角度(ラジアン)$\theta = \alpha \frac{2 \pi}{360}$に変換します。$\pi$は円周率でpiで扱えます。

・コード(クリックで展開)

 曲線上に関数の点を描画するためのデータフレームを作成します。

# 関数の点の描画用
point_df <- tibble::tibble(
  theta = theta, 
  sin_theta = sin(theta), 
  cos_theta = cos(theta)
)
point_df
## # A tibble: 1 × 3
##   theta sin_theta cos_theta
##   <dbl>     <dbl>     <dbl>
## 1 -2.09    -0.866      -0.5

 sin関数$\sin \theta$、cos関数$\cos \theta$を計算して、$\theta$と共に格納します。

 コサイン波の作図用のラジアンの値を作成します。

# 作図用のラジアンの値を作成
theta_vals <- seq(from = theta-pi, to = theta+pi, by = 0.01)
head(theta_vals)
## [1] -5.235988 -5.225988 -5.215988 -5.205988 -5.195988 -5.185988

 この例では、指定した$\theta$を中心に前後$\pi$を範囲とします。範囲(サイズ)を$2 \pi$にすると、1周期分の曲線を描画できます。

 x軸目盛として、角度ラベルを描画するのに用いるベクトルを作成します。

# x軸目盛ラベルの描画用の値を作成
tick_min <- floor(min(theta_vals) * 6 / pi)
tick_vec <- seq(from = tick_min, to = tick_min+13, by = 1)
tick_vec
##  [1] -10  -9  -8  -7  -6  -5  -4  -3  -2  -1   0   1   2   3

 単位円の目盛と同様に、$30^{\circ} = \frac{\pi}{6}$間隔で目盛を表示することにします。$\frac{n}{6} \pi$の形で目盛を描画するために、作図用の角度$\theta$の最小値を、floor()で小数点以下を切り捨てて、$\frac{6}{\pi}$倍して、最小値の$n$を求めてtick_minとします。
 theta_valsのサイズが$2 \pi$の場合は、$n$から$n+13$までの整数を生成してtick_vecとします。

 コサイン波を描画するためのデータフレームを作成します。

# コサイン波の描画用
cos_curve_df <- tibble::tibble(
  theta = theta_vals, 
  cos_theta = cos(theta_vals)
)
cos_curve_df
## # A tibble: 629 × 2
##    theta cos_theta
##    <dbl>     <dbl>
##  1 -5.24     0.500
##  2 -5.23     0.491
##  3 -5.22     0.483
##  4 -5.21     0.474
##  5 -5.20     0.465
##  6 -5.19     0.456
##  7 -5.18     0.447
##  8 -5.17     0.438
##  9 -5.16     0.429
## 10 -5.15     0.420
## # … with 619 more rows

 作図用の$\theta$の値と$\cos \theta$の値を格納します。


単位円の作図

 次に、cos関数の変数である角度を単位円上で確認するためのグラフを作成します。

・コード(クリックで展開)

 単位円を描画するためのデータフレームと、単位円上に角度目盛を描画するためのデータフレームを作成します。

# 単位円の描画用
circle_df <- tibble::tibble(
  theta = seq(from = 0, to = 2*pi, by = 0.04), 
  sin_theta = sin(theta), 
  cos_theta = cos(theta)
)

# 角度目盛の描画用
tick_df <- tibble::tibble(
  alpha = 0:11 * 30, 
  theta = 0:11 / 6 * pi, 
  x = cos(theta), 
  y = sin(theta), 
  rad_label = paste0("frac(", 0:11, ", 6)~pi")
)
circle_df; tick_df
## # A tibble: 158 × 3
##    theta sin_theta cos_theta
##    <dbl>     <dbl>     <dbl>
##  1  0       0          1    
##  2  0.04    0.0400     0.999
##  3  0.08    0.0799     0.997
##  4  0.12    0.120      0.993
##  5  0.16    0.159      0.987
##  6  0.2     0.199      0.980
##  7  0.24    0.238      0.971
##  8  0.28    0.276      0.961
##  9  0.32    0.315      0.949
## 10  0.36    0.352      0.936
## # … with 148 more rows
## # A tibble: 12 × 5
##    alpha theta         x         y rad_label     
##    <dbl> <dbl>     <dbl>     <dbl> <chr>         
##  1     0 0      1   e+ 0  0        frac(0, 6)~pi 
##  2    30 0.524  8.66e- 1  5   e- 1 frac(1, 6)~pi 
##  3    60 1.05   5   e- 1  8.66e- 1 frac(2, 6)~pi 
##  4    90 1.57   6.12e-17  1   e+ 0 frac(3, 6)~pi 
##  5   120 2.09  -5   e- 1  8.66e- 1 frac(4, 6)~pi 
##  6   150 2.62  -8.66e- 1  5   e- 1 frac(5, 6)~pi 
##  7   180 3.14  -1   e+ 0  1.22e-16 frac(6, 6)~pi 
##  8   210 3.67  -8.66e- 1 -5   e- 1 frac(7, 6)~pi 
##  9   240 4.19  -5.00e- 1 -8.66e- 1 frac(8, 6)~pi 
## 10   270 4.71  -1.84e-16 -1   e+ 0 frac(9, 6)~pi 
## 11   300 5.24   5   e- 1 -8.66e- 1 frac(10, 6)~pi
## 12   330 5.76   8.66e- 1 -5.00e- 1 frac(11, 6)~pi

 単位円の作図用のラジアン$0 \leq \theta < 2 \pi$を作成して、$\sin \theta, \cos \theta$を計算します。単位円の座標は、x軸の値$x = \cos \theta$、y軸の値$y = \sin \theta$に対応します。
 $30^{\circ} = 30 \frac{2 \pi}{360} = \frac{\pi}{6}$間隔で、$\frac{n}{6} \pi\ (n = 0, \dots, 11)$の形で目盛を表示することにします。単位円と同様に、x軸とy軸の値を計算して、ラベルと合わせて格納します。ラベルとして数式を表示する場合は、expression()の記法を使います。

 角度を表示するためのデータフレームを作成します。

# 半径の描画用
radius_df <- tibble::tibble(
  x_from = c(0, 0), 
  y_from = c(0, 0), 
  x_to = c(1, cos(theta)), 
  y_to = c(0, sin(theta))
)

# 角度マークの描画用
d <- 0.1
angle_df <- tibble::tibble(
  theta = seq(from = min(0, theta), to = max(0, theta), length.out = 100), 
  x = cos(theta) * d, 
  y = sin(theta) * d
)

# 角度ラベルの描画用
d <- 0.2
angle_label_df <- tibble::tibble(
  theta = 0.5 * theta, 
  x = cos(theta) * d, 
  y = sin(theta) * d, 
  angle_label = "theta"
)
radius_df; angle_df; angle_label_df
## # A tibble: 2 × 4
##   x_from y_from  x_to   y_to
##    <dbl>  <dbl> <dbl>  <dbl>
## 1      0      0   1    0    
## 2      0      0  -0.5 -0.866
## # A tibble: 100 × 3
##    theta       x       y
##    <dbl>   <dbl>   <dbl>
##  1 -2.09 -0.05   -0.0866
##  2 -2.07 -0.0482 -0.0876
##  3 -2.05 -0.0463 -0.0886
##  4 -2.03 -0.0444 -0.0896
##  5 -2.01 -0.0425 -0.0905
##  6 -1.99 -0.0406 -0.0914
##  7 -1.97 -0.0386 -0.0922
##  8 -1.95 -0.0367 -0.0930
##  9 -1.93 -0.0347 -0.0938
## 10 -1.90 -0.0327 -0.0945
## # … with 90 more rows
## # A tibble: 1 × 4
##   theta     x      y angle_label
##   <dbl> <dbl>  <dbl> <chr>      
## 1 -1.05   0.1 -0.173 theta

 $0^{\circ}, \alpha$のそれぞれの角度における半径を描画するために、原点と単位円上の点を結ぶ線分の座標を格納してradius_dfとします。始点の座標をx_from, y_from、終点の座標をx_to, y_to列とします。
 扇形(角度マーク)を描画するための値(ラジアン)を作成して、x軸とy軸の値を計算してangle_dfとします。$\theta$が正の値($\theta > 0$)のときは0から$\theta$、負の値($\theta < 0$)のときは$\theta$から0の値を使います。
 角度の中点に配置するように座標とラベル用の文字列を格納してangle_label_dfとします。

 単位円におけるcos関数の値を描画するためのデータフレームを作成します。

# cos関数の線の描画用
cos_line_df <- tibble::tibble(
  x_from = 0, 
  y_from = sin(theta), 
  x_to = cos(theta), 
  y_to = sin(theta)
)

# cos関数の線ラベルの描画用
cos_label_df <- cos_line_df |> 
  dplyr::mutate(
    # 中点に配置
    x = median(c(x_from, x_to)), 
    y = median(c(y_from, y_to)), 
    fnc_label = paste0("cos~theta")
  ) |> 
  dplyr::select(x, y, fnc_label)
cos_line_df; cos_label_df
## # A tibble: 1 × 4
##   x_from y_from  x_to   y_to
##    <dbl>  <dbl> <dbl>  <dbl>
## 1      0 -0.866  -0.5 -0.866
## # A tibble: 1 × 3
##       x      y fnc_label
##   <dbl>  <dbl> <chr>    
## 1 -0.25 -0.866 cos~theta

 cos関数の値は、角度$\theta$における単位円上の点$(\cos \theta, \sin \theta)$と、$y = 0$の直線への垂線との交点$(0, \sin \theta)$を結ぶ線分で表現できます。この2点の座標を格納してcos_line_dfとします。
 cos関数の水平線(線分)の中点に配置するように座標とラベル用の文字列を格納してcos_label_dfとします。

 グラフの描画範囲を固定するための値を設定します。

# 描画領域のサイズを設定
draw_size   <- 1
margin_size <- 0.5
axis_size   <- draw_size + margin_size

 曲線の描画範囲の0.5倍(単位円の半径)の値をdraw_size、曲線の最小値・最大値からx軸・y軸までの範囲をmargin_sizeとして指定し、またグラフ全体のサイズの0.5倍の値(2つの和)をaxis_sizeとします。

 単位円上の点とcos関数上の点を結ぶ線(の一部)を描画するためのデータフレームを作成します。

# サイン波との対応用
l <- 1
segment_circle_df <- tibble::tibble(
  x_from = cos(theta), 
  y_from = sin(theta), 
  x_to = cos(theta), 
  y_to_top = axis_size+l, # (上側に配置する場合)
  y_to_bottom = -axis_size-l # (下側に配置する場合)
)
segment_circle_df
## # A tibble: 1 × 5
##   x_from y_from  x_to y_to_top y_to_bottom
##    <dbl>  <dbl> <dbl>    <dbl>       <dbl>
## 1   -0.5 -0.866  -0.5      2.5        -2.5

 単位円上の点からy軸への垂線を引くように座標を指定します。作図時に、隣のグラフに届かないような長さlを指定します。
 コサイン波のグラフを上側・下側に配置する場合の2通りの値を格納しておきます。

 単位円のグラフを作成します。

# 関数ラベルを作成
fnc_label <- paste0(
  "list(", 
  "theta*degree==", round(theta*180/pi, digits = 2), "*degree", 
  ", theta==", round(theta/pi, digits = 2), "*pi", 
  ", sin~theta==", round(sin(theta), digits = 2), 
  ", cos~theta==", round(cos(theta), digits = 2), 
  ")"
)

# 単位円を作図
d <- 1.1
circle_graph_top <- ggplot() + 
  geom_path(data = circle_df, 
            mapping = aes(x = cos_theta, y = sin_theta), 
            size = 1) + # 単位円
  geom_text(data = tick_df, 
            mapping = aes(x = x, y = y, angle = alpha+90), 
            label = "|", size = 2) + # 角度目盛
  geom_text(data = tick_df, 
            mapping = aes(x = x*d, y = y*d, label = rad_label, 
                          hjust = 1-(x*0.5+0.5), vjust = 1-(y*0.5+0.5)), parse = TRUE) + # 角度目盛ラベル
  geom_segment(data = radius_df, 
               mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to)) + # 半径
  geom_path(data = angle_df, 
            mapping = aes(x = x, y = y)) + # 角度マーク
  geom_text(data = angle_label_df, 
            mapping = aes(x = x, y = y, label = angle_label), parse = TRUE) + # 角度ラベル
  geom_segment(data = cos_line_df, 
               mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), 
               color = "red", size = 1) + # sin関数
  geom_text(data = cos_label_df, 
            mapping = aes(x = x, y = y, label = fnc_label), parse = TRUE, 
            vjust = 1, color = "red") + # sin関数ラベル
  geom_segment(data = segment_circle_df, 
               mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to_top), 
               size = 1, linetype = "dotted") + # sin波との対応線:(上側に配置する場合)
  #geom_segment(data = segment_circle_df, 
  #             mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to_bottom), 
  #             size = 1, linetype = "dotted") + # sin波との対応線:(下側に配置する場合)
  geom_point(data = point_df, 
             mapping = aes(x = cos_theta, y = sin_theta), 
             size = 4) + # 単位円上の点
  coord_fixed(ratio = 1, clip = "off", 
              xlim = c(-axis_size, axis_size), ylim = c(-axis_size, axis_size)) + # 描画領域
  labs(title = "unit circle", 
       subtitle = parse(text = fnc_label), 
       x = "x", y = "y")
#circle_graph_top

 x軸を$\cos \theta$、y軸を$\sin \theta$として、(geom_line()ではなく)geom_path()で曲線(円)を描画します。綺麗な円を描画するにはcoord_***()のアスペクト比の引数ratio1にします。
 ラベル配置位置の調整用の値をdとして、tick_dfを使って角度ラベルを描画します。また、x軸・y軸方向の配置位置の調整用の引数hjust, yjustに、x, y列の値を使って指定しています。x, y列($\sin \theta, \cos \theta$)は―1から1の値をとります。0.5を掛けると-0.5から0.5の値となり、さらに0.5を足すと0から1の値になります。(hjust, vjustを指定せずにdを少し大きくしても同様の図になります。)\  半径などの線分をgeom_segment()で描画します。


単位円のグラフ

 コサイン波のグラフを上側・下側に配置する場合の2つの図を作成しておきます。

コサイン波の作図:上に配置

 続いて、単位円の上にコサイン波を配置したグラフを作成します。

・コード(クリックで展開)

 cos関数上の点と単位円上の点を結ぶ線(の一部)を描画するためのデータフレームを作成します。

# x軸(角度)・単位円との対応用
d <- 1.1
l <- 0.2
segment_cos_df <- tibble::tibble(
  x_from = c(cos(theta), cos(theta)), 
  y_from = c(theta, theta), 
  x_to = c(-axis_size*d, cos(theta)), 
  y_to = c(theta, max(theta_vals)+l)
)
segment_cos_df
## # A tibble: 2 × 4
##   x_from y_from  x_to  y_to
##    <dbl>  <dbl> <dbl> <dbl>
## 1   -0.5  -2.09 -1.65 -2.09
## 2   -0.5  -2.09 -0.5   1.24

 曲線上の点からx軸とy軸への垂線を引くように座標を指定します。

 コサイン波のグラフを作成します。

# 関数ラベルを作成
fnc_label <- paste0(
  "list(", 
  "theta==", round(theta/pi, digits = 2), "*pi", 
  ", cos~theta==", round(cos(theta), digits = 2), 
  ")"
)

# コサイン波を作図
cos_graph <- ggplot() + 
  geom_path(data = cos_curve_df, 
            mapping = aes(x = cos_theta, y = theta), 
            color = "red", size = 1) + # cos関数
  geom_segment(data = segment_cos_df, 
               mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), 
               size = 1, linetype = "dotted") + # 単位円との対応線
  geom_point(data = point_df, 
             mapping = aes(x = cos_theta, y = theta), 
             size = 4) + # 関数曲線上の点
  scale_y_reverse(breaks = tick_vec/6*pi, 
                  labels = parse(text = paste0("frac(", tick_vec, ", 6)~pi"))) + # 角度目盛ラベル
  coord_fixed(ratio = 1, clip = "off", 
              xlim = c(-axis_size, axis_size), ylim = c(max(theta_vals), min(theta_vals))) + # 描画領域
  labs(title = "cosine curve", 
       subtitle = parse(text = fnc_label), 
       x = expression(cos(theta)), y = expression(theta))
cos_graph

cos関数のグラフ

 x軸を$\cos \theta$、y軸を$\theta$としてgeom_path()で曲線を描画します。
 他の曲線と形(向き)を合わせるために、scale_y_reverse()でy軸を反転させます。また、目盛の表示位置の引数breakstick_vecを$\frac{\pi}{6}$倍した値、目盛ラベルの引数labelsに$\frac{n}{6} \pi$の形の文字列を指定します。

 2つのグラフを並べて描画します。

# グラフを並べて描画
graph <- patchwork::wrap_plots(cos_graph, circle_graph_top, nrow = 2, ncol = 1)
#graph

 patchworkパッケージのwrap_plots()を使ってグラフを並べます。


cos関数と単位円における角度の関係

 単位円上の点の横幅がコサイン波(cos関数)に対応しているのを確認できます。

コサイン波の作図:下に配置

 同様に、単位円の下にコサイン波を配置します。

・コード(クリックで展開)

 先ほどと同様に、cos関数上の点と単位円上の点を結ぶ線(の一部)を描画するためのデータフレームを作成します。

# x軸(角度)・単位円との対応用
d <- 1.1
l <- 0.7
segment_cos_df <- tibble::tibble(
  x_from = c(cos(theta), cos(theta)), 
  y_from = c(theta, theta), 
  x_to = c(-axis_size*d, cos(theta)), 
  y_to = c(theta, min(theta_vals)-l)
)
segment_cos_df
## # A tibble: 2 × 4
##   x_from y_from  x_to  y_to
##    <dbl>  <dbl> <dbl> <dbl>
## 1   -0.5  -2.09 -1.65 -2.09
## 2   -0.5  -2.09 -0.5  -5.94

 曲線上の点からx軸の反対側とy軸への垂線を引くように座標を指定します。

 「上に配置」の作図コードで、コサイン波のグラフを作成できます。

cos関数のグラフ

 x軸を$\theta$、y軸を$\sin \theta$としてgeom_line()で曲線を描画します。
 scale_x_continuous()でx軸目盛を設定します。目盛の表示位置の引数breakstick_vecを$\frac{\pi}{6}$倍した値、目盛ラベルの引数labelsに$\frac{n}{6} \pi$の形の文字列を指定します。

 2つのグラフを並べて描画します。

# グラフを並べて描画
graph <- patchwork::wrap_plots(circle_graph_bottom, cos_graph, nrow = 2, ncol = 1)
#graph



cos関数と単位円における角度の関係


コサイン波の作図:左上に配置

 ここまでは、コサイン波が縦方向に伸びるグラフを作成しました。今度は、横方向に伸びるグラフを単位円の左上に配置したグラフを作成します。

・コード(クリックで展開)

 コサイン波のグラフの前に、単位円における横軸の値を、関数曲線における縦軸の値に変換するためのグラフを用意します。

 x軸の値を90度回転する線を描画するためのデータフレームを作成します。

# cos関数の値の変換線の描画用
adapt_line_df <- tibble::tibble(
  rad = seq(from = 0, to = 0.5*pi, length.out = 50), 
  x = cos(rad) * (axis_size+cos(theta)) - axis_size, 
  y = sin(rad) * (axis_size+cos(theta)) - axis_size
)
adapt_line_df
## # A tibble: 50 × 3
##       rad      x     y
##     <dbl>  <dbl> <dbl>
##  1 0      -0.5   -1.5 
##  2 0.0321 -0.501 -1.47
##  3 0.0641 -0.502 -1.44
##  4 0.0962 -0.505 -1.40
##  5 0.128  -0.508 -1.37
##  6 0.160  -0.513 -1.34
##  7 0.192  -0.518 -1.31
##  8 0.224  -0.525 -1.28
##  9 0.256  -0.533 -1.25
## 10 0.289  -0.541 -1.22
## # … with 40 more rows

 単位円の第1象限に対応する値$0 \leq \theta \leq \frac{\pi}{2}$を作成して、x軸とy軸の値を計算します。その際に、x軸とy軸の値にaxis_size + cos(theta)を掛けることで、半径がaxis_size + cos(theta)の円になります。さらに、x軸とy軸の値からaxis_sizeを引くことで、円全体をaxis_sizeだけ左と下方向に並行移動します。axis_size + cos(theta)axis_sizeが移動分に対応し、グラフの左下を円の中心(扇形の頂点)として、$\cos \theta$を90度回転する曲線になります。

 単位円とコサイン波との変換図のグリッド線を描画するためのデータフレームを作成します。

# 変換図のグリッド線の描画用
adapt_grid_df <- tidyr::expand_grid(
  d = axis_size + seq(from = -1, to = 1, by = 0.5), # グリッド線の位置を指定
  rad = seq(from = 0, to = 0.5*pi, length.out = 50)
) |> # グリッド線の数に応じてラジアンを複製
  dplyr::mutate(
    x = cos(rad) * d - axis_size, 
    y = sin(rad) * d - axis_size
  )
adapt_grid_df
## # A tibble: 250 × 4
##        d    rad     x     y
##    <dbl>  <dbl> <dbl> <dbl>
##  1   0.5 0      -1    -1.5 
##  2   0.5 0.0321 -1.00 -1.48
##  3   0.5 0.0641 -1.00 -1.47
##  4   0.5 0.0962 -1.00 -1.45
##  5   0.5 0.128  -1.00 -1.44
##  6   0.5 0.160  -1.01 -1.42
##  7   0.5 0.192  -1.01 -1.40
##  8   0.5 0.224  -1.01 -1.39
##  9   0.5 0.256  -1.02 -1.37
## 10   0.5 0.289  -1.02 -1.36
## # … with 240 more rows

 x軸の値が$\{-1, -0.5, 0, 0.5, 1\}$の5つの値に関してグリッド線を表示することにします。
 0.5間隔の値をseq(from = -1, to = 1, by = 0.5)で作成して、先ほどと同様に、axis_sizeを足してx軸とy軸の値を計算します。その際に、expand_grid()で「曲線描画用の値(rad列の値)」を「グリッド線の数(d列の値の数)」に応じて複製します。

 変換図上の点とcos関数上の点・単位円上の点を結ぶ線(の一部)を描画するためのデータフレームを作成します。

# 単位円とコサイン波の対応用
l <- 0.5
segment_adapt_df <- tibble::tibble(
  x = c(cos(theta), -axis_size), 
  y = c(-axis_size, cos(theta)), 
  x_to = c(cos(theta), -axis_size-l), 
  y_to = c(-axis_size-l, cos(theta))
)
segment_adapt_df
## # A tibble: 2 × 4
##       x     y  x_to  y_to
##   <dbl> <dbl> <dbl> <dbl>
## 1  -0.5  -1.5  -0.5  -2  
## 2  -1.5  -0.5  -2    -0.5

 曲線(扇形の孤)を延長するように座標を指定します。

 変換図を作成します。

# 変換グラフを作図
adapt_graph <- ggplot() + 
  geom_line(data = adapt_grid_df, 
            mapping = aes(x = x, y = y, group = d), 
            color = "white") + # グリッド線
  geom_segment(data = segment_adapt_df, 
               mapping = aes(x = x, y = y, xend = x_to, yend = y_to), 
               size = 1, linetype = "dotted") + # 単位円・コサイン波との対応線
  geom_line(data = adapt_line_df, 
            mapping = aes(x = x, y = y), 
            size = 1, linetype = "dotted") + # cos関数の値の線
  geom_point(data = segment_adapt_df, 
             mapping = aes(x = x, y = y), 
             size = 4) + # cos関数の値
  #theme(panel.grid = element_blank()) + # 元のグリッド線を非表示
  coord_fixed(ratio = 1, clip = "off", 
              xlim = c(-axis_size, axis_size), ylim = c(-axis_size, axis_size)) + # 描画領域
  labs(x = "x", y = "x")
adapt_graph

変換グラフ


 cos関数上の点と変換図上の点を結ぶ線(の一部)を描画するためのデータフレームを作成します。

# x軸(角度)・単位円との対応用
d <- 1.1
l <- 0.5
segment_cos_df <- tibble::tibble(
  x_from = c(theta, theta), 
  y_from = c(cos(theta), cos(theta)), 
  x_to = c(theta, max(theta_vals)+l), 
  y_to = c(-axis_size*d, cos(theta))
)
segment_cos_df
## # A tibble: 2 × 4
##   x_from y_from  x_to  y_to
##    <dbl>  <dbl> <dbl> <dbl>
## 1  -2.09   -0.5 -2.09 -1.65
## 2  -2.09   -0.5  1.54 -0.5

 曲線上の点からx軸とy軸の反対側への垂線を引くように座標を指定します。

 コサイン波のグラフを作成します。

# 関数ラベルを作成
fnc_label <- paste0(
  "list(", 
  "theta==", round(theta/pi, digits = 2), "*pi", 
  ", cos~theta==", round(cos(theta), digits = 2), 
  ")"
)

# コサイン波を作図
cos_graph <- ggplot() + 
  geom_line(data = cos_curve_df, 
            mapping = aes(x = theta, y = cos_theta), 
            color = "red", size = 1) + # cos関数
  geom_segment(data = segment_cos_df, 
               mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), 
               size = 1, linetype = "dotted") + # 単位円との対応線
  geom_point(data = point_df, 
             mapping = aes(x = theta, y = cos_theta), 
             size = 4) + # 関数曲線上の点
  scale_x_continuous(breaks = tick_vec/6*pi, 
                     labels = parse(text = paste0("frac(", tick_vec, ", 6)~pi"))) + # 角度目盛ラベル
  coord_fixed(ratio = 1, clip = "off", 
              xlim = c(min(theta_vals), max(theta_vals)), ylim = c(-axis_size, axis_size)) + # 描画領域
  labs(title = "cosine curve", 
       subtitle = parse(text = fnc_label), 
       x = expression(theta), y = expression(cos(theta)))
cos_graph

cos関数のグラフ

 x軸を$\theta$、y軸を$\cos \theta$としてgeom_line()(またはgeom_path())で曲線を描画します。
 「上に配置」のときと同様に、scale_x_continuous()でx軸目盛を設定します。

 2つのグラフを並べて描画します。

# グラフを並べて描画
graph <- patchwork::wrap_plots(
  cos_graph, adapt_graph, patchwork::plot_spacer(), circle_graph_top, 
  nrow = 2, ncol = 2
)
#graph

 グラフを配置しない位置をplot_spacer()で指定します。


cos関数と単位円における角度の関係

  単位円上の点の横幅がコサイン波(cos関数)の高さに対応しているのを確認できます。

コサイン波の作図:右下に配置

 同様に、単位円の右下にコサイン波を配置します。

・コード(クリックで展開)

 x軸の値を90度変換する線を描画するためのデータフレームを作成します。

# cos関数の値の変換線の描画用
adapt_line_df <- tibble::tibble(
  rad = seq(from = pi, to = 1.5*pi, length.out = 50), 
  x = cos(rad) * (axis_size-cos(theta)) + axis_size, 
  y = sin(rad) * (axis_size-cos(theta)) + axis_size
)
adapt_line_df
## # A tibble: 50 × 3
##      rad      x     y
##    <dbl>  <dbl> <dbl>
##  1  3.14 -0.5   1.5  
##  2  3.17 -0.499 1.44 
##  3  3.21 -0.496 1.37 
##  4  3.24 -0.491 1.31 
##  5  3.27 -0.484 1.24 
##  6  3.30 -0.474 1.18 
##  7  3.33 -0.463 1.12 
##  8  3.37 -0.450 1.05 
##  9  3.40 -0.435 0.993
## 10  3.43 -0.417 0.931
## # … with 40 more rows

 こちらは、単位円の第3象限に対応する値$\pi \leq \theta \leq \frac{3 \pi}{2}$を作成して、グラフの右上が円の中心(扇形の頂点)となるように、x軸とy軸の値を計算します。

 単位円とコサイン波との変換図のグリッド線を描画するためのデータフレームを作成します。

# 変換図のグリッド線の描画用
adapt_grid_df <- tidyr::expand_grid(
  d = axis_size - seq(from = -1, to = 1, by = 0.5), # グリッド線の位置を指定
  rad = seq(from = pi, to = 1.5*pi, length.out = 50)
) |> # グリッド線の数に応じてラジアンを複製
  dplyr::mutate(
    x = cos(rad) * d + axis_size, 
    y = sin(rad) * d + axis_size
  )
adapt_grid_df
## # A tibble: 250 × 4
##        d   rad      x     y
##    <dbl> <dbl>  <dbl> <dbl>
##  1   2.5  3.14 -1     1.5  
##  2   2.5  3.17 -0.999 1.42 
##  3   2.5  3.21 -0.995 1.34 
##  4   2.5  3.24 -0.988 1.26 
##  5   2.5  3.27 -0.979 1.18 
##  6   2.5  3.30 -0.968 1.10 
##  7   2.5  3.33 -0.954 1.02 
##  8   2.5  3.37 -0.937 0.944
##  9   2.5  3.40 -0.918 0.866
## 10   2.5  3.43 -0.897 0.789
## # … with 240 more rows

 同様に処理します。

 変換図上の点とcos関数上の点・単位円上の点を結ぶ線(の一部)を描画するためのデータフレームを作成します。

# 単位円とコサイン波の対応用
l <- 0.5
segment_adapt_df <- tibble::tibble(
  x = c(cos(theta), axis_size), 
  y = c(axis_size, cos(theta)), 
  x_to = c(cos(theta), axis_size+l), 
  y_to = c(axis_size+l, cos(theta))
)
segment_adapt_df
## # A tibble: 2 × 4
##       x     y  x_to  y_to
##   <dbl> <dbl> <dbl> <dbl>
## 1  -0.5   1.5  -0.5   2  
## 2   1.5  -0.5   2    -0.5

 曲線(扇形の孤)を延長するように座標を指定します。

 「左上に配置」の作図コードで、変換図を作成できます。

変換グラフ


 cos関数上の点と変換図上の点を結ぶ線(の一部)を描画するためのデータフレームを作成します。

# x軸(角度)・単位円との対応用
d <- 1.1
l <- 0.8
segment_cos_df <- tibble::tibble(
  x_from = c(theta, theta), 
  y_from = c(cos(theta), cos(theta)), 
  x_to = c(theta, min(theta_vals)-l), 
  y_to = c(-axis_size*d, cos(theta))
)
segment_cos_df
## # A tibble: 2 × 4
##   x_from y_from  x_to  y_to
##    <dbl>  <dbl> <dbl> <dbl>
## 1  -2.09   -0.5 -2.09 -1.65
## 2  -2.09   -0.5 -6.04 -0.5

 曲線上の点からx軸・y軸への垂線を引くように座標を指定します。

 「左上に配置」の作図コードで、サイン波のグラフを作成できます。

cos関数のグラフ


 2つのグラフを並べて描画します。

# グラフを並べて描画
graph <- patchwork::wrap_plots(
  circle_graph_bottom, patchwork::plot_spacer(), 
  adapt_graph, cos_graph, widths = c(1, 2, 1, 2), 
  nrow = 2, ncol = 2
)
#graph



cos関数と単位円における角度の関係


アニメーションの作成:1周期

 次は、単位円と曲線上の点を変化させたアニメーションを作成します。

・コード(クリックで展開)

 フレーム数を指定して、角度として用いる値を作成します。

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

# 作図用のラジアンの値を作成
theta_vals <- seq(from = 0, to = 2*pi, length.out = frame_num+1)[1:frame_num]

# x軸目盛ラベルの描画用の値を作成
tick_min <- floor(min(theta_vals) * 6 / pi)
tick_vec <- seq(from = tick_min, to = tick_min+13, by = 1)

 フレーム数frame_numを指定して、$0$から$2 \pi$の範囲のframe_num個の値を作成します。$\cos 0 = \cos 2 \pi$なので、frame_num + 1個の等間隔に切り分けて最後の値$2 \pi$を除くと、最後のフレームと最初のフレームの繋がりが良くなります。

 theta_valsから順番に値を取り出して作図し、グラフを書き出します。

# 一時保存フォルダを指定
dir_path <- "tmp_folder"

# 描画領域のサイズを設定
draw_size   <- 1
margin_size <- 0.5
axis_size   <- draw_size + margin_size

# コサイン波の描画用
cos_curve_df <- tibble::tibble(
  theta = theta_vals, 
  cos_theta = cos(theta_vals)
)

# 角度ごとに作図
for(i in seq_along(theta_vals)) {
  
  # i番目のラジアンを抽出
  theta <- theta_vals[i]
  
  # 関数の点の描画用
  point_df <- tibble::tibble(
    theta = theta, 
    sin_theta = sin(theta), 
    cos_theta = cos(theta)
  )
  
  ## 単位円の作図:(上側・下側によって一部の処理を切り替える)
  
  # 半径の描画用
  radius_df <- tibble::tibble(
    x_from = c(0, 0), 
    y_from = c(0, 0), 
    x_to = c(1, cos(theta)), 
    y_to = c(0, sin(theta))
  )
  
  # 角度マークの描画用
  d <- 0.1
  angle_df <- tibble::tibble(
    theta = seq(from = min(0, theta), to = max(0, theta), length.out = 100), 
    x = cos(theta) * d, 
    y = sin(theta) * d
  )
  
  # 角度ラベルの描画用
  d <- 0.2
  angle_label_df <- tibble::tibble(
    theta = 0.5 * theta, 
    x = cos(theta) * d, 
    y = sin(theta) * d, 
    angle_label = "theta"
  )
  
  # cos関数の線の描画用
  cos_line_df <- tibble::tibble(
    x_from = 0, 
    y_from = sin(theta), 
    x_to = cos(theta), 
    y_to = sin(theta)
  )
  
  # cos関数の線ラベルの描画用
  cos_label_df <- cos_line_df |> 
    dplyr::mutate(
      # 中点に配置
      x = median(c(x_from, x_to)), 
      y = median(c(y_from, y_to)), 
      fnc_label = paste0("cos~theta")
    ) |> 
    dplyr::select(x, y, fnc_label)
  
  # サイン波との対応用
  l <- 0.9
  segment_circle_df <- tibble::tibble(
    x_from = cos(theta), 
    y_from = sin(theta), 
    x_to = cos(theta), 
    y_to = axis_size+l # (上側に配置する場合)
    #y_to = -axis_size-l # (下側に配置する場合)
  )
  
  # 関数ラベルを作成
  fnc_label <- paste0(
    "list(", 
    "theta*degree==", round(theta*180/pi, digits = 2), "*degree", 
    ", theta==", round(theta/pi, digits = 2), "*pi", 
    ", sin~theta==", round(sin(theta), digits = 2), 
    ", cos~theta==", round(cos(theta), digits = 2), 
    ")"
  )
  
  # 単位円を作図
  d <- 1.1
  circle_graph_top <- ggplot() +  # (上側に配置する場合)
  #circle_graph_bottom <- ggplot() +  # (下側に配置する場合)
    geom_path(data = circle_df, 
              mapping = aes(x = cos_theta, y = sin_theta), 
              size = 1) + # 単位円
    geom_text(data = tick_df, 
              mapping = aes(x = x, y = y, angle = alpha+90), 
              label = "|", size = 2) + # 角度目盛
    geom_text(data = tick_df, 
              mapping = aes(x = x*d, y = y*d, label = rad_label, 
                            hjust = 1-(x*0.5+0.5), vjust = 1-(y*0.5+0.5)), parse = TRUE) + # 角度目盛ラベル
    geom_segment(data = radius_df, 
                 mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to)) + # 半径
    geom_path(data = angle_df, 
              mapping = aes(x = x, y = y)) + # 角度マーク
    geom_text(data = angle_label_df, 
              mapping = aes(x = x, y = y, label = angle_label), parse = TRUE) + # 角度ラベル
    geom_segment(data = cos_line_df, 
                 mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), 
                 color = "red", size = 1) + # sin関数
    geom_text(data = cos_label_df, 
              mapping = aes(x = x, y = y, label = fnc_label), parse = TRUE, 
              vjust = 1, color = "red") + # sin関数ラベル
    geom_segment(data = segment_circle_df, 
                 mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), 
                 size = 1, linetype = "dotted") + # sin波との対応線
    geom_point(data = point_df, 
               mapping = aes(x = cos_theta, y = sin_theta), 
               size = 4) + # 単位円上の点
    coord_fixed(ratio = 1, clip = "off", 
                xlim = c(-axis_size, axis_size), ylim = c(-axis_size, axis_size)) + # 描画領域
    labs(title = "unit circle", 
         subtitle = parse(text = fnc_label), 
         x = "x", y = "y")
  
  ## コサイン波の作図:(上・下・左・右によって全部の処理を切り替える)
  
  # cos関数の値の変換線の描画用
  adapt_line_df <- tibble::tibble(
    rad = seq(from = 0, to = 0.5*pi, length.out = 50), 
    x = cos(rad) * (axis_size+cos(theta)) - axis_size, 
    y = sin(rad) * (axis_size+cos(theta)) - axis_size
  )
  
  # 単位円とコサイン波の対応用
  l <- 0.6
  segment_adapt_df <- tibble::tibble(
    x = c(cos(theta), -axis_size), 
    y = c(-axis_size, cos(theta)), 
    x_to = c(cos(theta), -axis_size-l), 
    y_to = c(-axis_size-l, cos(theta))
  )
  
  # 変換グラフを作図
  adapt_graph <- ggplot() + 
    geom_line(data = adapt_grid_df, 
              mapping = aes(x = x, y = y, group = d), 
              color = "white") + # グリッド線
    geom_segment(data = segment_adapt_df, 
                 mapping = aes(x = x, y = y, xend = x_to, yend = y_to), 
                 size = 1, linetype = "dotted") + # 単位円・コサイン波との対応線
    geom_line(data = adapt_line_df, 
              mapping = aes(x = x, y = y), 
              size = 1, linetype = "dotted") + # cos関数の値の線
    geom_point(data = segment_adapt_df, 
               mapping = aes(x = x, y = y), 
               size = 4) + # cos関数の値
    #theme(panel.grid = element_blank()) + # 元のグリッド線を非表示
    coord_fixed(ratio = 1, clip = "off", 
                xlim = c(-axis_size, axis_size), ylim = c(-axis_size, axis_size)) + # 描画領域
    labs(x = "x", y = "x")
  
  # x軸(角度)・単位円との対応用
  d <- 1.1
  l <- 0.5
  segment_cos_df <- tibble::tibble(
    x_from = c(theta, theta), 
    y_from = c(cos(theta), cos(theta)), 
    x_to = c(theta, max(theta_vals)+l), 
    y_to = c(-axis_size*d, cos(theta))
  )
  
  # 関数ラベルを作成
  fnc_label <- paste0(
    "list(", 
    "theta==", round(theta/pi, digits = 2), "*pi", 
    ", cos~theta==", round(cos(theta), digits = 2), 
    ")"
  )
  
  # コサイン波を作図
  cos_graph <- ggplot() + 
    geom_line(data = cos_curve_df, 
              mapping = aes(x = theta, y = cos_theta), 
              color = "red", size = 1) + # cos関数
    geom_segment(data = segment_cos_df, 
                 mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), 
                 size = 1, linetype = "dotted") + # 単位円との対応線
    geom_point(data = point_df, 
               mapping = aes(x = theta, y = cos_theta), 
               size = 4) + # 関数曲線上の点
    scale_x_continuous(breaks = tick_vec/6*pi, 
                       labels = parse(text = paste0("frac(", tick_vec, ", 6)~pi"))) + # 角度目盛ラベル
    coord_fixed(ratio = 1, clip = "off", 
                xlim = c(min(theta_vals), max(theta_vals)), ylim = c(-axis_size, axis_size)) + # 描画領域
    labs(title = "cosine curve", 
         subtitle = parse(text = fnc_label), 
         x = expression(theta), y = expression(cos(theta)))
  
  # グラフを並べて描画
  graph <- patchwork::wrap_plots(
    cos_graph, adapt_graph, patchwork::plot_spacer(), circle_graph_top, 
    nrow = 2, ncol = 2
  )
  
  ## 画像の保存
  
  # ファイルを書き出し
  file_path <- paste0(dir_path, "/", stringr::str_pad(i, width = nchar(frame_num), pad = "0"), ".png")
  ggplot2::ggsave(filename = file_path, plot = graph, width = 1500, height = 1000, units = "px", dpi = 100)
  
  # 途中経過を表示
  message("\r", i, "/", frame_num, appendLF = FALSE)
}

 単位円の作図に関して、上側・下側のどちらに配置するかによって、segment_circle_dfy_to列と、単位円のグラフの変数名circle_graph_***の処理を、コメントアウトなどで切り替える必要があります。
 コサイン波の作図に関して、どこに配置するかによって処理全体を書き換えてください(ほぼ同じコードを4つとも用意するのは面倒でした)。
 また、「グラフの作成」で作成したcircle_dftick_dfと、左・右に配置する場合はadapt_grid_dfも使います。

 変数の値(角度)ごとに「グラフの作成」と同様に処理します。作成したグラフをggsave()で保存します。
 保存用のフォルダは空である必要があります。また、画像ファイルの読み込み時に、文字列基準で書き出した順番になるようなファイル名である必要があります。
 ($\theta = 0$のときの作図では、angle_dfが1行しか値を持たないため、geom_path()で描画できずメッセージが表示されます。)

 アニメーション(gif画像)を作成します。

# ファイル名を取得
file_name_vec <- list.files(dir_path)

# ファイルパスを作成
file_path_vec <- paste0(dir_path, "/", file_name_vec)

# gif画像を作成
file_path_vec |> 
  magick::image_read() |> 
  magick::image_animate(fps = 1, dispose = "previous") |> 
  magick::image_write_gif(path = "cos_curve.gif", delay = 0.1) -> tmp_path

 list.files()dir_pathフォルダ内のファイル名を取得して、ファイルパスを作成します。
 image_read()で画像を読み込んで、image_animate()でgifファイルに変換して、image_write_gif()でgifファイルを書き出します。delay引数に1秒当たりのフレーム数の逆数を指定します。


cos関数と単位円における角度の関係

cos関数と単位円における角度の関係

 (図の作成時のミスでサブタイトル部分のラベルが重複してます。記事中のコードは直しましたが、図を作り直すのは手間なのでそのままです…)

アニメーションの作成:任意の周期

 最後に、角度を変化させたアニメーションで確認します。

 フレーム数と角度の範囲を指定して、角度として用いる値を作成します。

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

# 角度の範囲を指定
theta_min <- -2 * pi
theta_max <- 2 * pi

# フレームごとの角度を作成
theta_i <- seq(from = theta_min, to = theta_max, length.out = frame_num+1)[1:frame_num]
head(theta_i/pi*180)

 フレーム数frame_numと、角度(ラジアン)$\theta$の最小値theta_min・最大値theta_maxを指定して、frame_num個の等間隔の値を作成します。三角関数は$2 \pi$で1周期なので、最小値に$2 \pi$の$n$倍を加えた値を最大値にして、等間隔に切り分け最大値自体を除くと、最後のフレームと最初のフレームの繋がりが良くなります。

・コード(クリックで展開)

 theta_iから順番に値を取り出して作図し、グラフを書き出します。

# 一時保存フォルダを指定
dir_path <- "tmp_folder"

# 描画領域のサイズを設定
draw_size   <- 1
margin_size <- 0.5
axis_size   <- draw_size + margin_size

# 角度ごとに作図
for(i in 1:frame_num) {
  
  # i番目の角度を取得
  theta <- theta_i[i]
  
  # 関数の点の描画用
  point_df <- tibble::tibble(
    theta = theta, 
    sin_theta = sin(theta), 
    cos_theta = cos(theta)
  )
  
  ## 単位円の作図:(上側・下側によって一部の処理を切り替える)
  
  # 半径の描画用
  radius_df <- tibble::tibble(
    x_from = c(0, 0), 
    y_from = c(0, 0), 
    x_to = c(1, cos(theta)), 
    y_to = c(0, sin(theta))
  )
  
  # 角度マークの描画用
  d <- 0.1
  angle_df <- tibble::tibble(
    theta = seq(from = min(0, theta), to = max(0, theta), length.out = 100), 
    x = cos(theta) * d, 
    y = sin(theta) * d
  )
  
  # 角度ラベルの描画用
  d <- 0.2
  angle_label_df <- tibble::tibble(
    theta = 0.5 * theta, 
    x = cos(theta) * d, 
    y = sin(theta) * d, 
    angle_label = "theta"
  )
  
  # cos関数の線の描画用
  cos_line_df <- tibble::tibble(
    x_from = 0, 
    y_from = sin(theta), 
    x_to = cos(theta), 
    y_to = sin(theta)
  )
  
  # cos関数の線ラベルの描画用
  cos_label_df <- cos_line_df |> 
    dplyr::mutate(
      # 中点に配置
      x = median(c(x_from, x_to)), 
      y = median(c(y_from, y_to)), 
      fnc_label = paste0("cos~theta")
    ) |> 
    dplyr::select(x, y, fnc_label)
  
  # サイン波との対応用
  l <- 0.9
  segment_circle_df <- tibble::tibble(
    x_from = cos(theta), 
    y_from = sin(theta), 
    x_to = cos(theta), 
    y_to = axis_size+l # (上側に配置する場合)
    #y_to = -axis_size-l # (下側に配置する場合)
  )
  
  # 関数ラベルを作成
  fnc_label <- paste0(
    "list(", 
    "theta*degree==", round(theta*180/pi, digits = 2), "*degree", 
    ", theta==", round(theta/pi, digits = 2), "*pi", 
    ", sin~theta==", round(sin(theta), digits = 2), 
    ", cos~theta==", round(cos(theta), digits = 2), 
    ")"
  )
  
  # 単位円を作図
  d <- 1.1
  circle_graph_top <- ggplot() +  # (上側に配置する場合)
    #circle_graph_bottom <- ggplot() +  # (下側に配置する場合)
    geom_path(data = circle_df, 
              mapping = aes(x = cos_theta, y = sin_theta), 
              size = 1) + # 単位円
    geom_text(data = tick_df, 
              mapping = aes(x = x, y = y, angle = alpha+90), 
              label = "|", size = 2) + # 角度目盛
    geom_text(data = tick_df, 
              mapping = aes(x = x*d, y = y*d, label = rad_label, 
                            hjust = 1-(x*0.5+0.5), vjust = 1-(y*0.5+0.5)), parse = TRUE) + # 角度目盛ラベル
    geom_segment(data = radius_df, 
                 mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to)) + # 半径
    geom_path(data = angle_df, 
              mapping = aes(x = x, y = y)) + # 角度マーク
    geom_text(data = angle_label_df, 
              mapping = aes(x = x, y = y, label = angle_label), parse = TRUE) + # 角度ラベル
    geom_segment(data = cos_line_df, 
                 mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), 
                 color = "red", size = 1) + # sin関数
    geom_text(data = cos_label_df, 
              mapping = aes(x = x, y = y, label = fnc_label), parse = TRUE, 
              vjust = 1, color = "red") + # sin関数ラベル
    geom_segment(data = segment_circle_df, 
                 mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), 
                 size = 1, linetype = "dotted") + # sin波との対応線
    geom_point(data = point_df, 
               mapping = aes(x = cos_theta, y = sin_theta), 
               size = 4) + # 単位円上の点
    coord_fixed(ratio = 1, clip = "off", 
                xlim = c(-axis_size, axis_size), ylim = c(-axis_size, axis_size)) + # 描画領域
    labs(title = "unit circle", 
         subtitle = parse(text = fnc_label), 
         x = "x", y = "y")
  
  ## コサイン波の作図:(上・下・左・右によって全部の処理を切り替える)
  
  # cos関数の値の変換線の描画用
  adapt_line_df <- tibble::tibble(
    rad = seq(from = 0, to = 0.5*pi, length.out = 50), 
    x = cos(rad) * (axis_size+cos(theta)) - axis_size, 
    y = sin(rad) * (axis_size+cos(theta)) - axis_size
  )
  
  # 単位円とコサイン波の対応用
  l <- 0.6
  segment_adapt_df <- tibble::tibble(
    x = c(cos(theta), -axis_size), 
    y = c(-axis_size, cos(theta)), 
    x_to = c(cos(theta), -axis_size-l), 
    y_to = c(-axis_size-l, cos(theta))
  )
  
  # 変換グラフを作図
  adapt_graph <- ggplot() + 
    geom_line(data = adapt_grid_df, 
              mapping = aes(x = x, y = y, group = d), 
              color = "white") + # グリッド線
    geom_segment(data = segment_adapt_df, 
                 mapping = aes(x = x, y = y, xend = x_to, yend = y_to), 
                 size = 1, linetype = "dotted") + # 単位円・コサイン波との対応線
    geom_line(data = adapt_line_df, 
              mapping = aes(x = x, y = y), 
              size = 1, linetype = "dotted") + # cos関数の値の線
    geom_point(data = segment_adapt_df, 
               mapping = aes(x = x, y = y), 
               size = 4) + # cos関数の値
    #theme(panel.grid = element_blank()) + # 元のグリッド線を非表示
    coord_fixed(ratio = 1, clip = "off", 
                xlim = c(-axis_size, axis_size), ylim = c(-axis_size, axis_size)) + # 描画領域
    labs(x = "x", y = "x")
  
  # 作図用のラジアンの値を作成
  theta_size <- 2 * pi
  theta_vals <- seq(from = max(theta_min, theta-theta_size), to = theta, by = 0.01)
  
  # x軸目盛ラベルの描画用の値を作成
  tick_min <- floor((theta-theta_size) * 6 / pi)
  tick_vec <- seq(from = tick_min, to = tick_min+13, by = 1)
  
  # コサイン波の描画用
  cos_curve_df <- tibble::tibble(
    theta = theta_vals, 
    cos_theta = cos(theta_vals)
  )
  
  # x軸(角度)・単位円との対応用
  d <- 1.1
  l <- 0.5
  segment_cos_df <- tibble::tibble(
    x_from = c(theta, theta), 
    y_from = c(cos(theta), cos(theta)), 
    x_to = c(theta, max(theta_vals)+l), 
    y_to = c(-axis_size*d, cos(theta))
  )
  
  # 関数ラベルを作成
  fnc_label <- paste0(
    "list(", 
    "theta==", round(theta/pi, digits = 2), "*pi", 
    ", cos~theta==", round(cos(theta), digits = 2), 
    ")"
  )
  
  # コサイン波を作図
  cos_graph <- ggplot() + 
    geom_line(data = cos_curve_df, 
              mapping = aes(x = theta, y = cos_theta), 
              color = "red", size = 1) + # cos関数
    geom_segment(data = segment_cos_df, 
                 mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), 
                 size = 1, linetype = "dotted") + # 単位円との対応線
    geom_point(data = point_df, 
               mapping = aes(x = theta, y = cos_theta), 
               size = 4) + # 関数曲線上の点
    scale_x_continuous(breaks = tick_vec/6*pi, 
                       labels = parse(text = paste0("frac(", tick_vec, ", 6)~pi"))) + # 角度目盛ラベル
    coord_fixed(ratio = 1, clip = "off", 
                xlim = c(theta-theta_size, theta), ylim = c(-axis_size, axis_size)) + # 描画領域
    labs(title = "cosine curve", 
         subtitle = parse(text = fnc_label), 
         x = expression(theta), y = expression(cos(theta)))
  
  # グラフを並べて描画
  graph <- patchwork::wrap_plots(
    cos_graph, adapt_graph, patchwork::plot_spacer(), circle_graph_top, 
    nrow = 2, ncol = 2
  )
  
  ## 画像の保存
  
  # ファイルを書き出し
  file_path <- paste0(dir_path, "/", stringr::str_pad(i, width = nchar(frame_num), pad = "0"), ".png")
  ggplot2::ggsave(filename = file_path, plot = graph, width = 1500, height = 1000, units = "px", dpi = 100)
  
  # 途中経過を表示
  message("\r", i, "/", frame_num, appendLF = FALSE)
}

 「1周期」と同様に、変数の値(角度)ごとに「グラフの作成」と同様に処理します。ただし、コサイン波の作図用の$\theta$の値に関して、各thetaを最大値としてtheta_sizeを描画範囲とします。ただし、theta - theta_sizetheta_minよりも小さくなる場合は、theta_minからthetaを描画範囲とします。

 「1周期」のコードで、gif画像を作成できます。


cos関数と単位円における角度の関係

cos関数と単位円における角度の関係

 $\cos \theta$が$2 \pi$の範囲で周期するのを確認できます。
 (下側に配置する場合は、$\theta$軸を反転させた方が分かりやすいかもしれない。)

 この記事では、cos関数を可視化して確認しました。次の記事では、cos関数の値を加工した場合を可視化します。

参考書籍

  • 『三角関数(改定第3版)』(Newton別冊)ニュートンプレス,2022年.

おわりに

 単位円に対してどこにコサイン波を置けば分かりやすいのかな、別にどこでも置けるんだな、とりあえず4つともやってみよう、で結局どれが分かりやすいんだろう、全部載せとくか。という脳内会議がありました。

 まーちゃんのソロデビュー決定!!!!

ということで、とてもめでたい。楽しみ♬

【次の内容】

つづく