この記事はR Advent Calendar 2019の9日目に書いた「tidyverse(再)入門」の内容を補完するものです。その記事の中で「冬休みの宿題」としたbase Rからtidyverseへの書き換えについて書きました。もう春休みですが...
TL;DR
この記事で扱わないこと
- ggplot21
- baseのplot関数に比べて綺麗なグラフが描ける。
- 機械学習(モデルの構築と検証)2
- tidymodelsという、tidyverseの姉妹パッケージがある。
準備
環境
OS: macOS Mojave 10.14.6
R: 3.6.2 "Dark and Stormy Night"
tidyverse: 1.3.0
ライブラリを読み込みます(reshape2
は後で使用します)。
library(reshape2)
library(tidyverse)
─ Attaching packages ───────────────────── tidyverse 1.3.0 ─
✓ ggplot2 3.2.1 ✓ purrr 0.3.3
✓ tibble 2.1.3 ✓ dplyr 0.8.3
✓ tidyr 1.0.2 ✓ stringr 1.4.0
✓ readr 1.3.1 ✓ forcats 0.4.0
─ Conflicts ────────────────────── tidyverse_conflicts() ─
x dplyr::filter() masks stats::filter()
x dplyr::lag() masks stats::lag()
データ
Rに標準装備されているデータセットの1つinfert
を例として使います。自然流産もしくは誘発流産(人工妊娠中絶)の後の妊娠有無に関する症例対照研究のデータセット3です。
dat <- datasets::infert
head(dat)
summary(dat)
education age parity induced case spontaneous stratum pooled.stratum
1 0-5yrs 26 6 1 1 2 1 3
2 0-5yrs 42 1 1 1 0 2 1
3 0-5yrs 39 6 2 1 0 3 4
4 0-5yrs 34 4 2 1 0 4 2
5 6-11yrs 35 3 1 1 1 5 32
6 6-11yrs 36 4 2 1 1 6 36
education age parity induced case
0-5yrs : 12 Min. :21.00 Min. :1.000 Min. :0.0000 Min. :0.0000
6-11yrs:120 1st Qu.:28.00 1st Qu.:1.000 1st Qu.:0.0000 1st Qu.:0.0000
12+ yrs:116 Median :31.00 Median :2.000 Median :0.0000 Median :0.0000
Mean :31.50 Mean :2.093 Mean :0.5726 Mean :0.3347
3rd Qu.:35.25 3rd Qu.:3.000 3rd Qu.:1.0000 3rd Qu.:1.0000
Max. :44.00 Max. :6.000 Max. :2.0000 Max. :1.0000
spontaneous stratum pooled.stratum
Min. :0.0000 Min. : 1.00 Min. : 1.00
1st Qu.:0.0000 1st Qu.:21.00 1st Qu.:19.00
Median :0.0000 Median :42.00 Median :36.00
Mean :0.5766 Mean :41.87 Mean :33.58
3rd Qu.:1.0000 3rd Qu.:62.25 3rd Qu.:48.25
Max. :2.0000 Max. :83.00 Max. :63.00
行、列の抽出
条件による行の抽出
-
[
->filter
baseでは[
演算子、tidyverseではfilter
関数を使います。いずれも、条件を演算子ないし関数の中に記述します。
例として、parity
(出産回数)が3以上である行(サブセット)を取り出してみましょう。
high_parity_base <- dat[dat$parity >= 3, ]
high_parity_tidy <- dat %>% filter(parity >= 3)
複数条件でも同様です。先程の出産回数の条件に、誘発流産induced
の経験がないという条件を加えます。
high_parity_not_induced_base <- dat[dat$parity >= 3 & dat$induced == 0, ]
high_parity_not_induced_tidy <- dat %>% filter(parity >= 3 & induced == 0)
名前による列の選択
-
[
,$
->select
baseでは[
演算子または$
演算子、tidyverseではselect
関数を使います。いずれも取り出したい列の名前を指定します。
例として、教育レベルeducation
を取り出してみましょう。
education_base_bracket <- dat['education']
education_base_doller <- dat$education
[
演算子は列をデータフレーム(のサブセット)として取り出しますが、$
演算子はベクトルとして取り出します4。select
関数は[
演算子と同じくデータフレームとして取り出します。
education_tidy <- dat %>% select(education)
ベクトルにしたい場合は、select()
の後にunlist()
を適用します。
education_tidy_vector <- dat %>% select(education) %>% unlist()
複数の場合も同様です。先程のeducation
に加えて自然流産の回数spontaneous
も取り出します。
education_spontaneous_base <- dat[, c('education', 'spontaneous')]
education_spontaneous_tidy <- dat %>% select(education, spontaneous)
位置による列の選択
-
[
->select
baseでは[
演算子、tidyverseではselect
関数を使います。いずれも、名前で選ぶ場合と同じ要領で、列番号を指定します。
例として、1列目と3列目を取り出してみましょう。
cols_1_3_base <- dat[, c(1, 3)]
cols_1_3_tidy <- dat %>% select(1, 3)
加工
列の追加
-
[
,$
->mutate
baseでは[
演算子または$
演算子、tidyverseではmutate
関数を使います。いずれも追加したい列の名前とその値を与えます。
例として、年齢age
が中央値より高いかどうかを表す列age_high
を追加してみましょう(ここでは区別のために接尾辞を付けています)。
dat$age_high_base_1 <- dat$age > median(dat$age)
dat['age_high_base_2'] <- dat$age > median(dat$age)
dat <- dat %>% mutate(age_high_tidy = age > median(age))
mutate
関数は複数の列を一度に追加することができます。
dat <- dat %>% mutate(dummy_tidy_1 = 1,
dummy_tidy_2 = 'あ')
列の削除
-
NULL
の代入 ->select
baseでは列にNULL
を代入することでその列をデータフレームから削除できます5。tidyverseではselect
関数を使い、削除したい列名に-
を付けます。
例として、先ほど追加した列を削除してみましょう6。
dat$age_high_base_1 <- NULL
dat['age_high_base_2'] <- NULL
dat <- dat %>% select(-age_high_tidy)
複数の列をまとめて削除する場合は、c()
を用います。
dat[c('age_high_base_1', 'age_high_base_2')] <- NULL
dat <- dat %>% select(-c(dummy_tidy_1, dummy_tidy_2))
縦横変換
-
reshape2::melt
,data.table::melt
->pivot_longer
-
reshape2::dcast
,data.table::dcast
->pivot_wider
Rでデータフレームの縦横変換をする場合、reshape2のmelt
とdcast
もしくはdata.tableのmelt
とdcast
が使われることが多いと思います。tidyverse
ではpivot_longer
とpivot_wider
を使います7。
reshape2もtidyverseもbaseそのものではありませんが、tidyverse以外で標準的に使用されているライブラリです。ここではreshape2を例としますが、data.tableでも同様です。
準備として、ID列を追加します(相当する列がないため)。
dat <- dat %>% rowid_to_column("id")
まず、id
, stratum
, pooled.stratum
, case
をキーとし、age
, parity
, induced
, spontaneous
を値として、縦持ちに変換してみましょう。
dat_melt_reshape2 <- melt(dat, id.vars = c('id', 'stratum', 'pooled.stratum', 'case'),
measure.vars = c('age', 'parity',
'induced', 'spontaneous'))
pivot_longer
は与えられたデータフレーム全体を処理するので、予め必要な列をselect
で選んでおいて、キーとなる列を-c()
で指定します。
dat_melt_tidy <- dat %>%
select(c(id, stratum, pooled.stratum, case, age, parity, induced, spontaneous)) %>%
pivot_longer(-c(id, stratum, pooled.stratum, case))
次に、先ほど縦持ちに変換したデータフレームを横持ちに変換してみましょう。変換に含めなかったeducation
列を除けば、元の形に戻ります。
dat_dcast_reshape2 <- dcast(dat_melt_reshape2,
id + stratum + pooled.stratum + case ~ variable,
value.var = 'value')
dat_dcast_tidy <- dat_melt_tidy %>%
pivot_wider(id_cols = c(id, stratum, pooled.stratum, case),
names_from = name, values_from = value)
集約
単純集計
単純集計についてはbaseで十分だと思います。tidyverseにおいて集約をする関数はその名もsummarise
8です。
例として、年齢age
の平均値を計算してみましょう。
avg_age_base <- mean(dat$age)
avg_age_tidy <- dat %>% summarise(avg_age = mean(age))
ここで、avg_age_base
はベクトルですが、avg_age_tidy
はデータフレームです9。
Group by
-
aggregate
->group_by
+summarise
baseではaggregate
を使いますが、tidyverseではgroup_by
とsummarise
を組み合わせます。
例として、教育レベルeducation
ごとの年齢の平均値を計算してみましょう。
avg_age_by_education_base <- aggregate(age ~ education, dat, mean)
avg_age_by_education_tidy <- dat %>%
group_by(education) %>% summarise(avg_age = mean(age))
複数の集約関数を適用することはbaseのaggregate
ではできません(ここでは割愛しますが、data.tableのaggregate
を使います)。summarise
では以下のようにします(例として平均値と中央値を算出)。
avg_med_age_by_education_tidy <- dat %>%
group_by(education) %>% summarise(avg_age = mean(age), median_age = median(age))
まとめ
baseとtidyverseの比較を簡単にまとめました。より実務に即したケースや抜け漏れがあれば今後追加していく予定です。
Enjoy!
-
夏休みの宿題とします。 ↩
-
冬休みの宿題とします。 ↩
-
詳細は
?infert
を実行してヘルプを参照。 ↩ -
ただし、1つの列に限ります。 ↩
-
または、残したい列のみを指定して再帰代入します。 ↩
-
存在しない列に
NULL
を代入してもエラーは発生しませんが、select
で存在しない列を指定するとエラーが発生します。 ↩ -
tidyr 1.0.0以上 ↩
-
summarize
もエイリアスです。 ↩ -
tidyverseを使ってベクトルで値を返すには次のようにします(が大袈裟です)。
avg_age_tidy_vector <- dat %>% select(age) %>% unlist() %>% mean()
↩