はじめに
R言語で三角関数の定義や公式を可視化しようシリーズです。
この記事では、sin関数のグラフを作成します。
【前の内容】
【他の記事一覧】
【この記事の内容】
sin関数の可視化
単位円における角度とsin関数(サイン波・正弦波)の関係をグラフで可視化します。sin関数や単位円の作図については「【R】三角関数の可視化 - からっぽのしょこ」も参照してください。
利用するパッケージを読み込みます。
# 利用パッケージ library(tidyverse) library(patchwork) library(magick)
この記事では、基本的にパッケージ名::関数名()
の記法を使うので、パッケージを読み込む必要はありません。ただし、作図コードがごちゃごちゃしないようにパッケージ名を省略しているためggplot2
を読み込む必要があります。
また、ネイティブパイプ演算子|>
を使っています。magrittr
パッケージのパイプ演算子%>%
に置き換えても処理できますが、その場合はmagrittr
も読み込む必要があります。
グラフの作成
まずは、角度を固定したグラフで確認します。
角度を指定します。
# 角度を指定 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$と共に格納します。
単位円を描画するためのデータフレームを作成します。
# 単位円の描画用 circle_df <- tibble::tibble( theta = seq(from = 0, to = 2*pi, by = 0.04), sin_theta = sin(theta), cos_theta = cos(theta) ) circle_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
単位円の作図用のラジアン$0 \leq \theta \leq 2 \pi$を作成して、$\sin \theta, \cos \theta$を計算します。単位円の座標は、x軸の値$x = \cos \theta$、y軸の値$y = \sin \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") ) tick_df
## # 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
$30^{\circ} = 30 \frac{\pi}{180} = \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), by = 0.1), 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: 21 × 3 ## theta x y ## <dbl> <dbl> <dbl> ## 1 -2.09 -0.05 -0.0866 ## 2 -1.99 -0.0411 -0.0912 ## 3 -1.89 -0.0318 -0.0948 ## 4 -1.79 -0.0222 -0.0975 ## 5 -1.69 -0.0123 -0.0992 ## 6 -1.59 -0.00236 -0.100 ## 7 -1.49 0.00763 -0.0997 ## 8 -1.39 0.0175 -0.0984 ## 9 -1.29 0.0273 -0.0962 ## 10 -1.19 0.0368 -0.0930 ## # … with 11 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
とします。
単位円におけるsin関数の値を描画するためのデータフレームを作成します。
# sin関数の線の描画用 sin_line_df <- tibble::tibble( x_from = cos(theta), y_from = 0, x_to = cos(theta), y_to = sin(theta) ) # sin関数の線ラベルの描画用 sin_label_df <- sin_line_df |> dplyr::mutate( # 中点に配置 x = median(c(x_from, x_to)), y = median(c(y_from, y_to)), fnc_label = paste0("sin~theta") ) |> dplyr::select(x, y, fnc_label) sin_line_df; sin_label_df
## # A tibble: 1 × 4 ## x_from y_from x_to y_to ## <dbl> <dbl> <dbl> <dbl> ## 1 -0.5 0 -0.5 -0.866 ## # A tibble: 1 × 3 ## x y fnc_label ## <dbl> <dbl> <chr> ## 1 -0.5 -0.433 sin~theta
sin関数の値は、角度$\theta$における単位円上の点$(\cos \theta, \sin \theta)$と、$x = 0$の直線への垂線との交点$(\cos \theta, 0)$を結ぶ線分で表現できます。この2点の座標を格納してsin_line_df
とします。
sin関数の垂線(線分)の中点に配置するように座標とラベル用の文字列を格納してsin_label_df
とします。
単位円上の点とsin関数上の点を結ぶ線(の半分)を描画するためのデータフレームを作成します。
# 描画領域のサイズを指定 axis_size <- 1.5 l <- 2 # サイン波との対応用 segment_circle_df <- tibble::tibble( x_from = cos(theta), y_from = sin(theta), x_to = -l, y_to = sin(theta) ) segment_circle_df
## # A tibble: 1 × 4 ## x_from y_from x_to y_to ## <dbl> <dbl> <dbl> <dbl> ## 1 -0.5 -0.866 -2 -0.866
単位円上の点からy軸への垂線を引くように座標を指定します。作図時に、隣のグラフに届かないような長さl
を指定します。
また、2つのグラフの描画範囲を固定するために、axis_size
として値を指定しておきます。
単位円のグラフを作成します。
# 関数ラベルを作成 fnc_label <- paste0( "list(", "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 <- 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 = sin_line_df, mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), color = "red", size = 1) + # sin関数 geom_text(data = sin_label_df, mapping = aes(x = x, y = y, label = fnc_label), parse = TRUE, angle = 90, 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), linetype = "dashed") + # 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
x軸を$\cos \theta$、y軸を$\sin \theta$として、(geom_line()
ではなく)geom_path()
で曲線(円)を描画します。綺麗な円を描画するにはcoord_***()
のratio
引数を1
にします。
ラベル配置位置の調整用の値を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()
で描画します。
サイン波の作図用のラジアンの値を作成します。
# 作図用のラジアンの値を作成 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周期分の曲線を描画できます。
サイン波の描画するためのデータフレームを作成します。
# サイン波の描画用 sin_curve_df <- tibble::tibble( theta = theta_vals, sin_theta = sin(theta_vals) ) sin_curve_df
## # A tibble: 629 × 2 ## theta sin_theta ## <dbl> <dbl> ## 1 -5.24 0.866 ## 2 -5.23 0.871 ## 3 -5.22 0.876 ## 4 -5.21 0.881 ## 5 -5.20 0.885 ## 6 -5.19 0.890 ## 7 -5.18 0.894 ## 8 -5.17 0.899 ## 9 -5.16 0.903 ## 10 -5.15 0.907 ## # … with 619 more rows
作図用の$\theta$の値と$\sin \theta$の値を格納します。
先ほどと同様に、sin関数上の点と単位円上の点を結ぶ線(の半分)を描画するためのデータフレームを作成します。
# x軸(角度)・単位円との対応用 d <- 1.1 segment_sin_df <- tibble::tibble( x_from = c(theta, theta), y_from = c(sin(theta), sin(theta)), x_to = c(theta, max(theta_vals)+l), y_to = c(-axis_size*d, sin(theta)) ) segment_sin_df
## # A tibble: 2 × 4 ## x_from y_from x_to y_to ## <dbl> <dbl> <dbl> <dbl> ## 1 -2.09 -0.866 -2.09 -1.65 ## 2 -2.09 -0.866 3.04 -0.866
曲線上の点からx軸・y軸への垂線を引くように座標を指定します。
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
とします。
サイン波のグラフを作成します。
# 関数ラベルを作成 fnc_label <- paste0( "list(", "theta==", round(theta/pi, digits = 2), "*pi", ", sin~theta==", round(sin(theta), digits = 2), ")" ) # サイン波を作図 sin_graph <- ggplot() + geom_line(data = sin_curve_df, mapping = aes(x = theta, y = sin_theta), color = "red", size = 1) + # sin関数 geom_segment(data = segment_sin_df, mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), linetype = "dashed") + # 単位円との対応線 geom_point(data = point_df, mapping = aes(x = theta, y = sin_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 = "sine curve", subtitle = parse(text = fnc_label), x = expression(theta), y = expression(sin(theta))) sin_graph
x軸を$\theta$、y軸を$\sin \theta$としてgeom_line()
で曲線を描画します。
scale_x_continuous()
でx軸目盛を設定します。目盛の表示位置の引数breaks
にtick_vec
を$\frac{\pi}{6}$倍した値、目盛ラベルの引数labels
に$\frac{n}{6} \pi$の形の文字列を指定します。
2つのグラフを並べて描画します。
# グラフを並べて描画 patchwork::wrap_plots(sin_graph, circle_graph)
patchwork
パッケージのwrap_plots()
を使ってグラフを並べます。
単位円上の点の高さがサイン波(sin関数)に対応しているのを確認できます。
アニメーションの作成
次は、角度を変化させたアニメーションで確認します。
フレーム数と角度の範囲を指定して、角度として用いる値を作成します。
# フレーム数を指定 frame_num <- 100 # 角度の範囲を指定 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)
## [1] -6.283185 -6.157522 -6.031858 -5.906194 -5.780530 -5.654867
フレーム数frame_num
と、角度(ラジアン)$\theta$の最小値theta_min
・最大値theta_max
を指定して、frame_num
個の等間隔の$\theta$の値を作成します。三角関数は$2 \pi$で1周期なので、最小値に$2 \pi$の$n$倍を加えた値を最大値にして、等間隔に切り分け最大値自体を除くと、最後のフレームと最初のフレームの繋がりが良くなります。
・コード(クリックで展開)
theta_i
から順番に値を取り出して作図し、グラフを書き出します。
# 一時保存フォルダを指定 dir_path <- "tmp_folder" # 描画領域のサイズを指定 axis_size <- 1.5 l <- 2.5 # 角度ごとに作図 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), by = 0.1), 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" ) # sin関数の線の描画用 sin_line_df <- tibble::tibble( x_from = cos(theta), y_from = 0, x_to = cos(theta), y_to = sin(theta) ) # sin関数の線ラベルの描画用 sin_label_df <- sin_line_df |> dplyr::mutate( # 中点に配置 x = median(c(x_from, x_to)), y = median(c(y_from, y_to)), fnc_label = paste0("sin~theta") ) |> dplyr::select(x, y, fnc_label) # サイン波との対応用 segment_circle_df <- tibble::tibble( x_from = cos(theta), y_from = sin(theta), x_to = -l, y_to = sin(theta) ) # 関数ラベルを作成 fnc_label <- paste0( "list(", "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 <- 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 = sin_line_df, mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), color = "red", size = 1) + # sin関数 geom_text(data = sin_label_df, mapping = aes(x = x, y = y, label = fnc_label), parse = TRUE, angle = 90, 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), 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") # 作図用のラジアンの値を作成 theta_size <- 2 * pi theta_vals <- seq(from = max(theta_min, theta-theta_size), to = theta, by = 0.01) # サイン波の描画用 sin_curve_df <- tibble::tibble( theta = theta_vals, sin_theta = sin(theta_vals) ) # x軸(角度)・単位円との対応用 d <- 1.1 segment_sin_df <- tibble::tibble( x_from = c(theta, theta), y_from = c(sin(theta), sin(theta)), x_to = c(theta, max(theta_vals)+l), y_to = c(-axis_size*d, sin(theta)) ) # x軸ラベルの描画用の値を作成 tick_min <- floor((theta-theta_size) * 6 / pi) tick_vec <- seq(from = tick_min, to = tick_min+13, by = 1) # 関数ラベルを作成 fnc_label <- paste0( "list(", "theta==", round(theta/pi, digits = 2), "*pi", ", sin~theta==", round(sin(theta), digits = 2), ")" ) # サイン波を作図 sin_graph <- ggplot() + geom_line(data = sin_curve_df, mapping = aes(x = theta, y = sin_theta), color = "red", size = 1) + # sin関数 geom_segment(data = segment_sin_df, mapping = aes(x = x_from, y = y_from, xend = x_to, yend = y_to), linetype = "dotted") + # 単位円との対応線 geom_point(data = point_df, mapping = aes(x = theta, y = sin_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 = "sine curve", subtitle = parse(text = fnc_label), x = expression(theta), y = expression(sin(theta))) # グラフを並べて描画 graph <- patchwork::wrap_plots(sin_graph, circle_graph) # ファイルを書き出し file_path <- paste0(dir_path, "/", stringr::str_pad(i, width = nchar(frame_num), pad = "0"), ".png") ggplot2::ggsave(filename = file_path, plot = graph, width = 1200, height = 500, units = "px", dpi = 100) # 途中経過を表示 message("\r", i, "/", frame_num, appendLF = FALSE) }
変数の値(角度)ごとに「グラフの作成」と同様に処理します。作成したグラフを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 = "sin_curve.gif", delay = 0.1) -> tmp_path
list.files()
でdir_path
フォルダ内のファイル名を取得して、ファイルパスを作成します。
image_read()
で画像を読み込んで、image_animate()
でgifファイルに変換して、image_write_gif()
でgifファイルを書き出します。delay
引数に1秒当たりのフレーム数の逆数を指定します。
sin関数が$2 \pi$間隔で周期的に推移するのを確認できます。
この記事では、sin関数を可視化して確認しました。次の記事では、sin関数の値を加工した場合を可視化します。
おわりに
前回の記事(三角関数の可視化)から進んでいるのか戻っているのか分かりませんが、次の記事を書くのに必要になったので書きました。
gganimate
に加えてpatchwork
+magick
でもアニメーションを作れるようになったおかげで、やりたいことが無間に湧いてきて寄り道が捗ります。
【次の内容】