Multi-dim table, tables, and missing values
- 3줄 요약
table()
시NA
의 존재에 유의하자- 3차원 표를 2차원으로 출력하기 위해
ftable()
을 사용할 수 있다. - 새로운 함수
ftab2tab()
을 정의하여, 다차원 표(table()
) 결과를 2차원으로 변형한다.
base function table
table()
함수는 범주형 변수의 분포를 확인해보는 데 요긴하게 사용된다.
data(mpg, package='ggplot2')
library(dplyr)
drv <- mpg$drv
drv[c(1,4,10,25,44,89,102,155)] = NA
table(drv)
## drv ## 4 f r ## 101 101 24
table()
의 결과는 1차원 벡터이거나, 다차원 배열이 된다.
with(mpg, table(drv, trans)) # 2차원
## trans ## drv auto(av) auto(l3) auto(l4) auto(l5) auto(l6) auto(s4) auto(s5) auto(s6) manual(m5) manual(m6) ## 4 0 0 34 29 2 2 1 7 21 7 ## f 5 2 37 8 2 1 2 8 33 8 ## r 0 0 12 2 2 0 0 1 4 4
tab3d <- with(mpg, table(drv,trans, cyl))
tab3d
## , , cyl = 4 ## ## trans ## drv auto(av) auto(l3) auto(l4) auto(l5) auto(l6) auto(s4) auto(s5) auto(s6) manual(m5) manual(m6) ## 4 0 0 7 1 0 2 0 1 11 1 ## f 2 2 17 5 0 0 1 3 22 6 ## r 0 0 0 0 0 0 0 0 0 0 ## ## , , cyl = 5 ## ## trans ## drv auto(av) auto(l3) auto(l4) auto(l5) auto(l6) auto(s4) auto(s5) auto(s6) manual(m5) manual(m6) ## 4 0 0 0 0 0 0 0 0 0 0 ## f 0 0 0 0 0 0 0 2 2 0 ## r 0 0 0 0 0 0 0 0 0 0 ## ## , , cyl = 6 ## ## trans ## drv auto(av) auto(l3) auto(l4) auto(l5) auto(l6) auto(s4) auto(s5) auto(s6) manual(m5) manual(m6) ## 4 0 0 8 12 0 0 0 2 7 3 ## f 3 0 20 3 2 0 1 3 9 2 ## r 0 0 1 1 0 0 0 0 2 0 ## ## , , cyl = 8 ## ## trans ## drv auto(av) auto(l3) auto(l4) auto(l5) auto(l6) auto(s4) auto(s5) auto(s6) manual(m5) manual(m6) ## 4 0 0 19 16 2 0 1 4 3 3 ## f 0 0 0 0 0 1 0 0 0 0 ## r 0 0 11 1 2 0 0 1 2 4
3차원 테이블의 구조를 살펴보면 다음과 같다
str(tab3d)
## 'table' int [1:3, 1:10, 1:4] 0 2 0 0 2 0 7 17 0 1 ... ## - attr(*, "dimnames")=List of 3 ## ..$ drv : chr [1:3] "4" "f" "r" ## ..$ trans: chr [1:10] "auto(av)" "auto(l3)" "auto(l4)" "auto(l5)" ... ## ..$ cyl : chr [1:4] "4" "5" "6" "8"
클래스는 table
이지만 3차원 배열([1:3, 1:10, 1:4]
)이며, dimnames
로 차원의 이름이 붙여져 있음을 알 수 있다.
책에 정리되어 있듯이 as.data.frame()
으로 데이터프레임으로 변환할 수도 있다.
as.data.frame(tab3d, responseName = 'count') %>% head
## drv trans cyl count ## 1 4 auto(av) 4 0 ## 2 f auto(av) 4 2 ## 3 r auto(av) 4 0 ## 4 4 auto(l3) 4 0 ## 5 f auto(l3) 4 2 ## 6 r auto(l3) 4 0
ftable(_TABLE, row.vars=c())
의 row.vars=
에 행마다 바뀌는 차원을 적어서 좀 더 보기 편한 2차원 표를 만들 수도 있다.
ftab <- ftable(tab3d, row.vars = 2) # 2nd dim = trans
ftab
## drv 4 f r ## cyl 4 5 6 8 4 5 6 8 4 5 6 8 ## trans ## auto(av) 0 0 0 0 2 0 3 0 0 0 0 0 ## auto(l3) 0 0 0 0 2 0 0 0 0 0 0 0 ## auto(l4) 7 0 8 19 17 0 20 0 0 0 1 11 ## auto(l5) 1 0 12 16 5 0 3 0 0 0 1 1 ## auto(l6) 0 0 0 2 0 0 2 0 0 0 0 2 ## auto(s4) 2 0 0 0 0 0 0 1 0 0 0 0 ## auto(s5) 0 0 0 1 1 0 1 0 0 0 0 0 ## auto(s6) 1 0 2 4 3 2 3 0 0 0 0 1 ## manual(m5) 11 0 7 3 22 2 9 0 0 0 2 2 ## manual(m6) 1 0 3 3 6 0 2 0 0 0 0 4
패키지 ztable
패키지 ztable
을 써서 여러 형식의 표를 만들어 볼 수 있다.
문제는 ztable
이 3차원 이상의 표(table()
함수의 결과)나 ftable()
의 결과를 지원하지 않는다는 점이다. 아래의 함수는 ftable()
의 결과를 2차원의 표로 변환하여 ztable()
을 활용할 수 있게 한다.
ftab2tab = function(ftab) {
col_vars = attr(ftab, 'col.vars')
col_vars = col_vars[length(col_vars):1]
cols = do.call(expand.grid, c(col_vars, list(stringsAsFactors = FALSE)))
.cols = do.call(paste, c(cols, list(sep='-')))
row_vars = attr(ftab, 'row.vars')
row_vars = row_vars[length(row_vars):1]
rows = do.call(expand.grid, c(row_vars, list(stringsAsFactors = FALSE)))
.rows = do.call(paste, c(rows, list(sep='-')))
class(ftab) = 'table'
dimnames(ftab) = list(.rows, .cols)
names(dimnames(ftab)) = c(paste0(names(row_vars), collapse='-'),
paste0(names(col_vars), collapse='-'))
return(ftab)
}
library(ztable)
ftab <- ftable(tab3d, row.vars = 2) # 2nd dim = trans
ztable(ftab)
## ## Sorry, Currently function ztable() cannot handle the object of class ftable!
#https://cran.r-project.org/web/packages/kableExtra/vignettes/awesome_table_in_html.html
#아래 출력은 rmakrdown의 chunk option을 results='asis'로 두었음
ztable(ftab2tab(ftab))
4-4 | 5-4 | 6-4 | 8-4 | 4-f | 5-f | 6-f | 8-f | 4-r | 5-r | 6-r | 8-r | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
auto(av) | 0 | 0 | 0 | 0 | 2 | 0 | 3 | 0 | 0 | 0 | 0 | 0 |
auto(l3) | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
auto(l4) | 7 | 0 | 8 | 19 | 17 | 0 | 20 | 0 | 0 | 0 | 1 | 11 |
auto(l5) | 1 | 0 | 12 | 16 | 5 | 0 | 3 | 0 | 0 | 0 | 1 | 1 |
auto(l6) | 0 | 0 | 0 | 2 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 2 |
auto(s4) | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
auto(s5) | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
auto(s6) | 1 | 0 | 2 | 4 | 3 | 2 | 3 | 0 | 0 | 0 | 0 | 1 |
manual(m5) | 11 | 0 | 7 | 3 | 22 | 2 | 9 | 0 | 0 | 0 | 2 | 2 |
manual(m6) | 1 | 0 | 3 | 3 | 6 | 0 | 2 | 0 | 0 | 0 | 0 | 4 |
kable
패키지
사실 ztable
보다 kable
패키지가 더 유명하긴 하다. 하지만 kable
패키지도 ftable()
결과에 대해서는 지원하지 않는다. Vignettes를 참조하여 멋진 표(table)을 하나 만들어보는 것도 좋겠다.
library(kableExtra)
as.data.frame(tab3d, responseName = 'count') %>%
head() %>%
kbl() %>%
kable_styling()
drv | trans | cyl | count |
---|---|---|---|
4 | auto(av) | 4 | 0 |
f | auto(av) | 4 | 2 |
r | auto(av) | 4 | 0 |
4 | auto(l3) | 4 | 0 |
f | auto(l3) | 4 | 2 |
r | auto(l3) | 4 | 0 |
#tab3d %>% kbl() %>% kable_styling() # as.data.frame(tab3d) %>% ...와 동일한 결과
#ftab %>% kbl() %>% kable_styling() # 길고 긴 테이블
ftab %>% ftab2tab %>% kbl() %>% kable_styling()
4-4 | 5-4 | 6-4 | 8-4 | 4-f | 5-f | 6-f | 8-f | 4-r | 5-r | 6-r | 8-r | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
auto(av) | 0 | 0 | 0 | 0 | 2 | 0 | 3 | 0 | 0 | 0 | 0 | 0 |
auto(l3) | 0 | 0 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
auto(l4) | 7 | 0 | 8 | 19 | 17 | 0 | 20 | 0 | 0 | 0 | 1 | 11 |
auto(l5) | 1 | 0 | 12 | 16 | 5 | 0 | 3 | 0 | 0 | 0 | 1 | 1 |
auto(l6) | 0 | 0 | 0 | 2 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 2 |
auto(s4) | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
auto(s5) | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
auto(s6) | 1 | 0 | 2 | 4 | 3 | 2 | 3 | 0 | 0 | 0 | 0 | 1 |
manual(m5) | 11 | 0 | 7 | 3 | 22 | 2 | 9 | 0 | 0 | 0 | 2 | 2 |
manual(m6) | 1 | 0 | 3 | 3 | 6 | 0 | 2 | 0 | 0 | 0 | 0 | 4 |
tables
패키지
출력이 LaTeX이라면 tables
패키지를 사용할 수 있다고 한다.
useNA
인자
함수 table()
의 결과에 기본적으로 NA
는 포함되지 않음을 유의하자. NA
를 포함시키려면 useNA='ifany
나 useNA='always'
를 해야 한다.
table(drv, useNA = 'ifany')
## drv ## 4 f r <NA> ## 101 101 24 8
필자는 항상 NA
의 존재 및 갯수를 확인하고자 다음과 같이 table
함수를 재정의해 놓는다.
table = function(..., useNA = 'ifany') {
base::table(..., useNA = useNA)
}
조건에 해당하는 경우 확인하기
#trans <- mpg$trans
#trans[sample(length(trans), 10)] <- NA
mpg[sample(nrow(mpg), 10), 'trans'] <- NA
table(mpg$trans)
## ## auto(av) auto(l3) auto(l4) auto(l5) auto(l6) auto(s4) auto(s5) auto(s6) manual(m5) manual(m6) ## 4 2 79 38 6 3 3 15 56 18 ## <NA> ## 10
만약 trans
의 범주 중에서 갯수가 12개 미만인 경우만을 확인하고자 한다면 어떻게 해야 할까? 가장 손쉬운 방법은 다음과 같다.
tbl <- table(mpg$trans)
tbl[tbl < 12]
## ## auto(av) auto(l3) auto(l6) auto(s4) auto(s5) <NA> ## 4 2 6 3 3 10
참고자료
PS
내가 만들었지만 혹시 잘못 작동하는 부분은 없는지 확인해본다.
tab <- with(mtcars, table(vs, am, gear, carb))
ftab <- ftable(tab, row.vars=c(1,2))
ftab
## gear 3 4 5 NA ## carb 1 2 3 4 6 8 1 2 3 4 6 8 1 2 3 4 6 8 1 2 3 4 6 8 ## vs am ## 0 0 0 3 3 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ## 1 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 1 0 0 0 0 0 0 ## NA 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 ## 1 0 3 0 0 0 0 0 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ## 1 0 0 0 0 0 0 3 2 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 ## NA 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
ftab2tab(ftab)
## carb-gear ## am-vs 1-3 2-3 3-3 4-3 6-3 8-3 1-4 2-4 3-4 4-4 6-4 8-4 1-5 2-5 3-5 4-5 6-5 8-5 1-NA 2-NA 3-NA 4-NA 6-NA 8-NA ## 0-0 0 3 3 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ## 1-0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 1 0 0 0 0 0 0 ## NA-0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 ## 0-1 3 0 0 0 0 0 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ## 1-1 0 0 0 0 0 0 3 2 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 ## NA-1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
NA가 존재하면 결과가 어떻게 바뀌는지도 확인해보자.
mtcars[c(1,3,5), c(9,10)] = NA
tab <- with(mtcars, table(vs, am, gear, carb, useNA='always'))
Leave a comment