4장 요인(factor): 순서 있는 요인과 순서 없는 요인
4장 요인(factor): 순서 있는 요인과 순서 없는 요인
R에서 요인(factor) 은 어떤 벡터의 성분들을 이산적인 분류(그룹)로 묶어서 지정할 때 쓰는 벡터 객체예요. 같은 길이를 가진 다른 벡터들을 요인으로 나눠 그룹별로 분석하고 싶을 때가 바로 이 개념이 필요한 순간이죠. R은 순서 있는 요인(ordered factor) 과 순서 없는 요인(unordered factor) 을 모두 제공하는데, 요인의 "진짜" 쓰임새는 모델 공식(model formula) 안에서지만, 여기서는 구체적인 예를 하나 따라가며 이해해 볼게요.
출처: R 공식 매뉴얼
본문
4.1 구체적인 예 하나 보기
호주의 모든 주와 준주(territory)에서 온 세무 회계사 30명의 표본이 있다고 생각해 볼까요. 각 사람이 어느 주 출신인지는 주 이름의 약어(mnemonic)로 이루어진 문자형 벡터로 지정되어 있어요.
> state <- c("tas", "sa", "qld", "nsw", "nsw", "nt", "wa", "wa",
"qld", "vic", "nsw", "vic", "qld", "qld", "sa", "tas",
"sa", "nt", "wa", "vic", "qld", "nsw", "nsw", "wa",
"sa", "act", "nsw", "vic", "vic", "act")
문자형 벡터에서 "정렬(sorted)"이라는 말은 알파벳 순서를 뜻한다는 점을 기억해 두면 좋아요.
요인도 factor() 함수를 쓰면 거의 같은 방식으로 만들 수 있어요.
> statef <- factor(state)
print() 함수는 요인을 다른 객체와는 조금 다르게 다뤄요. statef를 출력하면 값들이 나열되고, 마지막에 그 요인이 가질 수 있는 범주들(Levels)이 함께 보이죠.
> statef
[1] tas sa qld nsw nsw nt wa wa qld vic nsw vic qld qld sa
[16] tas sa nt wa vic qld nsw nsw wa sa act nsw vic vic act
Levels: act nsw nt qld sa tas vic wa
요인의 범주(levels)가 무엇인지 알고 싶을 때는 levels() 함수를 쓰면 돼요.
> levels(statef)
[1] "act" "nsw" "nt" "qld" "sa" "tas" "vic" "wa"
4.2 tapply() 함수와 들쭉날쭉한 배열(ragged array)
앞의 예를 이어서, 같은 세무 회계사들의 소득이 또 다른 벡터로 주어져 있다고 해 볼게요. (단위는 충분히 큰 금액 단위라고 가정해요.)
> incomes <- c(60, 49, 40, 61, 64, 60, 59, 54, 62, 69, 70, 42, 56,
61, 61, 61, 58, 51, 48, 65, 49, 49, 41, 48, 52, 46,
59, 46, 58, 43)
주별로 소득의 표본 평균을 구하고 싶다면 특별한 함수 tapply()를 쓰면 됩니다.
> incmeans <- tapply(incomes, statef, mean)
그 결과는 각 범주 이름으로 라벨이 붙은 평균 벡터예요.
act nsw nt qld sa tas vic wa
44.500 57.333 55.500 53.600 55.000 60.500 56.000 52.250
tapply()는 첫 번째 인자(여기서는 incomes)의 성분들을 두 번째 인자(여기서는 statef)의 범주에 따라 각각의 그룹으로 나눠서, 마치 각자 별도의 벡터 구조인 것처럼 그룹마다 함수(여기서는 mean())를 적용해 주는 함수예요. 결과물은 요인의 levels 속성과 같은 길이를 가진 구조가 됩니다. 더 자세한 내용은 도움말 문서를 참고하면 좋아요.
이번에는 주별 소득 평균의 표준오차를 계산해야 한다고 가정해요. 그러려면 어떤 벡터가 주어졌을 때 그 표준오차를 계산하는 R 함수를 직접 하나 작성해야 하죠. 표본 분산을 구하는 내장 함수 var()가 있으니, 이 함수는 아주 간단한 한 줄짜리로 만들 수 있어요.
> stdError <- function(x) sqrt(var(x)/length(x))
(함수 작성은 뒤에 "Writing your own functions" 절에서 자세히 다루게 될 거예요. 참고로 R의 내장 함수 sd()는 이 표준오차와는 다른 일을 하는 함수니까 헷갈리지 마세요.) 이 함수를 할당해 두고 나면 표준오차는 tapply()로 이렇게 계산돼요.
> incster <- tapply(incomes, statef, stdError)
계산된 값은 이렇게 나옵니다.
> incster
act nsw nt qld sa tas vic wa
1.5 4.3102 4.5 4.1061 2.7386 0.5 5.244 2.6575
연습 삼아 주별 소득 평균의 95% 신뢰구간을 구해 보는 것도 재미있을 거예요. 그러려면 tapply()에 length() 함수를 다시 써서 각 표본 크기를 구하고, qt() 함수로 적절한 t-분포의 백분위수를 찾아 쓰면 됩니다. (R이 제공하는 t-검정 기능을 살펴보는 것도 좋고요.)
tapply()는 여러 범주로 벡터를 더 복잡하게 인덱싱할 때도 쓸 수 있어요. 예를 들어 세무 회계사들을 주와 성별 두 가지 기준으로 나눠 보고 싶을 수 있겠죠. 다만 이렇게 단순한 경우(요인이 딱 하나인 경우)에는 벌어지는 일이 이렇게 정리돼요. 벡터의 값들이 요인에서 서로 다른 항목에 해당하는 그룹으로 모이고, 함수가 그 그룹 각각에 따로 적용되는 거예요. 결과값은 함수 결과들의 벡터인데, 요인의 levels 속성으로 라벨이 붙어 있죠.
벡터와 그 벡터에 라벨을 붙여 주는 요인이 짝 지어진 형태를 가리켜 들쭉날쭉한 배열(ragged array) 이라고 부르기도 해요. 각 하위 그룹의 크기가 서로 다를 수 있기 때문이죠. 반대로 모든 하위 그룹의 크기가 같다면 인덱싱을 암묵적으로 훨씬 효율적으로 할 수 있는데, 그 내용은 다음 절에서 다루게 될 거예요.
4.3 순서 있는 요인(ordered factor)
요인의 levels는 기본적으로 알파벳 순서로 저장되고, factor에 명시적으로 지정해 줬다면 그 지정한 순서대로 저장돼요.
그런데 가끔은 levels에 자연스러운 순서가 있고, 그 순서를 기록해 두고 통계 분석에서 활용하고 싶을 때가 있어요. ordered() 함수가 바로 그런 순서 있는 요인을 만들어 주는데, 그 외에는 factor와 완전히 동일하게 동작합니다. 실제로 순서 있는 요인과 순서 없는 요인의 차이는, 대부분의 경우 후자(순서 없는 요인)를 출력할 때 levels의 순서가 함께 보이는지 여부뿐이에요. 다만 선형 모형을 적합할 때 생성되는 대비(contrast)는 서로 다르답니다.
더 알아보기
- 요인의 "진짜" 쓰임새인 모델 공식과 대비(contrast)에 대한 설명은 매뉴얼의 11장 "Statistical models in R" 절에서 다뤄요.
- 함수 작성에 대한 자세한 내용은 10장 "Writing your own functions"에서 살펴볼 수 있어요.
tapply()의 좀 더 복잡한 다중 범주 인덱싱과 그룹별 연산은 각각의 도움말 문서(help(tapply))에서 확인할 수 있어요.