R 기초

데이터 분석 · R기초

이 페이지의 목차

핵심 요약

R의 자료형·벡터·인덱싱·벡터화와 재활용, 함수·제어문, NA 처리, 데이터프레임과 apply 계열을 실제 코드 결과와 함께 정리한다.

본문

R 객체·원자 자료형·벡터·리스트·행렬과 형 변환

R에서 값은 객체에 저장하며, 가장 기본적인 자료구조는 벡터다. 원자 벡터는 한 벡터 안에서 공통 자료형을 사용한다. logical → integer → double → character처럼 서로 다른 형을 c()로 묶으면 더 넓은 형으로 강제 변환될 수 있다.

R
x <- c(TRUE, 1L, 2.5, "3")
typeof(x)
# [1] "character"

벡터와 달리 리스트는 서로 다른 자료형의 원소를 함께 보관할 수 있다. 행렬은 같은 자료형의 원소를 2차원으로 배열한 구조다.

벡터를 만들 때 자주 쓰는 수열·반복과 논리값의 수치 문맥도 함께 익힌다.

R
seq(2, 10, by = 2)
# [1]  2  4  6  8 10

rep(c("A", "B"), each = 2)
# [1] "A" "A" "B" "B"

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

seq()는 지정 간격의 수열을 만들고, rep(..., each=2)는 각 원소를 두 번씩 반복한다. sum()처럼 수치가 필요한 문맥에서는 논리값이 TRUE=1, FALSE=0으로 취급될 수 있다.

인덱싱·벡터화·재활용·연산자 우선순위·NA/NaN

R 인덱스는 1부터 시작한다. 양의 정수는 선택, 음의 정수는 제외에 사용한다.

R
x <- c(5, 10, 15, 20)
x[c(4, 2)]
# [1] 20 10

x[-c(1, 3)]
# [1] 10 20

벡터화 연산에서는 원소끼리 계산한다. 길이가 다른 벡터는 짧은 벡터를 반복해 맞추는 재활용(recycling)이 일어날 수 있다.

R
c(1, 2, 3, 4) + c(10, 20)
# [1] 11 22 13 24

긴 길이가 짧은 길이의 배수가 아니면 의도하지 않은 재활용이 발생할 수 있으므로 경고와 결과를 함께 확인한다.

연산자 우선순위도 코드 결과에 영향을 준다. :는 일반 덧셈보다 우선하므로 다음은 (1:3)+4로 계산된다.

R
1:3 + 4
# [1] 5 6 7

matrix(1:6, nrow=2)는 기본적으로 열 방향으로 값을 채운다. 행 방향으로 채우려면 byrow=TRUE를 명시한다.

NA는 값이 없음을 나타내며 대부분의 계산에 전파된다. NaN은 정의되지 않은 수치 계산 결과이고 is.na(NaN)도 TRUE다.

R
mean(c(2, NA, 6))
# [1] NA

mean(c(2, NA, 6), na.rm = TRUE)
# [1] 4

연산자 우선순위에서는 이 단원 범위에서 ^ → : → 이항 + 순서를 먼저 기억하면 된다. ^는 오른쪽에서 왼쪽으로 결합한다.

R
1:3 + 2^2
# [1] 5 6 7

2 ^ 2 ^ 3
# [1] 256

리스트와 원자 벡터의 [·[[는 반환 구조가 다르다. 리스트에서 [는 부분 리스트를, [[는 원소 자체를 꺼낸다. 이름 있는 원자 벡터에서는 x[1]이 선택된 원소의 names를 유지하지만 x[[1]]은 원소 값 자체를 반환한다. 양의 인덱스가 벡터 범위를 넘으면 해당 추출 위치는 NA가 될 수 있다.

R
z <- list(code = c("A", "B"), n = 2L)
z[1]
# $code
# [1] "A" "B"
z[[1]]
# [1] "A" "B"

x <- c(a = 4, b = 7)
x[1]
# a
# 4
x[[1]]
# [1] 4

c(8, 9, 10)[c(1, 4)]
# [1]  8 NA

NA는 ‘값을 모름’을 뜻하므로 논리식에서도 확정 가능한 부분만 결정된다. 문자열 "NA"는 결측값이 아니다.

R
c(FALSE & NA, TRUE | NA, NA == NA, is.na(NA))
# [1] FALSE  TRUE    NA  TRUE

is.na(c(NA, "NA", NA_character_))
# [1]  TRUE FALSE  TRUE

함수·기본 인자·if/ifelse·반복문·next/break

함수는 입력과 처리 규칙을 묶어 재사용한다.

R
add <- function(x, y = 2) {
  x + y
}
add(3)
# [1] 5

if는 길이 1의 논리 조건에 따른 제어 흐름에 쓰고, ifelse()는 벡터 원소별 조건 처리에 편리하다.

R
ifelse(c(TRUE, FALSE, NA), 10, 0)
# [1] 10  0 NA

반복문에서는 next가 현재 반복만 건너뛰고, break가 반복 전체를 끝낸다.

R
s <- 0
for (i in 1:5) {
  if (i == 3) next
  s <- s + i
}
s
# [1] 12

for (i in v)의 반복 대상은 반복을 시작할 때 평가된다. 반복 중 원래 객체 v를 바꿔도 이미 시작된 반복의 남은 시퀀스가 자동 교체되는 것은 아니다.

R
v <- 1:3
out <- integer(0)

for (i in v) {
  out <- c(out, i)
  if (i == 1) v <- 10:12
}
out
# [1] 1 2 3

함수 안에서 <-로 만든 같은 이름의 객체는 기본적으로 함수의 지역 환경에 생긴다. 따라서 다음 예에서 함수 내부 a <- 2는 전역 a <- 10을 바꾸지 않는다.

R
a <- 10
f <- function(x) {
  a <- 2
  x + a
}
c(f(3), a)
# [1]  5 10

데이터프레임 구조·추출·subset과 결측값을 포함한 요약

데이터프레임은 열마다 자료형이 달라도 되는 2차원 표 구조다. $, [행, 열], subset() 등을 이용해 필요한 부분을 고른다.

R
d <- data.frame(
  id = 1:3,
  score = c(80, NA, 90)
)

subset(d, !is.na(score) & score >= 85)
#   id score
# 3  3    90

비교식에 NA가 섞이면 조건도 NA가 될 수 있으므로 결측 여부를 명시적으로 처리하는 습관이 중요하다.

검수 기준인 base R 4.6.1의 기본 동작에서는 data.frame()에 넣은 문자열이 자동으로 factor가 되지 않고 character로 유지된다.

R
d0 <- data.frame(group = c("A", "B"))
class(d0$group)
# [1] "character"

데이터프레임의 단일 인덱스 d[1]은 첫 번째 열을 가진 data.frame을 유지한다. 반면 d[, 1]은 기본 drop 동작으로 열 벡터가 될 수 있다.

R
d <- data.frame(a = 1:3, b = 4:6)
class(d[1])
# [1] "data.frame"
typeof(d[, 1])
# [1] "integer"
c(nrow(d), ncol(d))
# [1] 3 2

subset()의 행 조건이 NA인 행은 선택 결과에서 제외된다.

R
d2 <- data.frame(
  x = 1:4,
  keep = c(TRUE, NA, FALSE, TRUE)
)
subset(d2, keep)$x
# [1] 1 4

apply·lapply·sapply와 코드 결과 계산

apply()는 행렬·배열의 행 또는 열 방향 계산에, lapply()는 리스트 형태 결과에, sapply()는 가능한 경우 결과를 단순화할 때 자주 사용한다.

R
m <- matrix(1:6, nrow = 2, byrow = TRUE)
m
#      [,1] [,2] [,3]
# [1,]    1    2    3
# [2,]    4    5    6

apply(m, 1, sum)
# [1]  6 15

apply(m, 2, mean)
# [1] 2.5 3.5 4.5

lapply(list(1:3, 4:5), length)은 길이 2의 리스트를 반환하고, sapply(list(1:3, 4:5), length)은 가능한 경우 c(3, 2) 같은 벡터로 단순화한다.

apply()를 data.frame에 쓰면 내부적으로 행렬로 강제 변환될 수 있다. 서로 다른 자료형이 섞여 있으면 공통 자료형으로 맞춰지므로, 숫자 열과 문자 열이 함께 있으면 전체가 character 행렬이 될 수 있다.

R
d <- data.frame(
  a = 1:2,
  b = c("x", "y")
)
apply(d, 1, paste, collapse = "-")
# [1] "1-x" "2-y"

따라서 혼합형 데이터프레임에서 apply()를 사용할 때는 계산 전 변환될 자료형을 먼저 확인한다.

복습 문제

답을 생각한 뒤 정답과 근거를 펼쳐 보세요.

01

c(1,2,3,4) + c(10,20)의 결과는?

정답과 근거

11 22 13 24다. 길이 2인 벡터 10,20이 두 번 재활용된다.

02

mean(c(2,NA,6), na.rm=TRUE)의 결과는?

정답과 근거

4다. na.rm=TRUE가 NA를 제외하므로 (2+6)/2=4다.

03

ifelse(c(TRUE,FALSE,NA), 10, 0)의 결과는?

정답과 근거

10 0 NA다. 세 번째 조건 자체가 NA이므로 결과도 NA가 된다.

04

위의 2×3 행렬 m에서 apply(m, 1, sum)의 결과는?

정답과 근거

6 15다. 첫 행 1+2+3=6, 둘째 행 4+5+6=15다.