16 팩터(Factors)
16.1 소개
팩터(Factors)는 범주형 변수, 즉 고정되고 알려진 가능한 값 집합을 가진 변수에 사용됩니다. 또한 문자 벡터를 알파벳 순서가 아닌 순서로 표시하려는 경우에도 유용합니다.
데이터 분석을 위해 팩터가 필요한 이유1와 factor()로 팩터를 생성하는 방법부터 시작하겠습니다. 그런 다음 실험할 수 있는 많은 범주형 변수가 포함된 gss_cat 데이터셋을 소개합니다. 그 후 해당 데이터셋을 사용하여 팩터의 순서와 값을 수정하는 연습을 한 다음, 순서형 팩터에 대한 논의로 마무리하겠습니다.
16.1.1 선수 지식
기본(base) R은 팩터를 생성하고 조작하기 위한 몇 가지 기본 도구를 제공합니다. 핵심 tidyverse의 일부인 forcats 패키지로 이를 보완할 것입니다. 이 패키지는 팩터 작업을 위한 광범위한 도우미를 사용하여 범주형(categorical) 변수(그리고 factors의 애너그램입니다!)를 다루는 도구를 제공합니다.
16.2 팩터 기초
월을 기록하는 변수가 있다고 상상해 보세요:
x1 <- c("Dec", "Apr", "Jan", "Mar")문자열을 사용하여 이 변수를 기록하면 두 가지 문제가 있습니다:
-
가능한 달은 12개뿐이며 오타로부터 당신을 구해줄 것이 없습니다:
x2 <- c("Dec", "Apr", "Jam", "Mar") -
유용한 방식으로 정렬되지 않습니다:
sort(x1) #> [1] "Apr" "Dec" "Jan" "Mar"
팩터를 사용하여 이 두 가지 문제를 모두 해결할 수 있습니다. 팩터를 생성하려면 유효한 수준(levels) 의 리스트를 생성하는 것으로 시작해야 합니다:
month_levels <- c(
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
)이제 팩터를 만들 수 있습니다:
그리고 수준에 없는 값은 조용히 NA로 변환됩니다:
y2 <- factor(x2, levels = month_levels)
y2
#> [1] Dec Apr <NA> Mar
#> Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec이것은 위험해 보이므로 대신 forcats::fct()를 사용하고 싶을 수 있습니다:
y2 <- fct(x2, levels = month_levels)
#> Error in `fct()`:
#> ! All values of `x` must appear in `levels` or `na`
#> ℹ Missing level: "Jam"수준을 생략하면 데이터에서 알파벳 순서로 가져옵니다:
factor(x1)
#> [1] Dec Apr Jan Mar
#> Levels: Apr Dec Jan Mar알파벳 순서로 정렬하는 것은 모든 컴퓨터가 문자열을 같은 방식으로 정렬하지 않기 때문에 약간 위험합니다. 따라서 forcats::fct()는 첫 번째 등장 순서로 정렬합니다:
fct(x1)
#> [1] Dec Apr Jan Mar
#> Levels: Dec Apr Jan Mar유효한 수준 집합에 직접 액세스해야 하는 경우 levels()를 사용하여 수행할 수 있습니다:
levels(y2)
#> [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"col_factor()를 사용하여 readr로 데이터를 읽을 때 팩터를 생성할 수도 있습니다:
csv <- "
month,value
Jan,12
Feb,56
Mar,12"
df <- read_csv(csv, col_types = cols(month = col_factor(month_levels)))
df$month
#> [1] Jan Feb Mar
#> Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec16.4 팩터 순서 수정
시각화에서 팩터 수준의 순서를 변경하는 것이 유용한 경우가 많습니다. 예를 들어 종교 전반에 걸쳐 하루 평균 TV 시청 시간을 탐색하고 싶다고 상상해 보세요:
전체적인 패턴이 없기 때문에 이 플롯을 읽기 어렵습니다. fct_reorder()를 사용하여 relig의 수준을 재정렬하여 개선할 수 있습니다. fct_reorder()는 세 가지 인수를 취합니다:
-
.f, 수준을 수정하려는 팩터. -
.x, 수준을 재정렬하는 데 사용하려는 수치형 벡터. - 선택적으로
.fun,.f의 각 값에 대해.x의 값이 여러 개 있는 경우 사용되는 함수. 기본값은median입니다.
ggplot(relig_summary, aes(x = tvhours, y = fct_reorder(relig, tvhours))) +
geom_point()종교를 재정렬하면 “Don’t know” 범주의 사람들이 TV를 훨씬 더 많이 보고 힌두교 및 기타 동양 종교는 훨씬 덜 본다는 것을 훨씬 쉽게 알 수 있습니다.
더 복잡한 변환을 만들기 시작하면 aes() 밖으로 이동하여 별도의 mutate() 단계로 옮기는 것이 좋습니다. 예를 들어 위의 플롯을 다음과 같이 다시 쓸 수 있습니다:
relig_summary |>
mutate(
relig = fct_reorder(relig, tvhours)
) |>
ggplot(aes(x = tvhours, y = relig)) +
geom_point()보고된 소득 수준에 따라 평균 연령이 어떻게 변하는지 살펴보는 유사한 플롯을 만들면 어떨까요?
rincome_summary <- gss_cat |>
group_by(rincome) |>
summarize(
age = mean(age, na.rm = TRUE),
n = n()
)
ggplot(rincome_summary, aes(x = age, y = fct_reorder(rincome, age))) +
geom_point()여기서 수준을 임의로 재정렬하는 것은 좋은 생각이 아닙니다! rincome은 이미 우리가 건드려서는 안 되는 원칙적인 순서를 가지고 있기 때문입니다. fct_reorder()는 수준이 임의로 정렬된 팩터에만 사용하세요.
그러나 “Not applicable”을 다른 특수 수준과 함께 맨 앞으로 끌어오는 것은 의미가 있습니다. fct_relevel()을 사용할 수 있습니다. 이것은 팩터 .f와 줄의 맨 앞으로 이동하려는 임의의 수의 수준을 취합니다.
ggplot(rincome_summary, aes(x = age, y = fct_relevel(rincome, "Not applicable"))) +
geom_point()“Not applicable”의 평균 연령이 그렇게 높은 이유는 무엇이라고 생각합니까?
플롯의 선에 색상을 입힐 때 유용한 또 다른 유형의 재정렬이 있습니다. fct_reorder2(.f, .x, .y)는 가장 큰 .x 값과 연관된 .y 값으로 팩터 .f를 재정렬합니다. 이렇게 하면 플롯의 맨 오른쪽에 있는 선의 색상이 범례와 정렬되기 때문에 플롯을 읽기 쉬워집니다.
by_age <- gss_cat |>
filter(!is.na(age)) |>
count(age, marital) |>
group_by(age) |>
mutate(
prop = n / sum(n)
)
ggplot(by_age, aes(x = age, y = prop, color = marital)) +
geom_line(linewidth = 1) +
scale_color_brewer(palette = "Set1")
ggplot(by_age, aes(x = age, y = prop, color = fct_reorder2(marital, age, prop))) +
geom_line(linewidth = 1) +
scale_color_brewer(palette = "Set1") +
labs(color = "marital")마지막으로 막대 플롯의 경우 fct_infreq()를 사용하여 빈도 내림차순으로 수준을 정렬할 수 있습니다. 추가 변수가 필요하지 않기 때문에 가장 간단한 재정렬 유형입니다. 막대 플롯에서 가장 큰 값이 왼쪽이 아닌 오른쪽에 오도록 빈도 오름차순으로 하려면 fct_rev()와 결합하세요.
16.4.1 연습문제
tvhours에 의심스럽게 높은 숫자가 몇 개 있습니다. 평균이 좋은 요약입니까?gss_cat의 각 팩터에 대해 수준의 순서가 임의적인지 원칙적인지 식별하세요.“Not applicable”을 수준의 맨 앞으로 이동시켰는데 왜 플롯의 맨 아래로 이동했습니까?
16.5 팩터 수준 수정
수준의 순서를 변경하는 것보다 더 강력한 것은 값을 변경하는 것입니다. 이를 통해 출판을 위해 레이블을 명확히 하고 상위 수준 디스플레이를 위해 수준을 축소할 수 있습니다. 가장 일반적이고 강력한 도구는 fct_recode()입니다. 각 수준의 값을 다시 코딩하거나 변경할 수 있습니다. 예를 들어 gss_cat 데이터 프레임의 partyid 변수를 가져와 보겠습니다:
gss_cat |> count(partyid)
#> # A tibble: 10 × 2
#> partyid n
#> <fct> <int>
#> 1 No answer 154
#> 2 Don't know 1
#> 3 Other party 393
#> 4 Strong republican 2314
#> 5 Not str republican 3032
#> 6 Ind,near rep 1791
#> # ℹ 4 more rows수준이 간결하고 일관성이 없습니다. 더 길게 수정하고 병렬 구조를 사용해 보겠습니다. tidyverse의 대부분의 이름 바꾸기 및 다시 코딩 함수와 마찬가지로 새 값은 왼쪽에 가고 이전 값은 오른쪽에 갑니다:
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)
#> # A tibble: 10 × 2
#> partyid n
#> <fct> <int>
#> 1 No answer 154
#> 2 Don't know 1
#> 3 Other party 393
#> 4 Republican, strong 2314
#> 5 Republican, weak 3032
#> 6 Independent, near rep 1791
#> # ℹ 4 more rowsfct_recode()는 명시적으로 언급되지 않은 수준은 그대로 두고 존재하지 않는 수준을 실수로 참조하면 경고합니다.
그룹을 결합하기 위해 여러 이전 수준을 동일한 새 수준에 할당할 수 있습니다:
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",
"Other" = "No answer",
"Other" = "Don't know",
"Other" = "Other party"
)
)이 기술을 주의해서 사용하세요. 진정으로 다른 범주를 함께 그룹화하면 오해의 소지가 있는 결과를 초래할 수 있습니다.
많은 수준을 축소하려는 경우 fct_collapse()가 fct_recode()의 유용한 변형입니다. 각 새 변수에 대해 이전 수준의 벡터를 제공할 수 있습니다:
gss_cat |>
mutate(
partyid = fct_collapse(partyid,
"other" = c("No answer", "Don't know", "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")
)
) |>
count(partyid)
#> # A tibble: 4 × 2
#> partyid n
#> <fct> <int>
#> 1 other 548
#> 2 rep 5346
#> 3 ind 8409
#> 4 dem 7180때로는 플롯이나 테이블을 더 간단하게 만들기 위해 작은 그룹을 덩어리로 묶고 싶을 때가 있습니다. 그것이 fct_lump_*() 함수 패밀리의 역할입니다. fct_lump_lowfreq()는 가장 작은 그룹 범주를 “Other”로 점진적으로 묶는 간단한 시작점이며, 항상 “Other”를 가장 작은 범주로 유지합니다.
gss_cat |>
mutate(relig = fct_lump_lowfreq(relig)) |>
count(relig)
#> # A tibble: 2 × 2
#> relig n
#> <fct> <int>
#> 1 Protestant 10846
#> 2 Other 10637이 경우 별로 도움이 되지 않습니다. 이 설문 조사의 대다수 미국인이 개신교인 것은 사실이지만 아마도 더 자세한 내용을 보고 싶을 것입니다! 대신 fct_lump_n()을 사용하여 정확히 10개의 그룹을 원한다고 지정할 수 있습니다:
gss_cat |>
mutate(relig = fct_lump_n(relig, n = 10)) |>
count(relig, sort = TRUE)
#> # A tibble: 10 × 2
#> relig n
#> <fct> <int>
#> 1 Protestant 10846
#> 2 Catholic 5124
#> 3 None 3523
#> 4 Christian 689
#> 5 Other 458
#> 6 Jewish 388
#> # ℹ 4 more rows다른 경우에 유용한 fct_lump_min() 및 fct_lump_prop()에 대해 알아보려면 문서를 읽어보세요.
16.5.1 연습문제
민주당, 공화당, 무소속으로 식별되는 사람들의 비율은 시간이 지남에 따라 어떻게 변했습니까?
rincome을 어떻게 작은 범주 집합으로 축소할 수 있습니까?위의
fct_lump예제에 9개의 그룹(other 제외)이 있음을 주목하세요. 왜 10개가 아닙니까? (힌트:?fct_lump를 입력하고other_level인수의 기본값이 “Other”인지 확인하세요.)
16.6 순서형 팩터(Ordered factors)
계속하기 전에 특수 유형의 팩터인 순서형 팩터에 대해 간단히 언급하는 것이 중요합니다. ordered() 함수로 생성된 순서형 팩터는 수준 간의 엄격한 순서를 의미하지만 수준 간의 차이의 크기에 대해서는 아무것도 지정하지 않습니다. 수준에 순위가 있지만 정확한 수치적 순위가 없음을 알 때 순서형 팩터를 사용합니다.
인쇄될 때 팩터 수준 사이에 < 기호를 사용하므로 순서형 팩터를 식별할 수 있습니다:
기본 R과 tidyverse 모두에서 순서형 팩터는 일반 팩터와 매우 유사하게 작동합니다. 동작의 차이를 알아차릴 수 있는 곳은 두 곳뿐입니다:
- 순서형 팩터를 ggplot2의 color 또는 fill에 매핑하면 순위를 암시하는 색상 척도인
scale_color_viridis()/scale_fill_viridis()가 기본값이 됩니다. - 선형 모델에서 순서형 예측 변수를 사용하면 “다항식 대비(polynomial contrasts)”를 사용합니다. 이것들은 약간 유용하지만 통계학 박사 학위가 있지 않는 한 들어본 적이 없을 것이며, 그렇다 하더라도 아마 일상적으로 해석하지는 않을 것입니다. 더 알고 싶다면 Lisa DeBruine의
vignette("contrasts", package = "faux")를 추천합니다.
이 책의 목적을 위해 일반 팩터와 순서형 팩터를 올바르게 구별하는 것은 특별히 중요하지 않습니다. 그러나 더 넓게 보면 특정 분야(특히 사회 과학)에서는 순서형 팩터를 광범위하게 사용합니다. 이러한 맥락에서는 다른 분석 패키지가 적절한 동작을 제공할 수 있도록 올바르게 식별하는 것이 중요합니다.
16.7 요약
이 장에서는 팩터 작업을 위한 편리한 forcats 패키지를 소개하고 가장 일반적으로 사용되는 함수를 소개했습니다. forcats에는 여기서 논의할 공간이 없었던 다른 다양한 도우미가 포함되어 있으므로 이전에 경험해보지 못한 요인 분석 문제에 직면할 때마다 참조 색인을 훑어보고 문제를 해결하는 데 도움이 될 수 있는 미리 준비된 함수가 있는지 확인하는 것을 강력히 추천합니다.
이 장을 읽은 후 팩터에 대해 더 알고 싶다면 Amelia McNamara와 Nicholas Horton의 논문 Wrangling categorical data in R을 읽어보는 것을 추천합니다. 이 논문은 stringsAsFactors: An unauthorized biography 및 stringsAsFactors = <sigh>에서 논의된 역사의 일부를 설명하고 이 책에 설명된 범주형 데이터에 대한 깔끔한 접근 방식과 기본 R 방법을 비교합니다. 논문의 초기 버전은 forcats 패키지에 동기를 부여하고 범위를 지정하는 데 도움이 되었습니다. Amelia와 Nick에게 감사합니다!
다음 장에서는 기어를 바꿔 R에서 날짜와 시간에 대해 배우기 시작할 것입니다. 날짜와 시간은 겉보기에는 단순해 보이지만 곧 알게 되겠지만 더 많이 알수록 더 복잡해지는 것 같습니다!
모델링에도 정말 중요합니다.↩︎