R에서 vector(엄밀히 말하면 atomic vector)는 같은 type의 값들을 모아 놓은 것이에요. 흔히 integer, numeric, character, logical이고, 드물게 complex나 raw도 있어요.
메모리에 연속적으로 저장되며, C의 배열과 비슷해요.
서로 다른 타입의 값을 함께 주면 R은 그것들을 조용히 하나의 공통 타입, 보통 character로 변환하는데, 이 때문에 예상치 못한 결과가 나올 수 있어요.
v <- c(2, 5.4, "txt")
v
#> [1] "2" "5.4" "txt"
typeof(v)
#> [1] "character"
값을 모두 가지고 있다면 c()("concatenate"의 줄임말)를 사용해요:
x <- 10
v <- c(4, 7, x)
c() 함수는 아주 유연해서 다른 벡터나 범위를 입력으로 넣을 수도 있어요.
c()는 어떤 개수와 종류의 입력이든 받아서 하나의 벡터로 펼쳐 줘요.
벡터가 작게 시작해서 실행 중에 많이 커진다면(예를 들어 루프 안에서 값을 추가하는 경우), 나중에 실제 데이터로 채울 수 있는 긴 벡터를 미리 할당해 두는 편이 훨씬 빠를 수 있어요:
v <- vector(length = 100)
이렇게 하면 객체를 복사할 일이 줄어드는데, 긴 벡터에서는 복사가 느리고 메모리도 많이 잡아먹는 작업이에요.
연속된 값들의 범위(증가하거나 감소하는)에는 : 표기를 사용할 수 있어요.
v <- 1:5 # equivalent to c(1, 2, 3, 4, 5)
w <- 10:7 # c(10, 9, 8, 7)
R에서 범위는 별도의 타입이 아니라, 벡터를 만드는 간단한 축약 표기일 뿐이에요.
seq() 함수를 쓰면 더 세밀하게 제어할 수 있어요:
# specify step size
seq(2, 11, by = 3) # c(2, 5, 8, 11)
# specify number of values, evenly-spaced
seq(2.5, 4, length.out = 5) # c(2.500, 2.875, 3.250, 3.625, 4.000)
rep() 함수를 쓰면 반복 패턴을 쉽게 만들 수 있어요:
count <- 3
rep(7, count)
#> [1] 7 7 7
rep(1:3, count)
#> [1] 1 2 3 1 2 3 1 2 3
rep(1:3, each = 2) # the "each" keyword is required in this case
#> [1] 1 1 2 2 3 3
몇 가지 범위는 내장 상수로 미리 정의되어 있어요:
letters # LETTERS is the UPPERCASE equivalent
#> [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
month.name
#> [1] "January" "February" "March" "April" "May" "June" "July" "August"
#> [9] "September" "October" "November" "December"
month.abb
#> [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
연산은 벡터를 따라 자동으로 원소별로 적용돼요. 다른 언어에서 흔히 쓰는 루프, 리스트 컴프리헨션, 재귀는 필요 없고 (성능 때문에) 가능하면 피하는 게 좋아요.
2 + 1:3 # c(3, 4, 5)
2 * 1:3 # c(2, 4, 6)
12 / 1:3 # c(12 6 4)
2 ^ (1:3) # c(2, 4, 8) : exponentiation, operator precedence order here needs ()
벡터 두 개를 짝지어 연산할 수도 있는데, 원소들이 짝을 이루어 처리돼요:
v <- 1:3
w <- 5:7
v + w
#> [1] 6 8 10
v * w
#> [1] 5 12 21
v ^ w
#> [1] 1 64 2187
이 기능은 처음 보이는 것보다 훨씬 강력하고, 뒤에 나오는 개념에서 더 자세히 살펴볼 거예요.
많은 언어에서 그렇듯, 대괄호 안에 인덱스를 넣어 접근할 수 있어요:
v <- 4:7
v[2]
#> [1] 5
v[2:4]
#> [1] 5 6 7
하지만 주의하세요: R은 기본적으로 1부터 시작하는 인덱싱을 사용해요. 이건 과학용 언어(Fortran, Matlab, Julia)에서는 흔한 방식이지만, C와 그 뒤를 이은 많은 언어의 0부터 시작하는 인덱싱과는 달라요.
범위는 위의 예제에서처럼 쓸 수도 있고, 아주 유연한 c() 함수 안에서도 쓸 수 있어요:
v <- c(4, 7, 10)
w <- c(2, v[2:3], 11) # c(2, 7, 10, 11)
vectors는 (R의 대부분의 것들처럼) 변경 가능하게 다룰 수 있어요. 하나 또는 여러 원소에 값을 쓸 때도 비슷한 유연한 문법을 사용해요:
v <- 1:10
v[2] <- 42
v[4:6] <- c(11, 12, 13)
v
#> [1] 1 42 3 11 12 13 7 8 9 10
다른 언어에 익숙한 프로그래머라면 v[-1]이 벡터의 마지막 원소에 접근하는 방법이라고 짐작할 수 있어요.
틀렸어요!
그럴 때는 tail()을 사용해요.
v <- c(2, 4, 6) # defaults to last 6 entries
tail(v)
#> [1] 2 4 6
tail(v, n=1) # just the last entry
#> [1] 6
음수 인덱스는 사실 원소를 제거하는 방법이에요:
v <- c(2, 4, 6)
v[-2]
#> [1] 2 6 # second element gone
이렇게 하면 더 짧아진 벡터의 복사본이 반환돼요. v 자체는 변하지 않아요.
벡터에 비교 연산을 적용하면 같은 길이의 불리언 벡터가 반환돼요.
이건 all()이나 any() 같은 함수 안에서 아주 편리해요:
v <- c(4, 7, 10)
v >= 6
#> [1] FALSE TRUE TRUE
all(v > 6)
#> [1] FALSE # not all elements match this condition
any(v > 6)
#> [1] TRUE # at least one element matches
더 복잡한 논리식에서는 벡터에 대해 동작하는 &와 | 연산자를 사용하는 게 중요해요.
두 글자짜리 &&와 ||는 단일 값만 비교하기 위한 것이에요. [불리언 개념][concept-boolean]에서 다룬 것처럼요.
v <- c(4, 7, 10)
v %% 2 == 0 & v > 5
#> [1] FALSE FALSE TRUE
이 주제는 뒤에서 다시 다룰 거예요. 지금은 콘솔에 출력되는 형식만 눈여겨봐요:
1:20
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#> [18] 18 19 20
각 줄은 [ ] 안의 숫자로 시작하는데, 이는 첫 번째 원소의 인덱스를 나타내요(여러 줄에 걸쳐 표시되는 긴 벡터에서 유용해요).
그런데 단일 값 앞에는 왜 [1]이 붙을까요?
R에는 진짜 스칼라 값이 없어요!
숫자 하나나 글자 하나처럼 보이는 것도 사실은 길이가 1인 벡터예요.
이미 기초와 불리언 개념에서 숫자형과 논리형을 다루며 이 점을 살짝 봤어요.
위에서 설명한 벡터는 더 엄밀하게는 atomic vectors라고 해요. 왜냐하면 numeric, character, boolean처럼 단순하고 더 이상 나눌 수 없는(그래서 "원자적"인) 값만 담을 수 있기 때문이에요. 더 복잡한 구조는 recursive vectors를 이룰 수 있는데, 이는 lists를 다룰 때 소개할 거예요.