UPDATE: 2022-10-23 11:42:28

はじめに

クロス集計の頻度からデータフレームを復元するメモ書き。データフレームからクロス集計をするから、復元するも何も、何だそれと思うかもしれないが、クロス集計された状態のデータがスタートで、そこからもとのデータフレームを得たい場合がある。集計されたものをIDレベルで他の列の情報も復元するというのは、不可能だけど、単純に戻すだけならなんとかなる。

サンプルデータ

ここではMASSパッケージのcaithデータを利用する。このデータは4行5列のクロス集計されたデータで、スコットランドのケイスネスに住む人々の目と髪の色のデータ。

library(MASS)
library(tidyverse)

caith
##        fair red medium dark black
## blue    326  38    241  110     3
## light   688 116    584  188     4
## medium  343  84    909  412    26
## dark     98  48    403  681    85

sliceを使う方法

クロス集計の表側、表頭の値の組み合わせを持つデータフレームを作成し、クロス集計の値分、slice()で行を増幅させる。ここでは、freqとかnumとかを付け加えているが、そこはどうでもいいので、そのままにしておく。

eye <- colnames(caith)
hair <- rownames(caith)
df <- tibble(expand_grid(eye, hair), freq = unlist(caith))

df %>%
  slice(rep(1:n(), times = freq)) %>%
  mutate(num = 1)
## # A tibble: 5,387 × 4
##    eye   hair   freq   num
##    <chr> <chr> <int> <dbl>
##  1 fair  blue    326     1
##  2 fair  blue    326     1
##  3 fair  blue    326     1
##  4 fair  blue    326     1
##  5 fair  blue    326     1
##  6 fair  blue    326     1
##  7 fair  blue    326     1
##  8 fair  blue    326     1
##  9 fair  blue    326     1
## 10 fair  blue    326     1
## # … with 5,377 more rows
## # ℹ Use `print(n = ...)` to see more rows

map()&unnest()を使う方法

データフレームの中にリストで必要な行数分データを用意し、それを展開する方法。map()でなくてもlappy()でも同じことはできる。

eye <- colnames(caith)
hair <- rownames(caith)
df <- tibble(expand_grid(eye, hair), freq = unlist(caith))
df %>%
  # lapply(df$freq, function(x){(rep(1, x))})
  mutate(num = map(.x = freq, .f = function(x){(rep(1, x))})) %>%
  unnest(num, .drop = FALSE)
## # A tibble: 5,387 × 4
##    eye   hair   freq   num
##    <chr> <chr> <int> <dbl>
##  1 fair  blue    326     1
##  2 fair  blue    326     1
##  3 fair  blue    326     1
##  4 fair  blue    326     1
##  5 fair  blue    326     1
##  6 fair  blue    326     1
##  7 fair  blue    326     1
##  8 fair  blue    326     1
##  9 fair  blue    326     1
## 10 fair  blue    326     1
## # … with 5,377 more rows
## # ℹ Use `print(n = ...)` to see more rows

map()の部分で実行すると、こんな感じになっている。

df %>%
  mutate(num = map(.x = freq, .f = function(x){(rep(1, x))}))
## # A tibble: 20 × 4
##    eye    hair    freq num         
##    <chr>  <chr>  <int> <named list>
##  1 fair   blue     326 <dbl [326]> 
##  2 fair   light    688 <dbl [688]> 
##  3 fair   medium   343 <dbl [343]> 
##  4 fair   dark      98 <dbl [98]>  
##  5 red    blue      38 <dbl [38]>  
##  6 red    light    116 <dbl [116]> 
##  7 red    medium    84 <dbl [84]>  
##  8 red    dark      48 <dbl [48]>  
##  9 medium blue     241 <dbl [241]> 
## 10 medium light    584 <dbl [584]> 
## 11 medium medium   909 <dbl [909]> 
## 12 medium dark     403 <dbl [403]> 
## 13 dark   blue     110 <dbl [110]> 
## 14 dark   light    188 <dbl [188]> 
## 15 dark   medium   412 <dbl [412]> 
## 16 dark   dark     681 <dbl [681]> 
## 17 black  blue       3 <dbl [3]>   
## 18 black  light      4 <dbl [4]>   
## 19 black  medium    26 <dbl [26]>  
## 20 black  dark      85 <dbl [85]>

for-loopで愚直に行く方法

最近はよく感じるのが、便利なパッケージがこの世からなくなったら、分析できなくなるなぁ〜、ということ。なので、プログラムの3つの制御構造である「順次」「繰り返し」「分岐」をもっとうまく使えるようにならないといけないので、for-loopでも書いておく。というよりも、便利なパッケージがこの世からなくならないけど、自分がやりたいことをやるためには、制御構造がもっと使えるようになる必要ある・・・。

n_col <- colnames(caith)
n_row <- rownames(caith)
data <- NULL
for (i in seq_along(n_row)) {
  for (j in seq_along(n_col)) {
    for (k in 1:caith[i, j]){
      data <- rbind(data, c(n_row[[i]], n_col[[j]]))
    }
  }
}
as.data.frame(data) %>% dim()
## [1] 5387    2