美文网首页
R语言编程-Tidyverse 书籍-第一章(2)

R语言编程-Tidyverse 书籍-第一章(2)

作者: Hello育种 | 来源:发表于2022-06-08 02:29 被阅读0次

此部分的内容,全部是对张敬信博士书籍的学习总结。
出发点: 因为tidyverse简洁编码,使R代码更加易读。我就想系统再次学习下R,这样让自己写的R代码更加简洁。


关于“for 循环运行速度慢” 的说法,实际上已经过时了,现在的R、Matlab 等软件经过多年的
内部优化已经不慢了,之所以表现出来慢,是因为你没有注意两个关键点:
提前为保存循环结果分配存储空间;
为循环体中涉及的数据选择合适的数据结构。
apply 函数族和purrr 泛函式编程能够更加高效简洁地实现一般的for 循环、while 循环,但这
不代表for 循环、while 循环就没用了,它们可以在更高的层次使用(相对于在逐元素级别使用)### 时间段数据
interval() ##计算两个时间点的时间间隔,返回时间段数据


image.png

t %within% gap # 判断t是否属于该时间段

考虑长时间段应该使用period
image.png

日期时间的计算

时间点+时间段生成一个新的时间点


image.png image.png
月份加运算: %m+%, 表示日期按月份增加的
image.png

时间序列

ts()是base R生成时间序列的函数,基本格式
ts(data, start = 1, end, frequency=1,...) ## frequency=1是年,4位季节,12是月, 52是周,

fpp3生态新的 新的tsibble包提供了更整洁的时间序列数据结构

as_tsibble()将数据框转为时间序列对象tsibble,只需要指定时间索引和分组索引

library(fpp3)
stocks = as_tsibble(stocks, key = stock, index = Data)

1.5正则表达式

正则表达式,是根据字符串规律按一定法则,简洁表达一组字符串的表达式。
正则表达式包括:只能匹配自身的普通字符(如英文字母、数字、标点等) 和被转义了的特殊字符
(称为‘‘元字符”)

image.png

需要创建多行模型的正则表达式


image.png
特殊字符类与反义
image.png
image.png
POSIX 字符类
image.png
运算优先级

圆括号括起来的表达式最优先,其次是表示重复次数的操作(即* + { }) ;再次是连接运算(即几
个字符放在一起,如abc) ;最后是或者运算(|)
另外,正则表达式还有若干高级用法,常用的有零宽断言和分组捕获,将在下面实例中进行演示。

例子

image.png

(零宽断言) 匹配两个标志之间的内容

适合想要匹配的内容没有规律性,但该内容位于两个有规律性的标志之间,标志也可以是开始和
结束。
通常想要匹配的内容不包含两边的‘‘标志’’,这就需要用零宽断言。简单来说,就是一种引导语法
告诉既要匹配到‘‘标志’’,但又不包含‘‘标志’’。左边标志的引导语法是(?<= 标志),右边标志的引导
语法是(?= 标志),而真正要匹配的内容放在它们中间。


image.png

分组捕获

正则表达式中可以用圆括号来分组,作用是

  • 确定优先规则
  • 组成一个整体
  • 拆分出整个匹配中的部分内容(称为捕获)
  • 捕获内容供后续引用或者替换。
    最后,再推荐一个来自Github 可以推断正则表达式的包inferregex,用函数infer_regex() 可根
    据字符串推断正则表达式。

1.6 控制结构

编程中的控制结构,是指分支结构循环结构。

分支结构

if (条件){

} else if {

} else {

}

另一种对分支写法switch()函数

x = "b"
v = switch(x, "a"="apple", "b"="banana", "c"="cherry")
v
## [1] "banana"

ifelse(x<0, -x, x) ### |x|的简单表达方式


image.png

1.6.2 循环结构

循环,用来处理对多个同类输入做相同事情(即迭代) ,如对向量的每个元素做相同操作,对数据
框不同列做相同操作、对不同数据集做相同操。
R中有三种方式实现循环:

  • for, while, repeat循环
  • apply函数家族
  • purrr泛函式编程
    跳出循环:
  • 用关键字next 跳出本次循环,进入下次循环
  • 用关键词break 跳出循环
说明

关于“for 循环运行速度慢” 的说法,实际上已经过时了,现在的R、Matlab 等软件经过多年的
内部优化已经不慢了,之所以表现出来慢,是因为你没有注意两个关键点:

  • 提前为保存循环结果分配存储空间;
  • 为循环体中涉及的数据选择合适的数据结构。

apply 函数族和purrr 泛函式编程能够更加高效简洁地实现一般的for 循环、while 循环,但这不代表for 循环、while 循环就没用了,它们可以在更高的层次使用(相对于在逐元素级别使用)。

举例子for
df = as_tibble(iris[,1:4]) ### 给出数据
output = vector("double", 4)#### 准备好输出的空间
for (i in 1:4) {   ### 迭代
  output[i] = mean(df[[i]]) ### 循环体
  
}

循环模式

  1. 根据数值索引:for(i in seq_along(xs)), 迭代中使用x[i]. 最常用
for (i in seq_along(x)) {
name = names(x)[i]
value = x[i]
}
  1. 根据元素值:for(x in xs), 迭代中使用x.
  2. 根据名字:for(nm in names(xs)), 迭代中使用x[nm].

(ii) 将每次循环得到的结果合并为一个整体对象

不要‘每循环一次,就做一次拼接’’
先将结果保存为列表,等循环结束再将列表unlist() 或purrr::flatten_dbl()成一个向量。

output = list() # output = NULL 也行
# output = vector("list", 3)
for(i in 1:3) {
    output[[i]] = c(i, i^2)
}

另外两种类似的情形是:

  1. 生成一个长字符串。不是用str_c() 函数将上一次的迭代结果拼接到一起,而是将结果保存为字符向量,再用函数str_c(output, collapse= " ") 合并为一个单独的字符串;
  2. 生成一个大的数据框。不是依次用rbind() 函数合并每次迭代的结果,而是将结果保存为列表,再用dplyr::bind_rows(output) 函数合并成一个单独的数据框,或者直接一步到位用purrr::map_dfr()

while用于迭代数未知的情况

while (condition) {
# 循环体
}

repeat()循环

注意,repeat 循环至少会执行一次。

repeat{
# 循环体
if(退出条件) break
}

apply函数族

注意:建议弃用apply 函数族,直接用purrr::map 系列。
基本格式:
apply(x, MARGIN, FUN, ...)

  • x:为数据对象(矩阵、多维数组、数据框) ;
  • MARGIN:1 表示按行,2 表示按列
  • FUN:表示要作用的函数。
tapply()

该函数可以按照因子分组计算分组统计:tapply(var1, var2, FUN)

lapply()

lapply(x, FUN, ...)

  • x:为数据对象(列表、数据框、向量) ;
  • FUN:表示要作用的函数。
sapply()与lappy类似,但是可以返回矩阵形式的结果

sapply(x, FUN, simplify = TRUE, ...)

purrr 泛函式编程

相对于apply 族,purrr 泛函式编程提供了更多的一致性、规范性和便利性,更容易记住和使用。
需要知道: 循环迭代, 泛函式编程(函数包含其他函数作为参数):
map(x, FUN)

常见的返回结果类型
  • map_chr(.x, .f): 返回字符型向量
  • map_lgl(.x, .f): 返回逻辑型向量
  • map_dbl(.x, .f): 返回实数型向量
  • map_int(.x, .f): 返回整数型向量
  • map_dfr(.x, .f): 返回数据框列表,再bind_rows 按行合并为一个数据框
  • map_dfc(.x, .f): 返回数据框列表,再bind_cols 按列合并为一个数据框

purrr风格的公式(使用匿名函数)

  • 一元函数:序列参数是.x 比如,f(x) = x2 + 1, 其purrr 风格公式就写为:~ .x ^ 2 + 1
  • 二元函数:序列参数是.x, .y 比如,f(x, y) = x2 −3y, 其purrr 风格公式就写为:~ .x ^ 2 - 3* .y
  • 多元函数:序列参数是..1, ..2, ..3 等比如,f(x, y, z) = ln(x + y + z), 其purrr 风格公式就写为:~ log(..1 + ..2 + ..3)
    所有序列参数,可以用... 代替,比如,sum(...) 同sum(..1, ..2, ..3)

map(): 依次应用到一个序列的每个元素上

map(.x, .f, ...)
map_*(.x, .f, ...)

  • .x 为序列
  • .f 为要应用的一元函数,或purrr 风格公式(匿名函数)
  • ... 可设置函数.f 的其他参数

map() 返回结果列表,基本同lapply()。例如,计算前文df,每列的均值,即依次将mean() 函数,应用到第1 列,第2 列,. . . ;并控制返回结果为double 向量:

map(df, mean)
或者
map_dbl(df, mean)  # better

另外,mean() 函数还有其他参数,如na.rm,若上述计算过程需要设置忽略缺失值,只需:

map_dbl(df, mean, na.rm = TRUE) # 数据不含NA, 故结果同上(略)
map_dbl(df, ~mean(.x, na.rm = TRUE)) # purrr 风格公式写法

有了map() 函数,对于自定义只接受标量的一元函数,比如f(x), 想要让它支持接受向量作为输
入,根本不需要改造原函数,只需:

map_*(xs, f) # xs 表示若干个x 构成的序列

二元函数

map2(.x, .y .f, ...)
map2_*(.x, .y, .f, ...)

  • .x 为序列1
  • .y 为序列2
  • .f 为要应用的二元函数,或purrr 风格公式(匿名函数)
  • ... 可设置函数.f 的其他参数
map2_dbl(height, weight, ~ .y / .x^2)

类似的:
map2_*(xs, ys, f) # xs, ys 分别表示若干个x, y 构成的序列

pmap应用多元函数到多个序列的每组元素,可以实现对数据框逐行迭代

pmap(.l, .f, ...)
pmap_*(.l, .f, ...)

  • .l 为数据框,
  • .f 为要应用的多元函数
  • ... 可设置函数.f 的其他参数

pmap_*() 提供了一种行化操作数据框的办法。
求均值

pmap_dbl(df, ~ mean(c(...)))  

其他的purrr函数

  1. imap_(.x, .f): 带索引的map_() 系列,迭代的时候既迭代元素,又迭代元素的索引(位置或名字),purrr 风格公式中用.y 表示索引;
  2. invoke_map_*(.f, .x, ...): 将多个函数依次应用到序列,相当于依次执行:.f[[1]](.x,
    ...), .f[[2]](.x, ...), . . . . . .
  3. walk 系列:walk(.l, .f, ...), walk2(.l, .f, ...), pwalk(.l, .f, ...) 将函数依次作用到序列上,不返回结果。有些批量操作是没有或不关心返回结果的, 例如批量保存数据到文件、批量绘图并保存到文件等。
  4. modify 系列:modify(.x, .f, ...), modify2(.x, .y, .f, ...), modify_depth(.x,
    .depth, .f, ...) 将函数.f 依次作用到序列.x,并返回修改后的序列.x
  5. reduce(): 可先对序列前两个元素应用函数,再对结果与第3 个元素应用函数,再对结果与第4 个元素应用函数,. . . . . . 直到所有的元都被“reduced”
    *reduce(1:100, sum) 是对1:100 求累加和;
    *reduce() 可用于批量数据连接
  6. accumulate(): 与reduce() 作用方式相同,不同之处是:reduce() 只返回最终的结果,而accumulate() 会返回所有中间结果。

自定义函数

函数名= function(输入1, ..., 输入n) {
函数体
return(返回值) ### 非必须,默认最后一行的值为返回值
}

第一步:分析输入和输出,设计函数外形

输入有几个,分别是什么,适合用什么数据类型存放;
输出有几个,分别是什么,适合用什么数据类型存放。

第二步,梳理功能的实现过程
第三步,将第二步的代码封装到函数体
调用函数:source("*.R", encoding="UTF-8")

多个数值时,需要向量化改进

  1. 结合for和 if两个函数
  2. 借助apply 族或map 系列函数:
scores = c(35, 67, 100)
map_chr(scores, Score_Conv)

处理多个返回值,将多个返回值放入一个列表(或数据框),再返回一个列表。

自定义时,增加默认参数值(需要if, else函数配合),也可用switch()函数

“...”参数,可用任意接收多个对象,并作为一个列表传递它们

R还有非常多的基本函数,可用使用

具有多个矩阵函数


nrow(A) # 返回矩阵A 的行数
ncol(A) # 返回矩阵A 的列数
dim(A) # 返回矩阵x 的维数(几行× 几列)
colSums(A) # 对矩阵A 的各列求和
rowSums(A) # 对矩阵A 的各行求和
colMeans(A) # 对矩阵A 的各列求均值
rowMeans(A) # 对矩阵A 的各行求均值
t(A) # 对矩阵A 转置
det(A) # 计算方阵A 的行列式
crossprod(A, B) # 计算矩阵A 与B 的内积, t(A) %*% B
outer(A, B) # 计算矩阵的外积(叉积) , A %o% B
diag(x) # 取矩阵对角线元素,或根据向量生成对角矩阵
diag(n) # 生成n 阶单位矩阵
solve(A) # 求逆矩阵(要求矩阵可逆)
solve(A, B) # 解线性方程组AX=B
ginv(A) # 求矩阵A 的广义逆(Moore-Penrose 逆)
eigen() # 返回矩阵的特征值与特征向量(列)
kronecker(A, B) # 计算矩阵A 与B 的Kronecker 积
svd(A) # 对矩阵A 做奇异值分解,A=UDV'
qr(A) # 对矩阵A 做QR 分解: A=QR, Q 为酉矩阵, R 为阶梯形矩阵
chol(A) # 对正定矩阵A 做Choleski 分解, A=P'P,P 为上三角矩阵
A[upper.tri(A)] # 提取矩阵A 的上三角矩阵
A[lower.tri(A)] # 提取矩阵A 的下三角矩阵


同样有很多的概率函数

factorial(n) # 计算n 的阶乘
choose(n, k) # 计算组合数
gamma(x) # Gamma 函数
beta(a, b) # beta 函数
combn(x, m) # 生成x 中任取m 个元的所有组合, x 为向量或整数n

R 中,常用的概率函数有密度函数、分布函数、分位数函数、生成随机数函数,其写法为:
d = 密度函数(density)
p = 分布函数(distribution)
q = 分位数函数(quantile)
r = 生成随机数(random)
上述4 个字母+ 分布缩写,就构成通常的概率函数,例如:

随机抽样

sample() 函数,用来从向量中重复或非重复地随机抽样,基本格式为:
sample(x, size, replace = FALSE, prob)

  • x:向量或整数;
  • size:设置抽样次数;
  • replace:设置是否重复抽样;
  • prob:设定抽样权重
时间序列函数lag()和diff() 函数

states::lag()
lag(x, k, ...)

  • x:为数值向量/矩阵或一元/多元时间序列;
  • k:为滞后阶数,默认为1.

diff() 函数,用来计算时间序列的差分,基本格式为:
diff(x, lag = 1, difference = 1, ...)

  • x:为数值向量/矩阵;
  • lag:为滞后阶数,默认为1;
  • difference:为差分阶数,默认为1.

相关文章

网友评论

      本文标题:R语言编程-Tidyverse 书籍-第一章(2)

      本文链接:https://www.haomeiwen.com/subject/bpmdprtx.html