LoginSignup
8
5

More than 3 years have passed since last update.

続・tidyverse(再)入門

Posted at

この記事は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)
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)
head(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
summary(dat)
  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以上である行(サブセット)を取り出してみましょう。

base
high_parity_base <- dat[dat$parity >= 3, ]
tidyverse
high_parity_tidy <- dat %>% filter(parity >= 3)

複数条件でも同様です。先程の出産回数の条件に、誘発流産inducedの経験がないという条件を加えます。

base
high_parity_not_induced_base <- dat[dat$parity >= 3 & dat$induced == 0, ]
tidyverse
high_parity_not_induced_tidy <- dat %>% filter(parity >= 3 & induced == 0)

名前による列の選択

  • [, $ -> select

baseでは[演算子または$演算子、tidyverseではselect関数を使います。いずれも取り出したい列の名前を指定します。

例として、教育レベルeducationを取り出してみましょう。

base
education_base_bracket <- dat['education']
education_base_doller <- dat$education

[演算子は列をデータフレーム(のサブセット)として取り出しますが、$演算子はベクトルとして取り出します4select関数は[演算子と同じくデータフレームとして取り出します。

tidyverse
education_tidy <- dat %>% select(education)

ベクトルにしたい場合は、select()の後にunlist()を適用します。

tidyverse
education_tidy_vector <- dat %>% select(education) %>% unlist()

複数の場合も同様です。先程のeducationに加えて自然流産の回数spontaneousも取り出します。

base
education_spontaneous_base <- dat[, c('education', 'spontaneous')]
tidyverse
education_spontaneous_tidy <- dat %>% select(education, spontaneous)

位置による列の選択

  • [ -> select

baseでは[演算子、tidyverseではselect関数を使います。いずれも、名前で選ぶ場合と同じ要領で、列番号を指定します。

例として、1列目と3列目を取り出してみましょう。

base
cols_1_3_base <- dat[, c(1, 3)]
tidyverse
cols_1_3_tidy <- dat %>% select(1, 3)

加工

列の追加

  • [, $ -> mutate

baseでは[演算子または$演算子、tidyverseではmutate関数を使います。いずれも追加したい列の名前とその値を与えます。

例として、年齢ageが中央値より高いかどうかを表す列age_highを追加してみましょう(ここでは区別のために接尾辞を付けています)。

base
dat$age_high_base_1 <- dat$age > median(dat$age)
dat['age_high_base_2'] <- dat$age > median(dat$age)
tidyverse
dat <- dat %>% mutate(age_high_tidy = age > median(age))

mutate関数は複数の列を一度に追加することができます。

tidyverse
dat <- dat %>% mutate(dummy_tidy_1 = 1,
                      dummy_tidy_2 = 'あ')

列の削除

  • NULLの代入 -> select

baseでは列にNULLを代入することでその列をデータフレームから削除できます5。tidyverseではselect関数を使い、削除したい列名に-を付けます。

例として、先ほど追加した列を削除してみましょう6

base
dat$age_high_base_1 <- NULL
dat['age_high_base_2'] <- NULL
tidyverse
dat <- dat %>% select(-age_high_tidy)

複数の列をまとめて削除する場合は、c()を用います。

base
dat[c('age_high_base_1', 'age_high_base_2')] <- NULL
tidyverse
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のmeltdcastもしくはdata.tableのmeltdcastが使われることが多いと思います。tidyverseではpivot_longerpivot_widerを使います7

reshape2もtidyverseもbaseそのものではありませんが、tidyverse以外で標準的に使用されているライブラリです。ここではreshape2を例としますが、data.tableでも同様です。

準備として、ID列を追加します(相当する列がないため)。

tidyverse
dat <- dat %>% rowid_to_column("id")

まず、id, stratum, pooled.stratum, caseをキーとし、age, parity, induced, spontaneousを値として、縦持ちに変換してみましょう。

reshape2
dat_melt_reshape2 <- melt(dat, id.vars = c('id', 'stratum', 'pooled.stratum', 'case'),
                          measure.vars = c('age', 'parity',
                                           'induced', 'spontaneous'))

pivot_longerは与えられたデータフレーム全体を処理するので、予め必要な列をselectで選んでおいて、キーとなる列を-c()で指定します。

tidyverse
dat_melt_tidy <- dat %>%
  select(c(id, stratum, pooled.stratum, case, age, parity, induced, spontaneous)) %>%
  pivot_longer(-c(id, stratum, pooled.stratum, case))

次に、先ほど縦持ちに変換したデータフレームを横持ちに変換してみましょう。変換に含めなかったeducation列を除けば、元の形に戻ります。

reshape2
dat_dcast_reshape2 <- dcast(dat_melt_reshape2,
                            id + stratum + pooled.stratum + case ~ variable,
                            value.var = 'value')
tidyverse
dat_dcast_tidy <- dat_melt_tidy %>%
  pivot_wider(id_cols = c(id, stratum, pooled.stratum, case),
              names_from = name, values_from = value)

集約

単純集計

単純集計についてはbaseで十分だと思います。tidyverseにおいて集約をする関数はその名もsummarise8です。

例として、年齢ageの平均値を計算してみましょう。

base
avg_age_base <- mean(dat$age)
tidyverse
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_bysummariseを組み合わせます。

例として、教育レベルeducationごとの年齢の平均値を計算してみましょう。

base
avg_age_by_education_base <- aggregate(age ~ education, dat, mean)
tidyverse
avg_age_by_education_tidy <- dat %>%
  group_by(education) %>% summarise(avg_age = mean(age))

複数の集約関数を適用することはbaseのaggregateではできません(ここでは割愛しますが、data.tableのaggregateを使います)。summariseでは以下のようにします(例として平均値と中央値を算出)。

tidyverse
avg_med_age_by_education_tidy <- dat %>%
  group_by(education) %>% summarise(avg_age = mean(age), median_age = median(age))

まとめ

baseとtidyverseの比較を簡単にまとめました。より実務に即したケースや抜け漏れがあれば今後追加していく予定です。

Enjoy!


  1. 夏休みの宿題とします。 

  2. 冬休みの宿題とします。 

  3. 詳細は?infertを実行してヘルプを参照。 

  4. ただし、1つの列に限ります。 

  5. または、残したい列のみを指定して再帰代入します。 

  6. 存在しない列にNULLを代入してもエラーは発生しませんが、selectで存在しない列を指定するとエラーが発生します。 

  7. tidyr 1.0.0以上 

  8. summarizeもエイリアスです。 

  9. tidyverseを使ってベクトルで値を返すには次のようにします(が大袈裟です)。avg_age_tidy_vector <- dat %>% select(age) %>% unlist() %>% mean() 

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5