Pivoting

イントロダクション

この vignette は、新しい関数 pivot_longer()pivot_wider() の使用法を説明する。その目的は。gather()spread() の使い勝手を向上させ、他のパッケージで見られるような最先端のフィーチャを取り入れることである。

以前から。spread()gather() のデザインには何か根本的な問題があることは明らかであった。多くの人がその名前を直感的に理解できず、どの方向が拡散に相当し、どの方向が収集に相当するかを覚えるのは困難である。また、これらの関数の引数を覚えるのも意外と大変なようで、多くの人が (私も含めて!) 毎回ドキュメントを参照しなければならないことになる。

Rでリシェイプを進めている他のRパッケージからヒントを得た重要な新機能が2つある。

この vignette では。pivot_longer()pivot_wider() を使って、単純なものから複雑なものまで、さまざまなデータ再形成の課題を解決する様子を見て、その鍵となる考え方を学んでいただく。

はじめに、必要なパッケージをいくつかロードする。実際の解析コードでは。library(tidyverse)、で行うのだろうが、この vignette はパッケージに組み込まれているので、ここではできない。

library(tidyr)
library(dplyr)
library(readr)

Longer

pivot_longer() は、行数を増やし列数を減らすことで、データセットを__longer__にする。データセットが「長い形」であると表現することに意味があるとは思えない。長さとは相対的な言葉であり、(例えば)データセットAはデータセットBよりも長いとしか言えないのである。

pivot_longer() は、野生で捕獲されたデータセットを整理するために一般的に必要とされるもので、分析の容易さよりもデータ入力の容易さや比較の容易さに最適化されていることが多いことがある。以下のセクションでは、現実的なデータセットに幅広く対応するために、pivot_longer() を使用する方法を紹介する。

列名には文字列データを使用する

relig_income データセットには、宗教と年収を尋ねたアンケートに基づくカウントが格納されている。

relig_income
#> # A tibble: 18 × 11
#>    religion      `<$10k` $10-2…¹ $20-3…² $30-4…³ $40-5…⁴ $50-7…⁵ $75-1…⁶ $100-…⁷
#>    <chr>           <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
#>  1 Agnostic           27      34      60      81      76     137     122     109
#>  2 Atheist            12      27      37      52      35      70      73      59
#>  3 Buddhist           27      21      30      34      33      58      62      39
#>  4 Catholic          418     617     732     670     638    1116     949     792
#>  5 Don’t know/…       15      14      15      11      10      35      21      17
#>  6 Evangelical …     575     869    1064     982     881    1486     949     723
#>  7 Hindu               1       9       7       9      11      34      47      48
#>  8 Historically…     228     244     236     238     197     223     131      81
#>  9 Jehovah's Wi…      20      27      24      24      21      30      15      11
#> 10 Jewish             19      19      25      25      30      95      69      87
#> # … with 8 more rows, 2 more variables: `>150k` <dbl>,
#> #   `Don't know/refused` <dbl>, and abbreviated variable names ¹​`$10-20k`,
#> #   ²​`$20-30k`, ³​`$30-40k`, ⁴​`$40-50k`, ⁵​`$50-75k`, ⁶​`$75-100k`, ⁷​`$100-150k`

このデータセットには3つの変数が含まれている。

これを整理するために。pivot_longer() を使用する。

relig_income %>% 
  pivot_longer(!religion, names_to = "income", values_to = "count")
#> # A tibble: 180 × 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

names_tovalues_to の両列は relig_income には存在しないので、引用符で囲まれた文字列として提供する。

列名における数値データ

billboard データセットは2000年の曲のビルボードランクを記録している。relig_income データと似たような形式だが、列名にエンコードされているデータは文字列ではなく本当に数字である。

billboard
#> # A tibble: 317 × 79
#>    artist track date.ent…¹   wk1   wk2   wk3   wk4   wk5   wk6   wk7   wk8   wk9
#>    <chr>  <chr> <date>     <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#>  1 2 Pac  Baby… 2000-02-26    87    82    72    77    87    94    99    NA    NA
#>  2 2Ge+h… The … 2000-09-02    91    87    92    NA    NA    NA    NA    NA    NA
#>  3 3 Doo… Kryp… 2000-04-08    81    70    68    67    66    57    54    53    51
#>  4 3 Doo… Loser 2000-10-21    76    76    72    69    67    65    55    59    62
#>  5 504 B… Wobb… 2000-04-15    57    34    25    17    17    31    36    49    53
#>  6 98^0   Give… 2000-08-19    51    39    34    26    26    19     2     2     3
#>  7 A*Tee… Danc… 2000-07-08    97    97    96    95   100    NA    NA    NA    NA
#>  8 Aaliy… I Do… 2000-01-29    84    62    51    41    38    35    35    38    38
#>  9 Aaliy… Try … 2000-03-18    59    53    38    28    21    18    16    14    12
#> 10 Adams… Open… 2000-08-26    76    76    74    69    68    67    61    58    57
#> # … with 307 more rows, 67 more variables: 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>, …

relig_income のデータセットと同じ基本仕様で始めることができる。ここでは、名前を week という変数に、値を rank という変数にしたい。また。values_drop_na を使って、欠損値に対応する行を削除する。すべての曲が76週すべてチャートにとどまるわけではないので、入力データの構造上、不必要な明示的な NA を作成せざるを得ない。

billboard %>% 
  pivot_longer(
    cols = starts_with("wk"), 
    names_to = "week", 
    values_to = "rank",
    values_drop_na = TRUE
  )
#> # A tibble: 5,307 × 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_prefixwk の接頭辞を除去し。names_transformweek を整数に変換する。

billboard %>% 
  pivot_longer(
    cols = starts_with("wk"), 
    names_to = "week", 
    names_prefix = "wk",
    names_transform = list(week = as.integer),
    values_to = "rank",
    values_drop_na = TRUE,
  )

あるいは、数値以外の成分を自動的に取り除く readr::parse_number() を使って、1つの引数でこれを行うこともできる。

billboard %>% 
  pivot_longer(
    cols = starts_with("wk"), 
    names_to = "week", 
    names_transform = list(week = readr::parse_number),
    values_to = "rank",
    values_drop_na = TRUE,
  )

列名には多くの変数が含まれる。

より困難な状況は、複数の変数を列名に詰め込んだ場合に発生する。例えば。who のデータセットを見てみよう。

who
#> # A tibble: 7,240 × 60
#>    country     iso2  iso3   year new_s…¹ new_s…² new_s…³ new_s…⁴ new_s…⁵ new_s…⁶
#>    <chr>       <chr> <chr> <int>   <int>   <int>   <int>   <int>   <int>   <int>
#>  1 Afghanistan AF    AFG    1980      NA      NA      NA      NA      NA      NA
#>  2 Afghanistan AF    AFG    1981      NA      NA      NA      NA      NA      NA
#>  3 Afghanistan AF    AFG    1982      NA      NA      NA      NA      NA      NA
#>  4 Afghanistan AF    AFG    1983      NA      NA      NA      NA      NA      NA
#>  5 Afghanistan AF    AFG    1984      NA      NA      NA      NA      NA      NA
#>  6 Afghanistan AF    AFG    1985      NA      NA      NA      NA      NA      NA
#>  7 Afghanistan AF    AFG    1986      NA      NA      NA      NA      NA      NA
#>  8 Afghanistan AF    AFG    1987      NA      NA      NA      NA      NA      NA
#>  9 Afghanistan AF    AFG    1988      NA      NA      NA      NA      NA      NA
#> 10 Afghanistan AF    AFG    1989      NA      NA      NA      NA      NA      NA
#> # … with 7,230 more rows, 50 more variables: 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>, …

country , iso2 , iso3 , year はすでに変数なので、そのままでもよい。しかし。new_sp_m014 から newrel_f65 までの列は、その名前に4つの変数をエンコードしている。

names_to で複数の列名を指定し。names_sep または names_pattern を提供することで、これらの変数を分割することができる。ここでは names_pattern が最も自然に適合している。これは extract と同様のインターフェイスを持っている。 グループを含む正規表現 ( () で定義) を与えると、各グループを1つの列に配置する。

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 × 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

さらに一歩進んで、性別と年齢を因数に変換するためにreadr関数を使用することもできる。これは、既知の値のセットを持つカテゴリ変数がある場合の良い練習になると思われる。

who %>% pivot_longer(
  cols = new_sp_m014:newrel_f65,
  names_to = c("diagnosis", "gender", "age"), 
  names_pattern = "new_?(.*)_(.)(.*)",
  names_transform = list(
    gender = ~ readr::parse_factor(.x, levels = c("f", "m")),
    age = ~ readr::parse_factor(
      .x,
      levels = c("014", "1524", "2534", "3544", "4554", "5564", "65"), 
      ordered = TRUE
    )
  ),
  values_to = "count",
)

1行に複数の観測値

これまで、1行に1つのオブザベーションを持つデータフレームを扱ってきましたが、重要なピボット問題の多くには、1行に複数のオブザベーションが含まれる。このような場合、出力に表示したい列の名前が入力の列名の一部であるため、通常は認識することができる。このセクションでは、このようなデータをピボットする方法について学ぶ。

以下の例は、tidyrがこの問題を解決するためのインスピレーションとして、 data.table vignette から引用したものである。

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 × 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

各子供について。genderdob (生年月日) という 2 つの情報 (または値) があることに注意。これらは、結果の中の別々の列に入れる必要がある。ここでも。names_to に複数の変数を与え。names_sep を使って各変数名を分割している。.value という特別な名前に注意。これは、列名のその部分が測定される「値」(出力では変数になる)を指定することを pivot_longer() に伝える。

family %>% 
  pivot_longer(
    !family, 
    names_to = c(".value", "child"), 
    names_sep = "_", 
    values_drop_na = TRUE
  )
#> # A tibble: 9 × 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 の使用に注意 : 入力形状は、存在しないオブザベーションのために明示的に欠損変数を作成することを強制する。

この問題は、ベース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

このデータセットには4組の変数( x1 and y1 , x2 and y2 , etc)が含まれており、Anscombeのカルテット(要約統計量(平均、sd、相関など)は同じだが、データは全く異なる4つのデータセットの集合)の根底をなすものである。私たちは。setxy の列を持つデータセットを作りたい。

anscombe %>% 
  pivot_longer(everything(), 
    names_to = c(".value", "set"), 
    names_pattern = "(.)(.)"
  ) %>% 
  arrange(set)
#> # A tibble: 44 × 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 × 6
#>       x     a     b time       y     z
#>   <int> <dbl> <dbl> <chr>  <dbl> <dbl>
#> 1     1     1     0 1      1.52      3
#> 2     1     1     0 2     -1.37     -2
#> 3     2     1     1 1     -0.224     3
#> 4     2     1     1 2     -0.910    -2
#> 5     3     0     1 1     -2.03      3
#> 6     3     0     1 2      0.451    -2
#> 7     4     0     1 1      1.47      3
#> 8     4     0     1 2      0.942    -2

重複する列名

時々、列名が重複しているデータセットを見かけることがある。一般に、このようなデータセットはRで扱うのが難しい。なぜなら、ある列を名前で参照したときに、最初にマッチしたものしか見つけられないからである。名前が重複している tibble を作成するには、名前の修復を明示的に拒否する必要がある。

df <- tibble(id = 1:3, y = 4:6, y = 5:7, y = 7:9, .name_repair = "minimal")
df
#> # A tibble: 3 × 4
#>      id     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(!id, names_to = "name", values_to = "value")
#> # A tibble: 9 × 3
#>      id name  value
#>   <int> <chr> <int>
#> 1     1 y         4
#> 2     1 y         5
#> 3     1 y         7
#> 4     2 y         5
#> 5     2 y         6
#> 6     2 y         8
#> 7     3 y         6
#> 8     3 y         7
#> 9     3 y         9

次の例のように、複数の入力列が同じ出力列にマップされる場合にも、同様の処理が行われる。ここでは、各列名の数字のサフィックスを無視する。

df <- tibble(id = 1:3, x1 = 4:6, x2 = 5:7, y1 = 7:9, y2 = 10:12)
df %>% pivot_longer(!id, names_to = ".value", names_pattern = "(.).")
#> # A tibble: 6 × 3
#>      id     x     y
#>   <int> <int> <int>
#> 1     1     4     7
#> 2     1     5    10
#> 3     2     5     8
#> 4     2     6    11
#> 5     3     6     9
#> 6     3     7    12

より広く

pivot_wider() は の逆で、列数を増やし行数を減らすことでデータセットをより広く__するものである。 を使って整頓されたデータを作成することは比較的稀であるが、プレゼンテーション用の要約表や、他のツールが必要とする形式のデータを作成する際にはよく役に立つ。 pivot_longer() pivot_wider() ### 捕獲-再捕獲データ

Myfanwy Johnston が提供する fish_encounters データセットは、川を泳ぐ魚が自動監視局で検出されるタイミングを記述したものである。

fish_encounters
#> # A tibble: 114 × 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 × 12
#>    fish  Release I80_1 Lisbon  Rstr Base_TD   BCE   BCW  BCE2  BCW2   MAE   MAW
#>    <fct>   <int> <int>  <int> <int>   <int> <int> <int> <int> <int> <int> <int>
#>  1 4842        1     1      1     1       1     1     1     1     1     1     1
#>  2 4843        1     1      1     1       1     1     1     1     1     1     1
#>  3 4844        1     1      1     1       1     1     1     1     1     1     1
#>  4 4845        1     1      1     1       1    NA    NA    NA    NA    NA    NA
#>  5 4847        1     1      1    NA      NA    NA    NA    NA    NA    NA    NA
#>  6 4848        1     1      1     1      NA    NA    NA    NA    NA    NA    NA
#>  7 4849        1     1     NA    NA      NA    NA    NA    NA    NA    NA    NA
#>  8 4850        1     1     NA     1       1     1     1    NA    NA    NA    NA
#>  9 4851        1     1     NA    NA      NA    NA    NA    NA    NA    NA    NA
#> 10 4854        1     1     NA    NA      NA    NA    NA    NA    NA    NA    NA
#> # … with 9 more rows

このデータセットでは、魚がステーションで検出されたときのみ記録され、検出されなかったときは記録されていない(これはこの種のデータではよくあることである)。つまり、出力データは NA s で埋め尽くされる。しかし、この場合、記録がないということは、魚が seen されなかったということだとわかるので、これらの欠損値をゼロで埋めるように pivot_wider() に依頼することができる。

fish_encounters %>% pivot_wider(
  names_from = station, 
  values_from = seen,
  values_fill = 0
)
#> # A tibble: 19 × 12
#>    fish  Release I80_1 Lisbon  Rstr Base_TD   BCE   BCW  BCE2  BCW2   MAE   MAW
#>    <fct>   <int> <int>  <int> <int>   <int> <int> <int> <int> <int> <int> <int>
#>  1 4842        1     1      1     1       1     1     1     1     1     1     1
#>  2 4843        1     1      1     1       1     1     1     1     1     1     1
#>  3 4844        1     1      1     1       1     1     1     1     1     1     1
#>  4 4845        1     1      1     1       1     0     0     0     0     0     0
#>  5 4847        1     1      1     0       0     0     0     0     0     0     0
#>  6 4848        1     1      1     1       0     0     0     0     0     0     0
#>  7 4849        1     1      0     0       0     0     0     0     0     0     0
#>  8 4850        1     1      0     1       1     1     1     0     0     0     0
#>  9 4851        1     1      0     0       0     0     0     0     0     0     0
#> 10 4854        1     1      0     0       0     0     0     0     0     0     0
#> # … with 9 more rows

アグリゲーション

また。pivot_wider() を使って、簡単な集計を行うこともできる。例えば、ベースRに組み込まれた warpbreaks データセット(印刷方法を良くするためにtibbleに変換されている)を見てみよう。

warpbreaks <- warpbreaks %>% as_tibble() %>% select(wool, tension, breaks)
warpbreaks
#> # A tibble: 54 × 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 ( AB ) と tension ( L , M , H ) の組み合わせごとに、9回の複製を行う設計実験である。

warpbreaks %>% count(wool, tension)
#> # A tibble: 6 × 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 のレベルを列にピボットしようとすると、どうなるだろうか。

warpbreaks %>% pivot_wider(names_from = wool, values_from = breaks)
#> Warning: Values from `breaks` are not uniquely identified; output will contain list-cols.
#> * Use `values_fn = list` to suppress this warning.
#> * Use `values_fn = {summary_fun}` to summarise duplicates.
#> * Use the following dplyr code to identify duplicates.
#>   {data} %>%
#>     dplyr::group_by(tension, wool) %>%
#>     dplyr::summarise(n = dplyr::n(), .groups = "drop") %>%
#>     dplyr::filter(n > 1L)
#> # A tibble: 3 × 3
#>   tension A         B        
#>   <fct>   <list>    <list>   
#> 1 L       <dbl [9]> <dbl [9]>
#> 2 M       <dbl [9]> <dbl [9]>
#> 3 H       <dbl [9]> <dbl [9]>

出力の各セルが入力の複数のセルに対応しているという警告が表示される。デフォルトの動作は、すべての個々の値を含むリスト列を生成する。より有用な出力は、要約統計、例えば、ウールとテンションの組み合わせごとの mean breakだろう。

warpbreaks %>% 
  pivot_wider(
    names_from = wool, 
    values_from = breaks,
    values_fn = list(breaks = mean)
  )
#> # A tibble: 3 × 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() の中で要約するのが便利なことがよくある。

複数の変数から列名を生成する

<https://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 × 4
#>    product country  year production
#>    <chr>   <chr>   <int>      <dbl>
#>  1 A       AI       2000      0.958
#>  2 A       AI       2001      1.29 
#>  3 A       AI       2002      0.117
#>  4 A       AI       2003     -0.755
#>  5 A       AI       2004      0.608
#>  6 A       AI       2005      0.532
#>  7 A       AI       2006      0.168
#>  8 A       AI       2007     -0.629
#>  9 A       AI       2008      0.908
#> 10 A       AI       2009      0.790
#> # … with 35 more rows

productcountry の組み合わせごとに1列ずつになるように、データを広げたい。重要なのは。names_from に複数の変数を指定することである。

production %>% pivot_wider(
  names_from = c(product, country), 
  values_from = production
)
#> # A tibble: 15 × 4
#>     year   A_AI    B_AI   B_EI
#>    <int>  <dbl>   <dbl>  <dbl>
#>  1  2000  0.958 -0.733   0.560
#>  2  2001  1.29  -1.67   -0.164
#>  3  2002  0.117  1.25    0.989
#>  4  2003 -0.755  2.48   -1.38 
#>  5  2004  0.608 -0.0895 -1.02 
#>  6  2005  0.532  1.42   -0.838
#>  7  2006  0.168  0.389   2.60 
#>  8  2007 -0.629 -0.208   0.945
#>  9  2008  0.908 -0.888   1.37 
#> 10  2009  0.790 -0.834   0.872
#> # … with 5 more rows

names_from または values_from が複数の変数を選択する場合。names_sepnames_prefix、または主力製品である names_glue で、出力で構築される列名を制御することができる。

production %>% pivot_wider(
  names_from = c(product, country), 
  values_from = production,
  names_sep = ".",
  names_prefix = "prod."
)
#> # A tibble: 15 × 4
#>     year prod.A.AI prod.B.AI prod.B.EI
#>    <int>     <dbl>     <dbl>     <dbl>
#>  1  2000     0.958   -0.733      0.560
#>  2  2001     1.29    -1.67      -0.164
#>  3  2002     0.117    1.25       0.989
#>  4  2003    -0.755    2.48      -1.38 
#>  5  2004     0.608   -0.0895    -1.02 
#>  6  2005     0.532    1.42      -0.838
#>  7  2006     0.168    0.389      2.60 
#>  8  2007    -0.629   -0.208      0.945
#>  9  2008     0.908   -0.888      1.37 
#> 10  2009     0.790   -0.834      0.872
#> # … with 5 more rows

production %>% pivot_wider(
  names_from = c(product, country), 
  values_from = production,
  names_glue = "prod_{product}_{country}"
)
#> # A tibble: 15 × 4
#>     year prod_A_AI prod_B_AI prod_B_EI
#>    <int>     <dbl>     <dbl>     <dbl>
#>  1  2000     0.958   -0.733      0.560
#>  2  2001     1.29    -1.67      -0.164
#>  3  2002     0.117    1.25       0.989
#>  4  2003    -0.755    2.48      -1.38 
#>  5  2004     0.608   -0.0895    -1.02 
#>  6  2005     0.532    1.42      -0.838
#>  7  2006     0.168    0.389      2.60 
#>  8  2007    -0.629   -0.208      0.945
#>  9  2008     0.908   -0.888      1.37 
#> 10  2009     0.790   -0.834      0.872
#> # … with 5 more rows

整頓された国勢調査

us_rent_income データセットは、2017年のアメリカの各州の所得と家賃の中央値の情報を含んでいる(American Community Surveyより、 tidycensus tidycensus パッケージで取得)。

us_rent_income
#> # A tibble: 104 × 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

ここでは。estimatemoe の両方が値列なので。values_from に供給することができる。

us_rent_income %>% 
  pivot_wider(names_from = variable, values_from = c(estimate, moe))
#> # A tibble: 52 × 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 Columbia           43198          1424        681       17
#> 10 12    Florida                        25952          1077         70        3
#> # … with 42 more rows

なお、出力列には自動的に変数名が付加される。

暗黙の欠損値

時折、名前変数が因子としてエンコードされているデータに出会うことがあるが、すべてのデータが表現されるわけではない。

weekdays <- c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")

daily <- tibble(
  day = factor(c("Tue", "Thu", "Fri", "Mon"), levels = weekdays),
  value = c(2, 3, 1, 5)
)

daily
#> # A tibble: 4 × 2
#>   day   value
#>   <fct> <dbl>
#> 1 Tue       2
#> 2 Thu       3
#> 3 Fri       1
#> 4 Mon       5

pivot_wider() のデフォルトでは、実際にデータで表される値から列を生成するが、将来的にデータが変更された場合に備えて、可能性のある各レベルの列を含めるようにするとよいだろう。

pivot_wider(daily, names_from = day, values_from = value)
#> # A tibble: 1 × 4
#>     Tue   Thu   Fri   Mon
#>   <dbl> <dbl> <dbl> <dbl>
#> 1     2     3     1     5

names_expand 引数は、暗黙的な因子レベルを明示的なものに変え、結果で表現することを強制する。また、レベルの順序を使用して列名をソートし、この場合、より直感的な結果を生成する。

pivot_wider(daily, names_from = day, values_from = value, names_expand = TRUE)
#> # A tibble: 1 × 7
#>     Mon   Tue   Wed   Thu   Fri   Sat   Sun
#>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1     5     2    NA     3     1    NA    NA

複数の列 ( names_from ) が指定された場合。names_expand は。names_from の値のすべての可能な組み合わせのデカルト積を生成する。以下のデータでは、パーセント値が 0 となる行がいくつか省略されていることに注意。names_expand を使用すると、ピボット時にこれらを明示すことができる。

percentages <- tibble(
  year = c(2018, 2019, 2020, 2020),
  type = factor(c("A", "B", "A", "B"), levels = c("A", "B")),
  percentage = c(100, 100, 40, 60)
)

percentages
#> # A tibble: 4 × 3
#>    year type  percentage
#>   <dbl> <fct>      <dbl>
#> 1  2018 A            100
#> 2  2019 B            100
#> 3  2020 A             40
#> 4  2020 B             60

pivot_wider(
  percentages,
  names_from = c(year, type),
  values_from = percentage,
  names_expand = TRUE,
  values_fill = 0
)
#> # A tibble: 1 × 6
#>   `2018_A` `2018_B` `2019_A` `2019_B` `2020_A` `2020_B`
#>      <dbl>    <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
#> 1      100        0        0      100       40       60

id_cols この場合、明示的に表現したい行 (列ではなく) が欠落していることになる。この例では。daily データに type 列を追加し、その代わりに day を ID 列としてピボットすることにする。

daily <- mutate(daily, type = factor(c("A", "B", "B", "A")))
daily
#> # A tibble: 4 × 3
#>   day   value type 
#>   <fct> <dbl> <fct>
#> 1 Tue       2 A    
#> 2 Thu       3 B    
#> 3 Fri       1 B    
#> 4 Mon       5 A

type のレベルはすべて列で表現されているが、表現されていない day の因子レベルに関連するいくつかの行が欠けている。

pivot_wider(
  daily, 
  names_from = type, 
  values_from = value,
  values_fill = 0
)
#> # A tibble: 4 × 3
#>   day       A     B
#>   <fct> <dbl> <dbl>
#> 1 Tue       2     0
#> 2 Thu       0     3
#> 3 Fri       0     1
#> 4 Mon       5     0

id_expandnames_expand と同じように使えば。id_cols の暗黙の欠落行を展開(ソート)することができる。

pivot_wider(
  daily, 
  names_from = type, 
  values_from = value,
  values_fill = 0,
  id_expand = TRUE
)
#> # A tibble: 7 × 3
#>   day       A     B
#>   <fct> <dbl> <dbl>
#> 1 Mon       5     0
#> 2 Tue       2     0
#> 3 Wed       0     0
#> 4 Thu       0     3
#> 5 Fri       0     1
#> 6 Sat       0     0
#> 7 Sun       0     0

未使用の列

ピボット処理とはまったく関係のない列がデータ内に存在する場合、その列の情報を何らかの方法で保持したいと想像してみよう。たとえば。updates で。system 列をピボットして、各郡のシステム更新の 1 行サマリーを作成したいとする。

updates <- tibble(
  county = c("Wake", "Wake", "Wake", "Guilford", "Guilford"),
  date = c(as.Date("2020-01-01") + 0:2, as.Date("2020-01-03") + 0:1),
  system = c("A", "B", "C", "A", "C"),
  value = c(3.2, 4, 5.5, 2, 1.2)
)

updates
#> # A tibble: 5 × 4
#>   county   date       system value
#>   <chr>    <date>     <chr>  <dbl>
#> 1 Wake     2020-01-01 A        3.2
#> 2 Wake     2020-01-02 B        4  
#> 3 Wake     2020-01-03 C        5.5
#> 4 Guilford 2020-01-03 A        2  
#> 5 Guilford 2020-01-04 C        1.2

一般的な pivot_wider() の呼び出しでも可能であるが。date の列に関するすべての情報を完全に失いる。

pivot_wider(
  updates, 
  id_cols = county, 
  names_from = system, 
  values_from = value
)
#> # A tibble: 2 × 4
#>   county       A     B     C
#>   <chr>    <dbl> <dbl> <dbl>
#> 1 Wake       3.2     4   5.5
#> 2 Guilford   2      NA   1.2

この例では、特定の郡のすべてのシステムで、最新の更新日を保持したいと思われる。これを実現するには。unused_fn 引数を使用する。これにより、ピボット処理で使用されない列の値を要約することができる。

pivot_wider(
  updates, 
  id_cols = county, 
  names_from = system, 
  values_from = value,
  unused_fn = list(date = max)
)
#> # A tibble: 2 × 5
#>   county       A     B     C date      
#>   <chr>    <dbl> <dbl> <dbl> <date>    
#> 1 Wake       3.2     4   5.5 2020-01-03
#> 2 Guilford   2      NA   1.2 2020-01-04

また。list() を要約関数として使用することで、データは保持しつつ、集計を完全に遅らせることができる。

pivot_wider(
  updates, 
  id_cols = county, 
  names_from = system, 
  values_from = value,
  unused_fn = list(date = list)
)
#> # A tibble: 2 × 5
#>   county       A     B     C date      
#>   <chr>    <dbl> <dbl> <dbl> <list>    
#> 1 Wake       3.2     4   5.5 <date [3]>
#> 2 Guilford   2      NA   1.2 <date [2]>

コンタクトリスト

最後の課題は、 Jiena Guに触発されたものである。ウェブサイトからコピー&ペーストした連絡先リストがあると想像してみよう。

contacts <- tribble(
  ~field, ~value,
  "name", "Jiena McLellan",
  "company", "Toyota", 
  "name", "John Smith", 
  "company", "google", 
  "email", "john@google.com",
  "name", "Huxley Ratcliffe"
)

これは、どのオブザベーションが一緒になっているかを識別する変数がないので、困難である。すべてのコンタクトは名前で始まるので。field として “name”を見るたびにカウントすることによってユニークなIDを作成することによってこれを修正することがでく :

contacts <- contacts %>% 
  mutate(
    person_id = cumsum(field == "name")
  )
contacts
#> # A tibble: 6 × 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   john@google.com          2
#> 6 name    Huxley Ratcliffe         3

各人に一意の識別子があるので。fieldvalue を列にピボットすることができる。

contacts %>% 
  pivot_wider(names_from = field, values_from = value)
#> # A tibble: 3 × 4
#>   person_id name             company email          
#>       <int> <chr>            <chr>   <chr>          
#> 1         1 Jiena McLellan   Toyota  <NA>           
#> 2         2 John Smith       google  john@google.com
#> 3         3 Huxley Ratcliffe <NA>    <NA>

Longer, then wider

問題によっては、単一方向のピボットでは解決できないものもある。このセクションの例では。pivot_longer()pivot_wider() を組み合わせて、より複雑な問題を解決する方法を紹介する。

世界銀行

world_bank_pop は、2000年から2018年までの国別人口について世界銀行のデータを収録している。

world_bank_pop
#> # A tibble: 1,056 × 20
#>    country indic…¹ `2000` `2001` `2002` `2003`  `2004`  `2005`   `2006`   `2007`
#>    <chr>   <chr>    <dbl>  <dbl>  <dbl>  <dbl>   <dbl>   <dbl>    <dbl>    <dbl>
#>  1 ABW     SP.URB… 4.24e4 4.30e4 4.37e4 4.42e4 4.47e+4 4.49e+4  4.49e+4  4.47e+4
#>  2 ABW     SP.URB… 1.18e0 1.41e0 1.43e0 1.31e0 9.51e-1 4.91e-1 -1.78e-2 -4.35e-1
#>  3 ABW     SP.POP… 9.09e4 9.29e4 9.50e4 9.70e4 9.87e+4 1.00e+5  1.01e+5  1.01e+5
#>  4 ABW     SP.POP… 2.06e0 2.23e0 2.23e0 2.11e0 1.76e+0 1.30e+0  7.98e-1  3.84e-1
#>  5 AFG     SP.URB… 4.44e6 4.65e6 4.89e6 5.16e6 5.43e+6 5.69e+6  5.93e+6  6.15e+6
#>  6 AFG     SP.URB… 3.91e0 4.66e0 5.13e0 5.23e0 5.12e+0 4.77e+0  4.12e+0  3.65e+0
#>  7 AFG     SP.POP… 2.01e7 2.10e7 2.20e7 2.31e7 2.41e+7 2.51e+7  2.59e+7  2.66e+7
#>  8 AFG     SP.POP… 3.49e0 4.25e0 4.72e0 4.82e0 4.47e+0 3.87e+0  3.23e+0  2.76e+0
#>  9 AGO     SP.URB… 8.23e6 8.71e6 9.22e6 9.77e6 1.03e+7 1.09e+7  1.15e+7  1.21e+7
#> 10 AGO     SP.URB… 5.44e0 5.59e0 5.70e0 5.76e0 5.75e+0 5.69e+0  4.92e+0  4.89e+0
#> # … with 1,046 more rows, 10 more variables: `2008` <dbl>, `2009` <dbl>,
#> #   `2010` <dbl>, `2011` <dbl>, `2012` <dbl>, `2013` <dbl>, `2014` <dbl>,
#> #   `2015` <dbl>, `2016` <dbl>, `2017` <dbl>, and abbreviated variable name
#> #   ¹​indicator

私の目標は、各変数が1列になっている整頓されたデータセットを作成することである。どのような手順が必要かはまだ明らかではないが、最も明白な問題から始めよう:yearが複数の列にまたがっている。

pop2 <- world_bank_pop %>% 
  pivot_longer(`2000`:`2017`, names_to = "year", values_to = "value")
pop2
#> # A tibble: 19,008 × 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 × 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 (総人口または都市部)と実際の変数(人口または成長率)の2つに分けて考えてみよう。

pop3 <- pop2 %>% 
  separate(indicator, c(NA, "area", "variable"))
pop3
#> # A tibble: 19,008 × 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

あとは。variablevalue をピボットして。TOTLGROW の列を作れば、整理整頓は完了である。

pop3 %>% 
  pivot_wider(names_from = variable, values_from = value)
#> # A tibble: 9,504 × 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
)

しかし、実際の順序は重要ではなく、個々の質問を列で表示することを好むだろう。2つのステップで目的の変換を行うことができる。まず、データを長くして、明示的な NA sを削除し、この選択肢が選ばれたことを示す列を追加する。

multi2 <- multi %>% 
  pivot_longer(!id, values_drop_na = TRUE) %>% 
  mutate(checked = TRUE)
multi2
#> # A tibble: 8 × 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

そして、データを広くして、欠落している観測値を FALSE で埋める。

multi2 %>% 
  pivot_wider(
    id_cols = id,
    names_from = value, 
    values_from = checked, 
    values_fill = FALSE
  )
#> # A tibble: 4 × 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

マニュアル仕様

pivot_longer()pivot_wider() の引数を使うと、さまざまなデータセットをピボット化することができる。しかし、データ構造に適用される創造性は無限であるため。pivot_longer()pivot_wider() を使用してどのように再構築するかすぐに分からないデータセットに遭遇する可能性は十分にある。ピボットの制御を強化するには、代わりに「spec」データフレームを作成して、列名に格納されたデータがどのように変数になるか(またはその逆)を正確に記述することができる。このセクションでは、spec データ構造を紹介し。pivot_longer()pivot_wider() では不十分な場合に使用する方法を説明する。

Longer

この仕組みを理解するために。relig_income のデータセットにピボットを適用する最も単純なケースに戻りよう。まず spec オブジェクトを作成し ( build_longer_spec() を使用)、そのオブジェクトでピボット操作を記述する。

spec <- relig_income %>% build_longer_spec(
  cols = !religion, 
  names_to = "income",
  values_to = "count"
)
pivot_longer_spec(relig_income, spec)
#> # A tibble: 180 × 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 はどのようなものか? これは、ワイドフォーマット版のデータでロングフォーマットには存在しない各列に1行ずつと。. で始まる2つの特別な列を持つデータフレームである。

また。spec には、データのロングフォーマットに存在し、データのワイドフォーマットには存在しない列がそれぞれ1つずつある。これは。pivot_longer()build_longer_spec()names_to 引数。pivot_wider()build_wider_spec()names_from 引数に相当する。 この例では、所得列はピボットされる列の名前を文字ベクトルで表したものである。

spec
#> # A tibble: 10 × 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_incomepivot_wider() で広げている。結果はオーケーであるが、もっと改善できると思われる。

us_rent_income %>% 
  pivot_wider(names_from = variable, values_from = c(estimate, moe))
#> # A tibble: 52 × 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 Columbia           43198          1424        681       17
#> 10 12    Florida                        25952          1077         70        3
#> # … with 42 more rows

income , rent , income_moe , rent_moe という列があった方がいいと思うのであるが、これは手動仕様で実現できる。現在の仕様はこのような感じである。

spec1 <- us_rent_income %>% 
  build_wider_spec(names_from = variable, values_from = c(estimate, moe))
spec1
#> # A tibble: 4 × 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

この場合。spec を変異させ、列名を慎重に構築する。

spec2 <- spec1 %>%
  mutate(.name = paste0(variable, ifelse(.value == "moe", "_moe", "")))
spec2
#> # A tibble: 4 × 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

この仕様を pivot_wider() に供給することで、求めている結果が得られる。

pivot_wider_spec(us_rent_income, spec2)
#> # A tibble: 52 × 6
#>    GEOID NAME                 income  rent income_moe rent_moe
#>    <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 Columbia  43198  1424        681       17
#> 10 12    Florida               25952  1077         70        3
#> # … with 42 more rows

手作業で

スペックを計算できない(あるいは便利でない)場合もあり、その場合は「手書き」でスペックを作成した方が便利である。 “. For example, take this construction data, which is lightly modified from Table 5” <https://www.census.gov/construction/nrc/index.html>; にある “completions” を参照してみよう。

construction
#> # A tibble: 9 × 9
#>    Year Month     `1 unit` `2 to 4 units` 5 units …¹ North…² Midwest South  West
#>   <dbl> <chr>        <dbl> <lgl>               <dbl>   <dbl>   <dbl> <dbl> <dbl>
#> 1  2018 January        859 NA                    348     114     169   596   339
#> 2  2018 February       882 NA                    400     138     160   655   336
#> 3  2018 March          862 NA                    356     150     154   595   330
#> 4  2018 April          797 NA                    447     144     196   613   304
#> 5  2018 May            875 NA                    364      90     169   673   319
#> 6  2018 June           867 NA                    342      76     170   610   360
#> 7  2018 July           829 NA                    360     108     183   594   310
#> 8  2018 August         939 NA                    286      90     205   649   286
#> 9  2018 September      835 NA                    304     117     175   560   296
#> # … with abbreviated variable names ¹​`5 units or more`, ²​Northeast

このようなデータは政府機関では珍しくない。列名は実際には異なる変数に属しており、ここではユニット数(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",      
)

となり、次のような長い形式が得られる。

pivot_longer_spec(construction, spec)
#> # A tibble: 63 × 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

unitsregion の変数には重複がないことに注意。この場合、データは2つの独立した表で記述されるのが最も自然であろう。

理論編

spec の素晴らしい特性のひとつは。pivot_longer()pivot_wider() で同じ仕様が必要なことである。これにより、2つの操作が対称的であることが非常に明確になる。

construction %>% 
  pivot_longer_spec(spec) %>% 
  pivot_wider_spec(spec)
#> # A tibble: 9 × 9
#>    Year Month     `1 unit` `2 to 4 units` 5 units …¹ North…² Midwest South  West
#>   <dbl> <chr>        <dbl>          <dbl>      <dbl>   <dbl>   <dbl> <dbl> <dbl>
#> 1  2018 January        859             NA        348     114     169   596   339
#> 2  2018 February       882             NA        400     138     160   655   336
#> 3  2018 March          862             NA        356     150     154   595   330
#> 4  2018 April          797             NA        447     144     196   613   304
#> 5  2018 May            875             NA        364      90     169   673   319
#> 6  2018 June           867             NA        342      76     170   610   360
#> 7  2018 July           829             NA        360     108     183   594   310
#> 8  2018 August         939             NA        286      90     205   649   286
#> 9  2018 September      835             NA        304     117     175   560   296
#> # … with abbreviated variable names ¹​`5 units or more`, ²​Northeast

ピボット仕様では。pivot_longer(df, spec = spec)df の形状をどのように変化させるかをより正確に把握することができる。nrow(df) * nrow(spec) の行と ncol(df) - nrow(spec) + ncol(spec) - 2 の列を持つことになる。