(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 bytes
y <- 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.47
x + "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.47
x + "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] FALSE
R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度
runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE FALSE FALSE FALSE TRUE FALSE TRUE FALSE 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
R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度
runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE FALSE FALSE FALSE TRUE FALSE TRUE FALSE 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 subscripts
x[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 subscripts
x[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 NA
x[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.14
a[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.14
a[4] %>% str()
#> List of 1#> $ d:List of 2#> ..$ : num -1#> ..$ : num -5
方法二:用 [[ 提取单一列表元素,并从列表中移除一个层级
a[[3]] %>% str()
#> num 3.14
a[[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")
#> NULL
attributes(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-06"#> #> $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 3
names(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 10
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()
③类属性(class):控制泛型函数(generic functions)的行为方式,这是 R 语言用来实现“S3 面向对象系统”的方法
# as.Date()是个典型的泛型函数# 列印as.Date()函数的源代码as.Date
#> function (x, ...) #> UseMethod("as.Date")#> <bytecode: 0x00000280f20921c0>#> <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: 0x00000280fc7df648>#> <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: 0x00000280fc7df648>#> <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 C
typeof(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] 3600
attributes(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] TRUE
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
y$year # 提取列表y的"year"元素
#> [1] 70
增强向量③: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 3
tb <- 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.409 0.456 0.905#> - attr(*, "row.names")= int [1:3] 1 2 3
df # 列印df的内容
#> x y X..#> 1 A 0 0.409#> 2 B 1 0.456#> 3 C 2 0.905
增强向量③: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.409 0.456 0.905#> - attr(*, "row.names")= int [1:3] 1 2 3
df # 列印df的内容
#> x y X..#> 1 A 0 0.409#> 2 B 1 0.456#> 3 C 2 0.905
# 看看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.267 0.315 0.35#> - attr(*, "row.names")= int [1:3] 1 2 3
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
增强向量③:dataframe
和 tibble
# 提取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
# 提取第2列,仍结果为tibble# 也可用tb[2]tb["y"]
#> # A tibble: 3 × 1#> y#> <int>#> 1 0#> 2 1#> 3 2
# 提取第2列,但结果为向量# 也可用tb[[2]]或tb[["y"]]tb$y
#> [1] 0 1 2
增强向量③: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.267#> 2 2 0.350
# 用dplyr工具可能更为直观tb %>% slice(c(1, 3)) %>% select(y, ":)")
#> # A tibble: 2 × 2#> y `:)`#> <int> <dbl>#> 1 0 0.267#> 2 2 0.350
(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 ================================================
通常函数返回其最后一个语句求值的结果
当然你也可以用 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: 0
class(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: 0
class(x)
#> [1] "data.frame"
library(dplyr)mtcars %>% show_missings() %>% mutate( mpg = ifelse(mpg < 20, NA, mpg) ) %>% show_missings()
#> Missing values: 0#> Missing values: 18
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 bytes
y <- 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.47
x + "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.47
x + "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] FALSE
R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度
runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE FALSE FALSE FALSE TRUE FALSE TRUE FALSE 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
R 不仅会隐性地转换向量类型, R 中的向量化函数还会采用循环规则自动转换向量的长度,通过重复将较短向量的长度增加至较长向量的长度
runif(10) > 0.5 # R中不存在所谓的标量(scalar),它们只是长度为1的向量
#> [1] TRUE FALSE FALSE FALSE FALSE TRUE FALSE TRUE FALSE 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 subscripts
x[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 subscripts
x[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 NA
x[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.14
a[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.14
a[4] %>% str()
#> List of 1#> $ d:List of 2#> ..$ : num -1#> ..$ : num -5
方法二:用 [[ 提取单一列表元素,并从列表中移除一个层级
a[[3]] %>% str()
#> num 3.14
a[[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")
#> NULL
attributes(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-06"#> #> $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 3
names(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 10
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()
③类属性(class):控制泛型函数(generic functions)的行为方式,这是 R 语言用来实现“S3 面向对象系统”的方法
# as.Date()是个典型的泛型函数# 列印as.Date()函数的源代码as.Date
#> function (x, ...) #> UseMethod("as.Date")#> <bytecode: 0x00000280f20921c0>#> <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: 0x00000280fc7df648>#> <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: 0x00000280fc7df648>#> <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 C
typeof(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] 3600
attributes(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] TRUE
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
y$year # 提取列表y的"year"元素
#> [1] 70
增强向量③: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 3
tb <- 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.409 0.456 0.905#> - attr(*, "row.names")= int [1:3] 1 2 3
df # 列印df的内容
#> x y X..#> 1 A 0 0.409#> 2 B 1 0.456#> 3 C 2 0.905
增强向量③: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.409 0.456 0.905#> - attr(*, "row.names")= int [1:3] 1 2 3
df # 列印df的内容
#> x y X..#> 1 A 0 0.409#> 2 B 1 0.456#> 3 C 2 0.905
# 看看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.267 0.315 0.35#> - attr(*, "row.names")= int [1:3] 1 2 3
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
增强向量③:dataframe
和 tibble
# 提取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
# 提取第2列,仍结果为tibble# 也可用tb[2]tb["y"]
#> # A tibble: 3 × 1#> y#> <int>#> 1 0#> 2 1#> 3 2
# 提取第2列,但结果为向量# 也可用tb[[2]]或tb[["y"]]tb$y
#> [1] 0 1 2
增强向量③: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.267#> 2 2 0.350
# 用dplyr工具可能更为直观tb %>% slice(c(1, 3)) %>% select(y, ":)")
#> # A tibble: 2 × 2#> y `:)`#> <int> <dbl>#> 1 0 0.267#> 2 2 0.350
(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 ================================================
通常函数返回其最后一个语句求值的结果
当然你也可以用 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: 0
class(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: 0
class(x)
#> [1] "data.frame"
library(dplyr)mtcars %>% show_missings() %>% mutate( mpg = ifelse(mpg < 20, NA, mpg) ) %>% show_missings()
#> Missing values: 0#> Missing values: 18