はじめに
R言語で三角関数の定義や公式を可視化しようシリーズです。
この記事では、円のグラフを作成します。
【他の記事一覧】
【この記事の内容】
円周の作図
ggplot2パッケージを利用して、円周(circumference)のグラフを作成します。また、円座標系(circular coordinates)をグラフで確認します。
詳しい計算については「円周の作図【Matplotlib】 - からっぽのしょこ」も参照してください。
利用するパッケージを読み込みます。
# 利用パッケージ library(tidyverse) library(gganimate)
この記事では、基本的にパッケージ名::関数名()
の記法を使うので、パッケージを読み込む必要はありません。ただし、作図コードがごちゃごちゃしないようにパッケージ名を省略しているためggplot2
を読み込む必要があります。
また、ネイティブパイプ演算子|>
を使っています。magrittr
パッケージのパイプ演算子%>%
に置き換えても処理できますが、その場合はmagrittr
も読み込む必要があります。
定義式の確認
まずは、円の定義式を確認します。
点 を中心とする半径 の円は、次の式で定義されます。
平面上の座標(x軸とy軸の値)は、それぞれ次の式で計算できます。
はサイン関数、 はコサイン関数です。 は、中心と点 を結ぶ線分(原点を中心としたときのx軸の正の部分)と、中心と点 を結ぶ線分のなす角の角度に対応します。(正確には、なす角は 以下の正の値なので、なす角ではないと思いますが適切な呼び方が分かりません。)
点 は、中心からのユークリッドノルム が の点です。
原点 を中心とする( の)場合は、次の式になります。
座標は、それぞれ次の式で計算できます。
原点を中心とする単位円(半径が1の円)の場合は、次の式になります。
座標は、それぞれ次の式で計算できます。
これらの式を使ってグラフを作成します。
円の描画
次は、円のグラフを作成します。
円を描画するためのデータフレームを作成します。
# 中心の座標を指定 a <- 5 b <- 6 # 半径を指定 r <- 2 # 円の描画用 circle_df <- tibble::tibble( theta_deg = seq(from = 0, to = 360, length.out = 361), # 度数法 theta_rad = theta_deg / 180 * pi, # 弧度法 x = a + r * cos(theta_rad), y = b + r * sin(theta_rad) ) circle_df
## # A tibble: 361 × 4 ## theta_deg theta_rad x y ## <dbl> <dbl> <dbl> <dbl> ## 1 0 0 7 6 ## 2 1 0.0175 7.00 6.03 ## 3 2 0.0349 7.00 6.07 ## 4 3 0.0524 7.00 6.10 ## 5 4 0.0698 7.00 6.14 ## 6 5 0.0873 6.99 6.17 ## 7 6 0.105 6.99 6.21 ## 8 7 0.122 6.99 6.24 ## 9 8 0.140 6.98 6.28 ## 10 9 0.157 6.98 6.31 ## # … with 351 more rows
半径 をr
、円の中心のx軸の値 をa
、y軸の値 をb
として値を指定して、円周上の点の座標を計算します。
度数法における角度 の範囲の値を作成してtheta_deg
列とします。
theta_deg
列を用いて、弧度法における角度(ラジアン)を で計算してtheta_rad
列とします。 は円周率で、pi
で扱えます。、 なので、ラジアンは、 の範囲の値になります。詳しくは「度数法と弧度法の関係」で確認します。
theta_rad
列を用いて、x軸の値 とy軸の値 を計算してx, y
列とします。
あるいは、ラジアンを作成して、座標を計算します。
# 円周の座標を計算 circle_df <- tibble::tibble( theta_rad = seq(from = 0, to = 2*pi, length.out = 361), # ラジアン x = a + r * cos(theta_rad), y = b + r * sin(theta_rad) ) circle_df
## # A tibble: 361 × 3 ## theta_rad x y ## <dbl> <dbl> <dbl> ## 1 0 7 6 ## 2 0.0175 7.00 6.03 ## 3 0.0349 7.00 6.07 ## 4 0.0524 7.00 6.10 ## 5 0.0698 7.00 6.14 ## 6 0.0873 6.99 6.17 ## 7 0.105 6.99 6.21 ## 8 0.122 6.99 6.24 ## 9 0.140 6.98 6.28 ## 10 0.157 6.98 6.31 ## # … with 351 more rows
ラジアンとして用いる の範囲の値を作成して、同様に座標を計算します。
円周のグラフを作成します。
# 円を作図 ggplot() + geom_path(data = circle_df, mapping = aes(x = x, y = y, linetype = "circle")) + # 円周 geom_segment(mapping = aes(x = a, y = b, xend = a+r, yend = b)) + # 半径 geom_text(mapping = aes(x = a+0.5*r, y = b), label = "r", parse = TRUE, size = 5, hjust = 0.5, vjust = -0.5) + # 半径ラベル geom_point(mapping = aes(x = a, y = b), size = 3) + # 中心点 geom_text(mapping = aes(x = a, y = b), label = "O", parse = TRUE, size = 5, hjust = 0.5, vjust = -0.5) + # 中心ラベル scale_linetype_manual(breaks = "circle", values = "solid", label = expression((x - a)^2 + (y - b)^2 == r^2), name = "formula") + # (凡例表示用) coord_fixed(ratio = 1) + # アスペクト比 labs(title = "circle", subtitle = paste0("O=(a, b)=(", a, ", ", b, "), r=", r), x = expression(x == a + r~cos~theta), y = expression(y == b + r~sin~theta))
横軸を 、縦軸を として、(geom_line()
ではなく)geom_path()
で円を描画します。綺麗な円を描画するには、coord_***()
のratio
引数に1
を指定します。
また半径の目安として、(なす角が0の)中心と円周上の点を結ぶ線分をgeom_segment()
で描画しています。
数式を表示する場合は、expression()
の記法で指定します。
原点を中心とする半径 の円全体をx軸方向に 、y軸方向に 移動したグラフになります。
以上で、円を描画できました。
度数法と弧度法の関係
最後に、度数法における角度と弧度法における角度(ラジアン)の関係をグラフで確認します。
グラフの作成
度数法と弧度法の関係を角度目盛を表示したグラフで確認します。
・作図コード(クリックで展開)
円を描画するためのデータフレームを作成します。
# 半径を指定 r <- 1 # 円周の座標を計算 circle_df <- tibble::tibble( theta = seq(from = 0, to = 2*pi, length.out = 601), # ラジアン x = r * cos(theta), y = r * sin(theta) ) circle_df
## # A tibble: 601 × 3 ## theta x y ## <dbl> <dbl> <dbl> ## 1 0 1 0 ## 2 0.0105 1.00 0.0105 ## 3 0.0209 1.00 0.0209 ## 4 0.0314 1.00 0.0314 ## 5 0.0419 0.999 0.0419 ## 6 0.0524 0.999 0.0523 ## 7 0.0628 0.998 0.0628 ## 8 0.0733 0.997 0.0732 ## 9 0.0838 0.996 0.0837 ## 10 0.0942 0.996 0.0941 ## # … with 591 more rows
「円の描画」のときと同様にして、円周の座標を計算します。ただし簡単のため、原点を中心とします。
角度に関する目盛やラベルを描画するためのデータフレームを作成します。
# 半円の目盛の数(分母の値)を指定 denom <- 6 # 目盛の通し番号(分子の値)を作成 tick_vec <- seq(from = 0, to = 2*denom-1, by = 1) # 角度目盛ラベルの描画用 rad_label_df <- tibble::tibble( theta_deg = tick_vec / denom * 180, # 度数法 theta_rad = tick_vec / denom * pi, # 弧度法 x = r * cos(theta_rad), y = r * sin(theta_rad), deg_label = paste0(theta_deg, "*degree"), # 度数法用ラベル rad_label = paste0("frac(", tick_vec, ", ", denom, ")~pi"), # 弧度法用ラベル angle_label = paste0(deg_label, "==", rad_label) # 度数法・弧度法の対応用ラベル ) rad_label_df
## # A tibble: 12 × 7 ## theta_deg theta_rad x y deg_label rad_label angle_label ## <dbl> <dbl> <dbl> <dbl> <chr> <chr> <chr> ## 1 0 0 1 e+ 0 0 0*degree frac(0, 6)~pi 0*degree==… ## 2 30 0.524 8.66e- 1 5 e- 1 30*degree frac(1, 6)~pi 30*degree=… ## 3 60 1.05 5 e- 1 8.66e- 1 60*degree frac(2, 6)~pi 60*degree=… ## 4 90 1.57 6.12e-17 1 e+ 0 90*degree frac(3, 6)~pi 90*degree=… ## 5 120 2.09 -5 e- 1 8.66e- 1 120*degree frac(4, 6)~pi 120*degree… ## 6 150 2.62 -8.66e- 1 5 e- 1 150*degree frac(5, 6)~pi 150*degree… ## 7 180 3.14 -1 e+ 0 1.22e-16 180*degree frac(6, 6)~pi 180*degree… ## 8 210 3.67 -8.66e- 1 -5 e- 1 210*degree frac(7, 6)~pi 210*degree… ## 9 240 4.19 -5.00e- 1 -8.66e- 1 240*degree frac(8, 6)~pi 240*degree… ## 10 270 4.71 -1.84e-16 -1 e+ 0 270*degree frac(9, 6)~pi 270*degree… ## 11 300 5.24 5 e- 1 -8.66e- 1 300*degree frac(10, 6)~pi 300*degree… ## 12 330 5.76 8.66e- 1 -5.00e- 1 330*degree frac(11, 6)~pi 330*degree…
角度 に関する軸目盛ラベルを の形で表示することにします。1周 は なので、この例では、 を から 未満の整数とします。 は、半円における目盛の数に対応します。
をn
、 をtick_vec
として値を作成します。
度数法における角度を で計算してtheta_deg
列、弧度法における角度を で計算してtheta_rad
列として、円周上の点の座標を計算します。
theta_deg
列やtick_vec
を用いて、ラベル用の文字列を作成します。数式を表示する場合は、expression()
の記法を用います。
角マークを描画するためのデータフレームを作成します。
# 角マークの座標を計算 d <- 0.2 angle_mark_df <- tibble::tibble( theta = seq(from = 0, to = 1/denom*pi, length.out = 100), x = d * cos(theta), y = d * sin(theta) ) angle_mark_df
## # A tibble: 100 × 3 ## theta x y ## <dbl> <dbl> <dbl> ## 1 0 0.2 0 ## 2 0.00529 0.200 0.00106 ## 3 0.0106 0.200 0.00212 ## 4 0.0159 0.200 0.00317 ## 5 0.0212 0.200 0.00423 ## 6 0.0264 0.200 0.00529 ## 7 0.0317 0.200 0.00635 ## 8 0.0370 0.200 0.00740 ## 9 0.0423 0.200 0.00846 ## 10 0.0476 0.200 0.00952 ## # … with 90 more rows
この例では、なす角 を示すために、軸目盛1つ分の角マーク(小さい扇型)を表示することにします。
そこで、角マーク用のラジアン を作成して、円弧の座標を計算します。サイズの調整用の値(半径)をd
とします。
角ラベルを描画するためのデータフレームを作成します。
# 角ラベルの座標を計算 d <- 0.3 angle_label_df <- tibble::tibble( theta = 0.5 / denom * pi, x = d * cos(theta), y = d * sin(theta) ) angle_label_df
## # A tibble: 1 × 3 ## theta x y ## <dbl> <dbl> <dbl> ## 1 0.262 0.290 0.0776
角ラベルは、角マークの中点に表示することにします。
そこで、角ラベル用のラジアンを として、点の座標を計算します。表示位置の調整用の値(原点からのノルム)をd
とします。
円周上に角度目盛を表示したグラフを作成します。
# 目盛ラベルの表示位置を指定 d <- 1.1 # グラフサイズ用の値を設定 axis_size <- r * d + 0.3 # 円周上の角度目盛を作図 ggplot() + geom_path(data = circle_df, mapping = aes(x = x, y = y), size = 1) + # 円周 geom_text(data = rad_label_df, mapping = aes(x = x*d, y = y*d, label = angle_label, hjust = 1-(x/r*0.5+0.5), vjust = 1-(y/r*0.5+0.5)), parse = TRUE) + # 角度目盛ラベル geom_text(data = rad_label_df, mapping = aes(x = x, y = y, angle = theta_deg+90), label = "|", size = 2) + # 角度目盛指示線 geom_segment(data = rad_label_df, mapping = aes(x = 0, y = 0, xend = x, yend = y), linetype = "dotted") + # 角度目盛グリッド geom_segment(mapping = aes(x = 0, y = 0, xend = r, yend = 0)) + # x軸上の半径 geom_text(mapping = aes(x = 0.5*r, y = 0), label = paste0("r==", r), parse = TRUE, size = 5, hjust = 0.5, vjust = 0) + # 半径ラベル geom_path(data = angle_mark_df, mapping = aes(x = x, y = y), size = 0.5) + # 角マーク geom_text(data = angle_label_df, mapping = aes(x = x, y = y), label = "theta", parse = TRUE, size = 5, hjust = 0.5, vjust = 0.5) + # 角ラベル coord_fixed(ratio = 1, xlim = c(-axis_size, axis_size), ylim = c(-axis_size, axis_size)) + # 描画領域 labs(title = "degree and radian", subtitle = expression(theta*degree == frac(2*pi, 360) ~ theta), #subtitle = expression(theta == frac(360, 2*pi) ~ theta*degree), x = expression(x == r ~ cos~theta), y = expression(y == r ~ sin~theta))
目盛ラベルの表示位置の調整用の値をd
とします。
また、横・縦方向の表示位置の調整用の引数hjust, vjust
を、x, y
列の値を使って指定しています。 (x, y
列)は から の値をとります。 で割ると から の値になり、さらに を掛けると から の値になり、 を足すと から の値になります。この値を から引いた値を使うといい感じの表示位置になります。hjust, vjust
を指定(こんな面倒な設定を)せずに、d
を少し大きくしても同様の図になります。
角度目盛の指示線を|
で表示します。線の向きは角度列theta_deg
を90°回転させた方向です。
度数法 と弧度法 は、、 の関係です。つまり、 のとき 、 のとき 、 のとき です。
また、、 なので、 で1周期です。
円の大きさ(半径)や中心の位置(座標)は角度に影響しません。
アニメーションの作成
続いて、なす角と円周上の点の関係をアニメーションで確認します。
・作図コード(クリックで展開)
フレームごとの角度を作成します。
# フレーム数を指定 frame_num <- 150 # フレームごとの角度を指定 theta_n <- seq(from = -2*pi, to = 2*pi, length.out = frame_num+1)[1:frame_num] # フレームごとの角度ラベルを作成 frame_label_vec <- paste0( "θ° = ", round(theta_n/pi*180, digits = 1), "°, ", "θ = ", round(theta_n/pi, digits = 2), "π" ) head(theta_n); head(frame_label_vec)
## [1] "θ° = -360°, θ = -2π" "θ° = -355.2°, θ = -1.97π" ## [3] "θ° = -350.4°, θ = -1.95π" "θ° = -345.6°, θ = -1.92π" ## [5] "θ° = -340.8°, θ = -1.89π" "θ° = -336°, θ = -1.87π"
フレーム数frame_num
を指定して、frame_num
個の の値を等間隔に作成します。範囲を の倍数にしてframe_num + 1
個の等間隔の値を作成して、最後の値を除くと繋がりの良いアニメーションになります。
この例では、フレームごとの角度をグラフに表示するために、theta_n
を用いた文字列をフレーム切替用のラベル列として使います。作図自体には不要な処理です。
円周上の点を描画するためのデータフレームを作成します。
# フレームごとの円周上の点の座標を計算 anim_angle_point_df <- tibble::tibble( frame_i = 1:frame_num, # フレーム番号 frame_label = factor(frame_label_vec, levels = frame_label_vec), # フレーム切替用ラベル theta = theta_n, x = r * cos(theta), y = r * sin(theta) ) anim_angle_point_df
## # A tibble: 150 × 5 ## frame_i frame_label theta x y ## <int> <fct> <dbl> <dbl> <dbl> ## 1 1 θ° = -360°, θ = -2π -6.28 1 2.45e-16 ## 2 2 θ° = -355.2°, θ = -1.97π -6.20 0.996 8.37e- 2 ## 3 3 θ° = -350.4°, θ = -1.95π -6.12 0.986 1.67e- 1 ## 4 4 θ° = -345.6°, θ = -1.92π -6.03 0.969 2.49e- 1 ## 5 5 θ° = -340.8°, θ = -1.89π -5.95 0.944 3.29e- 1 ## 6 6 θ° = -336°, θ = -1.87π -5.86 0.914 4.07e- 1 ## 7 7 θ° = -331.2°, θ = -1.84π -5.78 0.876 4.82e- 1 ## 8 8 θ° = -326.4°, θ = -1.81π -5.70 0.833 5.53e- 1 ## 9 9 θ° = -321.6°, θ = -1.79π -5.61 0.784 6.21e- 1 ## 10 10 θ° = -316.8°, θ = -1.76π -5.53 0.729 6.85e- 1 ## # … with 140 more rows
フレーム番号を1
からframe_num
の整数として、フレームラベルframe_label_vec
と合わせて格納します。どちらか1つの列があれば作図できます。
また、フレームごとの角度theta_n
を用いて、円周上の各点の座標を計算します。
角マークを描画するためのデータフレームを作成します。
# フレームごとの角マークの座標を計算 d <- 0.15 anim_angle_mark_df <- tibble::tibble( frame_i = 1:frame_num, # フレーム番号 frame_label = factor(frame_label_vec, levels = frame_label_vec), # フレーム切替用ラベル ) |> dplyr::group_by(frame_i, frame_label) |> # ラジアンの作成用 dplyr::summarise( theta = seq(from = 0, to = theta_n[frame_i], length.out = 100), .groups = "drop" ) |> # なす角以下のラジアンを作成 dplyr::mutate( x = d * cos(theta), y = d * sin(theta) ) anim_angle_mark_df
## # A tibble: 15,000 × 5 ## frame_i frame_label theta x y ## <int> <fct> <dbl> <dbl> <dbl> ## 1 1 θ° = -360°, θ = -2π 0 0.15 0 ## 2 1 θ° = -360°, θ = -2π -0.0635 0.150 -0.00951 ## 3 1 θ° = -360°, θ = -2π -0.127 0.149 -0.0190 ## 4 1 θ° = -360°, θ = -2π -0.190 0.147 -0.0284 ## 5 1 θ° = -360°, θ = -2π -0.254 0.145 -0.0377 ## 6 1 θ° = -360°, θ = -2π -0.317 0.143 -0.0468 ## 7 1 θ° = -360°, θ = -2π -0.381 0.139 -0.0557 ## 8 1 θ° = -360°, θ = -2π -0.444 0.135 -0.0645 ## 9 1 θ° = -360°, θ = -2π -0.508 0.131 -0.0729 ## 10 1 θ° = -360°, θ = -2π -0.571 0.126 -0.0811 ## # … with 14,990 more rows
フレームごとにグループ化して、summarise()
を使ってフレーム(角度)ごとに0
からtheta_n[frame_i]
までの値を作成して、円弧の座標を計算します。
角ラベルを描画するためのデータフレームを作成します。
# フレームごとの角ラベルの座標を計算 d <- 0.2 anim_angle_label_df <- tibble::tibble( frame_i = 1:frame_num, # フレーム番号 frame_label = factor(frame_label_vec, levels = frame_label_vec), # フレーム切替用ラベル theta = 0.5 * theta_n, x = d * cos(theta), y = d * sin(theta) ) anim_angle_label_df
## # A tibble: 150 × 5 ## frame_i frame_label theta x y ## <int> <fct> <dbl> <dbl> <dbl> ## 1 1 θ° = -360°, θ = -2π -3.14 -0.2 -2.45e-17 ## 2 2 θ° = -355.2°, θ = -1.97π -3.10 -0.200 -8.38e- 3 ## 3 3 θ° = -350.4°, θ = -1.95π -3.06 -0.199 -1.67e- 2 ## 4 4 θ° = -345.6°, θ = -1.92π -3.02 -0.198 -2.51e- 2 ## 5 5 θ° = -340.8°, θ = -1.89π -2.97 -0.197 -3.34e- 2 ## 6 6 θ° = -336°, θ = -1.87π -2.93 -0.196 -4.16e- 2 ## 7 7 θ° = -331.2°, θ = -1.84π -2.89 -0.194 -4.97e- 2 ## 8 8 θ° = -326.4°, θ = -1.81π -2.85 -0.191 -5.78e- 2 ## 9 9 θ° = -321.6°, θ = -1.79π -2.81 -0.189 -6.58e- 2 ## 10 10 θ° = -316.8°, θ = -1.76π -2.76 -0.186 -7.36e- 2 ## # … with 140 more rows
x軸の正の部分とのなす角の中点にラベルと配置するために、0.5 * theta_n
を用いて円弧上の点の座標を計算します。
フレームごとに角度を変化させたアニメーションを作成します。
# ラベルの表示位置を指定 d <- 1.1 # グラフサイズ用の値を設定 axis_size <- d + 0.3 # 角度と円周上の点のアニメーションを作図 anim <- ggplot() + geom_path(data = circle_df, mapping = aes(x = x, y = y), size = 1) + # 円周 geom_text(data = rad_label_df, mapping = aes(x = x*d, y = y*d, label = angle_label, hjust = 1-(x*0.5+0.5), vjust = 1-(y*0.5+0.5)), parse = TRUE) + # 角度目盛ラベル geom_text(data = rad_label_df, mapping = aes(x = x, y = y, angle = theta_deg+90), label = "|", size = 2) + # 角度目盛指示線 geom_segment(data = rad_label_df, mapping = aes(x = 0, y = 0, xend = x, yend = y), linetype = "dotted") + # 角度目盛グリッド geom_point(data = anim_angle_point_df, mapping = aes(x = x, y = y), size = 4) + # 円周上の点 geom_segment(data = anim_angle_point_df, mapping = aes(x = 0, y = 0, xend = x, yend = y), size = 1) + # 原点と円周上の点の線分 geom_segment(mapping = aes(x = 0, y = 0, xend = r, yend = 0), size = 1) + # 正のx軸上の半径 geom_text(mapping = aes(x = 0.5*r, y = 0), label = "r", parse = TRUE, size = 5, hjust = 0.5, vjust = -0.5) + # 半径ラベル geom_path(data = anim_angle_mark_df, mapping = aes(x = x, y = y), size = 0.5) + # 角マーク geom_text(data = anim_angle_label_df, mapping = aes(x = x, y = y), label = "theta", parse = TRUE, size = 5, hjust = 0.5, vjust = 0.5) + # 角ラベル gganimate::transition_manual(frames = frame_label) + # フレーム coord_fixed(ratio = 1, xlim = c(-axis_size, axis_size), ylim = c(-axis_size, axis_size)) + # 描画領域 labs(title = "degree and radian", subtitle = "{current_frame}", x = expression(x == r ~ cos~theta), y = expression(y == r ~ sin~theta)) # gif画像を作成 gganimate::animate(plot = anim, nframes = frame_num, fps = 100, width = 600, height = 600)
gganimate
パッケージを利用して、アニメーション(gif画像)を作成します。
transition_manual()
のフレーム制御の引数frames
にフレームラベル列frame_label
(またはフレーム番号列frame_i
)を指定して、グラフを作成します。
animate()
のplot
引数にグラフオブジェクト、nframes
引数にフレーム数frame_num
を指定して、gif画像を作成します。また、fps
引数に1秒当たりのフレーム数を指定できます。
ラジアン(角度) が大きくなるほど円周上の点が反時計回りに移動するのが分かります。角度が負の値のときは時計回りの方向にできるなす角になります。
この記事では、円を可視化しました。次の記事からは、三角関数(円関数)を可視化していきます。
おわりに
最近Python版の記事を書いたこともあり、「三角関数の可視化」他の記事の中で書いていた内容をバージョンアップして独立させました。
三角関数シリーズを中断して(途中で投げて)線形代数シリーズを書いていたら三角関数の内容が度々登場したので、再開のための準備運動がてら書きました。
円周率は!3.14159265358979ということで、最後にこの曲をどうぞ。
あり・をり・はべり・いまそかり♬
【次の内容】