R에서 이 개념은 꽤 큰 주제예요. 여러 가능성을 이해하는 데에는 다양한 함수의 입력과 출력이 몇 차원인지 살펴보는 것이 도움이 돼요.
벡터를 받아서 단일 스칼라 값 하나를 반환하는 함수는 이미 살펴봤어요:
v <- 1:5
length(v)
#> [1] 5
sum(v)
#> [1] 15
통계 함수 중에도 mean()처럼 여기에 속하는 것이 많아요.
논리형 벡터에는 all()과 any()가 있는데, 둘 다 TRUE나 FALSE 하나를 반환해요.
R의 많은 함수는 벡터 전체를 대상으로 동작하고, 대개 벡터를 결과로 내놓아요. 수학 함수 대부분이 여기에 해당해요:
sq <- c(4, 9, 16, 25)
sqrt(sq) # square root
#> [1] 2 3 4 5
이 방식은 간결하고 편리할 뿐만 아니라, 다른 언어에서 흔히 쓰는 루프나 리스트 컴프리헨션, 재귀가 필요 없다는 장점도 있어요. R에서는 이런 벡터 함수가 그런 기법보다 훨씬 빠르게 실행되는 경우가 많아요.
생각보다 이미 벡터화된 함수를 많이 사용해 왔을 거예요. 다음을 비교해 봐요:
v <- c(2, 7, 9)
w <- c(3, 1, 5)
v + w
#> [1] 5 8 14
"+"(v, w)
#> [1] 5 8 14
익숙한 중위 연산자는 사실 그 바탕에 있는 벡터화 함수를 편하게 쓰도록 만든 문법 설탕일 뿐이에요! 여기서는 "+"()가 그렇죠.
벡터를 정렬할 때는 값을 반환하는 sort()와 인덱스를 반환하는 order()가 있어요:
v <- c("I", "am", "not", "in", "order")
sort(v)
#> [1] "am" "I" "in" "not" "order"
order(v)
#> [1] 2 1 4 3 5
기본값은 오름차순 정렬이에요.
필요하면 decreasing = TRUE를 지정하거나, 이미 있는 벡터를 뒤집으려면 rev()를 사용해요.
v <- c("I", "am", "not", "in", "order")
sort(v, decreasing = TRUE)
#> [1] "order" "not" "in" "I" "am"
rev(1:4)
#> [1] 4 3 2 1
cumsum()과 cumprod()처럼 입력 벡터를 왼쪽에서 오른쪽으로 처리하면서 누적된 벡터 출력을 만들어 내는 함수도 있어요:
cumsum(1:5)
#> [1] 1 3 6 10 15 # sums
cumprod(1:5)
#> [1] 1 2 6 24 120 # equivalent to factorials
위 예시는 여러 함수형 언어의 foldl 연산과 비슷한데, 중간값을 모두 담은 벡터를 출력한다는 점이 달라요.
이 개념을 확장하면 벡터를 비교하는 데에도 쓸 수 있어요.
예를 들어 각 쌍의 최댓값을 구하는 함수 pmax()를 살펴봐요:
v
#> [1] 2 7 9
w
#> [1] 3 1 5
pmax(v, w)
#> [1] 3 7 9 # max of each pairwise comparison
이 함수와 비슷한 함수들은 입력 벡터를 두 개만이 아니라 임의의 개수만큼 받을 수 있어요.
위 예시는 길이가 같은 벡터를 사용했어요. 벡터 필터링 개념에서 설명했듯이, R은 어쩐지 "너무 짧은" 벡터를 재활용해서 늘려요. 다시 강조하면, 이 규칙은 주의해서, 가급적 길이가 1인 벡터에만 사용해요.
pmax(v, 5)
#> [1] 5 7 9
흔한 경우는 아니지만, 겉보기에는 스칼라를 입력받아 벡터를 출력하는 것처럼 보이는 함수를 작성하기는 꽤 쉬워요.
이런 함수에 벡터를 입력하면 출력은 2차원 matrix가 돼요. (이후 개념에서 다뤄요.)