class: center, middle, inverse, title-slide .title[ # 量化金融与金融编程 ] .subtitle[ ## L04 向量与函数 ] .author[ ###
曾永艺 ] .institute[ ### 厦门大学管理学院 ] .date[ ###
2022-10-07 ] ---
<br> .font120[ > To understand computations in __R__, two slogans are helpful: > > - Everything that exists is an _object_. > > - Everything that happens is a _function call_. > ] -- .pull-left.bold[ ## __1. 向量__ * .font140[原子向量] * .font140[列表] * .font140[向量属性] * .font140[增强向量] ] -- .pull-right.bold[ ## __2. 函数__ * .font140[使用函数的优点] * .font140[编写函数的套路] * .font140[函数的参数] * .font140[函数的返回值] * .font140[函数的环境] * .font140[作为一等公民的函数] ] --- class: inverse, center, middle # 1. 向量 .font150[(vectors)] --- class: middle background-image: url(imgs/vectors-01.png) background-size: 80% background-position: 50% 50% --- ### 1.1 原子向量 >> 逻辑向量(_logical vector_) .full-width[.content-box-blue.bold.font120.note[逻辑向量的元素只有三种可能取值:`TRUE | FALSE | NA`]] -- .full-width[.content-box-blue.bold.font120.note[逻辑向量通常由比较运算(`?Comparison`)生成]] ```r 1:10 %% 3 == 0 ``` ``` #> [1] FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE FALSE ``` -- .full-width[.content-box-blue.bold.font120.note[当然也可以用 `c()` 直接构造逻辑向量]] ```r c(TRUE, FALSE, NA, T, F) # c(): Combine Values into a Vector or List ``` ``` #> [1] TRUE FALSE NA TRUE FALSE ``` -- .full-width[.content-box-blue.bold.font120.warning[强烈建议不要使用 `TRUE` 和 `FALSE` 的缩写形式 `T` 和 `F`]] ```r T <- FALSE; F <- 2; c(TRUE, FALSE, NA, T, F); rm(list = c("T", "F")) ``` ``` #> [1] 1 0 NA 0 2 ``` --- ### 1.1 原子向量 >> 数值向量(_numeric vector_) .full-width[.content-box-blue.bold.font120.note[数值向量包括整数向量(integer)和实数向量(double)]] -- .pull-left.code100[ .full-width[.content-box-blue.bold.font120.note[默认为实数,除非在整数后加上 `L`]] .code80[ ```r typeof(1);typeof(1L);typeof(1.5L) ``` ``` #> [1] "double" ``` ``` #> [1] "integer" ``` ``` #> [1] "double" ``` ] .full-width[.content-box-blue.bold.font120.warning[R 用双精度来存储实数向量的数值,但在很多情况下只是近似值,因此不要用 `==` 直接比较实数向量的取值,而用 `dplyr::near()`]] ] -- .pull-right[ .full-width[.content-box-blue.bold.font120.info[整数向量只有一个特殊值 `NA`,而实数向量则有四个特殊值 `NA`、`NaN`、`Inf` 和 `-Inf`;用 `is.*()` 而不要直接用 `==` 检测这些特殊值]] .code100[ ```r c(-1, 0, 1, NA) / 0 # 4个特殊值 #> [1] -Inf NaN Inf NA ``` ] | | `0` |`Inf`|`NA` |`NaN`| |:-----------------|:---:|:---:|:---:|:---:| | `is.finite()` | √ | | | | | `is.infinite()` | | √ | | | | `is.na()` | | | √ | √ | | `is.nan()` | | | | √ | ] --- ### 1.1 原子向量 >> 字符向量(_charater vector_) .pull-left.code110[ .full-width[.content-box-blue.bold.font120.note[字符向量的每个元素由字符串构成,可用来表示任意数据]] ```r c("FALSE", "123L", "123.45", "a string", "1 + 2i", "3a 29", "2018-11-13 10:00 CST") ``` ``` #> [1] "FALSE" #> [2] "123L" #> [3] "123.45" #> [4] "a string" #> [5] "1 + 2i" #> [6] "3a 29" #> [7] "2018-11-13 10:00 CST" ``` ] -- .pull-right.code110[ .full-width[.content-box-blue.bold.font120.info[R 使用全局字符串池,这意味着每个字符串在内存中只存一次]] ```r x <- "This is a long string." object.size(x) # lobstr::obj_size(x) ``` ``` #> 136 bytes ``` ```r y <- rep(x, 1000) object.size(y) ``` ``` #> 8128 bytes ``` ```r # 8 * 1000 + 128 B ``` ] --- ### 1.1 原子向量 >> 类型转换 .full-width[.content-box-blue.bold.font120.note[显性转换:`as.logical()` | `as.integer()` | `as.double()` | `as.character()`]] -- .code90[ ```r as.integer(c("1", "2")) + c(10, 11) ## [1] 11 13 ``` ] -- .pull-left.font120[ .full-width[.content-box-blue.bold.note[隐性转换:当向量用于需要特定类型输入的函数时,R 会自动将向量转换为特定类型.red[(但非总是如此)]]] ```r x <- sample(1:20, 100, replace = TRUE) # what proportion > 10? mean(x > 10) ``` ``` #> [1] 0.46 ``` ```r x + "3" # 但并非总是如此 ``` ``` #> Error in x + "3": non-numeric argument to binary operator ``` ] -- .pull-right.font120[ .full-width[.content-box-blue.bold.note[隐性转换:当用 `c()` 联结不同类型的向量时,R 会套用最复杂的类型]] ```r c(FALSE, 1L, 1.5, "a", 1 + 1i, raw(2)) ``` ``` #> [1] "FALSE" "1" "1.5" "a" #> [5] "1+1i" "00" "00" ``` ```r 1 == "1"; -1 < FALSE; "one" < 2 ``` ``` #> [1] TRUE #> [1] TRUE #> [1] FALSE ``` ] --- ### 1.1 原子向量 >> 类型检测 .pull-left[ <br> .full-width[.content-box-blue.bold.font120.info[尽管 R 的 `base` 包提供很多类型检测函数(如 `is.logical()` ),但建议使用 .bold[`purrr`] 包中用法更加一致的相关函数 <sup>.red[*]</sup>]] ] .pull-right.font120[ <br> | | lgl | int | dbl | chr | list | |:-----------------|:---:|:---:|:---:|:---:|:----:| | `is_logical()` | √ | | | | | | `is_integer()` | | √ | | | | | `is_double()` | | | √ | | | | `is_numeric()` | | √ | √ | | | | `is_character()` | | | | √ | | | `is_atomic()` | √ | √ | √ | √ | | | `is_list()` | | | | | √ | | `is_vector()` | √ | √ | √ | √ | √ | ] .footnote.red[*:事实上这些函数现在都 import 自 `rlang` 包] --- ### 1.1 原子向量 >> 循环规则(_recycling_) .full-width[.content-box-blue.bold.font120.note[R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度]] -- .code100[ ```r runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量 ``` ``` #> [1] FALSE FALSE TRUE FALSE TRUE FALSE TRUE FALSE FALSE TRUE ``` ] -- .pull-left.code100[ .full-width[.content-box-blue.bold.font120.info[当较长向量不是较短向量的整数倍时,R 照样会进行转换,但会有个 .red.font90[Warning message]]] ```r 1:5 + c(1, NA) ``` ``` #> Warning in 1:5 + c(1, NA): longer #> object length is not a multiple of #> shorter object length ``` ``` #> [1] 2 NA 4 NA 6 ``` ] -- .pull-right.code100[ .full-width[.content-box-blue.bold.font120.info[当向量的长短不一时(标量除外),要求更严格的 `tidyverse` 则会抛出 Error,停止运行]] ```r tibble(x = 1:4, y = 1:2) ``` ``` #> Error: #> ! Tibble columns must have compatible sizes. #> • Size 4: Existing data. #> • Size 2: Column `y`. #> ℹ Only values of size one are recycled. ``` ] --- layout: true ### 1.1 原子向量 >> 用 `[` 函数选取元素(_subsetting_) --- .full-width[.content-box-blue.bold.font120[方法一:用.red[整数向量]选取元素]] -- ```r x <- c("one", "two", "three", "four", "five") x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始 ``` ``` #> [1] "one" "one" "three" "two" "two" "five" ``` -- ```r x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素 ``` ``` #> [1] "two" "four" ``` -- ```r x[c(1, -3, 5)] # 不可混用正整数和负整数 ``` ``` #> Error in x[c(1, -3, 5)]: only 0's may be mixed with negative subscripts ``` -- ```r x[0] # 返回空值(但非NULL) ``` ``` #> character(0) ``` -- ```r x[c(1.2, 2.3, 3.4, 4.5, 5.6, 6.7)] # 想想,这会返回什么? ``` --- .pull-left.code100[ .full-width[.content-box-blue.bold.font120[方法二:用.red[逻辑向量]选取元素,保留逻辑向量取值为 `TRUE` 位置的元素]] ```r x <- c(10, 3, NA, 5) x[!is.na(x)] # 提取非缺失值 ``` ``` #> [1] 10 3 5 ``` ```r # 逻辑向量取值为NA的位置也会被 # 保留下来,相应元素的取值也为NA x[x %% 2 == 0] # 提取偶数或缺失值 ``` ``` #> [1] 10 NA ``` ] -- .pull-right.code100[ .full-width[.content-box-blue.bold.font120[方法三:用.red[字符向量]选取命名向量的元素]] ```r # 命名向量:创建时直接命名 abc <- c(a = 1, b = 2, c = 3) # 命名向量:事后再命名 abc <- 1:3 abc <- purrr::set_names( # 也可用base包的setNames() x = abc, nm = c("a", "b", "c") ) ``` ```r # 提取相应命名的元素 abc[c("a", "c", "b", "a", "d")] ``` ``` #> a c b a <NA> #> 1 3 2 1 NA ``` ] --- .pull-left.code110[ .full-width[.content-box-blue.bold.font120[方法四:.red[空向量]表示选取全部元素]] ```r abc ``` ``` #> a b c #> 1 2 3 ``` ```r abc[] ``` ``` #> a b c #> 1 2 3 ``` ] -- .pull-right.code110[ .full-width[.content-box-blue.bold.font120[最后,[ 有个特殊的变型 [[,后者.red[只返回单个元素,且会丢弃元素名称]。当你想要明确表达你只需要一个元素时,推荐使用 [[。]] ```r abc["a"] # abc[1] ``` ``` #> a #> 1 ``` ```r abc[["a"]] # abc[[1]] ``` ``` #> [1] 1 ``` ] --- layout: true ### 1.2 列表 >> 基础 --- .full-width[.content-box-blue.bold.font120.note[和原子向量不同,列表可同时包含.red[多种类型(type)的元素(甚至嵌套下一级的列表)],这使得列表适合用来存储混合或树状结构的数据]] -- .pull-left.code90[ ```r a <- list(a = 1:3, b = "a string", c = pi, d = list(-1, -5)) a # print(a) ``` ``` #> $a #> [1] 1 2 3 #> #> $b #> [1] "a string" #> #> $c #> [1] 3.14 #> #> $d #> $d[[1]] #> [1] -1 #> #> $d[[2]] #> [1] -5 ``` ] -- .pull-right.code90[ .full-width[.content-box-blue.bold.font120.info[除了直接 `print()` 列表的内容之外,更推荐使用 `str()` 函数来查看列表的结构或使用 RStudio's Environment 面板中的对象浏览器]] ```r str(a) ``` ``` #> List of 4 #> $ a: int [1:3] 1 2 3 #> $ b: chr "a string" #> $ c: num 3.14 #> $ d:List of 2 #> ..$ : num -1 #> ..$ : num -5 ``` ] --- layout: true ### 1.2 列表 >> 提取列表元素(_subsetting_) --- .pull-left.code90[ .full-width[.content-box-blue.bold.font120[方法一:用 [ 提取子列表,.red[总是返回列表]]] ```r # 可用 数值|字符|逻辑向量 提取,如a[1:2] # 或a[c(TRUE, TRUE, FALSE, FALSE)] a[c("a", "b")] %>% str() ``` ``` #> List of 2 #> $ a: int [1:3] 1 2 3 #> $ b: chr "a string" ``` ```r # 尽管只有一个元素,但仍是列表 a[3] %>% str() ``` ``` #> List of 1 #> $ c: num 3.14 ``` ```r a[4] %>% str() ``` ``` #> List of 1 #> $ d:List of 2 #> ..$ : num -1 #> ..$ : num -5 ``` ] -- .pull-right.code90[ .full-width[.content-box-blue.bold.font120[方法二:用 [[ 提取单一列表元素,.red[并从列表中移除一个层级]]] ```r a[[3]] %>% str() ``` ``` #> num 3.14 ``` ```r a[[4]] %>% str() ``` ``` #> List of 2 #> $ : num -1 #> $ : num -5 ``` .full-width[.content-box-blue.bold.font120[方法三:`$` 大致等同 [[ ,更方便地提取一个.red[命名列表]的元素]] ```r a$a # a[["a"]] ``` ``` #> [1] 1 2 3 ``` ] --- .pull-left[ <img src="imgs/lists-subsetting-1.png" width="90%" style="display: block; margin: auto;" /> ] -- .pull-right[ <img src="imgs/lists-subsetting-2.png" width="95%" style="display: block; margin: auto;" /> .footnote[.red[ - 图中圆角方框代表**列表**,而普通方框代表**原子向量** - 子元素被包围在父元素之内 ]] ] --- layout: true ### 1.3 向量属性(_attributes_) --- .full-width[.content-box-blue.bold.font120.note[我们可以通过.red[向量属性]来给向量增加任意的额外元数据(metadata),而向量属性可被视作附加在向量上的.red[命名列表]]] -- .full-width[.content-box-blue.bold.font120.note[通过 `attr()` 来设定或获取向量的属性;通过 `attributes()` 来获取全部或批量设置向量的属性信息]] -- .pull-left.code100[ ```r x <- 1:10 attr(x, "source") ``` ``` #> NULL ``` ```r attributes(x) <- list( source = c("Wind", "GTA"), date = Sys.Date() ) attr(x, "cleaned_by") <- "Y.Z" ``` ] -- .pull-right.code100[ ```r attributes(x) ``` ``` #> $source #> [1] "Wind" "GTA" #> #> $date #> [1] "2022-10-06" #> #> $cleaned_by #> [1] "Y.Z" ``` ] --- .full-width[.content-box-blue.bold.font120.note[R 中的向量除了我们之前提到过的“类型 `typeof()`”和“长度 `length()`”两个性质之外,还有三个.red[基础]属性:名称、维度和类]] -- .pull-left.code100[ .full-width[.content-box-blue.bold.font120[①名称属性(_names_):给向量元素命名]] ```r # 命名向量 x = c(1, 2, 3) *names(x) <- c("a", "b", "c") x ``` ``` #> a b c #> 1 2 3 ``` ```r names(x) # attr(x, "names") ``` ``` #> [1] "a" "b" "c" ``` ] -- .pull-right.code90[ .full-width[.content-box-blue.bold.font120[②维度属性(_dimensions_):将向量“改造”为矩阵或数组]] ```r x <- 1:10 *dim(x) <- c(2, 5) # ?matrix x ``` ``` #> [,1] [,2] [,3] [,4] [,5] #> [1,] 1 3 5 7 9 #> [2,] 2 4 6 8 10 ``` ```r class(x); dim(x) # attr(x, "dim") ``` ``` #> [1] "matrix" "array" ``` ``` #> [1] 2 5 ``` ] --- .full-width[.content-box-blue.bold.font120[③类属性(_class_):控制.red[泛型函数(_generic functions_)]的行为方式,这是 R 语言用来实现“S3 面向对象系统”的方法]] -- .pull-left.code100[ ```r # as.Date()是个典型的泛型函数 # 列印as.Date()函数的源代码 as.Date ``` ``` #> function (x, ...) *#> UseMethod("as.Date") #> <bytecode: 0x000001759557f710> #> <environment: namespace:base> ``` ] -- .pull-right.code100[ .font110[**结果显示**,`as.Date()` 函数调用 `UseMethod()`,这就意味着 `as.Date()` 是个泛型函数,它将根据第一个参数 `x` 的类来确定该调用哪个特定方法(`method`)。] ```r # 查看as.Date()泛型函数调用的全部方法 methods("as.Date") ``` ``` #> [1] as.Date.character as.Date.default #> [3] as.Date.factor as.Date.numeric #> [5] as.Date.POSIXct as.Date.POSIXlt #> [7] as.Date.vctrs_sclr* as.Date.vctrs_vctr* #> see '?methods' for accessing help and source code ``` ] --- .code90[ ```r # 查看as.Date()函数指定类(如numeric)所实现的具体方法 getS3method("as.Date", "numeric") # 由于as.Date.numeric()是导出函数,也可用as.Date.numeric直接查看其代码 ``` ``` #> function (x, origin, ...) #> { #> if (missing(origin)) { #> if (!length(x)) #> return(.Date(numeric())) #> if (!any(is.finite(x))) #> return(.Date(x)) #> stop("'origin' must be supplied") #> } #> as.Date(origin, ...) + x #> } #> <bytecode: 0x000001759d68d618> #> <environment: namespace:base> ``` ] -- .font120.info[记住,**类**和**泛型函数**是`R`中非常重要的概念,有很多常见的函数都是泛型函数,具体如 `print()`、`summary()`、`mean()`、[、[[ 和 `$` 等。] --- layout: true ### 1.4 增强向量(_augmented vectors_) --- .full-width[.content-box-blue.bold.font120[增强向量①:因子向量(_factors_)]] -- .pull-left.code90[ ```r (x <- factor(c("A", "B", "A"), levels = c("A", "B", "C"))) ``` ``` #> [1] A B A #> Levels: A B C ``` ```r typeof(x) # 获取向量x的类型 ``` ``` #> [1] "integer" ``` ```r attributes(x) # 获取向量x的属性 ``` ``` #> $levels #> [1] "A" "B" "C" #> #> $class #> [1] "factor" ``` ] -- .pull-right.code100[ ```r # 获取x向量levels属性的便捷函数 levels(x) ``` ``` #> [1] "A" "B" "C" ``` ```r # 获取x向量class属性的便捷函数 class(x) ``` ``` #> [1] "factor" ``` ```r unclass(x) # 移除向量x的类属性 ``` ``` #> [1] 1 2 1 #> attr(,"levels") #> [1] "A" "B" "C" ``` ] --- .full-width[.content-box-blue.bold.font120[增强向量②:日期向量和日期-时间向量(_dates_ and _date-times_)]] -- .pull-left.code90[ ```r (x <- as.Date("1971-01-01")) ``` ``` #> [1] "1971-01-01" ``` ```r typeof(x) ``` ``` #> [1] "double" ``` ```r attributes(x) ``` ``` #> $class #> [1] "Date" ``` ```r unclass(x) # as.integer(x) ``` ``` #> [1] 365 ``` ] -- .pull-right.code90[ ```r (x <- lubridate::ymd_hm( "1970-01-01 01:00")) ``` ``` #> [1] "1970-01-01 01:00:00 UTC" ``` ```r typeof(x); as.double(x) # unclass(x) ``` ``` #> [1] "double" ``` ``` #> [1] 3600 ``` ```r attributes(x) ``` ``` #> $class #> [1] "POSIXct" "POSIXt" #> #> $tzone #> [1] "UTC" ``` ] --- .full-width[.content-box-blue.bold.font120[增强向量②:日期向量和日期-时间向量(_dates_ and _date-times_)]] -- .code90[ ```r (y <- as.POSIXlt(x)) # built on top of named lists ``` ``` #> [1] "1970-01-01 01:00:00 UTC" ``` ] -- .pull-left.code90[ ```r typeof(y) ``` ``` #> [1] "list" ``` ```r attributes(y) ``` ``` #> $names #> [1] "sec" "min" "hour" "mday" "mon" #> [6] "year" "wday" "yday" "isdst" #> #> $class #> [1] "POSIXlt" "POSIXt" #> #> $tzone #> [1] "UTC" ``` ] -- .pull-right.code90[ ```r unclass(y) %>% str ``` ``` #> List of 9 #> $ sec : num 0 #> $ min : int 0 #> $ hour : int 1 #> $ mday : int 1 #> $ mon : int 0 #> $ year : int 70 #> $ wday : int 4 #> $ yday : int 0 #> $ isdst: int 0 #> - attr(*, "tzone")= chr "UTC" ``` ```r y$year # 提取列表y的"year"元素 ``` ``` #> [1] 70 ``` ] --- .full-width[.content-box-blue.bold.font120[增强向量③:`dataframe` 和 `tibble`]] -- .pull-left.code90[ ```r df <- data.frame( x = LETTERS[1:3], y = 0:2, `:)` = runif(3)) typeof(df) ``` ``` #> [1] "list" ``` ```r attributes(df) ``` ``` #> $names *#> [1] "x" "y" "X.." #> #> $class #> [1] "data.frame" #> #> $row.names #> [1] 1 2 3 ``` ] -- .pull-right.code90[ ```r tb <- tibble( x = LETTERS[1:3], y = 0:2, `:)` = runif(3)) typeof(tb) ``` ``` #> [1] "list" ``` ```r attributes(tb) ``` ``` #> $class *#> [1] "tbl_df" "tbl" "data.frame" #> #> $row.names #> [1] 1 2 3 #> #> $names *#> [1] "x" "y" ":)" ``` ] --- .full-width[.content-box-blue.bold.font120[增强向量③:`dataframe` 和 `tibble` <sup>.red[*]</sup>]] .footnote.red[*:`dataframe` 和 `tibble` 的底层是个 `list`,每列是 `list` 的一个元素,但要求每列都必须是等长的向量] -- .pull-left.code90[ ```r # 看看df底层的list unclass(df) %>% str() ``` ``` #> List of 3 #> $ x : chr [1:3] "A" "B" "C" #> $ y : int [1:3] 0 1 2 #> $ X..: num [1:3] 0.6216 0.0929 0.0138 #> - attr(*, "row.names")= int [1:3] 1 2 3 ``` ```r df # 列印df的内容 ``` ``` #> x y X.. #> 1 A 0 0.6216 #> 2 B 1 0.0929 #> 3 C 2 0.0138 ``` ] -- .pull-right.code90[ ```r # 看看tb底层的list unclass(tb) %>% str() ``` ``` #> List of 3 #> $ x : chr [1:3] "A" "B" "C" #> $ y : int [1:3] 0 1 2 #> $ :): num [1:3] 0.1062 0.1188 0.0625 #> - attr(*, "row.names")= int [1:3] 1 2 3 ``` ```r tb # 列印tb的内容 ``` ``` *#> # A tibble: 3 × 3 #> x y `:)` *#> <chr> <int> <dbl> #> 1 A 0 0.106 #> 2 B 1 0.119 #> 3 C 2 0.0625 ``` ] --- .full-width[.content-box-blue.bold.font120[增强向量③:`dataframe` 和 `tibble`]] -- .pull-left.code100[ ```r # 提取tb的第1列和第3列 tb[c(1, 3)] ``` ``` #> # A tibble: 3 × 2 #> x `:)` #> <chr> <dbl> #> 1 A 0.106 #> 2 B 0.119 #> 3 C 0.0625 ``` ] -- .pull-right.code100[ ```r # 提取第2列,仍结果为tibble # 也可用tb[2] tb["y"] ``` ``` #> # A tibble: 3 × 1 #> y #> <int> #> 1 0 #> 2 1 #> 3 2 ``` ```r # 提取第2列,但结果为向量 # 也可用tb[[2]]或tb[["y"]] tb$y ``` ``` #> [1] 0 1 2 ``` ] --- .full-width[.content-box-blue.bold.font120[增强向量③:`dataframe` 和 `tibble`]] .code100[ ```r # tb(df)具有类似矩阵(matrix)类的性质 # tb的维度“属性” dim(tb) ``` ``` #> [1] 3 3 ``` ] -- .pull-left.code100[ ```r # 用提取子矩阵的方法提取子集 tb[c(1, 3), c("y", ":)")] ``` ``` #> # A tibble: 2 × 2 #> y `:)` #> <int> <dbl> #> 1 0 0.106 #> 2 2 0.0625 ``` ] -- .pull-right.code100[ ```r # 用dplyr工具可能更为直观 tb %>% slice(c(1, 3)) %>% select(y, ":)") ``` ``` #> # A tibble: 2 × 2 #> y `:)` #> <int> <dbl> #> 1 0 0.106 #> 2 2 0.0625 ``` ] --- layout: false ## 🙋♂️ Your Turn! <style type="text/css"> #special_timer.running { background-color: black; background-image: url(imgs/bg-stars.gif); } #special_timer.finished { background-color: black; background-image: url(imgs/bg-sqfw.gif); background-size: cover; } #special_timer.running .countdown-digits { color: #fdf6e3; } #special_timer.finished .countdown-digits { color: #fdf6e3; } </style>
−
+
03
:
00
.panelset.font150[ .panel[.panel-name[Question?] 1. 以嵌套集合的方式表示列表 `list(1, 2, list(3, 4), list(5, c(6, 7)))`; 2. 如何从上述列表中提取出 `list(3, 4)`? ] .panel[.panel-name[Answer!] 1. > <img src="imgs/myList.png" width="20%" style="display: block; margin: auto auto auto 0;" /> 2. `myList[[3]]` ] ] --- layout: false ## 🙋♂️ Your Turn!
−
+
03
:
00
.panelset.font120[ .panel.font120[.panel-name[Question?] 1. `hms::hms(3600)` 会返回什么?返回值如何输出?这种扩展向量是基于哪种基本类型构造的?使用了哪种向量属性? ] .panel[.panel-name[Answer!] > ```r x <- hms::hms(3600) x ``` ``` #> 01:00:00 ``` ```r typeof(x) ``` ``` #> [1] "double" ``` ```r attributes(x) ``` ``` #> $units #> [1] "secs" #> #> $class #> [1] "hms" "difftime" ``` ] ] --- layout: false class: inverse, center, middle # 2. 函数 .font150[(_functions_)] --- ### 2.1 使用函数的优点 -- .pull-left.font130[ .full-width.content-box-blue.bold.note[和复制-粘贴-修改比,使用函数的优点] - 清晰的函数名让代码更容易理解 - 当要求发生变化时,你只需要在一个地方进行改动 - 消除复制-粘贴-修改过程中可能出现的无心之误 - 有时你必须写函数(如某些函数的参数就是函数) .full-width[.content-box-blue.bold[来围观一个栗子 👉]] ] -- .pull-right[ ```r set.seed(1234) df <- tibble::tibble( a = rnorm(10), b = rnorm(10), c = rnorm(10) ) # 甲:以上生成正态分布的随机变量并组装成df …… # 乙:这我明白! ``` ```r # 甲:那么请问,以下又是神马操作? # 乙:…… 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)) ``` ] --- layout: true ### 2.2 编写函数的套路 --- .full-width[.content-box-blue.bold.font120[Step#1 分析重复性的代码 <sup>.red[*1]</sup>]] .code110[ ```r (df$a - min(df$a, na.rm = TRUE)) / (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE)) ``` ] -- <br> .full-width[.content-box-blue.bold.font120[Step#2 将函数的输入提取为临时变量 <sup>.red[*2]</sup>]] .code110[ ```r x <- df$a (x - min(x, na.rm = TRUE)) / (max(x, na.rm = TRUE) - min(x, na.rm = TRUE)) ``` ``` #> [1] 0.332 0.765 1.000 0.000 0.809 0.831 0.516 0.524 0.519 0.424 ``` ] .footnote.red[ \*1:可选步骤。 \*2: 多个__输入__也可类似处理。 ] --- .full-width[.content-box-blue.bold.font120[Step#3 包装成函数 [ -> 改进] -> 测试]] .code100[ ```r rescale01 <- function(x) { (x - min(x, na.rm = TRUE)) / (max(x, na.rm = TRUE) - min(x, na.rm = TRUE)) } rescale01(df$a) # 测试看看结果是否正确 ``` ``` #> [1] 0.332 0.765 1.000 0.000 0.809 0.831 0.516 0.524 0.519 0.424 ``` ] -- .code100[ ```r # 改进之:使用range(),一举得到min和max rescale01 <- function(x) { rng <- range(x, na.rm = TRUE) (x - rng[1]) / (rng[2] - rng[1]) } rescale01(df$a) # 测试看看结果是否正确 ``` ``` #> [1] 0.332 0.765 1.000 0.000 0.809 0.831 0.516 0.524 0.519 0.424 ``` ] --- .full-width[.content-box-blue.bold.font120[应用新编写的函数]] .code95[ ```r df$a <- rescale01(df$a) df$b <- rescale01(df$b) df$c <- rescale01(df$c) df ``` ``` #> # A tibble: 10 × 3 #> a b c #> <dbl> <dbl> <dbl> #> 1 0.332 0.153 0.782 #> 2 0.765 0 0.473 #> 3 1 0.0651 0.498 #> # … with 7 more rows ``` ] -- .full-width.content-box-blue.bold.font120[但 …… 上面的代码还是有点 WET,不够 DRY!] .full-width.content-box-blue.bold.font120.info[解决方案:用 `for` 循环或函数式编程] --- .full-width[.content-box-blue.bold.font120.note[R 中函数的构成要素]] <br> .code100[ ```r rescale01 <- # 函数名:rescale01 function(x) # 函数参数: <- formals() { # 函数体:{ <- body() rng <- range(x, na.rm = TRUE) # ... (x - rng[1]) / (rng[2] - rng[1]) # ... } # } + 函数环境 <- enviornment() ``` ] --- .pull-left.font110[ .full-width[.content-box-blue.font110.bold.note[函数名]] - 明晰易懂、建议为动词 - 推荐`snake_case`命名法 - 保持一致,如统一前缀 .code90[ ```r # Long, but clear impute_missing() collapse_years() # common prefix for a family stringr::str_*() # Never do this! col_mins <- function(x, y) {} rowMaxes <- function(y, x) {} ``` ] ] -- .pull-right.font110[ .full-width[.content-box-blue.font110.bold.note[函数体]] - 代码缩进,形成层级结构 - 使用空格 - 合理使用注释 - 说明函数目的 - 代码分块(Ctrl+Shift+R) - 使用中间变量 <br> - **保持一致风格** 👉 `styler` 包 ] --- layout: true ### 2.3 函数的参数 --- .full-width[.content-box-blue.bold.font120.note[函数的参数大致可划分为两类:]] .font110[ 1. 提供函数操作的(数据)对象 2. 控制函数如何操作对象的具体细节 ] .code90[ ```r ?t.test # args(t.test)或formals(t.test)在此不好用 # t.test(x, y = NULL, # alternative = c("two.sided", "less", "greater"), # mu = 0, paired = FALSE, var.equal = FALSE, # conf.level = 0.95, ...) ``` ] -- .full-width[.content-box-blue.bold.font120.info[通常把对象参数放在最前面,其余参数则按重要性排序,并设定默认值]] -- .pull-left[ .full-width[.content-box-blue.bold.font120.info[参数名可采用约定俗成的名字,并与通常用法保持一致 👉 ]] ] .pull-right.font90[ |参数名 |惯常含义 |参数名 |惯常含义 | |:------------|:-----------------|:------------|:--------------------| |`x`, `y`, `z`|数据向量 |`i`, `j` |行列序号 | |`df` |数据框 |`n` |向量长度或数据框行数 | |`w` |权重向量 |`na.rm` |是否移除缺失值 | ] --- .full-width[.content-box-blue.bold.font120.info[特殊参数 `...`]] -- .code100[ ```r # 很多R函数接受任意数量的输入,此时可用...来捕捉未被匹配的参数 sum(1, 2, 3, 4, 5, NA, 7, 8, 9, 10, na.rm = TRUE) ``` ``` #> [1] 49 ``` ] -- .code100[ ```r # 也可用...将不想直接处理的参数传递至函数体内调用的底层函数,如 rule <- function(..., pad = "-") { title <- paste0(...) width <- getOption("width") - nchar(title) - 6 cat("## ", title, " ", stringr::str_dup(pad, width), "\n", sep = "") } rule("Important output", pad = "=") ``` ``` ## Important output ================================================ ``` ] --- .full-width[.content-box-blue.bold.font120.info[在函数体中对重要参数的取值进行检验]] .code90[ ```r wt_mean <- function(x, w, na.rm = FALSE) { * if (length(x) != length(w)) { # conditional execution stop("`x` and `w` must be the same length", call. = FALSE) } * stopifnot(is.logical(na.rm), length(na.rm) == 1) * if (na.rm) { miss <- is.na(x) | is.na(w) x <- x[!miss] w <- w[!miss] } sum(w * x) / sum(w) } ``` ] -- .pull-left[ ```r wt_mean(1:6, 5:1, na.rm = "foo") ``` ``` #> Error: `x` and `w` must be the same length ``` ] -- .pull-right[ ```r wt_mean(1:6, 6:1, na.rm = "foo") ``` ``` #> Error in wt_mean(1:6, 6:1, na.rm = "foo"): is.logical(na.rm) is not TRUE ``` ] --- .full-width[.content-box-blue.bold.font120.note[调用函数时将_实际参数_映射到_形式参数_三种方法的优先级依次为:]] .bold.font120[ 名字完全匹配 >> 名字前缀部分匹配 >> 位置匹配] -- .full-width[.content-box-blue.bold.font120.note[调用函数时通常可略去对象参数名,而其余参数则建议使用全名]] -- .code100[ ```r # Good mean(1:10, na.rm = TRUE) ggplot(mpg, aes(x = displ, y = hwy)) + geom_point() ``` ] -- .code100[ ```r # Bad mean(1:10, , TRUE) # 位置匹配 mean(n = TRUE, x = 1:10) # 简写“细节”参数名 mean(, TRUE, x = 1:10) # 参数名匹配 + 位置匹配 ``` ] --- layout: true ### 2.4 函数的返回值 --- .full-width[.content-box-blue.bold.font120.note[通常函数返回其最后一个语句求值的结果]] -- .full-width[.content-box-blue.bold.font120.note[当然你也可以用 `return()` 来提前返回结果,从而让代码更易读]] -- .pull-left.code90[ ```r f <- function() { if (condition) { # Do # something # that # takes # many # lines # to # express } else { # return something short } } ``` ] -- .pull-right.code90[ ```r f <- function() { if (!condition) { * return(something_short) } # Do # something # that # takes # many # lines # to # express } ``` ] --- .full-width[.content-box-blue.bold.font120.note[编写支持管道操作 `%>%` 的函数]] - .font120[.bold[_transformation_ 类型的函数]:直接对输入的(数据)对象进行转化操作,生成新的(数据)对象。此类函数天然支持管道操作。] - .font120[.bold[_side-effect_ 类型的函数]:就输入的(数据)对象完成特定任务,如赋值 `<-`、打印结果、作图、存入文档等。此类函数最好能用 `invisible()` 不可见地返回输入对象,从而支持管道操作。] -- .pull-left.code90[ ```r show_missings <- function(df) { n <- sum(is.na(df)) cat("Missing values: ", n, "\n", sep = "") * invisible(df) } ``` ```r x <- show_missings(mtcars) ``` ``` #> Missing values: 0 ``` ```r class(x) ``` ``` #> [1] "data.frame" ``` ] -- .pull-right.code90[ ```r library(dplyr) mtcars %>% show_missings() %>% mutate( mpg = ifelse(mpg < 20, NA, mpg) ) %>% show_missings() ``` ``` #> Missing values: 0 #> Missing values: 18 ``` ] --- layout: false ### 2.5 函数的环境 .code90[ ```r f <- function(x) { x + y } ``` ] -- .full-width[.content-box-blue.bold.font120.info[前面定义的 `f` 看起来像个奇怪的函数:只有一个参数 `x`,希望返回 `x + y` 的值,但却没有对 `y` 进行定义和赋值 😲]] -- .full-width[.content-box-blue.bold.font120.info[但在 R 中这是个完全合法的函数,R 会根据所谓的“词法作用域”规则(_lexical scoping_)来查找 `y` 的取值,查找的结果受到函数环境的影响。在本例中,由于在函数 `f` 中并未定义 `y`,R 就会依次到创建函数 `f` 的环境(即封闭环境)及其父环境中查找 `y` 的值。]] .pull-left.code90[ ```r environment(f) ``` ``` #> <environment: R_GlobalEnv> ``` ] -- .pull-right.code90[ ```r x <- 100; y <- 100; f(10) ``` ``` #> [1] 110 ``` ```r x <- 100; y <- 1000; f(10) ``` ``` #> [1] 1010 ``` ] --- background-image: url(imgs/first-class.png) background-size: 15em background-position: 90% 90% layout: false ### 2.6 作为一等公民的函数 .full-width[.content-box-blue.bold.font120.info[R 的函数还是所谓的 _first-class_ 函数,适用于向量的所有操作也都适用于函数]] .font120[ 1. 可以将函数赋值给变量(当然也可以不 -> _匿名函数_) 2. 将函数存储在列表中(_函数列表_) 3. 在函数内创建函数(_函数工厂_ 与 _闭包_) 4. 将函数作为参数传递给其它函数(_泛函_) 5. 甚至把函数作为一个函数的结果返回(_函数工厂_ 与 _函数运算符_) ] --- layout: false ## 🙋♂️ Your Turn!
−
+
05
:
00
.panelset.font120[ .panel.font120[.panel-name[Question?] 1. 将下面的代码片段转换成函数。思考一下每个函数的作用,你应该为新函数选择什么样的名称? ```r mean(is.na(x)) x / sum(x, na.rm = TRUE) sd(x, na.rm = TRUE) / mean(x, na.rm = TRUE) ``` ] .panel[.panel-name[Answer!] - `mean(is.na(x))` takes a single argument `x`, and returns a single numeric value between 0 and 1 -> `prop_na()` - `x / sum(x, na.rm = TRUE)` standardizes a vector so that it sums to one -> `sum_to_one()` - `sd(x, na.rm = TRUE) / mean(x, na.rm = TRUE)` calculates the coefficient of variation -> `coef_variation()` ] ] --- layout: false class: inverse, center, middle # 课后作业 --- .large[ 🕐 结合讲义内容**复习** 📖 [_R for Data Science_](https://r4ds.had.co.nz/) 一书的第 19 章和第 20 章 ] -- .large[ 🕑 进一步完善并重新提交 *L03_RMarkdown与Quarto* 的课后作业 📑 ] -- .pull-left[ 1. 由老师(基于某些考虑因素)半随机分组(已完成,见右表 >>) 2. 小组成员以之前提交的多份 L03 课后作业为基础,碰头讨论并相互学习借鉴,以小组为单位重新制作一份课后作业 3. **在 slides 文档中增加若干页,记录小组讨论过程与内容、新作业的改进之处并根据组员的参与度和贡献大小分配小组的总分数(总分数 = 10 * 小组成员数)** 4. 确认两份文档运行无误后将文件夹📂 *L03_HW* 打包为 .zip 格式压缩文档,由小组中的**一位**同学于2022年10月12日22:00前将压缩包提交至 [{{坚果云链接}}](https://workspace.jianguoyun.com/inbox/collect/76ce62d2266649c68d899995c0afd1da/submitv2) 5. 老师将从同学们提交的作业中选取 3~5 份优秀作业,展示于课程网站上,供同学们学习参考 ] .pull-right.font110[ |成员1 |成员2 |成员3 |成员4 | |:------|:------|:------|:--------| |白雨婷 |王梓萌 |李芳玉 |严邓荆雅 | |陈颖倩 |邹桢皓 |刘之荣 |谢柏杰 | |高杨驰 |于小珊 |陈杰 |李世杰 | |胡安琪 |焦雨欣 |应舒恬 |赵子钧 | |李红浪 |杨星雨 |王圣鑫 |于野 | |林臻杰 |张欣 |王雨彤 |田伊迪 | |刘向尧 |胡典 |路旭飞 |龚福深 | |陆思橙 |林宸 |曹煜 |陈智杰 | |唐秋婷 |邵嘉蓉 |梅云开 |李植丹 | |詹辉铭 |李康平 |张婧瑶 |张道琳 | |张欣宇 |洪瑜蔓 |徐逸祺 | | ] --- class: middle -- .pull-left.font120[ <br> .font120[ **复习** 📖 [_R for Data Science_](https://r4ds.had.co.nz/) 一书以下章节,并(结队)**完成课后练习**。 ] - 第 1、2、17、26 章 (_Introduction_) - 第 4、6、8 章 (_Workflows_) - 第 3、28 章 (_Visualization_) - 第 27、29、30 章 (_R Markdown_) - 第 19、20 章 (_Functions & Vectors_) <br><br> ] -- .pull-right.font90[ <img src="imgs/r4ds-exercise-solutions-cover.png" width="60%" style="display: block; margin: auto;" /> <https://jrnold.github.io/r4ds-exercise-solutions/> ] --- class: center middle background-image: url(imgs/xaringan.png) background-size: 12% background-position: 50% 40% <br><br><br><br><br><br><br> <hr color='#f00' size='2px' width='80%'> <br> .Large.red[_**本网页版讲义的制作由 R 包 [{{`xaringan`}}](https://github.com/yihui/xaringan) 赋能!**_]