sessionInfo()
如果有大 size 的向量和矩阵运算,建议安装和使用 Microsoft R Open. 该版本对向量和矩阵的向量化运算(对所有元素分别执行)进行了大幅优化,无需引入任何包,就能自动调用多核并行计算,可提高运算速度数十倍。
这也意味着,向量化运算是 R 最基本、最主要的代码风格。
但是,Microsoft R Open 的某些包更新不够及时(如 rmarkdown)。为了 Knit 的方便,可以 Rstudio 选择 R 的最新版,在 VSCode 中用 Microsoft R Open 日常加速计算。
R 中词法作用域的机理通过环境实现。
#> label name
#> 1 <environment: R_GlobalEnv> ""
#> 2 <environment: package:cranlogs> "package:cranlogs"
#> 3 <environment: package:ggthemes> "package:ggthemes"
#> 4 <environment: package:xfun> "package:xfun"
#> 5 <environment: package:rootSolve> "package:rootSolve"
#> 6 <environment: package:numDeriv> "package:numDeriv"
#> 7 <environment: package:ivreg> "package:ivreg"
#> 8 <environment: package:zeallot> "package:zeallot"
#> 9 <environment: package:downloadthis> "package:downloadthis"
#> 10 <environment: package:htmlwidgets> "package:htmlwidgets"
#> 11 <environment: package:plotly> "package:plotly"
#> 12 <environment: package:kableExtra> "package:kableExtra"
#> 13 <environment: package:magrittr> "package:magrittr"
#> 14 <environment: package:see> "package:see"
#> 15 <environment: package:cowplot> "package:cowplot"
#> 16 <environment: package:ggtext> "package:ggtext"
#> 17 <environment: package:lmerTest> "package:lmerTest"
#> 18 <environment: package:lme4> "package:lme4"
#> 19 <environment: package:Matrix> "package:Matrix"
#> 20 <environment: package:performance> "package:performance"
#> 21 <environment: package:effectsize> "package:effectsize"
#> 22 <environment: package:emmeans> "package:emmeans"
#> 23 <environment: package:data.table> "package:data.table"
#> 24 <environment: package:bruceR> "package:bruceR"
#> 25 <environment: package:forcats> "package:forcats"
#> 26 <environment: package:stringr> "package:stringr"
#> 27 <environment: package:dplyr> "package:dplyr"
#> 28 <environment: package:purrr> "package:purrr"
#> 29 <environment: package:readr> "package:readr"
#> 30 <environment: package:tidyr> "package:tidyr"
#> 31 <environment: package:tibble> "package:tibble"
#> 32 <environment: package:ggplot2> "package:ggplot2"
#> 33 <environment: package:tidyverse> "package:tidyverse"
#> 34 <environment: package:stats> "package:stats"
#> 35 <environment: package:graphics> "package:graphics"
#> 36 <environment: package:grDevices> "package:grDevices"
#> 37 <environment: package:utils> "package:utils"
#> 38 <environment: package:datasets> "package:datasets"
#> 39 <environment: package:methods> "package:methods"
#> 40 <environment: 0x00000218b98f7538> "Autoloads"
#> 41 <environment: base> ""
#> 42 <environment: R_EmptyEnv> ""
最外层的 R_EmptyEnv
是唯一没有父环境的环境。
每加载一个扩展包,这个包的环境都会插入搜索路径,成为全局环境新的父级环境,并覆盖之前加载的包的同名函数。
调用函数时,首先创造一个 runtime 环境,函数优先在 runtime
环境搜索用到的变量,搜索不到时,就到上一级环境搜索,直至最外层的
R_EmptyEnv
。
函数 runtime 环境的父环境,是函数第一次创建时所在的环境。如果函数是全局创建的(即使是通过 .R 脚本导入),其父环境就是全局环境。
函数储存变量,也储存在 runtime 环境中,只有返回值能传入调用它的环境。
这里有两个例外:
x <<- 5
能将数据 5
储存到父环境的变量 x
中,如果父环境中不存在变量
x,会一直向上直至全局环境进行变量查找。若在查找过程中寻找到该名称的变量,就会进行赋值操作。否则,将在全局环境中创建变量并赋值。assign("var_name", value, envio = as.environment(pos))
可以直接指定将 value
储存到哪个环境中。该函数的第一个参数是字符串,也可以是字符串向量,一次性赋值多个变量。环境交互函数 | |
---|---|
pryr::parenvs(all = TRUE) |
显示当前环境的层级,返回一个列表,从当前环境到
R_EmptyEnv |
as.environment() |
接受一个字符串环境名,返回对应的环境 |
environment() |
返回当前 active 环境 |
globalenv() /.GlobalEnv |
返回全局环境 R_GlobalEnv,这是最活跃、默认储存对象的环境 |
baseenv() |
返回基环境 base |
emptyenv() |
返回空环境 R_EmptyEnv |
parent.env() |
返回参数环境的父环境 |
ls() /objects() |
返回字符串向量,列出当前环境的所有对象 |
ls.str(mode="list", pattern="^\\w$") |
显示当前环境的结构。mode 参数过滤对象类型,pattern 参数为正则表达式,过滤对象名 |
remove() /rm() |
删除环境中一个或更多个对象。 |
rm(list=ls()) |
删除环境中所有对象 |
getOption(x),
查看全局选项
options(...),
设定全局选项。如 digits=7,
设定显示数字的位数。warn=0, 可以改为1,在警告产生时立即显示。
Working Directory | |
---|---|
getwd() /setwd() |
查看/设定当前工作目录 |
R.home() |
R 软件的目录 |
list.dirs() |
查看当前目录的所有子孙目录(递归查看) |
dir([path, ][pattern, ][all.files = FALSE]) |
查看当前/指定目录中的文件和子目录,pattern为正则,all.files 控制是否显示隐藏文件 |
system("tree") |
查看当前目录的树状层级结构 |
dir.create()dir.create(path="a1/b2/c3",recursive = TRUE) |
在当前目录下创建一个子目录 递归创建深层子目录 |
file.create() |
创建文件 |
file.exists(path) |
路径是否存在 |
file.rename(from, to) | 路径重命名 |
file.remove() | 删除 |
file.append(file1, file2) | 合并 |
file.copy(file1, file2) | 用 file1 覆盖 file2 原本的内容 |
file.path(…) | 根据操作系统的不同,将参数列表中的各项用/ 或
\ 串成一个路径(拼接目录字符串) |
dirname(path) | 返回路径的上级路径 |
basename(path) | 返回路径的本级目录 |
path.expand() | 转换~为用户目录 |
normalizePath() | 返回绝对路径 |
使用 RStudio 创建 project 的一个优点在于,它的自动补全功能使编写文件路径变得更加高效。当你输入绝对路径或相对路径中的字符时,按下 Tab 键, RStudio 将会列出该目录中的所有文件,
tracemem()
查看对象的内存地址
Sys.sleep()
使程序暂停若干秒,一般用于某些循环(爬虫、动画等),故意降低运行频率
Sys.time()
返回系统时间;
system.time()
参数为大括号括起来的代码段,运行代码段并打印其运行时间
#> user system elapsed
#> 0.30 0.00 0.31
Help | |
---|---|
help(functionName) 或?functionName |
查看一个函数的用法 |
example(functionName) |
运行帮主文件中的例子 |
help.search(keyword) 或??keyword |
在所有包的文档中搜索关键词 |
RSiteSearch("xxx") |
搜索 CRAN 上包含关键词的包,自动打开一个网页,显示搜索结果 |
help(package = "") |
查看一个包的帮助 |
help(options) |
显示可用选项的说明 |
options() |
显示或设置当前选项 |
History | |
---|---|
history(#) |
显示最近使用过的#个命令(默认值为25) |
savehistory("myfile") |
保存命令历史到文件myfile中(默认值为.Rhistory) |
loadhistory("myfile") |
载入一个命令历史文件(默认值为.Rhistory) |
Function | 作用 |
---|---|
install.packages("package") |
安装包 |
remove.packages("xxx") |
卸载包 |
installed.packages() |
返回一个字符串矩阵,内含所有已安装的包的多项信息 |
packageVersion() |
返回包的版本信息 |
library() |
无参数时,显示库中已经安装了哪些包 |
library(package) |
有参数时,载入包. |
require(package) |
类似于 若已安装扩展包,则加载包;若尚未安装,则安装包:
|
unloadNamespace(package) |
解除载入一个包 |
search() |
查看环境中已经加载了哪些包 |
getOption('defaultPackages') |
查看默认加载的 R 包 |
update.packages("package") |
升级包。若不填入参数,则自动升级所有可以升级的包 |
package::function() |
调用相应包的函数(有时多个包用同一个名字命名不同的函数,会发生冲突,只能这样引用)。这种方式不会修改环境(搜索路径),所以如果一个函数只使用一两次,最好用这种方式调用。 |
data() |
查看所有预先提供的数据 |
data(package="") |
查看某个包所有预先提供的数据 |
data(dataset_name, package=) |
读入包中数据 |
# 查看 R 包的下载次数
library(cranlogs)
fashion <- function(package_name) {
d <- cran_downloads(
package = package_name,
from = "2022-01-01",
to = "2022-03-31"
)
sum(d$count)
}
fashion("ggplot2")
#> [1] 6591799
#> [1] 2414103
#> [1] 2512297
#> [1] 916778
后加载包的同名函数会将先加载包的函数屏蔽掉
如果两个都要用,则只能使用 package::function 语法
由此要注意,自己写包时,最好不要与已经被广泛使用的包中的函数重名
《R in Action 2nd Edition》 第21章有介绍。
代码见网盘中
R/Books/Programming/R-in-Action/Code/Ch21 Creating a package/
文件夹
更好的资源,见 Hadly 的《R packages》一书,讲授了良好的 R 软件项目实践,科学地创建 R 包:打包文件、生成文档、测试 代码
source('xxx.R')
不好,更推荐 modules 包
由于 R 的懒加载特性,模块中的代码不会运行,故 .R 脚本文件作为模块时,不必加载配置常量和包,纯写函数即可,所有的包和配置由主文件加载。
新建一个文本文件,写入
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.R]
@="rstudio.exe"
[HKEY_CLASSES_ROOT\.R\ShellNew]
"FileName"="C:\Users\humoo\OneDrive\ICT\Programming-Language\R\Best-Practice\template.R"
[HKEY_CLASSES_ROOT\rstudio.exe]
@="R Script"
另存为 register-R.reg,然后双击运行即可
数据处理的中间结果一般保存为 .rda 或 .rds 即可;有其他用途的数据转换结果,才有必要保存为 .csv 或 .json
二者区别:
return()
;只有在条件、循环等复杂逻辑中,需要提前返回的值,才用
return()
显式返回_
分隔单词,尽量使对象名
concise and meaningfulStop()
来抛出attach()
,
detach()
#
开头,后加一个空格# string ----------------
的注释分节行,帮助理清逻辑
Ctrl+Shift+R
可以生成这样一个分节行#'
开头,辅以若干标记:@title
,
@param
, @inheritParams
, @return
,
@description
, @details
,
@examples
, @seealso
, @export
#' @title 老虎机的返还规则
#' @param symbols 长度为3的字符串向量,表示老虎机的一次运行结果
#' @return 中奖金额
#' @export
score <- function(symbols) {
diamonds <- sum(symbols == "DD") # 有几个钻石
cherries <- sum(symbols == "C") # 有几个樱桃
## 识别模式
slots <- symbols[symbols != "DD"] # 考虑钻石以外的其他符号
same <- length(unique(slots)) == 1 # 是否其他符号相同,包含有且仅有2钻情形
all_bars <- all(slots %in% c("B", "BB", "BBB")) # 是否其他符号都是条
## 计算中奖金额
if (diamonds == 3) {
prize <- 100
} else if (same) {
# 哈希表
payouts <- c("7" = 80, "BBB" = 40, "BB" = 25, "B" = 10, "C" = 10, "0" = 0)
prize <- unname(payouts[slots[1]]) # 只要金额不要符号
} else if (all_bars) { # 0或1钻,其他全是条(一条、二条或三条)的金额
prize <- 5
} else if (cherries > 0) {
# 0或1钻,1-2个樱桃,且不能是1钻两樱桃
prize <- c(0, 2, 5)[cherries + diamonds + 1] # 如果只有一个樱桃,则将钻石当作樱桃
} else {
prize <- 0
}
## 根据钻石个数翻倍中奖金额
prize * 2^diamonds
}
单元测试应在另一个独立的的文件中进行
styler - A non-invasive source code formatter for R (lorenzwalthert.github.io)
style_text()
styles a stringstyle_file()
styles R and Rmd filesstyle_dir()
styles all R and/or Rmd files in a
directory.Addins
菜单,styles the active file R or Rmd
file, or the highlighted code.styler:::style_active_file()
向量化运算,即操作对象为向量或矩阵,对其所有元素执行同样的运算。向量化运算的速度不仅远快于显式循环(快两个数量级),也比
map()
等高阶函数要快得多。
如果运算符两边的变量 size 不相同,size 较小的变量会自动循环扩展,与 size 较大的变量保持一致。返回值的 size 将和较大的变量保持一致。
在计算矩阵的哈达马积、或对向量/矩阵的所有元素执行同样的变换时,向量化运算非常方便。
R 中的运算符和函数默认支持向量化运算,这是 R 的一大特色,Microsoft R Open 还对向量和矩阵的向量化运算进行了并行优化。因此,用好向量化运算、矩阵代数,才是符合 R 风格的代码。
编程经验丰富后,会产生一种下意识的直觉(或许写一写 julia 和 MATLAB 有明确语法形式的向量化运算有助于这种经验的养成),敏锐地意识到哪些函数支持向量化运算,并将代码尽量都写成向量式的。
如计算 \(\begin{aligned}w=\frac{1}{n} \sum_{i=1}^n|x_i-\hat{m}|\end{aligned}\),其中 \(\hat{m}\) 为中位数
[]
#' 本函数中的操作,逻辑判断、取子集、数乘、赋值,全部是向量化的
abs_set <- function(vec) {
vec[vec < 0] <- vec[vec < 0] * -1
vec
}
# 哈希表
vec <- c("DD", "BB", "C", "0")
tb <- c("DD" = "joker", "C" = "ace", "7" = "king", "B" = "queen", "BB" = "jack", "BBB" = "ten", "0" = "nine") # 键值对
vec
#> [1] "DD" "BB" "C" "0"
#> DD BB C 0
#> "joker" "jack" "ace" "nine"
#> [1] "joker" "jack" "ace" "nine"
R base 的很多内置函数是用 C 实现的,效率很高,尽量使用。
#> [1] 1 3 6 10 15 21 28 36 45 55
#> [1] 1 2 6 24 120 720 5040 40320 362880
#> [10] 3628800
多用 data.table 数据框在数据上就地修改
如果不得不用 for 循环,最好提前准备后储存空间。
#> user system elapsed
#> 0.16 0.03 0.21
如果没有提前确定足够大的空间储存 for 循环产生的大 size 数据 output,R 每次循环都需要在内存中找到一个新的位置以存放更大的对象。这意味着频繁地在内存中删除旧版 output,再找到新的地方存放该向量的新版本。因此,在循环结束的时候,R 已经将 output 在内存中反复重写了100万次。
#> user system elapsed
#> 0.03 0.00 0.03
参考《Advanced R》
stop("!")
/stopifnot()
,终止程序并打印错误信息:“Error:
!”warning("?!")
,打印警告信息 “Warning message: ?!”message("?")
,打印信息 “?”#> Error in try(stop("!")) : !
try(..., silent = FALSE)
try()
的返回值为一个
“try-error” 类的对象。#> [1] "numeric"
#> [1] "try-error"
对 list 列表中所有元素进行批量操作时,使用 try() 非常有用,可以有效避免个别元素不能计算引起的错误。
elements <- list(1:10, c(-1, 10), c(T, F), letters)
# results <- elements %>% map(~ log(.x)) # 报错,会阻止程序运行
result_list <- elements %>%
map(~ try(log(.x))) %>%
discard(function(result) {
"try-error" %in% class(result) # 去掉运算报错的项
})
#> Warning in log(.x): NaNs produced
#> Error in log(.x) : non-numeric argument to mathematical function
#> [[1]]
#> [1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379 1.7917595 1.9459101
#> [8] 2.0794415 2.1972246 2.3025851
#>
#> [[2]]
#> [1] NaN 2.302585
#>
#> [[3]]
#> [1] 0 -Inf
# 求解逆矩阵-错误处理
set.seed(1)
inverses <- vector(mode = "list", length = 5) # 预先分配空间
for (i in 1:5) {
x <- matrix(sample(0:2, 4, replace = T), nrow = 2)
x.inv <- try(solve(x), silent = TRUE)
if ("try-error" %in% class(x.inv)) {
# x.inv 是一个矩阵,class(x.inv) 返回长度为2的字符串向量
# 若用 class(x.inv) == 'try-error',返回长度为2的逻辑向量
# if() 无法处理长度大于 1 的条件
next
} else {
inverses[[i]] <- x.inv
}
}
inverses
#> [[1]]
#> NULL
#>
#> [[2]]
#> [,1] [,2]
#> [1,] -0.25 0.5
#> [2,] 0.50 0.0
#>
#> [[3]]
#> [,1] [,2]
#> [1,] 0.0 0.50
#> [2,] 0.5 -0.25
#>
#> [[4]]
#> NULL
#>
#> [[5]]
#> [,1] [,2]
#> [1,] 0.0 1.0
#> [2,] 0.5 -0.5
#> [[1]]
#> [,1] [,2]
#> [1,] -0.25 0.5
#> [2,] 0.50 0.0
#>
#> [[2]]
#> [,1] [,2]
#> [1,] 0.0 0.50
#> [2,] 0.5 -0.25
#>
#> [[3]]
#> [,1] [,2]
#> [1,] 0.0 1.0
#> [2,] 0.5 -0.5
#> Warning in file(file, "rt"): cannot open file 'possibly-bad-input.csv': No such
#> file or directory
#> Error in file(file, "rt") : cannot open the connection
tryCatch()
指定控制条件,进行异常捕捉,然后采用对应的函数处理异常和错误。
result <- tryCatch(
{
# 正常的逻辑
...
},
warning = function(w) {
# 出现warning的处理逻辑
...
},
error = function(e) {
# 出现error的处理逻辑
...
},
finally = {
# 不管出现异常还是正常都会执行的代码模块,
# 一般用来处理清理操作,例如关闭连接资源等。
...
}
)
# 例
get.msg <- function(path) {
con <- file(path, open = "rt", encoding = "latin1")
text <- readLines(con)
msg <- tryCatch(
{
text[seq(which(text == "")[1] + 1, length(text), 1)]
},
error = function(e) {
""
}
)
close(con)
return(paste(msg, collapse = "\n"))
}
show_condition <- function(code) {
tryCatch(code,
error = function(c) "error",
warning = function(c) "warning",
message = function(c) "message"
)
}
show_condition(stop("!"))
#> [1] "error"
#> [1] "warning"
#> [1] "message"
#> [1] 10
自定义发生错误时返回的信息
read.csv3 <- function(file, ...) {
tryCatch(
read.csv(file, ...),
error = function(c) {
c$message <- paste0(c$message, ' in "', file, '"')
stop(c) # 将文件路径加到错误信息中
}
)
}
try(read.csv("code/dummy.csv"))
#> Warning in file(file, "rt"): cannot open file 'code/dummy.csv': No such file or
#> directory
#> Error in file(file, "rt") : cannot open the connection
#> Warning in file(file, "rt"): cannot open file 'code/dummy.csv': No such file or
#> directory
#> Error in file(file, "rt") :
#> cannot open the connection in "code/dummy.csv"
downloadthis::download_file(
path = "../pdf/cheatsheet-rstudio-ide.pdf",
output_name = "cheatsheet-rstudio-ide",
button_label = "Download cheatsheet",
button_type = "success",
self_contained = FALSE
)
Shortcuts | |
---|---|
Ctrl + Shift + A | 选中部分行后,格式化代码 |
Ctrl + Alt + I | Insert chunk |
Alt + - | 插入 <- |
Ctrl + Shift + M | 插入 %>% 或 |> |
Alt + Shift + K | 显示快捷键 |
Ctrl + Shift + N | 新建脚本 .r文件 |
Ctrl + Enter Ctrl + Shift + Enter Ctrl + Alt + R |
运行一行代码 运行代码块 运行全部代码 |
Shift + Home/End | 选中光标到行首/末之间的部分 |
Tab / Ctrl+Space | 自动补齐 输入完函数名,按tab,自动添加开括号(和闭括号)。 |
Ctrl + Shift + C | 注释/取消注释 |
F1 | 查看帮助 |
Ctrl+ ↑ | 在 Console 中输入”xxx”,然后按 Ctrl+ ↑。就可以列出所有输入过的以”xxx”开头的命令。 |
GitHub 上可搜索、安装 rscodeio 主题
anthonynorth/rscodeio: An RStudio theme inspired by Visual Studio Code. (github.com)
Ctrl+S
)时自动格式化Ctrl+Enter
自动运行一行,Ctrl+Shift+Enter
自动运行当前文件安装 R 包 languageserver
在 VSCode 扩展商店中安装 R 插件。
r.rterm.option
,删除--no-save,--no-restore
,添加--no-site-file
和
R.exe
的路径--r-binary=C:\\Program Files\\R\\R-4.1.3\\bin\\R.exe
安装 Radian:一款现代的 R console,它是用 Python 编写的
安装完成后在 cmd 中输入 radian
查看是否安装成功。
若出现 “cannot determine R HOME”,可能在系统 PATH 中没有 R
路径,则在 PATH 中添加即可;或存在多个 R 路径(如新安装了某个版本)而
Radian 无法识别,要在 VSCode
中修改r.rterm.option
,也可以在设置界面的右上角打开
setting.json文件直接修改:
VSCode 中 Radian 相关设置
r.rterm.windows
,将其设置为 radian.exe 的路径。在
cmd 中(powershell 不行)输入 where radian
可以获取其路径。R: Bracketed Paste
并勾选,否则 Radian 不会启用r.sessionWatcher
并勾选定义在 R 脚本中使用的快捷键
最重要的两个快捷键:输入 <-
的快捷键
Alt
+-
;输入 %>%
或
|>
的快捷键
Ctrl
+Shift
+M
方法:VSCode 中打开 keybindings.json
(Ctrl
+K
Ctrl
+S
然后右上角打开配置文件),绑定快捷键覆盖默认值