팩터는 실제로 무엇인가
팩터는 범주형 데이터, 즉 처치 그룹, 설문 응답, 티셔츠 사이즈처럼 고정된 메뉴에서 나오는 값들에 대한 R의 답입니다. 출력하면 문자 벡터처럼 보이지만 문자 벡터가 아닙니다. 내부적으로 팩터는 정수 코드 벡터와 수준이라 불리는 라벨 조회 표로 이루어져 있습니다.
출력은 라벨을 보여 준 뒤 메뉴를 나열하는 Levels: 줄을 보여 줍니다. as.integer()는 그 기계 장치를 드러냅니다. 코드가 3 1 3 2로 돌아오는데, 각 값이 실제로는 수준 표의 인덱스이고 수준은 데이터가 도착한 순서가 아니라 기본적으로 알파벳순(large, medium, small)으로 정렬되기 때문입니다. 그래서 small이 코드 3이고 large가 코드 1인데, 이것은 누구의 직관과도 맞지 않습니다. 이 알파벳순 기본값을 기억하세요. 아래 함정 중 두 개가 여기서 비롯됩니다.
평범한 문자열 대신 왜 이런 두 층 설계를 감수할까요? 통계에 필요하기 때문입니다. 모델은 "small"에 계수를 곱할 수 없지만, 알려진 세 수준을 0과 1로 이루어진 열들로 더미 코딩할 수는 있습니다. lm(), glm(), table(), 그리고 분산분석 뒤의 장치들은 모두 팩터를 단서로 삼아 변수가 범주형임을, 범주의 완전한 집합이 무엇인지(데이터에 없는 것 포함), 어느 범주가 기저인지를 압니다. 평범한 문자 벡터는 그중 아무것도 담지 못합니다.
팩터 만들기: levels, labels, table()
기본적으로 factor()는 발견한 서로 다른 값들을 알파벳순으로 정렬해 수준으로 삼습니다. 종종 집합과 순서 모두를 통제하고 싶어질 텐데, 그때 levels =를 전달합니다.
이제 수준이 자연스러운 크기 순서로 놓이고, 팩터와 함께 끊임없이 쓰게 될 한 줄짜리 도수 집계 table()도 그 순서대로 개수를 보고합니다. nlevels()는 범주 개수를 셉니다.
levels =가 주는 이점이 두 가지 더 있습니다. 여러분이 지정한 수준 목록에 없는 데이터 값은 NA가 됩니다(좋은 일입니다. 오타가 독자적인 범주가 되는 대신 드러납니다). 그리고 한 번도 나타나지 않은 수준도 여전히 존재하므로, 설문 응답 요약이 그 선택지가 없었던 척하는 대신 "매우 반대: 0"을 보여 줍니다.
labels =는 생성 시점에 수준의 이름을 바꿔 주며, 원시 데이터가 코드를 사용할 때 유용합니다.
그리고 기본값으로 충분할 때 기존 벡터를 빠르게 변환하려면 as.factor(x)를 쓰세요.
순서형 데이터를 위한 순서 있는 팩터
평범한 팩터는 범주에 순서가 없다고 봅니다. "red"가 "blue"보다 작지 않습니다. 하지만 어떤 범주 척도에는 진짜 서열이 있습니다. 낮음/중간/높음, 반대/중립/찬성처럼요. ordered = TRUE로 그것을 선언하세요.
이제 출력에 Levels: low < medium < high가 보이고 비교 연산자가 동작합니다. 한 평가가 다른 것보다 큰지 물어볼 수도 있고, "medium" 이상인 것만 걸러 낼 수도 있습니다. 순서 없는 팩터에서는 둘 다 오류(정확히는 경고와 NA)입니다. 순서 있는 팩터는 모델이 변수를 코딩하는 방식도 바꿉니다(더미 변수 대신 다항 대비). 순서형 예측 변수에는 대개 그쪽이 원하는 바입니다.
서열이 실제로 존재할 때만 ordered = TRUE를 쓰세요. 평범한 그룹을 순서형으로 인코딩하면 모델 출력이 오해하기 쉬운 방식으로 바뀝니다.
기준 수준과 relevel()
팩터의 첫 수준은 특별합니다. 모델 함수는 이를 기준 범주, 즉 다른 모든 수준의 계수가 측정되는 기저로 취급합니다. 기본 수준 순서가 알파벳순이므로, 개입하지 않으면 기저가 알파벳에 의해 정해집니다. "control"이 "aspirin"에게 알파벳순으로 지는 것은 과학적 결정이 아닙니다.
relevel()은 한 수준을 첫 자리로 올립니다.
이전에는 control이 알파벳순 운으로 첫 자리였습니다. relevel(..., ref = "control") 이후에는 의도적으로 첫 자리입니다. 이 예측 변수를 쓰는 선형 회귀에서 treatment 계수는 이제 "처치가 대조와 어떻게 다른가?"라는, 여러분이 실제로 물은 질문에 답합니다. 모델의 범주형 계수가 헷갈려 보인다면 기준 수준부터 확인하세요.
(첫 자리만이 아니라 전체 순서를 바꾸려면 factor()에 완전한 levels = 벡터를 다시 전달하세요.)
고전적 함정: 팩터를 숫자로 변환하기
때때로 숫자가 팩터로 도착합니다. 대개 숫자가 아닌 값이 끼어 있던 CSV 열 때문입니다. 되돌리는 방법이 뻔해 보이지만 기억에 남을 만큼 잘못됩니다.
as.numeric(f)는 2 1 3을 돌려줍니다. 20, 10, 30이 아니라 수준 코드입니다. 수준은 알파벳순으로 "10", "20", "30"이 되므로 "20"은 수준 2이고 변환하면 2가 됩니다. 오류도 경고도 없이 그럴듯해 보이는 작은 정수가 조용히 데이터를 대체합니다. 이 하나 때문에 철회된 분석들이 있습니다.
올바른 경로는 문자를 거칩니다. as.numeric(as.character(f))는 먼저 라벨을 텍스트로 되찾은 뒤 그 텍스트를 숫자로 해석해 20 10 30을 줍니다. 이 관용구를 새기세요. 팩터에서 숫자로 갈 때는 항상 as.character()를 거칩니다.
droplevels()와 stringsAsFactors 이야기
팩터를 부분 선택해도 더 이상 나타나지 않는 범주까지 포함해 전체 수준 집합이 유지됩니다.
large를 걸러 낸 뒤에도 table()은 여전히 그것을 개수 0으로 보고합니다. 때로는 그것이 정확히 옳습니다(빈 범주가 보이길 원할 때죠). 그렇지 않을 때, 즉 개수 0인 그룹이 그래프를 어지럽히고 층화 분석을 망가뜨릴 수 있을 때 droplevels()가 관측값이 없는 수준을 버립니다.
오래된 코드나 Stack Overflow 답변을 읽을 때 필요한 역사적 사실이 하나 있습니다. R 4.0(2020) 이전에는 data.frame()과 read.csv()가 모든 문자 열을 자동으로 팩터로 변환했습니다. stringsAsFactors = TRUE가 기본값이었죠. 10년 치 튜토리얼에는 아무도 원하지 않은 팩터에 대한 우회책이 널려 있습니다. R 4.0부터 기본값은 FALSE입니다. 문자열은 문자열로 남고, 데이터 프레임의 변수가 진짜로 범주형인 곳에서 여러분이 의도적으로 팩터를 만듭니다. 그것이 올바른 습관입니다. 여러분이 고른 수준으로, 의도적으로, 명시적으로 만든 팩터 말입니다.
핵심 정리
- 팩터 = 정수 코드 + 수준 라벨입니다. 통계 함수에 변수가 범주형임을 알려 주는 장치입니다.
levels =로 범주 집합과 순서를 통제하고,labels =로 이름을 바꾸며,table()로 개수를 셉니다.ordered = TRUE는 진짜 순서형 척도에 대해 비교를 가능하게 합니다.- 첫 수준이 모델의 기저입니다.
relevel(f, ref = ...)로 의도적으로 정하세요. as.numeric(f)를 직접 쓰지 마세요. 항상as.numeric(as.character(f))입니다.- 부분 선택 후에는
droplevels()가 쓰이지 않는 수준을 정리합니다. R 4.0부터 문자열은 직접 팩터로 만들지 않는 한 문자열로 남습니다.
다음 순서는 데이터 프레임입니다. 팩터와 숫자와 텍스트가 하나의 표의 열들로 함께 살아가는 곳입니다.
자주 묻는 질문
R에서 팩터란 무엇인가요?
팩터는 범주형 데이터를 위한 R의 타입으로, 수준(levels)이라 불리는 고정된 가능성 집합에서 나온 값들입니다. 내부적으로는 정수 코드 벡터와 수준 라벨 표로 이루어져 있으며, 덕분에 통계 함수들이 범주를 자유 형식 텍스트가 아니라 범주로 올바르게 다룰 수 있습니다(개수를 세거나 모델에서 더미 코딩합니다).
R에서 팩터를 숫자로 어떻게 변환하나요?
문자를 거쳐 가세요. as.numeric(as.character(f))입니다. as.numeric(f)를 직접 호출하면 라벨이 보여 주는 값이 아니라 내부 수준 코드(1, 2, 3, ...)가 돌아옵니다. 그래서 "20"으로 표시되는 팩터가 2로 돌아올 수 있습니다. R에서 가장 흔한 조용한 버그 중 하나입니다.
R에서 relevel()은 무슨 일을 하나요?
선택한 수준을 첫 번째 자리로 옮깁니다. relevel(group, ref = "control")처럼 씁니다. 첫 수준은 lm()이나 glm() 같은 모델 함수가 다른 모든 수준을 비교하는 기준(기저) 범주이므로, 이를 의도적으로 고르면 회귀 계수가 여러분이 의도한 의미를 갖게 됩니다.
제거한 수준이 왜 팩터에 계속 보이나요?
팩터를 부분 선택해도 일부 수준이 더 이상 나타나지 않더라도 전체 수준 집합이 유지됩니다. 그래서 table()이 개수 0인 범주를 보여 주고 모델도 그 자리를 남겨 둡니다. 부분 집합에 droplevels()를 실행해 쓰이지 않는 수준을 버리세요.