Skip to Content
자격증ADsP10. R 기초 문법과 데이터 타입

이번 문서의 목표: R 콘솔에서 변수를 만들고 연산하는 방법, numeric·character·logical·factor 네 가지 데이터 타입의 차이, 결측치를 나타내는 NA와 빈 값을 나타내는 NULL의 차이, 그리고 타입이 섞였을 때 R이 어떤 규칙으로 형 변환을 하는지를 코드 실행 결과 수준까지 설명할 수 있다.

R의 변수 할당과 기본 연산

왜 필요한가

01편에서 R과 RStudio를 설치하고 콘솔·스크립트 개념을 익혔다. 이제 그 콘솔에 실제로 무언가를 입력해 볼 차례다. 프로그래밍 언어는 결국 “값을 저장하고, 그 값을 다시 꺼내 쓰는” 일의 반복이다. 값을 저장하는 상자에 이름을 붙이는 것이 변수(variable)이고, 그 상자에 값을 넣는 동작이 할당(assignment)이다. ADsP는 R 코드를 직접 짜게 하는 시험이 아니라 코드를 보고 실행 결과를 맞히는 시험이므로, 여기서 “코드를 눈으로 읽고 머릿속으로 실행해 보는 습관”을 들이는 것이 이 편 전체의 목표다.

쉽게 말하면: 변수는 이름표가 붙은 서랍이고, 할당은 그 서랍에 물건을 넣는 행동이다.

정의

R에서 변수에 값을 넣는 표준 연산자는 <-(왼쪽 화살표, less-than 기호와 hyphen을 붙여 화살표처럼 보이게 만든 것)다. 변수명 <- 값 형태로 쓰면 오른쪽 값이 왼쪽 변수에 저장된다. 등호(=)로도 할당이 되지만, R 커뮤니티와 시험 모두 <-를 표준으로 취급하므로 이 문서에서도 <-를 기본으로 쓴다.

x <- 10 y <- 3 x ## [1] 10

[1]은 값이 아니라 R이 출력할 때 붙이는 인덱스 표시다. 이 결과가 벡터의 1번째 원소부터 시작한다는 뜻이며, 뒤에 나올 11편의 벡터 인덱싱과 바로 연결되는 표기다.

R의 기본 산술 연산자는 더하기 +, 빼기 -, 곱하기 *, 나누기 /, 거듭제곱 ^(또는 **), 나머지 %%, 몫 %/%다.

x <- 10 y <- 3 x + y ## [1] 13 x %% y ## [1] 1 x %/% y ## [1] 3 2^3 ## [1] 8

x %% y는 10을 3으로 나눈 나머지 1을, x %/% y는 몫 3을 돌려준다. 나머지와 몫을 헷갈려 실행 결과를 반대로 고르게 하는 문제가 자주 나오므로, %%는 “나머지(remainder)”, %/%는 “정수 나눗셈의 몫(quotient)“이라고 소리 내어 구분해 외워 두는 편이 안전하다.

비교 연산자는 ==(같다), !=(같지 않다), >, <, >=, <=이며 결과는 항상 논리값(TRUE 또는 FALSE)이다.

x <- 10 x == 10 ## [1] TRUE x != 10 ## [1] FALSE

함수 정의와 호출

R에서 함수는 function(매개변수) { 실행할 코드 } 형태로 만들고, <-로 이름을 붙여 저장한다.

square <- function(n) { return(n^2) } square(5) ## [1] 25

return()은 함수가 무엇을 돌려줄지 명시하는 키워드다. R은 return()을 생략하고 함수 안 마지막 줄의 결과를 자동으로 돌려주기도 하지만, 시험에서는 명시적인 return()이 있는 코드가 더 흔하게 등장하므로 두 형태 모두 눈에 익혀 두자.

add_one <- function(n) { n + 1 } add_one(4) ## [1] 5

자주 틀리는 점

  • <-=을 모두 할당으로 쓸 수 있다고 착각해 함수 인자 안에서까지 <-를 남용하면, 함수 호출 문법과 헷갈리는 코드가 된다. 함수(인자 = 값)에서는 =을 쓰는 것이 관례다.
  • %%(나머지)와 %/%(몫)의 기호 모양이 비슷해서, 실행 결과 예측 문제에서 두 연산자의 역할을 바꿔 놓은 오답이 자주 등장한다.
  • ==(비교)과 <-(할당)를 눈으로 훑을 때 착각하기 쉽다. x = 10은 할당이지만 x == 10은 비교라는 것을 코드 한 줄 한 줄 정확히 읽어야 한다.

R의 4대 기본 데이터 타입

왜 필요한가

변수에는 숫자만 담을 수 있는 게 아니다. 이름 같은 글자, 참/거짓 같은 논리값, 범주형 구분값까지 다양한 성격의 데이터를 담아야 한다. R은 담긴 값의 성격에 따라 데이터 타입(data type)을 구분하고, 이 타입에 따라 어떤 연산이 가능한지가 달라진다. 타입을 착각하면 계산이 안 되거나 예상과 다른 결과가 나오므로, 지금 값이 어떤 타입인지 항상 확인하는 습관이 실제 데이터분석에서도, 시험에서도 핵심이다.

쉽게 말하면: 데이터 타입은 “이 값을 숫자로 볼지, 글자로 볼지, 참/거짓으로 볼지, 범주로 볼지”를 정하는 꼬리표다.

정의

R의 기본(basic) 데이터 타입은 크게 네 가지다.

타입영문의미예시
수치형numeric정수·실수를 포함하는 숫자10, 3.14, -2
문자형character따옴표로 감싼 텍스트"apple", "A"
논리형logical참(TRUE) 또는 거짓(FALSE) 두 값만 가짐TRUE, FALSE
범주형factor정해진 몇 개의 범주(level) 중 하나를 값으로 가짐"남", "여"를 범주로 갖는 성별

값의 타입을 확인하는 함수는 class()다.

class(10) ## [1] "numeric" class("apple") ## [1] "character" class(TRUE) ## [1] "logical"

factor는 뒤에서 별도 절로 깊게 다룬다. numeric·character·logical 세 가지가 R의 가장 기본적인 원자(atomic) 타입이고, factor는 이 원자 타입 중 문자형 데이터를 범주로 다루기 위해 R이 별도로 만든 특수한 타입이다.

논리형과 숫자의 관계

R 내부에서 TRUE는 1로, FALSE는 0으로 취급된다. 그래서 논리값을 산술 연산에 바로 쓸 수 있다.

TRUE + TRUE ## [1] 2 sum(c(TRUE, FALSE, TRUE, TRUE)) ## [1] 3

sum(c(TRUE, FALSE, TRUE, TRUE))는 TRUE 3개를 1씩 더한 값이므로 3이 나온다. 이 성질은 뒤에서 “조건을 만족하는 개수 세기”(예: 결측치 개수 세기)에 그대로 활용되므로 지금 정확히 이해하고 넘어가야 한다.

자주 틀리는 점

  • "10"(따옴표가 있는 문자형 10)과 10(따옴표 없는 수치형 10)을 같은 것으로 착각한다. "10" == 10은 TRUE(자동 형 변환이 일어남)지만 class("10")"character"다.
  • sum(TRUE, FALSE, TRUE)처럼 논리값을 더하는 코드의 결과를 “논리값끼리는 더할 수 없다”고 착각해 에러가 난다고 잘못 판단한다. 실제로는 TRUE·FALSE가 1·0으로 자동 변환되어 정상적으로 계산된다.

결측치: NA와 빈 값 NULL의 차이

왜 필요한가

실제 데이터에는 값이 비어 있는 경우가 흔하다. 설문조사에서 응답을 안 한 문항, 센서 고장으로 기록되지 않은 시각처럼 “값이 있어야 할 자리인데 값을 모른다”는 상황이 실무 데이터의 절반 이상을 차지한다고 해도 과언이 아니다. R은 이런 상황을 표현하는 특수한 값으로 NA(Not Available)를 두고 있다. 그런데 R에는 NA와 비슷해 보이지만 완전히 다른 개념인 NULL도 있다. 이 둘을 구분하지 못하면 데이터 전처리 단계에서 결측치를 놓치거나, 있지도 않은 값을 지우려다 오류를 내게 된다. ADsP 시험에서도 NA를 다루는 코드의 실행 결과를 묻는 문제가 회차마다 빠지지 않고 나온다.

쉽게 말하면: NA는 “자리는 있는데 그 안에 값이 없다”이고, NULL은 “그 자리 자체가 없다”이다.

정의

  • NA: 값이 존재해야 하는 자리에 값을 모른다는 뜻으로 채워 넣는 결측치 표시다. 벡터 안의 한 원소로 존재하므로 길이를 그대로 차지한다.
  • NULL: 값이 아예 정의되지 않았거나 존재하지 않음을 뜻한다. 벡터 안에 NULL을 넣으면 그 원소는 아예 사라진다.

이 차이를 length()(벡터의 길이, 즉 원소 개수를 세는 함수) 결과로 직접 확인해 보자.

a <- c(1, NA, 3) length(a) ## [1] 3

NA는 자리를 차지하는 값이므로 원소 3개(1, NA, 3)가 그대로 세어져 길이는 3이다.

b <- c(1, NULL, 3) length(b) ## [1] 2

반면 c(1, NULL, 3)NULL을 넣으면 R은 애초에 그 자리를 만들지 않는다. 그래서 벡터 b는 사실상 c(1, 3)과 같아지고 길이는 2다. 이것이 두 값의 가장 결정적인 차이다. NA는 “빈 의자”이고, NULL은 “의자 자체가 없음”이다.

is.na()is.null()로 각각을 판별한다.

x <- NA is.na(x) ## [1] TRUE is.null(x) ## [1] FALSE y <- NULL is.na(y) ## logical(0) is.null(y) ## [1] TRUE

is.na(y)의 결과가 logical(0)인 것도 함정 포인트다. yNULL이라는 것은 “길이가 0인 아무것도 없는 값”이라는 뜻이라서, is.na()가 검사할 원소 자체가 없어 결과도 길이 0짜리 빈 논리형 벡터가 된다. TRUE나 FALSE 하나가 나올 것이라 예상하면 틀리기 쉬운 지점이다.

결측치가 있는 데이터의 연산

NA가 하나라도 섞인 벡터에 sum()이나 mean() 같은 함수를 그대로 쓰면 결과도 NA가 된다. “값을 모르는 것이 하나라도 섞이면 전체 합계도 알 수 없다”는 논리다.

scores <- c(80, 90, NA, 70) sum(scores) ## [1] NA sum(scores, na.rm = TRUE) ## [1] 240

na.rm = TRUE(remove NA, 결측치를 제거하고 계산하라는 옵션)를 주면 NA를 뺀 나머지 값(80, 90, 70)만으로 합계를 계산해 240이 나온다.

자주 틀리는 점

  • NA == NA의 결과를 TRUE로 착각한다. 실제로는 “모르는 값과 모르는 값이 같은지도 알 수 없다”는 논리로 결과가 NA다.
NA == NA ## [1] NA
  • c(1, NA, 3)c(1, NULL, 3)length() 결과가 같을 것이라 착각한다. 위에서 본 대로 각각 3과 2로 다르다.
  • 결측치를 제거하는 함수를 na.rm이 아니라 rm.na처럼 순서를 바꿔 쓰는 실수가 잦다.

factor: 범주형 데이터와 level

왜 필요한가

성별(“남”/“여”), 등급(“상”/“중”/“하”)처럼 정해진 몇 가지 값 중 하나만 가지는 데이터를 문자형 그대로 다루면, R은 그 값들 사이에 어떤 순서나 개수 제한이 있는지 알지 못한다. factor는 이런 범주형 데이터를 위해 R이 제공하는 전용 타입으로, 가능한 값의 목록을 미리 등록해 두고 그 목록 안에서만 값을 갖도록 관리한다. 통계분석에서 범주형 변수를 다룰 때(예: 회귀분석의 더미 변수, 분류 모델의 목표 변수) factor 타입이 필수적으로 쓰이므로, ADsP 후반부 분류·회귀 단원을 이해하는 데도 이 개념이 선행 지식이 된다.

쉽게 말하면: factor는 “이 변수가 가질 수 있는 값은 이 목록 안의 것뿐”이라고 미리 정해 둔 범주형 상자다.

정의: level

factor를 만들면 R은 그 안에 등장한 고유한 값들을 알파벳(또는 가나다) 순으로 정렬해 level(수준, 범주형 변수가 가질 수 있는 값들의 목록)을 만든다.

gender <- factor(c("남", "여", "여", "남", "남")) gender ## [1] 남 여 여 남 남 ## Levels: 남 여 levels(gender) ## [1] "남" "여"

levels(gender)는 이 factor가 가질 수 있는 범주가 “남”과 “여” 두 가지뿐임을 보여준다. 내부적으로 R은 각 level에 정수 코드를 매겨 저장한다. 가나다 순 정렬이므로 “남”이 1번, “여”가 2번 코드를 받는다.

as.integer(gender) ## [1] 1 2 2 1 1

factor를 숫자로 바꿀 때의 함정

factor가 숫자처럼 보이는 값("1", "2", "3" 같은 문자)으로 만들어진 경우, 이것을 다시 숫자로 되돌리려는 실수가 매우 흔하다.

grade <- factor(c("3", "1", "2", "3")) levels(grade) ## [1] "1" "2" "3" as.numeric(grade) ## [1] 3 1 2 3

이 결과를 보면 원래 값(3, 1, 2, 3)과 as.numeric(grade)의 결과(3, 1, 2, 3)가 우연히 같아 보일 수 있다. 하지만 이것은 level이 “1”, “2”, “3” 순서로 정렬되어 코드가 원래 숫자와 마침 일치했기 때문이지, as.numeric()이 실제로 문자를 숫자로 변환한 것이 아니다. as.numeric()은 factor의 level 순번(내부 정수 코드)을 돌려줄 뿐, 겉보기 값 자체를 숫자로 바꿔주지 않는다. 이 차이가 실제로 드러나는 예를 보자.

score <- factor(c("50", "10", "90")) levels(score) ## [1] "10" "50" "90" as.numeric(score) ## [1] 2 1 3

원래 값은 50, 10, 90인데 as.numeric(score)는 2, 1, 3을 돌려준다. level이 문자열 기준 오름차순(“10” < “50” < “90”)으로 정렬되면서 매겨진 순번이 그대로 나온 것이다. factor를 원래의 숫자 값으로 되돌리려면 반드시 문자형을 거쳐야 한다.

as.numeric(as.character(score)) ## [1] 50 10 90

as.character(score)로 먼저 겉보기 문자값(“50”, “10”, “90”)을 꺼낸 뒤에 as.numeric()을 적용해야 원래 숫자가 정확히 복원된다. 이 두 줄짜리 코드는 ADsP 시험에서 factor를 숫자로 바꾸는 정석 절차로 여러 번 반복 출제되는 패턴이므로 통째로 외워 둘 가치가 있다.

자주 틀리는 점

  • as.numeric(factor형변수)가 원래 숫자값을 돌려준다고 착각한다. 실제로는 level의 순번을 돌려준다.
  • level의 정렬 순서가 항상 사람이 기대하는 순서(입력한 순서, 또는 숫자 크기 순서)라고 착각한다. 실제로는 기본적으로 문자열 기준 오름차순 정렬이다.
  • factor()로 만든 변수와 원래의 character 벡터가 겉보기에 똑같이 출력되어 같은 타입이라고 착각한다. class()로 찍어 보면 각각 "factor""character"로 다르다.

형 변환(type conversion)

왜 필요한가

서로 다른 타입의 값을 함께 연산하거나, 저장된 형식과 다른 형식으로 데이터를 다뤄야 할 때 형 변환(type conversion, 타입 캐스팅)이 필요하다. R은 상황에 따라 자동으로 형을 바꿔주기도 하고(암묵적 변환), as.numeric()처럼 명시적으로 지정한 함수로 바꾸기도 한다(명시적 변환).

쉽게 말하면: 형 변환은 “숫자를 글자로, 글자를 숫자로, 참/거짓을 0과 1로” 서로 다른 타입 사이를 오가는 작업이다.

정의: 주요 변환 함수

함수역할예시
as.numeric()다른 타입을 수치형으로 변환as.numeric("10")10
as.character()다른 타입을 문자형으로 변환as.character(10)"10"
as.logical()다른 타입을 논리형으로 변환as.logical("TRUE")TRUE
as.factor()다른 타입을 factor로 변환as.factor(c("A","B"))
as.numeric("10") ## [1] 10 as.numeric("abc") ## [1] NA

as.numeric("abc")처럼 숫자로 해석할 수 없는 문자를 변환하려 하면 에러가 나는 대신 NA가 나오고, “강제로 형 변환할 수 없다”는 경고 메시지가 함께 출력된다. 에러로 프로그램이 멈추지 않고 NA로 조용히 대체된다는 점이 중요한 함정이다.

암묵적 형 변환의 우선순위

c()처럼 여러 값을 하나의 벡터로 묶을 때, 서로 다른 타입이 섞여 있으면 R은 벡터 전체를 가장 표현력이 넓은 타입 하나로 통일한다. 우선순위는 character > numeric > logical 순이다.

c(1, "2", TRUE) ## [1] "1" "2" "TRUE" class(c(1, "2", TRUE)) ## [1] "character"

숫자 1, 문자 "2", 논리값 TRUE가 섞이면 문자형이 가장 표현력이 넓으므로 셋 다 문자형으로 바뀐다. 그 결과 숫자였던 1"1"이라는 문자열이 되어, 더 이상 산술 연산에 그대로 쓸 수 없다.

c(1, TRUE, FALSE) ## [1] 1 1 0 class(c(1, TRUE, FALSE)) ## [1] "numeric"

문자형이 섞이지 않은 경우에는 수치형이 논리형보다 표현력이 넓으므로, TRUE·FALSE가 각각 1과 0으로 바뀌어 수치형 벡터가 된다.

자주 틀리는 점

  • c(1, "2", TRUE)의 결과 타입을 수치형으로 착각한다. 문자형이 하나라도 섞이면 전체가 문자형으로 통일된다.
  • as.numeric("10.5")가 실패한다고 착각한다. 소수점을 포함한 숫자 형태의 문자열은 정상적으로 10.5(수치형)로 변환된다.
  • 형 변환의 우선순위를 numeric > character > logical처럼 잘못 기억한다. 실제로는 character가 가장 우선이다.

핵심 정리

  • 변수 할당은 <-, 산술 연산은 + - * / ^ %% %/%이며 %%는 나머지·%/%는 몫이다.
  • R의 4대 기본 데이터 타입은 numeric·character·logical·factor이며, class()로 타입을 확인한다.
  • NA는 값이 없는 자리(길이를 차지함), NULL은 자리 자체가 없음(길이를 차지하지 않음)이며 length() 비교로 이 차이가 뚜렷이 드러난다.
  • NA가 섞인 벡터의 sum()·mean()은 기본적으로 NA가 되며, na.rm = TRUE로 제거하고 계산할 수 있다.
  • factor는 level(범주 목록)을 가지며, as.numeric(factor)은 원래 값이 아니라 level 순번을 돌려준다. 원래 숫자를 얻으려면 as.numeric(as.character(factor))를 거쳐야 한다.
  • 여러 타입이 섞인 벡터를 만들면 character > numeric > logical 우선순위로 자동 형 변환된다.

마무리 복습

문제 14지선다
R에서 x = 17, y = 5일 때 x %% y와 x %/% y의 값으로 옳은 것은?
문제 24지선다
다음 중 R의 4대 기본 데이터 타입에 해당하지 않는 것은?
문제 34지선다
다음 코드를 실행했을 때 각 length()의 결과로 옳은 것은? a = c(5, NA, 8); b = c(5, NULL, 8)
문제 44지선다
다음 코드의 실행 결과로 옳은 것은? scores = c(70, NA, 90); sum(scores)
문제 54지선다
다음 코드를 실행했을 때 as.numeric(grade)의 결과로 옳은 것은? grade = factor(c('50', '10', '90'))
문제 64지선다
원래의 숫자값이 문자로 저장된 factor(예: factor(c('50', '10', '90')))에서 원래 숫자값 그대로를 얻으려면 어떤 코드를 써야 하는가?
문제 74지선다
다음 코드를 실행했을 때 결과 벡터의 class()로 옳은 것은? mixed = c(1, '2', TRUE)
문제 84지선다
다음 코드의 실행 결과로 옳은 것은? NA == NA

참고 자료

Last updated on