LoginSignup
5

More than 3 years have passed since last update.

posted at

続・tidyverse(再)入門

この記事は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() 

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
What you can do with signing up
5