library(magrittr)
%>%
iris split(.$Species) %>%
::iwalk(~ readr::write_csv(.x, glue::glue("{.y}.csv")))
purrr
::dir_tree(regexp = ".csv$") fs
.
├── setosa.csv
├── versicolor.csv
└── virginica.csv
August 18, 2024
August 27, 2024
Rのglue
パッケージを使うと文字列のプレースホルダをRの式を評価した結果に置き換えることができる。 これを利用して、他の言語のスクリプトで変更したい部分をプレースホルダにして、 glue::glue()
で一部分だけ変更したスクリプトを簡単に生成することができる。
An implementation of interpreted string literals, inspired by Pythons Literal String Interpolation <https://www.python.org/dev/peps/pep-0498/> and Docstrings <https://www.python.org/dev/peps/pep-0257/> and Julias Triple-Quoted String Literals <https://docs.julialang.org/en/v1.3/manual/strings/#Triple-Quoted-String-Literals-1>.
https://glue.tidyverse.org/
Rでスクリプト(ここではBash)を生成したい時というのは、 多くの場合複数のファイルや処理時のパラメータが存在するときである。
Bashスクリプトだけで、多数のファイルに対して同じ処理をするようなスクリプトを記述することも可能だが、 Rに比べるとBashスクリプトの機能は弱くファイル名や文字列処理が難しいという欠点がある。 また、文字列操作やファイル名操作、繰り返し処理をBashスクリプトで実装するための文法を追加で覚える労力が必要となる。
RでBashスクリプトを生成するようにすれば、 ファイルや文字列操作に使い慣れたRの機能やパッケージを利用することができ、 また覚えるBashスクリプトの文法も最小限ですむという利点がある。
複数のcsvファイルについてBashで何らかの処理を行いたい場合を考えてみよう。 まずは、csvファイルを用意する。 Rのiris
データをSpecies
列の値("setosa"
, "versicolor"
, "virginica"
)で分割し、 それぞれcsvファイルに書き出す。
library(magrittr)
iris %>%
split(.$Species) %>%
purrr::iwalk(~ readr::write_csv(.x, glue::glue("{.y}.csv")))
fs::dir_tree(regexp = ".csv$")
.
├── setosa.csv
├── versicolor.csv
└── virginica.csv
csvファイルに書き出すことが出来たので、これらのcsvファイルそれぞれについてBashでデータ処理を行うことを考えよう。 今回は、awk
コマンドを使ってそれぞれのcsvファイルについて、 一列目 Sepal.Length
と2列目 Sepal.Width
の平均値を計算して出力するような処理を行う。 glue::glue()
を使ってBashスクリプトを生成してみよう。 ちなみに、シェルスクリプトではバックスラッシュ\
をよく使う場合があるが、 その様な時はRのraw stringを使うと良い。 raw stringは"なにかの文字列"
のように囲む代わりに、r"(なにかの文字列)"
の様に囲むことで作成できる。 (raw stringについて)
# 1. scriptの最初の処理を`header`として保存する。
header <-
r"(#!/bin/bash
set -euC
echo "Process started!"
echo ""
)"
# 2. scriptの最後の処理を`footer`として保存する。
footer <-
r"(echo "All finished!"
)"
# 3. scriptのテンプレートを作成する。プレースホルダ `{label}`の部分を置換する。
# `awk`のスクリプト部分で`{}`を使う必要があるので、その部分はエスケープするために二重に囲う
cmd_template <-
r"(echo "Processing: '{label}'"
cat {label}.csv | \
awk -F, 'NR > 1 {{
sum1 += $1
sum2 += $2
}} END {{
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}}'
echo ""
)"
# 4. 先ほど作成したcsvファイルのファイル名(拡張子以外)を`labels`として、
# 文字列ベクトルに入れる。
labels <-
fs::dir_ls(regexp = ".csv$") %>%
fs::path_file() %>%
stringr::str_remove(".csv")
# 5. `label`変数を`labels`の各要素に順番に変更しながら、
# `glue::glue()`で`cmd_template`を置換して、
# `cmd_temp`ベクトルの要素として追加していく。
cmd_temp <- character()
for(label in labels) {
cmd_temp <- c(cmd_temp, glue::glue(cmd_template, .trim = FALSE))
}
# 6. `header`, `cmd_temp`, `footer`を併せて、
# スクリプトファイル`temp.sh`に書き込む。
c(header, cmd_temp, footer) %>% readr::write_lines("temp.sh")
上記のRのコードから出力されたBashスクリプトは以下の様になる。
temp.sh
#!/bin/bash
set -euC
echo "Process started!"
echo ""
echo "Processing: 'setosa'"
cat setosa.csv | \
awk -F, 'NR > 1 {
sum1 += $1
sum2 += $2
} END {
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}'
echo ""
echo "Processing: 'versicolor'"
cat versicolor.csv | \
awk -F, 'NR > 1 {
sum1 += $1
sum2 += $2
} END {
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}'
echo ""
echo "Processing: 'virginica'"
cat virginica.csv | \
awk -F, 'NR > 1 {
sum1 += $1
sum2 += $2
} END {
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}'
echo ""
echo "All finished!"
このようにglue::glue()
を用いると、 複数のファイルに対して同じ処理を行う(Bash)スクリプトを手軽に作成することができる。
実際にtemp.sh
を実行してみた結果が以下になる。
R version 4.3.2 (2023-10-31)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Ventura 13.1
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.11.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: Asia/Tokyo
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] magrittr_2.0.3
loaded via a namespace (and not attached):
[1] crayon_1.5.2 vctrs_0.6.5 cli_3.6.3 knitr_1.48
[5] rlang_1.1.4 xfun_0.46 stringi_1.8.4 purrr_1.0.2
[9] jsonlite_1.8.8 glue_1.7.0 bit_4.0.5 htmltools_0.5.7
[13] hms_1.1.3 fansi_1.0.6 rmarkdown_2.25 evaluate_0.24.0
[17] tibble_3.2.1 tzdb_0.4.0 fastmap_1.1.1 yaml_2.3.9
[21] lifecycle_1.0.4 stringr_1.5.1 compiler_4.3.2 fs_1.6.3
[25] htmlwidgets_1.6.4 pkgconfig_2.0.3 digest_0.6.34 R6_2.5.1
[29] tidyselect_1.2.1 readr_2.1.5 utf8_1.2.4 parallel_4.3.2
[33] vroom_1.6.5 pillar_1.9.0 tools_4.3.2 bit64_4.0.5