ill-identified diary

所属組織の見解などとは一切関係なく小難しい話しかしません

[R] 小ネタ: Rのサンプルデータの詳細情報を一括表示する

概要

ふと思いついたのでやってみたが時間がかかったわりにあまりおもしろくなかった.

ところで私は最近のRの参考書を全然読んでない. 今回のネタがそういう最近の本に既に書いてある話だったら申し訳ない.

初めに

data(iris) など, data() 関数は動作確認に使えるサンプルデータを取り出せる. しかし全部のデータセットを把握するのは難しいしする必要もあまりないので, 簡単に一覧を確認する方法を紹介する.

答えは

data()

だけである. これで一覧表示できる. package= で指定したパッケージが提供するもの一覧に絞ることもできる…… まあそれだけでわざわざこうやってブログを書いたりしない.

本題

ここからが本題. インストールしたパッケージが多いと膨大になり, どれを選べばよいかわからなくなる. データセットを指定しないとき, data() 関数が返す packageIQR オブジェクトはRStudio だと新しいタブとして開かれる. しかしこれだと検索しづらい. そこで, matrix 型で一覧情報が入っている results 要素を取り出す. これもそのままだと見づらいので tidyverse を使って tibble に変換する. 私の場合はこんな感じになる.

require(tidyverse)
as_tibble(data()$results)
# A tibble: 242 x 4
   Package  LibPath                                    Item              Title
   <chr>    <chr>                                      <chr>             <chr>
1 ggthemes /home/ill/R/x86_64-pc-linux-gnu-library/3.6 canva_palettes    150 Color Palettes from Canva
2 ggthemes /home/ill/R/x86_64-pc-linux-gnu-library/3.6 ggthemes_data     Palette and theme data
3 forcats  /home/ill/R/x86_64-pc-linux-gnu-library/3.6 gss_cat           A sample of categorical variables from the General Social survey
4 stringr  /home/ill/R/x86_64-pc-linux-gnu-library/3.6 fruit             Sample character vectors for practicing string manipulations.
5 stringr  /home/ill/R/x86_64-pc-linux-gnu-library/3.6 sentences         Sample character vectors for practicing string manipulations.
6 stringr  /home/ill/R/x86_64-pc-linux-gnu-library/3.6 words             Sample character vectors for practicing string manipulations.
7 dplyr    /home/ill/R/x86_64-pc-linux-gnu-library/3.6 band_instruments  Band membership                                  
8 dplyr    /home/ill/R/x86_64-pc-linux-gnu-library/3.6 band_instruments2 Band membership                                  
9 dplyr    /home/ill/R/x86_64-pc-linux-gnu-library/3.6 band_members      Band membership                                  
10 dplyr    /home/ill/R/x86_64-pc-linux-gnu-library/3.6 starwars          Starwars characters       

これで filter() を使ってキーワード検索ができる. しかしこれだけだとパッケージ, インストール場所, データ名と簡単な概要しか分からない. そこで, 各データセットに対応するヘルプからもう少し詳細な情報を引っ張ってくる. ?help() で個別に調べればいい話なのだが, せっかく tidyverse で一覧を読み込んだのでまとめてやってしまおう. R のヘルプは Rd というフォーマットで書かれており, そのなかでデータセットの形式は format というセクションで書くというルールがある (絶対守られているかは怪しい). そこで対応する Rd ドキュメントの format を取り出せるようにする.

しかし, ここが少し厄介で, 結局 tidyverse に加えて rvest, shiny が必要になった. 以下の make_help_Rd() はデータ・セット名とパッケージ名から対応するヘルプドキュメント (Rd オブジェクト) を取得する関数, display_help_section() はその Rd オブジェクトから任意のセクションを取り出す関数, detect_Rd_text() は複数の Rd 内の検索を簡単にするための関数である

require(tidyverse)
require(rvest)
require(shiny)
make_help_Rd <- function(topic, package){
  topic <- str_split(topic, " ", simplify = T)[, 1] # Item には name (Title)  のような書き方もある
  file <- utils:::index.search(topic, paths = find.package(package))
  path <- dirname(file)
  RdDB <- file.path(path, package)
  tools:::fetchRdDB(RdDB, basename(file))
}
display_help_section <- function(Rd, section = "Format"){
  txt <- capture.output({tools::Rd2HTML(Rd)}) %>% paste(collapse = "\n") %>%
    read_html  %>%
    html_nodes(xpath=paste0("//h3[contains(text(), '", section, "')]/following::p")) %>%
    as.character %>%  paste(collapse = "\n") %>%
    htmltools::HTML('<html><head><meta charset="utf-8"/></head>', ., "</html>") #%>%
  view_html <- function(html) {
    shiny::shinyApp(
      ui = htmltools::HTML(html),
      server = function(input, output) {}
    )
  }
  view_html(txt)
}

detect_Rd_text <- function(x, pattern, negate = F){
  capture.output({tools::Rd2txt(x)}) %>% paste(collapse = "\n") %>% str_detect(pattern, negate)
}

例えば以下のように使う.

(1) ヘルプドキュメントを全取得

as_tibble(data()$results) %>%
  mutate(Rd = map2(Item, Package, ~make_help_Rd(.x, .y)))
# A tibble: 139 x 5
   Package LibPath                                    Item              Title                                                          Rd   
   <chr>   <chr>                                      <chr>             <chr>                                                          <lis>
 1 forcats /home/ill/R/x86_64-pc-linux-gnu-library/3.6 gss_cat           A sample of categorical variables from the General Social sur… <Rd> 
 2 stringr /home/ill/R/x86_64-pc-linux-gnu-library/3.6 fruit             Sample character vectors for practicing string manipulations.  <Rd> 
 3 stringr /home/ill/R/x86_64-pc-linux-gnu-library/3.6 sentences         Sample character vectors for practicing string manipulations.  <Rd> 
 4 stringr /home/ill/R/x86_64-pc-linux-gnu-library/3.6 words             Sample character vectors for practicing string manipulations.  <Rd> 
 5 dplyr   /home/ill/R/x86_64-pc-linux-gnu-library/3.6 band_instruments  Band membership                                                <Rd> 
 6 dplyr   /home/ill/R/x86_64-pc-linux-gnu-library/3.6 band_instruments2 Band membership                                                <Rd> 
 7 dplyr   /home/ill/R/x86_64-pc-linux-gnu-library/3.6 band_members      Band membership                                                <Rd> 
 8 dplyr   /home/ill/R/x86_64-pc-linux-gnu-library/3.6 starwars          Starwars characters                                            <Rd> 
 9 dplyr   /home/ill/R/x86_64-pc-linux-gnu-library/3.6 storms            Storm tracks data                                              <Rd> 
10 tidyr   /home/ill/R/x86_64-pc-linux-gnu-library/3.6 billboard         Song rankings for billboard top 100 in the year 2000           <Rd> 
# … with 129 more rows

(2) Rd 内をキーワード検索してマッチしたものだけ取り出す. 例えば iris という文字列が含まれるのは

as_tibble(data()$results) %>%
  mutate(Rd = map2(Item, Package, ~make_help_Rd(.x, .y))) %>%
  filter(map_lgl(Rd, ~detect_Rd_text(.x, "iris")))
# A tibble: 2 x 5
  Package  LibPath            Item  Title                      Rd      
<chr>    <chr>              <chr> <chr>                      <list>
1 datasets /usr/lib/R/library iris  Edgar Anderson's Iris Data <Rd>
2 datasets /usr/lib/R/library iris3 Edgar Anderson's Iris Data <Rd>

(3) 1行目のRd のFormat 部分を表示

as_tibble(data()$results)[1, ] %>%
  mutate(Rd = map2(Item, Package, ~make_help_Rd(.x, .y))) %>%
  .$Rd %>% .[[1]] %>% display_help_section()
year of survey, 2000–2014
age. Maximum age truncated to 89.
marital status
race
reported income
party affiliation
religion
denomination
hours per day watching tv
Downloaded from https://gssdataexplorer.norc.org/.
f:id:ill-identified:20200906094230p:plain
出力例

技術的な解説

例えば以下の stackoverflow の質問にもあるように, 本来なら help() だけでドキュメントオブジェクトを取得できる.

How do I store the html or text of a R helpfile into a variable - Stack Overflow

しかし NSE が邪魔しているのか mutate() 内でうまく動作しなかった. 原因特定が大変そうだったので, help() のソースを確認し, utils:::.getHelpFile() などを参考に mutate() 内部でも動作する関数を自作した.

さらに, 組み込みパッケージの tools には Rd オブジェクトをプレーンテキストや HTML に変換する関数が用意されているので, 一旦 HTML に変換してから rvest を通して必要な部分だけを取り出すようにした.

このHTML形式のヘルプドキュメントを表示させたいのだが, 普段あまりそういう使い方をしないのでどうするのが最もシンプルなやり方なのかなかなか思いつかなかった. すぐに思いつくのは shiny を使うことだが, ちょっとHTMLをレンダリングするだけで持ち出すのも大げさな気がしていろいろ模索していた. R-wakalang の方でも質問したが, 結局 shiny が一番簡単そうだった.

結論

というわけでRデータ・セットのヘルプドキュメントを一括で取得する方法がわかった. しかしヘルプを取り出して表示するのに shiny を使うのは物々しすぎる気がする.

ドキュメントが少ないのでなんかあんまりおもしろくない割に時間がかかってしまった. というかデータセットのヘルプ取得以外に応用の余地がありそうな話である気がする. 例えば References を一括取得するとか.