Advanced R Week 2
這是Advanced R第二周的課程內容整理,很有趣。
Functional Programming
Functional Programming專注在四件事情
- Data(numbers, strings etc)
- Variables(function arguments)
- Functions
- Function Applications(evaluating functions given arguments and/or data)
範例,Function傳回Function
1 | add_maker <- function(n) { |
Map
這類型得函數會套用到資料結構中每個元素,例如vector/list中每個元素,然後回傳每個元素套用後的結果。
例如 purrr
套件中的map_lgl()
回傳vecotors of logical valuesmap_chr()
回傳stringsmap_dbl()
回傳numbers1
2
3
4
5
6
7
8
9
10
11library(purrr)
map_chr(c(5,4,3,2,1), function(x) {
c("one","two","three","four","five")[x]
})
[1] "five" "four" "three" "two" "one"
map_lgl(c(5,4,3,2,1), function(x){
x>3
})
[1] TRUE TRUE FALSE FALSE FALSE
map_lgl(c(1,2,3,4,5), gt, b=3)
其中 gt
吃a,b兩個參數,如果 a > b則回傳TRUE,反之FALSE。在這個例子中,b=3是gt的參數。
map_if
滿足第二個函數演算結果得就執行第三個函數1
2
3
4
5
6
7map_if(1:5, function(x) {
x%%2 == 0
},
function(y) {
y^2
}) %>% unlist()
[1] 1 4 3 16 5
1 | map_at(seq(100,500,100), c(1,3,5), function(x){x-10}) %>% unlist() |
map2 family
對兩組傳入的資料作map
,這兩組資料長度要一樣長。1
2map2_chr(letters, 1:26, paste)
[1] "a 1" "b 2" ... 不想寫了反正26個XD
1 | pmap_chr(list( |
Reduce
1 | reduce(c(1,3,5,7), function(x,y) {x+y}) |
1 | reduce(letters[1:4], function(x,y){paste0(x,y)}) |
search
找東西1
2
3
4
5
6
7
8
9contains(letters, 'a')
[1] TRUE
# detect 只會傳回第一個滿足條件得數字
detect(20:40, function(x){ x>22 && x %% 2 ==0 })
[1] 24
detect_index(20:40, function(x) { x>22 && x %% 2 ==0})
[1] 5
Filter
keep
, discard
, every
and some
1
2
3
4
5
6
7
8
9
10keep(1:20, function(x){x%%2==0})
[1] 2 4 6 8 10 12 14 16 18 20
discard(1:20, function(x){x%%2==0})
[1] 1 3 5 7 9 11 13 15 17 19
# 是不是所有得元素都滿足條件
every(1:20, function(x){x%%2==0})
[1] FALSE
# 有沒有哪個元素滿足條件得?
some(1:20, function(x){x%%2==0})
[1] TRUE
Compose
1 | n_unique <- compose(length, unique) |
Partial Application
partial(function(arg1,arg2,arg3))
可以讓function指定特定參數得值,partial製作一個新的函數,吃未被指定值的參數
1 | multi_three_n <- function(x,y,x) {x*y*z} |
Side Effects
walk
會呼叫f引起side effect,然後回傳input。1
2
3
4
5
6
7
8walk(c("Friends, Romans, countrymen,",
"lend me your ears;",
"I come to bury Caesar,",
"not to praise him."), message)
Friends, Romans, countrymen,
lend me your ears;
I come to bury Caesar,
not to praise him.R
Recursion
1 | # loop version |
紀錄計算過程得資料,避免重複計算1
2
3
4
5
6
7
8
9
10
11
12
13fib_tbl <- c(0,1,rep(NA,23))
fib_mem <- function(n) {
stopifnot(n>0)
if (!is.na(fib_tbl[n])) {
fib_tbl[n]
} else {
fib_tbl[n-1] <<- fib_mem(n-1)
fib_tbl[n-2] <<- fib_mem(n-2)
fib_tbl[n-1] + fib_tbl[n-2]
}
}
map_dbl(1:12, fib_mem)
計算不同的recursion方法的時間差異1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29library(purrr)
library(microbenchmark)
library(tidyr)
library(magrittr)
library(dplyr)
fib_data <- map(1:10, function(x){microbenchmark(fib(x), times = 100)$time})
names(fib_data) <- paste0(letters[1:10], 1:10)
fib_data <- as.data.frame(fib_data)
fib_data %<>%
gather(num, time) %>%
group_by(num) %>%
summarise(med_time = median(time))
memo_data <- map(1:10, function(x){microbenchmark(fib_mem(x))$time})
names(memo_data) <- paste0(letters[1:10], 1:10)
memo_data <- as.data.frame(memo_data)
memo_data %<>%
gather(num, time) %>%
group_by(num) %>%
summarise(med_time = median(time))
plot(1:10, fib_data$med_time, xlab = "Fibonacci Number", ylab = "Median Time (Nanoseconds)", pch = 18, bty = "n", xaxt = "n", yaxt = "n")
axis(1, at = 1:10)
axis(2, at = seq(0, 350000, by = 50000))
points(1:10 + .1, memo_data$med_time, col = "blue", pch = 18)
legend(x="topleft", y=NULL, c("Not Memorized", "Memoized"), pch = 18, col = c("black", "blue"), bty = "n", cex = 1, y.intersp = 1.5)
Expression & Environments
Expressions
- 利用
quote
存算式 parse
解出字串儲存得算式- 用
eval
計算算式的結果。 call
建立算式
1 | two_plus_two <- quote(2+2) |
像是處理list
一樣地處理算式,替換算式的內容。1
2
3
4
5
6
7
8
9
10
11
12sum_expr <- quote(sum(1,5))
sum_expr[[1]] # sum
sum_expr[[2]] # 1
sum_expr[[3]] # 5
sum_expr[[1]] <- quote(paste0)
sum_expr[[2]] <- 4
sum_expr[[3]] <- 6
eval(sum_expr)
[1] "46"
eval(call('sum', 40 ,50))
[1] 90
利用match.call
在函數中擷取函數的輸入參數1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16args_of_func <- function(...) {
expr <- match.call()
cat("Length of Expr:", length(expr), '\n')
if(length(expr) > 1) {
eval(expr[[2]])
} else {
expr
}
}
> args_of_func()
Length of Expr: 1
args_of_func()
> args_of_func(2 * 2 + 100)
Length of Expr: 2
[1] 104
Environments
new.env()
產新的Environment1
2
3
4
5
6
7my_new_env <- new.env()
# 指定Enrivonments的屬性值
my_new_env$x <- 4
assign("y", 9, envir = my_new_env)
# 取得Enrironment中得屬性值
get("y", envir = my_new_env)
my_new_env_$y
search
列出environment的串列
1 | search() |
Environment跟函數息息相關,當執行函數時,會產生Execution Environment。我們可以在Execution Environment中存放變數,當變數找不到時,會往去parent Environment找,在以下的案例中,就是往Global Environment去找。
1 | Example 1 |
Error Handling and Generation
error
當程式遇到非預期的使用狀態時,就會產生error
warning
提醒你程式有錯,應該要檢查一下message
印出訊息至R Console,產生message
的機制與error
, warning
相似。
Generating Errors
1 | a_func <- function() { |
Try Catch
1 | beera <- function(expr) { |
不要產生太多的Error,會有效能問題
案例1
2
3
4
5
6
7
8
9
10
11# 好處是可以處理多種得資料型態,反正只要不是整數且%%2不為零,就回傳FALSE。
is_even_error <- function(n) {
tryCatch(n%%2==0,
error = function(e){
FALSE
})
}
is_even_error(222)
[1] TRUE
is_even_error("aaa")
[1] FALSE
但也可以不用tryCatch
達到同樣的結果
利用 &&
達成 short circuiting1
2
3
4
5
6
7
8# 好處是當is.numeric()回傳FALSE,後面的算式也不用做了,節省時間。
is_even_check <- function(n) {
is.numeric(n) && n %% 2 == 0
}
is_even_check(222)
[1] TRUE
is_even_check("aaa")
[1] FALSE
比較兩者效能tryCatch
慢非常多倍。1
2
3
4
5
6
7
8
9
10
11
12
13library(microbenchmark)
microbenchmark(sapply(letters,is_even_check))
Unit: microseconds
expr min lq mean median
sapply(letters, is_even_check) 45.721 48.2405 71.4882 75.2625
uq max neval
82.19 179.777 100
microbenchmark(sapply(letters,is_even_error))
Unit: microseconds
expr min lq mean
sapply(letters, is_even_error) 635.915 651.0795 758.6887
median uq max neval
673.392 695.7405 5552.603 100