Chapter 4 データ・ハンドリング

データに新しく変数を加えたり、データの形式を変えるなど、より高度で複雑なデータの操作について学んでいく。
この章では、tidyverseというパッケージに入っている関数を解説する。

  • 変数の作成
  • データの抽出
  • パイプ
  • グルーピング
  • データの変換
  • データの結合
  • データの読み込み

4.1 パッケージのロード

まず、この章で使うパッケージのロードをする(初めて使う場合は、マシンに予めパッケージをインストールする必要がある)。パッケージのインストール及びロードについては、第2章で解説している。

library(tidyverse) 

注意:
以降のプログラムでは、関数を「XXXX::YYYY」と表現しているが、これらは「XXXXパッケージに入っているYYYYという名前の関数を使う」ということを意味している。XXXX::の部分は基本的に省略しても問題ないが、例えばtidyverseパッケージ以外もロードしていて、同じ名前の関数が別のパッケージに含まれている場合には、思った通りの結果が表示されない場合もある。外部パッケージの関数を使う場合は、できる限りXXXX::を付けた方が無難である。


以降では、Rに標準で入っているirisデータを例として、ファイル操作の練習を行う。以下のプログラムを実行し、irisデータをdatという名前に置き換えて使っていこう。

dat = iris 

4.2 変数の作成

dplyrパッケージに入っているmutate()を使うと、新たに変数を追加することができる。 mutate()に、データの名前、新しい変数の順番で入力すると、データの右端に新しい変数を追加してくれる。

dat2 = dplyr::mutate(dat, 
              new_var = Sepal.Length + Petal.Length, 
              hoge = ifelse(Species == "setosa", 1, 0)) 
head(dat2)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species new_var hoge
## 1          5.1         3.5          1.4         0.2  setosa     6.5    1
## 2          4.9         3.0          1.4         0.2  setosa     6.3    1
## 3          4.7         3.2          1.3         0.2  setosa     6.0    1
## 4          4.6         3.1          1.5         0.2  setosa     6.1    1
## 5          5.0         3.6          1.4         0.2  setosa     6.4    1
## 6          5.4         3.9          1.7         0.4  setosa     7.1    1

上の例では、Sepal.LengthPetal.Lengthを足したnew_varという名前の新しい変数を作っている。更に、「Sepeciesが”setosa“ならば1, そうでなければ0とする」という条件で新たにhogeという変数を作っている。

4.3 データの抽出

dplyrパッケージに入っているselectfilter関数を使うと、データの中から必要な部分のみを取り出すことができる。

4.3.1 必要な列のみを取り出す(select)

select()で、データの名前、取り出したい変数名(複数選択可)の順番で入力すると、指定した変数の列のみを取り出してくれる。

以下には、irisデータからSepal.LengthPetal.Lengthのみを取り出す場合のプログラム例を示す。

dat2 = dplyr::select(dat, Sepal.Length, Petal.Length) 

head(dat2)
##   Sepal.Length Petal.Length
## 1          5.1          1.4
## 2          4.9          1.4
## 3          4.7          1.3
## 4          4.6          1.5
## 5          5.0          1.4
## 6          5.4          1.7

上の例では、データの中からSepal.LengthPetal.Lengthの列を取り出している。

4.3.2 条件に合う行を取り出す(filter)

ある条件に合う行のみを取り出したい場合(例えばデータの中から男性のみを取り出したいなど)、filter()で、データの名前、条件式の順番で入力すると、データの中から条件に合う行のみを取り出してくれる。

以下には、irisデータから、あやめの種類("Species")のうち"versicolor"のみを取り出す場合のプログラム例を示す。

dat2 = dplyr::filter(dat, Species == "versicolor") 
head(dat2)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
## 1          7.0         3.2          4.7         1.4 versicolor
## 2          6.4         3.2          4.5         1.5 versicolor
## 3          6.9         3.1          4.9         1.5 versicolor
## 4          5.5         2.3          4.0         1.3 versicolor
## 5          6.5         2.8          4.6         1.5 versicolor
## 6          5.7         2.8          4.5         1.3 versicolor

データから条件に合う行だけが取り出される。上の例では、「Speciesversicolorである」行をdatから取り出している。


「イコール」は=ではなく、==と表記していることに注意。つまり、計算式と論理式ではイコールの表現の仕方が異なる。他の論理式の表現については、第2章で説明しているので確認しておこう。例えば、「Sepal.Lengthが7以上」という条件で取り出したいときは、dplyr::filter(iris, Sepal.Length >= 7)とする。


4.4 パイプ

複数のプログラムをつなげることをパイプ処理という。purrrパッケージで(tidyverseパッケージをロードすれば自動で使える)、Rでパイプ処理をすることができる。

例えば、irisデータで「あやめの種類のうち"setosa"のみの行を取り出して、更にSpeciesSepal.Length, Petal.Lengthのみの列を取り出したい」という複数の処理をする場合を例として考える。
先程まで学んだ内容で、以下のように複数のプラグラムを段階的に書けばできなくはないが、プログラムが非常に長くなる(プログラムを分けて書くと途中でミスも生じやすくなる)。

dat = iris #irisデータをdatという名前に置き換える

dat2 = dplyr::filter(dat, Species == "setosa") #まずSpeciesのうち、setosaのみを取り出す。dat2という名前で保存する。

dat3 = dplyr::select(dat2, Species, Sepal.Length, Petal.Length) #別の名前で保存し直したdat2から、Sepal.LengthとPetal.Lengthの列を取り出す。dat3という名前で保存する

head(dat3)
##   Species Sepal.Length Petal.Length
## 1  setosa          5.1          1.4
## 2  setosa          4.9          1.4
## 3  setosa          4.7          1.3
## 4  setosa          4.6          1.5
## 5  setosa          5.0          1.4
## 6  setosa          5.4          1.7

パイプ(%>%)を使えば、このプログラムを1文で書くことができる。

dat = iris #irisデータをdatという名前に置き換える

dat2 = dat %>% dplyr::filter(., Species == "setosa") %>% 
  dplyr::select(., Species, Sepal.Length, Petal.Length)

head(dat2)
##   Species Sepal.Length Petal.Length
## 1  setosa          5.1          1.4
## 2  setosa          4.9          1.4
## 3  setosa          4.7          1.3
## 4  setosa          4.6          1.5
## 5  setosa          5.0          1.4
## 6  setosa          5.4          1.7

%>%はプログラムを渡していく関数であり、.はそのプログラム以前の結果を示している。irisデータをfilterに渡し、その結果をselectに渡している。

なお、.は省略しても良い(以降でも、.は省略して表記する)。

dat2 = dat %>% dplyr::filter(Species == "setosa") %>% 
  dply::select(Species, Sepal.Length, Petal.Length)

4.5 グルーピング

パイプを利用することで、グループごとに統計量(平均値や標準偏差など)を算出することができる。

irisデータを例として、グループごとに平均や標準偏差を計算する方法を覚えよう。

あやめの種類ごとに、がくの長さの平均値と標準偏差を算出してみる。
先ほど学んだパイプ処理(%>%)に加え、dplyrパッケージのgroup_bysummarise関数を利用する。

dat = iris #irisデータをdatという名前に置き換える

dat2 = dat %>% 
  dplyr::group_by(Species) %>% 
  dplyr::summarise(Mean = mean(Sepal.Width, na.rm = TRUE), 
                   SD = sd(Sepal.Width, na.rm = TRUE), 
                   N = length(Sepal.Width))
dat2
## # A tibble: 3 × 4
##   Species     Mean    SD     N
##   <fct>      <dbl> <dbl> <int>
## 1 setosa      3.43 0.379    50
## 2 versicolor  2.77 0.314    50
## 3 virginica   2.97 0.322    50

group_by()はグループ変数を作成する関数である。データの中でグループとして使いたい変数を括弧内に指定する。
summarise()は、複数の関数を実行させる関数である。この例では、mean()sd(), length()の3つの関数を実行し、それぞれの結果をMean, SD, Nという別の名前で保存している。

4.6 データの変換

tidyrパッケージに入っているpivot_longer()pivot_wider()を使うと、データの並び替えなどをすることができる。

4.6.1 wide型とlong型の区別

まず、データのレイアウトには、wide型long型の二種類があることを理解しよう。

以下のデータを例として説明する。A, B, Cの3人の参加者が、X, Y, Z条件の3つの条件で実験課題を行ったとする。

まずは、以下のプログラムを実行してサンプルデータを作成しよう。

dat_wide = data.frame(Subject = c("A","B","C"), 
                      X = c(6,2,7), 
                      Y = c(9,3,4), 
                      Z = c(7,5,7), 
                      Gender = c("M", "F", "F"), 
                      Age = c(18, 19, 20))  #サンプルデータを作る
dat_wide
##   Subject X Y Z Gender Age
## 1       A 6 9 7      M  18
## 2       B 2 3 5      F  19
## 3       C 7 4 7      F  20

このようにデータの入力方法として、1行につき1人の参加者の情報を入力するやり方がある(実験や調査でデータを入力する際も、このレイアウトの方が入力しやすいだろう)。このようなデータのレイアウトをwide型という。

同じデータを、以下のようなレイアウトで表現することもできる。同じく、以下のプログラムを実行して、サンプルデータを作ろう。

dat_long = data.frame(Subject = sort(rep(c("A","B","C"), 3)), 
                      Condition = rep(c("X","Y","Z"), 3), 
                      Score = c(6,9,7,2,3,5,7,4,7), 
                      Gender = c("M", "M", "M", "F","F","F","F","F", "F"), 
                      Age = sort(rep(c(18,19,20), 3)))  
dat_long
##   Subject Condition Score Gender Age
## 1       A         X     6      M  18
## 2       A         Y     9      M  18
## 3       A         Z     7      M  18
## 4       B         X     2      F  19
## 5       B         Y     3      F  19
## 6       B         Z     5      F  19
## 7       C         X     7      F  20
## 8       C         Y     4      F  20
## 9       C         Z     7      F  20

実験成績ごとに1行ずつでデータが作られている。すなわち、同じ参加者1人につき3行のデータがある。このようなデータの方をlong型と呼ぶ。

どのデータ型にすべきか?

R でデータ解析に用いる関数のほとんどは、「1つの観測値(observation)につき1行」が原則、つまりデータがlong型であることを前提として作られている。このテキストで学ぶデータ解析も、基本的に分析で使うデータはlong型を前提とする。

その一方、データを入力するときなど、実務的にはwide型が扱いやすいということもあるだろう。データ入力は研究者の都合に応じてやりやすい方法で用意するとして、解析をする際に適切なデータ形式に変換するすべを身に着けておこう。

4.6.2 wide型からlong型に変換

tidyrパッケージのpivot_longer()を使う。

dat_long2 = dat_wide %>% 
  tidyr::pivot_longer(cols = c("X", "Y", "Z"),
names_to = "Condition", values_to = "Score")
dat_long2
## # A tibble: 9 × 5
##   Subject Gender   Age Condition Score
##   <chr>   <chr>  <dbl> <chr>     <dbl>
## 1 A       M         18 X             6
## 2 A       M         18 Y             9
## 3 A       M         18 Z             7
## 4 B       F         19 X             2
## 5 B       F         19 Y             3
## 6 B       F         19 Z             5
## 7 C       F         20 X             7
## 8 C       F         20 Y             4
## 9 C       F         20 Z             7

cols =で、並べ替える変数を指定する。names_to =で新しく作られるグループを意味する列の名前、values_to =で値を意味する列の名前を指定する。

4.6.3 long型からwide型に変換

tidyrパッケージのpivot_wider()を使う。

dat_wide2 = dat_long %>% 
  tidyr::pivot_wider(names_from = Condition, values_from = Score) 
dat_wide2
## # A tibble: 3 × 6
##   Subject Gender   Age     X     Y     Z
##   <chr>   <chr>  <dbl> <dbl> <dbl> <dbl>
## 1 A       M         18     6     9     7
## 2 B       F         19     2     3     5
## 3 C       F         20     7     4     7

names_from =で横並びにしたときの値のラベル名、values_from =で横並びの対象となる値を指定する。

4.7 データの結合

複数のデータを結合したい場合は、dplyrパッケージのjoin関数を使うとよい。join関数には、left_join, full_joinなど、いくつかの種類が用意されている。

サンプルデータを使いながら、手順について説明する。まず、以下のプログラムを実行して、サンプルデータを作ろう。

dat_sample = data.frame(Subject = c("A","B","C"), 
                        X = c(6,2,7), 
                        Y = c(9,3,4), 
                        Z = c(7,5,7), 
                        Gender = c("M", "F", "F"), 
                        Age = c(18, 19, 20))
dat_sample
##   Subject X Y Z Gender Age
## 1       A 6 9 7      M  18
## 2       B 2 3 5      F  19
## 3       C 7 4 7      F  20

実験で3人の参加者A, B, Cについて、X, Y, Zのデータを取ったとする。

更に、2人の参加者(AとB)に追加で実験を行い、Wのデータを取ったとする。

dat_sample2 = data.frame(Subject = c("A","B"), 
                         W = c(8,3))  
dat_sample2
##   Subject W
## 1       A 8
## 2       B 3

dat_sampledat_sample2のデータを結合して、一つのデータにしたい。

full_join()で結合したい2つのデータ、更に結合する際にキーとなる変数(2つのデータに共通して存在する変数)をby=で指定すると2つのデータを結合してくれる。

なお、by=を省くと、自動で2つのデータに共通する変数を見つけて、それを手がかりに結合してくれる。

dat_sample3 = dplyr::full_join(dat_sample, dat_sample2, by = "Subject")

dat_sample3
##   Subject X Y Z Gender Age  W
## 1       A 6 9 7      M  18  8
## 2       B 2 3 5      F  19  3
## 3       C 7 4 7      F  20 NA

full_join()だと、2つのデータをすべてつなげてくれる。データが含まれていない部分は、欠損になる(data_sample2に参加者Cのデータはないので、欠損になっている)。

left_join()だと、left_join()で左側に入力したデータを含む部分のみをつなげてくれる。

dat_sample3 = dplyr::left_join(dat_sample2, dat_sample, by = "Subject")

dat_sample3
##   Subject W X Y Z Gender Age
## 1       A 8 6 9 7      M  18
## 2       B 3 2 3 5      F  19

4.8 データの読み込み

Rにもともと入っているread.csv()を使えばcsvファイルを読み込むことができるが、readrパッケージの関数を使うと大量のデータが含まれるファイルも高速で読み込んでくれる。また、readxlパッケージの関数を使えば、Excelファイルも読み込んでくれる。

4.8.1 readr

様々な形式のファイルを高速で読み書きことを目標としたパッケージ。

dat = read.csv("./sample_data/0_sample.csv")
dat
##   X Y Gender
## 1 4 6      M
## 2 7 3      F
## 3 7 6      M
## 4 3 3      M
## 5 4 4      F
dat_2 = readr::read_csv("./sample_data/0_sample.csv")
dat_2
## # A tibble: 5 × 3
##       X     Y Gender
##   <dbl> <dbl> <chr> 
## 1     4     6 M     
## 2     7     3 F     
## 3     7     6 M     
## 4     3     3 M     
## 5     4     4 F
#データの書き出し
readr::write_excel_csv(dat_2, "./sample_data/0_sample_2.csv")

read.csv()ではなく、read_csv()なので注意(ドットではなく、アンダースコア)。

ファイルを書き出すための関数も用意されている。
ここではwrite_excel_csv() を使っているが、単にwrite_csv()でも可。

また、read_csv()で読み込んだファイルは、データフレームではなく、tibbleという形式になる。

tibble

tibbleとは、Rのデータ形式の一つである。

データフレームの可読性を向上させたものが、tibbleである。コンソールにはデータ全てではなく、最初の10行程度のみ、列も画面に入る範囲のみが表示される。

as_tibble()でデータフレームをtibble形式にすることもできる。

dat = tibble::as_tibble(iris) #irisデータをtibble型にして、datという名前で保存する
dat
## # A tibble: 150 × 5
##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##           <dbl>       <dbl>        <dbl>       <dbl> <fct>  
##  1          5.1         3.5          1.4         0.2 setosa 
##  2          4.9         3            1.4         0.2 setosa 
##  3          4.7         3.2          1.3         0.2 setosa 
##  4          4.6         3.1          1.5         0.2 setosa 
##  5          5           3.6          1.4         0.2 setosa 
##  6          5.4         3.9          1.7         0.4 setosa 
##  7          4.6         3.4          1.4         0.3 setosa 
##  8          5           3.4          1.5         0.2 setosa 
##  9          4.4         2.9          1.4         0.2 setosa 
## 10          4.9         3.1          1.5         0.1 setosa 
## # ℹ 140 more rows

tibbleだとデータをすべて閲覧することはできないが、すべて閲覧したい場合はView()を使えばよい。

View(dat)

4.8.2 readxl

readxlパッケージで、エクセル形式(xlsx)のファイルを読み込むことができる。

dat = readxl::read_excel("./sample/0_sample.xlsx") 
dat

特にオプションを指定しなければ、1番目に保存されているシートの中身をtibble形式で読み込んでくれる。読み込みたいシートや読み込む範囲を指定したい場合など、細かい点についてはread_excelのヘルプを参照してほしい。

確認問題

問1

irisデータから、1)Speciesversicolorである行を選び、2) Species, Petal.Length及びPetal.Widthの列を取り出し、3) Petal.LengthPetal.Widthを足し合わせた変数hogeを作るという一連の処理を、パイプ処理を使ってプログラム1行でやってみよう。

問2

irisデータから、1)Speciesvirginica以外の行を選び、2) Species, Petal.Length及びPetal.Widthの列を取り出す処理を、パイプ処理を使ってプログラム1行でやってみよう。

ヒント:Rでは、!=が「○○ではない」を意味する論理式である(第2章参照)。

問3

irisデータで、Species別にPetal.Lengthの平均値、標準偏差を求めよう。

ヒント:group_by()summarise()の使い方をおさらいする。