Tools (123)

반응형

Function

Three Component of function

body : 함수 내부의 내용
formal : 함수 전달 인자
environment : 함수가 정의된 환경
f <- function(x) x^2
f
#> function(x) x^2

formals(f)
#> $x
body(f)
#> x^2
environment(f)
#> <environment: R_GlobalEnv>

Three base types of function

Closure (사용자 정의 함수, print, mean 등의 대부분의 함수)
Built-in (sum, max, +, - 등)
Special (if, for, function, log 등) 

Built-in & Special function

  • .Primitive() 함수를 통해 C 코드를 곧바로 실행시킴
  • 또는 .Internal, .External R 내부 함수 실행
  • BASE 패키지에만 있음
formals(sum)
#> NULL
body(sum)
#> NULL
environment(sum)
#> NULL
typeof(sum)
#> [1] "builtin"

formals(`log`)
#> NULL
body(`log`)
#> NULL
environment(`log`)
#> NULL
typeof(`log`)
#> [1] "special"

Every operation is a function call

To understand computations in R, two slogans are helpful:

  1. Everything that exists is an object.
  2. Everything that happens is a function call.
x <- 10; y <- 5
x + y
#> [1] 15
`+`(x, y)
#> [1] 15
for (i in 1:2) print(i)
#> [1] 1
#> [1] 2
`for`(i, 1:2, print(i))
#> [1] 1
#> [1] 2
if (i == 1) print("yes!") else print("no.")
#> [1] "no."
`if`(i == 1, print("yes!"), print("no."))
#> [1] "no."

function 의 argument 를 list 형식으로 전달하기

args <- list(1:10, na.rm = TRUE)
do.call(mean, args)
#> [1] 5.5

Default and missing value

f <- function(a = 1, b = 2) {
  c(a, b)
}
f()
#> [1] 1 2

Special types of function

  1. Infix function
    • +, * 처럼 인수의 중간에 함수가 있는 경우
    • 유저가 infix function 을 정의하려면 앞 뒤로 %를 붙임
    • built-in function 은 % 가 없어도 됨 (+, ! & 등)
# 문자열을 잇는 함수 만들기 
`%+%` <- function(a, b) paste0(a, b)
"new" %+% " string"
#> [1] "new string"
`%+%`("new", "string")
#> [1] "newstring"
  1. Replacement function
    • 해당 위치의 원소를 바꾸는 특별한 함수
    • xxx<- 의 형태로 함수의 이름을 만들어야함
    • 아래 형태를 가져야 함
x <- 1:10
`modify<-` <- function(x, position, value) {
  x[position] <- value
  x
}
# 재할당하지 않아도, x의 첫 번째 원소를 바꾼다.
modify(x, 1) <- 10 
# x <- `modify<-`(x, 1, 10) 와 같다.

x
#>  [1] 10  2  3  4  5  6  7  8  9 10

replacement function의 예 : names<-

x <- c(a = 1, b = 2, c = 3)
names(x)
#> [1] "a" "b" "c"
names(x)[2] <- "two"
names(x)
#> [1] "a"   "two" "c"

# 실제로 내부적으로 아래 코드처럼 실행됨 
# `*tmp*` <- names(x)
# `*tmp*`[2] <- "two"
# names(x) <- `*tmp*`

data frame의 열 이름 바꾸기

df <- data.frame(a = c(1:3), b = c(2:4), c = c(3:5))
colnames(df)[2] <- 'd'
df
#>   a d c
#> 1 1 2 3
#> 2 2 3 4
#> 3 3 4 5

Excercise

  1. Boostrap + Random feature selection 구현하기
  • sample 10개를 생성하라 (사용자 정의 함수와 base 패키지의 replicate 함수를 이용)
make_boostraps <- function(data, n, row_perc, col_perc, label) {
  make_one_bootstrap <- function(data, row_perc, col_perc, label) {
    num_row <- nrow(data)
    num_col <- length(data)
    
    raws_to_keep <- sample(num_row, row_perc * num_row, replace = TRUE)
   
    cols_names <- colnames(BreastCancer)
    features <- cols_names[!(cols_names %in% label)]
    cols_to_keep <- c(label, sample(features, col_perc * num_col))
    
    return(data[raws_to_keep, cols_to_keep])
  }
  return(replicate(n,
    make_one_bootstrap(data, row_perc, col_perc, label = label),
    simplify = FALSE
  ))
}
library(mlbench)
data("BreastCancer")
bootstraps <- make_boostraps(BreastCancer,
  n = 10,
  row_perc = 0.5,
  col_perc = 0.7,
  label = c("Id", "Class")
)
str(bootstraps)
#> List of 10
#>  $ :'data.frame':    349 obs. of  9 variables:
#>   ..$ Id             : chr [1:349] "1276091" "1212422" "566509" "1240603" ...
#>   ..$ Class          : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 1 1 1 1 1 ...
#>   ..$ Cl.thickness   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 3 3 5 3 3 5 1 1 3 5 ...
#>   ..$ Bare.nuclei    : Factor w/ 10 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 5 1 ...
#>   ..$ Mitoses        : Factor w/ 9 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 2 ...
#>   ..$ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 1 1 1 1 1 2 1 1 1 1 ...
#>   ..$ Cell.shape     : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 1 1 1 1 2 1 1 1 ...
#>   ..$ Cell.size      : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 1 1 1 1 2 1 1 1 ...
#>   ..$ Marg.adhesion  : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 3 1 1 1 2 3 1 2 1 1 ...
#>  $ :'data.frame':    349 obs. of  9 variables:
#>   ..$ Id             : chr [1:349] "1295508" "1126417" "1182404" "1299596" ...
#>   ..$ Class          : Factor w/ 2 levels "benign","malignant": 1 2 1 1 1 1 2 1 1 2 ...
#>   ..$ Marg.adhesion  : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 1 1 1 1 3 1 1 4 ...
#>   ..$ Epith.c.size   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 2 3 2 2 2 3 4 2 2 6 ...
#>   ..$ Mitoses        : Factor w/ 9 levels "1","2","3","4",..: 1 3 1 1 1 1 8 1 1 1 ...
#>   ..$ Bare.nuclei    : Factor w/ 10 levels "1","2","3","4",..: 4 4 1 1 1 NA 9 1 1 1 ...
#>   ..$ Cell.size      : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 6 1 1 1 1 3 1 1 4 ...
#>   ..$ Bl.cromatin    : Factor w/ 10 levels "1","2","3","4",..: 1 3 3 1 3 1 8 2 2 4 ...
#>   ..$ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 1 2 2 1 1 1 9 1 1 3 ...
#>  $ :'data.frame':    349 obs. of  9 variables:
#>   ..$ Id           : chr [1:349] "466906" "640712" "1258549" "1211202" ...
#>   ..$ Class        : Factor w/ 2 levels "benign","malignant": 1 1 2 2 1 2 1 1 2 1 ...
#>   ..$ Epith.c.size : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 2 2 10 10 2 10 2 2 4 2 ...
#>   ..$ Cl.thickness : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 9 7 1 10 4 5 5 1 ...
#>   ..$ Marg.adhesion: Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 10 10 1 2 1 1 10 1 ...
#>   ..$ Bl.cromatin  : Factor w/ 10 levels "1","2","3","4",..: 1 2 10 4 2 5 3 2 5 1 ...
#>   ..$ Cell.shape   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 10 10 3 10 1 1 10 1 ...
#>   ..$ Cell.size    : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 10 5 1 10 1 1 10 1 ...
#>   ..$ Bare.nuclei  : Factor w/ 10 levels "1","2","3","4",..: 1 1 10 10 1 10 1 1 10 1 ...
#>  $ :'data.frame':    349 obs. of  9 variables:
#>   ..$ Id             : chr [1:349] "646904" "1115293" "646904" "1321264" ...
#>   ..$ Class          : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 2 1 1 1 2 ...
#>   ..$ Bare.nuclei    : Factor w/ 10 levels "1","2","3","4",..: 1 2 1 1 4 2 1 1 1 4 ...
#>   ..$ Bl.cromatin    : Factor w/ 10 levels "1","2","3","4",..: 3 2 3 2 3 4 2 3 1 8 ...
#>   ..$ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 1 1 1 1 7 10 1 1 1 10 ...
#>   ..$ Epith.c.size   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 2 2 2 1 3 6 1 2 2 6 ...
#>   ..$ Cell.size      : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 1 2 8 8 1 1 1 10 ...
#>   ..$ Cell.shape     : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 1 2 8 8 3 1 1 7 ...
#>   ..$ Mitoses        : Factor w/ 9 levels "1","2","3","4",..: 1 1 1 1 1 4 1 1 1 2 ...
#>  $ :'data.frame':    349 obs. of  9 variables:
#>   ..$ Id             : chr [1:349] "1324572" "1026122" "1272166" "1201870" ...
#>   ..$ Class          : Factor w/ 2 levels "benign","malignant": 1 1 1 1 1 2 1 1 1 1 ...
#>   ..$ Cell.size      : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 1 1 2 10 1 2 1 1 ...
#>   ..$ Epith.c.size   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 2 2 2 1 2 4 2 2 2 2 ...
#>   ..$ Cl.thickness   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 5 2 5 4 4 10 2 3 6 4 ...
#>   ..$ Bare.nuclei    : Factor w/ 10 levels "1","2","3","4",..: 1 1 1 1 2 5 1 1 1 1 ...
#>   ..$ Bl.cromatin    : Factor w/ 10 levels "1","2","3","4",..: 2 1 1 2 3 8 3 1 1 1 ...
#>   ..$ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 2 1 1 1 1 10 1 1 1 1 ...
#>   ..$ Marg.adhesion  : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 1 3 1 6 1 3 3 3 ...
#>  $ :'data.frame':    349 obs. of  9 variables:
#>   ..$ Id             : chr [1:349] "1241232" "733823" "873549" "543558" ...
#>   ..$ Class          : Factor w/ 2 levels "benign","malignant": 1 2 2 2 2 2 1 2 2 1 ...
#>   ..$ Mitoses        : Factor w/ 9 levels "1","2","3","4",..: 1 1 3 1 8 1 1 2 1 1 ...
#>   ..$ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 1 1 5 10 9 8 7 10 10 1 ...
#>   ..$ Epith.c.size   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 2 2 3 4 4 6 4 3 4 2 ...
#>   ..$ Cell.shape     : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 4 6 5 3 8 5 4 7 10 1 ...
#>   ..$ Cl.thickness   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 3 5 10 6 8 9 5 6 8 3 ...
#>   ..$ Bl.cromatin    : Factor w/ 10 levels "1","2","3","4",..: 3 4 3 5 8 4 4 8 7 3 ...
#>   ..$ Bare.nuclei    : Factor w/ 10 levels "1","2","3","4",..: NA 10 7 5 9 10 5 10 4 1 ...
#>  $ :'data.frame':    349 obs. of  9 variables:
#>   ..$ Id             : chr [1:349] "1218105" "1212232" "673637" "1136142" ...
#>   ..$ Class          : Factor w/ 2 levels "benign","malignant": 2 1 1 1 2 1 2 1 1 1 ...
#>   ..$ Mitoses        : Factor w/ 9 levels "1","2","3","4",..: 5 1 1 1 1 1 2 1 1 1 ...
#>   ..$ Bare.nuclei    : Factor w/ 10 levels "1","2","3","4",..: 10 1 5 1 4 1 10 NA 2 1 ...
#>   ..$ Bl.cromatin    : Factor w/ 10 levels "1","2","3","4",..: 7 2 5 2 8 3 1 2 7 3 ...
#>   ..$ Cell.size      : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 10 1 1 1 10 1 3 1 1 1 ...
#>   ..$ Cell.shape     : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 10 1 1 1 10 2 5 3 1 1 ...
#>   ..$ Marg.adhesion  : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 9 1 1 1 6 1 4 1 1 3 ...
#>   ..$ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 10 1 1 1 5 1 6 1 1 1 ...
#>  $ :'data.frame':    349 obs. of  9 variables:
#>   ..$ Id             : chr [1:349] "1182404" "1343068" "1200847" "1253955" ...
#>   ..$ Class          : Factor w/ 2 levels "benign","malignant": 1 2 2 2 1 1 1 1 1 1 ...
#>   ..$ Cl.thickness   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 3 8 6 8 4 3 3 3 3 4 ...
#>   ..$ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 1 5 10 10 1 1 1 1 8 1 ...
#>   ..$ Epith.c.size   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 2 6 8 5 2 2 2 1 8 2 ...
#>   ..$ Cell.size      : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 4 10 7 1 1 1 2 1 1 ...
#>   ..$ Mitoses        : Factor w/ 9 levels "1","2","3","4",..: 1 2 7 1 1 1 1 1 1 1 ...
#>   ..$ Marg.adhesion  : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 10 4 1 1 1 1 3 1 ...
#>   ..$ Bl.cromatin    : Factor w/ 10 levels "1","2","3","4",..: 2 2 10 5 2 2 1 2 5 2 ...
#>  $ :'data.frame':    349 obs. of  9 variables:
#>   ..$ Id             : chr [1:349] "1287775" "1043068" "1303489" "846423" ...
#>   ..$ Class          : Factor w/ 2 levels "benign","malignant": 1 1 1 2 1 2 1 1 2 1 ...
#>   ..$ Epith.c.size   : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 2 2 2 4 3 2 2 2 5 2 ...
#>   ..$ Bare.nuclei    : Factor w/ 10 levels "1","2","3","4",..: 2 1 1 10 10 3 1 1 10 1 ...
#>   ..$ Cell.shape     : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 1 3 3 8 1 1 6 1 ...
#>   ..$ Cell.size      : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 1 6 3 5 1 1 6 1 ...
#>   ..$ Mitoses        : Factor w/ 9 levels "1","2","3","4",..: 1 1 1 4 3 5 1 1 3 1 ...
#>   ..$ Bl.cromatin    : Factor w/ 10 levels "1","2","3","4",..: 3 2 2 7 3 2 2 3 6 2 ...
#>   ..$ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 1 1 1 8 5 1 1 1 8 1 ...
#>  $ :'data.frame':    349 obs. of  9 variables:
#>   ..$ Id             : chr [1:349] "1031608" "867392" "1198641" "803531" ...
#>   ..$ Class          : Factor w/ 2 levels "benign","malignant": 1 1 2 2 1 1 2 1 1 1 ...
#>   ..$ Cell.shape     : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 2 6 10 3 1 5 2 1 1 ...
#>   ..$ Mitoses        : Factor w/ 9 levels "1","2","3","4",..: 1 1 2 1 1 1 1 1 1 1 ...
#>   ..$ Marg.adhesion  : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 1 3 10 1 1 8 1 3 6 ...
#>   ..$ Bl.cromatin    : Factor w/ 10 levels "1","2","3","4",..: 2 2 4 8 2 3 8 2 3 1 ...
#>   ..$ Normal.nucleoli: Factor w/ 10 levels "1","2","3","4",..: 1 1 3 5 3 2 6 1 1 1 ...
#>   ..$ Bare.nuclei    : Factor w/ 10 levels "1","2","3","4",..: 1 1 10 2 NA 1 10 1 1 1 ...
#>   ..$ Cell.size      : Ord.factor w/ 10 levels "1"<"2"<"3"<"4"<..: 1 2 10 10 4 1 6 1 1 1 ...


반응형
반응형

Subsetting

Subsetting 의 6가지 종류

x <- c(2.1, 4.2, 3.3, 5.4)
# 1.positive integer 
x[c(3, 1)] 
#> [1] 3.3 2.1

# 2. negative integer 
x[-c(3, 1)] 
#> [1] 4.2 5.4

# 3. Logical vector
x[c(TRUE, TRUE, FALSE, FALSE)] # logical vector 는 그 position 의 값이 TRUE 인 것만 반환한다.
#> [1] 2.1 4.2
x[c(TRUE, FALSE)] # 길이가 짧을 경우 반복해서 적용된다. 
#> [1] 2.1 3.3
x[x > 3]
#> [1] 4.2 3.3 5.4

# 4. nothing. 원래 값을 반환
x[] 
#> [1] 2.1 4.2 3.3 5.4

# 5. 0. zero-length vector를 반환한다. 
x[0] 
#> numeric(0)

# 6. character vector (name 이 있는 경우)
(y <- setNames(x, letters[1:4]))
#>   a   b   c   d 
#> 2.1 4.2 3.3 5.4
y[c("d", "c", "a")]
#>   d   c   a 
#> 5.4 3.3 2.1

z <- c(abc = 1, def = 2)
z[c("a", "d")] 
#> <NA> <NA> 
#>   NA   NA

List

a <- list(a = c(1,2,3), b = c("a", "b"), 
          c = array(c(1,2,3), c(1,3)))
a
#> $a
#> [1] 1 2 3
#> 
#> $b
#> [1] "a" "b"
#> 
#> $c
#>      [,1] [,2] [,3]
#> [1,]    1    2    3
a$a 
#> [1] 1 2 3
a[1] # []는 항상 list 를 반환한다.
#> $a
#> [1] 1 2 3
a[[1]] # 해당 위치의 element 를 반환한다. 
#> [1] 1 2 3

Matrices and array

a <- matrix(1:9, nrow = 3) 
colnames(a) <- c("A", "B", "C")  
a
#>      A B C
#> [1,] 1 4 7
#> [2,] 2 5 8
#> [3,] 3 6 9

# integer vector를 통한 subsetting 
a[1:2, ]
#>      A B C
#> [1,] 1 4 7
#> [2,] 2 5 8

# logical vector와 character vector (name attribute 가 있을 때) 를 통한 subsetting 
a[c(TRUE, FALSE, TRUE), c("B", "A")]
#>      B A
#> [1,] 4 1
#> [2,] 6 3
# 한 개의 vector를 통한 subsetting
(vals <- outer(1:5, 1:5, FUN = "paste", sep = ","))
#>      [,1]  [,2]  [,3]  [,4]  [,5] 
#> [1,] "1,1" "1,2" "1,3" "1,4" "1,5"
#> [2,] "2,1" "2,2" "2,3" "2,4" "2,5"
#> [3,] "3,1" "3,2" "3,3" "3,4" "3,5"
#> [4,] "4,1" "4,2" "4,3" "4,4" "4,5"
#> [5,] "5,1" "5,2" "5,3" "5,4" "5,5"

# 중간에 , 가 없는 경우 R 은 column 우선
vals[c(4, 15)] 
#> [1] "4,1" "5,3"

# matrix 또는 array 를 통한 subsetting
vals <- outer(1:5, 1:5, FUN = "paste", sep = ",")
select <- matrix(ncol = 2, byrow = TRUE, c(
  1, 1,
  3, 1,
  2, 4
))
vals[select]
#> [1] "1,1" "3,1" "2,4"

# 위 방법은 이 결과와 같음 
c(vals[1, 1], vals[3, 1], vals[2, 4])
#> [1] "1,1" "3,1" "2,4"

Data frame

df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3])

# Raw subsetting 
df[df$x == 2, ]
#>   x y z
#> 2 2 2 b
df[c(1, 3), ]
#>   x y z
#> 1 1 3 a
#> 3 3 1 c

# Column subsetting
# Data frame은 matrix 와 list 의 성질을 동시에 갖는다.
df[c("x", "z")] # list 처럼 다루는 방법
#>   x z
#> 1 1 a
#> 2 2 b
#> 3 3 c
df[, c("x", "z")] # matrix 처럼 다루는 방법
#>   x z
#> 1 1 a
#> 2 2 b
#> 3 3 c

str(df["x"]) # list 로 subset 된다. 
#> 'data.frame':    3 obs. of  1 variable:
#>  $ x: int  1 2 3
str(df[, "x"]) # vector 로 subset 된다. 
#>  int [1:3] 1 2 3

S3 Object

  • S3 object 는 atomic vector, list, array 위에서 만들어짐. 따라서 위의 방법들을 활용할 수 있음.
  • str 을 사용해 S3 object 의 구조를 파악하고 위 방법들을 똑같이 활용할 수 있음

Excercise : S3 Object subsetting

  • lm fitting 후 r square 추출하기
library(pryr)
mod <- lm(mpg ~ wt, data = mtcars)
summ <- summary(mod)
attributes(summ)
#> $names
#>  [1] "call"          "terms"         "residuals"     "coefficients" 
#>  [5] "aliased"       "sigma"         "df"            "r.squared"    
#>  [9] "adj.r.squared" "fstatistic"    "cov.unscaled" 
#> 
#> $class
#> [1] "summary.lm"
summ$r.squared
#> [1] 0.7528328

Simplifying vs. Preserving subsetting

  • Simplifying : subsetting 시 output을 최대한 간단하게 표현하는 것
  • Preserving : subsetting 시 output 을 input 과 같게 유지하는 것

Vector

x <- c(a = 1, b = 2) 
x[1] # preserving 
#> a 
#> 1
x[[1]] # simplifying. name attribute 를 버린다. 
#> [1] 1

List

y <- list(a = 1, b = 2)
y[1] # preserving
#> $a
#> [1] 1
y[[1]] # simplifying
#> [1] 1
y$a # simplifying
#> [1] 1

Factor

z <- factor(c("a", "b")) 
z[1] # preserving
#> [1] a
#> Levels: a b
z[1, drop = TRUE] # simplifying
#> [1] a
#> Levels: a

Matrix

a <- matrix(1:4, nrow = 2)
a[1, , drop = FALSE] # preserving
#>      [,1] [,2]
#> [1,]    1    3
a[1, ] # simplifying
#> [1] 1 3

Data frame

df <- data.frame(a = 1:2, b = 1:2)
df[1] # preserving
#>   a
#> 1 1
#> 2 2
df[[1]] # simplifying
#> [1] 1 2
df[, "a", drop = FALSE] # preserving
#>   a
#> 1 1
#> 2 2
df[, "a"] # simplifying
#> [1] 1 2
df$a
#> [1] 1 2

$와 [[]] 의 차이

var <- "cyl" 
mtcars$var 
#> NULL

[[]] 는 column 명을 변수로 줄 수 있음

# Instead use [[
mtcars[[var]]
#>  [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4

$ 는 partial matching 을 사용함 [[]] 는 안됨

x <- list(abc = 1) 
x$a # $ does partial matching 
#> [1] 1

Subsetting and assignment

  • R 에서는 subset 하는 것처럼 값을 할당할 수 있음
x <- 1:5
x[c(1, 2)] <- 2:3
x
#> [1] 2 3 3 4 5

Excercise

Boostrap + Random feature selection 구현하기 - 전체 샘플의 50 % 크기를 중복을 허용해 추출하고, 열의 70 % 를 임의로 골라 Subsetting 해보기. (ID, Class 열 제외)

library(mlbench)
data("BreastCancer")
head(BreastCancer)
#>        Id Cl.thickness Cell.size Cell.shape Marg.adhesion Epith.c.size
#> 1 1000025            5         1          1             1            2
#> 2 1002945            5         4          4             5            7
#> 3 1015425            3         1          1             1            2
#> 4 1016277            6         8          8             1            3
#> 5 1017023            4         1          1             3            2
#> 6 1017122            8        10         10             8            7
#>   Bare.nuclei Bl.cromatin Normal.nucleoli Mitoses     Class
#> 1           1           3               1       1    benign
#> 2          10           3               2       1    benign
#> 3           2           3               1       1    benign
#> 4           4           3               7       1    benign
#> 5           1           3               1       1    benign
#> 6          10           9               7       1 malignant

num_row <- nrow(BreastCancer)
num_col <- length(BreastCancer)

raws_to_keep <- sample(num_row, 0.5 * num_row, replace = TRUE) # integer vector 를 통한 subsetting 

cols_names <- colnames(BreastCancer)
features <- cols_names[!(cols_names %in% c('Id', "Class"))] # logical vector를 통한 subsetting 
cols_to_keep <- c('Id', "Class", sample(features, 0.7 * num_col)) # character vector 를 통한 subsetting 

BreastCancerSample <- BreastCancer[raws_to_keep, cols_to_keep]
head(BreastCancerSample)
#>          Id     Class Mitoses Cell.size Normal.nucleoli Cell.shape
#> 513 1299994    benign       1         1               1          1
#> 374  521441    benign       1         1               1          1
#> 433 1277629    benign       1         1               2          1
#> 444  734111    benign       1         1               1          1
#> 649 1315807 malignant      10        10              10         10
#> 180 1202812 malignant       1         3               1          3
#>     Cl.thickness Epith.c.size Bl.cromatin
#> 513            5            2           1
#> 374            5            2           2
#> 433            5            2           2
#> 444            1            2           1
#> 649            5           10          10
#> 180            5            6           3


반응형
반응형

R의 데이터 구조


  • 프로그램에서 다양한 타입의 데이터를 변수에 저장하는 것이 필요하다.
  • 운영체제는 사용자가 지정한 데이터 타입에 맞게 변수에 메모리를 할당한다. 
  • R의 기본 데이터 타입 (base object) 은 Vector (atomic vector 와 list) 이고, 이를 기반으로 구현된 아래의 데이터 구조가 많이 사용된다.
    • Atomic vector
    • List
    • Matrix
    • Array
    • Factors
    • Data frame

모든 데이터 타입은 Vector 로부터 시작

Matrix 와 Array 는 atomic vector 를 기반으로 만들어진 base object 이다. 
Factor 와 Data frame 은 각각 atomic vector와 list 에 기반한 S3 클래스이다. 

Two types of Vector

  • Vector 는 Atomic vector 와 list 로 나눌 수 있다.
  • Atomic vector 는 원자가 동질적이고, list 는 이질적일 수 있다.
  • Atomic vector 는 1차원 구조이지만 list 는 다차원일 수 있다 (nested structure).

Atomic vector

  • 네 가지 atomic vector (logical, integer, double, character)
  • raw와 complex 도 있지만 잘 사용하지 않는다.
lgl_var <- c(TRUE, FALSE)
int_var <- c(1L, 6L, 10L)
dbl_var <- c(1, 2.5, 4.5)
chr_var <- c("these are", "some strings")
str(lgl_var)
##  logi [1:2] TRUE FALSE
str(int_var)
##  int [1:3] 1 6 10
str(dbl_var)
##  num [1:3] 1 2.5 4.5
str(chr_var)
##  chr [1:2] "these are" "some strings"

Coercion

  • 만약 vector에 다른 타입의 데이터가 들어가면 강제로 형변환된다 (Coercion).
  • 이 때, 우선순위는 logical, numeric, double, character 이다.
c(1, "a")
## [1] "1" "a"
c(TRUE, 1)
## [1] 1 1
c(1, 1.1)
## [1] 1.0 1.1
c(3.0, TRUE)
## [1] 3 1
c(c(1,2,3), 1)
## [1] 1 2 3 1
c(list(c(1,2,3)), "a") 
## [[1]]
## [1] 1 2 3
## 
## [[2]]
## [1] "a"

List

  • List 는 원자가 어떤 타입이든 될 수 있고, list 도 될 수 있다.
x <- list(1:3, "a", c(TRUE, FALSE, TRUE), c(2.3, 5.9))
str(x)
## List of 4
##  $ : int [1:3] 1 2 3
##  $ : chr "a"
##  $ : logi [1:3] TRUE FALSE TRUE
##  $ : num [1:2] 2.3 5.9
x <- list(list(list(list())))
str(x)
## List of 1
##  $ :List of 1
##   ..$ :List of 1
##   .. ..$ : list()

Excercises (Vector, list)

  1. atomic vector 의 6가지 종류는 무엇이고 list 와의 차이점은 무엇인가?
  • integer, double, character, complex, raw, boolean.
  • homogenous vs heterogenous
  • 1d vs nested structure
  1. 아래 코드의 결과는?
c(1, FALSE)     
## [1] 1 0
c("a", 1)        
## [1] "a" "1"
c(list(1), "a")  
## [[1]]
## [1] 1
## 
## [[2]]
## [1] "a"
c(TRUE, 1L)     
## [1] 1 1
  1. list 를 vector 로 변환하기 위해 사용하는 unlist 는 무엇을 하는가?
  • coercion, nested structure 제거, 1d structure 로 변환
a <- list(c(1,2,3,4,5), c(1,2,3))
a
## [[1]]
## [1] 1 2 3 4 5
## 
## [[2]]
## [1] 1 2 3
unlist(a)
## [1] 1 2 3 4 5 1 2 3

Attributes

  • 모든 object 는 attribute를 가질 수 있다.
  • attr 함수를 통해 attribute를 지정하거나 조회할 수 있다.
  • attribute가 중요한 이유는
    • attribute 를 통해 데이터의 구조를 설정하기도 한다.
    • 많은 함수에서 attribute 에 따른 기능을 구현한다.
    • attribute는 R 에서 객체를 구현하는 한 가지 방법이다.
    • 새로운 해결 전략을 만들 수도 있고, 문제를 해결하는데 시간을 단축할 수 있다.

Attribute 생성 방법

  1. attr 함수
y <- 1:10
attr(y, "my_attribute") <- "This is a vector"
attr(y, "my_attribute")
## [1] "This is a vector"
  1. structure 함수
    • attirubte가 추가된 새로운 object를 반환한다.
structure(1:10, my_attribute = "This is a vector")
##  [1]  1  2  3  4  5  6  7  8  9 10
## attr(,"my_attribute")
## [1] "This is a vector"
  • attribute 조회
  • attributes 함수를 이용하면 list 형태로 반환
attributes(y)
## $my_attribute
## [1] "This is a vector"

세 가지 중요한 attribute

  • 아래 attributes 는 R 기본 함수에서 사용법이 약속 되어있다.
    • Names : 각 element 에 지정될 수 있는 chracter vector 이다.
    • Dimensions : Matrix 와 Array에서 쓰인다.
    • Class : S3 object system 에서 쓰인다.
  • 데이터를 변형해도 이 attribute 는 사라지지 않는다.
  • 다양한 함수에서 이 attribute 를 통해 기능을 구현하고 있다.

Names

  • name attribute를 만드는 법은 1) names 함수를 이용 하는 방법, 2) 변수를 생성할 때 지정하는 방법이 있다.
  • names 함수는 object의 name 을 만들거나 조회하는 함수이다.

  • names 함수를 이용하는 방법

v <- c(1, 2, 3)
names(v) <- c('a')
names(v)
## [1] "a" NA  NA
  • 변수를 생성할 때 지정
y <- c(a = 1, 2, 3) 
names(y)
## [1] "a" ""  ""
  • attr 함수를 통해서도 name 확인 가능
attr(y, "names")
## [1] "a" ""  ""

Factors

  • Factor는 integer vector 를 기반으로한 S3 object 이다.
  • Factor는 ’미리 정한 값’만 인자로 넣을 수 있는 Vector 로 볼 수 있다.
  • Factor는 범주형 변수를 저장할 때만 쓰인다.
  • Factor의 중요한 두 개의 attributes,
    • class : 이것은 factor 가 factor 임을 알려주고, integer vector 와 다른 쓰임을 갖게 한다.
    • levels : level은 ‘미리 정한 값’ 을 정의한다.
library(pryr)
x <- factor(c("a", "b", "b", "a"))
x
## [1] a b b a
## Levels: a b
typeof(x) # factor 의 base type 은 integer vector 이다.
## [1] "integer"
otype(x)  # factor 는 S3 object 이다.
## [1] "S3"
class(x)  
## [1] "factor"
str(x)
##  Factor w/ 2 levels "a","b": 1 2 2 1
# getAnywhere(str.default) # factor의 str은 default 로 들어가며, if 문에서 is.factor 로 체크가 된다. 
class(x)
## [1] "factor"
levels(x) # levels 함수는 해당 객체의 levels attribute 를 반환하는 함수이다. 
## [1] "a" "b"
  • factor는 interger vector 의 확장 버전이다.
# Factor 를 c 로 결합하면 강제 형변환 (coercion) 되면서, integer vector 로 변한다.
c(factor(c("a", "b", "c")), factor("b")) 
## [1] 1 2 3 1
  • factor는 범주형 변수에서처럼 값이 한정적일 때 levels를 미리 정의할 수 있다.
sex_char <- c("m", "m", "m") 
sex_factor <- factor(sex_char, levels = c("m", "f")) 
table(sex_char) 
## sex_char
## m 
## 3

Excercise (Attributes, Names, Factor)

  • “test” attribute 를 갖는 vector 생성하기
a <- 1:5 
attr(a, "test") <- "my attribute" 
a
## [1] 1 2 3 4 5
## attr(,"test")
## [1] "my attribute"
a <- structure(1:5, test = "my attribute") 
a 
## [1] 1 2 3 4 5
## attr(,"test")
## [1] "my attribute"

“comment” attribute 만들고 출력해보기

b <- structure(1:5, comment = "my attribute")
b # print.default 함수에서 comment attribute는 출력이 안되도록 구현 
## [1] 1 2 3 4 5
# ?attributes 
# Note that some attributes (namely class, comment, dim, dimnames, names, row.names and tsp) are treated specially and have restrictions on the values which can be set. 

Matrices and arrays

  • dim attribute 를 atomic vector 에 추가하면 배열 (array) 이 된다.
  • Matrix 는 array 의 한 종류로 2차원 array 이다.
  • array 는 atomic vector에 dimension 이 할당된 것이다.
# Matrix 

# 2x3 matrix 생성
a <- matrix(1:6, ncol = 3, nrow = 2)
a
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
# 2x3x2 array 생성
b <- array(1:12, c(2, 3, 2)) 
b
## , , 1
## 
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
## 
## , , 2
## 
##      [,1] [,2] [,3]
## [1,]    7    9   11
## [2,]    8   10   12
# dim attribute 를 추가함으로써 array 를 생성할 수도 있다. 
d <- 1:6
dim(d) <- c(3, 2)
d
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6
e <- 1:6
attr(e, "dim") <- c(3,2)
e
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6
  • matrix, array 의 특성
library(pryr)

# matrix, array 의 base object 는 numeric vector 이다. 
typeof(a)
## [1] "integer"
typeof(b)
## [1] "integer"
typeof(d)
## [1] "integer"
# matrix, array는 base object 이다. 
otype(a) 
## [1] "base"
otype(b) 
## [1] "base"
otype(d) 
## [1] "base"
# matrix, array 는 atomic vector에 dim attribute 가 추가된 것이다.  
attributes(a)
## $dim
## [1] 2 3
attributes(b)
## $dim
## [1] 2 3 2
attributes(d)
## $dim
## [1] 3 2
  • Charcter array 도 생성 가능
a <- array(c("a", "b", "c", "d", "e", "f"), c(2,3))
a
##      [,1] [,2] [,3]
## [1,] "a"  "c"  "e" 
## [2,] "b"  "d"  "f"
  • rownames와 colnames는 matrix 의 name attribute 를 설정하기 위해 사용됨
rownames(a) <- c("A", "B")
colnames(a) <- c("a", "b", "c")
a
##   a   b   c  
## A "a" "c" "e"
## B "b" "d" "f"
attributes(a)
## $dim
## [1] 2 3
## 
## $dimnames
## $dimnames[[1]]
## [1] "A" "B"
## 
## $dimnames[[2]]
## [1] "a" "b" "c"
  • length 는 전체 원소의 갯수를 출력함
  • array 는 integer vector 기반이므로 원소의 갯수 출력
length(a) 
## [1] 6
  • dimnames 를 통해 array 에서 각 dimension 에 대한 변수의 이름을 설정할 수 있음
dimnames(b) <- list(c("one", "two"), c("a", "b", "c"), c("A", "B"))
b
## , , A
## 
##     a b c
## one 1 3 5
## two 2 4 6
## 
## , , B
## 
##     a  b  c
## one 7  9 11
## two 8 10 12

Dataframe

  • Dataframe은 R 에서 데이터 분석 시 가장 일반적으로 사용되는 데이터구조
  • Dataframe 은 list 를 기반으로 만들어진 S3 클래스이다.
    • 2-dimensional structure
    • 일반적으로 각 element 는 equal-length vector
  • Dataframe은 matrix 와 list 의 성질을 동시에 갖음

data frame 의 생성

df <- data.frame(x = 1:3, y = c("a", "b", "c"))
df
##   x y
## 1 1 a
## 2 2 b
## 3 3 c

Data frame 의 성질

library(pryr)
otype(df) # data frame은 list 를 기반으로 만들어진 s3 클래스이다. 
## [1] "S3"
typeof(df) # data frame의 base object는 list 이다. 
## [1] "list"
class(df) # data frame의 s3 클래스 이름은 data.frame 이다. 
## [1] "data.frame"
dim(df) # data frame 은 matrix 의 성질을 갖는다. 
## [1] 3 2
  • data frame 에서는 character vector를 자동으로 factor 로 변형한다.
# 이를 해제하기 위해서는 stringAsFactors = FALSE 로 설정한다.
df <- data.frame(
  x = 1:3,
  y = c("a", "b", "c"),
  stringsAsFactors = FALSE)
str(df)
## 'data.frame':    3 obs. of  2 variables:
##  $ x: int  1 2 3
##  $ y: chr  "a" "b" "c"

Data frame 의 병합

cbind(df, data.frame(z = 3:1))
##   x y z
## 1 1 a 3
## 2 2 b 2
## 3 3 c 1
rbind(df, data.frame(x = 10, y = "z"))
##    x y
## 1  1 a
## 2  2 b
## 3  3 c
## 4 10 z

Special columns

  • 사용하는 경우는 많이 없지만 개념적으로 가능함
df <- data.frame(x = 1:3)
df$y <- list(1:2, 1:3, 1:4)
df
##   x          y
## 1 1       1, 2
## 2 2    1, 2, 3
## 3 3 1, 2, 3, 4
  • data frame 생성 시 컬럼으로 넣으려면 I 키워드를 통해 가능하다.
dfl <- data.frame(x = 1:3, y = I(list(1:2, 1:3, 1:4)))
dfl
##   x          y
## 1 1       1, 2
## 2 2    1, 2, 3
## 3 3 1, 2, 3, 4
dfl$y[[1]]
## [1] 1 2
  • matrix 도 data frame 의 컬럼으로 추가할 수 있다.
dfm <- data.frame(x = 1:3, y = I(matrix(1:9, nrow = 3)))
dfm
##   x y.1 y.2 y.3
## 1 1   1   4   7
## 2 2   2   5   8
## 3 3   3   6   9
dfm[2, "y"]
##      [,1] [,2] [,3]
## [1,]    2    5    8

Excercise (Data frame)

  • Data frame은 어떤 attribute 를 갖는가?
df <- data.frame(
  x = 1:3,
  y = c("a", "b", "c"),
  stringsAsFactors = FALSE)
attributes(df)
## $names
## [1] "x" "y"
## 
## $class
## [1] "data.frame"
## 
## $row.names
## [1] 1 2 3


반응형
반응형



Jupyter Lab – 단축키와 매직 기능

단축키 (Shortcut) 을 숙지하고 잘 사용할 수 있다면 더욱 생산적인 작업을 가능하게 할 것입니다. 본 포스팅에서는 자주 사용하는 Jupyter lab 의 단축키를 정리해보고자 합니다. 예를 들어, 주피터 랩에서는 새로운 cell 을 만들거나 작업이 끝난 cell 을 삭제할 일이 많습니다. 단축키를 사용하면, 단축키를 사용하지 않고 키보드를 통해 새로운 cell 을 추가하고 삭제하는 것보다 훨씬 빠르게 작업을 할 수 있습니다. 그리고 외우는데 시간이 오래걸리지도 않습니다!

Jupyter Lab의 단축키

  1. ESC  를 눌러 커맨드 모드로 진입하여 ENTER 를 통해 cell 을 수정할 수 있습니다. 아래 커맨드는 커맨드 모드에서 동작합니다. 
  2. A 는 현재 cell 위에 새로운 cell 을 추가합니다.
  3. B 는 현재 cell 밑에 새로운 cell 을 추가합니다.
  4. D + D D를 연속해서 두번 누르면 현재 cell 을 삭제합니다. 
  5. M 은 Markdown 셀로 변환하며, Y 는 Code 셀로 변환하고  R 은 Raw Cell 로 변환합니다.
  6. CTRL + B 화면을 더 크게 사용할 수 있습니다. 왼쪽 파일 탐색기가 사라집니다.
  7. SHIFT + M 두 개의 셀을 한개의 셀로 Merge 합니다.
  8. CTRL + SHIFT + – 현재 커서 위치를 기준으로 두 개의 셀로 구분합니다. 
  9. SHIFT+J or SHIFT + DOWN 현재 셀에서 아래쪽 위치로 새로운 셀을 같이 선택합니다. 
  10. SHIFT + K or SHIFT + UP 현재 셀에서 위쪽 위치로 새로운 셀을 같이 선택합니다. 
  11. CTRL + / 선택한 코드를 주석처리합니다.

기타 잘 알려지지 않은 단축키

  1. CTRL+D: 한줄 삭제하는 단축키입니다. 
  2. CTRL+[ or CTRL+]: 단체 indentation / Tab과 Shift+Tab 으로도 가능하지만, text editor 에서는 Shift+tab 이 안먹혀서 CTRL+[ 을 쓰면 유용합니다.  

Jupyter Lab 매직 기능 (Magic function)

매직 기능은 Ipython kernel 에서 제공하는 것이며, Jupyter lab 과 Jupyhter notebook 모두에서 작동합니다. 이는 어떤 언어를 선택하든 (예를 들어, Jupyter lab 에서 R을 사용하든 Python 을 사용하든) 동작합니다.

  1. %matplotlib inline 플롯을 화면 안에서 보여준다.
  2. %lsmagic 매직 기능에 어떤것들이 있는지 출력해준다. 
  3. %env 

    • %env 모든 환경변수를 출력한다.
    • %env var 해당 이름의 환경변수를 출력한다.
    • (%env var val) or (%env var=val) 환경변수를 설정한다.
  4. %run 

    • %run file_name 해당 이름의 .py 파일 또는 .ipynb 파일을 셀 안에서 실행한다. 
  5. %load 

    • %load source 해당 파일을 셀 안에 로드한다.
  6. %who = will list all variables that exist in the global scope. It can be used to see what all data_frames or any other variable is there in memory. 
    • %who: 현재 전역 환경의 모든 변수를 리스트한다. (메모리에 어떤 변수들이 올라와 있나 확인할 수 있다.)
    • %who df: 현재 선언된 dataframe 을 볼 수 있다. 
    • %whos: %who 와 비슷하지만 각 변수들에 대해 상세한 설명을 볼 수 있다. 
  7. %time 한 셀이 실행된 시간을 볼 수 있다. 
  8. %timeit 10만 번 실행하여 평균 시간을 잰다. 
  9. %writefile 

    • %writefile file_name 해당 파일의 셀의 아웃풋을 쓴다.
    • %writefile -a file_name 해당 파일의 셀의 아웃풋을 덧붙인다.

Jupyter configuration file (환경설정) 변경하기

주피터 랩에서는 print 문을 쓰지 않고 어떤 변수를 cell 에 입력했을 때, 가장 마지막 줄만 실행해서 결과로 내보냅니다. 이것이 불편하다면 아래 문장을 실행하면 됩니다. 개인적으로 유용하다고 생각합니다. 이렇게 하면 일일히 print 문으로 감싸줄 필요가 없게 됩니다.   

from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

하지만 위 코드를 실행하는 것은 영구적으로 설정을 변경하지는 않는데, 영구적으로 설정을 변경하기 위해서는 configuration file 을 변경하면 됩니다. 이에 대한 설명은 해당 링크를 참고하시면 됩니다. configuration file 을 찾은 후에 아래 코드를 삽입해줍니다.

c.InteractiveShell.ast_node_interactivity = "all"

이외에도 주피터 랩에서는 더 많은 단축키와 매직 기능이 있습니다.



반응형
반응형

해들리 위컴은 어떻게 수많은 R 패키지를 개발할 수 있었을까?


R 에는 수많은 패키지들이 존재한다. 이들 중 어떤 패키지를 활용해서 데이터를 분석하고 결과를 도출해야 할까? 힘드게 문법을 읽힌 패키지가 아무도 쓰지 않는 패키지 였다면? 개발자가 더 이상 유지보수를 하지 않는다면? 다른 패키지의 함수들과 호환이 되지 않는다면? 우리의 시간은 제한되어 있기에 패키지 선정에 신중해야하며 위험이 존재하기도 한다. R packages for data science 라는 슬로건을 내걸은 Tidyverse 는 R 유저들에게 고민에 빠진 R 유저들에게 그 방법을 제시한다. Tidyverse 는 같은 철학은 공유하는 데이터과학을 위한 R 패키지들의 모임이며, 함께 사용할 수 있도록 디자인되었다. 


Hadleyverse 라고도 불리는 Tidyverse 패키지들


놀라운 것은 tidyverse 에 속한 패키지들의 많은 부분이 한 명에 의해 개발되었다는 것이다. 해들리위컴은 R 패키지 그룹인 Tidyverse 에 속한 dplyr, readr, tidyr, ggplot 와 같은 패키지들의 개발자로 유명하며, 그가 R 에 기여한 공헌도는 어마어마하다. 특히 tidy data 의 개념과  Leland Wilkinson 의 The Grammar of Graphics 을 구현한 층층이 쌓아 그래프를 그리는 ggplot2 는 가장 유명하다. 


2014년 Quora 에 어떻게 해들리 위컴이 이렇게 많이 R 에 기여할 수 있었는지에 대해 질문이 올라왔는데, 해들리 위컴이 직접 답변을 달았다. 그 내용은 아래와 같다. 


1. 글쓰기 (Writing)


그는 매일 아침 60~90분 동안 글쓰기를 한다. 아침에 일어나서 가장 먼저 하는 것이 글을 쓰는 것이다. 그가 글 쓰기를 중요시하는 첫 번재 이유는 직접 쓴 글은 스스로 참고하는데 유용하기 때문이다. 예를 들어, 그는 C++ 을 항상 코딩하는 것이 아니기 때문에, 참조가 필요한데 자신이 쓴 Rcpp 에 대한 글을 참조한다고 한다. 두 번째로, 글쓰기는 지식과 Tool 의 갭을 매울 수 있고, 이는 새로운 문제를 해결하는 방법을 고안하는데 도움이 된다고 한다. 


2. 읽기 (Reading)


그는 많이 읽는다. 약 300개의 blog 를 follow 하고 있고, Twitter 나 Stack Overflow 의 R tag 가 달린 글을 자주 본다고 한다. 물론 대부분의 글은 자세히 보지는 않지만, 이러한 수많은 글에 스스로를 노출시킴으로써 기술적인 변화를 따라가는데 도움되며, 새로운 프로그래밍 언어와 다른 사람들이 R 을 통해 데이터를 다루는 방법을 꾸준히 보고 어떠한 문제가 생겼을 때, 그 '이름' 을 빠르게 인식할 수 있다. 따라서 googling 등을 통해 가능한 해결법을 찾는 것을 더욱 빠르게 한다. 


3. 청킹 (Chunking)


문맥의 전환 (Coxtext switching)  은 매우 비싼 것이다. 많은 패키지들을 한 번에 작업하면, 이도저도 아니게 된다. 따라서 그는 대부분의 시간에서는 자신의 패키지들에 관한 어떠한 이슈나 아이디어를 점점 쌓아가고, 그렇게 쌓인 것 많아졌을 때 쯤, 그 패키지를 개선하는 작업에 며칠 간의 시간을 보낸다. 


마지막으로 그는 오래전부터 R 패키지 개발에 시간을 보냈기 때문에 그러한 경험이 축적되어 새로운 패키지를 순식간에 만들어낼 수 있는 지식과 경험을 보유하고 있다고 할 수 있다. 그의 학위 논문도 데이터를 exploration 하고 modeling 하는 R 패키지에 관한 것이었다. (http://had.co.nz/thesis/) 결국 그가 지금까지 R 커뮤니티에 공헌한 것들은 이러한 수년 간의 경험과 지식의 정리, 그리고 기술적 진보를 이해하는데 들인 노력의 산물이라고 할 수 있을 것이다.  


참고

https://www.quora.com/How-is-Hadley-Wickham-able-to-contribute-so-much-to-R-particularly-in-the-form-of-packages

반응형
반응형


Apply 계열 함수 정리


 Overview

FunctionDescription
applyApply functions over array margins
byApply a function to a data frame split by factors
eapplyApply a function over values in an environment
lapplyApply a function over a list or vector
mapplyApply a function to multiple list or vector arguments
rapplyRecursively apply a function to a list
tapplyApply a function over a ragged array

출처 - https://csgillespie.github.io/efficientR/programming.html



  • 데이터를 다룰 때, 원자별, 그룹별로 함수를 적용할 경우가 많다. 
  • Apply 계열의 함수는 데이터 구조를 갖는 R object 를 input 으로 받아 원소 별 혹은 그룹별로 함수를 적용시키는 것
  • input 과 output 의 형태에 따라 여러 종류로 나뉜다.
    • apply (input : array, output : array)
    • lapply (input : list or vector, output : list)
    • sapply (input : list or vector, output : vector or array)
    • vapply (input : list or vector, output : vector or array)
    • tapply (input : list or vector and factor, output : vector or array)
    • mapply (input : list or vector, output : vector or array)

apply

x <- matrix(1:9, c(3,3))  
x 
#>      [,1] [,2] [,3]
#> [1,]    1    4    7
#> [2,]    2    5    8
#> [3,]    3    6    9
  • Apply 함수는 행렬의 행 또는 열 방향으로 특정 함수를 적용한다.
  • apply(array, 방향, 함수)
  • 1: 행, 2: 열
apply(x, 1, function(x) {2*x}) 
#>      [,1] [,2] [,3]
#> [1,]    2    4    6
#> [2,]    8   10   12
#> [3,]   14   16   18
# apply 함수는 vector 에 적용할 수 없다. 
# dim attribute 가 있어야 함
# apply(c(1,2,3), 1, function(x) {2*x}) 
# 에러 출력

lapply

  • apply 함수의 단점은 input 으로 array 만 입력할 수 있다는 것이다. 
  • 일반적으로 vector 를 input 넣는 경우가 많은데, 이를 위해 lapply 가 존재한다. 
  • 입력으로 vector 또는 list 를 받아 list 를 반환한다.
result <- lapply(1:3, function(x) x*2)
result
#> [[1]]
#> [1] 2
#> 
#> [[2]]
#> [1] 4
#> 
#> [[3]]
#> [1] 6
unlist(result) # vector 로 바꾸고 싶으면 unlist 
#> [1] 2 4 6
  • 데이터 프레임에도 lapply 를 적용할 수 있다.
  • 데이터 프레임은 list 에 기반한 s3 object 이기 때문
head(iris)
#>   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
typeof(iris)
#> [1] "list"
lapply(iris[, 1:4], mean)
#> $Sepal.Length
#> [1] 5.843333
#> 
#> $Sepal.Width
#> [1] 3.057333
#> 
#> $Petal.Length
#> [1] 3.758
#> 
#> $Petal.Width
#> [1] 1.199333
y <- lapply(iris[, 1:4], function(x) {x > 3})
head(lapply(y, function(x) x[1:5]))
#> $Sepal.Length
#> [1] TRUE TRUE TRUE TRUE TRUE
#> 
#> $Sepal.Width
#> [1]  TRUE FALSE  TRUE  TRUE  TRUE
#> 
#> $Petal.Length
#> [1] FALSE FALSE FALSE FALSE FALSE
#> 
#> $Petal.Width
#> [1] FALSE FALSE FALSE FALSE FALSE

sapply

  • sapply 는 list 대신 행렬 or 벡터로 반환한다.
  • lapply는 list 를 반환하므로 list 를 다시 unlist 하는 것이 번거롭다.
  • lapply 의 wrapper 이다.
y <- sapply(iris[,1:4], function(x) {x > 3})
typeof(y) # Logical matrix 로 반환한다.
#> [1] "logical"
class(y)
#> [1] "matrix"
head(y)
#>      Sepal.Length Sepal.Width Petal.Length Petal.Width
#> [1,]         TRUE        TRUE        FALSE       FALSE
#> [2,]         TRUE       FALSE        FALSE       FALSE
#> [3,]         TRUE        TRUE        FALSE       FALSE
#> [4,]         TRUE        TRUE        FALSE       FALSE
#> [5,]         TRUE        TRUE        FALSE       FALSE
#> [6,]         TRUE        TRUE        FALSE       FALSE
z <- sapply(iris[, 1:4], mean)
z
#> Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
#>     5.843333     3.057333     3.758000     1.199333

vapply

  • FUN.VALUE argument 에 output format 을 명확히 정의해서 더 안전함
y <- vapply(iris[, 1:4], function(x) {x > 3}, numeric(length(iris[, 1]))) # numeric vector 로의 반환 
typeof(y)
#> [1] "double"
class(y)
#> [1] "matrix"
head(y)
#>      Sepal.Length Sepal.Width Petal.Length Petal.Width
#> [1,]            1           1            0           0
#> [2,]            1           0            0           0
#> [3,]            1           1            0           0
#> [4,]            1           1            0           0
#> [5,]            1           1            0           0
#> [6,]            1           1            0           0

z <- vapply(iris[, 1:4], function(x) {x > 3}, logical(length(iris[, 1]))) # logical vector 로의 반환 
typeof(z)
#> [1] "logical"
class(z)
#> [1] "matrix"
head(z)
#>      Sepal.Length Sepal.Width Petal.Length Petal.Width
#> [1,]         TRUE        TRUE        FALSE       FALSE
#> [2,]         TRUE       FALSE        FALSE       FALSE
#> [3,]         TRUE        TRUE        FALSE       FALSE
#> [4,]         TRUE        TRUE        FALSE       FALSE
#> [5,]         TRUE        TRUE        FALSE       FALSE
#> [6,]         TRUE        TRUE        FALSE       FALSE

tapply

  • tapply 는 그룹별 처리를 위한 함수이다. 
  • 그룹을 인자로 주고 (factor 형), 원소별 처리가 아니라 그룹별 처리를 함 
tapply(1:10, rep(c(1,2), each=5), sum) # 그룹 인자는 알아서 factor 형으로 변환이 됨 
#>  1  2 
#> 15 40
  • iris 데이터에서 Species 별로 Sepal.Length 의 평균 구하기
tapply(iris$Sepal.Length, iris$Species, mean)
#>     setosa versicolor  virginica 
#>      5.006      5.936      6.588

mapply

  • sapply 와 비슷하지만 여러개의 인자를 넘긴다.
mapply(function(i, s) {
       sprintf(" %d%s ", i, s)
}, 1:3, c("a", "b", "c"))
#> [1] " 1a " " 2b " " 3c "
  • iris 데이터에서 Sepal.Length 와 Sepal.Width 를 합친 새로운 변수 생성
  • 인자가 몇 개 올지 모르므로, 앞선 함수들에서는 data 를 먼저 인자로 넘겼지만 mapply 에서는 함수를 먼저 인자로 넘긴다.
y <- mapply(`+`, iris$Sepal.Length, iris$Sepal.Width)
# 위 코드는 mapply(function(a, b) a+b, iris$Sepal.Length, iris$Sepal.Width) 와 같다. 
head(iris[,c("Sepal.Length", "Sepal.Width")])
#>   Sepal.Length Sepal.Width
#> 1          5.1         3.5
#> 2          4.9         3.0
#> 3          4.7         3.2
#> 4          4.6         3.1
#> 5          5.0         3.6
#> 6          5.4         3.9
y[1:5]
#> [1] 8.6 7.9 7.9 7.7 8.6


반응형
반응형


작년 11월이 tensorflow 의 3주년이었습니다. tensorflow 는 출시된 이래로 지금까지 가장 사랑받는 딥러닝 라이브러리였습니다. 그리고 얼마전 tensorflow dev summit 에서 새로운 milestone 이라고 할 수 있는 tensorflow 2.0 소식이 발표되었습니다! (tensorflow dev summit 2019 의 동영성은 이 곳에서 볼 수 있습니다.)


Tensorflow 2.0의 주된 내용은 tf.keras 에 관한 것이라고 할 수 있습니다. 사실 Keras 는 2년 전부터 tensorflow에 통합되었습니다. Tensorflow 2.0 의 가장 큰 변화는 tf.keras 의 기능을 보다 강화하여 기존 tensorflow 와의 통합을 한 것이라고 할 수 있습니다. 즉, keras 를 단순히 high-level API 로 제시한 것이 아니라, 전면으로 내세워 tf 의 기능을 통합하려는 것입니다. 본 영상에서도 대부분 tf.keras 의 기능을 설명하고 있습니다. 

Keras 는 대규모 모델 보다는 간단한 모델을 빠르게 프로토타이핑을 하는데 강점이 있는 API 입니다. 따라서 사용하기는 힘들어도 대규모 네트워크를 구현할 때는 tensorflow 가 이점이 있었는데, tensorflow 2.0 에서는 keras와 tf 를 통합함으로써 keras 를 대규모 네트워크에서도 쉽고 효율적으로 사용할 수 있도록 강화한 것입니다. 

대표적인 내용은 아래와 같습니다. 

1. eager context 를 사용할 수 있다. 1.* 버전에서 tf.keras를 사용할 때는 keras 로 작성된 모델을 그래프로 변환하는 과정을 거쳤는데, 2 버전에서는 eager mode로 동작할 수 있다 (run_eagerly=True). 통해 eager 모드로 누릴 수 있는 이점을 누릴 수 있다. eager는 디버깅과 프로토타이핑을 쉽게 할 수 있다. 
2. tensorflow의 estimator 를 쉽게 사용할 수 있어, transfer learninng 이 쉽다.

3. tensorboard 와 keras 와의 통합이 매우 간단해졌다. call back 에 넣기만 하면 된다. 

4. distributed strategy 를 keras 에서 사용할 수 있다. 예를 들어, multi-gpu, multi-node 처리를 가능하게하는데, 이는 엄청난 성능의 증가를 가능하게 한다. 그리고 이것이 단 몇 줄로 가능하다. 

5. 모델을 저장하는 방식 (SavedMode)을 tf 와 keras 가 비슷하게 할 수 있다. 이는 다양한 플랫폼에서 interchangable 한 모델이 될 것이다. 


출처 - https://medium.com/tensorflow/whats-coming-in-tensorflow-2-0-d3663832e9b8


Tensorflow 2.0은 alpha 버전을 지금 사용할 수 있고, rc 버전 (release candidate) 이 올해 봄 중에 배포될 예정이라고 합니다. 전 지금까지 tensorflow 를 backend로 keras 를 사용해왔었는데, tensorflow 2.0이 release 되면 tensorflow 내부에서 keras 를 사용하는 쪽으로 바꾸어야겠습니다. 이외에도 tf 2.0 에서는 중복된 api 와 duplicated 된 api 를 제거하여 더욱 쉽게 라이브러리를 사용할 수 있도록 개선하였습니다. 이외에 다양한 개선점에 대해서는 해당 포스트를 참고하시면 좋을 것 같습니다.

반응형
반응형


다양한 R 스타일 가이드


좋은 코딩 스타일은 정확한 구두점을 찍는 것과 비슷합니다. 구두점이 없더라도 의미를 알 수는 있지만 구두점은 의미 전달을 더욱 쉽게 하고 글을 읽기 쉽게 만듭니다. R 에는 아래와 같이 유명한 스타일이 존재합니다.


Bioconductor’s coding standards (https://bioconductor.org/developers/how-to/coding-style/)

- Hadley Wickham’s style guide (http://stat405.had.co.nz/r-style.html )

- Google’s R style guide (http://google-styleguide.googlecode.com/ svn/trunk/google-r-style.html )

- Colin Gillespie’s R style guide (http://csgillespie.wordpress.com/2010/ 11/23/r-style-guide/)

The State of Naming Conventions in R (by Rasmus Bååth)


일관적으로 코딩하는 것은 1) 사람들과 소통하기 쉽도록하고, 2) 스스로도 읽기 쉬운 코드를 작성할 수 있습니다. 코드를 작성한지 오랜 시간이 지나고, 다시 그 코드를 보았을 때, 의미를 알기 힘든 경우가 많습니다. 하지만 최대한 비슷한 스타일로 코딩을 한다면, 코드 이해에 드는 부담을 줄일 수 있습니다. 또한 많은 사람들이 비슷한 코딩 규약을 사용한다면 코딩을 통한 소통이 편리할 것입니다.


해들리 위컴의 R 코딩 스타일 가이드


본 포스팅에서는 tidyverse (tidyr, dplyr, ggplot2 등) 패키지로 유명한 해들리 위컴의 R 스타일 가이드를 정리해보도록 하겠습니다. 해들리 위컴의 R 스타일은 Google 의 R 스타일 가이드 를 약간 변형한 스타일입니다.  


이름짓기

- 파일 이름

1. 파일 이름은 의미가 있어야합니다.

# Good
fit-models.R
utility-functions.R

# Bad
foo.r
stuff.r

2. 만약 파일이 순서대로 실행되어야 한다면, 이와 같이 숫자로 prefix 를 붙이는 것도 좋습니다. 

0-download.R
1-parse.R
2-explore.R

- 객체 이름 

“There are only two hard things in Computer Science: cache invalidation and naming things.”
Phil Karlton

변수명과 함수이름은 소문자를 사용하며, 단어를 분리할 때는 underscore(_) 를 사용합니다. 일반적으로 변수는 명사, 함수는 동사를 사용하는 것이 좋습니다. 이름은 최대한 간결하며 의미를 잘 전달할 수 있습니다. (이것은 때론 쉽지 않습니다.)

# Good
day_one
day_1

# Bad
first_day_of_the_month
DayOne
dayone
djm1

c, mean 과 같은 이미 사용되고 있는 객체에는 새로운 변수나 함수를 할당하는 것이 좋지 않습니다. 

# Bad
T <- FALSE
c <- 10
mean <- function(x) sum(x)

문법

Spacing

infix operators (=, +, -, <-, etc.) 에는 space 를 양옆으로 위치시킵니다. = 에서도 마찬가지입니다. comma 뒤는 한 칸 띄우며, comma 전에는 띄우지 않습니다. (이는 영어, 한글에서도 마찬가지입니다.)

# Good
average <- mean(feet / 12 + inches, na.rm = TRUE)

# Bad
average<-mean(feet/12+inches,na.rm=TRUE)

하지만 : 나 ::는 spacing을 하지 않습니다. 

# Good
x <- 1:10
base::get

# Bad
x <- 1 : 10
base :: get

if, for 문 등에서 ( 전에 한 칸을 띄웁니다. 하지만 함수를 호출할 때는 띄우지 않습니다. 

# Good
if (debug) do(x)
plot(x, y)

# Bad
if(debug)do(x)
plot (x, y)

아래처럼 정렬을 위해 space 를 두 번 이상하는것을 허용합니다. 

list(
  total   = a + b + c, 
  mean  = (a + b + c) / n
)

중괄호나 대괄호에는 comma 가 있는 경우가 아니라면 space 를 넣지 않습니다. 

# Good
if (debug) do(x)
diamonds[5, ]

# Bad
if ( debug ) do(x)  # No spaces around debug
x[1,]   # Needs a space after the comma
x[1 ,]  # Space goes after comma not before

중괄호

중괄호의 시작 ( { ) 이 있는 경우, 항상 개행을 해야합니다. 중괄호의 끝( } ) 이 있는 경우, if 문의 else 가 뒤따라오지 않는다면 그 줄에는 } 만 오도록 합니다. 물론 중괄호 안에서의 indentation 은 기본입니다. 

# Good

if (y < 0 && debug) {
  message("Y is negative")
}

if (y == 0) {
  log(x)
} else {
  y ^ x
}

# Bad

if (y < 0 && debug)
message("Y is negative")

if (y == 0) {
  log(x)
} 
else {
  y ^ x
}

매우 짧은 if 문의 경우 한 줄에 작성하는 것을 허용합니다. 

if (y < 0 && debug) message("Y is negative")

한 줄의 길이

한 줄의 길이를 80 자가 넘지 않도록 하는 것이 좋습니다. 이는 일반적인 크기의 글꼴로 프린트를 했을 때 용지에 잘 맞도록 하기 위함입니다. 

Indentation

Indentation 을 할 때, space 만 사용하는 것이 좋습니다. 다만 function 을 정의할 때, parameter 의 수가 많아 줄이 길어진다면 함수의 정의가 시작되는 부분에 맞게 indentation 을 합니다. 

long_function_name <- function(a = "a long argument", 
                               b = "another argument",
                               c = "another long argument") {
  # As usual code is indented by two spaces.
}

할당

변수 할당에는 = 를 사용하지 않고 <- 를 사용합니다. 

# Good
x <- 5
# Bad
x = 5

주석 달기

코드에 주석을 다는 것이 좋습니다. 주석은 무엇을 (what) 이 아닌 왜(why) 에 대한 내용을 설명해야 합니다. what 에 대한 내용은 코드 자체로 충분히 이해할 수 있도록 하는것이 좋습니다. 아래처럼 주석을 입력한 줄에 - 또는 = 을 사용해 읽기 쉽도록 분리하는 것도 좋습니다. 

# Load data ---------------------------

# Plot data ---------------------------


출처 - http://adv-r.had.co.nz/Style.html



반응형
반응형


PEP 은 무엇인가?


PEP 8 -- Style Guide for Python Code

PEP:8
Title:Style Guide for Python Code
Author:Guido van Rossum <guido at python.org>, Barry Warsaw <barry at python.org>, Nick Coghlan <ncoghlan at gmail.com>
Status:Active
Type:Process
Created:05-Jul-2001
Post-History:05-Jul-2001, 01-Aug-2013

PEP8은  Python Enhancement Proposals (PEP) 의 8 번째 내용으로, 더 나은 파이썬 코드를 작성하기 위한 하나의 코딩 규약 (Style Guide for Python Code)입니다. (https://www.python.org/dev/peps/pep-0008/)


Python PEP8 code convention cheat sheet


아래 파이썬 코드는 PEP8 style 을 활용한 코딩 예입니다. 일종의 cheat sheet로 이 코드만 알면 PEP8 에서 지향하는 파이썬 코딩 스타일의 대부분을 알 수 있습니다!


내용을 간단하게 요약하면 아래와 같습니다.


1. 모듈과 패키지 이름은 짧고 lower_case_with_underscore 이다. 

2. 다른 모듈에서 import 할 때 보일 필요가 없는 함수, 변수는 변수명 앞에 _를 추가한다. 

3. 상수는 A_CONSTANT 

4. 함수는 naming_convention

5. 클래스는 NamingConvention

6. 한 라인의 길이가 79 길이가 넘지 않도록 한다. (이것은 대략적인 A4 용지 사이즈이다.)


#! /usr/bin/env python # -*- coding: utf-8 -*- """This module's docstring summary line. This is a multi-line docstring. Paragraphs are separated with blank lines. Lines conform to 79-column limit. Module and packages names should be short, lower_case_with_underscores. Notice that this in not PEP8-cheatsheet.py Seriously, use flake8. Atom.io with https://atom.io/packages/linter-flake8 is awesome! See http://www.python.org/dev/peps/pep-0008/ for more PEP-8 details """ import os # STD lib imports first import sys # alphabetical import some_third_party_lib # 3rd party stuff next import some_third_party_other_lib # alphabetical import local_stuff # local stuff last import more_local_stuff import dont_import_two, modules_in_one_line # IMPORTANT! from pyflakes_cannot_handle import * # and there are other reasons it should be avoided # noqa # Using # noqa in the line above avoids flake8 warnings about line length! _a_global_var = 2 # so it won't get imported by 'from foo import *' _b_global_var = 3 A_CONSTANT = 'ugh.' # 2 empty lines between top-level funcs + classes def naming_convention(): """Write docstrings for ALL public classes, funcs and methods.     Functions use snake_case.     """ if x == 4: # x is blue <== USEFUL 1-liner comment (2 spaces before #) x, y = y, x # inverse x and y <== USELESS COMMENT (1 space after #) c = (a + b) * (a - b) # operator spacing should improve readability. dict['key'] = dict[0] = {'x': 2, 'cat': 'not a dog'} class NamingConvention(object): """First line of a docstring is short and next to the quotes.     Class and exception names are CapWords.     Closing quotes are on their own line     """ a = 2 b = 4 _internal_variable = 3 class_ = 'foo' # trailing underscore to avoid conflict with builtin # this will trigger name mangling to further discourage use from outside # this is also very useful if you intend your class to be subclassed, and # the children might also use the same var name for something else; e.g. # for simple variables like 'a' above. Name mangling will ensure that # *your* a and the children's a will not collide. __internal_var = 4 # NEVER use double leading and trailing underscores for your own names __nooooooodontdoit__ = 0 # don't call anything (because some fonts are hard to distiguish): l = 1 O = 2 I = 3 # some examples of how to wrap code to conform to 79-columns limit: def __init__(self, width, height, color='black', emphasis=None, highlight=0): if width == 0 and height == 0 and \ color == 'red' and emphasis == 'strong' or \ highlight > 100: raise ValueError('sorry, you lose') if width == 0 and height == 0 and (color == 'red' or emphasis is None): raise ValueError("I don't think so -- values are %s, %s" % (width, height)) Blob.__init__(self, width, height, color, emphasis, highlight) # empty lines within method to enhance readability; no set rule short_foo_dict = {'loooooooooooooooooooong_element_name': 'cat', 'other_element': 'dog'} long_foo_dict_with_many_elements = { 'foo': 'cat', 'bar': 'dog' } # 1 empty line between in-class def'ns def foo_method(self, x, y=None): """Method and function names are lower_case_with_underscores.         Always use self as first arg.         """ pass @classmethod def bar(cls): """Use cls!""" pass # a 79-char ruler: # 34567891123456789212345678931234567894123456789512345678961234567897123456789 """ Common naming convention names: snake_case MACRO_CASE camelCase CapWords """ # Newline at end of file


출처 - https://gist.github.com/RichardBronosky/454964087739a449da04



반응형
반응형

R - dplyr 을 통한 데이터 변형과 장점


본 포스팅에서는 tidyverse의 핵심 구성원이라고 할 수 있는 dplyr 을 통해 데이터를 변형하는 기본적인 방법을 basic R과 비교하여 설명하겠습니다


dplyr 의 5가지 핵심 함수

  • -값을 기준으로 선택하라 (filter)
  • -행을 재정렬하라 (arrange)
  • -이름으로 변수를 선택하라 (select)
  • -기존 변수들의 함수로 새로운 변수를 생성하라 (mutate)
  • -많은 값을 하나의 요약값으로 합쳐라 (summarize)

사용할 데이터셋은 뉴욕시에서 2013년에 출발한 336,776 개의 모든 항공편이 포함된 데이터 (nycflights13 패키지의 flights 데이터셋)

library(nycflights13)
library(tidyverse)
nrow(flights)
## [1] 336776

Filter

  • -1월 1일만 고르기
jan <- flights[flights$month == 1 & flights$day == 1, ]
nrow(jan)
## [1] 842
jan <- filter(flights, month == 1, day == 1)
nrow(jan)
## [1] 842
  • -출발 지연 시간이 120 미만인 항공편 고르기
  • -여기서 basic R과 dplyr 의 차이가 드러나는데, basic R 의 경우 arr_delay 변수가 NA 인 경우, 모든 column이 NA인 row를 포함한 dataframe을 반환한다.
  • -basic R의 경우 filter 를 구현할 때, NA 인 경우도 고려해야하므로, dplyr이 더 간단하게 원하는 목적을 달성할 수 있다고 볼 수 있다. 
'under_two <- flights[(flights$arr_delay < 120), ]
nrow(under_two) # arr_delay가 NA인 row가 모든 column이 NA인 row로 추가된다. .
## [1] 326576
sum(is.na(under_two$arr_delay))
## [1] 9430
sum(is.na(under_two$dep_delay))
## [1] 9430
under_two <- filter(flights, arr_delay < 120) 
nrow(under_two) 
## [1] 317146
sum(is.na(under_two$arr_delay))
## [1] 0
sum(is.na(under_two$dep_delay)) 
## [1] 0
# R 기본문법으로 dplyr의 filter를 구현하려면 이렇게 함. 
under_two <- flights[(flights$arr_delay < 120) & !is.na(flights$arr_delay), ]
nrow(under_two) 
## [1] 317146
sum(is.na(under_two$arr_delay))
## [1] 0
sum(is.na(under_two$dep_delay))
## [1] 0
  • -출발 혹은 도착에서 2시간 이상 지연되지 않은 항공편을 모두 찾기
  • -이 때, 두 조건절이 모두 NA 인 경우, basic R 에서는 모든 컬럼이 NA 인 행을 dataframe에 추가하여 반환한다. 
under_two <- flights[!((flights$arr_delay > 120) | (flights$dep_delay > 120)), ]
nrow(under_two) # 둘다 NA인 행 9430개의 행이, 포함되어있음
## [1] 325354
sum(is.na(under_two$arr_delay))
## [1] 9304
sum(is.na(under_two$dep_delay))
## [1] 9304
under_two <- filter(flights, !(arr_delay > 120 | dep_delay > 120)) 
nrow(under_two) # 둘다 NA인 행 제외. 이게 정상적인 결과.
## [1] 316050
sum(is.na(under_two$arr_delay))
## [1] 0
sum(is.na(under_two$dep_delay)) 
## [1] 0
  • -출발 시간, 도착 시간 둘 중 하나가 2시간 이상 지연된 항공편을 모두 찾기
over_two <- flights[((flights$arr_delay > 120) | (flights$dep_delay > 120)), ]
nrow(over_two) # 둘중 하나가 NA 이면서, delay > 120 인 행 포함, 근데 둘 다 NA인 경우 모든 컬럼이 NA인 행을 포함함 (9403 개)
## [1] 20726
sum(is.na(over_two$arr_delay)) # 9430 -> 둘중 하나가 NA인 수
## [1] 9430
sum(is.na(over_two$dep_delay)) #9304 -> 둘다 NA인 수 
## [1] 9304
over_two <- filter(flights, ((arr_delay > 120) | (dep_delay > 120))) # 둘중 하나가 NA 이면서, delay > 120인 행 포함
nrow(over_two) # 정상적인 결과 
## [1] 11422
sum(is.na(over_two$arr_delay)) 
## [1] 126
sum(is.na(over_two$dep_delay)) 
## [1] 0
  • -일반 데이터 프레임의 문제점. 조건이 NA인 경우 모든 컬럼이 NA인 행이 출력된다. 
  • -dplyr 의 filter 는 이러한 문제 없이 원하는 결과를 출력해준다. 
flights[NA,]
## # A tibble: 336,776 x 19
##     year month   day dep_time sched_dep_time dep_delay arr_time
##    <int> <int> <int>    <int>          <int>     <dbl>    <int>
##  1    NA    NA    NA       NA             NA        NA       NA
##  2    NA    NA    NA       NA             NA        NA       NA
##  3    NA    NA    NA       NA             NA        NA       NA
##  4    NA    NA    NA       NA             NA        NA       NA
##  5    NA    NA    NA       NA             NA        NA       NA
##  6    NA    NA    NA       NA             NA        NA       NA
##  7    NA    NA    NA       NA             NA        NA       NA
##  8    NA    NA    NA       NA             NA        NA       NA
##  9    NA    NA    NA       NA             NA        NA       NA
## 10    NA    NA    NA       NA             NA        NA       NA
## # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
## #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
## #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
## #   minute <dbl>, time_hour <dttm>

Arrange

  • -날짜별로 오름차순 정렬하기
  • -정렬의 경우, 마찬가지로 dplyr 이 훨씬 직관적이다.
arranged <- flights[order(flights$year, flights$month, flights$day),]
head(arranged)
## # A tibble: 6 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  2013     1     1      517            515         2      830
## 2  2013     1     1      533            529         4      850
## 3  2013     1     1      542            540         2      923
## 4  2013     1     1      544            545        -1     1004
## 5  2013     1     1      554            600        -6      812
## 6  2013     1     1      554            558        -4      740
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>
arranged <- arrange(flights, year, month, day)
head(arranged)
## # A tibble: 6 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  2013     1     1      517            515         2      830
## 2  2013     1     1      533            529         4      850
## 3  2013     1     1      542            540         2      923
## 4  2013     1     1      544            545        -1     1004
## 5  2013     1     1      554            600        -6      812
## 6  2013     1     1      554            558        -4      740
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>
  • -내림차순 정렬하기
desc <- flights[order(-flights$arr_delay),]
head(desc)
## # A tibble: 6 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  2013     1     9      641            900      1301     1242
## 2  2013     6    15     1432           1935      1137     1607
## 3  2013     1    10     1121           1635      1126     1239
## 4  2013     9    20     1139           1845      1014     1457
## 5  2013     7    22      845           1600      1005     1044
## 6  2013     4    10     1100           1900       960     1342
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>
desc <- arrange(flights, desc(arr_delay))
head(desc)
## # A tibble: 6 x 19
##    year month   day dep_time sched_dep_time dep_delay arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>
## 1  2013     1     9      641            900      1301     1242
## 2  2013     6    15     1432           1935      1137     1607
## 3  2013     1    10     1121           1635      1126     1239
## 4  2013     9    20     1139           1845      1014     1457
## 5  2013     7    22      845           1600      1005     1044
## 6  2013     4    10     1100           1900       960     1342
## # ... with 12 more variables: sched_arr_time <int>, arr_delay <dbl>,
## #   carrier <chr>, flight <int>, tailnum <chr>, origin <chr>, dest <chr>,
## #   air_time <dbl>, distance <dbl>, hour <dbl>, minute <dbl>,
## #   time_hour <dttm>

Select

  • -select 함수는 열을 선택하는 함수이다.
selected <- flights[, c("year", "month", "day")]
head(selected)
## # A tibble: 6 x 3
##    year month   day
##   <int> <int> <int>
## 1  2013     1     1
## 2  2013     1     1
## 3  2013     1     1
## 4  2013     1     1
## 5  2013     1     1
## 6  2013     1     1
selected <- select(flights, year, month, day)
head(selected)
## # A tibble: 6 x 3
##    year month   day
##   <int> <int> <int>
## 1  2013     1     1
## 2  2013     1     1
## 3  2013     1     1
## 4  2013     1     1
## 5  2013     1     1
## 6  2013     1     1

Mutate

  • -mutate는 새로운 열을 추가하는 함수이다.
  • -dplyr은 select를 할 때 end_with function 을 통해 해당 문자로 끝나는 컬럼을 선택할 수도 있고, : 를 통해 두 문자의 사이에 있는 컬럼을 선택할 수도 있다. 
  • -따라서 열 선택에 있어 더욱 많은 기능을 짧은 코드로 구현할 수 있으며, 새로운 변수를 정의할 때도, 한 문장에 정의할 수 있어 코드의 길이가 짧아지고 가독성이 좋아진다. 
flights_sml <- select(flights, year:day, ends_with("delay"), distance, air_time)
flights_sml_new <- mutate(flights_sml, gain=arr_delay - dep_delay, speed = distance / air_time * 60)
nrow(flights_sml_new)
## [1] 336776
  • -새 변수만을 남기고 싶다면 transmute를 사용한다. 
flights_transmute <- transmute(flights, gain = arr_delay - dep_delay,
                               hours = air_time / 60, 
                               gain_per_hour = gain / hours)
nrow(flights_transmute)
## [1] 336776

Summarize

  • -데이터 프레임을 하나의 행으로 축약한다. 
# dplyr을 사용한 방법
summarize(flights, delay = mean(dep_delay, na.rm = TRUE), delay_sd = sd(dep_delay, na.rm=TRUE))
## # A tibble: 1 x 2
##   delay delay_sd
##   <dbl>    <dbl>
## 1  12.6     40.2
  • -summarize 함수는 group_by (dplyr 에서 제공하는 함수) 와 보통 함께 사용하는 경우가 많다. 이것은 dplyr 의 이전 버전이라고 할 수 있는 plyr에서도 잘 구현되어 있었는데, dplyr 이 되면서 조금 더 이해하기 쉽게 문법이 바뀌었다. 
by_day <- group_by(flights, year, month, day)
summarize(by_day, delay = mean(dep_delay, na.rm = TRUE))
## # A tibble: 365 x 4
## # Groups:   year, month [12]
##     year month   day delay
##    <int> <int> <int> <dbl>
##  1  2013     1     1 11.5 
##  2  2013     1     2 13.9 
##  3  2013     1     3 11.0 
##  4  2013     1     4  8.95
##  5  2013     1     5  5.73
##  6  2013     1     6  7.15
##  7  2013     1     7  5.42
##  8  2013     1     8  2.55
##  9  2013     1     9  2.28
## 10  2013     1    10  2.84
## # ... with 355 more rows

파이프로 여러 작업 결합하기

  • -파이프 명령어인 %>% 를 통해 dplyr 을 통한 데이터 변형 작업을 더욱 직관적이고, 가독성있게 수행할 수 있다. 
  • -각 위치에 대해 거리와 평균 지연 사이의 관계를 탐색하고 싶은 경우, 파이프를 사용하지 않는 방법은 아래와 같다. 
by_dest <- group_by(flights, dest)
delay <- summarize(by_dest, count = n(),
                   dist = mean(distance, na.rm = TRUE),
                   delay = mean(arr_delay, na.rm = TRUE))
delay <- filter(delay, count > 20, dest != "HNL")
ggplot(data = delay, mapping = aes(x = dist, y = delay)) + 
  geom_point(aes(size = count), alpha = 1/3) + 
  geom_smooth(se=FALSE)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

  • -파이프를 사용하면? 
  • -아래와 같이 임시 변수를 따로 놓지 않으면서 단 한 번의 logic 으로 똑같은 목적을 달성할 수 있다! 
  • -이러한 파이프 방법은 일련의 데이터 변형 작업을 의미 단위로 묶어 코드가 모듈화가 되기 때문에 유저가 코드를 쉽게 구현하고 이해할 수 있도록 도와준다. 
  • -다만 파이프가 너무 길어지면 유용한 임시변수를 두는 방법이 효율적일 수 있다. 
delay <- flights %>% 
  group_by(dest) %>%
  summarize(count = n(),
            dist = mean(distance, na.rm=TRUE),
            delay = mean(arr_delay, na.rm=TRUE)
            ) %>% 
  filter(count > 20, dest != "HNL")
ggplot(data = delay, mapping = aes(x = dist, y = delay)) + 
  geom_point(aes(size = count), alpha = 1/3) + 
  geom_smooth(se=FALSE)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'


반응형
반응형