Heavy Watal

readr — 高速で柔軟なテーブル読み込み

タブ区切りテキストやCSVファイルを読み込んでdata.frameにするツール。 .gz.xz などの圧縮ファイルも透過的に読み書き可能。 標準でも read.table()read.csv() があるけど、それらと比べて

tidyverse に含まれているので、 install.packages("tidyverse") で一括インストール、 library(tidyverse) で一括ロード。 例えば:

library(conflicted)
library(tidyverse)
write_tsv(diamonds, "diamonds.tsv.gz")
read_tsv("diamonds.tsv.gz")

主な関数

ファイル読み込み

read_delim(file, delim,
  quote = '"',
  escape_backslash = FALSE,
  escape_double = TRUE,
  col_names = TRUE,
  col_types = NULL,
  col_select = NULL,
  id = NULL,
  locale = default_locale(),
  na = c("", "NA"),
  quoted_na = TRUE,
  comment = "",
  trim_ws = FALSE,
  skip = 0,
  n_max = Inf,
  guess_max = min(1000, n_max),
  name_repair = "unique",
  num_threads = readr_threads(),
  progress = show_progress(),
  show_col_types = should_show_types(),
  skip_empty_rows = TRUE,
  lazy = TRUE)

read_csv(...)read_tsv(...) は区切り文字 delim = 指定済みのショートカット。

read_table(...)
連続する空白文字をひとつの区切りと見なして処理
read_fwf(file, col_positions, ...)
fixed width file. 第二引数の指定方法は
  1. fwf_empty(infile, skip = 0, col_names = NULL) で自動推定
  2. fwf_widths(widths, col_names = NULL) で幅指定
  3. fwf_positions(start, end, col_names = NULL) で開始・終了位置指定
read_lines(file, skip = 0, n_max = -1L, ...), read_lines_raw(...)
1行を1要素とした文字列ベクタとして読み込む
read_file(file)
ファイルの内容まるごと文字列で返す

同じ形式の複数ファイルをvectorで渡せば、順に読み込んで rbind() してくれる。 purrr::map(file, readr::read_csv) |> purrr::list_rbind(names_to = "file") と同等だが、それよりも少し高速で、列の型推定もうまくいきやすい。 ただし names_to = のように元のファイルとの関連を残すオプションは無い。

ファイルの中身を文字列として渡すことも可能。 自動で判別してもらえるけど I() で包むのが確実。

content = "x,y\n1,a\n"
readr::read_csv(I(content))
  x y
1 1 a

ファイル書き出し

標準の write.*() 関数をオプション無しで使うと、 左端に余計な列を追加したり、不要な"クオート"を追加したりする。

iris |> head(2) |> write.csv(stdout())
"","Sepal.Length","Sepal.Width","Petal.Length","Petal.Width","Species"
"1",5.1,3.5,1.4,0.2,"setosa"
"2",4.9,3,1.4,0.2,"setosa"
iris |> head(2) |> readr::write_csv(stdout())
Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species
5.1,3.5,1.4,0.2,setosa
4.9,3,1.4,0.2,setosa

readr::write_*() のデフォルトはそれよりだいぶマシだが、 欠損値を空欄ではなく NA にしてしまうことだけ要注意。 空欄にするには毎回 na = "" が必要。

write_delim(x, file,
  delim = " ",
  na = "NA",
  append = FALSE,
  col_names = !append,
  quote = c("needed", "all", "none"),
  escape = c("double", "backslash", "none"),
  eol = "\n",
  num_threads = readr_threads(),
  progress = show_progress())

write_csv(...)write_tsv(...) は区切り文字 delim = 指定済みのショートカット。

write_lines(x, file, sep = "\n", na = "NA", append = FALSE)
vectorやlistを1行ずつ書き出す。
write_file(x, file, append = FALSE)
文字列をそのまま書き出す。

文字列から別の型へ

parse_number(x, na = c("", "NA"), locale = default_locale(), trim_ws = TRUE)
文字列で最初に登場する数値を抜き出す。 邪魔な文字が前後に入っていても大丈夫。
x = c("42", "1.293e2", "i18n", "24/7")
readr::parse_number(x)
[1]  42.0 129.3  18.0  24.0
parse_double(x, ...)
文字列を実数型として解釈して返す。 "6e23" のような指数形式も大丈夫。 異物が混じっていた場合は警告して欠損値扱い。
readr::parse_double(x)
[1]  42.0 129.3    NA    NA
attr(,"problems")
  row col               expected actual
1   3  NA               a double   i18n
2   4  NA no trailing characters   24/7
as.double(x)
[1]  42.0 129.3    NA    NA
parse_double(x, ...), parse_integer(x, ...)
文字列を整数型として解釈して返す。 小数点などを含む文字列は警告して欠損値扱い。
readr::parse_integer(x)
[1] 42 NA NA NA
attr(,"problems")
  row col               expected  actual
1   2  NA no trailing characters 1.293e2
2   3  NA no trailing characters    i18n
3   4  NA no trailing characters    24/7
as.integer(x)
[1]  42 129  NA  NA

標準の as.integer() が1ではなく129を返すということは、 一旦実数型で読み取ったあと小数点以下を切り捨ててるっぽい。

parse_logical(x, ...)
特定の文字列を論理値型として解釈。 大文字小文字は問わない。 “0以外の数字はtrue” のような数値型からの変換規則とは異なる。 T, FTRUE, FALSE 扱いしてしまうことに注意。
readr::parse_logical(c("1", "0", "t", "f", "TRUE", "FALSE", "2"))
[1]  TRUE FALSE  TRUE FALSE  TRUE FALSE    NA
attr(,"problems")
  row col           expected actual
1   7  NA 1/0/T/F/TRUE/FALSE      2
as.logical(c("1", "0", "t", "f", "TRUE", "FALSE", "2"))
[1]    NA    NA    NA    NA  TRUE FALSE    NA
as.logical(c(-1, 0, 1, 2))
[1]  TRUE FALSE  TRUE  TRUE

parse_factor(x, levels, ordered = FALSE, ...)

parse_date(x, format = "", ...), parse_datetime(x, format = "", ...), parse_time(x, format = "", ...)

列の型を指定する

https://cran.r-project.org/web/packages/readr/vignettes/column-types.html

基本的には何も指定しなくても数値などを認識していい感じに設定してくれる。 文字列を勝手にfactorに変換したりはしない。 整数と実数は区別せずnumeric型で読む(1.2から)。

明示的に型を指定したい場合は col_types 引数に cols() 関数の値を渡す。 文字列で "ccdi_" のように省略することも可能。

read_csv("mydata.csv", col_types="ccdi_")

colsp = cols(length=col_double(), count="i", .default="c")
read_csv("mydata.csv", col_types=colsp)

指定した列だけ読むには cols(..., .default = col_skip()) とするか cols_only(...) を使う。

設定

最近ちょっと表示がおせっかい過ぎるので ~/.Rprofile で設定を直す:

options(
  readr.num_columns = 0L,
  readr.show_col_types = FALSE,
  readr.show_progress = FALSE
)

read::read_*() で読み込んだばかりのtibbleは各列の型情報を含んだ spec_tbl_df という特殊なサブクラスになっている。 この機能を切るオプションは用意されていない。 すぐに普通のtibbleが欲しい場合は [] で空subsettingするのが手軽。

data = readr::readr_example("mtcars.csv") |> readr::read_csv()
class(data)
[1] "spec_tbl_df" "tbl_df"      "tbl"         "data.frame" 
class(data[])
[1] "tbl_df"     "tbl"        "data.frame"

Excelファイルを読み込む

https://github.com/tidyverse/readxl

自分のデータは絶対にExcel形式ではなくCSVやTSV形式で保存すべきだが、 人から受け取ったファイルや論文のサプリデータがExcelだったら仕方がない。 readxl というパッケージを利用すれば、 一旦Officeで開いてCSVに変換するという手間なしで直接Rに読み込める。

Rの中から install.packages("readxl") でインストールし、 使う前に library(readxl) でパッケージを読み込む。

excel_sheets(path)
ファイルに含まれるシートの名前を取得
read_excel(path, sheet = 1, col_names = TRUE, col_types = NULL, na = "", skip = 0)
.xlsxlsx のどちらの形式でも読める。 sheet は番号でも名前でもいい。 それ以降の引数については readr の関数と同じ。

tibble

tbl_df クラスが付与された改良版data.frameのことをtibbleと呼ぶ。 readr で読み込んだデータもこの形式になる。

tbl_mtcars = as_tibble(mtcars)
class(tbl_mtcars)
[1] "tbl_df"     "tbl"        "data.frame"
class(mtcars)
[1] "data.frame"

生のdata.frameとの違いは:

新しいtibble 1.4以降では pillar というパッケージが有効数字や欠損値などの表示形式を勝手にイジるようになってしまった。 見やすくない上に遅いので私は registerS3method() で上書きしている。

関数

tibble::tibble(...)
tibbleを新規作成。ちょっと昔までは dplyr::data_frame() だった。
base::data.frame() と違ってバグが混入しにくくて便利:
  • 勝手に型変換しない (stringsAsFactors = FALSEが基本)
  • 勝手に列名を変えない
  • 長さ1の変数以外はリサイクルしない
  • 引数の評価がlazyに行われるので前の列を利用して後の列を作ったりできる
  • tbl_df クラスを付加
  • ただし1.4以降のバージョンでは表示が遅くて見にくい
tibble::as_tibble(x)
既存のdata.frameやmatrixをtibbleに変換。 ちょっと昔までは dplyr::tbl_df() とか dplyr::as_data_frame() だった。 v2.0からは列名がちゃんとついてないとエラーになる。
tibble::new_tibble(x, ..., nrow, class = NULL)
tibbleのサブクラスを扱う開発者向け as_tibble() 。 検証なし、nrow 必須の代わりに高速。 クラスを先頭に追加できるのも便利。
tibble::enframe(x, name = "name", value = "value")
名前付きvectorとかlistを2列のtibbleに変換する。 tibble::deframe(x) はその逆。 c(a = 1, b = 2) |> enframe() |> deframe()
tibble::add_row(.data, ..., .before = NULL, .after = NULL)
既存のtibbleに新しいデータを1行追加する。
tibble::rownames_to_column(df, var = "rowname")
行の名前をcharacter型で1列目の変数にする。dplyr::add_rownames()の後継。
tibble::rowid_to_column(df, var = "rowid") はそれを整数で。
tibble::column_to_rownames(df, var = "rowname") はその逆。
tibble::remove_rownames(df) は消すだけ。
tibble::glimpse(.data, width = NULL)
データの中身をざっと見る。 print() とか str() のようなもの。
pillar::type_sum(x)
オブジェクトの型
pillar::obj_sum(x)
type_sumとサイズ e.g., "data.frame [150 x 5]"

設定

表示される行数や幅を調節する項目には以下のようなものがある。 ~/.Rprofile に書いておけば起動時に勝手に設定される。

height = 30L  # for example
width = 160L
options(
  pillar.neg = FALSE,
  pillar.subtle = FALSE,
  pillar.print_max = height,
  pillar.print_min = height,
  pillar.width = width,
  width = min(width, 10000L)
)

大きいtibbleの全体を表示する

普通に print() すると大きいtibbleの全体が見えない。 print() 関数にオプションを指定したり utils::page() を利用したりする必要がある。 RStudioやVSCodeを使っている場合は View() でスプレッドシートのように閲覧可能。

# pillar:::print.tbl()にオプションを渡す方式
diamonds |> print(n = Inf, width = Inf)
diamonds |> page("print", n = Inf, width = Inf)

# 標準data.frame用のprint()を呼ぶ方式
diamonds |> base::print.data.frame(max = .Machine$integer.max)

オプションをいちいち設定しなくて済むように max_print(), less() のような関数を定義しておくのもよい。

getOption("max.print") の初期値は99999だがRStudioでは勝手に1000まで下げられる。

関連書籍