医療職からデータサイエンティストへ

統計学、機械学習に関する記事をまとめています。

データの読み込みは{readr}にお任せを

f:id:h-wadsworth02:20190224205056j:plain:w500

Rでデータファイルを読み込むとき、{base}パッケージにはread.XXXという関数が備わっています。

私もcsvファイルを読み込むときは、ずっとread.csvを使用していましたが、{readr}を使うともっと高速に、そして処理のしやすい形でデータを読みこむことができます!

これを使いこなせば、データの読み込みでストレスを感じることはなくなるでしょう!

{readr}は何が違うのか?

{readr}には、Rの基本関数と比較して、以下のようなメリットがあります。

  • 読み込みが高速
  • 上手く読み込めなかった場合はレポートしてくれる
  • 勝手に文字列をfactore型に変換しない
  • 良い込まれたデータはtibble型になる
  • 読み込む列や行、欠損値の指定が分かりやすい

などなど、{base}では力不足を感じていたことができるようになります。

練習データファイルの作成

今回は、練習のために色々な型を含んだデータセットを作成しましょう。下記を実行すると、作業フォルダにpractice.csvというファイルが作成されます。(練習が終わったら削除するのをお忘れなく)

今回はcsvの読み込みを主に説明しますが、他の種類でも同じように使えるのでご心配なく!

library(readr)
library(dplyr)

set.seed(123)
ID <- seq(1,10)
day <- ymd("2019/2/1")+days(sample(seq(1,10),replace = T,10))
dweek <- wday(day,label = T)
age <- sample(seq(20,60),replace = T,10)
age[2] <- "30才"
sex <- sample(c("Men","Women"),replace = T,10)
sex[5] <- "NA"
sales <-sample(c(seq(500,10000)),replace = T,10) 
sales[5] <-"NULL"

#データセットの作成と書き出し
data_frame(ID=ID,Day=day,weeks=dweek,Age=age,Sex=sex,Sales = sales) %>% write_csv("practice.csv")

このデータセットは、あるお店の売り上げのデータで、購買者の年齢や性別、購入日、購入単価など10人分が変数として記録されています。 欠損値もちらほらみられます。

ID Day weeks Age Sex Sales
1 2019-02-04 59 Women 9649
2 2019-02-09 30才 Women 9072
3 2019-02-06 47 Women 7062
4 2019-02-10 43 Women 8057
5 2019-02-11 24 NA NULL
6 2019-02-02 56 Women 5039
7 2019-02-07 30 Women 7706
8 2019-02-10 21 Women 2556
9 2019-02-07 33 Men 3523
10 2019-02-06 59 Men 2700

データセットの読み込み

まずは普通に読み込んでみましょう。

> df <- read_csv("practice.csv")
Parsed with column specification:
cols(
  ID = col_integer(),
  Day = col_date(format = ""),
  weeks = col_character(),
  Age = col_character(),
  Sex = col_character(),
  Sales = col_character()
)
# A tibble: 10 x 6
      ID Day        weeks Age   Sex   Sales
   <int> <date>     <chr> <chr> <chr> <chr>
 1     1 2019-02-0459    Women 9649 
 2     2 2019-02-0930才  Women 9072 
 3     3 2019-02-0647    Women 7062 
 4     4 2019-02-1043    Women 8057 
 5     5 2019-02-1124    NA    NULL 
 6     6 2019-02-0256    Women 5039 
 7     7 2019-02-0730    Women 7706 
 8     8 2019-02-1021    Women 2556 
 9     9 2019-02-0733    Men   3523 
10    10 2019-02-0659    Men   2700 
> 

実際にやってみると分かりますが、文字がたくさん出てきて最初は驚きます。

しかし、この読み込んだ後の文字には重要な情報が含まれているのです。

Parsed with column specification:
cols(
  ID = col_integer(),
  Day = col_date(format = ""),
  weeks = col_character(),
  Age = col_character(),
  Sex = col_character(),
  Sales = col_character()
)

この部分ですね。これは各列をどんな型で読み込んだのかが記されています。 {readr}は最初の100行のデータから勝手に型を推測して、変換してくれます。なんと素晴らしい心遣い。

今回は、Dayはcol_date、つまり日付情報、IDはcol_intergerで整数、その他はcol_character、文字で読み込まれています。

ここで、Age、Salesは数値情報として読み込みたいのに文字になっていましました。

データをよくみてみると、"才"と"NULL"が文字として入っているため文字列として推定されてしまったようです。

欠損値の扱い

こんな時にna=を指定することで、欠損値として扱う文字を指定して読み込むことができます。ちなみにデフォルトはna=NAとなっているので、SexのNAは欠損値として処理されています。

#欠損値の数
>sum(is.na(df$Sex))
[1] 1
> sum(is.na(df$Sales))
[1] 0

それでは、欠損として扱う文字列をNAとNULLの二つを指定して読み込んでみましょう。

df <- read_csv("practice.csv",na = c("NA","NULL"))
Parsed with column specification:
cols(
  ID = col_integer(),
  Day = col_date(format = ""),
  weeks = col_character(),
  Age = col_character(),
  Sex = col_character(),
  Sales = col_integer()
)
# A tibble: 10 x 6
      ID Day        weeks Age   Sex   Sales
   <int> <date>     <chr> <chr> <chr> <int>
 1     1 2019-02-0459    Women  9649
 2     2 2019-02-0930才  Women  9072
 3     3 2019-02-0647    Women  7062
 4     4 2019-02-1043    Women  8057
 5     5 2019-02-1124    NA       NA
 6     6 2019-02-0256    Women  5039
 7     7 2019-02-0730    Women  7706
 8     8 2019-02-1021    Women  2556
 9     9 2019-02-0733    Men    3523
10    10 2019-02-0659    Men    2700

Salesを見てみるとNULLが欠損として扱われたため、しっかりと整数として読み込まれています。

型を指定して読み込む

さて、今度は自分で型を指定してデータを読み込んでみましょう。指定できる型は、

  • [c] col_character(): 文字列
  • [i] col_integer(): 整数
  • [d] col_double(): 実数
  • [D] col_date(format=''): 日付
  • [t] col_time(format=''): 時間
  • [T] col_datetime(format=''): 日付
  • [n] col_number(): 文字が含まれていても数字を返す
  • [?] col_guess(): 推測
  • [_] col_skip(): 列を読まない

などがあります。IDは文字で、Ageは才が含まれているのでnumberで指定してみます。col_type=cols(列名=型)と指定します。

> read_csv("practice.csv",na = c("NA","NULL"),col_types = cols(Age = col_number(),ID=col_character()))
# A tibble: 10 x 6
   ID    Day        weeks   Age Sex   Sales
   <chr> <date>     <chr> <dbl> <chr> <int>
 1 1     2019-02-0459 Women  9649
 2 2     2019-02-0930 Women  9072
 3 3     2019-02-0647 Women  7062
 4 4     2019-02-1043 Women  8057
 5 5     2019-02-1124 NA       NA
 6 6     2019-02-0256 Women  5039
 7 7     2019-02-0730 Women  7706
 8 8     2019-02-1021 Women  2556
 9 9     2019-02-0733 Men    3523
10 10    2019-02-0659 Men    2700

上手く読み込めました。型を指定するとParsed with column specification:~は出てこないようですね。

さらにcols(.defalt = 型)を指定すると、指定した列以外の列はその型で読み込まれます。普段は.default = col_guess()となっているので、勝手に推測されていたたんですね。

また、さっきの型リストを見て頂くとかっこの中に文字があるのがわかると思います。

実はこの一文字で指定することもできるんです。こっちのが楽ですねー

#col_number()を一文字”n”で指定する
#.defaultに文字を指定すると、Age以外は文字として読み込まれる
read_csv("practice.csv",na = c("NA","NULL"),col_types = cols(Age = "n",.default = "c"))
# A tibble: 10 x 6
   ID    Day        weeks   Age Sex   Sales
   <chr> <chr>      <chr> <dbl> <chr> <chr>
 1 1     2019-02-0459 Women 9649 
 2 2     2019-02-0930 Women 9072 
 3 3     2019-02-0647 Women 7062 
 4 4     2019-02-1043 Women 8057 
 5 5     2019-02-1124 NA    NA   
 6 6     2019-02-0256 Women 5039 
 7 7     2019-02-0730 Women 7706 
 8 8     2019-02-1021 Women 2556 
 9 9     2019-02-0733 Men   3523 
10 10    2019-02-0659 Men   2700

ccdddnなどと文字を並べるだけで、列名を指定しなくても型を指定できたりもします。

#列を右から文字、日付、スキップ、整数、文字、整数として読み込む
>read_csv("practice.csv",na = c("NA","NULL"),col_types = "cD_ici")
 警告:  1 parsing failure.
row # A tibble: 1 x 5 col     row col   expected               actual file           expected   <int> <chr> <chr>                  <chr>  <chr>          actual 1     2 Age   no trailing characters 才     'practice.csv' file # A tibble: 1 x 5

# A tibble: 10 x 5
   ID    Day          Age Sex   Sales
   <chr> <date>     <int> <chr> <int>
 1 1     2019-02-04    59 Women  9649
 2 2     2019-02-09    NA Women  9072
 3 3     2019-02-06    47 Women  7062
 4 4     2019-02-10    43 Women  8057
 5 5     2019-02-11    24 NA       NA
 6 6     2019-02-02    56 Women  5039
 7 7     2019-02-07    30 Women  7706
 8 8     2019-02-10    21 Women  2556
 9 9     2019-02-07    33 Men    3523
10 10    2019-02-06    59 Men    2700
 警告メッセージ: 
1:  read_tokens_(data, tokenizer, col_specs, col_names, locale_,: 
  length of NULL cannot be changed
2:  read_tokens_(data, tokenizer, col_specs, col_names, locale_,: 
  length of NULL cannot be changed
3:  rbind(names(probs), probs_f): 
  number of columns of result is not a multiple of vector length (arg 2)

ここで上のほうに警告: 1 parsing failure.が出ました。これは整数で指定したAgeに”才”が入っているため整数に変換でず、欠損値になりましたという意味です。

problemsを実行すると、2行目のAgeが変換できなかったことをご丁寧にレポートしてくれます。

problems(read_csv("practice.csv",na = c("NA","NULL"),col_types = "cD_ici"))
# A tibble: 1 x 5
    row col   expected               actual file          
  <int> <chr> <chr>                  <chr>  <chr>         
1     2 Age   no trailing characters 才     'practice.csv'

優しすぎます!

行や列を飛ばして読み込む

今度は行や列をスキップして読み込んでみましょう。列のスキップは先ほど実行しましたので、指定した列のみを読み込むことにします。

行のスキップ

行のスキップは、skip=行で行います。

#1行目をスキップ
> read_csv("practice.csv",na = c("NA","NULL"),skip=1,col_names = F)

# A tibble: 10 x 6
      X1 X2         X3    X4    X5       X6
   <int> <date>     <chr> <chr> <chr> <int>
 1     1 2019-02-0459    Women  9649
 2     2 2019-02-0930才  Women  9072
 3     3 2019-02-0647    Women  7062
 4     4 2019-02-1043    Women  8057
 5     5 2019-02-1124    NA       NA
 6     6 2019-02-0256    Women  5039
 7     7 2019-02-0730    Women  7706
 8     8 2019-02-1021    Women  2556
 9     9 2019-02-0733    Men    3523
10    10 2019-02-0659    Men    2700

1行目をスキップすると1行目の値を列名として認識してしまうので、col_names=FALSEを指定しました。ちなみにここに列目を任意でつけることもできます。

さらに、n_maxを指定すると任意の行数までを読み込んでくれます。

>read_csv("practice.csv",na = c("NA","NULL"),skip=1,col_names = F,n_max=5)

# A tibble: 5 x 6
     X1 X2         X3    X4    X5       X6
  <int> <date>     <chr> <chr> <chr> <int>
1     1 2019-02-0459    Women  9649
2     2 2019-02-0930才  Women  9072
3     3 2019-02-0647    Women  7062
4     4 2019-02-1043    Women  8057
5     5 2019-02-1124    NA       NA

指定した列のみを読み込む

列のスキップではなく、指定した列のみを読み込む時にはcol_types=cols()の代わりにcol_types=cols_only()を指定します。cols_onlyでも同様に型の指定ができます。

read_csv("practice.csv",na = c("NA","NULL"),col_types = cols_only(ID="c",Day="D",Sales="i"))

# A tibble: 10 x 3
   ID    Day        Sales
   <chr> <date>     <int>
 1 1     2019-02-04  9649
 2 2     2019-02-09  9072
 3 3     2019-02-06  7062
 4 4     2019-02-10  8057
 5 5     2019-02-11    NA
 6 6     2019-02-02  5039
 7 7     2019-02-07  7706
 8 8     2019-02-10  2556
 9 9     2019-02-07  3523
10 10    2019-02-06  2700
 

上手く読み込めたみたいです!

まとめ

今回は高速かつ優しい心遣いまでしてくれる{readr}を紹介しました。

他にもいくつかできることがあるので是非使ってみてください!

www.medi-08-data-06.work

www.medi-08-data-06.work

参考

readr: 高速で柔軟なテーブル読み込み - Heavy Watal

readrでファイル読み込み高速化 - Qiita

11 Data import | R for Data Science

上記サイトの和書版です。dplyrやpurrr、tidyrなどモダンなパッケージをまとめたtidyverseを中心に、データの解析手法そのものよりも、いかに効率よくデータを解析するかが書かれています。Rに慣れ始めた頃に読むのがおすすめです!

Rではじめるデータサイエンス

Rではじめるデータサイエンス