R / dplyr パッケージによるデータ操作と集計

dplyr

dplyr は表型データの操作に特化した R のパッケージである。dplyr パッケージには、表型データの中からサブセットを抽出したりする関数や抽出したサブセットに対して集計を行う関数などが多数用意されている。dplyr で扱うデータは数百 MB から 2 GB 前後のサイズを想定している。このサイズを超えるような 10〜100 GB 規模なビッグデータなどを取り扱う場合は、data.table パッケージを使用した方が無難。

dplyr の関数の記述方法が非常にシンプルであり、可読性が良い。dplyr を使用することで、研究者はデータの操作や集計に集中することができ、集計等に関係しない無駄なコードを書かなくて済む。

dplyr パッケージを使わないデータ操作例

R では、データフレームの中からある条件を満たすサブセットを抽出し、そのサブセットの平均値を求めたりすることが簡単にできる。ここで、 rice データセットを使って、データの操作方法を示す。rice データセットは 7 列を持ち、それぞれの列が個体番号(replicate)、ブロック番号(block)、根部乾燥重量(root_dry_mass)、地上部乾燥重量(shoot_dry_mass)、系統処理(trt)、処理(fert)、系統(variety)からなる。

d <- read.table('data/rice.txt', header = TRUE, sep = '\t')
head(d)
##   replicate block root_dry_mass shoot_dry_mass trt fert variety
## 1         1     1            56            132 F10  F10      wt
## 2         2     1            66            120 F10  F10      wt
## 3         3     1            40            108 F10  F10      wt
## 4         4     1            43            134 F10  F10      wt
## 5         5     1            55            119 F10  F10      wt
## 6         6     1            66            125 F10  F10      wt

ここで、例えば、各系統(wt 系統および ANU843 系統)に対して、根部乾燥重量を求める場合は、データフレームに対して、次のようの操作を行うことで計算できる。

wt <- d$root_dry_mass[d$variety == 'wt']
ANU843 <- d$root_dry_mass[d$variety == 'ANU843']

print(mean(wt))
## [1] 26.47222

print(mean(ANU843))
## [1] 9.666667

平均値・最大値・最小値などの簡単な値を求めるとき、R の標準関数の一つである aggregate 関数を使うと便利である。

aggregate(d$root_dry_mass, by = list(variety = d$variety), mean)
##   variety         x
## 1  ANU843  9.666667
## 2      wt 26.472222

wt 系統および ANU843 系統の中に、F10 処理、NH4Cl 処理、NH4NO3 処理の 3 つの処理群が含まれている。ここで、wt 系統と ANU843 系統の各処理群それぞれに対して、根部乾燥重量の平均を計算してみる。

wt.F10 <- d$root_dry_mass[d$variety == 'wt' & d$fert == 'F10']
wt.NH4Cl <- d$root_dry_mass[d$variety == 'wt' & d$fert == 'NH4Cl']
wt.NH4NO3 <- d$root_dry_mass[d$variety == 'wt' & d$fert == 'NH4NO3']
ANU843.F10 <- d$root_dry_mass[d$variety == 'ANU843' & d$fert == 'F10']
ANU843.NH4Cl <- d$root_dry_mass[d$variety == 'ANU843' & d$fert == 'NH4Cl']
ANU843.NH4NO3 <- d$root_dry_mass[d$variety == 'ANU843' & d$fert == 'NH4NO3']

m <- c(mean(wt.F10), mean(wt.NH4Cl), mean(wt.NH4NO3),
       mean(ANU843.F10), mean(ANU843.NH4Cl), mean(ANU843.NH4NO3))
print(m)
## [1] 49.500000 12.583333 17.333333  6.000000  9.166667 13.833333

複数条件の組み合わせでも aggregate 関数が使える。例えば次のように by オプションに、2 つの条件を与えればよい。

aggregate(d$root_dry_mass, by = list(variety = d$variety, fert = d$fert), mean)
##   variety   fert         x
## 1  ANU843    F10  6.000000
## 2      wt    F10 49.500000
## 3  ANU843  NH4Cl  9.166667
## 4      wt  NH4Cl 12.583333
## 5  ANU843 NH4NO3 13.833333
## 6      wt NH4NO3 17.333333

dplyr の基本的な使い方

dplyr を使用してデータ操作を扱うとき、データの流れに着目するとわかりやすい。例えば、d というデータに対して A 処理を行なった後に、B 処理を行い、その結果を x に保存したい場合は、d → A → B → x という流れに着目すると、dplyr のルールに従うと次のように記述することができる。d の内容を関数 A に流すときに %>% 演算子を使用する。次に、A 関数で処理した結果を B 関数に流すときに同様に %>% 演算子を使用する。最後に、関数 B の処理結果を x に代入したいから、R の基本文法により %<%- を使用する。

x <- d %>% A %>% B

dplyr の記述ルールを踏まえて、wt 系統の個体数を求めるには、次のように行う。データ d を group_by 関数に流し、ここで variety ごとにグループ分けを行う。グループ分けを行なった後に、各グループをさらに次の summarise 関数に流し、平均を求める処理を行なっている。

d <- read_tsv('data/rice.txt')
head(d)
## # A tibble: 6 x 7
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>
## 1         1     1            56            132 F10   F10   wt     
## 2         2     1            66            120 F10   F10   wt     
## 3         3     1            40            108 F10   F10   wt     
## 4         4     1            43            134 F10   F10   wt     
## 5         5     1            55            119 F10   F10   wt     
## 6         6     1            66            125 F10   F10   wt


variety_ave <- d %>% 
                group_by(variety) %>%
                summarise(mass_ave = mean(root_dry_mass))
head(variety_ave)
## # A tibble: 2 x 2
##   variety mass_ave
##   <chr>      <dbl>
## 1 ANU843      9.67
## 2 wt         26.5

各系統の各処理群に対して平均を求める場合は、aggregate 関数と同様に、group_by のところに条件を 2 つ書けばよい。

variety_ave <- d %>% 
                group_by(variety, fert) %>%
                summarise(mass_ave = mean(root_dry_mass))
head(variety_ave)
## # A tibble: 6 x 3
## # Groups:   variety [2]
##   variety fert   mass_ave
##   <chr>   <chr>     <dbl>
## 1 ANU843  F10        6   
## 2 ANU843  NH4Cl      9.17
## 3 ANU843  NH4NO3    13.8 
## 4 wt      F10       49.5 
## 5 wt      NH4Cl     12.6 
## 6 wt      NH4NO3    17.3

このように、dplyr ではデータの流れに着目して、データの操作や集計を行なっていくことができる。研究者がデータ操作・データ集計に関係のないコードを書く必要がなく、効率的である。

dplyr の基本関数

dplyr パッケージで一般的に知られている関数には、次のようなものがある。

関数動作
select与えられた条件に基づいて、特定の列を抽出する。
filter与えられた条件に基づいて、特定の行を抽出する。
arrange与えられた条件に基づいて、行を並べ替える。
group_by与えられた条件に基づいて、データセット全体をいくつかのグループに分ける。
summarise最大値・最小値・平均値を求めるなどのデータの集計を行う。
mutate既存のデータセットに新しい列を加える。

dplyr::select 関数

select 関数は、与えられた条件に基づいて、特定の列を抽出する関数である。例えば rice データセットから、系統(variety)、処理(fert)、根部乾燥重量(root_dry_mass)、地上部乾燥重量(shoot_dry_mass)の 4 列だけを取り出してサブセットを作成したい場合は、次のようにする。

# d.subset <- d[, c('variety', 'fert', 'root_dry_mass', 'shoot_dry_mass')]
d.subset <- d %>% select(variety, fert, root_dry_mass, shoot_dry_mass)
head(d.subset)
## # A tibble: 6 x 4
##   variety fert  root_dry_mass shoot_dry_mass
##   <chr>   <chr>         <dbl>          <dbl>
## 1 wt      F10              56            132
## 2 wt      F10              66            120
## 3 wt      F10              40            108
## 4 wt      F10              43            134
## 5 wt      F10              55            119
## 6 wt      F10              66            125

select 関数に列名を指定するとき、- をつけると、その列を含まないようなサブセットが作成される。

# d.subset <- d[, -c('replicate', 'block', 'trt')]
d.subset <- d %>% select(-replicate, -block, -trt)
head(d.subset)
## # A tibble: 6 x 4
##   root_dry_mass shoot_dry_mass fert  variety
##           <dbl>          <dbl> <chr> <chr>  
## 1            56            132 F10   wt     
## 2            66            120 F10   wt     
## 3            40            108 F10   wt     
## 4            43            134 F10   wt     
## 5            55            119 F10   wt     
## 6            66            125 F10   wt

select 関数に列名ではなく、条件を与えて列名を列を抽出することもできる。例えば、列名に mass を含む列を抽出する場合は次のようにする。

d.subset <- d %>% select(contains('mass'))
head(d.subset)
## # A tibble: 6 x 2
##   root_dry_mass shoot_dry_mass
##           <dbl>          <dbl>
## 1            56            132
## 2            66            120
## 3            40            108
## 4            43            134
## 5            55            119
## 6            66            125

抽出条件を与えるときに使用した contains 関数の他に、starts_withends_withmatches などの関数も使用できる。

dplyr::filter 関数

filter 関数は与えられた条件に基づいて、特定の行を抽出する関数である。例えば、根部乾燥重量が 50 以上の行を抽出するには、次のように行う。

d.subset <- d %>% filter(root_dry_mass > 50)
head(d.subset)
## # A tibble: 5 x 7
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>  
## 1         1     1            56            132 F10   F10   wt     
## 2         2     1            66            120 F10   F10   wt     
## 3         5     1            55            119 F10   F10   wt     
## 4         6     1            66            125 F10   F10   wt     
## 5         8     2            67            122 F10   F10   wt

条件が複数ある場合は、それらの条件を順に filter 関数に加えればよい。例えば、wt 系統かつ根部乾燥重量が 50 以上の行を抽出するには、次のようにする。

d.subset <- d %>% filter(root_dry_mass >= 50, variety == 'wt')
head(d.subset)
## # A tibble: 5 x 7
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>  
## 1         1     1            56            132 F10   F10   wt     
## 2         2     1            66            120 F10   F10   wt     
## 3         5     1            55            119 F10   F10   wt     
## 4         6     1            66            125 F10   F10   wt     
## 5         8     2            67            122 F10   F10   wt

複数条件の場合は & および | を使って AND 演算および OR 演算を行うことができる。例えば、「wt 系統」、「根部乾燥重量が 50 以上」、「地上部乾燥重量が 120 以上」の 3 つの条件を同時に満たす行を抽出する場合は、次のようにする。 ただし、複数の条件をカンマで区切って与える場合は AND 演算の結果が行われる。

d.subset <- d %>% filter(variety == 'wt', root_dry_mass >= 50, shoot_dry_mass >= 120)
# d.subset <- d %>% filter(variety == 'wt' & root_dry_mass >= 50 & shoot_dry_mass >= 120)
head(d.subset)
## # A tibble: 4 x 7
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>  
## 1         1     1            56            132 F10   F10   wt     
## 2         2     1            66            120 F10   F10   wt     
## 3         6     1            66            125 F10   F10   wt     
## 4         8     2            67            122 F10   F10   wt

wt 系統で、「根部乾燥重量が 50 以上」または「地上部乾燥重量が 120 以上」の条件を満たす行を抽出する場合は、次のようにする。

d.subset <- d %>% filter(variety == 'wt', root_dry_mass >= 50 | shoot_dry_mass >= 120)
# d.subset <- d %>% filter(variety == 'wt' & (root_dry_mass >= 50 | shoot_dry_mass >= 120))
head(d.subset)
## # A tibble: 6 x 7
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>  
## 1         1     1            56            132 F10   F10   wt     
## 2         2     1            66            120 F10   F10   wt     
## 3         4     1            43            134 F10   F10   wt     
## 4         5     1            55            119 F10   F10   wt     
## 5         6     1            66            125 F10   F10   wt     
## 6         8     2            67            122 F10   F10   wt

別の例として、処理群が F10 以外の行を抽出するときは次のように ! を使う。

# d.subset <- d %>% filter(!(fert == 'F10'))
d.subset <- d %>% filter(fert != 'F10')
head(d.subset)
## # A tibble: 6 x 7
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>  
## 1         1     1            12             45 NH4Cl NH4Cl wt     
## 2         2     1            20             60 NH4Cl NH4Cl wt     
## 3         3     1            21             87 NH4Cl NH4Cl wt     
## 4         4     1            15             57 NH4Cl NH4Cl wt     
## 5         5     1             5             26 NH4Cl NH4Cl wt     
## 6         6     1            18             78 NH4Cl NH4Cl wt

dplyr::arrange 関数

arrange 関数は、与えられた条件に基づいて、行を並べ替える。データに欠損値 NA が含まれる場合は、arrange 関数を使って並べ替えるとき、昇順・降順に関わらず、欠損値 NA は常にデータセットの最下部に並べ替えられる。例えば、rice データに対して、根部乾燥重量に基づいて昇順に並べ替えたい場合は、arrange 関数に列名 root_dry_mass を指定すればよい。

d.subset <- d %>% arrange(root_dry_mass)
head(d.subset)
## # A tibble: 6 x 7
##   replicate block root_dry_mass shoot_dry_mass trt           fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr>         <chr> <chr>  
## 1         7     2             1             35 NH4Cl +ANU843 NH4Cl ANU843 
## 2        10     2             3              5 F10 +ANU843   F10   ANU843 
## 3         2     1             4              6 F10 +ANU843   F10   ANU843 
## 4         3     1             4              3 F10 +ANU843   F10   ANU843 
## 5         1     1             4             22 NH4Cl +ANU843 NH4Cl ANU843 
## 6         5     1             5             26 NH4Cl         NH4Cl wt

根部乾燥重量に基づいて降順に並べ替えたい場合は、desc 関数を利用するか、列名に - をつけるかで並べ替えられる。

# d.subset <- d %>% arrange(-root_dry_mass)
d.subset <- d %gt;% arrange(desc(root_dry_mass))
head(d.subset)
## # A tibble: 6 x 7
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>  
## 1         8     2            67            122 F10   F10   wt     
## 2         2     1            66            120 F10   F10   wt     
## 3         6     1            66            125 F10   F10   wt     
## 4         1     1            56            132 F10   F10   wt     
## 5         5     1            55            119 F10   F10   wt     
## 6        11     2            44             37 F10   F10   wt

次に、根部乾燥重量に関して降順で、地上部乾燥重量に関しては昇順で並べるときは、filter 関数と同様に複数の条件を順に与えればよい。filter 関数に複数の条件を代入した場合は、左側の条件が優先される。

d.subset <- d %>% arrange(desc(root_dry_mass), shoot_dry_mass)
head(d.subset)
## # A tibble: 6 x 7
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>  
## 1         8     2            67            122 F10   F10   wt     
## 2         2     1            66            120 F10   F10   wt     
## 3         6     1            66            125 F10   F10   wt     
## 4         1     1            56            132 F10   F10   wt     
## 5         5     1            55            119 F10   F10   wt     
## 6        11     2            44             37 F10   F10   wt

これまでに紹介した select 関数、filter 関数および arrange 関数を組み合わせることで、データセットを柔軟に操作できる。例えば、次のような操作も簡単に行える。

  1. 系統が wt の行を抽出し、
  2. rice データから、処理(fert)、根部乾燥重量(root_dry_mass)、地上部乾燥重量(shoot_dry_mass)の 4 列だけを抽出し
  3. 手順 1-2 で抽出したサブセットを根部乾燥重量に基づいて降順に並べる
wt.subset <- d %>%
              filter(variety == 'wt') %>%
              select(fert, root_dry_mass, shoot_dry_mass) %>%
              arrange(desc(root_dry_mass))
head(wt.subset)
## # A tibble: 6 x 3
##   fert  root_dry_mass shoot_dry_mass
##   <chr>         <dbl>          <dbl>
## 1 F10              67            122
## 2 F10              66            120
## 3 F10              66            125
## 4 F10              56            132
## 5 F10              55            119
## 6 F10              44             37

dplyr::group_by 関数

group_by 関数は、与えられた条件い基づいて、データをいくつかのグループ分けるときに使用する関数である。group_by 関数で処理した結果を print しても、group_by 関数の処理前と処理後とでは見た目的には変わらない。しかし、group_by 関数で処理した後のデータセットには、グループ情報が属性として保存される。例えば、rice データセットに対して、各系統ごとにグループ分けを行う場合は次のようにする。

head(d)
## # A tibble: 6 x 7
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>
## 1         1     1            56            132 F10   F10   wt     
## 2         2     1            66            120 F10   F10   wt     
## 3         3     1            40            108 F10   F10   wt     
## 4         4     1            43            134 F10   F10   wt     
## 5         5     1            55            119 F10   F10   wt     
## 6         6     1            66            125 F10   F10   wt


d.grouped <- d %>% group_by(variety)
head(d.grouped)
## # A tibble: 6 x 7
## # Groups:   variety [1]
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>   
## 1         1     1            56            132 F10   F10   wt     
## 2         2     1            66            120 F10   F10   wt     
## 3         3     1            40            108 F10   F10   wt     
## 4         4     1            43            134 F10   F10   wt     
## 5         5     1            55            119 F10   F10   wt     
## 6         6     1            66            125 F10   F10   wt

group_by 関数で追加されたグループ情報を削除したい場合は ungroup 関数を使用する。

d.grouped <- d %>% group_by(variety)
d.ungrouped <- d.grouped %>% ungroup()

dplyr::summarise 関数

summarise 関数はデータの集計を行う関数で、group_by 関数などと一緒に使われることが多い。例えば、rice データセットに対して、各系統ごとに根部乾燥重量および地上部乾燥重量の平均値を求めたい場合は、まず variety 列に基づいてグループ分けを行い、続いて根部乾燥重量および地上部乾燥重量の列に対して集計処理(平均値計算)を行う。

d.massave <- d %>%
                group_by(variety) %>%
                summarise(root_dry_mass_ave = mean(root_dry_mass),
                          shoot_dry_mass_ave = mean(shoot_dry_mass))
head(d.massave)
## # A tibble: 2 x 3
##   variety root_dry_mass_ave shoot_dry_mass_ave
##   <chr>               <dbl>              <dbl>
## 1 ANU843               9.67               41.8
## 2 wt                  26.5                77.3

各系統の各処理群に対して平均を求める場合は、group_by のところに条件を 2 つ書く。

d.massave <- d %>%
                group_by(variety, fert) %>%
                summarise(root_dry_mass_ave = mean(root_dry_mass),
                          shoot_dry_mass_ave = mean(shoot_dry_mass))
head(d.massave)
## # A tibble: 6 x 4
## # Groups:   variety [2]
##   variety fert   root_dry_mass_ave shoot_dry_mass_ave
##   <chr>   <chr>              <dbl>              <dbl>
## 1 ANU843  F10                 6                  7.33
## 2 ANU843  NH4Cl               9.17              46.6 
## 3 ANU843  NH4NO3             13.8               71.5 
## 4 wt      F10                49.5              108.  
## 5 wt      NH4Cl              12.6               50.2 
## 6 wt      NH4NO3             17.3               73.3

summarise の中で使われている mean 関数は R の標準関数である。そのため、欠損地を無視して平均値を求めたい場合は、次のように書けばよい。

d.massave <- d %>%
                group_by(variety, fert) %>%
                summarise(root_dry_mass_ave = mean(root_dry_mass, na.rm = TRUE),
                          shoot_dry_mass_ave = mean(shoot_dry_mass, na.rm = TRUE))
head(d.massave)
## # A tibble: 6 x 4
## # Groups:   variety [2]
##   variety fert   root_dry_mass_ave shoot_dry_mass_ave
##   <chr>   <chr>              <dbl>              <dbl>
## 1 ANU843  F10                 6                  7.33
## 2 ANU843  NH4Cl               9.17              46.6 
## 3 ANU843  NH4NO3             13.8               71.5 
## 4 wt      F10                49.5              108.  
## 5 wt      NH4Cl              12.6               50.2 
## 6 wt      NH4NO3             17.3               73.3

summarise 中で使用する関数を独自に定義することもできる。例えば、四分位範囲(IQR)を求める関数 calc.IQR を独自に定義して、それを summarise 中で使う場合は次のようにする。

calc.IQR <- function(x) {
  q1 <- quantile(x, probs = 0.25)
  q3 <- quantile(x, probs = 0.75)
  iqr <- q3 - q1
  return(iqr)
}

d.massave <- d %>%
                group_by(variety, fert) %>%
                summarise(root_dry_mass_IQR = calc.IQR(root_dry_mass),
                          shoot_dry_mass_IQR = calc.IQR(shoot_dry_mass))
head(d.massave)
## # A tibble: 6 x 4
## # Groups:   variety [2]
##   variety fert   root_dry_mass_IQR shoot_dry_mass_IQR
##   <chr>   <chr>              <dbl>              <dbl>
## 1 ANU843  F10                 2.25                4  
## 2 ANU843  NH4Cl               2.5                23.2
## 3 ANU843  NH4NO3              8                  25.8
## 4 wt      F10                17.8                17.2
## 5 wt      NH4Cl              11                  19.2
## 6 wt      NH4NO3              8                  24

dplyr::mutate 関数

mutate 関数は、既存のデータに新しい列を加えたいときに使用する。例えば、根部乾燥重量と地上部乾燥重量の和を計算して、それを dry_mass 列名で既存のデータに追加し、さらに根部乾燥重量が乾燥重量全体における割合を root_dry_mass_ratio 列名で既存のデータに追加する場合は、次のようにする。

d.new <- d %>% mutate(dry_mass = root_dry_mass + shoot_dry_mass,
                      root_dry_mass_ratio = root_dry_mass / dry_mass)
head(d.new)
## # A tibble: 6 x 9
##   replicate block root_dry_mass shoot_dry_mass trt   fert  variety dry_mass
##       <dbl> <dbl>         <dbl>          <dbl> <chr> <chr> <chr>      <dbl>
## 1         1     1            56            132 F10   F10   wt           188
## 2         2     1            66            120 F10   F10   wt           186
## 3         3     1            40            108 F10   F10   wt           148
## 4         4     1            43            134 F10   F10   wt           177
## 5         5     1            55            119 F10   F10   wt           174
## 6         6     1            66            125 F10   F10   wt           191
## # … with 1 more variable: root_dry_mass_ratio <dbl>

新たに作成した列名だけを新しいデータとしたい場合は mutate 関数の代わりに transmute 関数を使う。この際、既存の列名を残すこともできる。

d.new <- d %>% transmute(variety,
                         dry_mass = root_dry_mass + shoot_dry_mass,
                         root_dry_mass_ratio = root_dry_mass / dry_mass)
head(d.new)
## # A tibble: 6 x 3
##   variety dry_mass root_dry_mass_ratio
##   <chr>      <dbl>               <dbl>
## 1 wt           188               0.298
## 2 wt           186               0.355
## 3 wt           148               0.270
## 4 wt           177               0.243
## 5 wt           174               0.316
## 6 wt           191               0.346