작성자: admin 작성일시: 2016-09-22 18:10:28 조회수: 194 다운로드: 39
카테고리: R 태그목록:

R 벡터와 행렬의 연산

R은 코드를 간단하게 만들고 계산 속도를 빠르게 하기 위한 벡터화 연산(vectorized operation)을 지원한다. 벡터화 연산이란 반복문(loop)을 사용하지 않고 선형 대수의 벡터 혹은 행렬 연산과 유사한 코드를 사용하는 것을 말한다.

예를 들어 다음과 같은 연산을 해야 한다고 하자.

$$ x = \begin{bmatrix}1 \\ 2 \\ 3 \\ \vdots \\ 1000 \end{bmatrix}, \;\;\;\; y = \begin{bmatrix}1001 \\ 1002 \\ 1003 \\ \vdots \\ 2000 \end{bmatrix}, $$$$z = x + y = \begin{bmatrix}1+101 \\ 2+1002 \\ 3+1003 \\ \vdots \\ 1000+2000 \end{bmatrix}= \begin{bmatrix}1002 \\ 1004 \\ 1006 \\ \vdots \\ 3000 \end{bmatrix} $$

만약 벡터화 연산을 사용하지 않는다면 이 연산은 반복문을 사용하여 다음과 같이 만들어야 한다.

In [22]:
x <- 1:1000
y <- 1001:2000
In [23]:
start.time <- proc.time()
z <- rep(0, 1000)
for (i in 1:1000) {
    z[[i]] <- x[[i]] + y[[i]]
}
proc.time() - start.time
   user  system elapsed 
  0.010   0.000   0.004 
In [25]:
z[1:10]
  1. 1002
  2. 1004
  3. 1006
  4. 1008
  5. 1010
  6. 1012
  7. 1014
  8. 1016
  9. 1018
  10. 1020

이 코드에서 proc.time은 현재 시각을 구하는 명령이다. 위와 같이 시작 시각을 저장하여 실행 후의 시각에서 빼면 코드가 실행되는데 걸린 시간을 알 수 있다.

그러나 벡터화 연산을 사용하면 덧셈 연산 하나로 끝난다. 위에서 보인 선형 대수의 벡터 기호를 사용한 연산과 코드가 거의 동일하다.

In [21]:
start.time <- proc.time()
z <- x + y
proc.time() - start.time
   user  system elapsed 
  0.010   0.000   0.002 
In [26]:
z[1:10]
  1. 1002
  2. 1004
  3. 1006
  4. 1008
  5. 1010
  6. 1012
  7. 1014
  8. 1016
  9. 1018
  10. 1020

연산 속도도 벡터화 연산이 훨씬 빠른 것을 볼 수 있다.

비교 연산도 벡터화 연산이 가능하다.

In [27]:
a <- c(1, 2, 3, 4)
b <- c(4, 2, 2, 4)
In [28]:
a == b
  1. FALSE
  2. TRUE
  3. FALSE
  4. TRUE
In [29]:
a >= b
  1. FALSE
  2. TRUE
  3. TRUE
  4. TRUE

만약 배열 전체를 비교하고 싶다면 all 명령을 사용한다.

In [30]:
a <- c(1, 2, 3, 4)
b <- c(4, 2, 2, 4)
c <- c(1, 2, 3, 4)
In [31]:
all(a == b)
FALSE
In [32]:
all(a == c)
TRUE

지수 함수, 로그 함수 등의 수학 함수도 벡터화 연산을 지원한다.

In [39]:
a <- 0:4
a
  1. 0
  2. 1
  3. 2
  4. 3
  5. 4
In [40]:
exp(a)
  1. 1
  2. 2.71828182845905
  3. 7.38905609893065
  4. 20.0855369231877
  5. 54.5981500331442
In [41]:
10 ** a
  1. 1
  2. 10
  3. 100
  4. 1000
  5. 10000
In [42]:
log(a)
  1. -Inf
  2. 0
  3. 0.693147180559945
  4. 1.09861228866811
  5. 1.38629436111989
In [43]:
log10(a)
  1. -Inf
  2. 0
  3. 0.301029995663981
  4. 0.477121254719662
  5. 0.602059991327962

스칼라와 벡터/행렬의 곱셈

스칼라와 벡터/행렬의 곱도 선형 대수에서 사용하는 식과 R 코드가 일치한다.

In [44]:
x <- 0:9
x
  1. 0
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
  7. 6
  8. 7
  9. 8
  10. 9
In [46]:
100 * x
  1. 0
  2. 100
  3. 200
  4. 300
  5. 400
  6. 500
  7. 600
  8. 700
  9. 800
  10. 900
In [48]:
x <- matrix(0:11, 3, 4)
x
0 3 6 9
1 4 7 10
2 5 8 11
In [49]:
100 * x
0 300 600 900
100 400 700 1000
200 500 800 1100

브로드캐스팅

선형 대수에서는 벡터(또는 행렬)끼리 덧셈 혹은 뺄셈을 하려면 두 벡터(또는 행렬)의 크기가 같아야 한다. 그러나 R에서는 서로 다른 크기를 가진 두 배열의 사칙 연산도 지원한다. 이 기능을 브로드캐스팅(broadcasting)이라고 하는데 크기가 작은 배열을 자동으로 반복 확장하여 크기가 큰 배열에 맞추는 방벙이다.

예를 들어 다음과 같이 벡터와 스칼라를 더하는 경우를 생각하자. 선형 대수에서는 이러한 연산이 불가능하다.

$$ x = \begin{bmatrix}0 \\ 1 \\ 2 \\ 3 \\ 4 \end{bmatrix}, \;\;\;\; x + 1 = \begin{bmatrix}0 \\ 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} + 1 = ? $$

그러나 R은 브로드캐스팅 기능을 사용하여 스칼라를 벡터와 같은 크기로 확장시켜서 덧셈 계산을 한다.

$$ \begin{bmatrix}0 \\ 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} \overset{\text{R}}+ 1 = \begin{bmatrix}0 \\ 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} + \begin{bmatrix}1 \\ 1 \\ 1 \\ 1 \\ 1 \end{bmatrix} = \begin{bmatrix}1 \\ 2 \\ 3 \\ 4 \\ 5 \end{bmatrix} $$
In [51]:
x <- 0:4
x
  1. 0
  2. 1
  3. 2
  4. 3
  5. 4
In [53]:
y <- rep(1, length(x))
y
  1. 1
  2. 1
  3. 1
  4. 1
  5. 1
In [54]:
x + y
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
In [55]:
x + 1
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5

2차원 이상의 경우에는 단순 반복을 통해 갯수를 맞추게 된다. 이 방법은 Python 패키지인 NumPy에서 제공하는 브로드캐스팅과 다르므로 Python을 사용하던 프로그래머는 주의해야 한다.

In [76]:
a = cbind(seq(0, 30, 10), seq(0, 30, 10), seq(0, 30, 10))
a
0 0 0
101010
202020
303030
In [77]:
b <- 0:2
b
  1. 0
  2. 1
  3. 2
In [78]:
a + b
0 1 2
111210
222021
303132

위 연산은 내부적으로 다음과 같이 계산된 것이다.

In [79]:
a + matrix(rep(b, 4), 4, 3)
0 1 2
111210
222021
303132

차원 축소 연산

행렬의 하나의 행에 있는 원소들을 하나의 데이터 집합으로 보고 그 집합의 평균을 구하면 각 행에 대해 하나의 숫자가 나오게 된다. 예를 들어 10x5 크기의 2차원 배열에 대해 행-평균을 구하면 10개의 숫자를 가진 1차원 벡터가 나오게 된다. 이러한 연산을 차원 축소(dimension reduction) 연산이라고 한다.

R은 다음과 같은 차원 축소 연산 명령 혹은 메서드를 지원한다.

  • 최대/최소: min, max, which.min, which.max
  • 통계: sum, mean, median, std, var
  • 불리언: all, any
In [88]:
x <- 1:4
x
  1. 1
  2. 2
  3. 3
  4. 4
In [89]:
sum(x)
10
In [90]:
x <- c(1, 3, 2)
In [91]:
min(x)
1
In [92]:
max(x)
3
In [93]:
which.min(x)
1
In [94]:
which.max(x)
2
In [95]:
x <- c(1, 2, 3, 1)
In [96]:
mean(x)
1.75
In [97]:
median(x)
1.5
In [98]:
all(c(TRUE, TRUE, FALSE))
FALSE
In [99]:
any(c(TRUE, TRUE, FALSE))
TRUE
In [101]:
a <- matrix(0, 100, 100)
In [102]:
any(a != 0)
FALSE
In [103]:
all(a == a)
TRUE
In [104]:
a = c(1, 2, 3, 2)
b = c(2, 2, 3, 2)
c = c(6, 4, 4, 5)
In [105]:
all((a <= b) & (b <= c))
TRUE

연산의 대상이 2차원 이상인 경우에는 rowSums, colSums 명령을 이용하면 행 또는 열의 합을 구할 수 있다.

In [107]:
x = matrix(c(1, 2, 1, 2), nrow=2)
x
11
22
In [111]:
colSums(x)   # 열 합계
  1. 3
  2. 3
In [117]:
rowSums(x)   # 행 합계
  1. 2
  2. 4

정렬

sort 명령를 사용하면 벡터 안의 원소를 크기에 따라 정렬하여 새로운 벡터를 만들 수 있다. decreasing=TRUE 인수를 주면 반대로 정렬한다.

In [120]:
a <- c(4, 3, 5, 1, 2, 1)
a
  1. 4
  2. 3
  3. 5
  4. 1
  5. 2
  6. 1
In [121]:
sort(a)
  1. 1
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
In [123]:
sort(a, decreasing=TRUE)
  1. 5
  2. 4
  3. 3
  4. 2
  5. 1
  6. 1

질문/덧글

아직 질문이나 덧글이 없습니다. 첫번째 글을 남겨주세요!