큰 자리수의 숫자를 불러오기하면 지수 표기법(exponential notation, scientific notation)으로 되어 있어서 읽기가 힘든 경우가 있습니다.

 

가령 '900000' 이라는 숫자를 R에서 읽어들였더니 '9e+5'라고 쓰여있다면 좀 어색하지요? 

 

이럴 경우 '9e+5'라고 프린트되어 있는 표기를 '900000'처럼 고정형 숫자 표기법(fixed notation)으로 바꿔주고 싶을 때 R에서 사용하는 방법 2가지를 소개하고자 합니다.

 

(1) 하나는 전역적으로(global environment) 영향을 미치는 옵션으로 : options(scipen = xxx) 이구요,

 

(2) 또 하나는 국소적으로(local) 특정 변수에 대해서 설정을 하는 옵션으로 : format(var, scientific = FALSE)

 

입니다.

 

아주 간단한 예를 들어서 설명을 해보겠습니다.

 

 

 

 

가령, "2016년 7월 9일 10시 30분 50초"의 시간을 나타내는 숫자가 "20160709103050" 처럼 입력되어 있는 데이터가 있다고 칩시다.  이를 options("scipen" = -100) 또는 options(scipen = -100)인 상태에서 입력을 받고서 프린트를 해보면 "2.016071e+13" 이라고 나옵니다.

(괄호 안의 숫자는 충분히 큰 숫자를 써주면 됩니다. options("scipen" = -30) 이라고 해도 동일하게 지수형태로 표기될겁니다)

 

 

> ################################################# > ## changing from exponential to numeric notation > ## options("scipen" = 100) > ## format(df$var, scientific = FALSE) > ################################################# >

> options("scipen" = -100)
> number <- c(20160709103050)
> number
[1] 2.016071e+13
> str(number)
 num 2.02e+13

 

 

 

이걸 options("scipen" = 100) 또는 options(scipen = 100)이라고 하고 다시 number를 프린트해보면 아래처럼 "20160709103050" 으로 원하는 형태로 잘 나옵니다. (괄호 안의 숫자는 충분히 큰 숫자를 써주면 됩니다.)

 

이 옵션은 R 사용환경 전체에 영향을 미칩니다.  즉, 특정 변수는 지수형 표기, 특정 변수는 숫자표기로 할 수는 없다는 뜻입니다.

 

> options("scipen" = 100)
> number
[1] 20160709103050 

 

 

참고로, options(scipen = x) 의 default 값은 '0' 입니다.

 

 

날짜/시간 얘기가 나온 김에 조금 더 진도를 나가 보자면요,

시계열분석이나 년/월/일/시간/분/초 단위로 구분해서 연산을 해야 하는 경우에 as.POSIXlt() 함수로 날짜/시간 type으로 바꾸어주면 편합니다. 

 

아래에 as.POSIXlt로 숫자형 number를 날짜/시간 유형으로 바꿔보려고 했더니 "NA" 값이 나오네요. 

 

> datetime_1 <- as.POSIXlt(number, # numeric
+                          format = '%Y%m%d%H%M%S', 
+                          origin = "1970-01-01", 
+                          tz ="UTC")
> 
> datetime_1 # NA
[1] NA

 

 

 

 

이럴경우 특정 변수만 format(var, scientific = FALSE) 옵션을 사용해서 포맷을 바꾸어주면 numeric이었던 것이 character로 바뀌었고, 큰 따옴표도 생겼습니다.

 

> # scientific = FALSE
> number_2 <- format(number, scientific = FALSE)
> number_2
[1] "20160709103050"
> str(number_2)
 chr "20160709103050"

 

 

 

 

이렇게 문자형으로 바꾸고 나서 다시 as.POSIXlt(char_var, format = '%Y%m%d%H%M%S', origin="1970-01-01", tz = UTC) 로 날짜/시간 유형으로 변환하면 잘 작동하네요.

 

> datetime_2 <- as.POSIXlt(number_2, # character
+                          format = '%Y%m%d%H%M%S', 
+                          origin = "1970-01-01", 
+                          tz ="UTC") # UTC : universal time
> 
> datetime_2 # "2016-07-09 10:30:50 UTC"
[1] "2016-07-09 10:30:50 UTC"

 

 

 

 

format() 대신에 as.character(numeric_var) 를 사용해서 숫자형을 문자형으로 바꾸어주고 나서, as.POSIXlt(char_var, format = '%Y%m%d%H%M%S', origin="1970-01-01", tz = UTC) 로 날짜/시간 유형으로 변환해도 똑같이 잘 작동합니다.

 

> # as.character
> number_3 <- as.character(number)
> number_3
[1] "20160709103050"
> str(number_3)
 chr "20160709103050"
> 
> datetime_3 <- as.POSIXlt(number_3, # character
+                          format = '%Y%m%d%H%M%S', 
+                          origin = "1970-01-01", 
+                          tz ="UTC") # UTC : universal time
> 
> datetime_3 # "2016-07-09 10:30:50 UTC"
[1] "2016-07-09 10:30:50 UTC" 

 

 

많은 도움 되었기를 바랍니다.

 

이번 포스팅이 도움이 되었다면 아래의 '공감 ~♡'를 꼬옥 눌러주세요.

 

 

Posted by R Friend R_Friend

이번 포스팅에서는 외부 텍스트 파일을 불러오기 할 때에

 

 

Error in scan(file = file, what = what, sep = sep, quote = quote, dec = dec, 

: line 3 did not have 5 elements

 

와 같은

(1) 에러 메시지가 뜨는 이유와

(2) 대처 방안

(3) 유의 사항

 

에 대해서 알아보겠습니다.

 

 

(1) 에러메시지 이유

Error in scan(file = file, what = what, sep = sep, quote = quote, dec = dec, 

: line 3 did not have 5 elements 

 

에러메시지가 뜨는 이유는 3번째 행(line 3)의 원소의 개수가 결측값(missing value) 때문에 총 5개가 안되기 때문입니다. (row name 1개, 행 4개 모두 포함해서 총 5개 원소)

 

아래에 간단한 예를 들어서 설명하겠습니다.

 

cat() 함수를 이용해서 V1, V2, V3, V4 라는 변수명을 가진 4개의 변수에 대해 First, Second, Third, Fourth라는 rowname을 가진 text 파일을 만들어보았습니다. 이때 의도적으로 3번째 행(3rd row, 3 line)에 원소를 row name 포함해서 4개만 만들어보았습니다. (다른 행은 모두 5개 원소, 3행만 4개 원소) 

MyDocument 폴더에 가보면 test.txt 라는 파일이 생성되어 있음을 확인할 수 있으며, 그 파일을 열어보면 아래 화면 캡쳐한 것 처럼 데이터가 들어가 있으며, 3행은 원소가 총 4개이며, 다른 행 대비 1개가 모자람을 알 수 있습니다.  

 

> ##-----------------------------------------------##
> ## Error in scan(file, what, nmax, sep, dec, quote, skip, nlines, na.strings,  
> ## :line 3 did not have 5 elements
> ##-----------------------------------------------##
> 
> # exmaple with missing value in line 3 at 5th element
> cat("V1 V2 V3 V4\nFirst 1 2 3 4\nSecond 5 6 7 8\nThird 9 10 11\nFourth 13 14 15 16", file="test.txt")

 

 

 

 

 

 

 

이렇게 생긴 데이터를 read.table() 함수를 써서 읽어들여보겠습니다.

 

> read.table("test.txt", header = TRUE)
Error in scan(file = file, what = what, sep = sep, quote = quote, dec = dec,  : 
  line 3 did not have 5 elements
In addition: Warning message:
In read.table("test.txt", header = TRUE) :
  'test.txt'에서 readTableHeader에 의하여 발견된 완성되지 않은 마지막 라인입니다

 

 

다른 행은 총 5개의 원소(row name 1개, 변수 4개)가 있는데 반해, 3번째 행은 5개 원소가 아니라는 에러 메시지가 떴습니다.  원소가 일부 모자란다는 뜻입니다.

 

 

 

(2) 대처 방안

첫번째 방법은 파일을 열어서 결측값 위치를 찾아가서 그 값에 제대로 된 값을 채워넣는 것입니다.  파일의 데이터 개수가 몇 개 안되고 육안으로 확인해서 채워넣을 수 있는 경우에는 적용가능 하겠지만, 만약 데이터 개수가 수천, 수만, 수십만 개라면 많이 힘들겠지요? ^^;

 

두번째 방법은, 만약 원소의 개수가 모자라는 이유가 제일 마지막 열에 결측값(missing value)가 있기 때문이라면 fill = TRUE 옵션을 부가하면 NA 표기가 부가되면서 읽어들이기를 간단하게 해결할 수 있습니다.

 

> # missing value => NA : fill = TRUE
> testfile <- read.table("test.txt", header = TRUE, fill = TRUE)
Warning message:
In read.table("test.txt", header = TRUE, fill = TRUE) :
  'test.txt'에서 readTableHeader에 의하여 발견된 완성되지 않은 마지막 라인입니다
> 
> testfile
       V1 V2 V3 V4
First   1  2  3  4
Second  5  6  7  8
Third   9 10 11 NA
Fourth 13 14 15 16

 

 

 

 

(3) 유의사항

 

한가지 주의할 사항이 있는데요, fill = TRUE 옵션은 "결측값이 마지막 행의 값일 경우에만 해당"된다는 점입니다.

 

아래에 예를 하나 들어보았는데요, 1번째 행의 1번째 열값과 3번째 행의 4번째 열값이 결측값인 데이터셋이 있다고 해봅시다.

 

> # matters that require attention : only for the last column's missing value
> cat("V1 V2 V3 V4\nFirst   2 3 4\nSecond 5 6 7 8\nThird 9 10 11\nFourth 13 14 15 16", file="test_2.txt")

 

 

> read.table("test_2.txt", header = TRUE)
Error in scan(file = file, what = what, sep = sep, quote = quote, dec = dec,  : 
  line 1 did not have 5 elements
In addition: Warning message:
In read.table("test_2.txt", header = TRUE) :
  'test_2.txt'에서 readTableHeader에 의하여 발견된 완성되지 않은 마지막 라인입니다
>

 

 

아래처럼 fill = TRUE 옵션을 써서 read.table()로 test_2.txt 파일 데이터 불러오기를 해봤습니다. 

첫번째 행(1st row)을 유심히 보시기 바랍니다.

첫번째 행의 1번째 열 값이 결측치(when value of 1st row and 1st column is missing)인 상황이라고 했는데요, fill = TRUE 옵션으로 해서 불어들였더니 첫번째 행의 4번째 열(1st row and 4th column)에 NA 라고 마킹이 되서 불러오기를 했습니다. 

 

아래 처럼 불러오기를 바랬는데요,

        V1  V2  V3  V4

First  NA   2    3    4

 

실제 R이 fill = TRUE 옵션을 썼을 때 불러온 것은

        V1  V2  V3  V4

First   2    3    4   NA

 

입니다. 

 

다시 한번 유의할 사항을 말씀드리자면, fill = TRUE 옵션은 "행의 마지막 값이 결측치인 경우에만" 사용해야 합니다.

 

> read.table("test_2.txt", header = TRUE, fill = TRUE)
       V1 V2 V3 V4
First   2  3  4 NA
Second  5  6  7  8
Third   9 10 11 NA
Fourth 13 14 15 16
Warning message:
In read.table("test_2.txt", header = TRUE, fill = TRUE) :
'test_2.txt'에서 readTableHeader에 의하여 발견된 완성되지 않은 마지막 라인입니다 

 

많은 도움 되었기를 바랍니다.

 

Posted by R Friend R_Friend

이번 포스팅에서는 벡터(vector)와 행렬(matrix)를 자유자재로 넘나들며 데이터 변환하는 방법을 소개하겠습니다. R에는 벡터와 행렬을 아주 쉽게 넘나들게 해주는 함수가 있답니다.

 

(1) 벡터를 행렬로 변환하기

    (1-1) 벡터를 열(column) 기준으로 행렬로 변환하기 : matrix(vector, byrow = FALSE, ncol = n)

    (1-2) 벡터를 행(row) 기준으로 행렬로 변환하기 : matrix(vector, byrow = TRUE, ncol = n)

 

(2) 행렬을 벡터로 변환하기 (vectorization)

    (2-1) 행렬을 열(column) 기준으로 벡터로 변환하기 : as.vector(matrix)

    (2-2) 행렬을 행(row) 기준으로 벡터로 변환하기 : as.vector( t(matrix) )

 

 


 

 

(1) 벡터를 행렬로 변환하기 (converting vector into matrix)

 

 

 

 

    (1-1) 벡터를 열(column) 기준으로 행렬로 변환하기

 

> ##-------------------------------------------------------
> ## vector into matrix, matrix into vector (vectorization)
> ##-------------------------------------------------------
> 
> # input vector
> vec <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
> vec
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
> 
> # (1-1) vector into matrix by column
> matrix_bycol <- matrix(vec, byrow = F, ncol = 3)
> matrix_bycol
     [,1] [,2] [,3]
[1,]    1    6   11
[2,]    2    7   12
[3,]    3    8   13
[4,]    4    9   14
[5,]    5   10   15

 

 

 

 

    (1-2) 벡터를 행(row) 기준으로 행렬로 변환하기

 

> # (1-2) vector into matrix by row
> matrix_byrow <- matrix(vec, byrow = T, ncol = 3)
> matrix_byrow
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6
[3,]    7    8    9
[4,]   10   11   12
[5,]   13   14   15

 

 

 

 


 

 

 

(2) 행렬을 벡터로 변환하기 (vectorization, converting matrix into vector)

 

 

 

 

    (2-1) 행렬을 열(column) 기준으로 벡터로 변환하기

 

> # (2-1) matrix into vector (vectorization) by column : as.vector()
> matrix_bycol
     [,1] [,2] [,3]
[1,]    1    6   11
[2,]    2    7   12
[3,]    3    8   13
[4,]    4    9   14
[5,]    5   10   15
> vectorization_bycol <- as.vector(matrix_bycol)
> vectorization_bycol
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15

 

 

 

    (2-2) 행렬을 행(row) 기준으로 벡터로 변환하기

 

> # (2-2) matrix into vector (vectorization) by row : as.vector(t())
> matrix_bycol
     [,1] [,2] [,3]
[1,]    1    6   11
[2,]    2    7   12
[3,]    3    8   13
[4,]    4    9   14
[5,]    5   10   15
> vectorization_byrow <- as.vector(t(matrix_bycol))
> vectorization_byrow
 [1]  1  6 11  2  7 12  3  8 13  4  9 14  5 10 15 

 

 

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

 

 

Posted by R Friend R_Friend

요즘에 개인정보, 금융정보를 다루거나 기술 기반의 제조회사의 경우 정보보안 (내부 대외비의 외부 유출 방지, 외부 해커의 침입 방지) 이슈가 커지다보니 인터넷망 사용에 제약이 가해지는 경우가 많습니다.

 

R의 경우 base package를 설치하고 나서 분석에 필요한 package를 install.packages("package name") 함수로 설치한 후 library()로 로딩해서 사용을 합니다.

 

그런데 만약 '인증된 웹 페이지' 외에는 일단 차단하고 보는 인터넷 사용 정책을 택하고 있는 회사의 경우에는 install.packages("package name") 함수로 package를 설치하려고 하면 "인증되지 않은 사이트"라서 설치가 안되었다는 에러 메시지가 콘솔 창에 뜹니다. 이럴 경우에 네트워크 담당자한테 아쉬운 소리 안하고도 분석가가 원하는 package를 설치할 수 있는 2가지 방법을 소개해드리겠습니다.  1번째 방법 추천이구요, 1번째 방법이 안되면 2번째 방법을 시도해보세요.

 

 

 

(1) Use secure download method for HTTP 메뉴 check box 해제

 

 

보안 인증이 안된 페이지라면서 R package 설치가 안되는 경우,

 

   Tools

     > Global Options...

          > Packages

               > Use secure download method for HTTP (check 박스 해제)

 

하면 해당 package 다운로드/설치 및 연계된 package도 모두 알아서 다운로드/설치됩니다.

 

혹시 이래도 안되면 아래 방법 (완전 수동 -_-) 사용하시면 됩니다.

 

 

 

 

 

(2) R package를 개별로 download해서 하나씩 수동 설치 (완전 노가다... -_-;;;)

 

Time Series Analysis 분석을 위해서 TSA package를 설치해야할 필요가 생겼다고 가정하고 예를 들어보겠습니다.

 

 

1) The Comprehensive R Archive Network  https://cran.r-project.org   

에 접속합니다.

 

 

2) 왼쪽 하단의 "Packages" 메뉴를 클릭 => 우측 본문 상단의 "Table of available packages, sorted by name" 클릭합니다.

 

 

 

 

3) 설치하고자 하는 package의 알파벳을 찾습니다.  (본 예에서는 "TSA" package를 설치하고자 하므로 상단의 "T" 알파벳을 누른 후에 "TSA" 나올 때까지 스크롤바를 내려서 찾았습니다.  Ctrl+F 한다음에 "TSA" 키워드를 넣고 찾아도 됩니다)

 

 

 

4) 찾은 package 이름을 클릭합니다. (본 예에서는 아래 캡쳐처럼 "TSA" package 를 클릭) 

 

 

 

5) 압축되어 있는 package를 다운로드 합니다.

    - 사용하는 OS에 맞게 다운로드 합니다. (본 예에서는, 저는 Windows OS를 사용하므로 중앙에 위치한 Windows binaries 의 r-release: TSA_1.01.zip 파일을 다운로드 함)

    - ※ package에 따라서는 상단에 'Depends' 라고 쓰여있는 곳에 package가 있으면 같이 다운로드를 해서 설치를 해줘야 합니다. 자동차가 엔진(예: TSA package)만 있다고 움직이는게 아니고, 네개의 바퀴(예: leaps, locfit, mgcv, tseries package) 도 있어야지 굴러갈 수 있으므로 엔진 설치할 때 바퀴도 덩달아서 같이 설치해줘야 한다고 생각하시면 편하겠습니다. 

(이런 경우 install.packages("") 함수를 사용하면 콘솔 창에 원래 설치하려고 했던 package 외에도 Depends packages 가 자동으로 주욱 깔리는 걸 본적이 있을 겁니다. 현재는 네트워크 망이 막혀서 자동으로 설치가 안되는 상황을 가정하고 설명하는 중이므로, 자동으로 못깔리므로 수동으로 다운로드해서 깔아줘야 한다는 뜻입니다.)

 

 

 

 

만약 Depends 에 있는 package (본 예에서는 leaps, locfit, mgcv, tseries package) 를 먼저 설치하지 않은 상태에서 바로 본론으로 들어가서 TSA_1.01.zip 을 설치하려고 하면 아래와 같은 에러 메시지가 뜰겁니다.

 

> install.packages("C:/Users/Owner/Downloads/TSA_1.01.zip", repos = NULL, type = "win.binary")
Installing package into ‘C:/Users/Owner/Documents/R/win-library/3.2’
(as ‘lib’ is unspecified)
package ‘TSA’ successfully unpacked and MD5 sums checked
> library(TSA)
Error: ‘leaps’에 의해 요구된 패키지 ‘TSA’를 찾을 수 없습니다
In addition: Warning message:
패키지 ‘TSA’는 R 버전 3.2.4에서 작성되었습니다 

 

 

 

6) 다운로드 완료

 

 

 

 

7) Rstudio > Tools > Install Packages... 메뉴 선택

 

 

 

8) install Packages 팝업 창이 뜨면 "Install from: Package Archive File (.zip; tar.gz)" 를 선택합니다.

 

 

 

 

9) "Package archive" 의 "Browse" 단추를 누르면 윈도우즈 탐색기가 뜹니다. 그러면 (6)번에서 보이는 package download 해놓은 폴더로 가서 방금 전에 다운로드한 package 압축파일을 선택합니다.

그리고 팝업창의 하단에 "Install" 단추를 누르면 됩니다.

 

 

 

 

 

> install.packages("C:/Users/Owner/Downloads/leaps_2.9.zip", repos = NULL, type = "win.binary")
Installing package into ‘C:/Users/Owner/Documents/R/win-library/3.2’
(as ‘lib’ is unspecified)
package ‘leaps’ successfully unpacked and MD5 sums checked

 

 

 

10) 나머지 package들(본 예에서는 locfit_1.5-9.1, mgcv_1.8-12, tseries_0.10-34, TSA_1.01)도 (9)번에서 했던 방법대로 설치를 해줍니다.

 

 

> install.packages("C:/Users/Owner/Downloads/locfit_1.5-9.1.zip", repos = NULL, type = "win.binary")
Installing package into ‘C:/Users/Owner/Documents/R/win-library/3.2’
(as ‘lib’ is unspecified)
package ‘locfit’ successfully unpacked and MD5 sums checked
> install.packages("C:/Users/Owner/Downloads/mgcv_1.8-12.zip", repos = NULL, type = "win.binary")
Installing package into ‘C:/Users/Owner/Documents/R/win-library/3.2’
(as ‘lib’ is unspecified)
package ‘mgcv’ successfully unpacked and MD5 sums checked
> install.packages("C:/Users/Owner/Downloads/tseries_0.10-34.zip", repos = NULL, type = "win.binary")
Installing package into ‘C:/Users/Owner/Documents/R/win-library/3.2’
(as ‘lib’ is unspecified)
package ‘tseries’ successfully unpacked and MD5 sums checked
> install.packages("C:/Users/Owner/Downloads/TSA_1.01.zip", repos = NULL, type = "win.binary")
Installing package into ‘C:/Users/Owner/Documents/R/win-library/3.2’
(as ‘lib’ is unspecified)
package ‘TSA’ successfully unpacked and MD5 sums checked

 

 

 

11) 마지막으로 library(package name) 함수로 로딩합니다.

(본 예에서는 library(TSA) 로 로딩을 했는데요, 중간에 보니 Error 메시지로 'quadprog'이라고 불리는 패키지가 없습니다.. 라고 떴네요. ㅋㅋ ^^;  찾아보니 Functions to solve Quadratic Programming Problems 해주는 package가 quadprog 인데요, 이것도 위에 안내해준 방법대로 설치하고 나서 다시 한번 library(TSA) 하면 Error 메시지 없이 잘 설치될겁니다... package 수동설치 방법 설명하는게 이번 포스팅의 목적이므로 quadprog 수동설치 예시는 더이상은 하지않겠습니다)

 

> library(TSA)
필요한 패키지를 로딩중입니다: leaps
필요한 패키지를 로딩중입니다: locfit
locfit 1.5-9.1 	 2013-03-22
필요한 패키지를 로딩중입니다: mgcv
필요한 패키지를 로딩중입니다: nlme
This is mgcv 1.8-12. For overview type 'help("mgcv-package")'.
필요한 패키지를 로딩중입니다: tseries
Error in loadNamespace(j <- i[[1L]], c(lib.loc, .libPaths()), versionCheck = vI[[j]]) : 
  ‘quadprog’이라고 불리는 패키지가 없습니다
In addition: Warning messages:
1: 패키지 ‘TSA’는 R 버전 3.2.4에서 작성되었습니다 
2: 패키지 ‘leaps’는 R 버전 3.2.4에서 작성되었습니다 
3: 패키지 ‘locfit’는 R 버전 3.2.4에서 작성되었습니다 
4: 패키지 ‘mgcv’는 R 버전 3.2.4에서 작성되었습니다 
5: 패키지 ‘tseries’는 R 버전 3.2.4에서 작성되었습니다

 

 

 

그리 어렵거나 복잡한 것은 아닌데요, 그래도 혹시 여기까지 쭉~ 읽어보시고 귀찮다 싶으시면 사내 네트워크 보안담당자에게 찾아가서 CRAN 사이트는 인증된 사이트로 열어주세요...라고 부탁(혹은 팀장 품의?) 하시면 됩니다. 

 

 

참고로, Python 사용자라면 SSL 인증 문제로 Python 패키지 설치가 안될 때는  http://rfriend.tistory.com/304 를 참고하세요.

 

많은 도움이 되었기를 바랍니다.

 

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

 

Posted by R Friend R_Friend

수집한 데이터셋의 관측치들 간에 중복(duplication)이 있는 경우가 있습니다.  혹은 여러개의 흩어져 있는 데이터셋을 특정 기준 변수(key)를 사용해서 병합(merge)하는 전처리 작업을 하다 보면 가끔씩 의도하지 않게 중복데이터가 생기는 수가 있습니다.  서로 다른 행인데 각 변수별 관측값들이 동일한 경우 말이지요.  이럴 경우 저장 공간을 중복되는 만큼 더 잡아먹는고 데이터 처리 속도가 더 걸린다는 문제가 있으며, 더 심각하게는 이러한 데이터셋을 그대로 두고 통계 분석을 진행할 경우 분석 결과에 왜곡이 생길 수도 있으므로 중복 관측값 전처리가 필요합니다. (가령, 평균만 하더라도 중복이 있는 관측치가 여럿 있을 경우 분자의 관측치 합과 분모의 관측치 개수가 중복 제거후와는 서로 달라지겠지요?)

 

중복 데이터가 있을 경우에는 먼저 한개의 유일한 관측치(unique elements)만을 남겨놓고 나머지 중복 데이터들은 제거하는 전처리를 거쳐야 합니다. 이를 위해 R의

 

- {base} 패키지 : unique()

- {base} 패키지 : dataframe[!duplicated(var), ]

- {dplyr} 패키지 : distinct()

 

함수를 사용할 수 있습니다.

 

SAS 사용자라면 datastep에서 특정 변수를 기준으로 sorting을 한 후에 first.variable_name 또는 last.variable_name 명령문을 사용했던 것을 생각하시면 이해하기 쉬울 것입니다.  SAS와는 다르게 R은 sorting을 할 필요가 없습니다. (SAS는 merge 할 때도 먼저 sorting을 해줘야 하는 반면, R은 merge 할 때 sorting 해줄 필요 없습니다)

 

단, unique도 그렇고, merge도 그렇고 크기가 작은 데이터셋에는 별 무리가 없지만 대용량 데이터셋에 사용하기에는 처리 성능에 부담이 아주 많이 되는 함수입니다.  수십 기가가 넘는 대용량 사이즈의 데이터셋이라면 하둡 클러스터 내에서 Hive 등으로 중복 데이터 전처리하여 사용하시길 권합니다.

 

 

 

[ 중복없이 유일한 관측치만 남기기 (extracting unique elements) ]

 

 

 

 

R의 unique() 함수는 base package 함수에 내장되어 있으므로 별도의 패키지를 설치할 필요는 없습니다.  데이터 프레임(data frame), 배열(array), 행렬(matrix), 벡터(vector)에 모두 사용할 수 있는데요, 일반적으로 데이터 프레임을 분석에 많이 사용하므로 아래에는 데이터 프레임을 대상으로 unique() 함수 사용하는 방법을 소개하겠습니다.  

 

 

먼저 R 실습을 위해 관측치들 간에 중복이 포함되어 있는 변수 3개짜리 데이터 프레임을 만들어 보겠습니다.

 

> ##------------------------------------------
> ## extracting unique elements : unique()
> ##------------------------------------------
> 
> a1 <- rep(1:10, each = 2)
> a1
 [1]  1  1  2  2  3  3  4  4  5  5  6  6  7  7  8  8  9  9 10 10
> 
> a2 <- rep(c(1, 3, 5, 7, 9), each = 4)
> a2
 [1] 1 1 1 1 3 3 3 3 5 5 5 5 7 7 7 7 9 9 9 9
> 
> a3 <- c(1, 1, 1, 1, 3, 3, 3, 3, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10, 11, 12)
> a3
 [1]  1  1  1  1  3  3  3  3  5  5  6  6  7  7  8  8  9 10 11 12
> 
> a1a2a3 <- data.frame(cbind(a1, a2, a3))
> a1a2a3
   a1 a2 a3
1   1  1  1
2   1  1  1
3   2  1  1
4   2  1  1
5   3  3  3
6   3  3  3
7   4  3  3
8   4  3  3
9   5  5  5
10  5  5  5
11  6  5  6
12  6  5  6
13  7  7  7
14  7  7  7
15  8  7  8
16  8  7  8
17  9  9  9
18  9  9 10
19 10  9 11
20 10  9 12
> 
> str(a1a2a3)
'data.frame':	20 obs. of  3 variables:
 $ a1: num  1 1 2 2 3 3 4 4 5 5 ...
 $ a2: num  1 1 1 1 3 3 3 3 5 5 ...
 $ a3: num  1 1 1 1 3 3 3 3 5 5 ...

 

 

 

 

[예제 1]

변수가 3개 있는 데이터 프레임인데요, a1과 a2의 두 개 변수를 기준으로 중복여부를 체크한 후에, 중복이 있을 경우에는 1개만 선택하고 나머지 중복된 관측치는 제거하는 방법에 대한 예시는 아래와 같습니다.

 

>

> # extracting unique elements by 2 variables > aqa2a3_uniq_var2 <- unique(a1a2a3[, c("a1", "a2")]) > aqa2a3_uniq_var2 a1 a2 1 1 1 3 2 1 5 3 3 7 4 3 9 5 5 11 6 5 13 7 7 15 8 7 17 9 9 19 10 9

 

 

 

 

위의 예제에서 "a1"변수와 "a2" 변수를 기준으로 중복 관측치를 제거하고 유일한 관측치만 남기는 처리 개념을 풀어서 설명하면 아래와 같습니다.  참고로, a1a2a3[, c("a1", "a2")] 은 a1a2a3 데이터 프레임에서 변수 "a1"과 "a2"만 select 하라는 뜻입니다.

 

 

 

 


 

 

[예제 2]

다음으로, "a1", "a2", "a3" 세 개의 변수를 기준으로 중복된 관측치가 있다면 유일한 관측치만 남기고 나머지 중복 관측치는 제거하는 예를 들어보겠습니다.

 

> 

> # extracting unique elements by 3 variables > aqa2a3_uniq_var3 <- unique(a1a2a3[, c("a1", "a2", "a3")]) > aqa2a3_uniq_var3 a1 a2 a3 1 1 1 1 3 2 1 1 5 3 3 3 7 4 3 3 9 5 5 5 11 6 5 6 13 7 7 7 15 8 7 8 17 9 9 9 18 9 9 10 19 10 9 11 20 10 9 12

 

 

 

 

위 R 함수의 처리 과정을 풀어보면 아래와 같습니다.

 

 

 

 

"a1", "a2"의 두 개 변수만을 기준으로 했을 때 대비, "a1", "a2", "a3"의 세 개변수를 기준으로 unique() 함수를 적용했을 때 18번째와 20번째 행이 중복없는 유일한 행으로서 추가로 살아남았음을 알 수 있습니다.

 

 

 


 

[예제 3]

unique(x, fromLast = TRUE) 옵션을 적용하면 중복된 관측치들 중에서 남길 관측치(행, row)를 선택할 때 가장 밑에 있는 관측치(from Last observation)를 기준으로 선택합니다. default 는 fromLast = FALSE 입니다.

 

> 
> # identical element will be kept from the last : fromLast = TRUE 
> # defaulu : fromLast = FALSE
> aqa2a3_uniq_var3_fromLast <- unique(a1a2a3[, c("a1", "a2", "a3")], fromLast = TRUE) 
> aqa2a3_uniq_var3_fromLast  # differnt order
   a1 a2 a3
2   1  1  1
4   2  1  1
6   3  3  3
8   4  3  3
10  5  5  5
12  6  5  6
14  7  7  7
16  8  7  8
17  9  9  9
18  9  9 10
19 10  9 11
20 10  9 12

 

 

 

 

 

[예제2]의 fromLast = FALSE (default 이므로 특별히 명기할 필요는 없음) 일 때와 [예제3]의 fromLast = TRUE 일 때의 차이가 무엇인지 잘 이해가 안 갈 수도 있는데요, 아래 [예제2]와 [예제3]의 데이터프레임들의 row names 의 번호가 서로 다름을 알 수 있습니다. 

 

1번째 행과 2번째 행이 중복된 것의 처리에 대해서만 설명드리자면, [예제2]의 fromLast = FALSE의 경우1번 행(first row)을 가져왔고, [예제3]의 fromLast = TRUE 의 경우 2버 행(second row)를 유일한 행으로 가져왔습니다.

 

 

 

 

[예제2]와 [예제3]의 결과는 동일합니다. 그런데 왜 굳이 이런것이 필요하지 싶을 것입니다. ^^?

위의 예제는 설명의 편의를 위해서 변수 3개 짜리 중복 데이터셋을 초간단으로 만들어본 것인데요, 실전에서 사용하는 데이터셋은 변수가 수십, 수백개, 관측치(행의 개수)도 수천, 수만, 수백만개인 경우가 다반사입니다.  이때 특정 변수를 기준으로 중복인 경우 유일한 관측치를 선별하고, 나머지 변수들은 그대로 사용해야 하는 경우 중복된 관측치의 first observation을 살려둘지 아니면 last obsetvation을 살려둘지에 따라서 중복 제거 기준 변수 이외의 타 변수들의 관측치 값들이 다른 경우에는 고민 좀 해야겠지요?  이럴 경우에 fromLast = FALSE/TRUE 옵션이 필요합니다.  (이럴 경우 SAS처럼 미리 특정 변수를 기준으로 정렬해놔야 겠군요)

 

 


 

 

{base} package에 내장되어 있는 duplicated() 함수를 사용해서 중복값을 제거하고 유일한 값만 선별할 수도 있습니다.  duplicated() 함수를 사용하면 아래 예시처럼 중복되는 행에 대해서 TRUE, FALSE boolean 값을 반환합니다.  이 논리형 값을 가지고 dataframe에서 indexing을 해오는 방식으로 중복값을 처리하고 유일한 값만 남겨놓을 수 있습니다.

 

 

> ##-----------------------
> ## duplicated() function
> ##-----------------------
> 
> # original dataset
> a1a2a3
   a1 a2 a3
1   1  1  1
2   1  1  1
3   2  1  1
4   2  1  1
5   3  3  3
6   3  3  3
7   4  3  3
8   4  3  3
9   5  5  5
10  5  5  5
11  6  5  6
12  6  5  6
13  7  7  7
14  7  7  7
15  8  7  8
16  8  7  8
17  9  9  9
18  9  9 10
19 10  9 11
20 10  9 12
> 
> 
> # returning TRUE for duplicated value
> duplicated(a1a2a3$a1) 
 [1] FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE
[12]  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE
> 
> 
> # indexing unduplicated rows using dataframe[!duplicated(dataframe$var1), ]
> a1a2a3_not_duplicated_var1 <- a1a2a3[!duplicated(a1a2a3$a1),]
> a1a2a3_not_duplicated_var1
   a1 a2 a3
1   1  1  1
3   2  1  1
5   3  3  3
7   4  3  3
9   5  5  5
11  6  5  6
13  7  7  7
15  8  7  8
17  9  9  9
19 10  9 11
> 
> # another exmaple
> a1a2a3[!duplicated(a1a2a3$a2),]
   a1 a2 a3
1   1  1  1
5   3  3  3
9   5  5  5
13  7  7  7
17  9  9  9

 

 

 


 

dplyr 패키지의 distinct() 함수도 중복이 없는 유일한 값을 반환합니다.  dplyr 패키지의 distinct() 가 깔끔하기도 하고, dplyr 패키지 내의 여러 데이터 전처리 함수를 함께 이용할 수 있어서 알아두시면 좋겠습니다.

 

 

 


많은 도움이 되었기를 바랍니다.

 

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

 

 

Posted by R Friend R_Friend

이번 포스팅에서는 RStudion 에서 한줄의 Script로 console 창, Environment 창의 datasets, Plots 들을 모조리 삭제/청소하는 방법을 소개하겠습니다.

 

 

아래의 명령문을 사용하지 않는다면 rm() 함수로 데이터셋 이름을 일일이 나열하는 노가다를 해야만 합니다. ^,^;  

 

RStudio에서 Datasets 이나 Plots 을 한꺼번에 삭제/청소하려면 붓 모양의 아이콘을 누르면 되긴 합니다만, programing 이나 사용자 정의함수(user defined function) 내에 모든 객체 삭제/청소 기능을 넣고 싶다면 붓 모양 아이콘을 누르는 행위를 Script로 옮길 수 있어야 겠지요.

 

 

 

(1) 좌측 하단의 Console 창에 있는 messages 들을 삭제(clear, delete)하고 싶을 때

 

# clearing of console in Rstudio
cat("\014")

 

 

 

(2) 우측 상단의 Environment 창에 있는 모든 Datasets 을 삭제(clear, delete)하고 싶을 때 

 

# clearing of datasets in Rstudio Environment
rm(list=ls())

 

 

 

(3) 우측 하단의 Plots 창에 있는 모든 Plots들을 삭제(clear, delete)하고 싶을 때

 

# clearing of plots in Rstudio
dev.off()

 

 

을 사용하면 되겠습니다.

 

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

Posted by R Friend R_Friend

DB에 들어있는 데이터를 내렸을 때, 설문조사 데이터를 받았을 때, 원천 거래 데이터를 받았을 때, 혹은 분석 과정 중의 데이터 셋이 분석가가 하고자 하는 통계분석, 데이터마이닝 분석이나 ggplot2 등의 그래프/시각화를 위해 필요한 데이터 구조로 딱 맞아떨어지지 않는 경우가 굉장히 많습니다.

 

이때 필요한 것이 데이터를 분석 목적, 기법에 맞게 자유자재로 변형하여 재구조화하는 일입니다.  

 

엑셀에서 Pilvot Table 생성하는 것이 제일 이해하기 쉬운 예가 될거 같습니다.  세로로 길게 늘어서 있는 원 데이터를 가로와 세로로 틀을 잡아 주고, 가운데에는 value 값에 대해서 개수를 센다든지, 합계를 낸다든지, 평균을 구한다든지 하는 함수를 적용하는 것을 해보셨을 겁니다. 

 

비유를 해보자면, 레고블록으로 높이 세워진 탑을 모양과 색깔별로 레고 블록을 잘 분해한 다음에, 이를 재료로 해서 가로와 세로로 넓게 펴고 높이는 낮추어서 새로운 탑을 만드는 것이라고 생각해보는 것도 이해하는데 도움이 될거 같습니다.

 

이때 데이터 재구조화하는 기준 변수를 무엇으로 하느냐, 가로로 길게 늘어뜨릴 변수는 또 무엇으로 하느냐, 가운데에 값을 볼 때 무슨 함수를 사용해서 값을 계산해서 볼 것이냐에 따라서 다양한 경우의 수가 발생하게 됨에 따라 말로 모든 것을 설명하는 것이 무리가 있습니다.  따라서 아래에 reshape 패키지의 melt(), cast() 함수를 몇 가지 경우의 수에 적용해 보면서 원래 값이 이후에 어떻게 바뀌는지를 유심히 보시고, 필요한 상황에 맞는 R script를 참고하시면 되겠습니다.

 

 

[ reshape 패키지 내 melt()함수, cast() 함수 사용 데이터 재구조화 예시 ]

 

 

 

 

예제로 사용할 데이터는 MASS 패키지에 내장되어 있는 Cars93 데이터 프레임의 차종(Type), 제조국(Origin), 도시 연비(MPG.city), 고속도로 연비(MPG.highway) 4개의 변수를 사용해서 몇가지의 경우의 수를 조합해 가면서 데이터를 녹였다가 (melt) 재구조화 (cast) 를 해보겠습니다.

 

> 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 ...

 

 

데이터 양이 너무 많으면 melt(), cast()를 적용했을 때 데이터 구조가 변화하는 모양을 보기가 쉽지 않기 때문에 차종(Type) 중에서 개수가 적은 "Compact"와 "Van" 만 선별해서 예제로 사용하겠습니다.

 

 

> table(Cars93$Type)

Compact   Large Midsize   Small  Sporty     Van 
     16      11      22      21      14       9 
> 
> Cars93_sample <- subset(Cars93, 
+                         select = c(Type, Origin, MPG.city, MPG.highway), 
+                         subset = (Type %in% c("Compact", "Van"))) 
> 
> Cars93_sample
      Type  Origin MPG.city MPG.highway
3  Compact non-USA       20          26
12 Compact     USA       25          36
13 Compact     USA       25          34
16     Van     USA       18          23
17     Van     USA       15          20
21 Compact     USA       23          28
25 Compact     USA       22          27
26     Van     USA       17          21
33 Compact     USA       22          27
36     Van     USA       15          20
43 Compact non-USA       24          31
55 Compact non-USA       26          34
56     Van non-USA       18          24
58 Compact non-USA       20          29
65 Compact non-USA       24          30
66     Van non-USA       17          23
68 Compact     USA       24          31
70     Van     USA       18          23
74 Compact     USA       23          31
78 Compact non-USA       20          26
82 Compact non-USA       23          30
87     Van non-USA       18          22
89     Van non-USA       17          21
90 Compact non-USA       21          30
92 Compact non-USA       21          28
> dim(Cars93_sample)
[1] 25  4

 

 

 

R의 reshape 패키지는 별도의 설치가 필요합니다. 

 

# reshape package installation, library
> install.packages("reshape")

> library(reshape)

 

 

 

이제 melt(data, id.vars, measure.vars) 함수를 사용해서 기존 데이터셋을 녹여보도록 하겠습니다. 

 

> # melt()
> Cars93_sample_melt <- melt(data = Cars93_sample, 
+                            id.vars = c("Type", "Origin"), 
+                            measure.vars = c("MPG.city", "MPG.highway"))
> 
> Cars93_sample_melt
      Type  Origin    variable value
1  Compact non-USA    MPG.city    20
2  Compact     USA    MPG.city    25
3  Compact     USA    MPG.city    25
4      Van     USA    MPG.city    18
5      Van     USA    MPG.city    15
6  Compact     USA    MPG.city    23
7  Compact     USA    MPG.city    22
8      Van     USA    MPG.city    17
9  Compact     USA    MPG.city    22
10     Van     USA    MPG.city    15
11 Compact non-USA    MPG.city    24
12 Compact non-USA    MPG.city    26
13     Van non-USA    MPG.city    18
14 Compact non-USA    MPG.city    20
15 Compact non-USA    MPG.city    24
16     Van non-USA    MPG.city    17
17 Compact     USA    MPG.city    24
18     Van     USA    MPG.city    18
19 Compact     USA    MPG.city    23
20 Compact non-USA    MPG.city    20
21 Compact non-USA    MPG.city    23
22     Van non-USA    MPG.city    18
23     Van non-USA    MPG.city    17
24 Compact non-USA    MPG.city    21
25 Compact non-USA    MPG.city    21
26 Compact non-USA MPG.highway    26
27 Compact     USA MPG.highway    36
28 Compact     USA MPG.highway    34
29     Van     USA MPG.highway    23
30     Van     USA MPG.highway    20
31 Compact     USA MPG.highway    28
32 Compact     USA MPG.highway    27
33     Van     USA MPG.highway    21
34 Compact     USA MPG.highway    27
35     Van     USA MPG.highway    20
36 Compact non-USA MPG.highway    31
37 Compact non-USA MPG.highway    34
38     Van non-USA MPG.highway    24
39 Compact non-USA MPG.highway    29
40 Compact non-USA MPG.highway    30
41     Van non-USA MPG.highway    23
42 Compact     USA MPG.highway    31
43     Van     USA MPG.highway    23
44 Compact     USA MPG.highway    31
45 Compact non-USA MPG.highway    26
46 Compact non-USA MPG.highway    30
47     Van non-USA MPG.highway    22
48     Van non-USA MPG.highway    21
49 Compact non-USA MPG.highway    30
50 Compact non-USA MPG.highway    28
> dim(Cars93_sample_melt)
[1] 50  4

 

 

 

 

이렇게 melt()함수를 사용해 녹인 데이터를 cast()함수를 사용해서 재구조화 해보겠습니다.  세로와 가로에 무슨 변수를 넣을지가 결정되었다면 아래의 예제를 참고해서 원하는 구조에 맞게 R script를 작성하시면 되겠습니다. (말로 설명하기가 쉽지가 않습니다 ^^;) function 란에는 R에서 사용할 수 있는 통계량 함수를 사용하면 되며, 이번 예제에서는 평균(mean) 함수를 사용하였습니다. 

 

> # cast()
> options(digits=3) # 소숫점 너무 밑에 까지 나오지 않도록 설정
> 
> # 한개의 id.var 기준(세로) & variable(가로) 조합의 value 값에 mean 함수 적용
> cast(data = Cars93_sample_melt, Type ~ variable, fun = mean)
     Type MPG.city MPG.highway
1 Compact     22.7        29.9
2     Van     17.0        21.9
 
> cast(data = Cars93_sample_melt, Origin ~ variable, fun = mean)
   Origin MPG.city MPG.highway
1     USA     20.6        26.8
2 non-USA     20.7        27.2
> 

 

> # 두개의 id.var 기준(세로) & variable(가로) 조합의 value 값에 mean 함수 적용
> cast(data = Cars93_sample_melt, Type + Origin ~ variable, fun = mean)
     Type  Origin MPG.city MPG.highway
1 Compact     USA     23.4        30.6
2 Compact non-USA     22.1        29.3
3     Van     USA     16.6        21.4
4     Van non-USA     17.5        22.5
> 

 

> # 한개의 id.var 기준(세로) & 다른 id.var + variable (가로) 조합의 value 값에 mean 함수 적용
> cast(data = Cars93_sample_melt, Type ~ Origin + variable, fun = mean)
     Type USA_MPG.city USA_MPG.highway non-USA_MPG.city non-USA_MPG.highway
1 Compact         23.4            30.6             22.1                29.3
2     Van         16.6            21.4             17.5                22.5
> cast(data = Cars93_sample_melt, Origin ~ Type + variable, fun = mean)
   Origin Compact_MPG.city Compact_MPG.highway Van_MPG.city Van_MPG.highway
1     USA             23.4                30.6         16.6            21.4
2 non-USA             22.1                29.3         17.5            22.5
> 

 

> # 한개의 id.var + variable (세로) & 다른 id.var (가로) 조합의 value 값에 mean 함수 적용
> cast(data = Cars93_sample_melt, Type + variable ~ Origin, fun = mean)
     Type    variable  USA non-USA
1 Compact    MPG.city 23.4    22.1
2 Compact MPG.highway 30.6    29.3
3     Van    MPG.city 16.6    17.5
4     Van MPG.highway 21.4    22.5

 

> cast(data = Cars93_sample_melt, Origin + variable ~ Type, fun = mean)
   Origin    variable Compact  Van
1     USA    MPG.city    23.4 16.6
2     USA MPG.highway    30.6 21.4
3 non-USA    MPG.city    22.1 17.5
4 non-USA MPG.highway    29.3 22.5

 

 

 

간혹 reshape 패키지가 cast() 함수를 사용할 때 에러가 나는 경우가 있더군요.  왜 그런지는 정확히는 모르겠습니마단, 그런 경우 reshape2 패키지를 설치해서 사용하면 해결이 되니 참고하시기 바랍니다. 

 

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

 

Posted by R Friend R_Friend

거래 원 데이터 (transaction raw data)를 받으면 분석 용도에 맞게 데이터 전처리를 할 때 보통 하는 일이 특정 기준 (가령, 고객 ID, 상품 ID, 채널 ID 등)에 대해 데이터를 집계(합계, 평균, 분산 등의 함수를 적용)하는 작업을 하게 됩니다. 

 

R에는 aggregate() 라는 함수가 있습니다만, 기존에 SQL에 익숙한 분석가라면 R 에서 SQL 문을 사용할 수 있게 해주는 sqldf package를 사용하면 쉽고 빠르게 집계를 할 수 있겠습니다. 

 

(단, sqldf 가 performance 이슈가 있으니 데이터 사이즈가 크다면, 그리고 데이터 처리 속도가 중요한 경우라면 sqldf 는 부적할 수도 있다는 점은 고려하셔야 겠습니다.)

 

R sqldf package 소개자료에 보면

  - Perform SQL Selects on R Data Frames
  - Manipulate R data frames using SQL

이라고 되어 있습니다.

 

 

 

 

그럼, sqldf package의 여러 기능, 함수 중에서 데이터 집계 관련한 함수만 몇 가지 선별하여서 소개하도록 하겠습니다.

 

실습에 사용할 데이터는 MASS 패키지에 내장된 Cars93 데이터 프레임의 자동차 유형(Type), 도시 연비(MPG.city), 고속도로 연비(MPG.highway) 를 사용하겠습니다.

 

> 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 ...

 

 

 

R의 aggregate() 함수로 차종(Type)별 도시 연비(MPG.city)와 고속도로 연비(MPG.highway)의 평균을 구해보겠습니다. 

 

> # aggregate

> R_aggregate_mean <- aggregate(Cars93[,c(7,8)], + by = list(Car_Type = Cars93$Type), # list + FUN = mean, # function + na.rm = TRUE)

> > R_aggregate_mean Car_Type MPG.city MPG.highway 1 Compact 22.68750 29.87500 2 Large 18.36364 26.72727 3 Midsize 19.54545 26.72727 4 Small 29.85714 35.47619 5 Sporty 21.78571 28.78571 6 Van 17.00000 21.88889 

 

 

 

 

이번에는 install.packages()함수와 library()함수를 사용하여 sqldf Package 를 설치하고 호출한 후에, sqldf 패키지를 사용하여 위와 같이 차종(Type)별 도시 연비(MPG.city)와 고속도로 연비(MPG.highway)의 평균을 구해보겠습니다.

 

> install.packages("sqldf")
Installing package into ‘C:/Users/user/Documents/R/win-library/3.2’
(as ‘lib’ is unspecified)
trying URL 'http://cran.rstudio.com/bin/windows/contrib/3.2/sqldf_0.4-10.zip'
Content type 'application/zip' length 71825 bytes (70 KB)
downloaded 70 KB

package ‘sqldf’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
	C:\Users\user\AppData\Local\Temp\Rtmp4i7Dhq\downloaded_packages
> library(sqldf)
필요한 패키지를 로딩중입니다: gsubfn
필요한 패키지를 로딩중입니다: proto
필요한 패키지를 로딩중입니다: RSQLite
필요한 패키지를 로딩중입니다: DBI
Warning messages:
1: 패키지 ‘sqldf’는 R 버전 3.2.2에서 작성되었습니다 
2: 패키지 ‘gsubfn’는 R 버전 3.2.2에서 작성되었습니다 
3: 패키지 ‘RSQLite’는 R 버전 3.2.2에서 작성되었습니다 
4: 패키지 ‘DBI’는 R 버전 3.2.2에서 작성되었습니다 

 

> R_sqldf_1 <- sqldf('
+                  select "Type" as "Car_Type", 
+                  avg("MPG.city") as "mean_MPG.city", 
+                  avg("MPG.highway") as "mean_MPG.highway"  
+                  from Cars93 
+                  group by Type
+                  order by Type
+                  ')
> R_sqldf_1
  Car_Type mean_MPG.city mean_MPG.highway
1  Compact      22.68750         29.87500
2    Large      18.36364         26.72727
3  Midsize      19.54545         26.72727
4    Small      29.85714         35.47619
5   Sporty      21.78571         28.78571
6      Van      17.00000         21.88889

 

 

R의 aggregate()함수로 만든 평균과 sqldf로 만든 평균 데이터 셋을 차종(Type) 을 key로 항 merge 한 후에 두 값들이 서로 같은지 한번 점검해보겠습니다.

 

> # 두개 데이터 셋 Merge, 동일 여부 check
> Type_mean <- merge(R_aggregate_mean, R_sqldf_1, by = 'Car_Type')
> Type_mean <- transform(Type_mean, 
+                        gap_MPG.city = MPG.city - mean_MPG.city, 
+                        gap_MPG.highway = MPG.highway - mean_MPG.highway)
> 
> Type_mean
  Car_Type MPG.city MPG.highway mean_MPG.city mean_MPG.highway gap_MPG.city gap_MPG.highway
1  Compact 22.68750    29.87500      22.68750         29.87500            0               0
2    Large 18.36364    26.72727      18.36364         26.72727            0               0
3  Midsize 19.54545    26.72727      19.54545         26.72727            0               0
4    Small 29.85714    35.47619      29.85714         35.47619            0               0
5   Sporty 21.78571    28.78571      21.78571         28.78571            0               0
6      Van 17.00000    21.88889      17.00000         21.88889            0               0

 

얼핏 보면 R의 aggregate() 함수와 sqldf 가 서로 큰 차이가 없거나 혹은 aggregate()함수가 더 편하다고 느낄 수도 있겠습니다.  그런데, 아래의 경우처럼 다수의 함수들(count, sum, avg, variance, stdev, min, max 등)을 그룹 변수에 대해서 구분해서 집계를 할 경우에는, 그리고 SQL에 익숙한 사용자라면 sqldf 패키지를 사용하는게 편할 수 있을 것입니다 

 

 

> # SQL의 aggregation 함수 사용하기
> R_sqldf_2 <- sqldf('
+                    select "Type" as "Car_Type", 
+                    count("MPG.city") as "count_MPG.city", 
+                    sum("MPG.city") as "sum_MPG.city", 
+                    
+                    avg("MPG.city") as "mean_MPG.city", 
+                    variance("MPG.city") as "variance_MPG.city", 
+                    stdev("MPG.city") as "stdev_MPG.city", 
+                    
+                    min("MPG.city") as "min_MPG.city", 
+ 
+                    max("MPG.city") as "max_MPG.city"
+                    
+                    from Cars93 
+                    group by Type
+                    order by Type desc
+                    ')
> 
> # count :  행의 개수
> # sum : 합계
> # avg : 평균
> # var : 분산
> # stddev : 표준편차
> # min : 최소값
> # max : 최대값
> # order by xx desc : 내림차순 정렬
> 
> R_sqldf_2
  Car_Type count_MPG.city sum_MPG.city mean_MPG.city variance_MPG.city stdev_MPG.city min_MPG.city max_MPG.city
1      Van              9          153      17.00000          1.500000       1.224745           15           18
2   Sporty             14          305      21.78571         15.258242       3.906180           17           30
3    Small             21          627      29.85714         37.328571       6.109711           22           46
4  Midsize             22          430      19.54545          3.593074       1.895540           16           23
5    Large             11          202      18.36364          2.254545       1.501514           16           20
6  Compact             16          363      22.68750          3.695833       1.922455           20           26

 

 

변수명을 SQL 문 내에서 바로 부여하는 것도 편리합니다.  그리고 SQL에 능숙한 분석가라면 subquery를 사용해서 한방에 query를 다 돌려서 원하는 데이터셋을 만들어낼 수도 있겠습니다.  (단, sqldf는 속도는 희생될 수 있음)

 

 

그렇다고 sqldf가 데이터 집계를 하는데 있어 모든 통계량을 다 한번에 할 수 있는것은 아닙니다.  R에서는 아래 처럼 median, quantile 을 1줄만에 처리할 수 있는 반면에, 이것과 동일한 결과를 얻으려면 SQL로는 참 어렵습니다.

 

> # R로 median, quantile 지정해서 구하기
> R_aggregate_median <- aggregate(Cars93[,c(7,8)], by = list(Car_Type = Cars93$Type), FUN = median)
> R_aggregate_median
  Car_Type MPG.city MPG.highway
1  Compact     23.0        30.0
2    Large     19.0        26.0
3  Midsize     19.0        26.5
4    Small     29.0        33.0
5   Sporty     22.5        28.5
6      Van     17.0        22.0
> 
> quantile_MPG.city <- quantile(Cars93[,c("MPG.city")], c(0, .01, .05, .1, .25, .5, .75, .9, .95, .99, 1))
> quantile_MPG.city
   0%    1%    5%   10%   25%   50%   75%   90%   95%   99%  100% 
15.00 15.00 16.60 17.00 18.00 21.00 25.00 29.00 31.40 42.32 46.00

 

sqldf 가 편하다고 했다가, 그냥 R 함수가 편하다가 했다가 오락가락 하는 것처럼 보일 수도 있겠는데요, 위의 예제를 보시고 데이터 전처리, 분석의 목적, 상황에 맞게 sqldf와 aggregate() 함수, R 함수를 선별해서 사용하시면 되겠습니다.

 

 

{dplyr} package의 summarise(n = n()), tally(), count() 함수를 사용한 집계 방법은 http://rfriend.tistory.com/240  포스팅을 참고하세요.

 

 

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

 

Posted by R Friend R_Friend

데이터 변환 방법으로서

(1) 표준화

(2) 정규분포화

(3) 범주화

     - 이산형화

     - 이항변수화

(4) 개수 축소

(5) 차원 축소

     - 주성분분석

     - 요인분석 

(6) 시그널 데이터 변환

   - 푸리에 변환 (FFT: Fast Fourier Transform)

    - 웨이블릿 변환(Wavelet Transform)

의 6개 구분 중에서

 

전파, 진동, 소리, 파도, 빛 등 시간(time domain)에 따라 주기성(periodicity)을 띠면서 파형을 형성하는 데이터를 주파수(spectrum domain) 대역별로 세기로 변환하는 푸리에 변환(Fourier Transform)에 대해서 알아보겠습니다.  참고로, 푸리에(Jean-Baptiste Joseph Fourier, 1768~1830)는 프랑스의 수학자이자 물리학자로서, 푸리에 변환은 바로 이분의 이름을 딴 것이랍니다.  저 아래에 애니메이션의 오른쪽 하단에 사진이 푸리에가 되겠습니다.

 

 

 

푸리에 변환은 물리나 공업 분야에서 폭넓게 사용되고 있기에, 특히 시그널 데이터, 기계 데이터, 센서 데이터 등의 주기성을 띤 데이터를 분석하는 분이라면 반드시 알아야 할 변환이라고 하겠습니다.

 

푸리에 변환을 이해하려면 삼각함수(특히 사인함수, 코사인함수), 미적분, 함수의 사칙연산, 함수의 직교성, 푸리에 급수와 계수 등에 대해서 알아야 하는데요, 어려운 수학 공식은 다른 교재를 참고하시기 바라며, 이번 포스팅에서는 직관적으로 이해할 수 있는 그림과 예시를 들어서 가급적 쉽게 설명을 해보겠습니다.

 

아래의 그림처럼 시간에 따른 진폭 데이터를 => 주파수별 세기의 데이터로 변환하는 작업을 푸리에변환이라고 하며, 그 반대를 푸리에역변환이라고 합니다.

 

 

[ 푸리에변환과 푸리에역변환 ]

 

 

 

먼저, 일정한 괘도를 회전하는 운동을 하는 시간함수로 나타내는 법에 대해서 알아보겠습니다.  아래 애니메이션의 왼쪽이 일정한 속도로 반지름이 일정한 괘도를 회전하는 운정이 되겠구요, 오른쪽이 이 회전운동을 시간축에 옮겨놓았을 때의 모양입니다.  전형적인 코사인(cosine) 형태를 띠고 있습니다.

 

* 출처: http://www.di.fc.ul.pt/~jpn/r/fourier/fourier.html

 

 

 

아래는 왼쪽의 반지름 거리와 시간 주기가 다른 4개의 회전운동을 오른쪽에 시간 축에 진폭을 나타낸 그래프가 되겠습니다.  아래 보는 것처럼 회전운동은 시간을 축으로 해서 진폭이 변화하는 값을 사인(sine) 또는 코사인(cosine) 함수로 나타낼 수 있습니다.

 

* 출처: http://www.di.fc.ul.pt/~jpn/r/fourier/fourier.html

 

 

 

이렇게 주기성을 띤 회전운동을 시간함수로 나타낼 수 있는데요, 이 시간함수는 사실 여러개의 주파수를 띤 시간함수들이 합해진 것입니다.  아래 예시로 든 그림에서는 주기(주파수)가 다른 3개의 시간함수가 함쳐져서 1개의 시간함수를 형성하고 있는데요, 주파수별로 필터링을 해서(주파수 성분을 구한다고 함) 세기가 큰 (peaks) 주파수를 헤아리면 되겠습니다. 시간함수를 알면 주파수 스펙트럼을 구할 수 있고, 주파수 스펙트럼을 알면 이들을 합쳐서 시간함수를 구할 수 있게 됩니다.

 

 

[ 시간함수와 주파수 스펙트럼의 관계 ]

 

* 그림 출처: aragec.com556

 

 

주파수는 1초에 파동 cycle이 몇 번 반복되느냐를 나타내는 말로서, 단위는 Hz(헤르츠)를 사용합니다.  아래의 3개의 파형을 예로 들면, 3개 파형 모두 진폭은 -1 ~ +1 로 동일한 반면에 주기는 모두 다릅니다. (즉, 주파수가 모두 다름)  첫번째 파형은 1초에 2회 주기이므로 주파수는 2Hz, 두번째 파형은 1초에 4회 주기이므로 4Hz, 세번째 파형은 1초에 6회 주기이므로 6Hz 주파수가 되겠습니다.

 

 

[ 주기와 진폭 ]

 

참고로, 악기 음 조율할 때 사용하는 소리굽쇠는 440Hz 의 '라'음을 낸답니다. 1초에 주기가 440회 진동이 발생한다는 뜻입니다. 서울/경기 지역의 '별이 빛나는 밤에' 라디오 주파수가 95.9kHz 인데요, 이는 전파가 1초에 9만 5천9백번 진동한다는 의미입니다.  음악에서는 주파수가 낮을 수록(현이 굷고 길수록) 저음이 나고, 주파수가 높을 수록(현이 가늘고 짧을 수록) 고음이 납니다.

 

이제 R을 가지고 1초에 2pi 만큼을 단위 시간 구간으로 해서 진폭과 주기를 달리한 4개의 사인함수 그래프도 그려보고, FFT (Fast Fourier Transform) 변환 실습을 해보도록 하겠습니다.

 

> # 사인함수 파라미터 설정 > x <- seq(0, 2*pi, by=pi/100) > > amp.1 <- 2 # 진폭(amplitude) 2 > amp.2 <- 2 # 진폭 2 > amp.3 <- 5 # 진폭 5 > amp.4 <- 5 # 진폭 5 > > wav.1 <- 1 # 주기(wave-length, cycle) 1 > wav.2 <- 2 # 주기 2 > wav.3 <- 3 # 주기 3 > wav.4 <- 7 # 주기 7 > > # 사인함수 생성 > signal.1 <- amp.1*sin(wav.1*x) # 진폭 2 & 주기 1인 사인함수 > signal.2 <- amp.2*sin(wav.2*x) # 진폭 2 & 주기 2인 사인함수 > signal.3 <- amp.3*sin(wav.3*x) # 진폭 5 & 주기 3인 사인함수 > signal.4 <- amp.4*sin(wav.4*x) # 진폭 5 & 주기 7인 사인함수 >

 

 

4개의 사인함수를 각각 순서대로 그려보면 아래와 같습니다. 첫번째와 두번째 그래프는 진폭(높이, y축)이 '2'로서 동일하고, 세번째와 네번째 그래프는 진폭이 '5'로서 동일합니다.  1초에 몇번의 주기가 있는지, 즉 주파수에 해당하는 주기는 순서대로 1, 2, 3, 7로서 뒤로 갈수록 증가하는 사인함수 그래프로서, 주파수가 커질수록 1초당 주기 갯수가 많아집니다 (진동 회수가 증가).

 

> # 사인함수 시간에 따른 그래프
> par(mfrow = c(1,4))
> plot(x, signal.1, type='l', ylim=c(-5,5)); abline(h=0, lty=3) # 진폭 2 & 주기 1인 사인함수
> plot(x, signal.2, type='l', ylim=c(-5,5)); abline(h=0, lty=3) # 진폭 2 & 주기 2인 사인함수
> plot(x, signal.3, type='l', ylim=c(-5,5)); abline(h=0, lty=3) # 진폭 5 & 주기 3인 사인함수
> plot(x, signal.4, type='l', ylim=c(-5,5)); abline(h=0, lty=3) # 진폭 5 & 주기 7인 사인함수

 

 

 

 

다음으로, 위 4개의 시간에 따른 사인함수를 합한 후에 그래프로 나타내보겠습니다.  위 4개의 개별 시간에 따른 사인함수가 주기성을 띠므로 아래의 1개로 합쳐진 시간함수도 일정한 주기성을 띠게 됩니다.  (푸리에 변환은 지금 하는 작업과는 거꾸로, 여러개의 시간함수들이 합쳐진 시간함수를 개별 시간함수들로 분해 해서 각 개별 시간함수들의 주파수 성분을 구하는 것입니다.  푸리에 변환할 때는 파형이 주기성을 띤다고 가정하고 변환을 진행합니다.) 

 

 

> # 사인함수 4개 합치기 (sine function summation)
> signal.1234 <- signal.1 + signal.2 + signal.3 + signal.4
> head(signal.1234, n=30)
 [1] 0.000000 1.749660 3.442051 5.022470 6.441235 7.655888 8.633062 9.349913 9.795059 9.968964
[11] 9.883774 9.562593 9.038247 8.351592 7.549453 6.682284 5.801674 4.957812 4.197038 3.559593
[21] 3.077684 2.773944 2.660382 2.737852 2.996074 3.414214 3.961977 4.601179 5.287703 5.973757
> 
> # 사인함수 4개 합친 그래프 그리기
> par(mfrow = c(1,1))
> plot(x, signal.1234, type='l', main = "Sum of signal.1&2&3&4", 
+      xlab = "Time", ylab = "Amplitude")
> abline(h=0, lty=3)

 

 

 

 

R로 푸리에 변환할 때는 stats 패키지의 fft() 함수를 사용합니다.  fft()함수를 적용한 값을 관측치 개수의 반개((N-1)/2 = (201-1)/2 = 100 )로 나누어서 표준화를 시켜주고, abs()함수를 적용해 절대값을 취하게 됩니다.  plot()으로 그래프를 그려보면 좌우로 대칭인 그래프가 그려지는데요, 절반을 기점으로 해서 똑같은 대칭 그래프라서 왼쪽의 값(그래프)만을 사용하면 되겠습니다.  

 

이 예제에서는 주파수가 1Hz, 2Hz, 3Hz, 7Hz 짜리 4개의 사인함수를 더한 것이었으므로 1Hz, 2Hz,  3Hz, 7Hz 지점에서 피크(peak)를 치는 스펙트럼을 보여줄 겁니다.  따라서 1~20Hz 까지만 뽑아서 자세히 본 그래프가 두번째 그래프가 되겠습니다. 

 

> # 푸리에 변환 (FFT: Fast Fourier Transform)

> library(stats) > N <- length(x) > > fft_x_abs <- abs(fft(signal.1234)/((N-1)/2)) # 표준화, 절대값 > plot(fft_x_abs, type="h") >



 

> # 앞의 주파수 20개만 그래프 그려보기 > plot(fft_x_abs[1:20], type="h")

 



 

위의 두번째 그래프에서 보면 원래는 1Hz, 2Hz, 3Hz, 7Hz 에서 피크를 쳐야하는데요, x축 주파수가 한칸씩 오른쪽으로 밀렸습니다. -_ㅜ;  어디서 잘못한건지 잘 모르겠는데요, 혹시 아시는 분 있으면 댓글로 알려주시면 대단히 감사하겠습니다.

 

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

 

Posted by R Friend R_Friend

데이터 변환 방법으로서

(1) 표준화

(2) 정규분포화

(3) 범주화

    - 이산형화

    - 이항변수화

(4) 개수 축소

(5) 차원 축소

   - (5-1) 주성분분석

   - (5-2) 요인분석

(6) 시그널 데이터 압축

의 6개 구분 중에서

 

등간척도(혹은 비율척도)로 측정한 두 개 이상의 다수의 변수들에 잠재되어 있는 공통인자를 찾아내는 (5-2) 요인분석(Factor Analysis)에 대해서 알아보겠습니다. 

 

요인분석은 통계학자 Spearman이 학생들의 여러개의 시험 성적(예: Classic, French, English, Math...) 간에 상관관계 행렬을 보다가 "어떻게 하면 연관성있는 변수들을 묶어주는 내재하는 속성을 찾을 수 있을까?"를 가지고 고민하다가 유래되었다고 합니다.

 

요인분석을 왜 하는지에 대해서는 이전에 포스팅한 (5-1) 주성분분석의 초반부를 참고하시기 바랍니다. (바로가기 ☞ 주성분분석)

 

 

 

 

대신에 요인분석과 주성분분석의 공통점과 차이점에 대해서 정리한 다른 분의 블로그(http://ai-times.tistory.com/112) 내용을 소개하겠습니다.

 

요인분석 과 주성분분석의 관계는?

많은 경우 (많은 사람들이) 요인분석과 주성분분석을 혼동한다.
두 용어를 같은 것으로 이해하는 사람들도 많다. ( 요인분석 = 주성분분석 ? ) 그러나 이것은 요인분석이나 주성분 분석을 잘 이해하지 못한 것이다. (참고는 요인분석은 Factor Analysis 이고, 주성분 분석은 Principle Component Analysis 이며 보통 PCA 라고 불린다.)

요인분석과 주성분분석은 물론 깊은 관계가 있다. 그러나 엄밀하게는 같은 것은 아니다.
요인분석을 수행하기 위해서 즉, 몇 개의 요인(잠재된 변수)들을 추출하기 위해서 여러 가지 방법이 사용될 수 있으나 그 중에 가장 많이 사용되는 방법이 <주성분 분석>이다. (그렇다고, 요인분석이 주성분분석의 상위 개념에 있는 것이라고 할 수는 없다. 집합으로 볼 때 포함 관계 아님)

 

* 공통점
[1] 모두 데이터를  축소한다. 
[2] 원래 데이터의 새로운 몇 개의 변수들로 만들어 낸다.


* 차이점
(아래에 정리해보았다. 요인분석은 FA 로, 주성분분석은 PCA 로 표현하였다.)

[1] 생성되는 변수의 수
FA  : 몇 개라고 지정할 수 없다. 데이터의 의미에 따라 다르다. 3개가 될 수도 있고, 또는 4개도 있고, ...
데이터에 서로 성관성을 갖는 변수들의 군집의 개수로 나뉘어질 것이다.
PCA : 주성분이라고 하며, 보통 2개를 찾는다. 제1주성분, 제2주성분 이라고 불린다.

[2] 생성되는 변수의 의미 (이름)
FA : 위에서 학생들의 성적데이터를 가지고 설명했듯이 분석가가 적절한 이름을 붙일 수 있다. 자동적으로 이름을 만들어주지는 않는다.
PCA : 보통 2개의 변수를 채택한다. 첫번째 것은 제1주성분, 제2주성분 이라고 부른다. (원래 데이터의 입력변수가 p라고 하면, ... 제p주성분까지 만들수 있다. 그러나 보통 2개 정도만 사용한다. 이걸로 보통 충분하다.)
요인분석에서는 서로 상관있는 변수들의 이름을 지을 수 있으나 제n주성분의 경우는 그게 좀 힘들다. (의미 중심으로 묶였다기 보다는 분류 결정력이 높은 임의의 변수를 만든 것이기 때문이다.)

[3] 생성된 변수들의 관계
FA : 새 (잠재)변수들은 기본적으로 대등한 관계를 갖는다. 어떤 것이 더 중요하다 라는 의미는 요인분석에서는 없다. 단, 분류/예측에 그 다음 단계로 사용된 다면 그 때 중요성의 의미가 부여될 것이다.  
PCA : 제1주성분이 가장 중요하고, 그 다음 제2주성분이 중요하게 취급된다. 그 다음은 제3주성분 ... 이런 식이다. 즉, 변수들 간의 중요성의 순위가 존재한다.

[4] 분석방법의 의미
FA : 목표 필드를 고려하지 않는다. 그냥 데이터가 주어지면 변수들을 비슷한 성격들로 묶어서 새로운 [잠재]변수들을 만들어 낸다.
PCA : 목표 변수를 고려한다. 목표 변수를 잘 예측/분류하기 위하여 원래 변수들의 선형 결합으로 이루어진 몇 개의 주성분(변수)들을 찾아낸다 

 

* 출처: http://ai-times.tistory.com/112

 

 

요인 추출 방법으로 주성분분석이 활용됩니다. 요인분석을 할 때 초기값 m을 어떻게 잡아주느냐에 따라서 계산 속도가 많이 영향을 받게 됩니다. 이때 보통은 반응변수들이 가지고 있는 변동량의 대부분들을 설명해줄 수 있는 고유값(eigenvalue)와 고객벡터(engenvector)의 수는 몇 개인가를 결정할 수 있는 주성분분석(Principal Component Analysis, PCA)를 활용해서 초기값 m을 잡게 됩니다. (지난 주성분분석 포스팅의 Scree Plot 참조)

 

 

[참고: 용어설명]

- 요인점수 (Factor Score) : 각 관측치의 요인 점수는 요인 점수 계수(Standardized Scoring Coefficients)와 실제 (표준화된) 관측치의 값의 곱으로 구하며, 요인별로 이를 summation하면 요인별 요인점수가 됨.

- 요인패턴 (Factor Loading) :  각 요인이 각 변수에 미치는 효과.  변수와 요인의 상관 행렬

- 공통 분산치 (Communality) : 요인에 의해 설명될 수 있는 변수의 분산량

- 요인회전 (Factor Rotation) : p개의 변수들을 m개의 요인(factor)로 묶어주기 편리하게 혹은 해석하기 쉽게하도록 축을 회전시키는 것. 직교회전에 varimax, transvarimax 등이 있고 비직교회전방법도 있으며, 보통 분산을 최대화하는 직교회전방법 varimax 를 많이 씀.

 

한국신용평가정보에서 나온 '국내 증권회사의 주요 재무제표' (2007.3.31 기준)를 가지고 요인분석을 R로 해보도록 하겠습니다. (지난번 포스팅에서는 똑같은 데이터에 대해 주성분분석을 해보았습니다)

 

이 데이터는 18개 증권사별로 V1.총자본순이익율, V2.자기자본순이익율, V3.자기자본비율, V4.부채비율, V5.자기자본회전율 재무지표 변수로 구성되어 있습니다.

 

예제 데이터('국내 증권회사의 주요 재무제표' (2007.3.31 기준)) 다운로드 ☞

secu_com_finance_2007.csv

 

R로 외부 csv 데이터 불러오기, 표준화 변환, 부채비율 방향 변환, 변수 선택, 상관계수분석, 산포도행렬은 아래와 같습니다. (지난 포스팅 주성분분석 설명과 동일)

 

주성분분석처럼 요인분석도 변수별 scale 영향을 없애기 위해서 표준화(standardization)한 관측값을 사용합니다.

 

> # csv 파일 불러오기 (file importing)
> secu_com_finance_2007 <- read.csv("C:/Users/user/Documents/R/secu_com_finance_2007.csv",
+                                   header = TRUE, 
+                                   stringsAsFactors = FALSE)
> # V1 : 총자본순이익율
> # V2 : 자기자본순이익율
> # V3 : 자기자본비율
> # V4 : 부채비율
> # V5 : 자기자본회전율
> 
> 
> # 표준화 변환 (standardization)
> secu_com_finance_2007 <- transform(secu_com_finance_2007, 
+                                    V1_s = scale(V1), 
+                                    V2_s = scale(V2), 
+                                    V3_s = scale(V3), 
+                                    V4_s = scale(V4),
+                                    V5_s = scale(V5))
> 
> # 부채비율(V4_s)을 방향(max(V4_s)-V4_s) 변환
> secu_com_finance_2007 <- transform(secu_com_finance_2007, 
+                                    V4_s2 = max(V4_s) - V4_s)
> 
> # variable selection
> secu_com_finance_2007_2 <- secu_com_finance_2007[,c("company", "V1_s", "V2_s", "V3_s", "V4_s2", "V5_s")]
> 
> 
> # Correlation analysis
> cor(secu_com_finance_2007_2[,-1])
            V1_s       V2_s       V3_s      V4_s2        V5_s
V1_s  1.00000000  0.6165153  0.3239780  0.3553930  0.01387883
V2_s  0.61651527  1.0000000 -0.5124351 -0.4659444  0.42263462
V3_s  0.32397800 -0.5124351  1.0000000  0.9366296 -0.56340782
V4_s2 0.35539305 -0.4659444  0.9366296  1.0000000 -0.53954570
V5_s  0.01387883  0.4226346 -0.5634078 -0.5395457  1.00000000
> 
> round(cor(secu_com_finance_2007_2[,-1]), digits=3) # 반올림
       V1_s   V2_s   V3_s  V4_s2   V5_s
V1_s  1.000  0.617  0.324  0.355  0.014
V2_s  0.617  1.000 -0.512 -0.466  0.423
V3_s  0.324 -0.512  1.000  0.937 -0.563
V4_s2 0.355 -0.466  0.937  1.000 -0.540
V5_s  0.014  0.423 -0.563 -0.540  1.000
> 
> 
> # Scatter plot matrix
> plot(secu_com_finance_2007_2[,-1])

 

 

 

factanal()함수를 활용해서 R로 요인분석을 해보도록 하겠습니다.

- secu_com_finance_2007_2 : 데이터를 지정해주고 (표준화된 숫자형 변수들)

- factors = 2 : 요인의 개수 지정

- ratation = "varimax" : 회전방법 지정

- scores = "regression" :  요인점수 계산 방법 지정

해주면 되겠습니다.

 

지난번 포스팅의 주성분분석에서는 동일한 데이터로 했을 때 주성분을 3개(Scree plot 보고서 결정)로 해서 분석 결과 해석을 했었는데요,

 

> # Scree Plot
> plot(prcomp(secu_com_finance_2007_2[,c(2:6)]), type="l",
+      sub = "Scree Plot")

 

 

 

 

 

 

요인분석에서 요인 개수를 3개로 집어넣었더닌 변수 5개밖에 안되는데 요인을 3개씩이나 한다고 경고메시지가 뜨네요. ^^;  그래서 요인 2개로 집어넣었습니다.

 

> # 요인분석(maximum likelihood factor analysis)
> # rotation = "varimax"
> secu_factanal <- factanal(secu_com_finance_2007_2[,2:6], 
+                           factors = 2, 
+                           rotation = "varimax", # "varimax", "promax", "none" 
+                           scores="regression") # "regression", "Bartlett"
> 
> print(secu_factanal)

Call:
factanal(x = secu_com_finance_2007_2[, 2:6], factors = 2, scores = "regression",     rotation = "varimax")

Uniquenesses:
 V1_s  V2_s  V3_s V4_s2  V5_s 
0.005 0.026 0.036 0.083 0.660 

Loadings:
      Factor1 Factor2
V1_s   0.252   0.965 
V2_s  -0.588   0.792 
V3_s   0.979         
V4_s2  0.950   0.120 
V5_s  -0.562   0.155 

               Factor1 Factor2
SS loadings      2.586   1.604
Proportion Var   0.517   0.321
Cumulative Var   0.517   0.838

Test of the hypothesis that 2 factors are sufficient.
The chi square statistic is 1.59 on 1 degree of freedom.
The p-value is 0.207 

 

 

 

위에 Loadings 에 보면 Factor2의 V3_s가 숫자가 비어있는데요, 아래처럼 cutoff 를 조정해주면 모두 볼 수 있습니다.

 

> print(secu_factanal$loadings, cutoff=0) # display every loadings

Loadings:
      Factor1 Factor2
V1_s   0.252   0.965 
V2_s  -0.588   0.792 
V3_s   0.979   0.080 
V4_s2  0.950   0.120 
V5_s  -0.562   0.155 

               Factor1 Factor2
SS loadings      2.586   1.604
Proportion Var   0.517   0.321
Cumulative Var   0.517   0.838 

 

요인1(Factor1)은 자기자본비율(V3_s)과 (방햔변환 후의) 부채비율(V4_s2) 이 같이 묶였으며, 요인2(Factor2)는 총자본순이익율(V1_s)과 자기자본순이익율(V2_s)이 함께 묶었습니다.  V5_s가 두 요인 중에서 어디에 속한다고 할지 좀 애매한데요, 요인1하고는 부호가 다르므로 요인2에 묶인다고 하겠습니다.

 

 

 

 

다음으로, 요인분석 Biplot을 그려보도록 하겠습니다.  주성분분석할 때는 prcomp() 함수로 분석하고 biplot()함수로 단 한번에 아주 쉽게 Biplot을 그렸었는데요, 요인분석에서는 biplot을 단번에 그릴 수 있는 함수를 못찾았습니다. (혹시 이 포스팅 보시는 분중에 요인분석 biplot 그릴 수 있는 패키지, 함수 알고 계신분은 댓글로 공유해주시면 감사하겠습니다. 미리 꾸벅~ ☞_☜)

 

> # factor scores plotting
> secu_factanal$scores
          Factor1     Factor2
 [1,] -1.01782141 -0.28535410
 [2,] -0.17230586  0.08808775
 [3,] -0.13294211 -0.71511403
 [4,] -1.03557284  2.77950626
 [5,] -0.34416962 -1.21841127
 [6,] -0.01993668  0.44223954
 [7,] -0.62177426  1.26909067
 [8,]  1.79002399  0.28314793
 [9,]  1.60353334  0.52158445
[10,] -0.55591603 -0.12331881
[11,]  0.55387868 -1.03939155
[12,] -0.93740279 -0.74332879
[13,]  0.45680247  0.06433085
[14,] -1.13490535 -0.63034122
[15,]  1.36209539 -0.98147959
[16,]  1.57141053  0.89812864
[17,] -0.56190944  0.38006982
[18,] -0.80308800 -0.98944656
> 
> plot(secu_factanal$scores, main="Biplot of the first 2 factors")
> 
 

 

 
 
> # 관측치별 이름 매핑(rownames mapping)
> text(secu_factanal$scores[,1], secu_factanal$scores[,2], 
+      labels = secu_com_finance_2007$company, 
+      cex = 0.7, pos = 3, col = "blue")
> 
 

 

 
 
> # factor loadings plotting
> points(secu_factanal$loadings, pch=19, col = "red")
>
 

 

> text(secu_factanal$loadings[,1], secu_factanal$loadings[,2], + labels = rownames(secu_factanal$loadings), + cex = 0.8, pos = 3, col = "red") >
> # plotting lines between (0,0) and (factor loadings by Var.)
> segments(0,0,secu_factanal$loadings[1,1], secu_factanal$loadings[1,2])
> segments(0,0,secu_factanal$loadings[2,1], secu_factanal$loadings[2,2])
> segments(0,0,secu_factanal$loadings[3,1], secu_factanal$loadings[3,2])
> segments(0,0,secu_factanal$loadings[4,1], secu_factanal$loadings[4,2])
> segments(0,0,secu_factanal$loadings[5,1], secu_factanal$loadings[5,2])

 

 

 

 

가로축 Factor1이 '안정성' (자기자본비율, 부채비율) 지표라고 했는데요, Factor1 축의 오른쪽에 위치한 한양증권, 브릿지증권, 부국증권, 유화증권사 등은 안정성이 높은 회사들이라고 해석할 수 있겠습니다.

 

(참고: Factor1 = 0.252*V1_s - 0.588*V2_s + 0.979*V3_s + 0.950*V4_s2 - 0.562*V5_s)

 

 

다음으로, 세로축 Factor2는 '수익성'(총자본순이익율, 자기자본순이익율, 자기자본회전율) 지표라고 했는데요, Factor2 축의 위쪽에 위치한 미래애셋증권, 한화증권, 메리츠증권, 교보증권, 삼성증권 등이 수익성이 양호한 증권사라고 해석할 수 있겠습니다.

 

(참고: Factor2 = 0.965*V1_s + 0.792*V2_s + 0.080*V3_s + 1.20*V4-s2 + 0.155*V5_s)

 

 

이처럼 요인분석을 활용하면 다수의 변수를 안정성과 수익성이라는 두 개의 축으로 차원을 축소해서 포지셔닝맵을 그려서 쉽게 전체 상황을 파악할 수 있겠습니다.

 

다음번 포스팅에서는 기계데이터, 신호데이터에서 나오는 신호를 압축 변환하는 방법에 대해서 알아보겠습니다.

 

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

 

Posted by R Friend R_Friend