此部分的内容,全部是对张敬信博士书籍的学习总结。
出发点: 因为tidyverse简洁编码,使R代码更加易读。我就想系统再次学习下R,这样让自己写的R代码更加简洁。
关于“for 循环运行速度慢” 的说法,实际上已经过时了,现在的R、Matlab 等软件经过多年的
内部优化已经不慢了,之所以表现出来慢,是因为你没有注意两个关键点:
提前为保存循环结果分配存储空间;
为循环体中涉及的数据选择合适的数据结构。
apply 函数族和purrr 泛函式编程能够更加高效简洁地实现一般的for 循环、while 循环,但这
不代表for 循环、while 循环就没用了,它们可以在更高的层次使用(相对于在逐元素级别使用)### 时间段数据
interval() ##计算两个时间点的时间间隔,返回时间段数据
t %within% gap # 判断t是否属于该时间段
考虑长时间段应该使用period
日期时间的计算
时间点+时间段生成一个新的时间点
月份加运算: %m+%, 表示日期按月份增加的
时间序列
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正则表达式
正则表达式,是根据字符串规律按一定法则,简洁表达一组字符串的表达式。
正则表达式包括:只能匹配自身的普通字符(如英文字母、数字、标点等) 和被转义了的特殊字符
(称为‘‘元字符”)
需要创建多行模型的正则表达式
特殊字符类与反义
POSIX 字符类
运算优先级
圆括号括起来的表达式最优先,其次是表示重复次数的操作(即* + { }) ;再次是连接运算(即几
个字符放在一起,如abc) ;最后是或者运算(|)
另外,正则表达式还有若干高级用法,常用的有零宽断言和分组捕获,将在下面实例中进行演示。
例子
(零宽断言) 匹配两个标志之间的内容
适合想要匹配的内容没有规律性,但该内容位于两个有规律性的标志之间,标志也可以是开始和
结束。
通常想要匹配的内容不包含两边的‘‘标志’’,这就需要用零宽断言。简单来说,就是一种引导语法
告诉既要匹配到‘‘标志’’,但又不包含‘‘标志’’。左边标志的引导语法是(?<= 标志),右边标志的引导
语法是(?= 标志),而真正要匹配的内容放在它们中间。
分组捕获
正则表达式中可以用圆括号来分组,作用是
- 确定优先规则
- 组成一个整体
- 拆分出整个匹配中的部分内容(称为捕获)
- 捕获内容供后续引用或者替换。
最后,再推荐一个来自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|的简单表达方式
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]]) ### 循环体
}
循环模式
- 根据数值索引:for(i in seq_along(xs)), 迭代中使用x[i]. 最常用
for (i in seq_along(x)) {
name = names(x)[i]
value = x[i]
}
- 根据元素值:for(x in xs), 迭代中使用x.
- 根据名字: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)
}
另外两种类似的情形是:
- 生成一个长字符串。不是用str_c() 函数将上一次的迭代结果拼接到一起,而是将结果保存为字符向量,再用函数str_c(output, collapse= " ") 合并为一个单独的字符串;
- 生成一个大的数据框。不是依次用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函数
- imap_(.x, .f): 带索引的map_() 系列,迭代的时候既迭代元素,又迭代元素的索引(位置或名字),purrr 风格公式中用.y 表示索引;
- invoke_map_*(.f, .x, ...): 将多个函数依次应用到序列,相当于依次执行:.f[[1]](.x,
...), .f[[2]](.x, ...), . . . . . . - walk 系列:walk(.l, .f, ...), walk2(.l, .f, ...), pwalk(.l, .f, ...) 将函数依次作用到序列上,不返回结果。有些批量操作是没有或不关心返回结果的, 例如批量保存数据到文件、批量绘图并保存到文件等。
- modify 系列:modify(.x, .f, ...), modify2(.x, .y, .f, ...), modify_depth(.x,
.depth, .f, ...) 将函数.f 依次作用到序列.x,并返回修改后的序列.x - reduce(): 可先对序列前两个元素应用函数,再对结果与第3 个元素应用函数,再对结果与第4 个元素应用函数,. . . . . . 直到所有的元都被“reduced”
*reduce(1:100, sum) 是对1:100 求累加和;
*reduce() 可用于批量数据连接 - accumulate(): 与reduce() 作用方式相同,不同之处是:reduce() 只返回最终的结果,而accumulate() 会返回所有中间结果。
自定义函数
函数名= function(输入1, ..., 输入n) {
函数体
return(返回值) ### 非必须,默认最后一行的值为返回值
}
第一步:分析输入和输出,设计函数外形
输入有几个,分别是什么,适合用什么数据类型存放;
输出有几个,分别是什么,适合用什么数据类型存放。
第二步,梳理功能的实现过程
第三步,将第二步的代码封装到函数体
调用函数:source("*.R", encoding="UTF-8")
多个数值时,需要向量化改进
- 结合for和 if两个函数
- 借助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.