선형대수, 통계분석, 데이터마이닝, 최적화 등을 수행할 때 행렬을 많이 사용합니다. 분석에 필요한 변수가 많아질 수록 변수들의 계수를 행렬로 해서 수식을 표현하고 컴퓨터에게 연산을 시키는 것이 편리하기 때문입니다.

 

선형대수(Linear Algebra)를 배우지 않은 분들께서는 행렬연산이 좀 낯설텐데요, 행렬연산에 대해 좀더 깊이 들어가는 부분은 일단 이번 포스팅에서는 생략하겠으며, 앞으로 특정 분석 주제에 대한 포스팅에서 기회가 되면 다루도록 하겠습니다.

 

대신, 이번 포스팅에서는 행렬 연산을 위한 R의 함수 중에서 특히 행렬, 데이터 프레임에서 데이터 전처리 하는데 있어 활용도가 높은 함수들 위주로 몇 가지를 살펴보겠습니다.  데이터 분석 쪽으로 진로를 잡으려고 생각하는 분이라면 선형대수는 꼭 배워두시면 기초를 다잡을 수 있을 거라서 추천드립니다.

 

 

[ m*n 행렬 (m by n matrix) ]

 

 

 

 

이번에 살표볼 R 행렬 연산 함수로 +, -, *, /, ^, %*%, cbind(), rbind(), colMeans(), rowMeans(), colSums(), rowSums(), t() 등을 순서대로 예를 들어 설명하겠습니다.

 

(참고로, R에서 배열, 행렬도 모양이 조금 다른 벡터입니다.  따라서 벡터의 명령어가 배열, 행렬에도 적용된다고 보면 되겠습니다.)

 

 

 R 행렬 연산 : +, -, *, /, ^, %*%, cbind(), rbind(),

                   colMeans(), rowMeans(), colSums(), rowSums(), t()

 

(1) 행렬 내 각 숫자끼리의 연산 : +, -, *, /, ^

 

> ## 행렬 X와 행렬 Y 생성

> X <- matrix(1:4, nrow=2, ncol=2, byrow=FALSE, dimnames = NULL)
> X
     [,1] [,2]
[1,]    1    3
[2,]    2    4
>
> Y <- matrix(5:8, nrow=2, ncol=2, byrow=TRUE, dimnames = NULL)
> Y
     [,1] [,2]
[1,]    5    6
[2,]    7    8

 

예전 데이터 구조에 대한 포스팅에서 행렬 생성에 대해 다루었었는데요, matrix()함수와 각 옵션에 대해서 한번 더 복습해 보겠습니다.  ncol 은 칼럼 갯수, nrow 는 행의 갯수, byrow=FALSE 는 X 행렬 예에서 처럼 위에서 아래로 byrow=TRUE는 Y 행렬 예시 처럼 왼쪽에서 오른쪽으로 행렬이 생성됩니다.

 

> ## 행렬 X와 행렬 Y의 각 숫자끼리의 연산: +, -, *, /, ^ > X + Y [,1] [,2] [1,] 6 9 [2,] 9 12 > > X - Y [,1] [,2] [1,] -4 -3 [2,] -5 -4 > > X * Y [,1] [,2] [1,] 5 18 [2,] 14 32 > > X / Y [,1] [,2] [1,] 0.2000000 0.5 [2,] 0.2857143 0.5 > > X ^ Y [,1] [,2] [1,] 1 729 [2,] 128 65536

 

숫자형으로 구성된 두 행렬에 대해 +, -, *, /, ^ 연산을 하게 되면 같은 위치에 있는 숫자끼리 연산을 하게 됩니다.  (1)번 X * Y 곱셉의 경우 아래 (2)번 예시의 X %*% Y 와 어떻게 다른지 유심히 살펴보시기 바랍니다.  선형대수를 공부하신 분이라면 (1)번 X * Y 곱셈 결과를 보고 '이거 뭐지?' 하고 의아해 하실 것 같은데요, (1) 번 형식의 X * Y 는 각 구성 원소를 순서대로 그냥 곱한 겁니다.  선형대수에서 배웠던 행렬과 행렬의 곱셉은 아래 (2) 번 X %&% Y 형식의 명령문을 사용하게 됩니다.

 

 

(2) 행렬 X와 행렬 Y의 곱 : X %*% Y

 

> X %*% Y
     [,1] [,2]
[1,]   26   30
[2,]   38   44

 

통계, 머신러닝, 최적화 등에서 사용하는 곱셉은 아래 (2)번 X %*% Y 곱셉인 경우가 많을 텐데요, 분석 목적에 맞게 선택해서 사용하시기 바랍니다.

 

 

(3) 행렬 세로 결합 cbind(), 행렬 가로 결합 rbind()

 

> ## 행렬 세로 결합 cbind() : column bind
>
cbind(X, Y) [,1] [,2] [,3] [,4] [1,] 1 3 5 6 [2,] 2 4 7 8 >
> ## 행렬 가로 겹합 rbind() : row bind
>
rbind(X, Y) [,1] [,2] [1,] 1 3 [2,] 2 4 [3,] 5 6 [4,] 7 8

 

두 행렬을 cbind(), rbind()가 행끼리 결합하는 건지, 열끼리 결합하는 건지 헷갈릴 수 도 있는데요, cbind()는 column bind, rbind()는 row bind 로 해서 기억하시면 이해하기 쉽겠지요?

 

 

(4) 행렬 X의 각 열의 평균값으로 구성된 벡터 colMeans(X), 행렬 Y의 각 행의 평균값으로 구성된 벡터 rowMeans(Y)

 

> ## colMeans(), rowMeans()
> colMeans(X)
[1] 1.5 3.5
> rowMeans(X)
[1] 2 3
> 
> colMeans(Y)
[1] 6 7
> rowMeans(Y)
[1] 5.5 7.5

 

colMeans()의 경우 데이터 프레임에서 특정 변수를 '$'로 지정해놓고 mean() 함수를 실행하면 동일한 값을 구할 수 있습니다.  데이터 프레임에서는 보통 열(변수)를 기준으로 통계 분석을 실시하므로, 만약 열을 기준으로 요약 통계를 보려면 colMeans(), 혹은 아래 colSums() 함수는 알아두면 편하겠지요. 

 

참고로, 보통은 행(row) 데이터에 대해서 분석을 하려면 (6)번의 전치 t() 함수나 melt(), cast()함수로 데이터를 열(column)으로 재구성해서 colMeans(), colSums() 나 그 밖의 통계함수를 써서 분석을 합니다.

 

 

(5)  행렬 X의 각 열의 합계로 구성된 벡터 colSums(X), 행렬 Y의 각 행의 합계로 구성된 벡터 rowSums(Y)

 

> ## colSums(), rowSums()
> colSums(X)
[1] 3 7
> rowSums(X)
[1] 4 6
> 
> colSums(Y, na.rm = TRUE)
[1] 12 14
> rowSums(Y, na.rm = TRUE)
[1] 11 15

 

na.rm = TRUE 는 행렬 연산 시에 결측값이 있으면 포함하지 말고 계산하라는 뜻입니다.  예전 포스팅에서 결측값 확인/처리 (☞ 바로 가기) 에 대해서 다룬 적이 있는데요, 아래에 Cars93 데이터 프레임을 가지고 na.omit() 함수와 동일하게 행 내에 결측값이 있으면 그 행 전체를 삭제하는 방법을  rowSums() 함수와 is.na() 함수를 사용해서 수행하는 방법을 알아보겠습니다.

 

> library(MASS)
> str(Cars93)
'data.frame':	93 obs. of  27 variables:
 $ Manufacturer      : Factor w/ 32 levels "Acura","Audi",..: 1 1 2 2 3 4 4 4 4 5 ...
 $ Model             : Factor w/ 93 levels "100","190E","240",..: 49 56 9 1 6 24 54 74 73 35 ...
 $ Type              : Factor w/ 6 levels "Compact","Large",..: 4 3 1 3 3 3 2 2 3 2 ...
 $ Min.Price         : num  12.9 29.2 25.9 30.8 23.7 14.2 19.9 22.6 26.3 33 ...
 $ Price             : num  15.9 33.9 29.1 37.7 30 15.7 20.8 23.7 26.3 34.7 ...
 $ Max.Price         : num  18.8 38.7 32.3 44.6 36.2 17.3 21.7 24.9 26.3 36.3 ...
 $ MPG.city          : int  25 18 20 19 22 22 19 16 19 16 ...
 $ MPG.highway       : int  31 25 26 26 30 31 28 25 27 25 ...
 $ AirBags           : Factor w/ 3 levels "Driver & Passenger",..: 3 1 2 1 2 2 2 2 2 2 ...
 $ DriveTrain        : Factor w/ 3 levels "4WD","Front",..: 2 2 2 2 3 2 2 3 2 2 ...
 $ Cylinders         : Factor w/ 6 levels "3","4","5","6",..: 2 4 4 4 2 2 4 4 4 5 ...
 $ EngineSize        : num  1.8 3.2 2.8 2.8 3.5 2.2 3.8 5.7 3.8 4.9 ...
 $ Horsepower        : int  140 200 172 172 208 110 170 180 170 200 ...
 $ RPM               : int  6300 5500 5500 5500 5700 5200 4800 4000 4800 4100 ...
 $ Rev.per.mile      : int  2890 2335 2280 2535 2545 2565 1570 1320 1690 1510 ...
 $ Man.trans.avail   : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 1 1 1 1 ...
 $ Fuel.tank.capacity: num  13.2 18 16.9 21.1 21.1 16.4 18 23 18.8 18 ...
 $ Passengers        : int  5 5 5 6 4 6 6 6 5 6 ...
 $ Length            : int  177 195 180 193 186 189 200 216 198 206 ...
 $ Wheelbase         : int  102 115 102 106 109 105 111 116 108 114 ...
 $ Width             : int  68 71 67 70 69 69 74 78 73 73 ...
 $ Turn.circle       : int  37 38 37 37 39 41 42 45 41 43 ...
 $ Rear.seat.room    : num  26.5 30 28 31 27 28 30.5 30.5 26.5 35 ...
 $ Luggage.room      : int  11 15 14 17 13 16 17 21 14 18 ...
 $ Weight            : int  2705 3560 3375 3405 3640 2880 3470 4105 3495 3620 ...
 $ Origin            : Factor w/ 2 levels "USA","non-USA": 2 2 2 2 2 1 1 1 1 1 ...
 $ Make              : Factor w/ 93 levels "Acura Integra",..: 1 2 4 3 5 6 7 9 8 10 ...
> sum(is.na(Cars93))
[1] 13
> 
> Cars93_na.omit <- na.omit(Cars93)
> sum(is.na(Cars93_na.omit))
[1] 0
> 
> Cars93_rowSums <- Cars93[rowSums(is.na(Cars93)) == 0, ]
> sum(is.na(Cars93_rowSums))
[1] 0

 

na.omit()함수가 훨씬 수월하므로 굳이 dataset[rowSums(is.na(dataset)) == 0, ] 처럼 프로그래밍을 할 필요가 있을까 싶기는 합니다만, rowSums() 함수를 이렇게도 이용할 수 있구나 정도로 알아두시면 좋겠습니다.

 

 

(6) 행렬 X의 전치 t(X)

 

> ## 행렬의 전치 t()
> X
     [,1] [,2]
[1,]    1    3
[2,]    2    4
> 
>
t(X) [,1] [,2] [1,] 1 2 [2,] 3 4 >
> > Y [,1] [,2] [1,] 5 6 [2,] 7 8 >
>
t(Y) [,1] [,2] [1,] 5 7 [2,] 6 8 > > Z <- matrix(1:6, nrow=2, ncol=3) > Z [,1] [,2] [,3] [1,] 1 3 5 [2,] 2 4 6 >
>
t(Z) [,1] [,2] [1,] 1 2 [2,] 3 4 [3,] 5 6

 

t(X)로 행렬을 전치하면 위의 예에서 보는 것처럼 행과 열이 서로 바뀌게 됩니다.  통계분석의 행과 열 기준을 바꾸고 싶거나, 그래프 그릴 때 가로와 세로를 바꾸고 싶을 때 t() 함수로 전치를 해서 쓰면 되겠지요.

 

한번더 부언하자면, 선형대수에 나오는 행렬 연산 전부를 다루지는 않았습니다만, 데이터 분석 쪽으로 계속 공부하려는 분이라면 선형대수 공부는 몸에 좋은 밑거름이 될것이니 따로 공부해보시길 권합니다.

 

 

행렬에 대해 소개한 포스팅을 아래에 링크 걸어놓습니다. 참고하세요.

 

행렬 기본 이해

특수한 형태의 행렬 (zero matrixtranspose matrixsymmetric matrixupper triangular matrixlower triangular matrixdiagonal matrixidentity matrix, I, or unit matrix, U)

가우스 소거법을 활용한 역행렬 계산 (Invertible matrix, Gauss-Jordan elimination method)

여인수를 활용한 역행렬 계산 (Invertible matrix, by using cofactor)

벡터의 기본 이해와 연산 (vector: addition, subtraction, multiplication by scalar)

벡터의 곱 (1) 내적 (inner product, dot product, scalar product, projection product)

벡터의 곱 (2) 외적 (outer product, cross product, vector product, tensor product)

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

 

Posted by R Friend R_Friend

R의 연산자 중에 %any% 식으로 해서 %가 들어간 특이한 경우를 본 적이 있으신지요?  혹시 사용자 정의 함수나 루프 연산 예제로 '홀수' 나 '짝수' 개수 구하기 등의 예제를 본 적이 있다면 '%%' 연산자를 보았을 수도 있겠습니다.  알고나면 사실 별거 아닌데요, 모르면 당최 이게 무슨 뜻일까 가늠이 안되는 연산자이기도 합니다. 그러니 한번은 봐두는게 좋겠습니다.

 

%any% 연산자의 예로 (1) 나머지 연산자 %%, (2) 정수 나누기 연산자 %/%, (3) 행렬 곱하기 연산자 %*%, (4) 벡터 내 특정 값 포함 여부 확인 연산자 %in% 의 4가지 연산자에 대해서 하나씩 예를 들어가면서 알아보도록 하겠습니다.

 

저는 앞의 3개는 사용해본적이 아직까지는 없는데요, 그래도 %in%는 나름 유용하게 잘 써먹고 있습니다.

 

 

 R %any% 특별연산자

(1) 나머지 연산자 %%

 

> # (1) 나머지 연산자 %% > x <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) > y <- c(5) > x %% y [1] 1 2 3 4 0 1 2 3 4 0 >
> # 5의 배수만 indexing 해오기 > x[x %% y == c(0)] [1] 5 10

 

x %% y 를 하게 되면 x가 y로 나누어지는 경우는 나머지가 '0'이고, 그 외에는 나머지 값이 숫자로 나오게 됩니다.  x가 1~10까지 정수이고 y가 5라고 했을 때 5의 배수만 indexing해오는 방법으로 x[x %% y == c(0)] 를 사용했는데요, x %% y == c(0) 하게 되면 5의 배수인 5와 10일때만 TRUE 가 되고, 이를 indexing해오면 5의 배수가 되겠지요.

 

 

(2) 정수 나누기 연산자 %/%

 

> x %/% y
 [1] 0 0 0 0 1 1 1 1 1 2

 

위 (1)번의 x와 y 벡터를 가지고 (2)번 예를 들어보았습니다.  %/% 연산자는 나누었을 때 '정수 몫' 만을 가져다가 결과로 나타내 줍니다. 5로 1~4까지 나누면 소수점 자리수 이므로 0, 5~9까지는 소수점 이하는 버리고 정수 몫만 취하므로 '1', 10을 5로 나누면 '2'가 되는 것이지요.

 

 

(3) 행렬 곱하기 연산자 %*%

 

> x %*% y
Error in x %*% y : non-conformable arguments
> c(1, 2, 3) %*% c(4, 5, 6)
     [,1]
[1,]   32

 

위 (1)번 x, y 벡터를 가지고 행렬 곱하기 %*%하면 'non-conformable arguments'라는 에러 메시지가 뜹니다. 행렬 곱하기를 하려고 하는데 갯수가 서로 안맞아서 그렇습니다.  구성요소가 각 각 3개씩인 두개의 벡터를 곱하려면 c(1, 2, 3) %*% c(4, 5, 6) 이렇게 입력하면 됩니다.  그러면 순서대로 곱하고 더해서, 즉, (1*4 + 2*5 + 3*6) = 4 + 10 + 18= 32 가 됩니다.

 

 

(4) 벡터 내 특정 값 포함 여부 확인 연산자 %in%

 

> # (4) 벡터 내 특정 값 포함 여부 확인 %in%
> x %in% y
 [1] FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE
> 

> # x 내에 y 값이 포함된 개수의 합

> sum( x %in% y)
[1] 1

 

마지막으로 벡터 내 특정 값이 포함되었는지 여부를 확인하는 %in%는 SQL이나 SAS에서 %like% 와 유사한 연산자라고 보면 되겠습니다.  R은 %in% 연산자의 결과로 TRUE, FALSE 논리형 벡터를 출력합니다.  위 (4)번 예에서는 x의 5번째 자리에 y 값 5가 하나 들어있어서 TRUE로 나왔음을 알 수 있습니다.  위의 예는 x가 단지 10개 뿐이어서 눈으로도 확인할 수 있지만 구성요소 갯수가 수천, 수만, 수백만개면 눈으로 일일이 확인하는 것은 불가능합니다.  이럴 때 sum(x %in% y) 함수를 사용하면 x 에 y가 총 몇개나 들어있는지 금방 확인할 수가 있답니다.

 

%any% 연산자에 대해서 알아보았습니다.  도움이 되었기를 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡' 단추를 꾸욱 눌러주세요.^^

 

Posted by R Friend R_Friend