R 오류 메시지 읽기
R의 오류에는 두 부분이 있고 둘 다 유용합니다:
Error in "10" + 5 : non-numeric argument to binary operator
Error in 다음에 오는 것이 호출 입니다 - 실패한 정확한 코드 조각("10" + 5)이죠. 콜론 다음에 오는 것이 조건 입니다 - 무엇이 잘못되었는지(non-numeric argument to binary operator)를 알려 줍니다. 호출을 먼저 읽으세요: 어디서 인지를 알려 주며, 인용된 코드 안에서 문제를 바로 볼 수 있는 경우가 매우 많습니다. 그다음 왜 인지는 조건을 읽으면 됩니다.
빠르게 디버깅하는 사람과 고생하는 사람을 가르는 습관이 두 가지 있습니다. 첫째, 메시지를 실제로 읽으세요 - R의 메시지는 대개 정확하며 다만 표현이 간결할 뿐입니다. 둘째, 마지막 오류가 아니라 첫 오류를 디버깅하세요: 스크립트 초반의 실패 하나가 하류의 "object not found" 오류 더미로 번지는데, 원래 문제를 고치면 그것들이 모두 사라집니다. 이 페이지의 나머지는 가장 자주 만날 메시지의 해독기이고, 그다음이 읽는 것만으로 부족할 때의 도구들입니다.
이름 오류: 찾을 수 없음
Error: object 'total' not found - R이 알고 있는 모든 환경을 뒤졌지만 그 이름의 변수가 없다는 뜻입니다. 거의 모든 경우가 세 가지 원인에 해당합니다:
- 대소문자를 포함한 오타. R은 대소문자를 구분합니다:
Total,total,TOTAL은 서로 다른 세 이름이며, R은 어느 쪽을 뜻했는지 추측하지 않습니다. - 정의하는 줄이 실행되지 않음. 스크립트에
total <- sum(x)를 썼지만 이번 세션에서 실행한 적이 없는 경우입니다 - R을 재시작한 뒤에 흔한데, 스크립트 파일에는 그 줄이 보이지만 세션은 그것을 본 적이 없죠. 스크립트를 처음부터 실행하세요. - 잘못된 환경. 함수 안에서 만들어진 변수는 그 호출 안에서 태어나고 죽습니다. 함수 바깥에서 그것을 쓰는 것은 더 이상 존재하지 않는 무언가를 요구하는 셈입니다 - 대신 값을 반환하세요.
Error: could not find function "read_excel" - 같은 이야기이지만 함수 이름에 대한 것입니다. 열에 아홉은 설치는 했지만 이번 세션에 불러오지 않은 패키지 안에 함수가 있는 경우입니다:
library(readxl) # the fix: loading is per-session, installing is per-machine
df <- read_excel("data.xlsx")
library(readxl) 자체가 오류를 낸다면 패키지가 설치되지 않은 것이니 install.packages("readxl")을 먼저 하세요. 그리고 그 함수가 base R의 것이라면 오타를 낸 것입니다(lenght()는 누구나 거치는 통과의례입니다).
문법 오류: unexpected symbol
Error: unexpected symbol in "..."(그리고 사촌인 unexpected ')', unexpected string constant)는 R이 코드를 파싱조차 하지 못했다는 뜻입니다. 메시지는 R이 알아챈 지점을 가리키는데, 실제 실수가 있는 곳보다 뒤인 경우가 많습니다. 흔한 용의자들입니다:
mean(x na.rm = TRUE) # missing comma - should be mean(x, na.rm = TRUE)
name <- "Ada # unclosed quote - swallows the following lines
total <- sum(c(1, 2, 3) # unclosed paren - the error fires lines later
지목된 줄이 멀쩡해 보인다면 실수는 거의 언제나 그 위 에 있습니다: 파일 앞쪽에 닫히지 않은 따옴표, 괄호, 중괄호가 있는 것이죠. 짝을 강조해 주는 코드 편집기라면 몇 초 만에 찾아냅니다.
타입과 인덱싱 오류 해독하기
non-numeric argument to binary operator - 숫자가 아닌 무언가로 계산을 했다는 뜻이며, 보통은 텍스트로 들어온 숫자입니다(임포트가 전형적인 원인입니다 - 데이터 타입에서 다뤘듯이 단어가 하나 섞인 열은 문자형으로 들어옵니다). 깨지는 버전입니다:
x <- "10"
x + 5
# Error in x + 5 : non-numeric argument to binary operator
그리고 해결책 - 변환한 뒤 계산하기입니다:
subscript out of bounds - [[ ]]로 원소 수보다 큰 n번째 위치를 요청했다는 뜻입니다:
scores <- list(ada = 92, grace = 88)
scores[[3]]
# Error in scores[[3]] : subscript out of bounds
인덱싱 전에 length()를 확인하거나, 더 나아가 이름으로 요청하세요(scores[["grace"]]). 그러면 순서가 바뀌어도 깨지지 않습니다. 비대칭에 유의하세요: 홑 대괄호는 더 관대합니다 - 벡터에 범위를 벗어난 [ ]를 쓰면 오류 대신 조용히 NA를 반환하는데, 시끄러운 버그를 조용한 버그와 맞바꾸는 셈입니다.
$ operator is invalid for atomic vectors - $는 리스트와 데이터 프레임의 것입니다. 이름 있는 벡터에는 대괄호를 쓰세요:
([[ ]]는 값만 주고 [ ]는 이름을 붙인 채로 줍니다.) 이 오류는 데이터 프레임을 기대한 자리에서 상류의 무언가가 벡터를 반환했다는 뜻인 경우가 많습니다 - 연산자만 바꾸지 말고 그 가정을 확인해 보세요.
argument is of length zero - if ()가 아무것도 들어 있지 않은 조건을 받았다는 뜻이며, 거의 언제나 없는 리스트 원소나 아무것도 반환하지 않은 함수에서 슬며시 들어온 NULL입니다:
threshold <- NULL
if (threshold > 5) print("big")
# Error in if (threshold > 5) print("big") : argument is of length zero
검사를 방어하세요 - 그리고 &&는 답이 정해지는 즉시 평가를 멈추므로 비교가 NULL에 대해 실행되지 않는다는 점에 유의하세요:
(관련 오류인 missing value where TRUE/FALSE needed는 NULL 대신 NA로 인한 같은 실패이며, 이때의 방어책은 결측값에서 다루는 is.na()입니다.)
replacement has length zero - 같은 병의 할당 버전입니다: x[2] <- numeric(0)은 칸 하나를 값 0개로 채우려 합니다. 우변을 만들어 낸 무언가가 빈 값을 돌려준 것이니 할당이 아니라 그쪽을 디버깅하세요.
경고는 오류가 아닙니다 - 바로 그 점이 위험합니다
오류는 실행을 멈추지만 경고는 멈추지 않습니다. R은 계산을 끝내고 결과를 건네준 뒤에 유보 사항을 언급합니다. 그 결과는 괜찮을 때도 있고 조용히 틀렸을 때도 있습니다:
두 줄 모두 완료됩니다. 첫 번째는 짧은 벡터를 재활용하며 longer object length is not a multiple of shorter object length라고 경고하는데, 길이 2 벡터를 길이 3에 대해 재활용하는 것은 거의 누구의 의도도 아닙니다. 두 번째는 NAs introduced by coercion이라고 경고하고 구멍 난 벡터를 건네주는데, 그러면 하류의 모든 mean()이 NA를 반환하게 됩니다. 두 경고 모두 지나칠 잡음이 아니라 조사할 버그로 취급하세요. 스크립트에서는 options(warn = 2)로 이 태도를 강제할 수 있는데, 모든 경고를 오류로 승격시켜 아무것도 몰래 빠져나가지 못하게 합니다.
tryCatch()로 실패 처리하기
때로는 오류가 예상된 것입니다 - 수백 개 파일 중 하나가 손상되었거나, 행 하나가 불량이거나 - 그럴 때는 죽는 대신 처리하고 넘어가고 싶습니다. tryCatch()는 위험한 표현식을 처리기로 감쌉니다:
작동 방식은 이렇습니다: 본문 블록이 성공하면 그 값이 결과가 됩니다. 오류가 나면 대신 error = 처리기가 실행되고 그것의 반환값(여기서는 NA)이 결과가 되어 스크립트가 계속 진행됩니다. conditionMessage(e)가 기록용으로 원래 메시지를 되찾아 줍니다. finally =는 성공이든 실패든 모든 경우에 실행되며, 연결을 닫는 것 같은 정리 작업이 들어갈 자리입니다 - 위 예제에서 각 결과보다 먼저 출력되는 것을 볼 수 있습니다.
warning = 처리기도 있습니다 - tryCatch(as.numeric(x), warning = function(w) NA)는 앞 절의 강제변환 경고를 그냥 넘기지 않고 붙잡습니다. 주의할 점 하나: 아무것도 기록하지 않고 대비책만 반환하는 처리기는 실패를 처리하는 것이 아니라 감추는 방법입니다. 언제나 conditionMessage()를 기록하세요 - 미래의 여러분에게 필요합니다.
실패 지점 찾기: traceback(), browser(), 그리고 정직한 출력
중첩된 함수 호출 깊숙한 곳에서 오류가 나오면 메시지만으로는 어떤 호출 사슬을 거쳤는지 알 수 없습니다. 오류 직후에 traceback()을 실행하세요:
f <- function(x) g(x)
g <- function(x) stop("boom")
f(1)
# Error in g(x) : boom
traceback()
# 2: g(x)
# 1: f(1)
실패한 순간의 호출 스택을 출력합니다 - 한쪽 끝에는 여러분의 호출이, 다른 쪽 끝에는 실패한 호출이 있습니다. 반드시 그다음에 바로 실행해야 합니다. 다른 오류가 발생하면 스택은 버려집니다.
실시간으로 들여다보려면 browser()가 심어 둔 자리에서 실행을 멈추고 함수 안 의 대화형 프롬프트로 데려다줍니다 - 변수를 살펴보고, n으로 한 단계씩 진행하고, c로 계속하고, Q로 빠져나오세요. debug(f)는 코드를 고치지 않고 같은 일을 합니다: f에 표시를 해 두어 다음 호출이 브라우저에서 열리게 합니다(undebug(f)로 되돌립니다).
그리고 아무도 학회 슬라이드에 올리지 않지만 모두가 쓰는 기법이 있습니다: 출력하기입니다. 검문 지점마다 print()나 cat()을 뿌리고 실행해서 현실이 기대와 어긋나기 시작하는 지점을 찾으세요. 정당하고 빠르며, 스크립트에서는 대개 가장 실용적인 도구입니다. 그 절친은 str()인데, 아마도 모든 R 오류의 절반쯤이 품고 있는 질문 - "이 객체는 대체 무엇 인가?" - 에 답해 줍니다:
한 번의 간결한 출력으로 알 수 있습니다: 리스트이고, 원소가 둘이며, 하나는 정수 벡터, 하나는 이런 열과 타입을 가진 데이터 프레임이라는 것을요. $가 실패하거나 계산이 이상하게 굴 때는 이론을 세우기 전에 객체에 str()을 실행하세요 - 답은 대개 바로 거기 있습니다("...아, 내 데이터 프레임을 담고 있는 길이 1짜리 리스트였구나").
핵심 정리
- 메시지를 읽으세요:
Error in다음이 어디인지를, 콜론 다음이 왜인지를 말합니다. 가장 시끄러운 오류가 아니라 첫 오류를 고치세요. object not found= 오타, 아직 실행하지 않은 코드, 또는 함수 안에서만 존재했던 변수입니다.could not find function= 거의 언제나 빠뜨린library()호출입니다.unexpected symbol은 문법을 파싱할 수 없다는 뜻이며, 진짜 실수는 종종 지목된 지점보다 앞의 닫히지 않은 따옴표나 괄호입니다.- 타입/인덱스 관련 고전들 -
non-numeric argument,subscript out of bounds, 원자 벡터의$,argument is of length zero- 은 모두 객체가 무엇인지에 대한 잘못된 가정을 가리킵니다.str()이 그 가정을 한 번의 호출로 확인해 줍니다. - 경고는 실행을 멈추지 않으며, 바로 그래서 주의를 기울일 만합니다 - 결과가 조용히 틀렸을 수 있습니다.
tryCatch(error =, warning =, finally =)는 예상된 실패를 죽지 않고 처리합니다(언제나conditionMessage()를 기록하세요). 오류 직후의traceback()은 호출 사슬을 보여 주고,browser()/debug()는 그 안에서 멈추며,print()디버깅도 정당한 작업입니다.
다음 주제: 사실은 오류가 아닌 많은 "오류"가 세 글자짜리 값 하나로 거슬러 올라갑니다 - NA, 그리고 결측값이 R의 모든 계산을 어떻게 흘러 다니는지입니다.
자주 묻는 질문
R에서 "object 'x' not found"는 무슨 뜻인가요?
R이 x라는 변수를 찾았지만 검색한 어떤 환경에도 그런 이름이 없다는 뜻입니다. 가능성이 높은 순서대로 원인은 이렇습니다: 이름의 오타(R은 대소문자를 구분하므로 Total은 total이 아닙니다), x를 만드는 줄을 이번 세션에서 아직 실행하지 않았거나, x가 함수 안에서 만들어졌는데 바깥에서 쓰려는 경우입니다.
R에서 "could not find function"은 무슨 뜻인가요?
그 함수가 이번 세션에 불러오지 않은 패키지 안에 있다는 뜻입니다. 패키지 설치는 기계당 한 번이고 library()는 세션당 한 번인데, library() 호출을 잊는 것이 보통의 원인입니다. library() 자체가 실패한다면 패키지가 설치되지 않은 것입니다. 함수 이름의 오타도 같은 오류를 냅니다.
R에서 tryCatch로 오류를 어떻게 처리하나요?
위험한 표현식을 감싸세요: tryCatch(expr, error = function(e) fallback, warning = function(w) fallback, finally = cleanup). expr이 실패하면 스크립트가 죽는 대신 일치하는 처리기가 실행되고, 그 처리기가 반환하는 값이 결과가 됩니다. 처리기 안의 conditionMessage(e)는 기록용으로 원래 메시지를 알려 줍니다.
R에서 traceback()은 무엇을 하나요?
오류 직후에 실행하면 traceback()은 오류가 발생한 시점에 활성화되어 있던 함수 호출의 사슬을 출력합니다 - 한쪽 끝에는 여러분의 호출이, 다른 쪽 끝에는 실패한 호출이 있죠. 무언가를 고쳐 주지는 않지만 어디를 봐야 할지 알려 주는데, 중첩된 함수 깊숙한 곳에서 나온 오류라면 그것이 싸움의 절반입니다.