Column type guessing

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

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 を設定する必要性が少なくなったと感じるはずです。

データ分析プロジェクトが成熟し、探索的な段階を過ぎると、列のタイプを明示することが最善の策であることを、常に念頭に置いてください。

readr ファースト・エディションと readr < 2.0.0

2.0.0 より前のバージョンの readr の構文解析エンジンは、現在ではファースト・エディションと呼ばれています。 readr >= 2.0.0を使用している場合、関数 with_edition()local_edition() を使ってファースト・エディションのパースにアクセスすることができます。 また、readr < 2.0.0を使用している場合、定義上、ファースト・エディションのパージングを得ることができます。

ファースト・エディションのパーサーは「全てのデータを使って列の型を推測する」ということを完璧に伝える方法を持っていません。 (これは readr >= 2.0.0を好むいくつかの理由のうちの1つです。)

ちょっとトリッキーなファイルをセットして、いろいろなアプローチを実証してみましょう。 列 x はほとんど空ですが、一番最後の1001行目に数値データがあります。

tricky_dat <- tibble::tibble(
  x = rep(c("", "2"), c(1000, 1)),
  y = "y"
)
tfile <- tempfile("tricky-column-type-guessing-", fileext = ".csv")
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 は論理型としてインポートされ、2NA になります。

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つの方法がありますが、それぞれデメリットがあります。

一時的なトリッキーなcsvファイルをクリーンアップします。

file.remove(tfile)
#> [1] TRUE