(vectors)
逻辑向量的元素只有三种可能取值:TRUE | FALSE | NA
逻辑向量通常由比较运算(?Comparison)生成
1:10 %% 3 == 0
#> [1] FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE FALSE当然也可以用 c() 直接构造逻辑向量
c(TRUE, FALSE, NA, T, F) # c(): Combine Values into a Vector or List
#> [1] TRUE FALSE NA TRUE FALSE强烈建议不要使用 TRUE 和 FALSE 的缩写形式 T 和 F
T <- FALSE; F <- 2; c(TRUE, FALSE, NA, T, F); rm(list = c("T", "F"))
#> [1] 1 0 NA 0 2数值向量包括整数向量(integer)和实数向量(double)
默认为实数,除非在整数后加上 L
typeof(1);typeof(1L);typeof(1.5L)
#> [1] "double"#> [1] "integer"#> [1] "double"R 用双精度来存储实数向量的数值,但在很多情况下只是近似值,因此不要用 == 直接比较实数向量的取值,而用 dplyr::near()
整数向量只有一个特殊值 NA,而实数向量则有四个特殊值 NA、NaN、Inf 和 -Inf;用 is.*() 而不要直接用 == 检测这些特殊值
c(-1, 0, 1, NA) / 0 # 4个特殊值#> [1] -Inf NaN Inf NA
0 |
Inf |
NA |
NaN |
|
|---|---|---|---|---|
is.finite() |
√ | |||
is.infinite() |
√ | |||
is.na() |
√ | √ | ||
is.nan() |
√ |
字符向量的每个元素由字符串构成,可用来表示任意数据
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"R 使用全局字符串池,这意味着每个字符串在内存中只存一次
x <- "This is a long string."object.size(x)# lobstr::obj_size(x)
#> 136 bytesy <- rep(x, 1000)object.size(y)
#> 8128 bytes# 8 * 1000 + 128 B
显性转换:as.logical() | as.integer() | as.double() | as.character()
as.integer(c("1", "2")) + c(10, 11) ## [1] 11 13
隐性转换:当向量用于需要特定类型输入的函数时,R 会自动将向量转换为特定类型(但非总是如此)
x <- sample(1:20, 100, replace = TRUE)# what proportion > 10?mean(x > 10)
#> [1] 0.52x + "3" # 但并非总是如此
#> Error in x + "3": non-numeric argument to binary operator显性转换:as.logical() | as.integer() | as.double() | as.character()
as.integer(c("1", "2")) + c(10, 11) ## [1] 11 13
隐性转换:当向量用于需要特定类型输入的函数时,R 会自动将向量转换为特定类型(但非总是如此)
x <- sample(1:20, 100, replace = TRUE)# what proportion > 10?mean(x > 10)
#> [1] 0.52x + "3" # 但并非总是如此
#> Error in x + "3": non-numeric argument to binary operator隐性转换:当用 c() 联结不同类型的向量时,R 会套用最复杂的类型
c(FALSE, 1L, 1.5, "a", 1 + 1i, raw(2))
#> [1] "FALSE" "1" "1.5" "a" #> [5] "1+1i" "00" "00"1 == "1"; -1 < FALSE; "one" < 2
#> [1] TRUE#> [1] TRUE#> [1] FALSER 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度
runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE TRUE FALSE FALSE FALSE TRUE FALSE TRUE TRUE当较长向量不是较短向量的整数倍时,R 照样会进行转换,但会有个 Warning message
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 6R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度
runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE TRUE FALSE FALSE FALSE TRUE FALSE TRUE TRUE当较长向量不是较短向量的整数倍时,R 照样会进行转换,但会有个 Warning message
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当向量的长短不一时(标量除外),要求更严格的 tidyverse 则会抛出 Error,停止运行
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.[ 函数选取元素(subsetting)方法一:用整数向量选取元素
x <- c("one", "two", "three", "four", "five")x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素
#> [1] "two" "four"x[c(1, -3, 5)] # 不可混用正整数和负整数
#> Error in x[c(1, -3, 5)]: only 0's may be mixed with negative subscripts[ 函数选取元素(subsetting)方法一:用整数向量选取元素
x <- c("one", "two", "three", "four", "five")x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素
#> [1] "two" "four"x[c(1, -3, 5)] # 不可混用正整数和负整数
#> Error in x[c(1, -3, 5)]: only 0's may be mixed with negative subscriptsx[0] # 返回空值(但非NULL)
#> character(0)[ 函数选取元素(subsetting)方法一:用整数向量选取元素
x <- c("one", "two", "three", "four", "five")x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素
#> [1] "two" "four"x[c(1, -3, 5)] # 不可混用正整数和负整数
#> Error in x[c(1, -3, 5)]: only 0's may be mixed with negative subscriptsx[0] # 返回空值(但非NULL)
#> character(0)x[c(1.2, 2.3, 3.4, 4.5, 5.6, 6.7)] # 想想,这会返回什么?[ 函数选取元素(subsetting)方法二:用逻辑向量选取元素,保留逻辑向量取值为 TRUE 位置的元素
x <- c(10, 3, NA, 5)x[!is.na(x)] # 提取非缺失值
#> [1] 10 3 5# 注意:逻辑向量取值为NA的位置也会被# 保留下来x[x %% 2 == 0] # 提取偶数或缺失值
#> [1] 10 NAx[x %% 2 == 1] # 提取奇数或缺失值
#> [1] 3 NA 5方法三:用字符向量选取命名向量的元素
# 命名向量:创建时直接命名abc <- c(a = 1, b = 2, c = 3)# 命名向量:事后再命名abc <- 1:3abc <- purrr::set_names( # 也可用base包的setNames() x = abc, nm = c("a", "b", "c"))
# 提取相应命名的元素abc[c("a", "c", "b", "a", "d")]
#> a c b a <NA> #> 1 3 2 1 NA和原子向量不同,列表可同时包含多种类型(type)的元素(甚至嵌套下一级的列表),这使得列表很适合用来存储混合或树状结构的数据
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除了直接 print() 列表对象的内容之外,更推荐使用 str() 函数 / RStudio Environment 面板来查看 简单 / 复杂 列表对象的结构
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方法一:用 [ 提取子列表,总是返回列表
# 可用 数值|字符|逻辑向量 提取,如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"# 尽管只有一个元素,但仍是列表a[3] %>% str()
#> List of 1#> $ c: num 3.14a[4] %>% str()
#> List of 1#> $ d:List of 2#> ..$ : num -1#> ..$ : num -5方法一:用 [ 提取子列表,总是返回列表
# 可用 数值|字符|逻辑向量 提取,如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"# 尽管只有一个元素,但仍是列表a[3] %>% str()
#> List of 1#> $ c: num 3.14a[4] %>% str()
#> List of 1#> $ d:List of 2#> ..$ : num -1#> ..$ : num -5方法二:用 [[ 提取单一列表元素,并从列表中移除一个层级
a[[3]] %>% str()
#> num 3.14a[[4]] %>% str()
#> List of 2#> $ : num -1#> $ : num -5方法三:$ 大致等同 [[ ,能更方便地提取一个命名列表的元素
a$a # a[["a"]]
#> [1] 1 2 3我们可以通过向量属性来给向量增加任意的额外元数据(metadata),而向量属性可被视作附加在向量上的命名列表
通过 attr() 来设定或获取向量的属性;通过 attributes() 来获取全部或批量设置向量的属性信息
x <- 1:10attr(x, "source")
#> NULLattributes(x) <- list( source = c("Wind", "GTA"), date = Sys.Date())attr(x, "cleaned_by") <- "Y.Z"
attributes(x)
#> $source#> [1] "Wind" "GTA" #> #> $date#> [1] "2023-11-09"#> #> $cleaned_by#> [1] "Y.Z"R 中的向量除了我们之前提到过的“类型 typeof()”和“长度 length()”两个性质之外,还有三个基础属性:名称、维度和类
①名称属性(names):给向量元素命名
# 命名向量x = c(1, 2, 3)names(x) <- c("a", "b", "c")x
#> a b c #> 1 2 3names(x) # attr(x, "names")
#> [1] "a" "b" "c"②维度属性(dimensions):将向量“改造”为矩阵或数组
x <- 1:10dim(x) <- c(2, 5) # ?matrixx
#> [,1] [,2] [,3] [,4] [,5]#> [1,] 1 3 5 7 9#> [2,] 2 4 6 8 10class(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()
③类属性(class):控制泛型函数(generic functions)的行为方式,这是 R 语言用来实现“S3 面向对象系统”的方法
# as.Date()是个典型的泛型函数# 列印as.Date()函数的源代码as.Date
#> function (x, ...) #> UseMethod("as.Date")#> <bytecode: 0x000001ed6dcbef18>#> <environment: namespace:base>结果显示,as.Date() 函数调用 UseMethod(),这就意味着 as.Date() 是个泛型函数,它将根据第一个参数 x 的类来确定该调用哪个特定方法(method)。
# 查看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# 查看as.Date()函数指定类(如numeric)所实现的具体方法getS3method("as.Date", "numeric")
#> function (x, origin, ...) #> if (missing(origin)) .Date(x) else as.Date(origin, ...) + x#> <bytecode: 0x000001ed6c7ac3e8>#> <environment: namespace:base># 由于as.Date.numeric()是导出函数,也可用as.Date.numeric直接查看其代码
# 查看as.Date()函数指定类(如numeric)所实现的具体方法getS3method("as.Date", "numeric")
#> function (x, origin, ...) #> if (missing(origin)) .Date(x) else as.Date(origin, ...) + x#> <bytecode: 0x000001ed6c7ac3e8>#> <environment: namespace:base># 由于as.Date.numeric()是导出函数,也可用as.Date.numeric直接查看其代码
记住,类和泛型函数是R中非常重要的概念,有很多常见的函数都是泛型函数,具体如 print()、summary()、mean()、[、[[ 和 $ 等。
增强向量①:因子向量(factors)
(x <- factor(c("A", "B", "A"), levels = c("A", "B", "C")))
#> [1] A B A#> Levels: A B Ctypeof(x) # 获取向量x的类型
#> [1] "integer"attributes(x) # 获取向量x的属性
#> $levels#> [1] "A" "B" "C"#> #> $class#> [1] "factor"# 获取x向量levels属性的便捷函数levels(x)
#> [1] "A" "B" "C"# 获取x向量class属性的便捷函数class(x)
#> [1] "factor"unclass(x) # 移除向量x的类属性
#> [1] 1 2 1#> attr(,"levels")#> [1] "A" "B" "C"增强向量②:日期向量和日期-时间向量(dates and date-times)
(x <- as.Date("1971-01-01"))
#> [1] "1971-01-01"typeof(x)
#> [1] "double"attributes(x)
#> $class#> [1] "Date"unclass(x) # as.integer(x)
#> [1] 365(x <- lubridate::ymd_hm( "1970-01-01 01:00"))
#> [1] "1970-01-01 01:00:00 UTC"typeof(x); as.double(x) # unclass(x)
#> [1] "double"#> [1] 3600attributes(x)
#> $class#> [1] "POSIXct" "POSIXt" #> #> $tzone#> [1] "UTC"增强向量②:日期向量和日期-时间向量(dates and date-times)
(y <- as.POSIXlt(x)) # built on top of named lists
#> [1] "1970-01-01 01:00:00 UTC"typeof(y)
#> [1] "list"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增强向量②:日期向量和日期-时间向量(dates and date-times)
(y <- as.POSIXlt(x)) # built on top of named lists
#> [1] "1970-01-01 01:00:00 UTC"typeof(y)
#> [1] "list"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] TRUEunclass(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 TRUEy$year # 提取列表y的"year"元素
#> [1] 7003:00
hms::hms(seconds = 1, minutes = 1, hours = 1)
返回值如何输出?
这种扩展向量是基于哪种基本类型构造的?
使用了哪些向量属性?
(x <- hms::hms(seconds = 1, minutes = 1, hours = 1)) # hms::hms(seconds = 3661)#> 01:01:01class(x); typeof(x); as.numeric(x)#> [1] "hms" "difftime"#> [1] "double"#> [1] 3661attributes(x)#> $units#> [1] "secs"#> #> $class#> [1] "hms" "difftime"增强向量③:dataframe 和 tibble
df <- data.frame( x = LETTERS[1:3], y = 0:2, `:)` = runif(3))typeof(df)
#> [1] "list"attributes(df)
#> $names#> [1] "x" "y" "X.."#> #> $class#> [1] "data.frame"#> #> $row.names#> [1] 1 2 3tb <- tibble( x = LETTERS[1:3], y = 0:2, `:)` = runif(3))typeof(tb)
#> [1] "list"attributes(tb)
#> $class#> [1] "tbl_df" "tbl" "data.frame"#> #> $row.names#> [1] 1 2 3#> #> $names#> [1] "x" "y" ":)"增强向量③:dataframe 和 tibble *
*:dataframe 和 tibble 的底层都是个 list,每一列是 list 的一个元素,但要求每列都必须是等长的向量
# 看看df底层的listunclass(df) %>% str()
#> List of 3#> $ x : chr [1:3] "A" "B" "C"#> $ y : int [1:3] 0 1 2#> $ X..: num [1:3] 0.5232 0.5718 0.0529#> - attr(*, "row.names")= int [1:3] 1 2 3df # 列印df的内容
#> x y X..#> 1 A 0 0.5232#> 2 B 1 0.5718#> 3 C 2 0.0529增强向量③:dataframe 和 tibble *
*:dataframe 和 tibble 的底层都是个 list,每一列是 list 的一个元素,但要求每列都必须是等长的向量
# 看看df底层的listunclass(df) %>% str()
#> List of 3#> $ x : chr [1:3] "A" "B" "C"#> $ y : int [1:3] 0 1 2#> $ X..: num [1:3] 0.5232 0.5718 0.0529#> - attr(*, "row.names")= int [1:3] 1 2 3df # 列印df的内容
#> x y X..#> 1 A 0 0.5232#> 2 B 1 0.5718#> 3 C 2 0.0529# 看看tb底层的listunclass(tb) %>% str()
#> List of 3#> $ x : chr [1:3] "A" "B" "C"#> $ y : int [1:3] 0 1 2#> $ :): num [1:3] 0.525 0.239 0.675#> - attr(*, "row.names")= int [1:3] 1 2 3tb # 列印tb的内容
#> # A tibble: 3 × 3#> x y `:)`#> <chr> <int> <dbl>#> 1 A 0 0.525#> 2 B 1 0.239#> 3 C 2 0.675增强向量③:dataframe 和 tibble
# tb(df)具有类似矩阵(matrix)类的性质# tb的维度“属性”dim(tb)
#> [1] 3 3# 用提取子矩阵的方法提取子集tb[c(1, 3), c("y", ":)")]
#> # A tibble: 2 × 2#> y `:)`#> <int> <dbl>#> 1 0 0.525#> 2 2 0.675# 用dplyr工具可能更为直观tb %>% slice(c(1, 3)) %>% select(y, ":)")
#> # A tibble: 2 × 2#> y `:)`#> <int> <dbl>#> 1 0 0.525#> 2 2 0.675(functions)
和复制-粘贴-修改比,使用函数的优点
清晰的函数名让代码更容易理解
当要求发生变化时,你只需要在一个地方进行改动
消除复制-粘贴-修改过程中可能出现的无心错误
有时你必须写函数(如某些函数的参数就是函数)
来围观一个栗子 👉
# 甲:以下生成正态分布的随机变量并组装成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))# 乙:~~~
Step#1 分析重复性的代码 *1
(df$a - min(df$a, na.rm = TRUE)) / (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
Step#2 将函数的输入提取为临时变量 *2
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*1:可选步骤。
*2:多个输入也可类似处理。
Step#3 包装成函数 [ -> 改进] -> 测试
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# 改进之:使用range(),一举得到min和maxrescale01 <- 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应用新编写的函数 rescale01()
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😒 但 ……
左边的示例代码还是
有点 WET 1,
不够 DRY 2!
解决方案
用 for 循环或函数式编程,
这是下一讲才会讲到的内容。
1: Write Everything Twice
2: Don't Repeat Yourself
函数名
明晰易懂、建议为动词
推荐snake_case命名法
保持一致,如统一前缀
# Long, but clearimpute_missing()collapse_years()# common prefix for a familystringr::str_*()# Never do this!col_mins <- function(x, y) {}rowMaxes <- function(y, x) {}
函数体
代码缩进,形成层级结构
使用空格
合理使用注释
使用中间变量
styler 包函数的参数大致可划分为两类:
?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, ...)
通常把对象参数放在最前面,其余参数则按重要性排序,并设定默认值
参数名可采用约定俗成的名字,并与通常用法保持一致 👉
| 参数名 | 惯常含义 | 参数名 | 惯常含义 |
|---|---|---|---|
x, y, z |
数据向量 | i, j |
行列序号 |
df |
数据框 | n |
向量长度或数据框行数 |
w |
权重向量 | na.rm |
是否移除缺失值 |
特殊参数 ...
# 很多R函数接受任意数量的输入,此时可用...来捕捉未被匹配的参数sum(1, 2, 3, 4, 5, NA, 7, 8, 9, 10, na.rm = TRUE)
#> [1] 49# 也可用...将不想直接处理的参数传递至函数体内调用的底层函数,如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 ================================================在函数体中对重要参数的取值进行检验
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)}
在函数体中对重要参数的取值进行检验
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)}
wt_mean(1:6, 5:1, na.rm = "foo")
#> Error: `x` and `w` must be the same length在函数体中对重要参数的取值进行检验
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)}
wt_mean(1:6, 5:1, na.rm = "foo")
#> Error: `x` and `w` must be the same lengthwt_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通常函数返回其最后一个语句求值的结果
当然你也可以用 return() 来提前返回结果,从而让代码更易读
f <- function() { if (condition) { # Do # something # that # takes # many # lines # to # express } else { # return something short }}
f <- function() { if (!condition) { return(something_short) } # Do # something # that # takes # many # lines # to # express}
编写支持管道操作 %>% 的函数
<-、打印结果、作图、存入文档等。此类函数最好能用 invisible() 不可见地返回输入对象,从而支持管道操作。show_missings <- function(df) { n <- sum(is.na(df)) cat("Missing values: ", n, "\n", sep = "") invisible(df)}
x <- show_missings(mtcars)
#> Missing values: 0class(x)
#> [1] "data.frame"编写支持管道操作 %>% 的函数
<-、打印结果、作图、存入文档等。此类函数最好能用 invisible() 不可见地返回输入对象,从而支持管道操作。show_missings <- function(df) { n <- sum(is.na(df)) cat("Missing values: ", n, "\n", sep = "") invisible(df)}
x <- show_missings(mtcars)
#> Missing values: 0class(x)
#> [1] "data.frame"library(dplyr)mtcars %>% show_missings() %>% mutate( mpg = ifelse(mpg < 20, NA, mpg) ) %>% show_missings()
#> Missing values: 0#> Missing values: 18f <- function(x) { x + y}
前面定义的 f 看起来像个奇怪的函数:只有一个参数 x,希望返回 x + y 的值,但却没有对 y 进行定义和赋值 😲
但在 R 中这是个完全合法的函数,R 会根据所谓的“词法作用域”规则(lexical scoping)来查找 y 的取值,查找的结果受到函数环境的影响。在本例中,由于在函数 f 中并未定义 y,R 就会依次到创建函数 f 的环境(即封闭环境)及其父环境中查找 y 的值。
environment(f)
#> <environment: R_GlobalEnv>x <- 100; y <- 100; f(10)
#> [1] 110x <- 100; y <- 1000; f(10)
#> [1] 101005:00
将下面的代码片段转换成函数。思考一下每个函数的作用,你应该为新函数选择什么样的名称?
#1 ========================================mean(is.na(x))#2 ========================================x / sum(x, na.rm = TRUE)#3 ========================================sd(x, na.rm = TRUE) / mean(x, na.rm = TRUE)
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()
{{ tidyverse }}library(tidyverse)# 定义一个计算分组均值的函数grouped_mean <- function(df, group_var, mean_var) { df |> group_by(group_var) |> summarize(mean(mean_var))}# 调用函数,可惜报错啦 😲diamonds |> grouped_mean(cut, carat)
#> Error in `group_by()`:#> ! Must group by variables found in `.data`.#> ✖ Column `group_var` is not found.{{ tidyverse }}library(tidyverse)# 定义一个计算分组均值的函数grouped_mean <- function(df, group_var, mean_var) { df |> group_by(group_var) |> summarize(mean(mean_var))}# 调用函数,可惜报错啦 😲diamonds |> grouped_mean(cut, carat)
#> Error in `group_by()`:#> ! Must group by variables found in `.data`.#> ✖ Column `group_var` is not found.问题症结:为了方便交互式调用,tidyverse 中的很多函数都采用所谓的 tidy evaluation —— 让你可以直接引用数据框中的变量名。
{{ tidyverse }}解决之道:我们需要某种方式告诉这些函数 不要 直接将形式参数名视为数据框中的变量名,而是将其替换为传递给它的实际参数 —— {{ }} 粉墨登场!
library(tidyverse)# 定义一个计算分组均值的函数grouped_mean <- function(df, group_var, mean_var) { df |> group_by({{ group_var }}) |> summarize(mean({{ mean_var }}))}# 调用函数,这次就没问题啦 😄diamonds |> grouped_mean(cut, log(carat))
#> # A tibble: 5 × 2#> cut `mean(log(carat))`#> <ord> <dbl>#> 1 Fair -0.0629#> 2 Good -0.307 #> 3 Very Good -0.379 #> 4 Premium -0.288 #> 5 Ideal -0.518{{ tidyverse }}解决之道:我们需要某种方式告诉这些函数 不要 直接将形式参数名视为数据框中的变量名,而是将其替换为传递给它的实际参数 —— {{ }} 粉墨登场!
library(tidyverse)# 定义一个计算分组均值的函数grouped_mean <- function(df, group_var, mean_var) { df |> group_by({{ group_var }}) |> summarize(mean({{ mean_var }}))}# 调用函数,这次就没问题啦 😄diamonds |> grouped_mean(cut, log(carat))
#> # A tibble: 5 × 2#> cut `mean(log(carat))`#> <ord> <dbl>#> 1 Fair -0.0629#> 2 Good -0.307 #> 3 Very Good -0.379 #> 4 Premium -0.288 #> 5 Ideal -0.518什么时候需要 {{ }} ?
data-masking,如:
?mutate#> Arguments#> .data A data frame ...#> ... <data-masking> ...
tidy-select,如:
?select#> Arguments#> .data A data frame ...#> ... <tidy-select> ...
{{ tidyverse }}pick() / across() 多个变量
# 定义计算分组均值的函数grouped_means <- function(df, g_vars, m_vars) { df |> group_by(pick({{ g_vars }})) |> summarize( across({{ m_vars }}, mean))}# 调用函数diamonds |> grouped_means(c(cut, color), c(carat, price))
#> # A tibble: 35 × 4#> # Groups: cut [5]#> cut color carat price#> <ord> <ord> <dbl> <dbl>#> 1 Fair D 0.920 4291.#> 2 Fair E 0.857 3682.#> 3 Fair F 0.905 3827.#> # ℹ 32 more rows{{ tidyverse }}pick() / across() 多个变量
# 定义计算分组均值的函数grouped_means <- function(df, g_vars, m_vars) { df |> group_by(pick({{ g_vars }})) |> summarize( across({{ m_vars }}, mean))}# 调用函数diamonds |> grouped_means(c(cut, color), c(carat, price))
#> # A tibble: 35 × 4#> # Groups: cut [5]#> cut color carat price#> <ord> <ord> <dbl> <dbl>#> 1 Fair D 0.920 4291.#> 2 Fair E 0.857 3682.#> 3 Fair F 0.905 3827.#> # ℹ 32 more rows直接传递参数 ...,而无需 {{ }}
# 定义计算分组均值的函数grouped_mean <- function(.df, ..., .mean_var) { .df |> group_by(...) |> summarize(mean({{ .mean_var }}))}# 调用函数diamonds |> grouped_mean(cut, color, .mean_var = carat)
#> # A tibble: 35 × 3#> # Groups: cut [5]#> cut color `mean(carat)`#> <ord> <ord> <dbl>#> 1 Fair D 0.920#> 2 Fair E 0.857#> 3 Fair F 0.905#> # ℹ 32 more rows{{ tidyverse }}形式参数 / 环境变量为字符向量 => 使用代词 .data
# 定义计算分组均值的函数,但参数取值为字符向量grouped_mean <- function(df, group_var, mean_var) { df |> group_by(.data[[group_var]]) |> summarize(mean(.data[[mean_var]]))}# 用字符向量调用函数diamonds |> grouped_mean("cut", "carat")
#> # A tibble: 5 × 2#> cut `mean(.data[["carat"]])`#> <ord> <dbl>#> 1 Fair 1.05 #> 2 Good 0.849#> 3 Very Good 0.806#> # ℹ 2 more rows{{ tidyverse }}形式参数 / 环境变量为字符向量 => 使用代词 .data
# 定义计算分组均值的函数,但参数取值为字符向量grouped_mean <- function(df, group_var, mean_var) { df |> group_by(.data[[group_var]]) |> summarize(mean(.data[[mean_var]]))}# 用字符向量调用函数diamonds |> grouped_mean("cut", "carat")
#> # A tibble: 5 × 2#> cut `mean(.data[["carat"]])`#> <ord> <dbl>#> 1 Fair 1.05 #> 2 Good 0.849#> 3 Very Good 0.806#> # ℹ 2 more rows# 示例数据集xy <- tibble( x = sample(1:2, 100, replace = TRUE), y = sample(LETTERS[1:2], 100, TRUE))# 字符环境变量for (var in names(xy)) { xy %>% count(.data[[var]]) %>% print()}
#> # A tibble: 2 × 2#> x n#> <int> <int>#> 1 1 47#> 2 2 53#> # A tibble: 2 × 2#> y n#> <chr> <int>#> 1 A 55#> 2 B 45{{ tidyverse }}形式参数 / 环境变量为字符向量 => 使用代词 .data
# 定义计算分组均值的函数,但参数取值为字符向量grouped_mean <- function(df, group_var, mean_var) { df |> group_by(.data[[group_var]]) |> summarize(mean(.data[[mean_var]]))}# 用字符向量调用函数diamonds |> grouped_mean("cut", "carat")
#> # A tibble: 5 × 2#> cut `mean(.data[["carat"]])`#> <ord> <dbl>#> 1 Fair 1.05 #> 2 Good 0.849#> 3 Very Good 0.806#> # ℹ 2 more rows# 示例数据集xy <- tibble( x = sample(1:2, 100, replace = TRUE), y = sample(LETTERS[1:2], 100, TRUE))# 字符环境变量for (var in names(xy)) { xy %>% count(.data[[var]]) %>% print()}
#> # A tibble: 2 × 2#> x n#> <int> <int>#> 1 1 47#> 2 2 53#> # A tibble: 2 × 2#> y n#> <chr> <int>#> 1 A 55#> 2 B 45👉 vignette("programming", package = "dplyr")
复习 📖 R for Data Science, 2e 一书的以下章节:
28 A field guide to base R 的
28.1-28.3
自学 📖 R for Data Science, 2e 一书第4部分 Transform 的以下章节:
Keyboard shortcuts
| ↑, ←, Pg Up, k | Go to previous slide |
| ↓, →, Pg Dn, Space, j | Go to next slide |
| Home | Go to first slide |
| End | Go to last slide |
| Number + Return | Go to specific slide |
| b / m / f | Toggle blackout / mirrored / fullscreen mode |
| c | Clone slideshow |
| p | Toggle presenter mode |
| t | Restart the presentation timer |
| ?, h | Toggle this help |
| o | Tile View: Overview of Slides |
| Esc | Back to slideshow |
(vectors)
逻辑向量的元素只有三种可能取值:TRUE | FALSE | NA
逻辑向量通常由比较运算(?Comparison)生成
1:10 %% 3 == 0
#> [1] FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE FALSE当然也可以用 c() 直接构造逻辑向量
c(TRUE, FALSE, NA, T, F) # c(): Combine Values into a Vector or List
#> [1] TRUE FALSE NA TRUE FALSE强烈建议不要使用 TRUE 和 FALSE 的缩写形式 T 和 F
T <- FALSE; F <- 2; c(TRUE, FALSE, NA, T, F); rm(list = c("T", "F"))
#> [1] 1 0 NA 0 2数值向量包括整数向量(integer)和实数向量(double)
默认为实数,除非在整数后加上 L
typeof(1);typeof(1L);typeof(1.5L)
#> [1] "double"#> [1] "integer"#> [1] "double"R 用双精度来存储实数向量的数值,但在很多情况下只是近似值,因此不要用 == 直接比较实数向量的取值,而用 dplyr::near()
整数向量只有一个特殊值 NA,而实数向量则有四个特殊值 NA、NaN、Inf 和 -Inf;用 is.*() 而不要直接用 == 检测这些特殊值
c(-1, 0, 1, NA) / 0 # 4个特殊值#> [1] -Inf NaN Inf NA
0 |
Inf |
NA |
NaN |
|
|---|---|---|---|---|
is.finite() |
√ | |||
is.infinite() |
√ | |||
is.na() |
√ | √ | ||
is.nan() |
√ |
字符向量的每个元素由字符串构成,可用来表示任意数据
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"R 使用全局字符串池,这意味着每个字符串在内存中只存一次
x <- "This is a long string."object.size(x)# lobstr::obj_size(x)
#> 136 bytesy <- rep(x, 1000)object.size(y)
#> 8128 bytes# 8 * 1000 + 128 B
显性转换:as.logical() | as.integer() | as.double() | as.character()
as.integer(c("1", "2")) + c(10, 11) ## [1] 11 13
隐性转换:当向量用于需要特定类型输入的函数时,R 会自动将向量转换为特定类型(但非总是如此)
x <- sample(1:20, 100, replace = TRUE)# what proportion > 10?mean(x > 10)
#> [1] 0.52x + "3" # 但并非总是如此
#> Error in x + "3": non-numeric argument to binary operator显性转换:as.logical() | as.integer() | as.double() | as.character()
as.integer(c("1", "2")) + c(10, 11) ## [1] 11 13
隐性转换:当向量用于需要特定类型输入的函数时,R 会自动将向量转换为特定类型(但非总是如此)
x <- sample(1:20, 100, replace = TRUE)# what proportion > 10?mean(x > 10)
#> [1] 0.52x + "3" # 但并非总是如此
#> Error in x + "3": non-numeric argument to binary operator隐性转换:当用 c() 联结不同类型的向量时,R 会套用最复杂的类型
c(FALSE, 1L, 1.5, "a", 1 + 1i, raw(2))
#> [1] "FALSE" "1" "1.5" "a" #> [5] "1+1i" "00" "00"1 == "1"; -1 < FALSE; "one" < 2
#> [1] TRUE#> [1] TRUE#> [1] FALSER 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度
runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE TRUE FALSE FALSE FALSE TRUE FALSE TRUE TRUE当较长向量不是较短向量的整数倍时,R 照样会进行转换,但会有个 Warning message
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 6R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度
runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE TRUE FALSE FALSE FALSE TRUE FALSE TRUE TRUE当较长向量不是较短向量的整数倍时,R 照样会进行转换,但会有个 Warning message
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当向量的长短不一时(标量除外),要求更严格的 tidyverse 则会抛出 Error,停止运行
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.[ 函数选取元素(subsetting)方法一:用整数向量选取元素
x <- c("one", "two", "three", "four", "five")x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素
#> [1] "two" "four"x[c(1, -3, 5)] # 不可混用正整数和负整数
#> Error in x[c(1, -3, 5)]: only 0's may be mixed with negative subscripts[ 函数选取元素(subsetting)方法一:用整数向量选取元素
x <- c("one", "two", "three", "four", "five")x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素
#> [1] "two" "four"x[c(1, -3, 5)] # 不可混用正整数和负整数
#> Error in x[c(1, -3, 5)]: only 0's may be mixed with negative subscriptsx[0] # 返回空值(但非NULL)
#> character(0)[ 函数选取元素(subsetting)方法一:用整数向量选取元素
x <- c("one", "two", "three", "four", "five")x[c(1, 1, 3, 2, 2, 5)] # 正整数表示提取相应位置的元素,可重复;位置索引从1开始
#> [1] "one" "one" "three" "two" "two" "five"x[c(-1, -3, -5)] # 负整数表示剔除相应位置的元素
#> [1] "two" "four"x[c(1, -3, 5)] # 不可混用正整数和负整数
#> Error in x[c(1, -3, 5)]: only 0's may be mixed with negative subscriptsx[0] # 返回空值(但非NULL)
#> character(0)x[c(1.2, 2.3, 3.4, 4.5, 5.6, 6.7)] # 想想,这会返回什么?[ 函数选取元素(subsetting)方法二:用逻辑向量选取元素,保留逻辑向量取值为 TRUE 位置的元素
x <- c(10, 3, NA, 5)x[!is.na(x)] # 提取非缺失值
#> [1] 10 3 5# 注意:逻辑向量取值为NA的位置也会被# 保留下来x[x %% 2 == 0] # 提取偶数或缺失值
#> [1] 10 NAx[x %% 2 == 1] # 提取奇数或缺失值
#> [1] 3 NA 5方法三:用字符向量选取命名向量的元素
# 命名向量:创建时直接命名abc <- c(a = 1, b = 2, c = 3)# 命名向量:事后再命名abc <- 1:3abc <- purrr::set_names( # 也可用base包的setNames() x = abc, nm = c("a", "b", "c"))
# 提取相应命名的元素abc[c("a", "c", "b", "a", "d")]
#> a c b a <NA> #> 1 3 2 1 NA和原子向量不同,列表可同时包含多种类型(type)的元素(甚至嵌套下一级的列表),这使得列表很适合用来存储混合或树状结构的数据
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除了直接 print() 列表对象的内容之外,更推荐使用 str() 函数 / RStudio Environment 面板来查看 简单 / 复杂 列表对象的结构
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方法一:用 [ 提取子列表,总是返回列表
# 可用 数值|字符|逻辑向量 提取,如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"# 尽管只有一个元素,但仍是列表a[3] %>% str()
#> List of 1#> $ c: num 3.14a[4] %>% str()
#> List of 1#> $ d:List of 2#> ..$ : num -1#> ..$ : num -5方法一:用 [ 提取子列表,总是返回列表
# 可用 数值|字符|逻辑向量 提取,如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"# 尽管只有一个元素,但仍是列表a[3] %>% str()
#> List of 1#> $ c: num 3.14a[4] %>% str()
#> List of 1#> $ d:List of 2#> ..$ : num -1#> ..$ : num -5方法二:用 [[ 提取单一列表元素,并从列表中移除一个层级
a[[3]] %>% str()
#> num 3.14a[[4]] %>% str()
#> List of 2#> $ : num -1#> $ : num -5方法三:$ 大致等同 [[ ,能更方便地提取一个命名列表的元素
a$a # a[["a"]]
#> [1] 1 2 3我们可以通过向量属性来给向量增加任意的额外元数据(metadata),而向量属性可被视作附加在向量上的命名列表
通过 attr() 来设定或获取向量的属性;通过 attributes() 来获取全部或批量设置向量的属性信息
x <- 1:10attr(x, "source")
#> NULLattributes(x) <- list( source = c("Wind", "GTA"), date = Sys.Date())attr(x, "cleaned_by") <- "Y.Z"
attributes(x)
#> $source#> [1] "Wind" "GTA" #> #> $date#> [1] "2023-11-09"#> #> $cleaned_by#> [1] "Y.Z"R 中的向量除了我们之前提到过的“类型 typeof()”和“长度 length()”两个性质之外,还有三个基础属性:名称、维度和类
①名称属性(names):给向量元素命名
# 命名向量x = c(1, 2, 3)names(x) <- c("a", "b", "c")x
#> a b c #> 1 2 3names(x) # attr(x, "names")
#> [1] "a" "b" "c"②维度属性(dimensions):将向量“改造”为矩阵或数组
x <- 1:10dim(x) <- c(2, 5) # ?matrixx
#> [,1] [,2] [,3] [,4] [,5]#> [1,] 1 3 5 7 9#> [2,] 2 4 6 8 10class(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()
③类属性(class):控制泛型函数(generic functions)的行为方式,这是 R 语言用来实现“S3 面向对象系统”的方法
# as.Date()是个典型的泛型函数# 列印as.Date()函数的源代码as.Date
#> function (x, ...) #> UseMethod("as.Date")#> <bytecode: 0x000001ed6dcbef18>#> <environment: namespace:base>结果显示,as.Date() 函数调用 UseMethod(),这就意味着 as.Date() 是个泛型函数,它将根据第一个参数 x 的类来确定该调用哪个特定方法(method)。
# 查看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# 查看as.Date()函数指定类(如numeric)所实现的具体方法getS3method("as.Date", "numeric")
#> function (x, origin, ...) #> if (missing(origin)) .Date(x) else as.Date(origin, ...) + x#> <bytecode: 0x000001ed6c7ac3e8>#> <environment: namespace:base># 由于as.Date.numeric()是导出函数,也可用as.Date.numeric直接查看其代码
# 查看as.Date()函数指定类(如numeric)所实现的具体方法getS3method("as.Date", "numeric")
#> function (x, origin, ...) #> if (missing(origin)) .Date(x) else as.Date(origin, ...) + x#> <bytecode: 0x000001ed6c7ac3e8>#> <environment: namespace:base># 由于as.Date.numeric()是导出函数,也可用as.Date.numeric直接查看其代码
记住,类和泛型函数是R中非常重要的概念,有很多常见的函数都是泛型函数,具体如 print()、summary()、mean()、[、[[ 和 $ 等。
增强向量①:因子向量(factors)
(x <- factor(c("A", "B", "A"), levels = c("A", "B", "C")))
#> [1] A B A#> Levels: A B Ctypeof(x) # 获取向量x的类型
#> [1] "integer"attributes(x) # 获取向量x的属性
#> $levels#> [1] "A" "B" "C"#> #> $class#> [1] "factor"# 获取x向量levels属性的便捷函数levels(x)
#> [1] "A" "B" "C"# 获取x向量class属性的便捷函数class(x)
#> [1] "factor"unclass(x) # 移除向量x的类属性
#> [1] 1 2 1#> attr(,"levels")#> [1] "A" "B" "C"增强向量②:日期向量和日期-时间向量(dates and date-times)
(x <- as.Date("1971-01-01"))
#> [1] "1971-01-01"typeof(x)
#> [1] "double"attributes(x)
#> $class#> [1] "Date"unclass(x) # as.integer(x)
#> [1] 365(x <- lubridate::ymd_hm( "1970-01-01 01:00"))
#> [1] "1970-01-01 01:00:00 UTC"typeof(x); as.double(x) # unclass(x)
#> [1] "double"#> [1] 3600attributes(x)
#> $class#> [1] "POSIXct" "POSIXt" #> #> $tzone#> [1] "UTC"增强向量②:日期向量和日期-时间向量(dates and date-times)
(y <- as.POSIXlt(x)) # built on top of named lists
#> [1] "1970-01-01 01:00:00 UTC"typeof(y)
#> [1] "list"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增强向量②:日期向量和日期-时间向量(dates and date-times)
(y <- as.POSIXlt(x)) # built on top of named lists
#> [1] "1970-01-01 01:00:00 UTC"typeof(y)
#> [1] "list"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] TRUEunclass(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 TRUEy$year # 提取列表y的"year"元素
#> [1] 7003:00
hms::hms(seconds = 1, minutes = 1, hours = 1)
返回值如何输出?
这种扩展向量是基于哪种基本类型构造的?
使用了哪些向量属性?
(x <- hms::hms(seconds = 1, minutes = 1, hours = 1)) # hms::hms(seconds = 3661)#> 01:01:01class(x); typeof(x); as.numeric(x)#> [1] "hms" "difftime"#> [1] "double"#> [1] 3661attributes(x)#> $units#> [1] "secs"#> #> $class#> [1] "hms" "difftime"增强向量③:dataframe 和 tibble
df <- data.frame( x = LETTERS[1:3], y = 0:2, `:)` = runif(3))typeof(df)
#> [1] "list"attributes(df)
#> $names#> [1] "x" "y" "X.."#> #> $class#> [1] "data.frame"#> #> $row.names#> [1] 1 2 3tb <- tibble( x = LETTERS[1:3], y = 0:2, `:)` = runif(3))typeof(tb)
#> [1] "list"attributes(tb)
#> $class#> [1] "tbl_df" "tbl" "data.frame"#> #> $row.names#> [1] 1 2 3#> #> $names#> [1] "x" "y" ":)"增强向量③:dataframe 和 tibble *
*:dataframe 和 tibble 的底层都是个 list,每一列是 list 的一个元素,但要求每列都必须是等长的向量
# 看看df底层的listunclass(df) %>% str()
#> List of 3#> $ x : chr [1:3] "A" "B" "C"#> $ y : int [1:3] 0 1 2#> $ X..: num [1:3] 0.5232 0.5718 0.0529#> - attr(*, "row.names")= int [1:3] 1 2 3df # 列印df的内容
#> x y X..#> 1 A 0 0.5232#> 2 B 1 0.5718#> 3 C 2 0.0529增强向量③:dataframe 和 tibble *
*:dataframe 和 tibble 的底层都是个 list,每一列是 list 的一个元素,但要求每列都必须是等长的向量
# 看看df底层的listunclass(df) %>% str()
#> List of 3#> $ x : chr [1:3] "A" "B" "C"#> $ y : int [1:3] 0 1 2#> $ X..: num [1:3] 0.5232 0.5718 0.0529#> - attr(*, "row.names")= int [1:3] 1 2 3df # 列印df的内容
#> x y X..#> 1 A 0 0.5232#> 2 B 1 0.5718#> 3 C 2 0.0529# 看看tb底层的listunclass(tb) %>% str()
#> List of 3#> $ x : chr [1:3] "A" "B" "C"#> $ y : int [1:3] 0 1 2#> $ :): num [1:3] 0.525 0.239 0.675#> - attr(*, "row.names")= int [1:3] 1 2 3tb # 列印tb的内容
#> # A tibble: 3 × 3#> x y `:)`#> <chr> <int> <dbl>#> 1 A 0 0.525#> 2 B 1 0.239#> 3 C 2 0.675增强向量③:dataframe 和 tibble
# tb(df)具有类似矩阵(matrix)类的性质# tb的维度“属性”dim(tb)
#> [1] 3 3# 用提取子矩阵的方法提取子集tb[c(1, 3), c("y", ":)")]
#> # A tibble: 2 × 2#> y `:)`#> <int> <dbl>#> 1 0 0.525#> 2 2 0.675# 用dplyr工具可能更为直观tb %>% slice(c(1, 3)) %>% select(y, ":)")
#> # A tibble: 2 × 2#> y `:)`#> <int> <dbl>#> 1 0 0.525#> 2 2 0.675(functions)
和复制-粘贴-修改比,使用函数的优点
清晰的函数名让代码更容易理解
当要求发生变化时,你只需要在一个地方进行改动
消除复制-粘贴-修改过程中可能出现的无心错误
有时你必须写函数(如某些函数的参数就是函数)
来围观一个栗子 👉
# 甲:以下生成正态分布的随机变量并组装成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))# 乙:~~~
Step#1 分析重复性的代码 *1
(df$a - min(df$a, na.rm = TRUE)) / (max(df$a, na.rm = TRUE) - min(df$a, na.rm = TRUE))
Step#2 将函数的输入提取为临时变量 *2
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*1:可选步骤。
*2:多个输入也可类似处理。
Step#3 包装成函数 [ -> 改进] -> 测试
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# 改进之:使用range(),一举得到min和maxrescale01 <- 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应用新编写的函数 rescale01()
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😒 但 ……
左边的示例代码还是
有点 WET 1,
不够 DRY 2!
解决方案
用 for 循环或函数式编程,
这是下一讲才会讲到的内容。
1: Write Everything Twice
2: Don't Repeat Yourself
函数名
明晰易懂、建议为动词
推荐snake_case命名法
保持一致,如统一前缀
# Long, but clearimpute_missing()collapse_years()# common prefix for a familystringr::str_*()# Never do this!col_mins <- function(x, y) {}rowMaxes <- function(y, x) {}
函数体
代码缩进,形成层级结构
使用空格
合理使用注释
使用中间变量
styler 包函数的参数大致可划分为两类:
?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, ...)
通常把对象参数放在最前面,其余参数则按重要性排序,并设定默认值
参数名可采用约定俗成的名字,并与通常用法保持一致 👉
| 参数名 | 惯常含义 | 参数名 | 惯常含义 |
|---|---|---|---|
x, y, z |
数据向量 | i, j |
行列序号 |
df |
数据框 | n |
向量长度或数据框行数 |
w |
权重向量 | na.rm |
是否移除缺失值 |
特殊参数 ...
# 很多R函数接受任意数量的输入,此时可用...来捕捉未被匹配的参数sum(1, 2, 3, 4, 5, NA, 7, 8, 9, 10, na.rm = TRUE)
#> [1] 49# 也可用...将不想直接处理的参数传递至函数体内调用的底层函数,如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 ================================================在函数体中对重要参数的取值进行检验
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)}
在函数体中对重要参数的取值进行检验
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)}
wt_mean(1:6, 5:1, na.rm = "foo")
#> Error: `x` and `w` must be the same length在函数体中对重要参数的取值进行检验
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)}
wt_mean(1:6, 5:1, na.rm = "foo")
#> Error: `x` and `w` must be the same lengthwt_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通常函数返回其最后一个语句求值的结果
当然你也可以用 return() 来提前返回结果,从而让代码更易读
f <- function() { if (condition) { # Do # something # that # takes # many # lines # to # express } else { # return something short }}
f <- function() { if (!condition) { return(something_short) } # Do # something # that # takes # many # lines # to # express}
编写支持管道操作 %>% 的函数
<-、打印结果、作图、存入文档等。此类函数最好能用 invisible() 不可见地返回输入对象,从而支持管道操作。show_missings <- function(df) { n <- sum(is.na(df)) cat("Missing values: ", n, "\n", sep = "") invisible(df)}
x <- show_missings(mtcars)
#> Missing values: 0class(x)
#> [1] "data.frame"编写支持管道操作 %>% 的函数
<-、打印结果、作图、存入文档等。此类函数最好能用 invisible() 不可见地返回输入对象,从而支持管道操作。show_missings <- function(df) { n <- sum(is.na(df)) cat("Missing values: ", n, "\n", sep = "") invisible(df)}
x <- show_missings(mtcars)
#> Missing values: 0class(x)
#> [1] "data.frame"library(dplyr)mtcars %>% show_missings() %>% mutate( mpg = ifelse(mpg < 20, NA, mpg) ) %>% show_missings()
#> Missing values: 0#> Missing values: 18f <- function(x) { x + y}
前面定义的 f 看起来像个奇怪的函数:只有一个参数 x,希望返回 x + y 的值,但却没有对 y 进行定义和赋值 😲
但在 R 中这是个完全合法的函数,R 会根据所谓的“词法作用域”规则(lexical scoping)来查找 y 的取值,查找的结果受到函数环境的影响。在本例中,由于在函数 f 中并未定义 y,R 就会依次到创建函数 f 的环境(即封闭环境)及其父环境中查找 y 的值。
environment(f)
#> <environment: R_GlobalEnv>x <- 100; y <- 100; f(10)
#> [1] 110x <- 100; y <- 1000; f(10)
#> [1] 101005:00
将下面的代码片段转换成函数。思考一下每个函数的作用,你应该为新函数选择什么样的名称?
#1 ========================================mean(is.na(x))#2 ========================================x / sum(x, na.rm = TRUE)#3 ========================================sd(x, na.rm = TRUE) / mean(x, na.rm = TRUE)
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()
{{ tidyverse }}library(tidyverse)# 定义一个计算分组均值的函数grouped_mean <- function(df, group_var, mean_var) { df |> group_by(group_var) |> summarize(mean(mean_var))}# 调用函数,可惜报错啦 😲diamonds |> grouped_mean(cut, carat)
#> Error in `group_by()`:#> ! Must group by variables found in `.data`.#> ✖ Column `group_var` is not found.{{ tidyverse }}library(tidyverse)# 定义一个计算分组均值的函数grouped_mean <- function(df, group_var, mean_var) { df |> group_by(group_var) |> summarize(mean(mean_var))}# 调用函数,可惜报错啦 😲diamonds |> grouped_mean(cut, carat)
#> Error in `group_by()`:#> ! Must group by variables found in `.data`.#> ✖ Column `group_var` is not found.问题症结:为了方便交互式调用,tidyverse 中的很多函数都采用所谓的 tidy evaluation —— 让你可以直接引用数据框中的变量名。
{{ tidyverse }}解决之道:我们需要某种方式告诉这些函数 不要 直接将形式参数名视为数据框中的变量名,而是将其替换为传递给它的实际参数 —— {{ }} 粉墨登场!
library(tidyverse)# 定义一个计算分组均值的函数grouped_mean <- function(df, group_var, mean_var) { df |> group_by({{ group_var }}) |> summarize(mean({{ mean_var }}))}# 调用函数,这次就没问题啦 😄diamonds |> grouped_mean(cut, log(carat))
#> # A tibble: 5 × 2#> cut `mean(log(carat))`#> <ord> <dbl>#> 1 Fair -0.0629#> 2 Good -0.307 #> 3 Very Good -0.379 #> 4 Premium -0.288 #> 5 Ideal -0.518{{ tidyverse }}解决之道:我们需要某种方式告诉这些函数 不要 直接将形式参数名视为数据框中的变量名,而是将其替换为传递给它的实际参数 —— {{ }} 粉墨登场!
library(tidyverse)# 定义一个计算分组均值的函数grouped_mean <- function(df, group_var, mean_var) { df |> group_by({{ group_var }}) |> summarize(mean({{ mean_var }}))}# 调用函数,这次就没问题啦 😄diamonds |> grouped_mean(cut, log(carat))
#> # A tibble: 5 × 2#> cut `mean(log(carat))`#> <ord> <dbl>#> 1 Fair -0.0629#> 2 Good -0.307 #> 3 Very Good -0.379 #> 4 Premium -0.288 #> 5 Ideal -0.518什么时候需要 {{ }} ?
data-masking,如:
?mutate#> Arguments#> .data A data frame ...#> ... <data-masking> ...
tidy-select,如:
?select#> Arguments#> .data A data frame ...#> ... <tidy-select> ...
{{ tidyverse }}pick() / across() 多个变量
# 定义计算分组均值的函数grouped_means <- function(df, g_vars, m_vars) { df |> group_by(pick({{ g_vars }})) |> summarize( across({{ m_vars }}, mean))}# 调用函数diamonds |> grouped_means(c(cut, color), c(carat, price))
#> # A tibble: 35 × 4#> # Groups: cut [5]#> cut color carat price#> <ord> <ord> <dbl> <dbl>#> 1 Fair D 0.920 4291.#> 2 Fair E 0.857 3682.#> 3 Fair F 0.905 3827.#> # ℹ 32 more rows{{ tidyverse }}pick() / across() 多个变量
# 定义计算分组均值的函数grouped_means <- function(df, g_vars, m_vars) { df |> group_by(pick({{ g_vars }})) |> summarize( across({{ m_vars }}, mean))}# 调用函数diamonds |> grouped_means(c(cut, color), c(carat, price))
#> # A tibble: 35 × 4#> # Groups: cut [5]#> cut color carat price#> <ord> <ord> <dbl> <dbl>#> 1 Fair D 0.920 4291.#> 2 Fair E 0.857 3682.#> 3 Fair F 0.905 3827.#> # ℹ 32 more rows直接传递参数 ...,而无需 {{ }}
# 定义计算分组均值的函数grouped_mean <- function(.df, ..., .mean_var) { .df |> group_by(...) |> summarize(mean({{ .mean_var }}))}# 调用函数diamonds |> grouped_mean(cut, color, .mean_var = carat)
#> # A tibble: 35 × 3#> # Groups: cut [5]#> cut color `mean(carat)`#> <ord> <ord> <dbl>#> 1 Fair D 0.920#> 2 Fair E 0.857#> 3 Fair F 0.905#> # ℹ 32 more rows{{ tidyverse }}形式参数 / 环境变量为字符向量 => 使用代词 .data
# 定义计算分组均值的函数,但参数取值为字符向量grouped_mean <- function(df, group_var, mean_var) { df |> group_by(.data[[group_var]]) |> summarize(mean(.data[[mean_var]]))}# 用字符向量调用函数diamonds |> grouped_mean("cut", "carat")
#> # A tibble: 5 × 2#> cut `mean(.data[["carat"]])`#> <ord> <dbl>#> 1 Fair 1.05 #> 2 Good 0.849#> 3 Very Good 0.806#> # ℹ 2 more rows{{ tidyverse }}形式参数 / 环境变量为字符向量 => 使用代词 .data
# 定义计算分组均值的函数,但参数取值为字符向量grouped_mean <- function(df, group_var, mean_var) { df |> group_by(.data[[group_var]]) |> summarize(mean(.data[[mean_var]]))}# 用字符向量调用函数diamonds |> grouped_mean("cut", "carat")
#> # A tibble: 5 × 2#> cut `mean(.data[["carat"]])`#> <ord> <dbl>#> 1 Fair 1.05 #> 2 Good 0.849#> 3 Very Good 0.806#> # ℹ 2 more rows# 示例数据集xy <- tibble( x = sample(1:2, 100, replace = TRUE), y = sample(LETTERS[1:2], 100, TRUE))# 字符环境变量for (var in names(xy)) { xy %>% count(.data[[var]]) %>% print()}
#> # A tibble: 2 × 2#> x n#> <int> <int>#> 1 1 47#> 2 2 53#> # A tibble: 2 × 2#> y n#> <chr> <int>#> 1 A 55#> 2 B 45{{ tidyverse }}形式参数 / 环境变量为字符向量 => 使用代词 .data
# 定义计算分组均值的函数,但参数取值为字符向量grouped_mean <- function(df, group_var, mean_var) { df |> group_by(.data[[group_var]]) |> summarize(mean(.data[[mean_var]]))}# 用字符向量调用函数diamonds |> grouped_mean("cut", "carat")
#> # A tibble: 5 × 2#> cut `mean(.data[["carat"]])`#> <ord> <dbl>#> 1 Fair 1.05 #> 2 Good 0.849#> 3 Very Good 0.806#> # ℹ 2 more rows# 示例数据集xy <- tibble( x = sample(1:2, 100, replace = TRUE), y = sample(LETTERS[1:2], 100, TRUE))# 字符环境变量for (var in names(xy)) { xy %>% count(.data[[var]]) %>% print()}
#> # A tibble: 2 × 2#> x n#> <int> <int>#> 1 1 47#> 2 2 53#> # A tibble: 2 × 2#> y n#> <chr> <int>#> 1 A 55#> 2 B 45👉 vignette("programming", package = "dplyr")
复习 📖 R for Data Science, 2e 一书的以下章节:
28 A field guide to base R 的
28.1-28.3
自学 📖 R for Data Science, 2e 一书第4部分 Transform 的以下章节: