tiydr 1.0.0 で追加される pivot_longer()
と pivot_wider()
の使い方を紹介する vignette を雑に訳した.まだ正式リリースしていないので,仕様もドキュメントも変わる可能性がある.これらの点に注意の上読んで欲しい.
実際,気付いたことをコメントした結果,変わってしまった仕様もある
(#630).他にも仕様やドキュメントに関する Issues 4件 と typo に対する PR を2件挙げてしまったので,pivot_*
はまだまだこれから感が強い.
また,翻訳にはこなれていない部分や,適切な訳が分からず英語が残された部分もある.是非みなさんのコメントで訳を育てて欲しい.質問・コメントは @Atsushi776 (Twitter) まで.
個人的には, @yutannihilation 氏によるスライド「idyr 1.0.0?の新機能 pivot_*()」の説明が非常に分かりやすいのでまず,こちらを読んで,更に深いところを
vignette から学ぶことを薦める.
https://speakerdeck.com/yutannihilation/tidyr-pivot
英語の記事では “Pivoting data from columns to rows (and back!) in the tidyverse” も評判が良い.私はまだ読んでいない.
http://www.storybench.org/pivoting-data-from-columns-to-rows-and-back-in-the-tidyverse/
vignette 原文: https://tidyr.tidyverse.org/dev/articles/pivot.html
はじめに
この vignette では pivot_longer()
と pivot_wider()
の使い方を紹介する.これらの関数は gather()
と spread()
の機能を拡張し,他のパッケージから最新の機能を取り込むことを目指す.
spread()
と gather()
には設計の基本的な部分に誤りがあった.これらの関数がどの方向にデータを広げたり集めたりするのか予想することも覚えることも難しかった.更に引数も非常に覚え辛く,開発者を含め多くの人が度々ドキュメントを参照する羽目になった.
そこで R におけるデータ整形を発展させる2つの重要な新機能が,他のパッケージを参考に実装された.
pivot_longer()
は型の異なる複数の値を記録した変数を扱うことができる.この機能は Matt Dowle と Arun Srinivasan による data.table パッケージにおける改良型のmelt()
とdcast()
を参考にした.pivot_longer()
とpivot_wider()
はデータフレームに対し,列名に記録されたメタデータがどのようにデータ変数に変換されるべきか,あるいは逆変換されるべきかを的確に指定できる.この機能は John Mount と Nina Zumel による cdata パッケージから着想を得た.
この vignette では,pivot_longer()
と pivot_wider()
の背景にある重要なアイディアを紹介する.これらのアイディアはこれまであなたが単純なものから複雑なものまで様々なデータ整形に取り組んできた中で用いてきたものだ.
始めに必要なパッケージを読み込む.実用的には library(tidyverse)
で読み込むだろうが,tidyverse
をパッケージの vignette で読み込むことはできない1.
library(tidyr)
library(dplyr)
library(readr)
Longer
pivot_longer()
はデータセットの行を増やし,列を減らすことで長く (longer) する.開発者は「長い形式 (long form)」という呼び方は合理的ではないと考えている.なぜなら,長さは相対的なもので例えばデータセットの A と B のどちらが長いといった言い方しかできないからだ.
pivot_longer()
は野生のデータセットに対してよく適用されるもので,分析を楽にするためというよりは,データの入力や比較を適切に行うために用いられる.次節からは, pivot_longer()
の使い方を現実にありうる様々なデータセットを用いて紹介する.
列の名前が文字列データに相当する場合
relig_income
データセットは人々の宗教や年収について集計した結果をカウントデータとして記録している.
relig_income
#> # A tibble: 18 x 11
#> religion `<$10k` `$10-20k` `$20-30k` `$30-40k` `$40-50k` `$50-75k`
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 Agnostic 27 34 60 81 76 137
#> 2 Atheist 12 27 37 52 35 70
#> 3 Buddhist 27 21 30 34 33 58
#> 4 Catholic 418 617 732 670 638 1116
#> 5 Don’t k… 15 14 15 11 10 35
#> 6 Evangel… 575 869 1064 982 881 1486
#> 7 Hindu 1 9 7 9 11 34
#> 8 Histori… 228 244 236 238 197 223
#> 9 Jehovah… 20 27 24 24 21 30
#> 10 Jewish 19 19 25 25 30 95
#> # … with 8 more rows, and 4 more variables: `$75-100k` <dbl>,
#> # `$100-150k` <dbl>, `>150k` <dbl>, `Don't know/refused` <dbl>
データセットには3つの変数が含まれる.
religion
は行ごとに記録されているincome
は複数の列の名前として記録されているcount
はセルごとの値として記録されている
pivot_longer()
を使って整形してみよう.
relig_income %>%
pivot_longer(-religion, names_to = "income", values_to = "count")
#> Warning: `vec_type_common()` has been renamed to `vec_ptype_common()`.
#> This warning is displayed once per session.
#> # A tibble: 180 x 3
#> religion income count
#> <chr> <chr> <dbl>
#> 1 Agnostic <$10k 27
#> 2 Agnostic $10-20k 34
#> 3 Agnostic $20-30k 60
#> 4 Agnostic $30-40k 81
#> 5 Agnostic $40-50k 76
#> 6 Agnostic $50-75k 137
#> 7 Agnostic $75-100k 122
#> 8 Agnostic $100-150k 109
#> 9 Agnostic >150k 84
#> 10 Agnostic Don't know/refused 96
#> # … with 170 more rows
第一引数は整形したいデータセットをとる (例えば
relig_income
).第二引数は整形対象となる列を指定する.今回は
religion
以外の全ての列だ.names_to
引数には,列の名前として記録されたデータに変数としての名前を与える.今回ならincome
だ.values_to
引数にはセルの値として記録されているデータに変数としての名前を与える.今回ならcount
だ.
names_to
と values_to
のどちらによって作られる列も relig_income
には含まれていないため,これらの引数には引用符で囲った文字列を指定する.
列の名前が数値データに相当する場合
billboard
データセットは2000年のビルボードランキングを記録している.データの形式としては relig_income
と似ているが,列の名前に記録されたデータは文字列ではなく数値そのものである.
billboard
#> # A tibble: 317 x 79
#> artist track date.entered wk1 wk2 wk3 wk4 wk5 wk6 wk7
#> <chr> <chr> <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2 Pac Baby… 2000-02-26 87 82 72 77 87 94 99
#> 2 2Ge+h… The … 2000-09-02 91 87 92 NA NA NA NA
#> 3 3 Doo… Kryp… 2000-04-08 81 70 68 67 66 57 54
#> 4 3 Doo… Loser 2000-10-21 76 76 72 69 67 65 55
#> 5 504 B… Wobb… 2000-04-15 57 34 25 17 17 31 36
#> 6 98^0 Give… 2000-08-19 51 39 34 26 26 19 2
#> 7 A*Tee… Danc… 2000-07-08 97 97 96 95 100 NA NA
#> 8 Aaliy… I Do… 2000-01-29 84 62 51 41 38 35 35
#> 9 Aaliy… Try … 2000-03-18 59 53 38 28 21 18 16
#> 10 Adams… Open… 2000-08-26 76 76 74 69 68 67 61
#> # … with 307 more rows, and 69 more variables: wk8 <dbl>, wk9 <dbl>,
#> # wk10 <dbl>, wk11 <dbl>, wk12 <dbl>, wk13 <dbl>, wk14 <dbl>,
#> # wk15 <dbl>, wk16 <dbl>, wk17 <dbl>, wk18 <dbl>, wk19 <dbl>,
#> # wk20 <dbl>, wk21 <dbl>, wk22 <dbl>, wk23 <dbl>, wk24 <dbl>,
#> # wk25 <dbl>, wk26 <dbl>, wk27 <dbl>, wk28 <dbl>, wk29 <dbl>,
#> # wk30 <dbl>, wk31 <dbl>, wk32 <dbl>, wk33 <dbl>, wk34 <dbl>,
#> # wk35 <dbl>, wk36 <dbl>, wk37 <dbl>, wk38 <dbl>, wk39 <dbl>,
#> # wk40 <dbl>, wk41 <dbl>, wk42 <dbl>, wk43 <dbl>, wk44 <dbl>,
#> # wk45 <dbl>, wk46 <dbl>, wk47 <dbl>, wk48 <dbl>, wk49 <dbl>,
#> # wk50 <dbl>, wk51 <dbl>, wk52 <dbl>, wk53 <dbl>, wk54 <dbl>,
#> # wk55 <dbl>, wk56 <dbl>, wk57 <dbl>, wk58 <dbl>, wk59 <dbl>,
#> # wk60 <dbl>, wk61 <dbl>, wk62 <dbl>, wk63 <dbl>, wk64 <dbl>,
#> # wk65 <dbl>, wk66 <lgl>, wk67 <lgl>, wk68 <lgl>, wk69 <lgl>,
#> # wk70 <lgl>, wk71 <lgl>, wk72 <lgl>, wk73 <lgl>, wk74 <lgl>,
#> # wk75 <lgl>, wk76 <lgl>
まずは relig_income
dataset と同様に billboard
データセットを整形してみよう.列名は week
という変数に,セルの値は rank
という変数にしよう.加えて,整形時に欠損値が出たらその行を消去するように values_drop_na
を使おう.必ずしも全ての曲が76週間ランキング圏内にあるわけではないため,入力したデータをvalues_drop_na
を使わず整形すると,不必要に自明な NA
が強制的に生じてしまう.
billboard %>%
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
values_to = "rank",
values_drop_na = TRUE
)
#> # A tibble: 5,307 x 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <chr> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk6 94
#> 7 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk7 99
#> 8 2Ge+her The Hardest Part Of ... 2000-09-02 wk1 91
#> 9 2Ge+her The Hardest Part Of ... 2000-09-02 wk2 87
#> 10 2Ge+her The Hardest Part Of ... 2000-09-02 wk3 92
#> # … with 5,297 more rows
各曲が何週間ランキング入りしていたか簡単に分かると嬉しいが,それには変数 week
を整数に直す必要がある.これには2つの引数を追加で指定する必要がある.まず names_prefix
引数によって接頭辞の wk
を消した上で,names_ptypes
引数によって week
が整数であることを指定する2.
billboard %>%
pivot_longer(
cols = starts_with("wk"),
names_to = "week",
names_prefix = "wk",
names_ptypes = list(week = integer()),
values_to = "rank",
values_drop_na = TRUE
)
#> # A tibble: 5,307 x 5
#> artist track date.entered week rank
#> <chr> <chr> <date> <int> <dbl>
#> 1 2 Pac Baby Don't Cry (Keep... 2000-02-26 1 87
#> 2 2 Pac Baby Don't Cry (Keep... 2000-02-26 2 82
#> 3 2 Pac Baby Don't Cry (Keep... 2000-02-26 3 72
#> 4 2 Pac Baby Don't Cry (Keep... 2000-02-26 4 77
#> 5 2 Pac Baby Don't Cry (Keep... 2000-02-26 5 87
#> 6 2 Pac Baby Don't Cry (Keep... 2000-02-26 6 94
#> 7 2 Pac Baby Don't Cry (Keep... 2000-02-26 7 99
#> 8 2Ge+her The Hardest Part Of ... 2000-09-02 1 91
#> 9 2Ge+her The Hardest Part Of ... 2000-09-02 2 87
#> 10 2Ge+her The Hardest Part Of ... 2000-09-02 3 92
#> # … with 5,297 more rows
列名に複数の変数がある場合
列名に複数の変数が詰めこまれていると,整形はより大変になる.例えば who
データセットを見てみよう.
who
#> # A tibble: 7,240 x 60
#> country iso2 iso3 year new_sp_m014 new_sp_m1524 new_sp_m2534
#> <chr> <chr> <chr> <int> <int> <int> <int>
#> 1 Afghan… AF AFG 1980 NA NA NA
#> 2 Afghan… AF AFG 1981 NA NA NA
#> 3 Afghan… AF AFG 1982 NA NA NA
#> 4 Afghan… AF AFG 1983 NA NA NA
#> 5 Afghan… AF AFG 1984 NA NA NA
#> 6 Afghan… AF AFG 1985 NA NA NA
#> 7 Afghan… AF AFG 1986 NA NA NA
#> 8 Afghan… AF AFG 1987 NA NA NA
#> 9 Afghan… AF AFG 1988 NA NA NA
#> 10 Afghan… AF AFG 1989 NA NA NA
#> # … with 7,230 more rows, and 53 more variables: new_sp_m3544 <int>,
#> # new_sp_m4554 <int>, new_sp_m5564 <int>, new_sp_m65 <int>,
#> # new_sp_f014 <int>, new_sp_f1524 <int>, new_sp_f2534 <int>,
#> # new_sp_f3544 <int>, new_sp_f4554 <int>, new_sp_f5564 <int>,
#> # new_sp_f65 <int>, new_sn_m014 <int>, new_sn_m1524 <int>,
#> # new_sn_m2534 <int>, new_sn_m3544 <int>, new_sn_m4554 <int>,
#> # new_sn_m5564 <int>, new_sn_m65 <int>, new_sn_f014 <int>,
#> # new_sn_f1524 <int>, new_sn_f2534 <int>, new_sn_f3544 <int>,
#> # new_sn_f4554 <int>, new_sn_f5564 <int>, new_sn_f65 <int>,
#> # new_ep_m014 <int>, new_ep_m1524 <int>, new_ep_m2534 <int>,
#> # new_ep_m3544 <int>, new_ep_m4554 <int>, new_ep_m5564 <int>,
#> # new_ep_m65 <int>, new_ep_f014 <int>, new_ep_f1524 <int>,
#> # new_ep_f2534 <int>, new_ep_f3544 <int>, new_ep_f4554 <int>,
#> # new_ep_f5564 <int>, new_ep_f65 <int>, newrel_m014 <int>,
#> # newrel_m1524 <int>, newrel_m2534 <int>, newrel_m3544 <int>,
#> # newrel_m4554 <int>, newrel_m5564 <int>, newrel_m65 <int>,
#> # newrel_f014 <int>, newrel_f1524 <int>, newrel_f2534 <int>,
#> # newrel_f3544 <int>, newrel_f4554 <int>, newrel_f5564 <int>,
#> # newrel_f65 <int>
country
, iso2
, iso3
, そして year
は既に変数であり,そのままにしておけばいい.しかし new_sp_m014
列から newrel_f65
列までは4種類の変数がそれぞれの列名に含まれている.
接頭辞の
new_
/new
は新しくカウントした症状であることを示す.このデータセットは新しい例しか含まない.定数に相当するため今回は無視する.sp
/rel
/sp
/ep
は症状を診断した手法を示す.m
/f
は性別を示す.014
/1524
/2535
/3544
/4554
/65
は年齢層を示す3.
今回は names_pattern
を使うとしっくりくるだろう.
names_pattern
は extract
と似た操作性を持ち,
()
を用いてグループ化した正規表現を指定することで,列名からグループとして各変数を取り出すことができる.
who %>% pivot_longer(
cols = new_sp_m014:newrel_f65,
names_to = c("diagnosis", "gender", "age"),
names_pattern = "new_?(.*)_(.)(.*)",
values_to = "count"
)
#> # A tibble: 405,440 x 8
#> country iso2 iso3 year diagnosis gender age count
#> <chr> <chr> <chr> <int> <chr> <chr> <chr> <int>
#> 1 Afghanistan AF AFG 1980 sp m 014 NA
#> 2 Afghanistan AF AFG 1980 sp m 1524 NA
#> 3 Afghanistan AF AFG 1980 sp m 2534 NA
#> 4 Afghanistan AF AFG 1980 sp m 3544 NA
#> 5 Afghanistan AF AFG 1980 sp m 4554 NA
#> 6 Afghanistan AF AFG 1980 sp m 5564 NA
#> 7 Afghanistan AF AFG 1980 sp m 65 NA
#> 8 Afghanistan AF AFG 1980 sp f 014 NA
#> 9 Afghanistan AF AFG 1980 sp f 1524 NA
#> 10 Afghanistan AF AFG 1980 sp f 2534 NA
#> # … with 405,430 more rows
更に一歩進んで gender
と age
列に型を指定してみよう.この作法は値の分かっているカテゴリカルな変数の操作に優れている.
who %>% pivot_longer(
cols = new_sp_m014:newrel_f65,
names_to = c("diagnosis", "gender", "age"),
names_pattern = "new_?(.*)_(.)(.*)",
names_ptypes = list(
gender = factor(levels = c("f", "m")),
age = factor(
levels = c("014", "1524", "2534", "3544", "4554", "5564", "65"),
ordered = TRUE
)
),
values_to = "count"
)
#> # A tibble: 405,440 x 8
#> country iso2 iso3 year diagnosis gender age count
#> <chr> <chr> <chr> <int> <chr> <fct> <ord> <int>
#> 1 Afghanistan AF AFG 1980 sp m 014 NA
#> 2 Afghanistan AF AFG 1980 sp m 1524 NA
#> 3 Afghanistan AF AFG 1980 sp m 2534 NA
#> 4 Afghanistan AF AFG 1980 sp m 3544 NA
#> 5 Afghanistan AF AFG 1980 sp m 4554 NA
#> 6 Afghanistan AF AFG 1980 sp m 5564 NA
#> 7 Afghanistan AF AFG 1980 sp m 65 NA
#> 8 Afghanistan AF AFG 1980 sp f 014 NA
#> 9 Afghanistan AF AFG 1980 sp f 1524 NA
#> 10 Afghanistan AF AFG 1980 sp f 2534 NA
#> # … with 405,430 more rows
一行に複数の観測値がある場合4
これまで見てきたデータフレームでは,整形時にセルの値が入る列 (value column) は1つだった.しかし多くの重要な整形問題では,セルの値が複数列に入ることがある.このような問題は,整形後のデータフレームに欲しい列名が,整形前のデータフレームの列名の一部になっていることで発覚する.この節では,そんなデータをどうやって整形するか見ていく.
以下の例の出典は
data.table vignette
で,tidyr
における解法を閃くきっかけとなった.
family <- tribble(
~family, ~dob_child1, ~dob_child2, ~gender_child1, ~gender_child2,
1L, "1998-11-26", "2000-01-29", 1L, 2L,
2L, "1996-06-22", NA, 2L, NA,
3L, "2002-07-11", "2004-04-05", 2L, 2L,
4L, "2004-10-10", "2009-08-27", 1L, 1L,
5L, "2000-12-05", "2005-02-28", 2L, 1L,
)
family <- family %>% mutate_at(vars(starts_with("dob")), parse_date)
family
#> # A tibble: 5 x 5
#> family dob_child1 dob_child2 gender_child1 gender_child2
#> <int> <date> <date> <int> <int>
#> 1 1 1998-11-26 2000-01-29 1 2
#> 2 2 1996-06-22 NA 2 NA
#> 3 3 2002-07-11 2004-04-05 2 2
#> 4 4 2004-10-10 2009-08-27 1 1
#> 5 5 2000-12-05 2005-02-28 2 1
ここには子供たちそれぞれの gender
と dob
(誕生日) の2種類の情報 (または値) が載っている.これらの情報を別々の列に分けなければならい.そこで names_to
に複数の値を指定し,names_sep
を使って変数の名前を切り分ける.ただし,特別な変数名である .value
を使って pivot_longer()
に列名の一部が整形後の value column になることを伝える.
family %>%
pivot_longer(
-family,
names_to = c(".value", "child"),
names_sep = "_",
values_drop_na = TRUE
)
#> # A tibble: 9 x 4
#> family child dob gender
#> <int> <chr> <date> <int>
#> 1 1 child1 1998-11-26 1
#> 2 1 child2 2000-01-29 2
#> 3 2 child1 1996-06-22 2
#> 4 3 child1 2002-07-11 2
#> 5 3 child2 2004-04-05 2
#> 6 4 child1 2004-10-10 1
#> 7 4 child2 2009-08-27 1
#> 8 5 child1 2000-12-05 2
#> 9 5 child2 2005-02-28 1
values_drop_na = TRUE
を使うことで,入力データ上に観測値のない部分が出力において明示的に欠測値になってしまうことを防いでいる点に注意されたい.
この問題は base R に含まれている anscombe
データセットにも存在する.
anscombe
#> x1 x2 x3 x4 y1 y2 y3 y4
#> 1 10 10 10 8 8.04 9.14 7.46 6.58
#> 2 8 8 8 8 6.95 8.14 6.77 5.76
#> 3 13 13 13 8 7.58 8.74 12.74 7.71
#> 4 9 9 9 8 8.81 8.77 7.11 8.84
#> 5 11 11 11 8 8.33 9.26 7.81 8.47
#> 6 14 14 14 8 9.96 8.10 8.84 7.04
#> 7 6 6 6 8 7.24 6.13 6.08 5.25
#> 8 4 4 4 19 4.26 3.10 5.39 12.50
#> 9 12 12 12 8 10.84 9.13 8.15 5.56
#> 10 7 7 7 8 4.82 7.26 6.42 7.91
#> 11 5 5 5 8 5.68 4.74 5.73 6.89
このデータセットは Anscombe’s quartet を構成する4対の変数から成る (x1
と y1
,x2
と y2
,など).これら4つのデータセットは内容が大きく異なるにも拘らず同じ要約統計量を示す(平均,標準偏差,相関など).これを set
, x
, y
から成るデータセットに整形してみる.
anscombe %>%
pivot_longer(everything(),
names_to = c(".value", "set"),
names_pattern = "(.)(.)"
) %>%
arrange(set)
#> # A tibble: 44 x 3
#> set x y
#> <chr> <dbl> <dbl>
#> 1 1 10 8.04
#> 2 1 8 6.95
#> 3 1 13 7.58
#> 4 1 9 8.81
#> 5 1 11 8.33
#> 6 1 14 9.96
#> 7 1 6 7.24
#> 8 1 4 4.26
#> 9 1 12 10.8
#> 10 1 7 4.82
#> # … with 34 more rows
パネルデータでも似たような状況に遭遇する.例えば
Thomas Leeper
によるデータセットを例にとってみよう.このデータの整形方法は anscombe
に対して行ったものと同様だ.
pnl <- tibble(
x = 1:4,
a = c(1, 1,0, 0),
b = c(0, 1, 1, 1),
y1 = rnorm(4),
y2 = rnorm(4),
z1 = rep(3, 4),
z2 = rep(-2, 4),
)
pnl %>%
pivot_longer(
-c(x, a, b),
names_to = c(".value", "time"),
names_pattern = "(.)(.)"
)
#> # A tibble: 8 x 6
#> x a b time y z
#> <int> <dbl> <dbl> <chr> <dbl> <dbl>
#> 1 1 1 0 1 0.148 3
#> 2 1 1 0 2 -0.598 -2
#> 3 2 1 1 1 1.25 3
#> 4 2 1 1 2 0.171 -2
#> 5 3 0 1 1 -0.858 3
#> 6 3 0 1 2 -0.205 -2
#> 7 4 0 1 1 1.39 3
#> 8 4 0 1 2 -0.262 -2
列名が重複している場合
列名が重複したデータセットに遭遇することもあるだろう.一般的には R でこのようなデータセットを扱うのは難しい.なぜなら,名前で列を参照しようとすると,最初にマッチしたものしか参照できないからだ.列名の重複した tibble を作るには,そのようなデータセットの作成を防ぐ列名の修正機能を明示的に止めておく必要がある.
df <- tibble(x = 1:3, y = 4:6, y = 5:7, y = 7:9, .name_repair = "minimal")
df
#> # A tibble: 3 x 4
#> x y y y
#> <int> <int> <int> <int>
#> 1 1 4 5 7
#> 2 2 5 6 8
#> 3 3 6 7 9
このようなデータを pivot_longer()
で処理すると,出力には自動的に新しい列を追加される.
df %>% pivot_longer(-x, names_to = "name", values_to = "value")
#> Warning: Duplicate column names detected, adding .copy variable
#> # A tibble: 9 x 4
#> x name .copy value
#> <int> <chr> <int> <int>
#> 1 1 y 1 4
#> 2 1 y 2 5
#> 3 1 y 3 7
#> 4 2 y 1 5
#> 5 2 y 2 6
#> 6 2 y 3 8
#> 7 3 y 1 6
#> 8 3 y 2 7
#> 9 3 y 3 9
Wider
pivot_wider()
は pivot_longer()
の反対で,列数を増やして行数を減らすことで,データセットを広く (wider) する.pivot_wider()
を使って整然データを作ることは珍しいが,pivot_wider()
は発表用に要約した表を作る際やデータを他のツールに必要な形式に変換する際に役立つ.
Capture-recapture data
Myfanwy Johnston による
fish_encounters
データセットは,自動観測機が川を下る魚を検出したかをまとめている.
fish_encounters
#> # A tibble: 114 x 3
#> fish station seen
#> <fct> <fct> <int>
#> 1 4842 Release 1
#> 2 4842 I80_1 1
#> 3 4842 Lisbon 1
#> 4 4842 Rstr 1
#> 5 4842 Base_TD 1
#> 6 4842 BCE 1
#> 7 4842 BCW 1
#> 8 4842 BCE2 1
#> 9 4842 BCW2 1
#> 10 4842 MAE 1
#> # … with 104 more rows
解析ツールの多くは,このデータが観測機ごとに列を成す形式に従っていることを求める.
fish_encounters %>% pivot_wider(names_from = station, values_from = seen)
#> # A tibble: 19 x 12
#> fish Release I80_1 Lisbon Rstr Base_TD BCE BCW BCE2 BCW2 MAE
#> <fct> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 4842 1 1 1 1 1 1 1 1 1 1
#> 2 4843 1 1 1 1 1 1 1 1 1 1
#> 3 4844 1 1 1 1 1 1 1 1 1 1
#> 4 4845 1 1 1 1 1 NA NA NA NA NA
#> 5 4847 1 1 1 NA NA NA NA NA NA NA
#> 6 4848 1 1 1 1 NA NA NA NA NA NA
#> 7 4849 1 1 NA NA NA NA NA NA NA NA
#> 8 4850 1 1 NA 1 1 1 1 NA NA NA
#> 9 4851 1 1 NA NA NA NA NA NA NA NA
#> 10 4854 1 1 NA NA NA NA NA NA NA NA
#> # … with 9 more rows, and 1 more variable: MAW <int>
このデータセットは,ある魚が観測機に観測された時だけ記録していて,観測されなかった時のことは記録していない (この種のデータでは一般的なことだ).従って出力は NA
で埋められる.しかしこの場合,欠測が魚を見なかった
ことを意味すると分かっているので,pivot_wider()
を使って欠測値を0で埋められる.
fish_encounters %>% pivot_wider(
names_from = station,
values_from = seen,
values_fill = list(seen = 0)
)
#> # A tibble: 19 x 12
#> fish Release I80_1 Lisbon Rstr Base_TD BCE BCW BCE2 BCW2 MAE
#> <fct> <int> <int> <int> <int> <int> <int> <int> <int> <int> <int>
#> 1 4842 1 1 1 1 1 1 1 1 1 1
#> 2 4843 1 1 1 1 1 1 1 1 1 1
#> 3 4844 1 1 1 1 1 1 1 1 1 1
#> 4 4845 1 1 1 1 1 0 0 0 0 0
#> 5 4847 1 1 1 0 0 0 0 0 0 0
#> 6 4848 1 1 1 1 0 0 0 0 0 0
#> 7 4849 1 1 0 0 0 0 0 0 0 0
#> 8 4850 1 1 0 1 1 1 1 0 0 0
#> 9 4851 1 1 0 0 0 0 0 0 0 0
#> 10 4854 1 1 0 0 0 0 0 0 0 0
#> # … with 9 more rows, and 1 more variable: MAW <int>
集約 (Aggregation)
また,pivot_wider()
を使って単純な集約を行うこともできる.Base R に組込まれている warpbreaks
データセットを例にとってみよう (出力の見栄えをよくするため tibble に変換しておく):
warpbreaks <- warpbreaks %>% as_tibble() %>% select(wool, tension, breaks)
warpbreaks
#> # A tibble: 54 x 3
#> wool tension breaks
#> <fct> <fct> <dbl>
#> 1 A L 26
#> 2 A L 30
#> 3 A L 54
#> 4 A L 25
#> 5 A L 70
#> 6 A L 52
#> 7 A L 51
#> 8 A L 26
#> 9 A L 67
#> 10 A M 18
#> # … with 44 more rows
これは,wool
(A
と B
) と tension
(L
, M
, H
) の全組み合わせに対し,
9回ずつ実験を繰り返した計画実験だ.
warpbreaks %>% count(wool, tension)
#> # A tibble: 6 x 3
#> wool tension n
#> <fct> <fct> <int>
#> 1 A L 9
#> 2 A M 9
#> 3 A H 9
#> 4 B L 9
#> 5 B M 9
#> 6 B H 9
wool
(訳注: factor 型) の水準を列に展開するとどうなるだろうか.
warpbreaks %>% pivot_wider(names_from = wool, values_from = breaks)
#> Warning: Values in `breaks` are not uniquely identified; output will contain list-cols.
#> * Use `values_fn = list(breaks = list)` to suppress this warning.
#> * Use `values_fn = list(breaks = length)` to identify where the duplicates arise
#> * Use `values_fn = list(breaks = summary_fun)` to summarise duplicates
#> # A tibble: 3 x 3
#> tension A B
#> <fct> <list<dbl>> <list<dbl>>
#> 1 L [9] [9]
#> 2 M [9] [9]
#> 3 H [9] [9]
出力される各セルが入力された複数のセルに対応すると警告される.既定の挙動ではリストから成る列 (list-columns) を生成し,各要素は全ての個別の値を格納する.より便利な出力は要約統計量だろう.例えば mean
によって,wool
と tention
の組み合わせごとに平均値を算出できる.
warpbreaks %>%
pivot_wider(
names_from = wool,
values_from = breaks,
values_fn = list(breaks = mean)
)
#> # A tibble: 3 x 3
#> tension A B
#> <fct> <dbl> <dbl>
#> 1 L 44.6 28.2
#> 2 M 24 28.8
#> 3 H 24.6 18.8
より複雑な要約は,整形前に行っておくことを薦めるが,単純なものは pivot_wider()
で済ましてしまう方が便利なことも多い.
複数の変数から複数の列名を生成する
あるデータセットが product,country,yearの組み合わせで構成されているとしよう (例えば http://stackoverflow.com/questions/24929954).整然とした形式では以下のようになる.
production <- expand_grid(
product = c("A", "B"),
country = c("AI", "EI"),
year = 2000:2014
) %>%
filter((product == "A" & country == "AI") | product == "B") %>%
mutate(production = rnorm(nrow(.)))
production
#> # A tibble: 45 x 4
#> product country year production
#> <chr> <chr> <int> <dbl>
#> 1 A AI 2000 0.755
#> 2 A AI 2001 -0.730
#> 3 A AI 2002 0.0795
#> 4 A AI 2003 -2.40
#> 5 A AI 2004 -1.20
#> 6 A AI 2005 0.751
#> 7 A AI 2006 -0.432
#> 8 A AI 2007 0.523
#> 9 A AI 2008 -0.522
#> 10 A AI 2009 -1.12
#> # … with 35 more rows
このデータを広げて,列ごとに異なる product
と country
の組み合わせを示すようにしよう.names_from
に複数の変数を指定するのが鍵だ.
production %>% pivot_wider(
names_from = c(product, country),
values_from = production
)
#> # A tibble: 15 x 4
#> year A_AI B_AI B_EI
#> <int> <dbl> <dbl> <dbl>
#> 1 2000 0.755 -0.107 1.32
#> 2 2001 -0.730 -1.03 0.968
#> 3 2002 0.0795 -0.279 1.85
#> 4 2003 -2.40 0.667 0.682
#> 5 2004 -1.20 0.100 0.215
#> 6 2005 0.751 1.77 1.57
#> 7 2006 -0.432 -0.914 -0.145
#> 8 2007 0.523 0.363 -1.85
#> 9 2008 -0.522 0.337 -0.748
#> 10 2009 -1.12 -0.336 -0.491
#> # … with 5 more rows
整然とした国勢調査 (Tidy census)
us_rent_income
データセットは,2017年のアメリカ合衆国における年収と家賃の中央値を州ごとに集計している (the American Community Survey から tidycensus パッケージによって入手した).
us_rent_income
#> # A tibble: 104 x 5
#> GEOID NAME variable estimate moe
#> <chr> <chr> <chr> <dbl> <dbl>
#> 1 01 Alabama income 24476 136
#> 2 01 Alabama rent 747 3
#> 3 02 Alaska income 32940 508
#> 4 02 Alaska rent 1200 13
#> 5 04 Arizona income 27517 148
#> 6 04 Arizona rent 972 4
#> 7 05 Arkansas income 23789 165
#> 8 05 Arkansas rent 709 5
#> 9 06 California income 29454 109
#> 10 06 California rent 1358 3
#> # … with 94 more rows
estimate
と moe
は両方とも values columns であるため,values_from
に指定する.
us_rent_income %>%
pivot_wider(names_from = variable, values_from = c(estimate, moe))
#> # A tibble: 52 x 6
#> GEOID NAME estimate_income estimate_rent moe_income moe_rent
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 01 Alabama 24476 747 136 3
#> 2 02 Alaska 32940 1200 508 13
#> 3 04 Arizona 27517 972 148 4
#> 4 05 Arkansas 23789 709 165 5
#> 5 06 California 29454 1358 109 3
#> 6 08 Colorado 32401 1125 109 5
#> 7 09 Connecticut 35326 1123 195 5
#> 8 10 Delaware 31560 1076 247 10
#> 9 11 District of Col… 43198 1424 681 17
#> 10 12 Florida 25952 1077 70 3
#> # … with 42 more rows
出力される列には変数名が自動的に追記されることに注意されたい.
連絡帳
最後に取り組む問題は Jiena Gu に影響されたものだ.以下のようにウェブサイトからコピー&ペーストした連絡帳があるとしよう.
contacts <- tribble(
~field, ~value,
"name", "Jiena McLellan",
"company", "Toyota",
"name", "John Smith",
"company", "google",
"email", "[email protected]",
"name", "Huxley Ratcliffe"
)
このデータセットではどの値が同じ組になるか分からないことが課題だ.全ての連絡先が名前 (name) から始まっていることに気付けば,この課題を解決できる.field
列に “name” が現れる度に固有の id を割り当てよう.
contacts <- contacts %>%
mutate(
person_id = cumsum(field == "name")
)
contacts
#> # A tibble: 6 x 3
#> field value person_id
#> <chr> <chr> <int>
#> 1 name Jiena McLellan 1
#> 2 company Toyota 1
#> 3 name John Smith 2
#> 4 company google 2
#> 5 email [email protected] 2
#> 6 name Huxley Ratcliffe 3
これで各人に対し,固有な識別子が割り当てられたので,field
と value
を列に展開できる.
contacts %>%
pivot_wider(names_from = field, values_from = value)
#> # A tibble: 3 x 4
#> person_id name company email
#> <int> <chr> <chr> <chr>
#> 1 1 Jiena McLellan Toyota <NA>
#> 2 2 John Smith google [email protected]
#> 3 3 Huxley Ratcliffe <NA> <NA>
Longer にしてから wider にする
一方向に pivot するだけでは問題が解決しないことがある.本節ではpivot_longer()
と pivot_wider()
を組み合わせて複雑な問題を解決する例を紹介する.
世界銀行
world_bank_pop
は世界銀行による2000年から2018年までの各国の人口に関するデータが記録されている.
world_bank_pop
#> # A tibble: 1,056 x 20
#> country indicator `2000` `2001` `2002` `2003` `2004` `2005` `2006`
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 ABW SP.URB.T… 4.24e4 4.30e4 4.37e4 4.42e4 4.47e+4 4.49e+4 4.49e+4
#> 2 ABW SP.URB.G… 1.18e0 1.41e0 1.43e0 1.31e0 9.51e-1 4.91e-1 -1.78e-2
#> 3 ABW SP.POP.T… 9.09e4 9.29e4 9.50e4 9.70e4 9.87e+4 1.00e+5 1.01e+5
#> 4 ABW SP.POP.G… 2.06e0 2.23e0 2.23e0 2.11e0 1.76e+0 1.30e+0 7.98e-1
#> 5 AFG SP.URB.T… 4.44e6 4.65e6 4.89e6 5.16e6 5.43e+6 5.69e+6 5.93e+6
#> 6 AFG SP.URB.G… 3.91e0 4.66e0 5.13e0 5.23e0 5.12e+0 4.77e+0 4.12e+0
#> 7 AFG SP.POP.T… 2.01e7 2.10e7 2.20e7 2.31e7 2.41e+7 2.51e+7 2.59e+7
#> 8 AFG SP.POP.G… 3.49e0 4.25e0 4.72e0 4.82e0 4.47e+0 3.87e+0 3.23e+0
#> 9 AGO SP.URB.T… 8.23e6 8.71e6 9.22e6 9.77e6 1.03e+7 1.09e+7 1.15e+7
#> 10 AGO SP.URB.G… 5.44e0 5.59e0 5.70e0 5.76e0 5.75e+0 5.69e+0 4.92e+0
#> # … with 1,046 more rows, and 11 more variables: `2007` <dbl>,
#> # `2008` <dbl>, `2009` <dbl>, `2010` <dbl>, `2011` <dbl>, `2012` <dbl>,
#> # `2013` <dbl>, `2014` <dbl>, `2015` <dbl>, `2016` <dbl>, `2017` <dbl>
このデータセットを,それぞれの変数が一つの列に納ままるように整形しよう.この段階ではどのような手順を踏めばいいか分からないが,まず最も明らかな問題である,複数列にまたがる yaer
を修正しよう.
pop2 <- world_bank_pop %>%
pivot_longer(`2000`:`2017`, names_to = "year", values_to = "value")
pop2
#> # A tibble: 19,008 x 4
#> country indicator year value
#> <chr> <chr> <chr> <dbl>
#> 1 ABW SP.URB.TOTL 2000 42444
#> 2 ABW SP.URB.TOTL 2001 43048
#> 3 ABW SP.URB.TOTL 2002 43670
#> 4 ABW SP.URB.TOTL 2003 44246
#> 5 ABW SP.URB.TOTL 2004 44669
#> 6 ABW SP.URB.TOTL 2005 44889
#> 7 ABW SP.URB.TOTL 2006 44881
#> 8 ABW SP.URB.TOTL 2007 44686
#> 9 ABW SP.URB.TOTL 2008 44375
#> 10 ABW SP.URB.TOTL 2009 44052
#> # … with 18,998 more rows
次に, indicator
列を見てみよう.
pop2 %>% count(indicator)
#> # A tibble: 4 x 2
#> indicator n
#> <chr> <int>
#> 1 SP.POP.GROW 4752
#> 2 SP.POP.TOTL 4752
#> 3 SP.URB.GROW 4752
#> 4 SP.URB.TOTL 4752
SP.POP.GROW
は人口増加を, SP.POP.TOTL
は総人口を, そして SP.URB.*
は都会における人口増加と総人口を示す.これらを観測値が全域から得たものか都会から得たものか示す area
列と,観測値が総人口と人口増加のどちらを記録しているか示す variable
列の2列に分けよう.
pop3 <- pop2 %>%
separate(indicator, c(NA, "area", "variable"))
pop3
#> # A tibble: 19,008 x 5
#> country area variable year value
#> <chr> <chr> <chr> <chr> <dbl>
#> 1 ABW URB TOTL 2000 42444
#> 2 ABW URB TOTL 2001 43048
#> 3 ABW URB TOTL 2002 43670
#> 4 ABW URB TOTL 2003 44246
#> 5 ABW URB TOTL 2004 44669
#> 6 ABW URB TOTL 2005 44889
#> 7 ABW URB TOTL 2006 44881
#> 8 ABW URB TOTL 2007 44686
#> 9 ABW URB TOTL 2008 44375
#> 10 ABW URB TOTL 2009 44052
#> # … with 18,998 more rows
最後に variable
列と value
列を展開し,TOTL
列と GROW
列にしよう.
pop3 %>%
pivot_wider(names_from = variable, values_from = value)
#> # A tibble: 9,504 x 5
#> country area year TOTL GROW
#> <chr> <chr> <chr> <dbl> <dbl>
#> 1 ABW URB 2000 42444 1.18
#> 2 ABW URB 2001 43048 1.41
#> 3 ABW URB 2002 43670 1.43
#> 4 ABW URB 2003 44246 1.31
#> 5 ABW URB 2004 44669 0.951
#> 6 ABW URB 2005 44889 0.491
#> 7 ABW URB 2006 44881 -0.0178
#> 8 ABW URB 2007 44686 -0.435
#> 9 ABW URB 2008 44375 -0.698
#> 10 ABW URB 2009 44052 -0.731
#> # … with 9,494 more rows
複数回答
Maxime Wack による提案を受けて https://github.com/tidyverse/tidyr/issues/384),よくある複数回答データをどのように扱えばいいかを最後の例としよう.複数回答データは以下のような形式であることが多い.
multi <- tribble(
~id, ~choice1, ~choice2, ~choice3,
1, "A", "B", "C",
2, "C", "B", NA,
3, "D", NA, NA,
4, "B", "D", NA
)
しかし,実際の順番は重要ではなく,id ごとの解答内容を示したいとしよう5.望んだ形式への変換は2つの操作で完了する.まず,データを longer 形式にし,自明な NA
を消去し,選択肢から回答を得られたかを示す列を追加する.
multi2 <- multi %>%
pivot_longer(-id, values_drop_na = TRUE) %>%
mutate(checked = TRUE)
multi2
#> # A tibble: 8 x 4
#> id name value checked
#> <dbl> <chr> <chr> <lgl>
#> 1 1 choice1 A TRUE
#> 2 1 choice2 B TRUE
#> 3 1 choice3 C TRUE
#> 4 2 choice1 C TRUE
#> 5 2 choice2 B TRUE
#> 6 3 choice1 D TRUE
#> 7 4 choice1 B TRUE
#> 8 4 choice2 D TRUE
次にデータを wider
型にし,無回答部分を FALSE
で埋める.
multi2 %>%
pivot_wider(
id_cols = id,
names_from = value,
values_from = checked,
values_fill = list(checked = FALSE)
)
#> # A tibble: 4 x 5
#> id A B C D
#> <dbl> <lgl> <lgl> <lgl> <lgl>
#> 1 1 TRUE TRUE TRUE FALSE
#> 2 2 FALSE TRUE TRUE FALSE
#> 3 3 FALSE FALSE FALSE TRUE
#> 4 4 FALSE TRUE FALSE TRUE
Manual specs
pivot_longer()
と pivot_wider()
の引数は,様々なデータセットの整形を可能とする.しかし人々がデータ構造に対して発揮する想像力は無尽蔵にも見えるので,一見 pivot_longer()
と pivot_wider()
でもどうしようもない問題に遭遇することもありえる.より柔軟な整形を行うためには,仕様となるデータフレームを作り,厳格に列名ごとに格納されるデータがどのようにあるべきか,あるいはある種のデータが格納される列の名前がどのようにあるべきか,決めておける.本節では,どのようにデータ構造の仕様を定めればよいか紹介し,
pivot_longer()
と pivot_wider()
が不十分な場合にその仕様をどう示せばいいのかを紹介する.
Longer
仕様の策定方法を学ぶにあたって, relig_income
データセットを用いたもっとも単純な整形をやり直してみよう.今回は整形に2段階の手順を踏む.まず,仕様となるオブジェクトを pivot_longer_spec()
を用いて作り,整形時に仕様を利用する.
spec <- relig_income %>% pivot_longer_spec(
cols = -religion,
names_to = "income",
values_to = "count"
)
relig_income %>% pivot_longer(spec = spec)
#> # A tibble: 180 x 3
#> religion income count
#> <chr> <chr> <dbl>
#> 1 Agnostic <$10k 27
#> 2 Agnostic $10-20k 34
#> 3 Agnostic $20-30k 60
#> 4 Agnostic $30-40k 81
#> 5 Agnostic $40-50k 76
#> 6 Agnostic $50-75k 137
#> 7 Agnostic $75-100k 122
#> 8 Agnostic $100-150k 109
#> 9 Agnostic >150k 84
#> 10 Agnostic Don't know/refused 96
#> # … with 170 more rows
(コードは増えたが結果は以前と同じだ.この場合, spec
を定める必要はないが,用例を示す単純な例として紹介した.)
spec
の中身はデータフレームで,各行は元のデータフレームの各列に対応しており6,
.
から始まる2つの特別な列を持つ.
.name
は列名を示す..value
はセルの移動先となる列の名前を示す.
spec
#> # A tibble: 10 x 3
#> .name .value income
#> <chr> <chr> <chr>
#> 1 <$10k count <$10k
#> 2 $10-20k count $10-20k
#> 3 $20-30k count $20-30k
#> 4 $30-40k count $30-40k
#> 5 $40-50k count $40-50k
#> 6 $50-75k count $50-75k
#> 7 $75-100k count $75-100k
#> 8 $100-150k count $100-150k
#> 9 >150k count >150k
#> 10 Don't know/refused count Don't know/refused
Wider
以下では us_rent_income
を pivot_wider()
によって広げている.良い結果が得られているが改善の余地があるだろう.
us_rent_income %>%
pivot_wider(names_from = variable, values_from = c(estimate, moe))
#> # A tibble: 52 x 6
#> GEOID NAME estimate_income estimate_rent moe_income moe_rent
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 01 Alabama 24476 747 136 3
#> 2 02 Alaska 32940 1200 508 13
#> 3 04 Arizona 27517 972 148 4
#> 4 05 Arkansas 23789 709 165 5
#> 5 06 California 29454 1358 109 3
#> 6 08 Colorado 32401 1125 109 5
#> 7 09 Connecticut 35326 1123 195 5
#> 8 10 Delaware 31560 1076 247 10
#> 9 11 District of Col… 43198 1424 681 17
#> 10 12 Florida 25952 1077 70 3
#> # … with 42 more rows
手製の仕様 (manual spec) を使って列名を rent
,rent_moe
,income
,income_moe
に改善しよう.現在の仕様は以下の通りだ.
us_rent_income %>%
pivot_wider_spec(names_from = variable, values_from = c(estimate, moe))
#> # A tibble: 4 x 3
#> .name .value variable
#> <chr> <chr> <chr>
#> 1 estimate_income estimate income
#> 2 estimate_rent estimate rent
#> 3 moe_income moe income
#> 4 moe_rent moe rent
まず,.value
と variable
の組み合わせを全て作り,注意深く列名を決める7.
spec <- us_rent_income %>%
expand(variable, .value = c("estimate", "moe")) %>%
mutate(
.name = paste0(variable, ifelse(.value == "moe", "_moe", ""))
)
spec
#> # A tibble: 4 x 3
#> variable .value .name
#> <chr> <chr> <chr>
#> 1 income estimate income
#> 2 income moe income_moe
#> 3 rent estimate rent
#> 4 rent moe rent_moe
この仕様を pivot_wider()
に与えると,望み通りのデータフレームが得られる.
us_rent_income %>% pivot_wider(spec = spec)
#> # A tibble: 52 x 6
#> GEOID NAME income income_moe rent rent_moe
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 01 Alabama 24476 136 747 3
#> 2 02 Alaska 32940 508 1200 13
#> 3 04 Arizona 27517 148 972 4
#> 4 05 Arkansas 23789 165 709 5
#> 5 06 California 29454 109 1358 3
#> 6 08 Colorado 32401 109 1125 5
#> 7 09 Connecticut 35326 195 1123 5
#> 8 10 Delaware 31560 247 1076 10
#> 9 11 District of Columbia 43198 681 1424 17
#> 10 12 Florida 25952 70 1077 3
#> # … with 42 more rows
By hand
時には仕様を算出させるのが不可能な場合や不都合な場合があり,自前 (“by hand”) で仕様を定めた方が都合がいいこともある.construction
データを例に見てみよう.このデータは下記 URL の表5 “completions” を少し改造したものだ https://www.census.gov/construction/nrc/index.html.
construction
#> # A tibble: 9 x 9
#> Year Month `1 unit` `2 to 4 units` `5 units or mor… Northeast Midwest
#> <dbl> <chr> <dbl> <lgl> <dbl> <dbl> <dbl>
#> 1 2018 Janu… 859 NA 348 114 169
#> 2 2018 Febr… 882 NA 400 138 160
#> 3 2018 March 862 NA 356 150 154
#> 4 2018 April 797 NA 447 144 196
#> 5 2018 May 875 NA 364 90 169
#> 6 2018 June 867 NA 342 76 170
#> 7 2018 July 829 NA 360 108 183
#> 8 2018 Augu… 939 NA 286 90 205
#> 9 2018 Sept… 835 NA 304 117 175
#> # … with 2 more variables: South <dbl>, West <dbl>
このようなデータは政府機関が発行するものにありがちで,列名が色々な変数に属している.今回の場合は色々なユニットごと (1, 2–4, 5+) に要約した列と,国内の地方ごと (NE, NW, midwest, S, W) の要約した列がある.このようなデータを整形する際の仕様は tibble を使って簡単に記述できる.
spec <- tribble(
~.name, ~.value, ~units, ~region,
"1 unit", "n", "1", NA,
"2 to 4 units", "n", "2-4", NA,
"5 units or more", "n", "5+", NA,
"Northeast", "n", NA, "Northeast",
"Midwest", "n", NA, "Midwest",
"South", "n", NA, "South",
"West", "n", NA, "West",
)
この仕様を用いると,以下のような longer な形式になる.
construction %>% pivot_longer(spec = spec)
#> # A tibble: 63 x 5
#> Year Month units region n
#> <dbl> <chr> <chr> <chr> <dbl>
#> 1 2018 January 1 <NA> 859
#> 2 2018 January 2-4 <NA> NA
#> 3 2018 January 5+ <NA> 348
#> 4 2018 January <NA> Northeast 114
#> 5 2018 January <NA> Midwest 169
#> 6 2018 January <NA> South 596
#> 7 2018 January <NA> West 339
#> 8 2018 February 1 <NA> 882
#> 9 2018 February 2-4 <NA> NA
#> 10 2018 February 5+ <NA> 400
#> # … with 53 more rows
units
変数と region
変数には重複するとこがないことに注意されたい.今回の場合はデータを独立した二つの表に分けるのが最も自然だ.
理論
spec
の良いところは,pivot_longer()
と pivot_wider()
に同じものを与えられることだ.これにより,二つの操作の対称性が明確になる.
construction %>%
pivot_longer(spec = spec) %>%
pivot_wider(spec = spec)
#> # A tibble: 9 x 9
#> Year Month `1 unit` `2 to 4 units` `5 units or mor… Northeast Midwest
#> <dbl> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2018 Janu… 859 NA 348 114 169
#> 2 2018 Febr… 882 NA 400 138 160
#> 3 2018 March 862 NA 356 150 154
#> 4 2018 April 797 NA 447 144 196
#> 5 2018 May 875 NA 364 90 169
#> 6 2018 June 867 NA 342 76 170
#> 7 2018 July 829 NA 360 108 183
#> 8 2018 Augu… 939 NA 286 90 205
#> 9 2018 Sept… 835 NA 304 117 175
#> # … with 2 more variables: South <dbl>, West <dbl>
整形の仕様を定めることで,pivot_longer(df, spec = spec)
がどのように df
を整形するかより明瞭で厳密にできる.出力結果は nrow(df) * nrow(spec)
に相当する行数と, ncol(df) - nrow(spec) + ncol(spec) - 2
に相当する列数を持つ.
訳注:
tidyverse
を vignette で利用すると,最低でもSuggests
にtidyverse
を加えなければならない.しかしtidyverse
は多くのパッケージを纏めたメタパッケージなので,不要なパッケージにも依存することになり,パッケージがいたずらに肥大化するのでよくない. (参考: “The tidyverse is for EDA, not packages” https://www.tidyverse.org/articles/2018/06/tidyverse-not-for-packages/)↩︎訳注: ptype は prototype の略称.本来のタイプくらいの意味合いだろうか. @yutannihilation 氏によるスライドも参考にして欲しい (https://speakerdeck.com/yutannihilation/tidyr-pivot?slide=51).↩︎
訳注:
014
なら0歳から14歳,1524
なら15歳から24歳であることを示す.↩︎訳注: 直感的な見出しではないと思う.かと言って良い対案も思いついていない.暫定版は整形対象となる列の値に複数種の変数がある場合,くらいだろうか.↩︎
訳注: 原文は “you’d prefer to have the individual questions in the columns.” であるが,“questions” ではなく “answers” が正しい気がする.↩︎
訳注: 原文では元のデータフレームとまで書いていないが伝わらないと思ったので追記した.
pivot_wider_spec()
の場合は.name
列の値が出力のデータフレームの各列に対応するので注意.↩︎訳注:
expand %>% mutate
よりもpivot_wider_spec %>% mutate
の方が,行や列の順序がpivot_wider_spec
の結果と整合的で比べやすく,安全だと思う (#666).
↩︎us_rent_income %>% pivot_wider_spec(names_from = variable, values_from = c(estimate, moe)) %>% mutate(.name = paste0(variable, ifelse(.value == "moe", "_moe", ""))) #> # A tibble: 4 x 3 #> .name .value variable #> <chr> <chr> <chr> #> 1 income estimate income #> 2 rent estimate rent #> 3 income_moe moe income #> 4 rent_moe moe rent