범주형 자료는 가질 수 있는 값(범주)이 일반적으로 알려져 있고 고정되어 있는 데이터를 뜻합니다.
성별은 대표적인 범주형 변수로 남자와 여자 두 가지 범주로 구성되어 있습니다.
또한 성별 외에도 혈액형(A/B/AB/O형), 검사결과(음성/양성) 등을 범주형 자료라고 볼 수 있습니다.
마지막으로 키, 연령과 같이 연속형인 변수는 Binning(구간화)를 통해 범주형 데이터로 변형할 수 있습니다.
R에서는 범주형 데이터를 나타내기 위해 주로 Factor(팩터)를 사용하는데요.
base R에서는 factor(x, levels, labels, ordered) 함수를 이용해서 팩터형으로 변환할 수 있습니다.
이때 levels는 범주가 가질 수 있는 값들을 입력받아 범주에 포함되지 않는 값은 NA로 변환하여 출력합니다.
data <- c("Men", "Men", "Women", "Men", "Women")
A <- factor(data, levels=c("Men", "Women"))
B <- factor(data, levels=c("Women", "Men"))
sort(A) # Men Men Men Women Women
sort(B) # Women Women Men Men Men
다만 베이스 R에서는 범주형 데이터를 위한 함수들이 부족해서 forcats 라이브러리의 함수를 소개합니다.
- fct : factor의 엄격한 함수로, levels에 없는 값을 NA로 바꾸지 않고 에러를 발생시킴
- fct( x = character(), levels = NULL, na = character)
- levels를 지정하지 않는 경우 처음 등장하는 범주부터 수준(level)를 부여 (* base R은 알파벳 순서로 부여)
x <- c("A", "O", "O", "AB", "A")
factor(x, levels = c("A", "B", "AB")) # Work!
fct(x, levels = c("A", "B", "AB")) # Error!
factor(x) # Levels: A AB O
fct(x) # Levels: A O AB
- fct_infreq(f, w = NULL, ordered = NA) : 각 범주 마다의 관측치 수에 따라 수준을 정하는 함수
p1 <- gss_cat |>
ggplot(aes(marital)) + geom_bar() + labs(title = "Basic")
p2 <- gss_cat |>
ggplot(aes(fct_infreq(marital))) + geom_bar() + labs(title = "fct_infreq", x="marital")
p1 / p2

- fct_reorder / fct_reorder2 : 다른 변수의 값을 사용해 범주의 수준을 변경하는 함수
- fct_reorder(.f, .x, .fun = median, ..., .na_rm=NULL) : 범주의 순서를 .x의 중앙값(.fun)을 사용해 작은 순으로 재정렬
- fct_reorder2(.f, .x, .y, .fun = last2, ..., na_rm=NULL) : 범주의 순서를 가장 큰 x값과 연관된 y값으로 변경
- fct_rev(f) : 범주의 순서를 뒤바꾸는 함수
gss_cat |>
summarise(mean_age = mean(age, na.rm = T),
n = n(), .by = "partyid") |>
ggplot(mapping=aes(y=partyid, x=mean_age)) +
geom_col(fill="white", color = "grey40") +
geom_point(color = "red", size = 3) +
coord_cartesian(xlim=c(25, 55)) +
labs(x = "Average Age", y = NULL, title = "Original") -> p1
gss_cat |>
summarise(mean_age = mean(age, na.rm = T),
n = n(), .by = "partyid") |>
ggplot(mapping=aes(y=fct_reorder(partyid, mean_age), x=mean_age)) +
geom_col(fill="white", color = "grey40") +
geom_line(aes(group=1), linewidth = 0.4) +
geom_point(color = "red", size = 3) +
coord_cartesian(xlim=c(25, 55)) +
labs(x = "Average Age", y = NULL, title = "fct_reorder") -> p2
gss_cat |>
summarise(mean_age = mean(age, na.rm = T),
n = n(), .by = "partyid") |>
ggplot(mapping=aes(y=fct_reorder(partyid, mean_age) |> fct_rev(), x=mean_age)) +
geom_col(fill="white", color = "grey40") +
geom_line(aes(group=1), linewidth = 0.4) +
geom_point(color = "red", size = 3) +
coord_cartesian(xlim=c(25, 55)) +
labs(x = "Average Age", y = NULL, title = "fct_reorder + fct_rev") -> p3
p1 + p2 + p3

- fct_recode(.f, ...) : 범주를 변경하는 함수로 ...에 새값"="기존값"와 같이 named 문자형 벡터를 제공
- named vector에 언급되지 않은 범주는 그대로 사용됨
- fct_relabel(.f, .fun, ...)는 문자 조작 함수(stringr와 같은)를 사용해 변경할 범주의 이름을 일괄로 변경
gss_cat |> count(partyid)
gss_cat |> mutate(partyid =
fct_recode(partyid,
"Republican, strong" = "Strong republican",
"Republican, weak" = "Not str republican",
"Independent, near rep" = "Ind,near rep",
"Independent, near dem" = "Ind,near dem",
"Democrat, weak" = "Not str democrat",
"Democrat, strong" = "Strong democrat"
)) |> count(partyid)


- fct_collapse(.f, ..., other_level = NULL) : 기존에 존재하는 범주들을 지정된 범주로 변경하는 함수로 다수의 범주를 적은 수의 범주로 병합할때 사용
- ...에는 named character vector를 지정하여 기존 변수들을 새로운 범주로 변경
fct_collapse(gss_cat$partyid,
missing = c("No answer", "Don't know"),
other = "Other party",
rep = c("Strong republican", "Not str republican"),
ind = c("Ind,near rep", "Independent", "Ind,near dem"),
dem = c("Not str democrat", "Strong democrat"))
- fct_lump_min(f, min, w=NULL, other_level = "Other") : min보다 적은 관측치를 가지는 범주들을 합치는 함수
- fct_lump_prop(f, prop, w=NULL, other_level = "Other") : prop * n 보다 적은 관측치를 가지는 범주들을 합치는 함수
- fct_lump_n(f, n, w = NULL, other_level = "Other", ties.method = "min") : 관측치가 많은 n개의 범주를 제외한 나머지 범주들을 합치는 함수
x <- fct(rep(LETTERS[1:10], times = c(100, 10, 5, 200, 30, 450, 20, 2, 15, 30)))
x |> table()
# fct_lump_min
x |> fct_lump_min(30) |> table() # 30보다 적은 관측치를 가지는 범주들은 하나로 묶임
x |> fct_lump_min(100)|> table() # 100보다 적은 관측치를 가지는 범주들은 하나로 묶임
# fct_lump_prop
x |> fct_lump_prop(0.10) |> table() # 관측치수가 862 * 0.1 보다 작은 범주는 하나로 묶임
x |> fct_lump_prop(0.02) |> table() # 관측치수가 862 * 0.02보다 작은 범주는 하나로 묶임
# fct_lump_n
x |> fct_lump_n(1) |> table() # F(450) Other(412)
x |> fct_lump_n(3) |> table() # A(100), D(200), F(450), Other(112)
- fct_relevel(.f, ..., after=0L) : 범주의 순서를 앞 n번째로 바꾸는 방법
p1 <- gss_cat |> ggplot(mapping=aes(x=rincome)) + geom_bar() +
labs(x=NULL, y=NULL, title = "Original") + theme(axis.text.x = element_text(angle=30)) +
scale_y_continuous(labels = scales::label_dollar())
p2 <- gss_cat |>
mutate(rincome = fct_relevel(rincome, "Not applicable", after = 0)) |>
ggplot(mapping=aes(x=rincome)) + geom_bar() +
labs(x=NULL, y=NULL, title = "fct_relevel - change one level") + theme(axis.text.x = element_text(angle=30)) +
scale_y_continuous(labels = scales::label_dollar())
p3 <- gss_cat |>
mutate(rincome = fct_relevel(rincome, "Not applicable", "No answer", "Don't know", "Refused", after = Inf)) |>
ggplot(mapping=aes(x=rincome)) + geom_bar() +
labs(x=NULL, y=NULL, title = "fct_relevel - change four level") +
theme(axis.text.x = element_text(angle=30)) +
scale_y_continuous(labels = scales::label_dollar())
p1 + p2 / p3 + patchwork::plot_layout(widths = c(0.4, 0.6))

- fct_c(...) : 서로 다른 factor를 하나의 범주형 데이터로 묶어주는 함수 (수준이 같을 필요는 없음)
facA <- factor(x=c("A", "B", "A", "A")) # levels : A B
facB <- factor(x=c("B", "AB", "AB")) # levels : AB B
facC <- factor(x=c("O", "O")) # levels : O
fct_c(facA, facB, facC) # levels : A B AB O
fct_c(!!!list(facA, facB, facC)) # levels : A B AB O
- fct_expand(f, ..., after = Inf) : 범주형 객체에 범주를 추가하는 함수
- after로 새로운 범주의 수준을 결정할 수 있음
f <- factor(c("A", "A", "B", "A", "AB", "B", "A"))
f # levels : A AB B
fct_expand(f, "O") # levels : A AB B O
fct_expand(f, "O", after=2) # levels : A AB O B
기타 유용한 함수
- fct_count(f, sort=F, prop=F) : 범주의 개수를 요약해주는 함수
- sort = T 경우 가장 많은 개수를 가진 범주부터 정렬해서 출력
- prop = T 경우 특정 범주의 비율을 출력
f<- factor(sample(letters)[rpois(1000, 10)])
table(f)
fct_count(f)
fct_count(f, sort = TRUE)
fct_count(f, sort = TRUE, prop = TRUE)
- fct_drop(f, only = NULL) : 사용하지 않는 범주를 드랍하는 함수 (값이 존재하는 범주는 제거할 수 없음)
- only : 값이 존재하지 않는 범주 중에서 제거할 범주를 문자형 벡터로 제공
f <- factor(c("a", "b"), levels = c("a", "b", "c"))
f
fct_drop(f) # levels : a b
# Set only to restrict which levels to drop
fct_drop(f, only = "a") # levels : a b c
fct_drop(f, only = "c") # levels : a b
- fct_anon(f, prefix = "") : 범주의 수준을 임의의 수치형 값으로 변환하는 함수로 범주의 값이나 범주의 순서가 보존되지 않음
gss_cat$relig %>%
fct_count(sort=T) |> slice_head(n=5)
gss_cat$relig %>% fct_anon(prefix = "C") %>%
fct_count(sort=T) |> slice_head(n=5)


- fct_inorder(f, ordered = NA) : 처음 등장하는 범주부터 첫번째 수준으로 재정렬하는 함수
- fct_inseq(f, ordered = NA) : 범주 자체의 값으로 수준을 재정렬하는 함수 (범주는 무조건 숫자여야 함)
f <- fct(x=c("3", "1", "3", "2", "1", "1"), levels = 4:1 |> as.character())
f # levels : 4 3 2 1
f |> fct_inorder() # levels : 3 1 2 4
f |> fct_inseq() # levels : 1 2 3 4
- fct_shuffle(f) : 범주의 수준을 임의로 설정
# fct_shuffle : 범주의 수준을 임의로 설정
f <- fct(x=letters[1:5])
f # levels : a b c d e
f |> fct_shuffle() # levels : d a b e c (실행할때마다 변함)
마지막으로 ggplot2로 작업시 값이 없는 레벨은 제거하는 것을 방지하기 위해 scale_x_discrete(drop=F)를 추가하여 모든 레벨을 보이게 할 수 있습니다.
toy_data <- tibble(
mon = factor(sample(x=month_level[-2], size = 1000, replace = T),
levels = month_level))
toy_data %>%
ggplot(mapping=aes(x=mon)) + geom_bar()
toy_data %>%
ggplot(mapping=aes(x=mon)) + geom_bar() +
scale_x_discrete(drop=F)


'Data Science > Manipulation' 카테고리의 다른 글
| [Data Science With R] 12. 함수(Function) (0) | 2023.04.16 |
|---|---|
| [Data Science With R] 11. Time Data with lubridate (202406) (0) | 2023.04.08 |
| [Data Science With R] 9. 정규표현식(Regular Expression) with Stringr (202406) (0) | 2023.04.04 |
| [Data Science With R] 8. 관계형 데이터 (0) | 2023.04.01 |
| [Data Science With R] 7. 데이터 변형 with tidyr (202405) (1) | 2023.04.01 |