データハンドリング

SappoRo.R #7

前田和寛(@kazutan)

2016/10/29

今回の内容

Rのポイントはデータハンドリング

  • 変数(列)の選択
  • 変数(列)を新規で作成、更新
  • レコード(行)を条件により抽出、並べ替え
  • データを集計(要約)
  • データをグループ化
  • 複数のデータセットを結合
  • wide型(横型)とlong型(縦型)を変換
  • 列の結合と分離

みなさんどうやってます?

具体例_1

具体例_1: ほしいもの

このような結果がほしいとします:

Species Sepal.Length Sepal.Width
versicolor 5.936 2.770
virginica 6.588 2.974
setosa 5.006 3.428

具体例_1: コード

iris %>% 
  dplyr::select(starts_with("sepal"),Species) %>% 
  dplyr::group_by(Species) %>% 
  dplyr::summarize_each(funs(mean)) %>% 
  dplyr::arrange(Sepal.Width) 
## # A tibble: 3 × 3
##      Species Sepal.Length Sepal.Width
##       <fctr>        <dbl>       <dbl>
## 1 versicolor        5.936       2.770
## 2  virginica        6.588       2.974
## 3     setosa        5.006       3.428

具体例_1: 内容

iris %>% 
  dplyr::select(starts_with("sepal"),Species) %>% 
  dplyr::group_by(Species) %>% 
  dplyr::summarize_each(funs(mean)) %>% 
  dplyr::arrange(Sepal.Width) 
irisデータを…
  "sepal"で始まる変数と"Species"を取り出して…
  "Species"の値でグループ化して…
  各列に対して"平均"で要約して…
  "Sepal.Width"の昇順でソート

具体例_2

具体例_2: ほしいもの

このような結果がほしいとします:

var_name value
Sepal.Length 5.1
Sepal.Length 4.9
Sepal.Length 4.7
Sepal.Length 4.6
Sepal.Length 5.0
Sepal.Length 5.4

具体例_2: コード

iris %>% 
  dplyr::select(-Species) %>% 
  tidyr::gather(key = var_name, value = value) %>% 
  head
##       var_name value
## 1 Sepal.Length   5.1
## 2 Sepal.Length   4.9
## 3 Sepal.Length   4.7
## 4 Sepal.Length   4.6
## 5 Sepal.Length   5.0
## 6 Sepal.Length   5.4

具体例_2: 内容

iris %>% 
  dplyr::select(-Species) %>% 
  tidyr::gather(key = var_name, value = value) %>% 
  head
irisデータを…
  "Species"以外の変数を取り出して…
  4つの変数を縦型のデータに整形して…
  上6つのデータを表示

補足: %>% について

%>% はpipe演算子

  • “%>%”は、前のコマンドでのoutputを、次のコマンドの第一引数に放り込む
  • dplyrを読み込むと利用可能
# 使わない書き方
df <- subset(iris, Species == "setosa")
df2 <- subset(df, select = c(Sepal.Length, Sepal.Width))
head(df2, 2)
##   Sepal.Length Sepal.Width
## 1          5.1         3.5
## 2          4.9         3.0
# 使った書き方
(df <- subset(iris, Species == "setosa") %>% 
  subset(select = c(Sepal.Length, Sepal.Width)) %>% 
  head(2))
##   Sepal.Length Sepal.Width
## 1          5.1         3.5
## 2          4.9         3.0

ポイント

メリット

  • 流れるようにコードがかける
  • 引き渡しのためにわざわざオブジェクトを作る必要がない
    混乱やメモリを抑制できる
  • RStudioにショートカットがある(Ctrl/Cmd + Shift + M)
  • dplyrやtidyrとの相性が非常にいい

デメリット

  • 第一引数に投入されるため、使用できる関数に制限あり
    明示的に放り込む方法はあるが割愛
  • (人によっては)掴みづらい

前準備

パッケージインストール

必要なパッケージをインストール&読み込み

install.packages("dplyr")
install.packages("tidyr")
library(dplyr)
library(tidyr)
  • CRANにあります
  • 今回は以下のバージョンで説明します
  • dplyr 0.5.0
  • tidyr 0.6.0

sessioninfo

sessionInfo()
## R version 3.3.1 (2016-06-21)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 14.04.4 LTS
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] ggplot2_2.1.0.9000 knitr_1.14         tidyr_0.6.0       
## [4] dplyr_0.5.0        rmarkdown_1.0.9016 stringr_1.1.0     
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_0.12.7      magrittr_1.5     munsell_0.4.3    colorspace_1.2-6
##  [5] R6_2.2.0         highr_0.6        plyr_1.8.4       tools_3.3.1     
##  [9] revealjs_0.7     grid_3.3.1       gtable_0.2.0     DBI_0.5-1       
## [13] htmltools_0.3.5  yaml_2.1.13      lazyeval_0.2.0   assertthat_0.1  
## [17] digest_0.6.10    tibble_1.2       formatR_1.4      htmlwidgets_0.7 
## [21] rsconnect_0.4.4  evaluate_0.10    labeling_0.3     stringi_1.1.2   
## [25] scales_0.4.0     jsonlite_1.1

変数(列)の選択

dplyr::select

df <- dplyr::select(iris, c(Sepal.Width,Species))
head(df, 3)
##   Sepal.Width Species
## 1         3.5  setosa
## 2         3.0  setosa
## 3         3.2  setosa
  • dplyr::select(データフレーム, 列の指定, …)
  • 列の指定方法は、基本subsetと同様
  • 列の指定については、さらに特別な関数が利用可能

列選択用関数: starts_with()

df <- dplyr::select(iris, starts_with("sepal"))
head(df, 3)
##   Sepal.Length Sepal.Width
## 1          5.1         3.5
## 2          4.9         3.0
## 3          4.7         3.2
  • start_with(x, ignore.case = TRUE)
  • 前方一致で変数を検索してもってくる
  • なおignore.case = FALSEにすると、大文字と小文字を区別してくれる

列選択用関数: ends_with()

df <- dplyr::select(iris, ends_with("width"))
head(df, 3)
##   Sepal.Width Petal.Width
## 1         3.5         0.2
## 2         3.0         0.2
## 3         3.2         0.2
  • ends_with(x, ignore.case = TRUE)
  • 後方一致で変数を検索して持ってくる

列選択用関数: contains()

df <- dplyr::select(iris, contains("pe"))
head(df, 3)
##   Petal.Length Petal.Width Species
## 1          1.4         0.2  setosa
## 2          1.4         0.2  setosa
## 3          1.3         0.2  setosa
  • contains(x, ignore.case = TRUE)
  • 部分一致で変数を検索して持ってくる

列選択用関数: matches()

df <- dplyr::select(iris, matches(".t."))
head(df, 3)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width
## 1          5.1         3.5          1.4         0.2
## 2          4.9         3.0          1.4         0.2
## 3          4.7         3.2          1.3         0.2
  • matches(x, ignore.case = TRUE)
  • 正規表現で指定してマッチする変数を持ってくる

列選択用関数: num_range()

df <- as.data.frame(matrix(1:30, nrow = 3, ncol = 10))
colnames(df) <- c(paste0("beer", 1:5), paste0("sake0", 1:5))
ls(df)
##  [1] "beer1"  "beer2"  "beer3"  "beer4"  "beer5"  "sake01" "sake02"
##  [8] "sake03" "sake04" "sake05"
dplyr::select(df, num_range("beer", 1:3, 1))
##   beer1 beer2 beer3
## 1     1     4     7
## 2     2     5     8
## 3     3     6     9
dplyr::select(df, num_range("sake", 2:4, 2))
##   sake02 sake03 sake04
## 1     19     22     25
## 2     20     23     26
## 3     21     24     27
  • num_range(“文字列”, 対象の数値, 桁数)
  • “beer1からbeer3まで”といった指定方法。桁数があるのがGood!

列選択用関数: one_of()

vname <- c("Petal.Length", "Sepal.Width")
df <- dplyr::select(iris, one_of(vname))
head(df, 3)
##   Petal.Length Sepal.Width
## 1          1.4         3.5
## 2          1.4         3.0
## 3          1.3         3.2
  • 変数名を文字列ベクトルでまとめて渡す時に使用。
  • これで挟み込まずにそのままベクトルを指定してもエラーが出る

列選択用関数: everything()

df <- as.data.frame(matrix(1:15, nrow = 3, ncol = 5))
colnames(df) <- c("touyama", "hanazawa", "komatsu", "asumi", "sakura")
dplyr::select(df, everything())
##   touyama hanazawa komatsu asumi sakura
## 1       1        4       7    10     13
## 2       2        5       8    11     14
## 3       3        6       9    12     15
dplyr::select(df, hanazawa, touyama, everything())
##   hanazawa touyama komatsu asumi sakura
## 1        4       1       7    10     13
## 2        5       2       8    11     14
## 3        6       3       9    12     15
  • 全部持ってくる

こういう使い方も。

head(dplyr::select(iris, kosaki=starts_with("Petal"), everything()))
##   kosaki1 kosaki2 Sepal.Length Sepal.Width Species
## 1     1.4     0.2          5.1         3.5  setosa
## 2     1.4     0.2          4.9         3.0  setosa
## 3     1.3     0.2          4.7         3.2  setosa
## 4     1.5     0.2          4.6         3.1  setosa
## 5     1.4     0.2          5.0         3.6  setosa
## 6     1.7     0.4          5.4         3.9  setosa

変数(列)を新規で作成、更新

dplyr::mutate()

df <- dplyr::mutate(iris, beer=Sepal.Width*2)
head(df, 2)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species beer
## 1          5.1         3.5          1.4         0.2  setosa    7
## 2          4.9         3.0          1.4         0.2  setosa    6
df <- dplyr::mutate(df, beer=Sepal.Width*3)
head(df, 2)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species beer
## 1          5.1         3.5          1.4         0.2  setosa 10.5
## 2          4.9         3.0          1.4         0.2  setosa  9.0
  • dplyr::mutate(データフレーム、変数名=処理内容)
  • データフレームに新たに列を追加します
  • 変数名に既存の変数を指定すると上書きします

dplyr::mutate_each()

df <- dplyr::mutate_each(iris, funs(. * 2), -Species)
kable(head(df,2))
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
10.2 7 2.8 0.4 setosa
9.8 6 2.8 0.4 setosa
  • dplyr::mutate_each(データフレーム、関数, …)
  • 複数の列に、関数の内容を一気にあてます
  • …のところで変数を指定します
    • select()と同様に指定可能

(参考)その他のmutate関連関数

  • mutate_if(), mutate_at(), mutate_all()が追加されています
  • dplyrを最新にして,ヘルプで確認してみてください

レコード(行)を条件により抽出、並べ替え

dplyr::filter()

df <- dplyr::filter(iris, Species=='virginica')
head(df, 3)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
## 1          6.3         3.3          6.0         2.5 virginica
## 2          5.8         2.7          5.1         1.9 virginica
## 3          7.1         3.0          5.9         2.1 virginica
  • dplyr::filter(データフレーム, 条件式)
  • 使い方はsubset()と同様

dplyr::arrange()

df <- dplyr::arrange(iris, Sepal.Length)
head(df, 3)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          4.3         3.0          1.1         0.1  setosa
## 2          4.4         2.9          1.4         0.2  setosa
## 3          4.4         3.0          1.3         0.2  setosa
df <- dplyr::arrange(df, desc(Sepal.Length))
head(df, 3)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
## 1          7.9         3.8          6.4         2.0 virginica
## 2          7.7         3.8          6.7         2.2 virginica
## 3          7.7         2.6          6.9         2.3 virginica
  • dplyr::arrange(データフレーム, キー列)で昇順
  • dplyr::arrange(データフレーム, desc(キー列))で降順

データを集計(要約)

dplyr::summarize()

df <- dplyr::summarize(iris, varmean=mean(Sepal.Length))
df
##    varmean
## 1 5.843333
  • dplyr::summarize(データフレーム, 新変数名=関数(処理対象の変数名))
  • 列の集計を算出
  • 使える関数はsum,sd,meanなど

dplyr::summarize_each()

df <- dplyr::summarize_each(iris, funs(min, mean, max, sd), ends_with("Length"))
df
##   Sepal.Length_min Petal.Length_min Sepal.Length_mean Petal.Length_mean
## 1              4.3                1          5.843333             3.758
##   Sepal.Length_max Petal.Length_max Sepal.Length_sd Petal.Length_sd
## 1              7.9              6.9       0.8280661        1.765298
  • dplyr::summarize_each(データフレーム, funs(関数), 変数)
  • 変数で指定した各変数に対して、関数で集計した結果を返します
  • 変数の指定には、dplyr::select()での方法がそのまま使えます

(参考)その他のsummarize関連関数

  • summarize_if(), summarize_at(), summarize_all()が追加されています
  • dplyrを最新にして,ヘルプで確認してみてください

データをグループ化

dplyr::group_by()

df <- dplyr::group_by(iris, Species) %>% 
  dplyr::summarize(cmean=mean(Sepal.Length))
df
## # A tibble: 3 × 2
##      Species cmean
##       <fctr> <dbl>
## 1     setosa 5.006
## 2 versicolor 5.936
## 3  virginica 6.588
  • dplyr::group_by(データフレーム、グループ化に使う変数)
  • これを当てると、データがグループ化されます
  • この後にグループごとにやりたい処理を実行すると、このようになります

複数のデータセットを結合

データセットの準備

  • 例として、以下のデータフレームを使います
a <- data.frame(x1=c("A","B","C"),x2=1:3)
b <- data.frame(x1=c("A","B","D"),x3=c(TRUE, FALSE, TRUE))
y <- data.frame(x1=c("A","B","C"),x2=1:3)
z <- data.frame(x1=c("B","C","D"),x2=2:4)
  • aとbを結合する場合、共通する変数はx1
    x1をキーにして、mergeします
  • yとzを結合する場合、変数は共通です 行を追加してまとめる、あるいは列を追加してまとめます

dplyr::full_join()

df <- dplyr::full_join(a,b,by="x1")
## Warning in full_join_impl(x, y, by$x, by$y, suffix$x, suffix$y): joining
## factors with different levels, coercing to character vector
kable(df)
x1 x2 x3
A 1 TRUE
B 2 FALSE
C 3 NA
D NA TRUE
  • dplyr::full_join(データフレーム1, データフレーム2, by=キー変数)
  • 全ての行と列を結合します
  • 該当するものがない場合、NAがはいります

dplyr::inner_join()

df <- dplyr::inner_join(a,b,by="x1")
## Warning in inner_join_impl(x, y, by$x, by$y, suffix$x, suffix$y): joining
## factors with different levels, coercing to character vector
kable(df)
x1 x2 x3
A 1 TRUE
B 2 FALSE
  • dplyr::full_join(データフレーム1, データフレーム2, by=キー変数)
  • 両方のデータフレームに存在する行のみを残して結合します

dplyr::bind_cols()

df <- dplyr::bind_cols(y, z)
kable(df)
x1 x2 x1 x2
A 1 B 2
B 2 C 3
C 3 D 4
  • 左のデータフレームに右のデータフレームの列を追加します
  • 行数が一致してないとエラーになります

dplyr::bind_rows()

df <- dplyr::bind_rows(y, z)
## Warning in bind_rows_(x, .id): Unequal factor levels: coercing to character
kable(df)
x1 x2
A 1
B 2
C 3
B 2
C 3
D 4
  • 左のデータフレームに右のデータフレームの行を追加します
  • 列数が一致してないとエラーになります

dplyr::bind_row(…, .id=)

df <- dplyr::bind_rows(y, z, .id = "df_id")
## Warning in bind_rows_(x, .id): Unequal factor levels: coercing to character
kable(df)
df_id x1 x2
1 A 1
1 B 2
1 C 3
2 B 2
2 C 3
2 D 4
  • 引数として.id=**を指定すると、テーブルidを変数として作成してくれます
  • これ、めっちゃ便利です

wide型(横型)とlong型(縦型)を変換

tidyr::gather()

df <- tidyr::gather(data=iris, key = keykey, value = valuevalue, -Species)
kable(head(df, 4))
Species keykey valuevalue
setosa Sepal.Length 5.1
setosa Sepal.Length 4.9
setosa Sepal.Length 4.7
setosa Sepal.Length 4.6
  • data: 使用するデータフレーム
  • key: まとめた時に、「この行の値はどの変数に入ってたものか」を示す変数。
  • value: まとめた変数の値。
  • …: まとめる変数を指定します。dplyr::select()のテクニックがそのまま使えます

tidyr::spread()

# irisにID列を追加して、gatherでまとめている
df <- dplyr::mutate(iris, id=rownames(iris)) %>% 
  tidyr::gather(key = keykey, value = valuevalue, contains("l."))
knitr::kable(head(df,2))
Species id keykey valuevalue
setosa 1 Sepal.Length 5.1
setosa 2 Sepal.Length 4.9
# ひっつけたけどspreadでバラします
df_2 <- tidyr::spread(df, key = keykey, value = valuevalue)
knitr::kable(head(df_2,2))
Species id Petal.Length Petal.Width Sepal.Length Sepal.Width
setosa 1 1.4 0.2 5.1 3.5
setosa 10 1.5 0.1 4.9 3.1
  • gatherと逆の動きをします
  • ただし、spreadを実行した後に、主キーとなるような変数が存在する必要があります
  • データベース的なものがあるんだと思います

列の結合・分離

tidyr::unite

df <- tidyr::unite(data = iris, col = colll, starts_with("Sepal"), sep = "-")
knitr::kable(head(df))
colll Petal.Length Petal.Width Species
5.1-3.5 1.4 0.2 setosa
4.9-3 1.4 0.2 setosa
4.7-3.2 1.3 0.2 setosa
4.6-3.1 1.5 0.2 setosa
5-3.6 1.4 0.2 setosa
5.4-3.9 1.7 0.4 setosa
  • 複数の列をひっつけて一つの列にします

tidyr::separate

# 一旦ひっつけます(uniteのコードと同一)
df <- tidyr::unite(data = iris, col = colll, starts_with("Sepal"), sep = "-")

# separateを実行
df2 <- tidyr::separate(data = df, col = colll, into = c("Sepal.Length","Sepal.Width"), sep = "-")
knitr::kable(head(df2))
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
5.1 3.5 1.4 0.2 setosa
4.9 3 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
5 3.6 1.4 0.2 setosa
5.4 3.9 1.7 0.4 setosa
  • 一つの列を、指定した文字で分割して別々の列にします

参考資料

RStudio謹製チートシート

https://www.rstudio.com/resources/cheatsheets/

  • 「Data Wrangling Cheat Sheet」というのがdplyr&tidyrのチートシート
  • 正直、これ1枚あれば今日の説明は不要です
  • 英語がいやなら、日本語もあります(Data Wranglingのみ, pdf)

https://www.rstudio.com/wp-content/uploads/2015/09/data-wrangling-japanese.pdf

その他のWeb上にある資料

Enjoy!

ビールのみてぇ