戯言日記

Rの話だと思ったら唐突にサバゲーが混じってくる何か。

gtパッケージの表を便利にするための小技

この記事はR言語 Advent Calendar 2023の20日目の記事です。
ギリギリ日付跨いだことについては見なかったことにしてください。

qiita.com

gtパッケージとは

適当なデータをgt::gt()に放り込むだけで、簡単にいい感じの表として出力できるパッケージ。
見た目に関してもかなり詳細に調整できるので論文用の表作成などにも十分使える。
個人的にはExcelに頼らずに済む点で非常に有用。

pacman::p_load(tidyverse, gt)

iris |> 
  reframe(across(where(is.numeric), mean), .by=Species) |> 
  gt()

もっと詳しく知りたい方はこちらが非常に参考になる。

qiita.com


今回のお話

とにかく便利なgtパッケージだが、

  1. セルの文章を改行したい
  2. 特定のセルにハイパーリンクをつけたい

をやろうとした際に沼ってしまった。
具体的にはこんな感じのデータを想定している。

pacman::p_load(tidyverse, gt, tidyRSS)

# R Advent Calendar 2023のRSSからデータを貰ってきて表にする
# なお分量が多いので5行だけ引っ張ってくる
advent <- tidyRSS::tidyfeed("https://qiita.com/advent-calendar/2023/rlang/feed")
colnames(advent)
advent |> 
  select(entry_published, entry_title, entry_link, entry_author) |> 
  arrange(entry_published) |> 
  head(5) |> 
  gt::gt()

タイトルのところはいい感じの位置で改行したいし、URLも無駄に幅を取っていて、このまま表記するのはちょっと見た目的によろしくない。

こういう表に対して、

  1. 単純に改行したい
     → 改行したい位置に『\n』や『<br>』を挿入すればいいのでは?
     → そのままgt::gt()に渡しても改行してくれない

  2. ハイパーリンクを付けたい
     → str_c()とかで「<a ref=link>name<\a>」みたいに変形すればいいのでは?
     → そのまm(ry

という訳で、この辺りを何とかしたいと思って探していたら意外と資料が少なかったので、自分用のメモも兼ねて紹介したい。


文字列の改行 - gt::md()

gt::md()は受け取った文字列をマークダウン記法としてgt::gt()で扱えるようにするための関数。
実際の処理としては、改行したい文字列に予め「<br>」を仕込んでおいて、gt::md()を噛ませてからgt::gt()に渡せばいい。

何もしないとこんな感じ。

advent |> 
  select(entry_published, entry_title, entry_author) |> 
  arrange(entry_published) |> 
  head(5) |> 
  gt::gt()

改行を入れることで見やすくすることができる。
ゴリ押しで改行を捻じ込んでいるのはご愛敬。

advent |> 
  select(entry_published, entry_title, entry_author) |> 
  arrange(entry_published) |> 
  dplyr::mutate(
    entry_title = str_replace(entry_title, "他の人の|時間を|を使って", "\\0<br>") |> purrr::map(gt::md),
    .keep="unused"
  ) |> 
  head(5) |> 
  gt::gt()


表にリンクを置く - gt::html()

gt::html()gt::md()のhtml版みたいなもの。
これを利用してハイパーリンクを付ければいい。

htmlについては直接書いてもいいが、データから変換するならhtmltoolsパッケージを使うと便利。
また、リンクを入れたい文章に改行も入れる場合はマークダウン記法で書いてgt::md()で変換すればいい。

advent |> 
  select(entry_published, entry_title, entry_link, entry_author) |> 
  arrange(entry_published) |> 
  mutate(
    entry_title = str_replace(entry_title, "他の人の|時間を|を使って", "\\0<br>") |> purrr::map(gt::md),
    .keep="unused"
  ) |> 
  mutate(
    #entry_title = map2(entry_link, entry_title, \(x,y) htmltools::a(href=x, y) |> as.character() |> gt::html()),
    #entry_link = stringr::str_c("[", entry_title, "](", entry_link, ")") |> purrr::map(gt::md),
    entry_link = glue::glue("[{entry_title}]({entry_link})") |> purrr::map(gt::md),
    .keep="unused"
  ) |>
  head(5) |> 
  gt::gt()


まとめ

gtパッケージはいいぞ。


Enjoy!

bookdownで図の相互参照が機能しない

wordで解析結果を報告するとかいう魔の業務が降ってきたので、cookbookを参考に図の相互参照を使おうとしたのだが機能してくれなかった。


gedevan-aleksizde.github.io


  • bookdown::html_document2: へは変更済み
  • チャンクのラベルも設定してある
  • 参照タイプ(figやtabなど)も問題なし

最終的には大元であるbookdownのリファレンスを読んだら解決した。


bookdown.org


If you want to cross-reference figures or tables generated from a code chunk, please make sure the chunk label only contains alphanumeric characters (a-z, A-Z, 0-9), slashes (/), or dashes (-).
意訳:コードチャンクから相互参照の図表を作る時、チャンクラベルには「アルファベット」「数字」「/」「-(ハイフン)」だけを使ってください。


確認するとチャンクラベルにがっつり「_(アンダーハイフン)」が入っていたので、これを削ったら動いた。
cookbookをざっと眺めた感じだとこの辺の記載がなさそうなので、相互参照でハマるとちょっと厄介かも1


Enjoy!


  1. 現在開発が進んでいる「Quarto」(RMarkdownの強化版みたいなもの)だと相互参照が標準装備になっているが、こちらもチャンクラベルに縛りがある(「@fig-dat1」みたいな書き方)ので、いずれにせよチャンクラベルはシンプルに書く癖をつけておいた方が良さそう。

Rのバージョンアップでゴリ押ししようとしたけどpacmanパッケージでいいじゃんとなった

概要

以前にこんな記事を書いた。

doubtpad.hatenablog.com

最新版のRStudioでいくつかの関数が文字化けする問題で、R 4.2にすれば治るのか確認したくて開発者版を入れたため、この記事の時と同じようにセットした。


install.packages(dir(file.path("C:/....../R/R-4.1.2/library")))


そしてRStudioがクラッシュした。
なんかここ最近、普通に解析してる場面でのクラッシュやら急な文字化けやら、Windows版に関してはかなり動作が怪しい気がするの自分だけだろうか……1

一括で進めていた途中でクラッシュしたので再度ダウンロードからやり直さないといけないのだが、以前のコードのままだとインストールできたものも改めて再インストールしてしまうため、ちょっと手直しした。
と言っても、for文で回して「パッケージがあるか確認→必要ならインストール」といった手順を踏むことで、エラーが起きても途中までは確実に進むようにしただけ。


for(i in dir(file.path("C:/Program Files/R/R-4.1.2/library"))){
  if(!requireNamespace(i, quietly = T)){
    try(install.packages(i))
  }
  else{
    print(i) #確認のためにコンソールへ出力
  }
}


注意点として、パッケージがインストールされているかの判断でよく見かけるrequire()を使うと、インストールしてあるパッケージを全て読み込んでしまう。
そのため今回はrequreNamespaceで判定する形にしている。

pacmanパッケージの活用

などとごちゃごちゃコードを書いていたのだが、もっと綺麗な書き方はないかと色々見ていたら2018年のRアドカレにこんな記事を見つけた。

blog.atusy.net

例えばpacman::p_load()はパッケージの有無の確認からインストール、その後のロードまで一括でやってくれるらしい。しかもパッケージ名は纏めて渡せる。何これ超便利。


pacman::p_load(tidyverse, magrittr, lubridate, data.table)


「……ということは、パッケージのロードはせずにインストールだけ実施できる関数もあるのでは?」と思って調べたらやはりあった。


for(i in dir(file.path("C:/Program Files/R/R-4.1.2/library")))
  pacman::p_install(i, character.only = T, force = F, type = "source")


正確には、p_install()でforce = Fを指定すると未インストールのパッケージのみ対象にとってインストールができる。
もうこれでいいじゃん。

p_install()はp_load()と違ってパッケージ名を纏めて渡すことができないのでfor文で回す必要はあるものの、先ほども言及した「途中でクラッシュした場合に面倒」という問題が回避できるため特に気にならない。
その際はcharacter.only = Tを入れることで変数に代入した文字列を読めるようになる(入れないと「i」という名前のパッケージを探そうとする)。

また、install.packages()のオプションも併用できるので、type = "source"などを指定しておくと一々操作する手間が省ける。
以前までなんかごちゃごちゃ書いていた分、簡潔に実現できることへの感動がすごい。


なお、環境を丸ごと引っ越さなくていいって人はRMarkdownなどでのライブラリ読込を全てp_load()に切り替えておけば、環境移行時にpacmanだけインストールするとあとは勝手に全部やってくれるのでストレスフリーになる。
もうこれでいいじゃん(2回目)。

結論

pacmanを使おう。


Enjoy!!



  1. たぶんR 4.2でUTF-8が標準になる仕様変更の煽りだと思うので、正式版の早期登場が待たれる。

RStudioの最新バージョンで`str_c()`の挙動がおかしい

概要

RStudioを最新版に更新して、さて解析するかと意気込んでいたら、どでかい地雷を踏んだ。

> library(tidyverse)

> str_c("文字", "化け")
[1] "\u0095�\u008e\u009a\u0089�\u0082�"

日本語の結合がおかしなことになっている。

簡単に検証

確認すると、str_flatten()str_detect()などの挙動もおかしくなっている。
全部確認した訳ではないが、stringrパッケージは全部ダメかもしれない(もしくは、確認してないけどstringiパッケージ系?)。

> str_flatten(c("文字", "化け"))
[1] "\u0095�\u008e\u009a\u0089�\u0082�"

> str_detect(c("文字", "化け"), "文字")
[1] TRUE TRUE

RStudioの更新前は普通に動いていたのと、試しにRguiで走らせてみると普通に出力されたので、やはりRStudioが原因っぽい。
という訳でRStudioの設定を見直したが、エンコードはどこもUTF-8にしてあるのでぱっと見だと問題はない。

しかも、むしゃくしゃしてpaste0()を使ったら何故か通る。なんでさ。

> paste0("文字", "化け")
[1] "文字化け"

あと、str_conv()エンコードすると普通に表示されるので、関数内で処理する時に何か挟んでいるのかもしれない。

> str_c("文字", "化け") %>% str_conv("shift-jis")
[1] "文字化け"

一応ロケールを表示。いやまぁpaste0()が通っちゃってるのでここが原因ではないとは思うのだが。
なお、locale関連のアレコレについてはこれまで調べたことがなかったので、こちらを参照しつつ確認した。

notchained.hatenablog.com

> Sys.getlocale()
[1] "LC_COLLATE=Japanese_Japan.932;LC_CTYPE=Japanese_Japan.932;LC_MONETARY=Japanese_Japan.932;LC_NUMERIC=C;LC_TIME=Japanese_Japan.932"

何となく軽い気持ちでロケールを英語に変更して走らせてみる。

> Sys.setlocale("LC_ALL", "English_US")
[1] "LC_COLLATE=English_United States.1252;LC_CTYPE=English_United States.1252;LC_MONETARY=English_United States.1252;LC_NUMERIC=C;LC_TIME=English_United States.1252"

> str_c("文字", "化け")
[1] "文字化け"

そこは通らないで欲しかった

GitHubで確認

事ここに至って、どうあがいても自分の手に余る状況になったので、諦めてGitHubを眺めていたら似たような事例があった。

github.com

中国語でのディレクトリ関連の文字化けについてだが、RStudioのバージョンアップで発生してるところとかも同じなので、たぶん根幹の部分は同じ気がする1

他にもちょこちょこロケール絡みのバグはあるっぽい2が、UTF-8が標準になるR4.2へのアップデートで解決する(らしい)ので、しばらくは待機するしかないかもしれない。


結論

現状だと、RStudioのアップデートはよく考えた上でやった方が良さそう。


Enjoy!


追記(2022/02/05)

readr::read_csv()で日本語のフォルダ内に格納したファイルを読もうとしたら、ファイルパスが文字化けしてエラーになった。
data.table::fread()では普通に読み込めたので、もしかするとtidyverse系の処理と相性が悪いのかもしれない。


さらに追記(2022/02/11)

開発版であるR-devel(R 4.2相当)で確認したところ、str_c()での日本語の処理に問題がなかった(Sys.getlocale()で確認したところUTF-8の表示)。
バージョンアップが来たら即座にやった方が良さそう。



  1. dir()を走らせてみると「メモ.txt」が「繝。繝「.txt」に文字化けしたので、UTF-8がデフォルトにならない限り回避不能なトラブルな気がしてきた……。

  2. 観測した範囲だと、gsub()が上手く機能しない事例(https://github.com/rstudio/rstudio/issues/10294)とかも若干近い内容な気がする。

RMarkdownでtargetsパッケージを使って前処理データを引っ張ろうとしたらknitr時にエラーを起こした

この記事はR Advent Calendar 2021 23日目のところに大遅刻して投稿した記事です。

概要

  • targetsパッケージとRMarkdownを併用しようとしたらエラーが発生
  • RStudioのRMarkdownの設定でチャンク評価時のディレクトリの設定変更を忘れていたせいだった
  • targetsパッケージに限らず、プロジェクト内で複数のRMarkdownを1つのフォルダに入れていたりすると引っ掛かりそう

targetsパッケージとは

targetsパッケージは処理フローを管理するためのパッケージ。
最近TokyoRの資料を眺めていて知った。

{targets}でワークフローを管理せよ / Workflow management with targets - Speaker Deck

個人的な推し関数はtar_load()1

概要だけざっくり書くと、あるデータに対してこれまでは

  1. 前処理のコードを書いて、
  2. 前処理をして、
  3. それぞれrdsファイルとかに保存して、
  4. ファイルパス指定で読み出して、
  5. 解析を実施する

みたいな流れの中で、ソースコードやデータの保存・修正・管理にクソ面倒な手順を踏んでいたのが、処理フローの可視化を含めて一括で管理しやすくなるもの。上記フローの2~4までが分かりやすくなるイメージでいいと思う。
割と便利なので、新しい解析データを弄る際にお試しでちょこちょこ使っている。

どんなエラーを踏んだのか

自分の場合、日頃の解析では後でレポート形式にする手間を省くため2、RMarkdown上でコードを書いて検討している。

RMarkdown上でもtargetsは普通に使える3ので、事前に別ファイルのソースコードで前処理した結果をtar_load()で呼び出しつつ解析をしている。

で、解析が一段落したのでknitrボタンで保存しておこうとしたら、tar_load()の部分で以下のようなエラーが発生4

Error: missing files: _targets/meta/meta

フォルダを見ると普通に存在しているし、チャンク内でコードを動かす分には問題なく読めている。
で、試しにtar_make()をチャンクに入れてknitrしてみると以下のようなエラーが発生。

Error: could not find file _targets.R. Main functions like tar_make() require a target script file (default: _targets.R) to define the pipeline. Functions tar_edit() and tar_script() can help. 

knitrする時だけ発生しているので、たぶんRMarkdown関連の設定のどれかが悪さをしていると思われる。

原因

で、RStudioの設定を眺めていたら、思いの他簡単に見つかった。

f:id:doubtpad:20211224235616p:plain
RMarkdownの設定

RStudioの画面上部で「Tools→Global Options...→R Markdown」の順に開いていったところにある「Evaluate chunks in directory」がDocumentになっていた。
この設定を「Project」に変更しておかないとknitr時のディレクトリがRmdファイルのある場所になるため、プロジェクト内の階層構造によってはエラーを起こす5
実際、「Project」に変更したところ問題なく動き始めた。

結論

プロジェクト内でRMarkdownをファルダ作って管理する場合は設定に注意。


Enjoy!



  1. targetsパッケージを使った処理フロー内の任意のデータを環境変数として引っ張ってこれる関数。ファイルパスとか保存場所とかから解放されるので想像以上に快適。

  2. knitrボタン1つで纏められるおかげで、報告書とかに時間取られなくて本当に助かる。ただしdocx形式で要求されるとかいう特殊な状況は考慮してないので、その場合は死にそうになる。

  3. 公式が紹介しているTarget Markdownではなく、解析用にデータを引っ張ってくるために使用。

  4. tar_read()でも同様に発生。

  5. データ解析の際はプロジェクトフォルダの中にデータ保存フォルダやRmdファイル用フォルダなど細かく分けているため、以前からここの設定については変更しておいたはずなのだが、年末に色々あってR環境を再構築してた時に事故った模様。