readr は、ユーザが型を指定しない場合、データから列の型を推測します。 guess_max
パラメータは、入力ファイルの何行目から推測を行うかを制御します。 理想的には、ヘッダでない最初の行から列の型が完全にわかるようにし、 guess_max = 1
を使用することができます。 そうすれば非常に効率的です。 しかし、状況はそれほど明確ではありません。
デフォルトでは、readr は型推測を行う際に1000行を参照します。つまり、 guess_max = 1000
となります。 readr はインポートされない行を参照しないので、実際のデフォルトは guess_max = min(1000, n_max)
であることに注意してください。
時には、n_max
を指定せずに、「これからインポートする全てのデータから列の型を推測する」ということを伝えたい場合があります。 その場合、どのように表現すればよいでしょうか。 また、このような要求の考えられるマイナス面についても議論しておく価値があります。
library(readr)
readr はバージョン 2.0.0(2021年7月リリース)で新しい parsing エンジンを手に入れました。 このいわゆるセカンド・エディションでは、readr はデフォルトで vroom::vroom()
を呼び出します。 vroom パッケージ、そして readr のセカンド・エディションは、「すべてのデータを使って推測する」という非常に自然な表現、すなわち guess_max = Inf
をサポートしています。
read_csv("path/to/your/file", ..., guess_max = Inf)
なぜ、これがデフォルトではないのでしょうか? なぜいつもそうしないのでしょうか? なぜなら、列型の推測は基本的に、メインのパースに加えて、データをもう一回通過させるからです。
もし guess_max = Inf
を日常的に使用している場合、基本的にすべてのファイルを2回、全体的に処理することになります。 小さなファイルしか扱わないのであれば、これは問題ありません。 しかし、大きなファイルでは、これは非常にコストがかかるだけでなく、メリットもあまりありません。 多くの場合、ファイルのサブセットに基づいて推測される列の型は、「十分なもの」です。
また、有限の n
に対して guess_max = n
を指定すると、セカンド・エディションのパーサーでよりうまく動作することに注意してください。 以前のバージョンの readr は最初の n
行を参照するだけでしたが、 vroom はファイル中の n
行をサブサンプルすることができ、常に最後の行を含めます。 実際、デフォルトの guess_max = min(1000, n_max)
は、以前よりも列タイプを上手に推測することができるようになりました。 これで guess_max
を設定する必要性が少なくなったと感じるはずです。
データ分析プロジェクトが成熟し、探索的な段階を過ぎると、列のタイプを明示することが最善の策であることを、常に念頭に置いてください。
2.0.0 より前のバージョンの readr の構文解析エンジンは、現在ではファースト・エディションと呼ばれています。 readr >= 2.0.0を使用している場合、関数 with_edition()
と local_edition()
を使ってファースト・エディションのパースにアクセスすることができます。 また、readr < 2.0.0を使用している場合、定義上、ファースト・エディションのパージングを得ることができます。
ファースト・エディションのパーサーは「全てのデータを使って列の型を推測する」ということを完璧に伝える方法を持っていません。 (これは readr >= 2.0.0を好むいくつかの理由のうちの1つです。)
ちょっとトリッキーなファイルをセットして、いろいろなアプローチを実証してみましょう。 列 x
はほとんど空ですが、一番最後の1001行目に数値データがあります。
<- tibble::tibble(
tricky_dat x = rep(c("", "2"), c(1000, 1)),
y = "y"
)<- tempfile("tricky-column-type-guessing-", fileext = ".csv")
tfile write_csv(tricky_dat, tfile)
まず、セカンド・エディションのパーサーは、デフォルトの guess_max
の動作でも x
に対して正しい型を推測していることに注意してください。
tail(read_csv(tfile))
#> Rows: 1001 Columns: 2
#> ─ Column specification ────────────────────────────
#> Delimiter: ","
#> chr (1): y
#> dbl (1): x
#>
#> ℹ Use `spec()` to retrieve the full column specification for this data.
#> ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
#> # A tibble: 6 × 2
#> x y
#> <dbl> <chr>
#> 1 NA y
#> 2 NA y
#> 3 NA y
#> 4 NA y
#> 5 NA y
#> 6 2 y
これに対して、ファースト・エディションのパーサーは guess_max
のデフォルトでは x
に対して正しい型を推測しません。 x
は論理型としてインポートされ、2
は NA
になります。
with_edition(
1,
tail(read_csv(tfile))
)#>
#> ─ Column specification ────────────────────────────
#> cols(
#> x = col_logical(),
#> y = col_character()
#> )
#> Warning: 1 parsing failure.
#> row col expected actual file
#> 1001 x 1/0/T/F/TRUE/FALSE 2 '/var/folders/ct/3v1w53cn1z5fc94bsyt7vr0c0000gn/T//RtmpmfBzPr/tricky-column-type-guessing-d3dc57681814.csv'
#> # A tibble: 6 × 2
#> x y
#> <lgl> <chr>
#> 1 NA y
#> 2 NA y
#> 3 NA y
#> 4 NA y
#> 5 NA y
#> 6 NA y
3つの方法がありますが、それぞれデメリットがあります。
セカンド・エディションのパーサーと同じように guess_max = Inf
を指定します。
readr は処理するデータの量を知らないので、ファースト・エディションのエンジンはこの不確実性に直面して、大量のメモリを事前に割り当てます。 つまり、guess_max = Inf
で読み込むと、非常に遅くなり、Rのセッションがクラッシュする可能性さえあるのです。
with_edition(
1,
tail(read_csv(tfile, guess_max = Inf))
)#> Warning: `guess_max` is a very large value, setting to `21474836` to avoid
#> exhausting memory
#>
#> ─ Column specification ────────────────────────────
#> cols(
#> x = col_double(),
#> y = col_character()
#> )
#> # A tibble: 6 × 2
#> x y
#> <dbl> <chr>
#> 1 NA y
#> 2 NA y
#> 3 NA y
#> 4 NA y
#> 5 NA y
#> 6 2 y
実際の、無限でない値を guess_max
に指定してください。
というのも、もし行の数がわかっていれば、そもそもこのような会話はしないでしょう。 しかし、時には適切な推定値があり、 guess_max
の値として「十分な大きさ」を選択することができます。 これは通常、 guess_max = Inf
よりもはるかに良いパフォーマンスとなります。
with_edition(
1,
tail(read_csv(tfile, guess_max = 1200))
)#>
#> ─ Column specification ────────────────────────────
#> cols(
#> x = col_double(),
#> y = col_character()
#> )
#> # A tibble: 6 × 2
#> x y
#> <dbl> <chr>
#> 1 NA y
#> 2 NA y
#> 3 NA y
#> 4 NA y
#> 5 NA y
#> 6 2 y
すべての列を文字として読み、次に type_convert()
を使用します。 これは、データをRに取り込んだ後に後処理をしなければならないので、少し不便です。
<- with_edition(
dat_chr 1,
read_csv(tfile, col_types = cols(.default = col_character()))
)tail(dat_chr)
#> # A tibble: 6 × 2
#> x y
#> <chr> <chr>
#> 1 <NA> y
#> 2 <NA> y
#> 3 <NA> y
#> 4 <NA> y
#> 5 <NA> y
#> 6 2 y
<- type_convert(dat_chr)
dat #>
#> ─ Column specification ────────────────────────────
#> cols(
#> x = col_double(),
#> y = col_character()
#> )
tail(dat)
#> # A tibble: 6 × 2
#> x y
#> <dbl> <chr>
#> 1 NA y
#> 2 NA y
#> 3 NA y
#> 4 NA y
#> 5 NA y
#> 6 2 y
一時的なトリッキーなcsvファイルをクリーンアップします。
file.remove(tfile)
#> [1] TRUE