【最下部に追記あり】
こんにちは
今回は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さんにコメントいただきました。ありがとうございます!
readrのGithubページ(こちら)には下記の通り記載されています。
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