#install.packages("tibble")
library(tibble)
예를 들어 보자
df <- tibble::tibble(
a = rnorm(10),
b = rnorm(10),
c = rnorm(10),
d = rnorm(10)
)
df$a <- (df$a - min(df$a, na.rm = TRUE)) /
(max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
df$b <- (df$b - min(df$b, na.rm = TRUE)) /
(max(df$b, na.rm = TRUE) - min(df$a, na.rm = TRUE))
df$c <- (df$c - min(df$c, na.rm = TRUE)) /
(max(df$c, na.rm = TRUE) - min(df$c, na.rm = TRUE))
df$d <- (df$d - min(df$d, na.rm = TRUE)) /
(max(df$d, na.rm = TRUE) - min(df$d, na.rm = TRUE))
–>> 반복코드를 함수로 추출
df
## # A tibble: 10 x 4
## a b c d
## <dbl> <dbl> <dbl> <dbl>
## 1 1 0.699 0.539 0.342
## 2 0.681 1.46 0.0268 0.421
## 3 0.489 0 0 0
## 4 0.572 0.931 0.194 0.548
## 5 0.585 1.04 0.253 0.425
## 6 0.909 0.882 0.600 0.152
## 7 0.788 2.05 1 0.349
## 8 0 0.234 0.647 1
## 9 0.700 0.901 0.300 0.489
## 10 0.235 1.37 0.493 0.146
(df$a - min(df$a, na.rm = TRUE)) /
(max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
## [1] 1.0000000 0.6810625 0.4890928 0.5716939 0.5850221 0.9087034 0.7876932
## [8] 0.0000000 0.6999519 0.2352838
x <- df$a
(x - min(x, na.rm = TRUE)) /
(max(x, na.rm = TRUE) - min(x, na.rm = TRUE))
## [1] 1.0000000 0.6810625 0.4890928 0.5716939 0.5850221 0.9087034 0.7876932
## [8] 0.0000000 0.6999519 0.2352838
rng <- range(x, na.rm = TRUE)
(x - rng[1]) / (rng[2] - rng[1])
## [1] 1.0000000 0.6810625 0.4890928 0.5716939 0.5850221 0.9087034 0.7876932
## [8] 0.0000000 0.6999519 0.2352838
rescale01 <- function(x) {
rng <- range(x, na.rm = TRUE)
(x - rng[1]) / (rng[2] - rng[1])
}
rescale01(c(0, 5, 10))
## [1] 0.0 0.5 1.0
함수 생성에는 세 가지 주요 단계가 있다.
함수 이름을 지어야한다. 여기선 rescale01이라고 했는데, 함수가 0과 1 사이에 놓이도록 벡터를 다시 스케일하기 때문이다.
function 내부에 함수 입력값, 즉 인수를 나열한다. 여기에서는 인수가 x 한 개만 있다. 여러 개가 있었다면 호출은 function(x, y, z)와 같을 것이다.
개발한 코드를 함수의 본문(body), 즉 function(…) 다음에 오는 { 블록에 넣는다.
우리는 함수를 생성한 후 작동되도록 노력하는 것보다 작동되는 코드를 우선 만들고, 이를 함수로 변환하는 것이 더 쉽다는 것을 알아야한다.
rescale01(c(-10, 0, 10))
## [1] 0.0 0.5 1.0
rescale01(c(1, 2, 3, NA, 5))
## [1] 0.00 0.25 0.50 NA 1.00
df$a <- rescale01(df$a)
df$b <- rescale01(df$b)
df$c <- rescale01(df$c)
df$d <- rescale01(df$d)
원본과 비교하면 이 코드는 이해하기 쉽고, 한 종류의 복사하여 붙여넣기 오류도 제거했다.
함수의 다른 장점은 요구사항이 변경되면 한 곳에서만 변경 작업을 하면 된다는 것이다.
예를 들어 일부 변수가 무한값을 포함하면 rescale01()은 작동하지 않는다는 것을 알았다고 하자.
x <- c(1:10, Inf)
rescale01(x)
## [1] 0 0 0 0 0 0 0 0 0 0 NaN
finite = TRUE를 추가!
rescale01 <- function(x) {
rng <- range(x, na.rm = TRUE, finite = TRUE)
(x - rng[1]) / (rng[2] - rng[1])
}
rescale01(x)
## [1] 0.0000000 0.1111111 0.2222222 0.3333333 0.4444444 0.5555556 0.6666667
## [8] 0.7777778 0.8888889 1.0000000 Inf
rescale01 <- function(x) {
rng <- range(x, na.rm = TRUE, finite = TRUE)
y <- (x - rng[1]) / (rng[2] - rng[1])
y[y == -Inf] <- 0
y[y == Inf] <- 1
y
}
rescale01(c(Inf, -Inf, 0:5, NA))
#> [1] 1.0 0.0 0.0 0.2 0.4 0.6 0.8 1.0 NA
mean(is.na(x))
## [1] 0
x / sum(x, na.rm = TRUE)
## [1] 0 0 0 0 0 0 0 0 0 0 NaN
sd(x, na.rm = TRUE) / mean(x, na.rm = TRUE)
## [1] NaN
5. 같은 길이의 두 벡터를 입력으로 하여, 두 벡터 모두 NA인 위치를 반환하는 함수 both_na()를 작성하라.
6. 다름 두 함수는 무슨 작업을 하는가? 이 짧은 함수들이 유용한 이유는 무엇인가?
is_directory <- function(x) file.info(x)$isdir
is_readable <- function(x) file.access(x, 4) == 0
#너무 짧음
f()
#동사가 아니거나 기술하지 않음
my_awesome_function()
#길지만 명확함
impute_missing()
collapse_years()
#절대 이렇게 하지 말것!
col_mins <- function(x, y)
rowMaxes <- function(y, x)
# Good
input_select()
input_checkbox()
input_text()
# Not so good
select_input()
checkbox_input()
text_input()
# 이렇게 하지 말 것!
T <- FALSE
c <- 10
mean <- function(x) sum(x)
f1 <- function(string, prefix) {
substr(string, 1, nchar(prefix)) == prefix
}
f2 <- function(x) {
if (length(x) <= 1) return(NULL)
x[-length(x)]
}
f3 <- function(x, y) {
rep(y, length.out = length(x))
}
최근에 작성한 함수에 대해 더 나은 함수 이름과 인수에 대해 5분 동안 브레인스토밍해보라.
rnorm()과 MASS::mvrnorm()을 비교 대조하라. 어떻게 더 일관되게 만들겠는가?
if (조건문) {
# 조건문이 TRUE 일 떄 수행되는 코드
} else {
# 조건문이 FALSE 일 때 수행되는 코드
}
has_name <- function(x) {
nms <- names(x)
if (is.null(nms)) {
rep(FALSE, length(x))
} else {
!is.na(nms) & nms != ""
}
}
if (c(TRUE, FALSE)) {}
#> Warning in if (c(TRUE, FALSE)) {: the condition has length > 1 and only the
#> first element will be used
#> NULL
if (NA) {}
#> Error in if (NA) {: missing value where TRUE/FALSE needed
|| (or) 와 && (and)를 사용하여 논리 표현식을 조합할 수 있다. 이 연산자들은 앞의 조건이 만족되면 뒤의 조건들은 무시하는데, 이를 ‘단락평가(short-circuit evaluation)’라 한다. 즉, ||는 첫 TRUE를 보는 즉시 다른 것 계산없이 TRUE를 반환한다. 마찬가지로 &&는 FALSE를 처음으로 보게 되면 즉시 FALSE를 반환한다.
논리형 벡터인 경우 any() 또는 all()을 사용하여 단일 값으로 축소할 수 있다.
identical(0L, 0)
## [1] FALSE
x <- sqrt(2) ^2
x
## [1] 2
x == 2
## [1] FALSE
x - 2
## [1] 4.440892e-16
if (이 조건) {
# 저것 수행
} else if (저 조건) {
# 다른 것 수행
} else{
#
}
#> function(x, y, op) {
#> switch(op,
#> plus = x + y,
#> minus = x - y,
#> times = x * y,
#> divide = x / y,
#> stop("Unknown op!")
#> )
#> }
# 좋음
if (y < 0 && debug) {
message("Y 가 음수")
}
if (y == 0) {
log(x)
} else {
y ^ x
}
# 나쁨
if (y < 0 && debug)
message("Y 가 음수")
if (y == 0) {
log(x)
}
else {
y ^ x
}
if (temp <= 0) {
"freezing"
} else if (temp <= 10) {
"cold"
} else if (temp <= 20) {
"cool"
} else if (temp <= 30) {
"warm"
} else {
"hot"
}
< 대신 <=를 사용하면 어떻게 cut() 호출을 변경하겠는가? 이 문제에서 cut()의 다른 장점은 무엇인가? (힌트 : temp에 값이 많다면 어떻게 될까?)
5. switch()를 수치형과 함께 사용하면 어떻게 되나?
6. 다음의 switch() 호출은 어떤 일은 하는가? x가 ’e’이면 어떻게 되는가?
switch(x,
a = ,
b = "ab",
c = ,
d = "cd"
)
## [1] "ab"
# Good
mean(1:10, na.rm = TRUE)
# Bad
mean(x = 1:10, , FALSE)
mean(, TRUE, x = c(1:10, NA))
# 좋음
average <- mean(feet / 12 + inches, na.rm = TRUE)
# 나쁨
average<-mean(feet/12+inches,na.rm=TRUE)
함수를 많이 작성하다 보면 함수가 정확하게 어떻게 작동하는지 기억하지 못할 때가 있다.
이 문제를 피하려면 제약조건을 명시적으로 나타내는 것이 좋다.
wt_mean <- function(x, w) {
sum(x * w) / sum(w)
}
wt_var <- function(x, w) {
mu <- wt_mean(x, w)
sum(w * (x - mu) ^ 2) / sum(w)
}
wt_sd <- function(x, w) {
sqrt(wt_var(x, w))
}
wt_mean(1:6, 1:3)
## [1] 7.666667
wt_mean <- function(x, w) {
if (length(x) != length(w)) {
stop("`x` and `w` must be the same length", call. = FALSE)
}
sum(w * x) / sum(w)
}
sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
## [1] 55
stringr::str_c("a", "b", "c", "d", "e", "f")
## [1] "abcdef"
commas <- function(...) stringr::str_c(..., collapse = ", ")
commas(letters[1:10])
#> [1] "a, b, c, d, e, f, g, h, i, j"
rule <- function(..., pad = "-") {
title <- paste0(...)
width <- getOption("width") - nchar(title) - 5
cat(title, " ", stringr::str_dup(pad, width), "\n", sep = "")
}
rule("Important output")
#> Important output -----------------------------------------------------------
x <- c(1, 2)
sum(x, na.mr = TRUE)
## [1] 4
complicated_function <- function(x, y, z) {
if (length(x) == 0 || length(y) == 0) {
return(0)
}
# 복잡한 코드 구역
}
f <- function() {
if (x) {
# 표현하는데
# 많은
# 라인이
# 필요한
# 것을
# 하는
# 구역
} else {
# 짧은 것 반환
}
}
f <- function() {
if (!x) {
return(짧은 것)
}
# 긴
# 라인
# 으로
# 표현
# 하는
# 구역
}
show_missings <- function(df) {
n <- sum(is.na(df))
cat("Missing values: ", n, "\n", sep = "")
invisible(df)
}
show_missings(mtcars)
## Missing values: 0
x <- show_missings(mtcars)
## Missing values: 0
class(x)
## [1] "data.frame"
dim(x)
## [1] 32 11
mtcars %>%
show_missings() %>%
mutate(mpg = ifelse(mpg < 20, NA, mpg)) %>%
show_missings()
#> Missing values: 0
#> Missing values: 18
f <- function(x) {
x + y
}
y <- 100
f(10)
## [1] 110
y <- 1000
f(10)
## [1] 1010