在涉及大批量数据或重复操作时,使用流程控制能提高工作效率。流程控制主要内容包括:1. 条件执行结构1.1 if_else 语句
1.2 switch 语句
2. 循环执行结构2.1 for 和 while 语句
2.2 repeat 语句
2.3 循环中的 next 和 break
3. 循环结构在滚动回归分析中的运用
4. 附录
1. 条件执行结构
1.1 if_else 语句
基本语法:
if (condition) cons.expr else alt.expr
# 执行体代码量较少时可以在一行完成
if (condition) {
cons.expr_1
cons.expr_2
...
} else {
alt.expr_1
alt.expr_2
...
}
# 执行体代码量较多时可以用 {} 分行书写condition:判定条件,能够被转换为逻辑值 TRUE 或 FALSE
cons.expr:对应 TRUE 执行的命令
alt.expr:对应 FALSE 执行的命令
【注】若 condition 返回长度大于 1 的结果,就只会使用其中第一个元素进行判定
举几个例子:
x
if (x == 2) 1 else 2
# [1] 2
if (x > 2) 1 else 2
# [1] 1
x
if (x >= 0) sqrt(x) else 0
# [1] 1 0 NaN
if (x < 0) sqrt(x) else 0
# [1] 0
最后一个例子提示 warning,是因为 x 是向量 (1, 0, -1),x >= 0 返回结果是 (TRUE, TRUE, FALSE),系统对于长度大于 1 的 condition 只取第一个元素 TRUE,因此进行了开方运算;而 x < 0 返回结果是 (FALSE, FALSE, TRUE),取第一个元素 FALSE,因此返回 0。
要实现长度大于 1 的 condition 识别,一种方法是使用循环语句判定,另一种方法则是使用 ifelse() 函数,语法如下:
ifelse(test, yes, no)test:能被转换为逻辑型的对象
yes:对应 test 为 TRUE 执行的命令
no:对应 test 为 FALSE 执行的命令
用上面的例子:
x
ifelse(x >= 0, sqrt(x), NA)
# [1] 1 0 NA
看到这里就够了,下面的内容有点无聊,大家可以直接跳到 1.2 继续阅读 = =
为了弄清 ifelse() 的机制,作者又尝试了另外的例子:
x
yes
ifelse(x, yes, 0)
# [1] 5 6 7 0 5
ifelse 的返回似乎与 x 和 yes 的索引有关:x 转换为逻辑型变量后是 (T, T, T, F, T),test 为真对应的 x 的索引为 (1, 2, 3, NA, 5),将该索引应用到 yes 上,返回结果是 (yes[1], yes[2], yes[3], NA, yes[5]),其中 NA 使用 ifelse() 中 no 对应的 0 代替,而 yes[5] 则循环对应 yes[1],因此最终结果应该是(5, 6, 7, 0, 5),与返回的结果一致。ifelse() 具体的代码在附录查看,作者添加了注释以便大家阅读(实际可能造成阅读障碍,捂脸)。
1.2 switch 语句
基本语法:
switch(expr, object_1, object_2, ...)expr:长度为 1 的向量,数值型则根据索引值返回对象,字符型则根据对象名返回对象
若 expr 的返回值少于等于 object 总个数,则返回 expr 对应序数的 object
若 expr 的返回值多于 object 总个数,则返回 NULL
举几个例子:
a1
a2
a3
# 根据索引值返回对象
switch(1, a1, a2, a3)
# [1] 1 2 3
switch(3, a3, a2, a1)
# [1] 1 2 3
#根据对象名返回对象
switch("object2", object1 = a1, object2 = a2, object3 = a3)
# [,1] [,2] [,3]
# [1,] 1 5 9
# [2,] 2 6 10
# [3,] 3 7 11
# [4,] 4 8 12
switch 语句常与循环语句一起使用。
2. 循环执行结构
2.1 for 和 while 语句
基本语法:
for (var in seq) expr
# 循环体只有一条代码时
for (var in seq) {
expr_1
expr_2
...
}
# 循环体包含多条代码时var:一个变量
seq:一个序列,因子型数据会被转换为字符型
expr:循环体命令
while (cond) expr
while (cond) {
expr_1
expr_2
...
}cond:一个长度为 1 的非空逻辑型向量,长度大于 1 时仅使用第一个元素
for 和 while 使用较为简单,这里举几个例子:
for(i in 1: 3) print(1: i)
# [1] 1
# [1] 1 2
# [1] 1 2 3
set.seed(100)
for(n in c(2, 4, 8, 16, 32)) {
x
cat(n, ": ", sum(x^2), "\n", sep = "")
# 将 n、冒号、sum(x^2)拼接后返回
}
# 2: 0.2694976
# 4: 0.9078226
# 8: 2.26496
# 16: 10.87217
# 32: 39.83575
i = 1
while(i <= 3) {
print(1: i)
i = i + 1
}
# [1] 1
# [1] 1 2
# [1] 1 2 3
set.seed(100)
n = 2
while (n <= 32) {
x
cat(n, ": ", sum(x^2), "\n", sep = "")
n = n * 2
}
# 输出结果同 for 一致(记得运行 set.seed(100))
2.2 repeat 语句
基本语法:
repeat expr
repeat {
expr_1
expr_2
...
if (cond) break
}
repeat 会重复执行命令 expr,如果不使用 break 跳出循环,就会一直执行下去,相当于:
while (TRUE) expr
可以试试执行下列代码[doge]:
i = 0
repeat {
i = i + 1
print(i)
} # ESC 跳出循环
# 也可以写成 while
i = 0
while (T) {
i = i + 1
print(i)
}
2.3 循环中的 next 和 break
执行到 next ,就会跳过当前循环节剩余的代码,直接进入到下一个循环节中;执行到 break 则相反,会直接跳出整个循环。这两个语句通常和循环/条件执行结构一同使用。
举两个例子:
for (i in 1: 5) {
if (i == 3) next
# 当 i 等于 3 时,就跳过下列语句并进入下一个循环节
print(i)
}
# 输出结果为 1 2 4 5
i = 0
while (i <= 100) {
if (i > 10) break
# 当 i 大于 10 时,就终止循环
print(i)
i = i + 2
}
# 输出结果为 0 2 4 6 8 10
3. 循环结构在滚动回归分析中的运用
时间序列数据的分析中,回归分析是主要手段之一,可以通过对某一个时间段的数据回归得到该时间段对应的回归信息(系数、截距、残差等),但这只能获得一个时点的数据,如果我们想要获得这些回归信息随时间变化的情况,应该怎么办呢?
我们不妨将一个固定长度的时间段称为一个窗口(如一个月),以某个时间点作为研究的起始时间(如 2016 年 8 月 1 日),将第一个时间段作为第一个窗口(从 2016 年 8 月 1 日到 2016 年 9 月 1 日),对该窗口的数据进行回归拟合,获得第一组回归信息;而后将窗口向后一天移动,获得第二个窗口(从 2016 年 8 月 2 日到 2016 年 9 月 2 日),进行拟合得到第二组回归信息;以此类推。通过不断滚动窗口我们就能得到以天为单位,回归信息随时间变化的情况了,进而就能利用这个信息推断事物的发展规律或趋势。这种方法叫做“滚动回归分析法”(瞎编的= =我也不懂是不是叫这个)。
这种滚动回归就可以用循环执行结构实现,下面以医药板块从 2010-07-01 到 2018-06-29 的股市收益率为例分析(模型采用自回归条件异方差模型)。
library(timeSeries)
library(rugarch)
dat_link
dat
r
# 根据收盘价计算对数收益率
# 根据过去作者的拟合经验,选择三阶自回归模型
ar1 = c() # 储存一阶自回归系数
ar3 = c()
se1 = c() # 储存一阶自回归系数标准差
se3 = c()
# 设置模型参数
mod = ugarchspec(variance.model = list(model = "sGARCH",
garchOrder = c(1, 1),
submodel = NULL,
external.regressors = NULL,
variance.targeting = FALSE),
mean.model = list(armaOrder = c(3, 0),
include.mean = TRUE,
archm = FALSE,
archpow = 1,
arfima = FALSE,
external.regressors = NULL,
archex = FALSE),
distribution.model = "std")
# 数据量较小,跑完循环大概耗时两分钟
for(i in 1:243)
{
subr = r[i:(970+i)] # 设置窗口
cgarch = ugarchfit(mod, data = subr, solver = "solnp") # 拟合模型
coe = data.frame(cgarch@fit$coef)# 获取系数
std = data.frame(cgarch@fit$robust.se.coef) # 获取标准差
ar1[i] = coe[2, ] # 存储系数
ar3[i] = coe[4, ]
se1[i] = std[2, ] # 存储标准差
se3[i] = std[4, ]
}
ar1_up = ar1 + 1.96*se1 # 计算 ar1 95% 置信上界
ar1_low = ar1 - 1.96*se1 # 计算 ar1 95% 置信下界
ar3_up = ar3 + 1.96*se3
ar3_low = ar3 - 1.96*se3
result
ar1_up = ar1_up, ar1_low = ar1_low,
ar3_up = ar3_up, ar3_low = ar3_low)
获取了数据以后我们就可以绘图看看什么效果了:
先绘制一阶自相关系数的变化趋势图
library(ggplot2)
p
p + geom_line(aes(x = x, y = ar1)) +
geom_line(aes(x = x, y = ar1_up), color = I("blue")) +
geom_line(aes(x = x, y = ar1_low), color = I("blue")) +
geom_line(aes(x = x, y = 0),linetype = 2, color = I("red"))
黑色实现代表系数,两条蓝色实线分别是置信上界和下界,红色虚线落入两条蓝色实线之间则表明系数不显著。可以看到,一阶自相关系数围绕 0.05 上下波动,游离在显著边缘,说明收益率存在一阶自相关性,但其相关性较弱。
绘制三阶自回归系数变化趋势图
p + geom_line(aes(x = x, y = ar3)) +
geom_line(aes(x = x, y = ar3_up), color = I("blue")) +
geom_line(aes(x = x, y = ar3_low), color = I("blue")) +
geom_line(aes(x = x, y = 0),linetype = 2, color = I("red"))
三阶自相关系数有明显趋于 0 的趋势,0 刻度线最后落入置信区间,表明系数的显著性不断降低,三阶自相关性减弱。
基本思路就是使用 for/while/repeat 循环对每个窗口分别拟合数据,并将拟合结果整合到一起,从而实现滚动回归分析。
4. 附录
ifelse 的代码如下(函数的主要架构作者在注释中用【】分割):
function (test, yes, no)
{
#【test 是向量形式时执行以下代码】
if (is.atomic(test)) {
# is.atomic 判断 test 是否是向量
if (typeof(test) != "logical")
storage.mode(test)
# 将 test 转换为逻辑型数据
#【 test 长度为 1 时执行下列代码】
if (length(test) == 1 && is.null(attributes(test))) {
if (is.na(test))
return(NA)
# 如果 test 是 NA,直接返回 NA,否则执行下列代码
# test 为 TRUE 时执行下列代码
else if (test) {
if (length(yes) == 1) {
yat
if (is.null(yat) || (is.function(yes) && identical(names(yat),
"srcref")))
# 若 yes 符合上述格式,返回 yes,否则 report error
return(yes)
}
}
# test 为 TRUE 时执行上述代码
# test 为 FALSE 时执行下列代码
else if (length(no) == 1) {
nat
if (is.null(nat) || (is.function(no) && identical(names(nat),
"srcref")))
# 若 no 符合上述格式,返回 no,否则 report error
return(no)
}
# test 为 FALSE 时执行上述代码
}
}
#【 test 长度为 1 时执行上述代码】
#【test 是向量形式且长度为 1 时执行上述代码】
#【当 test 形式为递归或列表(非向量),或 test 长度大于 1 时,执行下列代码】
else test
methods::as(test, "logical")
else as.logical(test)
# 将 test 转换为逻辑型数据
ans
len
ypos
npos
if (length(ypos) > 0L) # 0L 指整数 0
ans[ypos]
# 将 test 为 TRUE 的索引应用到 yes 的循环上,并赋值给 ans
if (length(npos) > 0L)
ans[npos]
# 将 test 为 FALSE 的索引应用到 no 的循环上,并赋值给 ans
ans # 返回最终结果
#【当 test 形式为递归或列表(非向量),或 test 长度大于 1 时,执行上述代码】
}
可以目测下列代码执行结果作为练习:
x
y
z
ifelse(x, y, z)