블로그 보관함

레이블이 r인 게시물을 표시합니다. 모든 게시물 표시
레이블이 r인 게시물을 표시합니다. 모든 게시물 표시

2013년 6월 7일 금요일

R 프로그래밍: R에서 C 함수 부르기

R 프로그래밍: R에서 C 함수 부르기

이 글에 사용된 코드와 명령은 MacOSX와 Linux에서 확인된 것이다. 특별히 설치되어야 하는 것은 없다. 기본적으로 R과 기본 개발도구가 설치되어 있으면 된다. MacOSX에서는 Xcode를 설치하면 된다. Fortran/C/C++ 무엇으로 짜야 하나 고민이 된다. 다음은 확인된 사실은 아니지만...

  • R에서 vectorize할수 있는 데까지 다 했는데도 속도를 빠르게 하고 싶다면 Fortran/C/C++을 생각해야 한다.
  • Fortran이 C보다 우월한가? 논쟁만 부르고 결론은 나지 않는 질문이다. 그럴 수도 있고 아닐 수도 있다.
  • R에서는 공식적(?)으로 Fortran 77만 지원한다. Fortran 95로 작성한 코드를 R에서 부르는 방법이 없는 건 아니지만... 어떤 사람은 Fortran 77은 절대로 쓰지 말아야한다고도 한다. Fortran 95도 있고 최근 2010년에 표준화된 흔히 부르는 이름으로 "Fortran 2008"도 있다.

.Call

R 패키지를 개발할 때나 아니면 그냥 함수를 짤 때 C/C++/Fortran으로 짠 코드를 불러서 쓸 수 있다. R 함수 중 .C()는 컴파일된 C 코드를 호출할 때, .Fortran()는 컴파일된 Fortran 77 코드를 호출할 때 쓸 수 있다. 이것보다 좀더 유연하고 성능도 나은 것이 .Call()이다. 이것은 C/C++ 코드에 R 객체를 넘겨줄 때 사용된다. .External()도 마찬가지인데 .Call()은 고정된 수의 인자를 넘기고 .External()은 argument list로 넘긴다는 데에 차이가 있다. .Call()을 통해 사용할 C 코드를 작성할 때

  • R.h, Rinternals.h 헤더를 반드시 include한다.
  • R 객체를 받을 때 SEXP, REALSXP, INTSXP, STRSXP 타입을 이용한다.
  • R 객체의 속성은 getAttrib()로 뽑아낼 수 있다.
  • R 객체 안에 들어있는 데이터에 대한 포인터는 REAL(), INTEGER() 등을 이용하여 얻을 수 있다.
  • R 객체를 만들 때는 allocVector(), allocMatrix()를 이용하여 SEXP 타입 객체로 만든다.
  • 이때 항상 PROTECT()UNPROTECT()를 이용한다. 안전한 메모리 확보를 위한 장치이다.

예를 들어 다음과 같은 함수를 작성하고 myfun.c 파일에 저장하였다고 하자.
#include <R.h>
#include <Rinternals.h>

SEXP say(SEXP x) {
    return x
} 

컴파일은 다음과 같이 한다.
$ R CMD SHLIB myfun.c
컴파일하면 만들어지는 myfun.so를 R에서 불러 사용한다. R에서 다음과 같이 로딩한다.
> dyn.load("myfun.so")
로딩이 되었으면 다음처럼 실행할 수 있다.
> .Call("say", 3)
[1] 3


예제: 평균 계산하기

이제 C로 평균을 구하는 함수를 다음과 같이 작성하고 mystat.c 파일로 저장하였다고 하자.
// mystat.c
#include <R.h>
#include <Rinternals.h>

SEXP mean(SEXP x) {
  double *vec = REAL(x);
  int n = length(x);
  SEXP val = PROTECT(allocVector(REALSXP, 1));

  double sum = 0;
  for(int i=0; i < n; i++) sum += vec[i];
  REAL(val)[0] = sum / n;
  UNPROTECT(1);
  return val;
}
컴파일을 하면 mystat.so 파일이 생긴다.
$ R CMD SHLIB mystat.c
R에서 이것을 불러쓰기 위해 다음처럼 wrapper를 작성하자.
mymean <- function(x) {
    if(!is.loaded("mystat")) dyn.load("mystat.so")
    .Call("mean", as.numeric(x))
}
이제 R에서 mymean()이라는 함수를 사용하면 된다.

속도 비교

R에는 물론 평균을 계산해주는 mean() 함수가 있다. 만약 R에서 for으로 평균을 구하면 어떨까? iMac 3.06GHz Intel Core 2 Duo에서 1e7개의 난수를 더한 후 평균을 구하는 데에 다음처럼 오랜 시간이 걸린다.
> n <- 1e7
> x <- runif(n)
> system.time( {
+     total <- 0
+     for(i in 1:n) total <- total + x[i]
+     total / n
+ })
   user  system elapsed 
  8.436   0.076   9.165 
물론 R의 mean() 함수를 이용하면 빠르게 계산된다.
> system.time( mean(x) )
   user  system elapsed 
  0.079   0.001   0.086 
방금 C로 짠 함수의 경우에는:
> system.time( mymean(x) )
   user  system elapsed 
  0.020   0.000   0.023 
R의 내장 mean()보다 빠른 속도를 보여주고 있는데 데이터에 따라서 다를 수 있으니 항상 성능이 좋다고는 못한다. 어쨌든 R에서 for문 돌리는 것에 비하면 빠르다.

행렬

R에서 넘어온 행렬은 컬럼을 쌓은 벡터로 받아서 사용한다. $p\times q$ 행렬 $A$와 $q\times r$ 행렬 $B$의 행렬곱은 다음처럼 계산할 수 있다.
#include <R.h>
#include <Rinternals.h>

/**
 * @SmatA Sp by Sq matrix
 * @SmatB Sq by Sr matrix
 **/
SEXP prod(SEXP SmatA, SEXP SmatB, SEXP Sp, SEXP Sq, SEXP Sr) {

  int p = INTEGER(Sp)[0];
  int q = INTEGER(Sq)[0];
  int r = INTEGER(Sr)[0];

  // R matrix as a vector stacked column by column
  double* a = REAL(SmatA);
  double* b = REAL(SmatB);
  SEXP ans = PROTECT(allocVector(REALSXP, p*r));
  double sum;
  int i, j, k;
  for(i=0; i < p; i++) {
    for(k=0; k < r; k++) {
      sum = 0;
      for(j=0; j < q; j++) {
        sum += a[i + j*p] * b[j + k*q];
      }
      REAL(ans)[i + k*p] = sum;
    }
  }
  UNPROTECT(1);
  return ans;
}




2013년 6월 1일 토요일

R에서 parallel 패키지 이용하여 멀티코어 계산하기

R 3.0 기준으로 얘기하자면 parallel 패키지는 멀티코어 이용도 가능하게 해준다. multicore 패키지는 더이상 사용되지 않는다. 다음처럼 패키지를 로딩한다.
> require(parallel)

Linux, MacOSX에서 간단하게 mclappy와 같은 함수를 이용하는 예를 들면 다음과 같다. iMac 3.06GHz Intel Core 2 Duo에서 돌린 결과이다.
> system.time( out <- sapply(1:100000, function(.) mean(rnorm(100))))
   user  system elapsed
  2.916   0.039   2.959
> system.time( out <- mclapply(mc.cores=2, 1:100000, function(.) mean(rnorm(100))))
   user  system elapsed
  1.487   0.073   1.603
MS Windows에서는 mclapply가 제대로 작동하지 않는다. 대신 clusterApply를 쓸 수 있다. Intel Core i3 3.20GHz에 Windows 7이 설치된 PC에서 돌린 결과이다.
> system.time( out <- sapply(1:100000, function(.) mean(rnorm(100))) )
   user  system elapsed 
   2.66    0.00    2.67 
> cl <- makeCluster(4)
> system.time( out <- clusterApply(cl, 1:100000, function(.) mean(rnorm(100))) )
   user  system elapsed 
  12.85    8.23   21.94 
> stopCluster(cl)
이해가 가지 않는 결과이다. 코어 하나만 쓰는 것보다 코어 4개를 모두 쓰면 더 느려진다. 작업관리자에서 CPU 사용현황을 모니터링하면 멀티코어로 실행되는 것은 분명히 맞다. 이것은 core에 작업을 할당하는 문제 때문이다. 위 실험의 경우 100000개의 작업을 4개의 코어에 분배하여야한다. 그리고 하나의 작업은 매우 연산량이 매우 작다. 그 분배 처리 작업 때문에 하나의 코어에서 10만개 작업을 순차적으로 할당하는 것보다 느리게 되는 것이다. 코어가 4개이므로 10만번 시뮬레이션을 25000번의 시뮬레이션 4개로 나누어 할당하면 속도가 빨라진다.
> simul <- function(n) sapply(1:n, mean(rnorm(100)))
> system.time( out <- sapply(rep(25000, 4), simul) )
   user  system elapsed 
   2.60    0.01    2.68 
> cl <- makeCluster(4)
> system.time( out <-clusterApply(cl, rep(25000, 4), simul) )
   user  system elapsed 
   0.00    0.02    1.04 

2012년 2월 7일 화요일

교호 작용이 있는 모형에서 다중 비교

R에서 multcomp 패키지의 glht를 이용하면 다중비교(multiple comparison)가 가능하다. 더 자세한 설명은 multcomp 매뉴얼을 참조하자.


기본

R에 기본으로 들어있는 warpbreaks 데이터세터를 예제로 한다. 이 데이터는 다음과 같이 연속형 반응변수 breaks와 범주형 설명변수 wool, tension으로 구성되어있다.
> summary(warpbreaks)
     breaks      wool   tension
 Min.   :10.00   A:27   L:18   
 1st Qu.:18.25   B:27   M:18   
 Median :26.00          H:18   
 Mean   :28.15                 
 3rd Qu.:34.00                 
 Max.   :70.00 
다음과 같은 선형모형을 적합하였다고 하자.
> mod <- lm(breaks ~ wool + tension, data=warpbreaks)
그리고 나서 tension의 세 수준 L, M, H 사이의 다중 비교를 할 필요가 있다고 하자. 물론 우선 multcomp패키지를 로딩해야 하고 glht() (General Linear Hypothesis Test)를 이용한다.
> require(multcomp)
> mod.mc <- glht(mod, linfct=mcp(tension="Tukey"))
결과는 다음과 같이 summary() 함수를 이용해 볼 수 있다. plot(mod.mc)으로 그래프를 그려 볼 수도 있다.
> summary(mod.mc)

  Simultaneous Tests for General Linear Hypotheses

Multiple Comparisons of Means: Tukey Contrasts


Fit: lm(formula = breaks ~ wool + tension, data = warpbreaks)

Linear Hypotheses:
           Estimate Std. Error t value Pr(>|t|)   
M - L == 0  -10.000      3.872  -2.582   0.0336 * 
H - L == 0  -14.722      3.872  -3.802   0.0011 **
H - M == 0   -4.722      3.872  -1.219   0.4474   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 
(Adjusted p values reported -- single-step method)

교호작용이 있는 경우 다중비교

교호작용(interaction)이 있는 모형이 필요하다고 하자. 예를 들면 다음과 같다.
> mod1 <- lm(breaks ~ wool * tension, data=warpbreaks)
이 경우에 모든 가능한 조합은 6가지가 있다. 다음과 같이 6개의 셀을 평균을 비교하는 것이다.
> xtabs(~wool+tension, warpbreaks)
    tension
wool L M H
   A 9 9 9
   B 9 9 9
이때 우리가 다중비교를 원하는 대상이 wool의 동일한 수준 내에서 tension을 비교하는 것이라고 하자. 우선 모형을 만들때 다음과 같이 interaction() 함수를 이용해 새로운 변수를 만들고 절편은 없이 하는 것이 생각하기 편리하다.
tw <- with(warpbreaks, interaction(tension, wool))
mod1 <- lm(breaks ~ tw-1, warpbreaks)
이렇게 했을 때 회귀계수는 각 셀의 평균이 된다. 다음과 같다.
> coef(mod1)
   twL.A    twM.A    twH.A    twL.B    twM.B    twH.B 
44.55556 24.00000 24.55556 28.22222 28.77778 18.77778 
이것과 순서를 맞추어 다음처럼 contrast matrix를 만들어주자.
contr <- rbind("A:M-L" = c(-1,1,0,0,0,0),
               "A:H-L" = c(-1,0,1,0,0,0),
               "A:H-M" = c(0,-1,1,0,0,0),
               "B:M-L" = c(0,0,0,-1,1,0),
               "B:H-L" = c(0,0,0,-1,0,1),
               "B:H-M" = c(0,0,0,0,-1,1))
첫행을 보면 "A:M-L"이라고 이름을 붙였는데 wool=A에서 tension=Mtension=L을 비교하겠다는 것이다. 위에 적합한 모형의 회귀계수 중 두번째 것에서 첫번째 것을 빼준 것이 이에 해당한다. 따라서 c(-1,1,0,0,0,0)이 된다. 이제 다음과 같은 결과를 얻을 수 있다.
> summary(glht(mod1, linfct=contr))

  Simultaneous Tests for General Linear Hypotheses

Fit: lm(formula = breaks ~ tw - 1, data = warpbreaks)

Linear Hypotheses:
           Estimate Std. Error t value Pr(>|t|)   
A:M-L == 0 -20.5556     5.1573  -3.986  0.00133 **
A:H-L == 0 -20.0000     5.1573  -3.878  0.00185 **
A:H-M == 0   0.5556     5.1573   0.108  0.99996   
B:M-L == 0   0.5556     5.1573   0.108  0.99996   
B:H-L == 0  -9.4444     5.1573  -1.831  0.30801   
B:H-M == 0 -10.0000     5.1573  -1.939  0.25536   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 
(Adjusted p values reported -- single-step method)









2011년 12월 16일 금요일

R 속도비교: loop, apply, vectorization

이 실험은 R version 2.14.0 (2011-10-31)에서 이루어진 것이다.
R version 2.14.0 (2011-10-31)
Copyright (C) 2011 The R Foundation for Statistical Computing
ISBN 3-900051-07-0
Platform: x86_64-apple-darwin9.8.0/x86_64 (64-bit)
컴퓨터는 iMac 3.06GHz Intel Core 2 Duo, 메모리는 12GB 1067 MHz DDR3. 운영체제는 Mac OS X Lion 10.7.2이다.

loop를 돌릴 때

우선, 결과값을 저장할 벡터를 미리 크기를 정해서 만들어 놓는 것과 아닌 것에는 엄청난 차이가 난다.
> x <- 1:(10^5)
> y <- c()
> system.time(for(i in 1:length(x)) y[i] <- x[i]^2)
   user  system elapsed 
 13.054  33.554  65.792
> y <- numeric(length(x))
> system.time(for(i in 1:length(x)) y[i] <- x[i]^2)
   user  system elapsed 
  0.312   0.008   0.455 
loop를 돌리며 결과를 벡터에 저장하려고 할 때는 반드시 해당 벡터를 먼저 초기화해야 한다.


loop, apply, vectorization

이제 계산할 벡터의 크기를 10배 늘려서 loop, apply, vectorization을 비교해 보자. apply 함수를 사용할 때는 결과값이 저장될 벡터를 미리 초기화할 필요가 없다. vectorization의 속도가 엄청나게 빠른 것을 확인할 수 있다.
> x <- 1:(10^6)
> y <- numeric(length(x))
> system.time(for(i in 1:length(x)) y[i] <- x[i]^2)
   user  system elapsed 
  3.019   0.045   4.533 
> rm(y)
> system.time(y <- sapply(x, function(i) i^2))
   user  system elapsed 
  3.248   0.071   4.605 
> rm(y)
> system.time(y <- x^2)
   user  system elapsed 
  0.006   0.000   0.006 
흔히 for loop보다 apply()가 "훨씬" 빠르다고 이야기하는 경우가 있는데 그건 사실이 아니다. 혹시 S-Plus에서는 그렇다는 말이 있는데 사실인지 모르겠다. R에서는 루프도 apply만큼 빠르다. 단 결과를 저장할 벡터를 초기화하는 것을 잊어서는 안 된다. 둘의 속도 차이는 경우에 따라 없을 수도 있고 루프가 빠를 수도 있고 apply가 빠를 수도 있다. 물론, apply()를 쓰는 것이 코드가 더 깔끔하다.


Vectorization의 엄청난 속도

경우에 따라 다르겠지만 원소들의 제곱으로 이루어진 벡터를 만드는 바로 이 문제의 경우, 루프나 apply와 비교했을 때 vectoriation이 500배 이상 빠른 속도를 보이는 것을 확인할 수 있다. 벡터의 크기를 늘려가며 vectorization을 해 보았다. 1천만(10^8)개까지는 큰 문제가 없이 산술적으로 속도가 증가한다. 1억개(10^9)부터는 연산에 걸린 시간은 정상적으로 증가했지만 elapsed가 갑자기 시간이 오래 걸렸는데 메모리 부족 문제인 듯하다.
> x <- 1:(10^6)
> system.time(y <- x^2)
   user  system elapsed 
  0.005   0.000   0.005 
> x <- 1:(10^7)
> system.time(y <- x^2)
   user  system elapsed 
  0.059   0.001   0.065 
> x <- 1:(10^8)
> system.time(y <- x^2)
   user  system elapsed 
  0.575   0.509   1.246 
> x <- 1:(10^9)
> system.time(y <- x^2)
   user  system elapsed 
  6.266   8.735 155.847 
> x <- 1:(10^10)
이하에 에러1:(10^10) : 결과의 벡터가 너무 깁니다
마지막 에러가 난 것은 벡터의 크기가 너무 크기 때문이다. R에서 허용하는 벡터의 최대 크기는 2^31 - 1, 약 2*10^9이다.



matrix, array의 경우: rowSums(), colSums() 사용하기

vectoriation을 모든 경우에 할 수 있는 것은 아니다. 행렬(matrix)이나 배열(array)에서 행이나 열의 합을 구하는 경우에 루프나 apply보다는 rowSums(), colSums()를 쓰는 것이 빠르다.

> x <- matrix(runif(10^8), ncol=10^4)
> str(x)
 num [1:10000, 1:10000] 0.3492 0.0621 0.2593 0.8765 0.5281 ...
> y <- numeric(10000)
> system.time(for(i in 1:10000) y[i] <- sum(x[i,]))
   user  system elapsed 
  6.836   0.755   9.653 
> rm(y)
> system.time(y <- apply(x, 1, sum))
   user  system elapsed 
  6.597   1.512  10.263 
> rm(y)
> system.time(y <- rowSums(x))
   user  system elapsed 
  0.263   0.003   0.326 

list의 경우: lapply()

list는 vector가 아니라 list이니까 Vectorizatioin하다는 것은 이름 그대로 말이 안 되지 않는가? 루프나 apply를 써야 하는데, list를 대상으로 할 때는 루프보다 lapply()의 코드가 효율적이라고 알려져 있고 실제로도 조금 빠른 것 같다.
> x <- list()
> for(i in 1:10000) x[[i]] <- runif(10000)
> y <- numeric(10000)
> system.time(for(i in 1:10000) y[i] <- sum(x[[i]]))
   user  system elapsed 
  0.257   0.004   0.390 
> system.time(y <- lapply(x, sum))
   user  system elapsed 
  0.228   0.005   0.281 

약간 다르게:
> x <- list()
> for(i in 1:100000) x[[i]] <- runif(10)
> y <- numeric(100000)
> system.time(for(i in 1:100000) y[i] <- sum(x[[i]]))
   user  system elapsed 
  0.393   0.007   0.543 
> system.time(y <- lapply(x, sum))
   user  system elapsed 
  0.147   0.003   0.229 

누적 계산이 필요한 경우

> x <- runif(1000000)
> system.time(y <- cumsum(x))
   user  system elapsed 
  0.032   0.006   0.102 
> system.time({y[1] <- x[1]; for(i in 2:length(x)) y[i] <- y[i-1] + x[i]})
   user  system elapsed 
  4.140   0.029   4.330 







2011년 7월 13일 수요일

R에서 생존분석: Kaplan-Meier 방법

R에서 생존분석: Kaplan-Meier 방법

준비

생존 분석 패키지를 다음 명령으로 읽어들인다.
> require(survival)
급성 골수성 백혈병(Acute Myelogenous Leukemia) 데이터가 예제로 들어있다. 작은 데이터이다. 내용을 보면:
> aml
   time status             x
1     9      1    Maintained
2    13      1    Maintained
3    13      0    Maintained
4    18      1    Maintained
5    23      1    Maintained
6    28      0    Maintained
7    31      1    Maintained
8    34      1    Maintained
9    45      0    Maintained
10   48      1    Maintained
11  161      0    Maintained
12    5      1 Nonmaintained
13    5      1 Nonmaintained
14    8      1 Nonmaintained
15    8      1 Nonmaintained
16   12      1 Nonmaintained
17   16      0 Nonmaintained
18   23      1 Nonmaintained
19   27      1 Nonmaintained
20   30      1 Nonmaintained
21   33      1 Nonmaintained
22   43      1 Nonmaintained
23   45      1 Nonmaintained
이 데이터는 단순한 데이터프레임이다. 확인해 보자.
> class(aml)
[1] "data.frame"
> str(aml)
'data.frame': 23 obs. of  3 variables:
 $ time  : num  9 13 13 18 23 28 31 34 45 48 ...
 $ status: num  1 1 0 1 1 0 1 1 0 1 ...
 $ x     : Factor w/ 2 levels "Maintained","Nonmaintained": 1 1 1 1 1 1 1 1 1 1 ...
즉, 단순한 테이블 데이터라는 뜻의다. 아직 특별히 생존 분석을 위한 가공이 이루어지 않은 것이다. 위와 같은 형태로만 데이터가 입력되어 있으면 된다.

Survival Object

생존 분석을 하기 위해 데이터를 Survival Object로 만들어 보자. 이 객체는 survival 패키지에서 제공하는 것이다. Survival Object는 Surv()를 이용하여 만든다. 이 함수는 기본적으로 Surv(time, event)로 사용할 수 있다. 두 변수가 필요하다. time은 생존시간이고 event는 상태(status)를 나타내는 변수이다. 상태는 기본적으로
  • 0=right censored
  • 1=event at time
  • 2=left censored
  • 3=interval censored
로 코딩한다. 지금 주어진 aml데이터는 status 변수에 0/1밖에 없다. 다음과 같이 Survival Object를 만들면 status 값이 0인 데이터 즉 right censored 데이터인 경우에 시간 뒤에 +가 붙어 표시되는 것을 확인할 수 있다.
> (aml.Surv <- with(aml, Surv(time, status)))
 [1]   9   13   13+  18   23   28+  31   34   45+  48  161+   5    5    8    8   12   16+  23   27   30   33   43 
[23]  45 

Kaplan-Meier method

Kaplan-Meier 방법으로 중도 절단이 있는 자료(censored data)의 생존 함수를 추정하는 것은 survfit()로 할 수 있다. 앞서 만든 Survival Object를 이용해서
> aml.survfit <- survfit(aml.Surv ~ aml$x)
라고 할 수도 있지만,
> aml.survfit <- survfit(Surv(time, status) ~ x, aml)
라고 하는 것이 보기 좋다. 분석 자료 개요는 다음과 같다.
> aml.survfit
Call: survfit(formula = Surv(time, status) ~ x, data = aml)

                records n.max n.start events median 0.95LCL 0.95UCL
x=Maintained         11    11      11      7     31      18      NA
x=Nonmaintained      12    12      12     11     23       8      NA
분석 결과를 보려면 summary() 함수를 이용하면 된다. 결과는 다음과 같다.
> summary(aml.survfit)
Call: survfit(formula = Surv(time, status) ~ x, data = aml)

                x=Maintained 
 time n.risk n.event survival std.err lower 95% CI upper 95% CI
    9     11       1    0.909  0.0867       0.7541        1.000
   13     10       1    0.818  0.1163       0.6192        1.000
   18      8       1    0.716  0.1397       0.4884        1.000
   23      7       1    0.614  0.1526       0.3769        0.999
   31      5       1    0.491  0.1642       0.2549        0.946
   34      4       1    0.368  0.1627       0.1549        0.875
   48      2       1    0.184  0.1535       0.0359        0.944

                x=Nonmaintained 
 time n.risk n.event survival std.err lower 95% CI upper 95% CI
    5     12       2   0.8333  0.1076       0.6470        1.000
    8     10       2   0.6667  0.1361       0.4468        0.995
   12      8       1   0.5833  0.1423       0.3616        0.941
   23      6       1   0.4861  0.1481       0.2675        0.883
   27      5       1   0.3889  0.1470       0.1854        0.816
   30      4       1   0.2917  0.1387       0.1148        0.741
   33      3       1   0.1944  0.1219       0.0569        0.664
   43      2       1   0.0972  0.0919       0.0153        0.620
   45      1       1   0.0000     NaN           NA           NA

생존 곡선 그리기

생존 곡선은 plot() 명령으로 간단히 그릴 수 있다.
> plot(aml.survfit)
위 데이터에서 이미 보았듯이 aml$x를 보면
> levels(aml$x)
[1] "Maintained"    "Nonmaintained"
두 개의 집단으로 이루어져 있다. 두 생존곡선을 다른 형태의 선으로 그려서 구별하고 싶다면
> plot(aml.survfit, lty=1:2)
라고 lty (line type) 옵션을 주는 것만으로도 충분하다. 범례도 달고 싶다면 일반적으로는 다음과 같이 하면 된다.
K <- length(aml.survfit$strata)
LEGEND <- attr(aml.survfit$strata, "names")
plot(aml.survfit, lty=1:K)
legend("topright", LEGEND, lty=1:K)
물론, 여기서 KLEGEND를 직접 타이핑해도 된다. 직접 써줄 때는 범례 이름을 바꾸어 쓰는 일이 없도록 주의해야 한다.
plot(aml.survfit, lty=1:2)
legend("topright", c("Maintained", "Nonmaintained"), lty=1:2)

2011년 7월 6일 수요일

R 객체 지향 프로그래밍: S4 기초

R OOP S4 기초

먼저 S3에 대해서는 여기 참조. S4 클래스로 프로그래밍을 하기 위해서 기본적으로 다음 함수들을 시용해야 한다.

  • setClass()
  • new()
  • setGeneric()
  • setMethod()

클래스

새로운 클래스는 다음처럼 setClass() 함수를 이용하여 정의한다.

setClass("circle", 
  representation(x="numeric", y="numeric", r="numeric"))
이제 new()를 이용하여 인스턴스를 만들 수 있다. 객체의 속성을 S4에서늘 슬롯(slot)이라고 한다. 슬롯은 object@slot 형태로 접근할 수 있다.
> a <- new("circle", x=5, y=10, r=4)
> str(a)
Formal class 'circle' [package ".GlobalEnv"] with 3 slots
  ..@ x: num 5
  ..@ y: num 10
  ..@ r: num 4
> a@x
[1] 5
> a@y
[1] 10
> a@r
[1] 4
한 객체가 특정 클래스에 속하는지 체크할 때는 is()를 이용한다.
> is(a, "circle")
[1] TRUE

메소드

메소드는 setMethod()를 이용하여 정의한다. 기존의 정의된 generic function이 없다면 우선 generic function부터 만들어야 하는데 이때에는 setGeneric()을 이용한다. 예를 들면 다음처럼 한다.

setGeneric("area", function(object) standardGeneric("area"))

setMethod("area", "circle",
          function(object) pi * object@r^2
          )
이제 메소드를 사용해 보면
> area(a)
[1] 50.26548
> area(new("circle", x=3, y=4, r=3))
[1] 28.27433

한 generic function에 어떤 method들이 있는지 알고 싶을 때는 showMethods()를 이용한다.

> showMethods("area")
Function: area (package .GlobalEnv)
object="circle"

참조

R에서 객체 지향 프로그래밍: S3 기초

R S3 OOP 기초

R에는 3가지 유형의 객체 지향 시스템이 있다. S3, S4, R5 이렇게 세 가지이다. S3는 R의 핵심적인 패키지들에서 광범위하게 사용되고 있으므로 반드시 알아두어야 한다. S4는 S3와 기저에 깔린 아이디어는 유사하지만 구현 방식이 더 엄격하고 요즘 대세인 OO 시스템(Java 등의)에 좀더 비슷하다. R5는 R 2.12에서야 등장한 최신 시스템으로 요즘의 OO 시스템에 더 비슷하다.

S3는 1990년경에 S언어에 도입된 것으로 'generic-function' 방식으로 불리는 스타일의 객체 지향 프로그래밍 기법이다. 'message-passing' 방식으로 불리는 OO를 구현한 Java, C++ 등의 객체 지향과는 다르다. 'message-passing' 방식인 Java, C++에서는 객체가 어떤 function을 실행할지 결정하는 데에 비해 S3에서는 generic function이라는 특별한 함수가 어떤 method를 호출할지를 결정한다.

클래스

S3에서는 클래스라고 해봐야 별 것 없다. 단지 class라는 속성(attribute)의 값에 따라 클래스가 결정될 뿐이다. 딱히 instance/class 구별이 큰 의미도 없다. 객체를 만드는 방법은 간단하다. class() 함수를 이용하여 class 속성을 지정할 수 있다. 다음처럼 사각형과 원을 만들어 보자.

a <- c(xleft=10, ybottom=7, xright=15, ytop=9)
class(a) <- "rectangle"

b <- list(center=c(10, 5), radius=5)
class(b) <- "circle"

어떤 객체가 어떤 내용을 담고 있는지 들여다 보는 데에 여러가지 함수들이 사용될 수 있다. 위처럼 사각형 a와 원 b를 만들었다면 class() 함수로 클래스를 확인해 볼 수 있다.

> class(a)
[1] "rectangle"
> class(b)
[1] "circle"
객체에 대해 attributes(), str(), structure() 명령을 사용해서 어떤 내용이 출력되는지 살펴보라. 그러니까
> str(a)
Class 'rectangle'  Named num [1:4] 10 7 15 9
  ..- attr(*, "names")= chr [1:4] "xleft" "ybottom" "xright" "ytop"
와 같은 식으로 명령을 내려 보라. attr() 함수를 이용하면 원하는 속성을 지정해서 살펴볼 수 있다.
> attr(a, "class")
[1] "rectangle"
아까 사용한 class() 함수는 단지 "class"라는 이름의 속성값에 접근하게 해주는 함수일 뿐이다.

메소드

넓이를 계산해 주는 메소드 area()를 만들어 보자. 사각형과 원은 넓이를 계산하는 방법이 다르다. 그러니까, 클래스마다 다른 메소드가 실행되어야 한다. 그런데 다음처럼 실행되게 하려고 한다.

> area(a)
[1] 10
> area(b)
[1] 78.53982
이것을 S3에서 구현하는 방법을 method dispatch라고 하는데 여기서 area() 함수를 generic function이라고 한다. 우선 필요하는 것은 generic function을 정의하는 것이다. 방법은 간단하다. 다음처럼 한다.
area <- function(x, ...) {
    UseMethod("area", x)
}
이것으로 area()라는 generic function이 만들어졌다. 이것은 인자로 주어진 객체의 class를 체크해서 적절한 메소드를 dispatch해 준다. 각 클래스를 위한 method는 generic function의 이름 다음에 점을 찍고 클래스 이름을 붙여서 만든다. 말로 하는 것보다 한번 보는 것이 쉽다.
area.rectangle <- function(x, ...) {
  as.numeric((x["xright"] - x["xleft"]) * (x["ytop"] - x["ybottom"]))
}

area.circle <- function(x, ...) {
  pi * x$radius^2
}
이것으로 끝이다. 이제 generic function area()rectangle 객체를 받으면 area.rectangle() 메소드가 실행되고 circle 객체를 받으면 area.circle() 메소드가 실행된다.

한 generic function에 어떤 메소드들이 있는지 알고 싶다면 methods() 함수를 이용한다.

> methods("area")
[1] area.circle area.rectangle
한번 methods(plot) 명령을 내려보라. 여러가지 메소드들을 볼 수 있을 것이다. 만약 우리가 MyClass라는 새로운 클래스를 만들었고 plot()로 그것에 알맞은 그래프가 그려지게 하고 싶다면 plot.MyClass()라는 이름의 함수를 새로 정의하기만 하면 된다. plot()가 이미 generic function이므로 알아서 해준다.

S3 generic function에서 주어진 클래스에 맞는 method를 얻는 것은 getS3method()로 가능하다. 즉 다음처럼 작동한다.

> getS3method("area", "circle")
function(x, ...) {
  pi * x$radius^2
}

생성자

S3에서는 생성자(constructor)를 형식적으로 지원하지 않는다. 프로그램 짜는 사람이 알아서 해야한다. 어쨌든 언제나 생성자는 만드는 습관을 들이는 것이 좋다. 생성자는 데이터를 받아서 대개는 리스트 형태로 만들고 class 속성값을 붙여주는 형태가 된다. 예를 들면:
new_circle <- function(x, y, r) {
  circle <- list(center=c(x,y), radius=r)
  class(circle) <- "circle"
  circle
}

참고

2011년 7월 1일 금요일

시계열자료: 분해법에서 말하는 성분들

시계열 예측모형의 종류에는 다음과 같은 것들이 있다.
  •  추세분석(trend analysis): 관측값을 시간의 함수로 표현하는 전통적인 분석 방법.
  •  평활법(smoothing method): 최근의 자료에 큰 가중값, 과거로 갈수록 작은 가중값을 주는 방법. 1970년대 이전에 많이 사용됨.
  •  분해법(decomposition method): 가장 오래된 전통적인 방법으로 20세기 초 경제학자들의 경기변동 예측 시도에서 비롯. 시계열을 구성하는 성분들이 결정적이고 서로 독립이라는 가정에 기반.
  • ARIMA: 현 시점의 관측값을 과거 관측값과 오차의 선형결합으로 표현. 1970년대에 Box-Jenkins 모형이 제시되면서 널리 사용고 있음.
전통적인 시계열 분석 방법인 분해법에서는  시계열이 다음 성분들로 이루어졌다고 본다. 정말로 시계열이 이렇게 몇가지 성분들로 분해될 수 있는지는 논란이 있지만 나름대로 시계열의 특성을 이해하는 데에 도움이 되는 아이디어이다.
  • 불규칙 성분(irregular component)
  • 체계적 성분(systematic component)
    • 추세 성분(trend component)
    • 계절 성분(seasonal component)
    • 순환 성분(cyclical component)

불규칙 성분

불규칙 성분으로만 이루어진 시계열을 흉내내기 위해 \[ Z_t = 1000 + \epsilon_t,\quad \epsilon_t \sim N(0, 10^2) \] 와 같은 모형을 생각하면 R에서
Z <- rnorm(120, mean=1000, sd=10)
Zt <- ts(Z, start=2001, frequency=12)
plot(Zt)
와 같이 생성할 수 있다.

추세 성분

추세 성분과 불규칙 성분으로 이루어진 시계열을 흉내내기 위해 \[ Z_t = 5 + 0.1t + \epsilon_t, \quad \epsilon_t \sim N(0,1) \] 와 같은 모형을 R에서
t <- 1:120
e <- rnorm(120)
Z <- 5 + 0.1*t + e
Zt <- ts(Z, start=2001, frequency=12)
plot(Zt)
와 같이 생성해 보면…

계절 성분

계절 성분과 불규칙 성분으로 이루어진 시계열을 흉내내기 위해 \[ Z_t = 5 + 2\sin(2\pi t /12) + \epsilon_t, \quad \epsilon_t \sim (0,1) \] 와 같은 모형을 R에서
N <- 120
t <- 1:N
e <- rnorm(N)
Z <- 5 + 2*cos(2*pi*t/12) + e
Zt <- ts(Z, start=2001, frequency=12)
plot(Zt)
와 같이 생성해 보면…

추세 성분과 계절 성분

추세 성분과 계절 성분을 가진 시계열을 흉내내기 위해 \[ Z_t = 5 + 0.1t + 2\cos(2\pi t /12) + \epsilon_t, \quad \epsilon_t \sim (0,1) \] 와 같은 모형을 R에서
N <- 120
t <- 1:N
e <- rnorm(N)
Z <- 5 + 0.1*t + 2*cos(2*pi*t/12) + e
Zt <- ts(Z, start=2001, frequency=12)
plot(Zt)
와 같이 생성해 보면…
분해는 stl() 함수를 이용하여 할 수 있다. 바로 위에서 생성한 데이터를 분해해 보면
> plot(stl(Zt, "periodic"))


참고

  • 조신섭, 손영숙 (2010), SAS/ETS를 이용한 시계열분석, 3판 2쇄, 율곡출판사, 제1장
  • Crawley (2007), The R Book, Wiley, Chapter 22.

2011년 4월 30일 토요일

Deducer: R GUI

Deducer는 R에서 메뉴로 데이터를 편집, 조작하고, 분석하고, 그래프를 그릴 수 있게 해준다. 상당히 쓸 만한 듯하다. 데이터는 분석은 기본적인 것과 함께 Linear Model, Logistic Model, Generalized Linear Model까지만 지원한다.

http://www.deducer.org

RStudio와는 성격이 다르다. RStudio는 R 개발 환경이다. 메뉴로 데이터 분석을 하거나 그래프를 그릴 수 있게 해주는 것은 아니다.

http://www.rstudio.org/

R GUI에 관한 전반적인 소개는 다음 페이지에서

http://www.sciviews.org/_rgui/