タグ別アーカイブ: tidyr

[R] 初心者向け pivot_longer関数で縦型変換

はじめに

以前(というかかなり昔),tidyrやdplyrについての記事を書きました。

どちらの記事を書いたときからもだいぶ月日が経っていて,dplyrのアップデート等もあって色々とやり方が変わっているのですが,自分自身が研究で分析をする際にアップデートしていたことをブログ記事には反映させられていないので,上の2本の記事のアップデート版のようなことを書いておこうと思います。

まずはサンプルデータ

#事前・事後・遅延事後でCAFのデータを取ってるというデザイン
pre.fluency <-rnorm(50,mean=10,sd=2)
pre.accuracy<-rnorm(50,mean=7,sd=2)
pre.complexity<-rnorm(50,mean=4,sd=2)
post.fluency<-rnorm(50,mean=14,sd=4)
post.accuracy<-rnorm(50,mean=10,sd=3)
post.complexity<-rnorm(50,mean=7,sd=2)
delayed.fluency<-rnorm(50,mean=12,sd=2)
delayed.accuracy<-rnorm(50,mean=8,sd=1)
delayed.complexity<-rnorm(50,mean=6,sd=1)

#それぞれの列を横にくっつける
dat<-cbind(pre.fluency,pre.accuracy,pre.complexity,post.fluency,post.accuracy,post.complexity,delayed.fluency,delayed.accuracy,delayed.complexity)
dat<-as.data.frame(dat) #データフレーム型に変換
dat$subject<-rep(1:50) #実験参与者のID列をつける

とりあえずこんな感じでいまデータを持ってるとする

head(dat)
##   pre.fluency pre.accuracy pre.complexity post.fluency post.accuracy
## 1    9.344978     3.921626      4.5778491    13.932376      2.241629
## 2    9.126920     7.307178      3.2309289    13.624622     12.813440
## 3   13.433524     6.048270      5.0082008     7.932601     11.832851
## 4   11.937755     5.859142      0.6414096    11.289177      7.009059
## 5    7.913083     5.748651      2.6099159    17.933721     10.765706
## 6    8.683515     4.363661      5.4608116    16.550468     16.483160
##   post.complexity delayed.fluency delayed.accuracy delayed.complexity subject
## 1        9.381369       10.628923         6.929672           7.923673       1
## 2        4.485729       14.456886         9.048825           5.306266       2
## 3        7.534226       13.031400         7.144670           7.781451       3
## 4        8.076248       12.390157         9.247418           6.244443       4
## 5        8.702016        9.604130         7.530200           5.927892       5
## 6        8.973912        8.060271         8.188526           6.458383       6

縦横変換

昔の縦横変換は,gatherでやっていました。現在は,pivot_関数を使います。その名の通り,縦長(long型)にするのがpivot_longer関数で,横長にするのがpivot_wider関数です。

library(dplyr)
dat %>%
  tidyr::pivot_longer(cols = 1:9)
## # A tibble: 450 × 3
##    subject name               value
##      <int> <chr>              <dbl>
##  1       1 pre.fluency         9.34
##  2       1 pre.accuracy        3.92
##  3       1 pre.complexity      4.58
##  4       1 post.fluency       13.9 
##  5       1 post.accuracy       2.24
##  6       1 post.complexity     9.38
##  7       1 delayed.fluency    10.6 
##  8       1 delayed.accuracy    6.93
##  9       1 delayed.complexity  7.92
## 10       2 pre.fluency         9.13
## # … with 440 more rows

colsのところでどの列をまとめるかという指定をします。ここの指定は,上のやり方だと数字で列指定(1列目から9列目)としています。この部分はc()関数を使って文字列で指定してもいいですし,列指定のときに使えるstarts_with()やcontains() なんかもできます。例えば,まあこれはあくまで偶然そうなだけですけど,ここではまとめたい1列目から9列目はすべて”y”で終わっているので,以下のようにすることもできます。

dat %>% 
  tidyr::pivot_longer(cols = ends_with("y"))
## # A tibble: 450 × 3
##    subject name               value
##      <int> <chr>              <dbl>
##  1       1 pre.fluency         9.34
##  2       1 pre.accuracy        3.92
##  3       1 pre.complexity      4.58
##  4       1 post.fluency       13.9 
##  5       1 post.accuracy       2.24
##  6       1 post.complexity     9.38
##  7       1 delayed.fluency    10.6 
##  8       1 delayed.accuracy    6.93
##  9       1 delayed.complexity  7.92
## 10       2 pre.fluency         9.13
## # … with 440 more rows

また,まとめたくない列を”!“で指定することもできるので,今回のようにまとめないでほしい列が少ないという場合については,次のようにも出来ます。

dat %>% 
  tidyr::pivot_longer(!subject)
## # A tibble: 450 × 3
##    subject name               value
##      <int> <chr>              <dbl>
##  1       1 pre.fluency         9.34
##  2       1 pre.accuracy        3.92
##  3       1 pre.complexity      4.58
##  4       1 post.fluency       13.9 
##  5       1 post.accuracy       2.24
##  6       1 post.complexity     9.38
##  7       1 delayed.fluency    10.6 
##  8       1 delayed.accuracy    6.93
##  9       1 delayed.complexity  7.92
## 10       2 pre.fluency         9.13
## # … with 440 more rows

「まとめる」という感覚がいまいちよくわからないなぁとか,どの列を「まとめ」て,どの列は「まとめ」なくていいのかというのがピンと来ない場合には,次のように考えてください。

分析の際に従属変数(応答変数)となる列をまとめる

さて,今の段階ではまとめた際の列名が”name”になっていて,数値の部分が”value”という列名になっていますよね。ここも次のように指定できます。

dat %>% 
  tidyr::pivot_longer(!subject, names_to = "variable", values_to = "score")
## # A tibble: 450 × 3
##    subject variable           score
##      <int> <chr>              <dbl>
##  1       1 pre.fluency         9.34
##  2       1 pre.accuracy        3.92
##  3       1 pre.complexity      4.58
##  4       1 post.fluency       13.9 
##  5       1 post.accuracy       2.24
##  6       1 post.complexity     9.38
##  7       1 delayed.fluency    10.6 
##  8       1 delayed.accuracy    6.93
##  9       1 delayed.complexity  7.92
## 10       2 pre.fluency         9.13
## # … with 440 more rows

ただ,今回のケースではまとめた列に”pre”, “post”, “delayed”というテスト実施時期(test)という要因と,“complexity”, “accuracy”, “fluency”という測定値の要因が混在していますね。よって,あまり”name”の列名を変えることは意味がありません。むしろ,この列を分割してそれぞれの列に名前をつける必要があります。この列分割をseparate関数で行うという点は私が以前ブログ記事を書いた際と同じです。というわけで,次のようにします。

dat %>% 
  tidyr::pivot_longer(!subject) %>% 
  tidyr::separate(name, c("test","measure"), sep = "\\.")
## # A tibble: 450 × 4
##    subject test    measure    value
##      <int> <chr>   <chr>      <dbl>
##  1       1 pre     fluency     9.34
##  2       1 pre     accuracy    3.92
##  3       1 pre     complexity  4.58
##  4       1 post    fluency    13.9 
##  5       1 post    accuracy    2.24
##  6       1 post    complexity  9.38
##  7       1 delayed fluency    10.6 
##  8       1 delayed accuracy    6.93
##  9       1 delayed complexity  7.92
## 10       2 pre     fluency     9.13
## # … with 440 more rows

これで,完璧ですね。と思いきや…!実はpivot_関数には”name_sep”という便利な引数があります。これはどういう時に使うかと言うと,まとめた際に一つの列に複数の要因が混在してしまうときに,それを指定した区切り文字によって分割するために使います。まさに上で起こった問題ですよね。テスト実施時期と測定値が一緒の列になっていたのをseparate関数で分割したわけですが,なんとseparateを使わなくても縦型に変換する段階で分割までできてしまいます。

dat %>% 
  tidyr::pivot_longer(!subject, names_to = c("test", "measure"), names_sep = "\\.", values_to = "score")
## # A tibble: 450 × 4
##    subject test    measure    score
##      <int> <chr>   <chr>      <dbl>
##  1       1 pre     fluency     9.34
##  2       1 pre     accuracy    3.92
##  3       1 pre     complexity  4.58
##  4       1 post    fluency    13.9 
##  5       1 post    accuracy    2.24
##  6       1 post    complexity  9.38
##  7       1 delayed fluency    10.6 
##  8       1 delayed accuracy    6.93
##  9       1 delayed complexity  7.92
## 10       2 pre     fluency     9.13
## # … with 440 more rows

このときのポイントは2点あります。1つは”names_to”で列名を2つ指定すること。縦型変換の際に列の分割もするので,ここでのnames_toの指定は分割後の列名とします。もう一つは,区切り文字は正規表現を受け付けるということ。区切り文字がドット(.)なので,ここでnames_sep= “.”としてしまうと,正規表現におけるドットだと認識されてしまいます。これでは任意の1文字ですので,列名がうまく分割されずに以下のようになってしまいます。

dat %>% 
  tidyr::pivot_longer(!subject, names_to = c("test", "measure"), names_sep = ".", values_to = "score")
## Warning: Expected 2 pieces. Additional pieces discarded in 9 rows [1, 2, 3, 4,
## 5, 6, 7, 8, 9].
## # A tibble: 450 × 4
##    subject test  measure score
##      <int> <chr> <chr>   <dbl>
##  1       1 ""    ""       9.34
##  2       1 ""    ""       3.92
##  3       1 ""    ""       4.58
##  4       1 ""    ""      13.9 
##  5       1 ""    ""       2.24
##  6       1 ""    ""       9.38
##  7       1 ""    ""      10.6 
##  8       1 ""    ""       6.93
##  9       1 ""    ""       7.92
## 10       2 ""    ""       9.13
## # … with 440 more rows

したがって,正規表現ではありませんよということを追記する必要があります。このことをエスケープするなんて言いますが,Rにおけるエスケープは “\\”です。一般的にはエスケープは”\“ですが,R上では2つ重ねないといけないことに注意が必要です。

おわりに

とりあえず,long型に変換する作業をこの記事では説明しました。少し長くなってしまったので,記述統計を出すという話はまた別の記事にしたいと思います。

なにをゆう たむらゆう。

おしまい。

追記(2022.03.16)

従属変数を3列待ちにしたい(横長にしたい)

よく考えたら,分析するときはcomplexity, accuracy, fluencyの3つの列があったほうが便利ですよね。ということで,その形に変形させましょう。ここで,横長に変換するためのpivot_wider関数を使います。“names_from”の引数で「分解」したい列を指定します。ここでは,accuracy, complexity, fluencyの入っている“measure”の列を「分解」して横長にするので,“measure”を指定します。あとは,横長にしたときに持ってくる数値がどこに入っているかを“values_from”の引数で指定します。これは“score”に入ってますから,これを指定すればいいですね。というわけで,次のようになります。

dat %>% 
  tidyr::pivot_longer(!subject, names_to = c("test", "measure"), names_sep = "\\.", values_to = "score") %>% 
  tidyr::pivot_wider(names_from = "measure", values_from="score")
## # A tibble: 150 × 5
##    subject test    fluency accuracy complexity
##      <int> <chr>     <dbl>    <dbl>      <dbl>
##  1       1 pre       10.1     10.0        3.97
##  2       1 post      15.8      8.50       9.81
##  3       1 delayed   15.5      8.59       4.67
##  4       2 pre       12.0      6.40       1.03
##  5       2 post       6.90    10.1        7.30
##  6       2 delayed   15.0      8.54       7.70
##  7       3 pre        9.07     8.49       2.78
##  8       3 post       7.11    10.2        8.55
##  9       3 delayed   10.7      7.97       5.54
## 10       4 pre       12.5      6.03       2.26
## # … with 140 more rows

もしも,fluency, accuracy, complexityなどの順番が気になる場合は,select関数で指定してください。

dat %>% 
  tidyr::pivot_longer(!subject, names_to = c("test", "measure"), names_sep = "\\.", values_to = "score") %>% 
  tidyr::pivot_wider(names_from = "measure", values_from="score") %>%
  dplyr::select(subject, test, complexity, accuracy, fluency)
## # A tibble: 150 × 5
##    subject test    complexity accuracy fluency
##      <int> <chr>        <dbl>    <dbl>   <dbl>
##  1       1 pre           3.97    10.0    10.1 
##  2       1 post          9.81     8.50   15.8 
##  3       1 delayed       4.67     8.59   15.5 
##  4       2 pre           1.03     6.40   12.0 
##  5       2 post          7.30    10.1     6.90
##  6       2 delayed       7.70     8.54   15.0 
##  7       3 pre           2.78     8.49    9.07
##  8       3 post          8.55    10.2     7.11
##  9       3 delayed       5.54     7.97   10.7 
## 10       4 pre           2.26     6.03   12.5 
## # … with 140 more rows

この変換後のデータを別の変数に保存するのを忘れずに!

dat2 <- dat %>% 
  tidyr::pivot_longer(!subject, names_to = c("test", "measure"), names_sep = "\\.", values_to = "score") %>% 
  tidyr::pivot_wider(names_from = "measure", values_from="score") %>%
  dplyr::select(subject, test, complexity, accuracy, fluency)

head(dat2)
## # A tibble: 6 × 5
##   subject test    complexity accuracy fluency
##     <int> <chr>        <dbl>    <dbl>   <dbl>
## 1       1 pre           3.97    10.0    10.1 
## 2       1 post          9.81     8.50   15.8 
## 3       1 delayed       4.67     8.59   15.5 
## 4       2 pre           1.03     6.40   12.0 
## 5       2 post          7.30    10.1     6.90
## 6       2 delayed       7.70     8.54   15.0