5.4 R 반복 함수

5.4.1 apply()

apply(X, MARGIN, FUN, …)는 X를 입력받아 행 또는 열 방향으로 함수를 적용하여 결과값을 반환합니다. MARGIN 인수가 1이면 행 방향으로, 2이면 열 방향으로 연산이 됩니다. apply 함수에 입력하는 데이터(X)는 배열, 매트릭스만 가능하고, 만일 데이터프레임이 모두 같은 데이터 타입이면 가능합니다. 반환되는 값은 벡터나 행렬입니다.

(x <- matrix(1:12, c(3,4)))
#>      [,1] [,2] [,3] [,4]
#> [1,]    1    4    7   10
#> [2,]    2    5    8   11
#> [3,]    3    6    9   12
apply(x, 1, mean)   # 행 방향으로 평균
#> [1] 5.5 6.5 7.5
apply(x, 2, mean, na.rm = TRUE)   # 열 방향으로 평균, mean함수 옵션 추가
#> [1]  2  5  8 11

벡터는 apply에 입력데이터로 사용할 수 없습니다(에러 발생). 만일 벡터를 사용하고자 한다면 배열로 변환하여 사용하여야 합니다.

x <- 1:12
dim(x) <- c(1, length(x)); x     # 벡터를 배열로 변환
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
#> [1,]    1    2    3    4    5    6    7    8    9    10    11    12
apply(x, 1, mean)
#> [1] 6.5

함수는 사용자 정의 함수를 만들어서 사용할 수 있습니다.

(x <- matrix(1:12, c(3,4)))
#>      [,1] [,2] [,3] [,4]
#> [1,]    1    4    7   10
#> [2,]    2    5    8   11
#> [3,]    3    6    9   12
apply(x, 2, function(x) {x*2})
#>      [,1] [,2] [,3] [,4]
#> [1,]    2    8   14   20
#> [2,]    4   10   16   22
#> [3,]    6   12   18   24

# 행방향으로 하면 행과 열이 바뀝니다.
apply(x, 1, function(x) {x})
#>      [,1] [,2] [,3]
#> [1,]    1    2    3
#> [2,]    4    5    6
#> [3,]    7    8    9
#> [4,]   10   11   12
apply(x, 1, function(x) {x*2})
#>      [,1] [,2] [,3]
#> [1,]    2    4    6
#> [2,]    8   10   12
#> [3,]   14   16   18
#> [4,]   20   22   24

데이터프레임도 데이터가 모두 같은 타입이라면 apply를 적용할 수 있습니다. R의 기본 데이터셋인 iris에서 Factor 타입인 Species를 제거한 후 apply에 사용하도록 하겠습니다.

str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
x <- iris[, -5]; str(x)
#> 'data.frame':    150 obs. of  4 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
apply(x, 2, mean, na.rm = TRUE)
#> Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
#>         5.84         3.06         3.76         1.20
apply(x, 2, function(x) {median(x*2-1, na.rm = TRUE)})
#> Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
#>         10.6          5.0          7.7          1.6

5.4.2 lapply()

lapply(X, FUN, …)는 X를 입력받아 함수를 적용하여 결과값을 반환합니다. lapply 함수에 입력하는 데이터(X)는 벡터, 리스트 등도 가능하고, 반환되는 값은 리스트입니다.

apply는 X의 행이나 열 방향의 데이터가 한꺼번에 함수로 전달되는 반면에, lapply는 X의 데이터 요소가 하나 하나 함수로 전달됩니다. 행렬이나 배열의 요소는 기본적으로 벡터의 요소와 같은 방식이기 때문에 값이 하나 하나 전달됩니다.

# 아래와 같이 코드를 실행하면 x의 평균이 반환되지 않습니다.
# x값이 하나 하나 mean 함수에 전달되어 각각 계산되기 때문입니다.
x <- matrix(1:6, c(2,3))
lapply(x, mean, na.rm = TRUE)
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 2
#> 
#> [[3]]
#> [1] 3
#> 
#> [[4]]
#> [1] 4
#> 
#> [[5]]
#> [1] 5
#> 
#> [[6]]
#> [1] 6

lapply는 리스트로 반환이 됩니다. 이를 벡터로 변환하고자 한다면 unlist 함수를 적용합니다.

x <- 1:3
lapply(x, function(x) {x*2+1})          # 리스트 형태로 반환
#> [[1]]
#> [1] 3
#> 
#> [[2]]
#> [1] 5
#> 
#> [[3]]
#> [1] 7
unlist(lapply(x, function(x) {x*2+1}))  # 리스트를 벡터로 변환하여 반환
#> [1] 3 5 7

데이터프레임도 lapply 함수에 입력데이터로 사용 가능합니다. 데이터프레임의 각 요소 즉 각 변수별로 한꺼번에 함수에 전달됩니다.

str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
lapply(iris, mean, na.rm = TRUE)
#> Warning in mean.default(X[[i]], ...): argument is not numeric or logical:
#> returning NA
#> $Sepal.Length
#> [1] 5.84
#> 
#> $Sepal.Width
#> [1] 3.06
#> 
#> $Petal.Length
#> [1] 3.76
#> 
#> $Petal.Width
#> [1] 1.2
#> 
#> $Species
#> [1] NA

리스트가 입력되면 리스트의 각 요소가 한꺼번에 함수에 전달됩니다. 예를 들어 mean함수를 사용하면 리스트 각 요소별 평균값을 반환합니다.

x <- list(a = 1:10, 
          beta = exp(-3:3), 
          logic = c(TRUE,FALSE,FALSE,TRUE))
x
#> $a
#>  [1]  1  2  3  4  5  6  7  8  9 10
#> 
#> $beta
#> [1]  0.0498  0.1353  0.3679  1.0000  2.7183  7.3891 20.0855
#> 
#> $logic
#> [1]  TRUE FALSE FALSE  TRUE
lapply(x, mean)
#> $a
#> [1] 5.5
#> 
#> $beta
#> [1] 4.54
#> 
#> $logic
#> [1] 0.5
lapply(x, quantile, probs = (1:3)/4)
#> $a
#>  25%  50%  75% 
#> 3.25 5.50 7.75 
#> 
#> $beta
#>   25%   50%   75% 
#> 0.252 1.000 5.054 
#> 
#> $logic
#> 25% 50% 75% 
#> 0.0 0.5 1.0

5.4.3 sapply()

sapply(X, FUN, …, simplify = TRUE, USE.NAMES = TRUE)는 단순화된(simplify) lapply 함수라 할 수 있습니다. lapply는 리스트 형태로 반환되기 때문에 사용하기 불편한 점이 있는데 sapply는 기본적으로 벡터나 행렬 형태로 반환합니다. 만일 옵션 simplify = FALSE이면 lapply와 동일하게 리스트 형태로 반환됩니다.

x <- 1:3
sapply(x, function(x) {x*2+1})    
#> [1] 3 5 7
str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
sapply(iris, mean, na.rm = TRUE)
#> Warning in mean.default(X[[i]], ...): argument is not numeric or logical:
#> returning NA
#> Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
#>         5.84         3.06         3.76         1.20           NA
x <- list(a = 1:10, 
          beta = exp(-3:3), 
          logic = c(TRUE,FALSE,FALSE,TRUE))
x
#> $a
#>  [1]  1  2  3  4  5  6  7  8  9 10
#> 
#> $beta
#> [1]  0.0498  0.1353  0.3679  1.0000  2.7183  7.3891 20.0855
#> 
#> $logic
#> [1]  TRUE FALSE FALSE  TRUE
sapply(x, mean)
#>     a  beta logic 
#>  5.50  4.54  0.50
sapply(x, quantile, probs = (1:3)/4)
#>        a  beta logic
#> 25% 3.25 0.252   0.0
#> 50% 5.50 1.000   0.5
#> 75% 7.75 5.054   1.0

5.4.4 vapply()

vapply(X, FUN, FUN.VALUE, …, USE.NAMES = TRUE)는 sapply와 유사합니다. 차이점은 반환되는 결과의 양식을 지정할 수 있습니다.

str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
# sapply로 fivenum 출력
sapply(iris[, 1:4], fivenum, na.rm = TRUE)
#>      Sepal.Length Sepal.Width Petal.Length Petal.Width
#> [1,]          4.3         2.0         1.00         0.1
#> [2,]          5.1         2.8         1.60         0.3
#> [3,]          5.8         3.0         4.35         1.3
#> [4,]          6.4         3.3         5.10         1.8
#> [5,]          7.9         4.4         6.90         2.5

# vapply로 fivenum 출력, 출력양식 지정
vapply(iris[, 1:4], fivenum, 
       c("최소값" = 0, "1사분위수" = 0, "중위수" = 0, 
         "3사분위수" = 0, "최대값" = 0),
       na.rm = TRUE)
#>           Sepal.Length Sepal.Width Petal.Length Petal.Width
#> 최소값             4.3         2.0         1.00         0.1
#> 1사분위수          5.1         2.8         1.60         0.3
#> 중위수             5.8         3.0         4.35         1.3
#> 3사분위수          6.4         3.3         5.10         1.8
#> 최대값             7.9         4.4         6.90         2.5

5.4.5 tapply()

tapply(X, INDEX, FUN = NULL, ..., default = NA, simplify = TRUE)는 요인(factor) 변수를 기준으로 그룹별로 나누어서 함수를 적용합니다. X는 벡터입니다. INDEX는 요인 또는 요인 리스트가 들어가는 인수입니다. 만일 X에 데이터프레임을 넣고 싶으면 by()함수를 쓰면 됩니다.

# iris 데이터의 요인변수 Species 별로 평균 구하기
str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
tapply(iris$Sepal.Length, iris$Species, mean, na.rm = TRUE)
#>     setosa versicolor  virginica 
#>       5.01       5.94       6.59
# 팩터변수가 2개가 되면 교차표가 만들어짐
# x[, -1]은 wool과 tension으로 구성된 데이터프레임
str(x <- warpbreaks)
#> 'data.frame':    54 obs. of  3 variables:
#>  $ breaks : num  26 30 54 25 70 52 51 26 67 18 ...
#>  $ wool   : Factor w/ 2 levels "A","B": 1 1 1 1 1 1 1 1 1 1 ...
#>  $ tension: Factor w/ 3 levels "L","M","H": 1 1 1 1 1 1 1 1 1 2 ...
tapply(x$breaks, x[, -1], sum)
#>     tension
#> wool   L   M   H
#>    A 401 216 221
#>    B 254 259 169
# 데이터 무작위 30개 만들어 x에 할당
set.seed(234)
x <- c(rnorm(10), runif(10), rnorm(10, 1))
str(x)
#>  num [1:30] 0.661 -2.053 -1.499 1.471 1.459 ...

# 팩터 변수 생성(값 30개)
# gl(n, k)는 1부터 n까지 정수로된 팩터 레벨을 k만큼 반복해서 만듬 
fac <- gl(3, 10)   # factor(rep(1:3, each = 10), levels = 1:3)와 동일
fac
#>  [1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
#> Levels: 1 2 3
  
tapply(x, fac, mean)
#>      1      2      3 
#> -0.422  0.606  1.116
tapply(x, fac, range)
#> $`1`
#> [1] -3.04  1.47
#> 
#> $`2`
#> [1] 0.192 0.883
#> 
#> $`3`
#> [1] 0.0699 2.0163

5.4.6 mapply()

mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE, USE.NAMES = TRUE)는 sapply의 다변량 버전이라 할 수 있습니다. 여러 개의 인자를 입력할 수 있고, 반환하는 값은 리스트, 벡터, 배열 등이 가능합니다. MoreArgs는 함수에 들어갈 또다른 인수 리스트입니다.

# rep(1, 4), rep(2, 3), ..., rep(x = 4, times = 1)  => 리스트 반환
mapply(rep, 1:4, 4:1) 
#> [[1]]
#> [1] 1 1 1 1
#> 
#> [[2]]
#> [1] 2 2 2
#> 
#> [[3]]
#> [1] 3 3
#> 
#> [[4]]
#> [1] 4

# rep(1, 3), rep(2, 3), ..., rep(x = 4, times = 3)  => 행렬 반환
mapply(rep, 1:4, 3)
#>      [,1] [,2] [,3] [,4]
#> [1,]    1    2    3    4
#> [2,]    1    2    3    4
#> [3,]    1    2    3    4

# rep(times = 1, x = 4), rep(times = 2, x = 3), ... 
mapply(rep, times = 1:4, x = 4:1)
#> [[1]]
#> [1] 4
#> 
#> [[2]]
#> [1] 3 3
#> 
#> [[3]]
#> [1] 2 2 2
#> 
#> [[4]]
#> [1] 1 1 1 1

# rep(x = 23, times = 1), rep(x = 23, times = 2), ...  
mapply(rep, 1:4, MoreArgs = list(x = 23))
#> [[1]]
#> [1] 23
#> 
#> [[2]]
#> [1] 23 23
#> 
#> [[3]]
#> [1] 23 23 23
#> 
#> [[4]]
#> [1] 23 23 23 23
# sprintf(" %d%s ", 1, "a"), ....
mapply(function(i, s) { 
  sprintf(" %d%s ", i, s) 
  }, 
  i = 1:3, s = c("a", "b", "c"))
#> [1] " 1a " " 2b " " 3c "

MoreArgs 인수는 여러 개의 데이터를 한꺼번에 함수에 넣어줍니다. 이 것을 사용하지 않으면 데이터가 하나씩 함수에 들어갑니다.

sumsq <- function(x, mean = 0, sd = 1) {
  sum(((x - mean) / sd)^2)
}

set.seed(123)
x <- rnorm(10000)

# sumsq(x, mean = 1, sd = 1), sumsq(x, mean = 2, sd = 2), ...
mapply(sumsq, mean = 1:100, sd = 1:100, MoreArgs = list(x = x))
#>   [1] 20019 12517 11124 10635 10408 10285 10210 10162 10128 10104 10087 10073
#>  [13] 10063 10054 10047 10042 10037 10033 10030 10027 10025 10023 10021 10019
#>  [25] 10018 10017 10015 10014 10013 10013 10012 10011 10011 10010 10009 10009
#>  [37] 10009 10008 10008 10007 10007 10007 10006 10006 10006 10006 10006 10005
#>  [49] 10005 10005 10005 10005 10004 10004 10004 10004 10004 10004 10004 10004
#>  [61] 10003 10003 10003 10003 10003 10003 10003 10003 10003 10003 10003 10003
#>  [73] 10003 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002
#>  [85] 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002
#>  [97] 10002 10002 10001 10001

# 만일 이렇게 mapply(sumsq, mean = 1:100, sd = 1:100, x = x) 하면
# sumsq(x = x[1], mean = 1, sd = 1), ... 이런식으로 계산되어
# 원하는 결과가 나오지 않음. 여기서 x[1]은 -0.560475647임

5.4.7 eapply()

eapply(env, FUN, ..., all.names = FALSE, USE.NAMES = TRUE)는 환경(Environment)에 있는 변수나 함수들을 반복 계산해 줍니다.

# 새로운 환경 만들기; R에서 전역환경은 .GlobalEnv 임
env <- new.env()
env$a <- 10
env$b <- 20:23
env$c <- 30:35

# 전역변수 자료형 확인하기
eapply(env, typeof)
#> $a
#> [1] "double"
#> 
#> $b
#> [1] "integer"
#> 
#> $c
#> [1] "integer"

# env 환경변수의 각 요소에 2를 곱하기
eapply(env, function(x) {x * 2})
#> $a
#> [1] 20
#> 
#> $b
#> [1] 40 42 44 46
#> 
#> $c
#> [1] 60 62 64 66 68 70

5.4.8 by()

by(data, INDICES, FUN, …, simplify = TRUE)는 데이터프레임을 위한 tapply라 할 수 있다. 요인(factor) 변수를 기준으로 그룹별로 나누어서 함수를 적용합니다. data는 데이터프레임이나 행렬입니다. INDEX는 요인 또는 요인 리스트가 들어가는 인수입니다.

str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
by(iris, iris$Species, summary)
#> iris$Species: setosa
#>   Sepal.Length   Sepal.Width    Petal.Length   Petal.Width          Species  
#>  Min.   :4.30   Min.   :2.30   Min.   :1.00   Min.   :0.100   setosa    :50  
#>  1st Qu.:4.80   1st Qu.:3.20   1st Qu.:1.40   1st Qu.:0.200   versicolor: 0  
#>  Median :5.00   Median :3.40   Median :1.50   Median :0.200   virginica : 0  
#>  Mean   :5.01   Mean   :3.43   Mean   :1.46   Mean   :0.246                  
#>  3rd Qu.:5.20   3rd Qu.:3.67   3rd Qu.:1.57   3rd Qu.:0.300                  
#>  Max.   :5.80   Max.   :4.40   Max.   :1.90   Max.   :0.600                  
#> ------------------------------------------------------------ 
#> iris$Species: versicolor
#>   Sepal.Length   Sepal.Width    Petal.Length   Petal.Width         Species  
#>  Min.   :4.90   Min.   :2.00   Min.   :3.00   Min.   :1.00   setosa    : 0  
#>  1st Qu.:5.60   1st Qu.:2.52   1st Qu.:4.00   1st Qu.:1.20   versicolor:50  
#>  Median :5.90   Median :2.80   Median :4.35   Median :1.30   virginica : 0  
#>  Mean   :5.94   Mean   :2.77   Mean   :4.26   Mean   :1.33                  
#>  3rd Qu.:6.30   3rd Qu.:3.00   3rd Qu.:4.60   3rd Qu.:1.50                  
#>  Max.   :7.00   Max.   :3.40   Max.   :5.10   Max.   :1.80                  
#> ------------------------------------------------------------ 
#> iris$Species: virginica
#>   Sepal.Length   Sepal.Width    Petal.Length   Petal.Width         Species  
#>  Min.   :4.90   Min.   :2.20   Min.   :4.50   Min.   :1.40   setosa    : 0  
#>  1st Qu.:6.22   1st Qu.:2.80   1st Qu.:5.10   1st Qu.:1.80   versicolor: 0  
#>  Median :6.50   Median :3.00   Median :5.55   Median :2.00   virginica :50  
#>  Mean   :6.59   Mean   :2.97   Mean   :5.55   Mean   :2.03                  
#>  3rd Qu.:6.90   3rd Qu.:3.17   3rd Qu.:5.88   3rd Qu.:2.30                  
#>  Max.   :7.90   Max.   :3.80   Max.   :6.90   Max.   :2.50
# 팩터변수가 2개가 되면 그만큼 그룹으로 더 나뉘어짐
# x[, -1]은 wool과 tension으로 구성된 데이터프레임
str(warpbreaks)
#> 'data.frame':    54 obs. of  3 variables:
#>  $ breaks : num  26 30 54 25 70 52 51 26 67 18 ...
#>  $ wool   : Factor w/ 2 levels "A","B": 1 1 1 1 1 1 1 1 1 1 ...
#>  $ tension: Factor w/ 3 levels "L","M","H": 1 1 1 1 1 1 1 1 1 2 ...
by(warpbreaks[, 1], warpbreaks[, -1], summary)
#> wool: A
#> tension: L
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>    25.0    26.0    51.0    44.6    54.0    70.0 
#> ------------------------------------------------------------ 
#> wool: B
#> tension: L
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>    14.0    20.0    29.0    28.2    31.0    44.0 
#> ------------------------------------------------------------ 
#> wool: A
#> tension: M
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>      12      18      21      24      30      36 
#> ------------------------------------------------------------ 
#> wool: B
#> tension: M
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>    16.0    21.0    28.0    28.8    39.0    42.0 
#> ------------------------------------------------------------ 
#> wool: A
#> tension: H
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>    10.0    18.0    24.0    24.6    28.0    43.0 
#> ------------------------------------------------------------ 
#> wool: B
#> tension: H
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>    13.0    15.0    17.0    18.8    21.0    28.0

5.4.9 split()

  • split(x, f, drop = FALSE, ...)은 f에 정의된 그룹(보통 팩터 또는 리스트)에 따라 벡터 x를 분리합니다. 반환되는 값은 리스트입니다.
  • unsplit(value, f, drop = FALSE)은 분리된 벡터 리스트 또는 데이터프레임인 value를 f에 정의된 그룹기준으로 원상태로 되돌립니다. 반환되는 값은 벡터나 데이터프레임입니다.
str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

# iris 데이터프레임을 Species 기준으로 분리합니다. (결과 리스트 형태) 
x <- split(iris, iris$Species)
str(x)
#> List of 3
#>  $ setosa    :'data.frame':  50 obs. of  5 variables:
#>   ..$ Sepal.Length: num [1:50] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>   ..$ Sepal.Width : num [1:50] 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>   ..$ Petal.Length: num [1:50] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>   ..$ Petal.Width : num [1:50] 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>   ..$ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
#>  $ versicolor:'data.frame':  50 obs. of  5 variables:
#>   ..$ Sepal.Length: num [1:50] 7 6.4 6.9 5.5 6.5 5.7 6.3 4.9 6.6 5.2 ...
#>   ..$ Sepal.Width : num [1:50] 3.2 3.2 3.1 2.3 2.8 2.8 3.3 2.4 2.9 2.7 ...
#>   ..$ Petal.Length: num [1:50] 4.7 4.5 4.9 4 4.6 4.5 4.7 3.3 4.6 3.9 ...
#>   ..$ Petal.Width : num [1:50] 1.4 1.5 1.5 1.3 1.5 1.3 1.6 1 1.3 1.4 ...
#>   ..$ Species     : Factor w/ 3 levels "setosa","versicolor",..: 2 2 2 2 2 2 2 2 2 2 ...
#>  $ virginica :'data.frame':  50 obs. of  5 variables:
#>   ..$ Sepal.Length: num [1:50] 6.3 5.8 7.1 6.3 6.5 7.6 4.9 7.3 6.7 7.2 ...
#>   ..$ Sepal.Width : num [1:50] 3.3 2.7 3 2.9 3 3 2.5 2.9 2.5 3.6 ...
#>   ..$ Petal.Length: num [1:50] 6 5.1 5.9 5.6 5.8 6.6 4.5 6.3 5.8 6.1 ...
#>   ..$ Petal.Width : num [1:50] 2.5 1.9 2.1 1.8 2.2 2.1 1.7 1.8 1.8 2.5 ...
#>   ..$ Species     : Factor w/ 3 levels "setosa","versicolor",..: 3 3 3 3 3 3 3 3 3 3 ...

# 분리된 것을 다시 원상태로 돌립니다. (결과 데이터프레임)
y <- unsplit(x, iris$Species)
str(y)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

두 개 칼럼을 기준으로 분리할 수 있습니다. 2개 이상의 칼럼을 기준으로 할 때에는 리스트 구조로 묶어줍니다.

str(infert)  # R 내장 데이터 infert
#> 'data.frame':    248 obs. of  8 variables:
#>  $ education     : Factor w/ 3 levels "0-5yrs","6-11yrs",..: 1 1 1 1 2 2 2 2 2 2 ...
#>  $ age           : num  26 42 39 34 35 36 23 32 21 28 ...
#>  $ parity        : num  6 1 6 4 3 4 1 2 1 2 ...
#>  $ induced       : num  1 1 2 2 1 2 0 0 0 0 ...
#>  $ case          : num  1 1 1 1 1 1 1 1 1 1 ...
#>  $ spontaneous   : num  2 0 0 0 1 1 0 0 1 0 ...
#>  $ stratum       : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ pooled.stratum: num  3 1 4 2 32 36 6 22 5 19 ...

x <- split(infert, list(infert$spontaneous, infert$education))
str(x, max.level = 1)
#> List of 9
#>  $ 0.0-5yrs :'data.frame':   9 obs. of  8 variables:
#>  $ 1.0-5yrs :'data.frame':   1 obs. of  8 variables:
#>  $ 2.0-5yrs :'data.frame':   2 obs. of  8 variables:
#>  $ 0.6-11yrs:'data.frame':   71 obs. of  8 variables:
#>  $ 1.6-11yrs:'data.frame':   33 obs. of  8 variables:
#>  $ 2.6-11yrs:'data.frame':   16 obs. of  8 variables:
#>  $ 0.12+ yrs:'data.frame':   61 obs. of  8 variables:
#>  $ 1.12+ yrs:'data.frame':   37 obs. of  8 variables:
#>  $ 2.12+ yrs:'data.frame':   18 obs. of  8 variables:

행의 갯수를 지정해서 데이터를 분리할 수 있습니다. 인수 f에 데이터를 나눌 기준이 될 벡터을 만들면 됩니다. 그러면 같은 수를 가진 행끼리 하나의 그룹으로 만들어집니다. 단, 이 기준 벡터의 수와 데이터의 갯수가 같아야 오류가 나지 않습니다.

# infert 데이터의 전체 행을 10개씩 구분하는 기준 벡터를 만듭니다. 
df <- iris     # 데이터
gnum <- 10     # 그룹의 갯수
f <- c(rep(1:floor((nrow(df)/gnum)), each = gnum),
       rep(ceiling(nrow(df)/gnum), each = nrow(df)%%gnum))

x <- split(df, f)
print(x[1:2]); cat("... 이하 생략 ...")
#> $`1`
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1           5.1         3.5          1.4         0.2  setosa
#> 2           4.9         3.0          1.4         0.2  setosa
#> 3           4.7         3.2          1.3         0.2  setosa
#> 4           4.6         3.1          1.5         0.2  setosa
#> 5           5.0         3.6          1.4         0.2  setosa
#> 6           5.4         3.9          1.7         0.4  setosa
#> 7           4.6         3.4          1.4         0.3  setosa
#> 8           5.0         3.4          1.5         0.2  setosa
#> 9           4.4         2.9          1.4         0.2  setosa
#> 10          4.9         3.1          1.5         0.1  setosa
#> 
#> $`2`
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 11          5.4         3.7          1.5         0.2  setosa
#> 12          4.8         3.4          1.6         0.2  setosa
#> 13          4.8         3.0          1.4         0.1  setosa
#> 14          4.3         3.0          1.1         0.1  setosa
#> 15          5.8         4.0          1.2         0.2  setosa
#> 16          5.7         4.4          1.5         0.4  setosa
#> 17          5.4         3.9          1.3         0.4  setosa
#> 18          5.1         3.5          1.4         0.3  setosa
#> 19          5.7         3.8          1.7         0.3  setosa
#> 20          5.1         3.8          1.5         0.3  setosa
#> ... 이하 생략 ...
# 위 예제에서 분리된 것을 같은 기준으로 하나로 묶습니다.
y <- unsplit(x, f)
str(y)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

5.4.10 Vectorize()

Vectorize(FUN, vectorize.args = arg.names, SIMPLIFY = TRUE, USE.NAMES = TRUE) 함수는 인자 FUN에 들어간 함수가 벡터 방식으로 작동하도록 만드는 함수입니다. 함수 중에는 함수의 인자가 단 하나의 값만 들어가야 하는 경우가 있어 인자에 여러 개의 값 즉 벡터형식으로 넣을 수 없는 함수들이 있습니다. Vectorize() 함수는 이 문제를 해결하여 줍니다. 이때 벡터형식으로 들어갈 인자를 vectorize.args에 지정합니다. 반환되는 결과는 벡터로 들어간 인자들이 각각 투입된 만큼의 결과들이 행렬이나 배열로 묶여서 나옵니다.

아래 예제는 iris$Species에서 vi나 se로 시작하는 행을 추출하고자 하는 것입니다.

# grepl함수는 pattern 인수에 단 하나의 값만 넣을 수 있습니다. 
# 만일 아래 같이 벡터형식으로 값을 여러 개 넣으면 첫번째 요소만 사용되어
# 원하는 결과를 얻을 수없습니다.
x <- grepl(pattern = c("^vi", "^se"), x = iris$Species)
#> Warning in grepl(pattern = c("^vi", "^se"), x = iris$Species): 인자 'pattern'는
#> 반드시 길이가 1 보다 커야 하고, 오로지 첫번째 요소만이 사용될 것입니다
head(x)
#> [1] FALSE FALSE FALSE FALSE FALSE FALSE

# grepl 함수를 벡터작업이 가능한 함수 vgrepl로 만듭니다. 
# 벡터로 들어갈 인수는 pattern으로 지정합니다.
vgrepl <- Vectorize(grepl, vectorize.args = "pattern")
x <- vgrepl(pattern = c("^vi", "^se"), x = iris$Species)
head(x)
#>        ^vi  ^se
#> [1,] FALSE TRUE
#> [2,] FALSE TRUE
#> [3,] FALSE TRUE
#> [4,] FALSE TRUE
#> [5,] FALSE TRUE
#> [6,] FALSE TRUE

# 조건에 맞는 행들을 추출합니다.
str(iris[rowSums(x) > 0, ])
#> 'data.frame':    100 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

물론 Vectorize() 함수를 사용하지 않고 sapply() 함수를 사용하여 같은 결과를 얻을 수 있습니다.

x <- sapply(c("^vi", "^se"),
            function(pattern) {grepl(pattern = pattern, x = iris$Species)})
head(x)
#>        ^vi  ^se
#> [1,] FALSE TRUE
#> [2,] FALSE TRUE
#> [3,] FALSE TRUE
#> [4,] FALSE TRUE
#> [5,] FALSE TRUE
#> [6,] FALSE TRUE
sumsq <- function(x, mean = 0, sd = 1) {
  sum(((x - mean) / sd)^2)}

set.seed(123)
x <- rnorm(10000)

# mapply(sumsq, mean = 1:100, sd = 1:100, MoreArgs = list(x = x))와 같은 결과
vsumsq <- Vectorize(sumsq, c("mean", "sd"))
vsumsq(x, 1:100, 1:100)
#>   [1] 20019 12517 11124 10635 10408 10285 10210 10162 10128 10104 10087 10073
#>  [13] 10063 10054 10047 10042 10037 10033 10030 10027 10025 10023 10021 10019
#>  [25] 10018 10017 10015 10014 10013 10013 10012 10011 10011 10010 10009 10009
#>  [37] 10009 10008 10008 10007 10007 10007 10006 10006 10006 10006 10006 10005
#>  [49] 10005 10005 10005 10005 10004 10004 10004 10004 10004 10004 10004 10004
#>  [61] 10003 10003 10003 10003 10003 10003 10003 10003 10003 10003 10003 10003
#>  [73] 10003 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002
#>  [85] 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002
#>  [97] 10002 10002 10001 10001
sumsq <- function(x, mean = 0, sd = 1) {
  sum(((x - mean) / sd)^2)
}

set.seed(123)
x <- rnorm(10000)

# sumsq(x, mean = 1, sd = 1), sumsq(x, mean = 2, sd = 2), ...
mapply(sumsq, mean = 1:100, sd = 1:100, MoreArgs = list(x = x))
#>   [1] 20019 12517 11124 10635 10408 10285 10210 10162 10128 10104 10087 10073
#>  [13] 10063 10054 10047 10042 10037 10033 10030 10027 10025 10023 10021 10019
#>  [25] 10018 10017 10015 10014 10013 10013 10012 10011 10011 10010 10009 10009
#>  [37] 10009 10008 10008 10007 10007 10007 10006 10006 10006 10006 10006 10005
#>  [49] 10005 10005 10005 10005 10004 10004 10004 10004 10004 10004 10004 10004
#>  [61] 10003 10003 10003 10003 10003 10003 10003 10003 10003 10003 10003 10003
#>  [73] 10003 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002
#>  [85] 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002 10002
#>  [97] 10002 10002 10001 10001

# 만일 이렇게 mapply(sumsq, mean = 1:100, sd = 1:100, x = x) 하면
# sumsq(x = x[1], mean = 1, sd = 1), ... 이런식으로 계산되어
# 원하는 결과가 나오지 않음. 여기서 x[1]은 -0.560475647임

5.4.11 replicate()

replicate(n, expr, simplify = "array") 함수는 sapply() 함수의 간소화 버전이라 할 수 있습니다. expr 인자를 n번 만큼 반복수행합니다. expr 인자에는 주로 함수들이 많이 들어갑니다. 반환되는 결과는 기본적으로 배열입니다. 만일 반환되는 값을 리스트 형태로 받고자 한다면 simplify = FALSE로 지정합니다.

아래 예제는 rnorm(5, mean = 0, sd = 1)을 3번 반복하여 그 결과를 행렬형태로 얻는 것입니다.

set.seed(123)
replicate(n = 3, rnorm(5, mean = 0, sd = 1))
#>         [,1]   [,2]   [,3]
#> [1,] -0.5605  1.715  1.224
#> [2,] -0.2302  0.461  0.360
#> [3,]  1.5587 -1.265  0.401
#> [4,]  0.0705 -0.687  0.111
#> [5,]  0.1293 -0.446 -0.556

5.4.12 rep()

rep(x, ...) 함수는 특정값을 반복해서 만들어 반환해주는 함수입니다. ...에는 인자로 times, length.out, each가 들어갈 수 있습니다. times는 x를 몇 번 반복할지를 정해주는 인수입니다. length.out은 최종 반환되는 값들의 길이를 정해주는 인수입니다. each는 x가 여러개의 값으로 구성된 벡터일 경우 각각 몇 번 반복할지를 정해주는 인수 입니다.

rep.int(x, times)rep_len(x, length.out)rep() 함수와 기능이 거의 같으나 인수가 제한됩니다. 이 함수들은 rep() 함수보다 더 빠릅니다.

rep(1:5, 2)   # rep(x = 1:4, times = 2)와 동일
#>  [1] 1 2 3 4 5 1 2 3 4 5
rep(1:5, each = 2)    
#>  [1] 1 1 2 2 3 3 4 4 5 5
rep(1:5, c(2, 3, 2, 1, 2))  # rep(1:5, times = c(2, 3, 2, 1, 2)) 동일
#>  [1] 1 1 2 2 2 3 3 4 5 5

rep(1:5, each = 2, len = 5)
#> [1] 1 1 2 2 3
rep(1:2, each = 2, len = 7)  # 길이를 채우기 위해 다시 반복
#> [1] 1 1 2 2 1 1 2
rep(1:3, each = 2, times =3)
#>  [1] 1 1 2 2 3 3 1 1 2 2 3 3 1 1 2 2 3 3

rep(2, 5.9)  # 소수점 이하는 버리고 반복
#> [1] 2 2 2 2 2

rep(c("abc", "123"), times = 3)
#> [1] "abc" "123" "abc" "123" "abc" "123"
rep(c("abc", "123"), each = 3)
#> [1] "abc" "abc" "abc" "123" "123" "123"

rep.int(c("abc", "123"), 3)  # 기본인자는 times임. each 인자는 사용할 수 없음
#> [1] "abc" "123" "abc" "123" "abc" "123"
rep_len(c("abc", "123"), 5)  # 기본인자는 length.out임
#> [1] "abc" "123" "abc" "123" "abc"

5.4.13 seq()

seq(...) 함수는 일련번호 숫자를 생성하여 반환합니다. ...에는 인자로 from, to, by, length.out, along.with가 들어갈 수 있습니다.

  • from : 시작 숫자
  • to : 끝 숫자
  • by : 증가 간격
  • length.out : 생성되는 일련번호의 길이
  • along.with : 인자에 들어가는 값들의 길이만큼 일련번호 생성

seq.int()는 seq() 함수와 거의 동일하고, seq_along(along.with)seq_len(length.out)는 무조건 시작이 1이고, 각각 하나의 인자를 받아 일련번호를 생성합니다. 이 함수들은 1부터 시작하는 일련번호를 seq() 함수보다 약간 더 빠르게 만들어 줍니다.

가장 많이 사용하는 형태는 다음과 같습니다.

  • seq(from, to)
  • seq(from, to, by = )
  • seq(from, to, length.out = )
  • seq(along.with = )
  • seq(from)
  • seq(length.out = )
seq(2, 7)  # seq(from = 2, to = 7과 동일)
#> [1] 2 3 4 5 6 7
seq(7, 2)
#> [1] 7 6 5 4 3 2
seq(2, 10, by = 2)
#> [1]  2  4  6  8 10
seq(2, 10, by = pi)
#> [1] 2.00 5.14 8.28
seq(2, 10, length.out = 3)
#> [1]  2  6 10
seq(along.with = c(2:7))   # c(2:7)의 길이는 6이므로 6개 생성
#> [1] 1 2 3 4 5 6
seq(2, 10, along.with = c(2:7))
#> [1]  2.0  3.6  5.2  6.8  8.4 10.0
seq(7)   # 1부터 7까지 생성
#> [1] 1 2 3 4 5 6 7
seq(length.out = 12)
#>  [1]  1  2  3  4  5  6  7  8  9 10 11 12
seq.int(2, 10, by = pi)
#> [1] 2.00 5.14 8.28
seq_len(12)    # seq(length.out = 12)와 동일하나 약간 더 빠름
#>  [1]  1  2  3  4  5  6  7  8  9 10 11 12
seq_along(c(2:7))
#> [1] 1 2 3 4 5 6
n <- 999999999999999

start <- proc.time()
x <- seq(length.out = n)
end <- proc.time()
end - start
#>    user  system elapsed 
#>       0       0       0

start <- proc.time()
x <- seq_len(n)
end <- proc.time()
end - start
#>    user  system elapsed 
#>       0       0       0

일련 날짜를 생성할 수 있습니다.

# 1년 간격
seq(as.Date("2001/1/1"), as.Date("2020/1/1"), "years")
#>  [1] "2001-01-01" "2002-01-01" "2003-01-01" "2004-01-01" "2005-01-01"
#>  [6] "2006-01-01" "2007-01-01" "2008-01-01" "2009-01-01" "2010-01-01"
#> [11] "2011-01-01" "2012-01-01" "2013-01-01" "2014-01-01" "2015-01-01"
#> [16] "2016-01-01" "2017-01-01" "2018-01-01" "2019-01-01" "2020-01-01"

# 월 간격
seq(as.Date("2001/1/1"), by = "month", length.out = 12)
#>  [1] "2001-01-01" "2001-02-01" "2001-03-01" "2001-04-01" "2001-05-01"
#>  [6] "2001-06-01" "2001-07-01" "2001-08-01" "2001-09-01" "2001-10-01"
#> [11] "2001-11-01" "2001-12-01"

# 분기 간격
seq(as.Date("2015/1/1"), as.Date("2020/1/1"), by = "quarter")
#>  [1] "2015-01-01" "2015-04-01" "2015-07-01" "2015-10-01" "2016-01-01"
#>  [6] "2016-04-01" "2016-07-01" "2016-10-01" "2017-01-01" "2017-04-01"
#> [11] "2017-07-01" "2017-10-01" "2018-01-01" "2018-04-01" "2018-07-01"
#> [16] "2018-10-01" "2019-01-01" "2019-04-01" "2019-07-01" "2019-10-01"
#> [21] "2020-01-01"

# 월 간격 반대로
seq(as.Date("2020-1-7"), as.Date("2019-1-17"), by = "-1 month")
#>  [1] "2020-01-07" "2019-12-07" "2019-11-07" "2019-10-07" "2019-09-07"
#>  [6] "2019-08-07" "2019-07-07" "2019-06-07" "2019-05-07" "2019-04-07"
#> [11] "2019-03-07" "2019-02-07"
seq(ISOdate(2015,1,1), ISOdate(2020,1,1), "years")
#> [1] "2015-01-01 12:00:00 GMT" "2016-01-01 12:00:00 GMT"
#> [3] "2017-01-01 12:00:00 GMT" "2018-01-01 12:00:00 GMT"
#> [5] "2019-01-01 12:00:00 GMT" "2020-01-01 12:00:00 GMT"

seq(c(ISOdate(2020,3,20)), by = "DSTday", length.out = 10)
#>  [1] "2020-03-20 21:00:00 KST" "2020-03-21 21:00:00 KST"
#>  [3] "2020-03-22 21:00:00 KST" "2020-03-23 21:00:00 KST"
#>  [5] "2020-03-24 21:00:00 KST" "2020-03-25 21:00:00 KST"
#>  [7] "2020-03-26 21:00:00 KST" "2020-03-27 21:00:00 KST"
#>  [9] "2020-03-28 21:00:00 KST" "2020-03-29 21:00:00 KST"

seq(c(ISOdate(2020,3,20)), by = "7 DSTdays", length.out = 5)
#> [1] "2020-03-20 21:00:00 KST" "2020-03-27 21:00:00 KST"
#> [3] "2020-04-03 21:00:00 KST" "2020-04-10 21:00:00 KST"
#> [5] "2020-04-17 21:00:00 KST"
seq(as.POSIXct("2015-3-14 17:22:15"), as.POSIXct("2015-3-15 9:12:25"),
    by = "hours")
#>  [1] "2015-03-14 17:22:15 KST" "2015-03-14 18:22:15 KST"
#>  [3] "2015-03-14 19:22:15 KST" "2015-03-14 20:22:15 KST"
#>  [5] "2015-03-14 21:22:15 KST" "2015-03-14 22:22:15 KST"
#>  [7] "2015-03-14 23:22:15 KST" "2015-03-15 00:22:15 KST"
#>  [9] "2015-03-15 01:22:15 KST" "2015-03-15 02:22:15 KST"
#> [11] "2015-03-15 03:22:15 KST" "2015-03-15 04:22:15 KST"
#> [13] "2015-03-15 05:22:15 KST" "2015-03-15 06:22:15 KST"
#> [15] "2015-03-15 07:22:15 KST" "2015-03-15 08:22:15 KST"

5.4.14 sequence()

sequence(nvec, ...) 함수는 연속적인 숫자를 만들어 내는 함수입니다. 인자 nvec에 들어간 숫자 벡터들 만큼 seq() 함수가 수행되어 연속적인 숫자가 만들어집니다. 예를 들면 nvec = c(3, 2)이면 seq(3), seq(2)가 수행됩니다.

sequence(nvec = c(3, 2))   # c(seq(3), seq(2)) 또는 c(1:3, 1:2)와 동일
#> [1] 1 2 3 1 2
sequence(nvec = c(3, 2, 5))
#>  [1] 1 2 3 1 2 1 2 3 4 5
sequence(c(3, 2), from = 2)  # 시작숫자 지정
#> [1] 2 3 4 2 3
sequence(c(3, 2), from = 2, by = -2)  # 시작숫자, 간격 지정
#> [1]  2  0 -2  2  0
sequence(c(3, 2), by = c(2, -2))
#> [1]  1  3  5  1 -1

5.4.15 gl()

gl(n, k, length = n*k, labels = seq_len(n), ordered = FALSE) 함수는 요인 수준을 반복적으로 생성하여 반환합니다. gl은 generate levels의 약자로 생각됩니다.

  • n : 수준을 나태내는 숫자. n = 2이면 label을 따로 지정하지 않은 경우 수준이 1, 2로 표현됩니다.
  • k : 수준을 반복할 숫자. k = 8이면 8번 반복합니다.
  • length : 생성되는 총 데이터의 갯수
  • labels : 수준에 대한 이름
  • ordered : TRUE이면 요인의 순서가 정해집니다. (순서형 데이터)
gl(n = 2, k = 8)  # 수준 1, 2를 각각 8번 반복
#>  [1] 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2
#> Levels: 1 2
gl(n = 2, k = 8, labels = c("Contol", "Treat"))  # 수준 이름 지정
#>  [1] Contol Contol Contol Contol Contol Contol Contol Contol Treat  Treat 
#> [11] Treat  Treat  Treat  Treat  Treat  Treat 
#> Levels: Contol Treat
gl(n = 2, k = 1, length = 10)  # 수준 1, 2가 각각 1번씩 반복되며 10개 생성
#>  [1] 1 2 1 2 1 2 1 2 1 2
#> Levels: 1 2
gl(n = 2, k = 2, length = 10)  # 수준 1, 2가 각각 2번씩 반복되며 10개 생성
#>  [1] 1 1 2 2 1 1 2 2 1 1
#> Levels: 1 2

# 수준 1, 2가 M과 F로 순서있게 네이밍 되고, 각각 3번씩 반복되며 10개 생성
gl(2, 3, 10, labels = c("M", "F"), ordered = TRUE)
#>  [1] M M M F F F M M M F
#> Levels: M < F

5.4.16 sweep()

sweep(x, MARGIN, STATS, FUN = "-", check.margin = TRUE, ...) 함수는 행렬, 데이터프레임 등에 통계량과 함수를 적용하여 그 결과를 반환하는 함수입니다. 데이터 x에 일률적으로 특정 숫자만큼 더하거나 빼기를 할 때 많이 사용합니다.

  • x : 입력 데이터로서 배열, 행렬, 데이터프레임
  • MARGIN : 행과 열 방향 지정. 1이면 열방향, 2이면 행방향
  • STATS : 적용할 요약 통계량 또는 수치
  • FUN : 적용할 함수 (기본값은 “-” 임)
x <- matrix(1:12, ncol = 3); x
#>      [,1] [,2] [,3]
#> [1,]    1    5    9
#> [2,]    2    6   10
#> [3,]    3    7   11
#> [4,]    4    8   12

# 행렬 x를 행방향(2)으로 1씩 빼줌
sweep(x, MARGIN = 2, STATS = 1, FUN = "-")  
#>      [,1] [,2] [,3]
#> [1,]    0    4    8
#> [2,]    1    5    9
#> [3,]    2    6   10
#> [4,]    3    7   11

# 행렬 x를 열방향(1)으로 각각 1, 2, 3, 4를 더해줌
sweep(x, 1, c(1,2,3,4), FUN = "+")  
#>      [,1] [,2] [,3]
#> [1,]    2    6   10
#> [2,]    4    8   12
#> [3,]    6   10   14
#> [4,]    8   12   16

# 행 기준 비율 계산
round(sweep(x, 1, apply(x, 1, sum), FUN = "/") * 100, 1)
#>      [,1] [,2] [,3]
#> [1,]  6.7 33.3 60.0
#> [2,] 11.1 33.3 55.6
#> [3,] 14.3 33.3 52.4
#> [4,] 16.7 33.3 50.0

# 열 기준 비율 계산
round(sweep(x, 2, apply(x, 2, sum), FUN = "/") * 100, 1)
#>      [,1] [,2] [,3]
#> [1,]   10 19.2 21.4
#> [2,]   20 23.1 23.8
#> [3,]   30 26.9 26.2
#> [4,]   40 30.8 28.6

# 전체 기준 비율 계산
round(sweep(x, 2, sum(x), FUN = "/") * 100, 1)
#>      [,1] [,2] [,3]
#> [1,]  1.3  6.4 11.5
#> [2,]  2.6  7.7 12.8
#> [3,]  3.8  9.0 14.1
#> [4,]  5.1 10.3 15.4
# 데이터프레임의 숫자 열에 각각 1씩 빼기
str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
y <- sweep(iris[1:4], MARGIN = 2, STATS = 1, FUN = "-") 
str(y)
#> 'data.frame':    150 obs. of  4 variables:
#>  $ Sepal.Length: num  4.1 3.9 3.7 3.6 4 4.4 3.6 4 3.4 3.9 ...
#>  $ Sepal.Width : num  2.5 2 2.2 2.1 2.6 2.9 2.4 2.4 1.9 2.1 ...
#>  $ Petal.Length: num  0.4 0.4 0.3 0.5 0.4 0.7 0.4 0.5 0.4 0.5 ...
#>  $ Petal.Width : num  -0.8 -0.8 -0.8 -0.8 -0.8 -0.6 -0.7 -0.8 -0.8 -0.9 ...

5.4.17 aggregate()

aggregate(x, ...) 함수는 데이터 x의 그룹별로 함수를 적용하여 그 결과를 반환합니다.

  • x : 입력 데이터
  • by : 그룹핑 변수(열)로서 리스트 형식이어야 함
  • FUN : 적용할 함수
str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
# 종류별로 평균을 구함
aggregate(iris[1:4], by = list(iris$Species), mean, na.rm = TRUE)
#>      Group.1 Sepal.Length Sepal.Width Petal.Length Petal.Width
#> 1     setosa         5.01        3.43         1.46       0.246
#> 2 versicolor         5.94        2.77         4.26       1.326
#> 3  virginica         6.59        2.97         5.55       2.026

# 그룹핑 그룹에 이름을 지정할 수 있음(종류)
aggregate(iris[1:4], by = list(종류 = iris$Species), mean, na.rm = TRUE)
#>         종류 Sepal.Length Sepal.Width Petal.Length Petal.Width
#> 1     setosa         5.01        3.43         1.46       0.246
#> 2 versicolor         5.94        2.77         4.26       1.326
#> 3  virginica         6.59        2.97         5.55       2.026

# 적용할 함수에 인자들을 지정할 수 있음
aggregate(iris[1:4], by = list(종류 = iris$Species), mean, na.rm = TRUE, trim = 0.1)
#>         종류 Sepal.Length Sepal.Width Petal.Length Petal.Width
#> 1     setosa         5.00        3.42         1.46       0.238
#> 2 versicolor         5.94        2.78         4.29       1.325
#> 3  virginica         6.57        2.96         5.51       2.033
str(infert)  # R 내장 데이터 infert
#> 'data.frame':    248 obs. of  8 variables:
#>  $ education     : Factor w/ 3 levels "0-5yrs","6-11yrs",..: 1 1 1 1 2 2 2 2 2 2 ...
#>  $ age           : num  26 42 39 34 35 36 23 32 21 28 ...
#>  $ parity        : num  6 1 6 4 3 4 1 2 1 2 ...
#>  $ induced       : num  1 1 2 2 1 2 0 0 0 0 ...
#>  $ case          : num  1 1 1 1 1 1 1 1 1 1 ...
#>  $ spontaneous   : num  2 0 0 0 1 1 0 0 1 0 ...
#>  $ stratum       : int  1 2 3 4 5 6 7 8 9 10 ...
#>  $ pooled.stratum: num  3 1 4 2 32 36 6 22 5 19 ...

# 2개 변수로 그룹핑
aggregate(infert[2:3], by = list(infert$education, infert$spontaneous), 
          mean, na.rm = TRUE)
#>   Group.1 Group.2  age parity
#> 1  0-5yrs       0 36.6   4.11
#> 2 6-11yrs       0 33.7   1.72
#> 3 12+ yrs       0 29.7   1.66
#> 4  0-5yrs       1 34.0   4.00
#> 5 6-11yrs       1 31.2   2.45
#> 6 12+ yrs       1 28.9   1.70
#> 7  0-5yrs       2 30.0   5.00
#> 8 6-11yrs       2 32.4   3.06
#> 9 12+ yrs       2 31.6   2.89