class: center, middle, inverse, title-slide .title[ # 量化金融与金融编程 ] .subtitle[ ## L06 向量与函数 ] .author[ ###
曾永艺 ] .institute[ ### 厦门大学管理学院 ] .date[ ###
2023-11-10 ] ---
.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[ ## __2. 函数__ * .font140.bold[使用函数的优点] * .font140.bold[编写函数的套路] * .font140.bold[函数的参数] * .font140.bold[函数的返回值] * .font140.bold.gray[函数的环境] * .font140.bold.gray[`{{ tidyverse }}` <sup>.red.font80[*]</sup>] * .font140.bold.gray[作为一等公民的函数] .footnote.red.font80.right[\* i.e., functions that __embrace__ `tidyverse`.] ] --- 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.47 ``` ```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] TRUE FALSE FALSE FALSE FALSE TRUE FALSE TRUE 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 in `tibble()`: #> ! 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的位置也会被 # 保留下来 x[x %% 2 == 0] # 提取偶数或缺失值 ``` ``` #> [1] 10 NA ``` ```r x[x %% 2 == 1] # 提取奇数或缺失值 ``` ``` #> [1] 3 NA 5 ``` ] -- .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 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="92%" style="display: block; margin: auto;" /> ] -- .pull-right[ .font110.bold[另一种图示说明] <img src="imgs/lists-subsetting-2.png" width="92%" 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] "2023-11-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 ``` ] ------------------|----------------------------|----------------- `names()` | `rownames()`, `colnames()` | `dimnames()` `length()` | `nrow()`, `ncol()` | `dim()` `c()` | `rbind()`, `cbind()` | `abind::abind()` — | `t()` | `aperm()` `is.null(dim(x))` | `is.matrix()` | `is.array()` --- .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: 0x00000280f20921c0> #> <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 ``` ] --- .code100[ ```r # 查看as.Date()函数指定类(如numeric)所实现的具体方法 getS3method("as.Date", "numeric") ``` ``` #> function (x, origin, ...) #> if (missing(origin)) .Date(x) else as.Date(origin, ...) + x #> <bytecode: 0x00000280fc7df648> #> <environment: namespace:base> ``` ```r # 由于as.Date.numeric()是导出函数,也可用as.Date.numeric直接查看其代码 ``` ] -- <br> .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" #> [5] "mon" "year" "wday" "yday" #> [9] "isdst" "zone" "gmtoff" #> #> $class #> [1] "POSIXlt" "POSIXt" #> #> $tzone #> [1] "UTC" #> #> $balanced #> [1] TRUE ``` ] -- .pull-right.code90[ ```r unclass(y) %>% str ``` ``` #> List of 11 #> $ 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 #> $ zone : chr "UTC" #> $ gmtoff: int 0 #> - attr(*, "tzone")= chr "UTC" #> - attr(*, "balanced")= logi TRUE ``` ```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.409 0.456 0.905 #> - attr(*, "row.names")= int [1:3] 1 2 3 ``` ```r df # 列印df的内容 ``` ``` #> x y X.. #> 1 A 0 0.409 #> 2 B 1 0.456 #> 3 C 2 0.905 ``` ] -- .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.267 0.315 0.35 #> - 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.267 #> 2 B 1 0.315 #> 3 C 2 0.350 ``` ] --- .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.267 #> 2 B 0.315 #> 3 C 0.350 ``` ] -- .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.267 #> 2 2 0.350 ``` ] -- .pull-right.code100[ ```r # 用dplyr工具可能更为直观 tb %>% slice(c(1, 3)) %>% select(y, ":)") ``` ``` #> # A tibble: 2 × 2 #> y `:)` #> <int> <dbl> #> 1 0 0.267 #> 2 2 0.350 ``` ] --- 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 # 甲:以下生成正态分布的随机变量并组装成df …… set.seed(1234) df <- tibble::tibble( a = rnorm(10), b = rnorm(10), c = 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)) # 乙:~~~ ``` ] --- 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[应用新编写的函数 `rescale01()`]] .pull-left[ .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 #> 4 0 0.311 0.943 #> 5 0.809 0.573 0.373 #> 6 0.831 0.260 0 #> # ℹ 4 more rows ``` ] ] -- .pull-right.font120.bold[ 😒 但 …… <br>  左边的示例代码还是<br>  有点 WET <sup>.red[1]</sup>,<br>  不够 DRY <sup>.red[2]</sup>! <br> .info[ 解决方案<br>  用 `for` 循环或函数式编程,<br>  这是下一讲才会讲到的内容。] .footnote.font80.red[ 1: _Write Everything Twice_ 2: _Don't Repeat Yourself_ ] ] --- .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.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 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) 赋能!**_]