가끔 R에 대한 기초적인 정보를 소개할까 한다.
오늘은 R의 첫 시작이라 할 수 있는 R의 자료형을 소개해본다.
R 기본 스펙의 자료형에는 vector, factor, matrix, array, list, dataframe 등이 있는데,
가장 기본이면서 독특한 녀석이 vector다.
이런 자료형을 접해보지 못한 사람도 꽤 있을 것이다.
(물론 R만이 가진 자료형은 아니라고도 들은 것 같다.)
-----------------------------------------------------------------
아래가 벡터의 할당이다.
x <- c(원소1, 원소2, 원소3, 원소4, ... )
(R은 기본 변수 할당 연산자가 <-다. 탑 레벨에서는 =도 가능하나 그 이하 레벨에서는 =의 의미가 달라진다.
참고로 ->도 있다. 흔히 생각하는 좌측항 우측항이 반대로 된다.
할당할 식을 엄청 길게 썼는데 정작 할당될 변수를 까먹고 안 썼을 경우 제일 오른쪽에 -> x 이런 식 가능)
벡터는 데이터들의 1차원적인 모임이며,
각 원소들은 숫자면 숫자, 문자열이면 문자열 등 같은 타입이어야 한다.
그러고는 숫자 벡터니 문자열 벡터니 하고 부른다.
또한 인덱스가 1부터 시작하며 슬라이싱은 양쪽 모두 닫힌 경계여서,
x <- c('a', 'b', 'c', 'd', 'e')
이 경우 x[3:4]는 'c'를 'd'를 뽑는다.
여기서 주의할 것이 R에는 단일 원소 자료형이 없고 모두가 집합형 자료형이다.
결국 벡터의 원소를 뽑을 때 한 개를 뽑든 여러개를 뽑든 또다시 벡터가 된다.
즉 벡터는 집합형이면서도 R 자료형의 최소단위이다.
(사실 다른 자료형들도 결국 벡터를 변형시키거나 벡터를 쌓아올린 구조이다.
그래서 R 기초 책들에서 "Everything is a vector."라는 멋드러진(?) 말을 자주 한다.)
y <- c(99) 같이 99라는 하나의 원소를 가진 y라는 벡터가 있다면,
y와 y[1]은 c(99)로써 완전히 동일하다.
파이썬의 리스트에서,
y, [99]와 y[0], 99가 다른 것과 매우 다르다.
위로 돌아가서 x[3:4]는 3번째, 4번째를 뽑아 c(3, 4)라는 벡터가 된다.
이건 타언어의 집합형 자료형도 비슷하니 직관적이다.
-----------------------------------------------------------------
혹시 벡터간에 서로 중첩이 될까?
> c(c('ab', 'cd', 'ef'), 'gh', c('ij', 'kl'))
[1] "ab" "cd" "ef" "gh" "ij" "kl"
이렇게 원소가 다 풀어진채로 1차원적인 벡터가 된다.
표현을 중첩해서 썼다고 의미상 중첩이 되지 않는다.
-----------------------------------------------------------------
숫자 벡터를 할당할 때 방법에 따라 숫자 벡터가 아닌 정수 벡터가 나올 수도 있다.
> a <- 1
> class(a)
[1] "numeric"
> a <- c(1, 2)
> class(a)
[1] "numeric"
> a <- 1:5
> class(a)
[1] "integer"
다른 방법으로 하면 다 숫자인데,
파이썬의 range 함수 비슷한 ":"라는 연산자를 이용하면 정수가 된다.
":"라는 것 자체가 정수를 가정해야 되니까 정수 벡터를 리턴하도록 했나보다.
(참고로 R의 range는 최소값과 최대값을 구해주는 함수라 range(1, 5)와 1:5는 다르다.
두 언어가 생김새가 비슷해서 실수할 수도 있다.)
사실 R에서는 정수와 실수의 구별을 뚜렷하게 짓지 않아서 별로 신경쓸 필요는 없다.
c(1, 2, 3, 4, 5)와 1:5를 가지고 다른 것들과 연산을 어떻게 해도 과정과 결과는 동일하다.
-----------------------------------------------------------------
그리고 여기가 하이라이트인데,
숫자형 벡터간의 연산이 매우 독특하게 이루어진다.
먼저 원소의 숫자가 같은 숫자형 벡터를 더해보자.
> c(1, 2, 3) + c(4, 5, 6)
[1] 5 7 9
딱 보면 알겠지만 파이썬의 리스트마냥 원소를 길게 이어주는 게 아니라,
같은 위치의 원소끼리 해당 연산을 해준다.
1번과 1번, 2번과 2번, 3번과 3번.
뭐 이건 그렇구나 할만한데 서로 원소의 개수가 다르면?
> c(1, 2) + c(1, 2, 3, 4)
[1] 2 4 4 6
긴거에 맞추어 작은쪽의 벡터를 반복해서 늘려주고 계산한다.
1번과 1번, 2번과 2번, 1번과 3번, 2번과 4번.
이런 걸 재사용이라고 한다.
긴쪽의 길이가 짧은쪽의 길이의 정수배가 아닌 경우는?
> c(1, 2, 3) + c(1, 2, 3, 4)
[1] 2 4 6 5
Warning message:
In c(1, 2, 3) + c(1, 2, 3, 4) :
longer object length is not a multiple of shorter object length
경고를 띄우지만 그래도 결과는 내준다.
참고로 벡터를 인자로 갖는 함수에 넣을 때도
x = c(a, b, c)
func(x)의 결과가 c(func(a), func(b), func(c))일 때가 많다.
물론 이건 당연히 함수마다 다르다.
func(x)가 숫자를 세제곱 해주는 함수라든가 문자열을 1번부터 3번까지로 잘라주는 함수라면 위처럼 될 것이고,
sum같은 거면 싸그리 더해서 c(a + b + c)가 나온다.
그래도 함수가 각 원소(이면서 벡터이기도 한)별로 적용되도록 설계하는 비율이 높아보인다.
이게 R스럽달까.
-----------------------------------------------------------------
이 외에도 벡터가 R의 다른 자료형을 만드는 데 어떻게 쓰이는지 말하고 싶지만 너무 길어지므로 투 비 컨티뉴드..
내가 썼는데 대학교 때는 수학 안 배웠고 고등학교 물리 시간 때 배우는 벡터와는 달라서..
또 고등학교 때 배우는 행렬 말하는 거면 기본적으로는 같지만 프로그래밍에서의 구현과는 또 다른 이야기니. 예를 들어 거기에는 원소 재사용이 없잖