readrとdata.tableでデータを読み込む時に値が抜け落ちたり型がへんてこになる現象

【最下部に追記あり】

こんにちは

今回はRでデータをいい感じに読み込んでくれるパッケージであるreadrやdata.tableについて、業務利用時に予想外の挙動をして時間を溶かしてしまったので、備忘録的に記録します。

具体的に発生した挙動を簡単に説明すると下記です。

- __readrにおいて、先頭行にNAが固まっていると、全てNAで読み込んでしまう__
- __data.tableにおいて、NAを含む日付型の列が文字列型になってしまい、NAも空白セルの文字列という扱いで読み込んでしまう__


自分のケースでは、データベースからSQLでローデータを抽出し、それをRで加工するために読み込む際に発生しました。
NAの多いデータセット扱う際に割と発生しそうな罠なので、気にしてみるといいかもしれません。


それでは本文です。

# データと前準備
今回は下記のようなcsvデータを作成しました。
https://docs.google.com/spreadsheets/d/1hhNxisbIWrIwTY2w0xLFGwaOsWBCInP2vAuoHpmpS4o/edit?usp=sharing
自分で作成したい方は、行数が3000行で、かつ下記要件を満たすデータセットを自作してみてください。

- 何かしらの値で埋まっている列を一つ
- 冒頭1000行以上がNAでその後に日付型の値が入っている列を一つ
- 冒頭1000行以上がNAでその後に数値型の値が入っている列を一つ

後はreadrとdata.tableパッケージを読み込んておいてください。

```
library(readr)
library(data.table)
```

# readr
読み込むと下記のような表示が出ます

```
test_readr <- read_csv("dataset_compare_readr_data.table.csv")

# x,y,zがそれぞれ上述の1つ目、2つ目、3つ目の列と対応しています

Parsed with column specification:
cols(
  x = col_double(),
  y = col_logical(),
  z = col_logical()
)
Warning: 3001 parsing failures.
 row col           expected     actual                                   file
1500   y 1/0/T/F/TRUE/FALSE 2019-10-01 'dataset_compare_readr_data.table.csv'
1501   y 1/0/T/F/TRUE/FALSE 2019-10-02 'dataset_compare_readr_data.table.csv'
1501   z 1/0/T/F/TRUE/FALSE 2          'dataset_compare_readr_data.table.csv'
1502   y 1/0/T/F/TRUE/FALSE 2019-10-03 'dataset_compare_readr_data.table.csv'
1502   z 1/0/T/F/TRUE/FALSE 3          'dataset_compare_readr_data.table.csv'
.... ... .................. .......... ......................................
See problems(...) for more details.
```

__不吉ですね。__

yとzがlogi型で読み込まれてるし、Warningとか出てるし。

summaryで様子を見てみましょう。

```
summary(test_readr)

       x        y              z          
 Min.   :1   Mode:logical   Mode:logical  
 1st Qu.:1   NA's:3000      TRUE:1        
 Median :1                  NA's:2999     
 Mean   :1                                
 3rd Qu.:1                                
 Max.   :1         
```
__おどろ木ももの木さんしょの木__
yには日付型の値が入っていたのに、それは反映されていませんし、zには途中から1~1501の値が連番で入っていたのに、TRUEが1個だけになってます。

調べてみると、readrは最初の1000行で型を予測するらしく、その影響で、最初の1000行がたまたまNAだったらlogi型になってしまうものと思われます。
https://qiita.com/uri/items/9fdc5d831ff69a2d9128
https://readr.tidyverse.org/reference/read_delim.html


また、zで1個だけTRUEになってるのは、数値として入れていた"1"が反映されているようです。
データを読み込む時は、読み込んだ段階で型やデータ数などを簡単にチェックするとよいですね。

__ちなみに私はこのトラップに気づくのに3時間を浪費しました__

# data.table
データを読み込むと、特にエラー表記は出ません

```
test_data.table <- fread("dataset_compare_readr_data.table.csv")
```

__不吉じゃない！__

しかし、summaryをしてみると。。。

```
summary(test_data.table)

      x          y                   z       
 Min.   :1   Length:3000        Min.   :1     
 1st Qu.:1   Class :character   1st Qu.:1     
 Median :1   Mode  :character   Median :1     
 Mean   :1                      Mean   :1     
 3rd Qu.:1                      3rd Qu.:1     
 Max.   :1                      Max.   :1     
                                NA's   :1499  
```

__むーむーむっ！__

yとzはどちらも冒頭がNAのはずなのに、yはchr型でzはint型になっています。

こちらはイマイチ理由が分からなかったのですが、NA以外で入っている値が日付型なのか数値型なのかで変わっているようです。
これに気づかず、`is.na(y) == TRUE` とかやるとデータが0件になって *_"Why R language!"_* ってなります
データを読み込む時は、読み込んだ段階で型やデータ数などを簡単にチェックするとよいですね(2度目)。

__ちなみに私はこのトラップに気づくのに30分を浪費しました__


# まとめ
挙動は上で記載したとおりです。
実務で扱う際は、readrの挙動ではそもそものデータの値が変わってしまうので、__その点ではdata.tableを利用するほうが安全かもしれません__。
readrも、読み込む際に型指定をできるそうですが、個人的には一旦読み込んでから型変換などしたほうが楽なので、今後はdata.tableも併用していこうかと思います。

以上です。


# 追記
readrでの読み込み時の型指定について、[konandoiruasa](https://qiita.com/konandoiruasa)さんにコメントいただきました。ありがとうございます！
readrのGithubページ([こちら](https://github.com/tidyverse/readr))には下記の通り記載されています。



```
In many cases, these functions will just work: you supply the path to a file and you get a tibble back. The following example loads a sample file bundled with readr:

mtcars <- read_csv(readr_example("mtcars.csv"))
#> Parsed with column specification:
#> cols(
#>   mpg = col_double(),
#>   cyl = col_double(),
#>   disp = col_double(),
#>   hp = col_double(),
#>   drat = col_double(),
#>   wt = col_double(),
#>   qsec = col_double(),
#>   vs = col_double(),
#>   am = col_double(),
#>   gear = col_double(),
#>   carb = col_double()
#> )
Note that readr prints the column specification. This is useful because it allows you to check that the columns have been read in as you expect, and if they haven’t, you can easily copy and paste into a new call:

mtcars <- read_csv(readr_example("mtcars.csv"), col_types = 
  cols(
    mpg = col_double(),
    cyl = col_integer(),
    disp = col_double(),
    hp = col_integer(),
    drat = col_double(),
    vs = col_integer(),
    wt = col_double(),
    qsec = col_double(),
    am = col_integer(),
    gear = col_integer(),
    carb = col_integer()
  )
)
```

`Parsed with column specification:`に読み込んだ列の型を記載しているから、自分で型指定をしたい時はそれをコピーして、`read_csv`の`col_types`に入れた上で、指定したい部分のみ書き換えれば簡単に型指定ができるようです。

試してみましょう

```
test_readr <- read_csv("dataset_compare_readr_data.table.csv",
                       col_types = cols(
                         x = col_double()
                         ,y = col_date()
                         ,z = col_integer() #col_double()でも良かったですが、なんとなくこちらにしました
                       ))

summary(test_readr)
       x           y                    z       
 Min.   :1   Min.   :2019-10-01   Min.   :   1  
 1st Qu.:1   1st Qu.:2020-10-10   1st Qu.: 376  
 Median :1   Median :2021-10-20   Median : 751  
 Mean   :1   Mean   :2021-10-20   Mean   : 751  
 3rd Qu.:1   3rd Qu.:2022-10-30   3rd Qu.:1126  
 Max.   :1   Max.   :2023-11-09   Max.   :1501  
             NA's   :1499         NA's   :1499 
```
__できてますね。__
__これはイージー__

ちなみに一部の列のみの指定もできるようです。

```
test_readr <- read_csv("dataset_compare_readr_data.table.csv",
                       col_types = cols(
                         x = col_double()
                         ,y = col_date()
                         #,z = col_integer()
                       ))
Warning: 1500 parsing failures.
 row col           expected actual                                   file
1501   z 1/0/T/F/TRUE/FALSE      2 'dataset_compare_readr_data.table.csv'
1502   z 1/0/T/F/TRUE/FALSE      3 'dataset_compare_readr_data.table.csv'
1503   z 1/0/T/F/TRUE/FALSE      4 'dataset_compare_readr_data.table.csv'
1504   z 1/0/T/F/TRUE/FALSE      5 'dataset_compare_readr_data.table.csv'
1505   z 1/0/T/F/TRUE/FALSE      6 'dataset_compare_readr_data.table.csv'
.... ... .................. ...... ......................................
See problems(...) for more details.

summary(test_readr)
       x           y                 z          
 Min.   :1   Min.   :2019-10-01   Mode:logical  
 1st Qu.:1   1st Qu.:2020-10-10   TRUE:1        
 Median :1   Median :2021-10-20   NA's:2999     
 Mean   :1   Mean   :2021-10-20                 
 3rd Qu.:1   3rd Qu.:2022-10-30                 
 Max.   :1   Max.   :2023-11-09                 
             NA's   :1499  
```

こちらだと`z`のみlogi型で読み込まれる挙動になっています。

`col_types`での型指定方法については下記記事で細かく触れられているので、参考にしてみるとよいかもしれません。
https://heavywatal.github.io/rstats/readr.html
https://qiita.com/matsuou1/items/78fedad2766ce4bbf861
https://qiita.com/uri/items/9fdc5d831ff69a2d9128