R编码规范_1_Analyses
引言
好的编码规范就像是用对了标点符号。最重要的作用就是,提供一致性的编写规范,使代码更易读,并且更易写,因为固定的规范使你不需要纠结于选择而提高编程效率。
这篇R代码风格是,Hadley Wickham总结的tidyverse style guide,原规范是一本书,包含两部分,第一部分是Analyses脚本的规范,第二部分是package开发的编码规范。本文是第一部分Analyses 的内容。向大牛学习编码规范,自然没有错的。
本文中还引用了一个standford关于Rstyle 的描述网页的部分内容,内容不和Hadley Wickham的规范冲突,是补充覆盖到Hadley Wickham没有提到的细节部分。在本文中,引用该网页的内容,我用(standford)进行了标注。
对于进行编码规范的R包,有两个R包是支持本编码规范的,分别是
- 'styler'。styler可以实现交互地规范你选择的文本、文件、课题的编码。在Rstudio中有本插件,是最方便的规范代码的方法。
- 'linter'。linter执行自动检查来确认你的代码是否符合本编码规范。
1. 文件
1.1 文件命名
- 文件名以 .R 结尾
- 文件名只包含数字、字母、短横线和下划线。避免有空格、特殊字符。
- 如果有脚本顺序,以数字打头命名。如果超过10个,以01, 02等开头。
- 文件名,避免使用大写字母。因为windows和Mac OS X系统对文件命名的大小写不区分。
# Good
fit_models.R
# Bad
fit models.R
fit.r
# 顺序
00_download.R
01_explore.R
...
09_model.R
10_visualize.R
1.2 文件组织
不同的项目难以有一个普适的文件组织规律。我认为最好的经验法则是,给每一个文件起一个简洁的名字,同时能让人想起文件的内容,那么你就对一个课题的脚本文件组织好了文件名。但要做到这一点是要花功夫的。
以下是来自standford_R_style 的内容:
如果每个人都使用通用顺序,我们将能够更快、更容易地阅读和理解彼此的脚本。
General Layout and Ordering
- Copyright statement comment
- Author comment
- File description comment, including purpose of program, inputs, and outputs
source()
andlibrary()
statements- Function definitions
- Executed statements, if applicable (e.g.,
print
,plot
). Unit tests should go in a separate file namedoriginalfilename_unittest.R
.
1.3 文件内部结构
- 使用注释行对代码进行分块。RStudio中,插入代码块快捷键为ctrl + shift + r。
- 如果代码中调用了其他程序包,在文件的开始一次性全部调用,这比随处library 程序包更简单明了。
- 调用程序包时,避免使用attach(),因为attach()常常会引起不知名的错误。
# Load data ---------------------------------------------
# Plot data ---------------------------------------------
2. 语法
2.1 对象命名
“计算机科学中,最难的两件事就是,缓存失效和对象命名。” —— Phil Karlton
- 变量和函数命名,只包含小写字母、数字和下划线。下划线是用于分隔命名中的多个单词。
- 变量名,一般是名词。
- 函数名,一般是动词。
- 避免使用保留字符来命名自定义的变量或函数。
# Good
day_one
# Bad
first_day_of_the_month
T <- FALSE
c <- 10
2.2 空格
2.2.1 逗号前后
- 和正常英文书写一样,在逗号后面保持有空格,逗号前面不要有空格。
# Good
x[, 1]
# Bad
x[,1]
x[ ,1]
2.2.2 圆括号前后
- 调用函数的时候,圆括号前后都不要有空格
# Good
mean(x, na.rm = TRUE)
# Bad
mean (x, na.rm = TRUE)
mean( x, na.rm = TRUE)
- 当使用if, for, while 的时候,在圆括号前后都加一个空格
# Good
if (debug) {
show(x)
}
# Bad
if(debug){
show(x)
}
- 函数参数的括号,在括号后面加一个空格
# Good
function(x) {}
# Bad
function (x) {}
function(x){}
2.2.3 双重花括号{{ }} 里侧两边
- 双重花括号,里侧两边,始终加一个空格,来强调双重花括号的特殊性。
# Good
max_by <- function(data, var, by) {
data %>%
group_by({{ by }}) %>%
summarise(maximum = max({{ var }}), na.rm = TRUE)
}
# Bad
max_by <- function(data, var, by) {
data %>%
group_by({{by}}) %>%
summarise(maximum = max({{var}}), na.rm = TRUE)
}
2.2.4 二元操作符前后
- 绝大多数二元操作符(==, +, -, <-, etc.)的前后均有空格。
# Good
height <- (feet * 12) + inches
# Bad
height <- (feet*12)+inches
- 以下操作符的前后均不能有空格,单冒号:, 双冒号::, 三冒号:::, 美元符$, at符@, 单方括号[, 双方括号[[, 幂符号^, 正号+, 负号-。
# Good
sqrt(x^2 + y^2)
df$z
x <- 1:10
# Bad
sqrt(x ^ 2 + y ^ 2)
df $ z
x <- 1 : 10
- Single-sided formulas when the right-hand side is a single identifier
- Note that single-sided formulas with a complex right-hand side do need a space
- When used in tidy evaluation !! (bang-bang) and !!! (bang-bang-bang) (because have precedence equivalent to unary -/+)
- help操作符 ?前后均没有空格。
# Good
package?stats
?mean
# Bad
package ? stats
? mean
2.3.5 额外的空格
- 如果增加空格可以提高赋值 = 或者 <- 对齐效果,可以使用额外的空格
- 其他地方,不要随意添加空格
# Good
list(
total = a +b +c,
mean = total / n
)
# Also fine
list(
total = a +b +c,
mean = total / n
)
2.3 函数调用
2.3.1 命名参数
函数参数,一般有两类,一类是提供用于计算的数据,一类是控制如何计算的细节参数。
- 当你调用函数时,如果要覆盖参数默认值,就要写明参数名称
# Good
mean(1:10, na.rm = TRUE)
# Bad
mean(1:10, , FALSE)
mean(, TRUE, x = c(1:10, NA))
2.3.1 赋值
- 避免在函数调用时赋值。
# Good
x <- complicated_function()
if (nzchar(x) < 1) {
# do something
}
# Bad
if (nzchar(x <- complicated_function()) < 1) {
# do something
}
- 唯一的例外,就是该函数是为了捕捉函数副作用。
output <- capture.output(x <- f())
2.4 控制流
2.4.1 花括号的使用规范
- 左花括号 {,应该跟在if语句、或函数声明语句等的后面,是一行的最后一个字符
- 中间的内容,以两个空格缩进
- 右花括号 },是一行的第一个字符。
# Good
if (y <0 && debug) {
message("y is negative")
}
if (y ==0) {
if (x >0) {
log(x)
} else {
message("x is negative or zero")
}
} else {
y ^x
}
test_that("call returns an ordered factor", {
expect_s3_class(call1(x, y), c("factor", "ordered"))
})
tryCatch(
{
x <- scan()
cat("Total: ", sum(x), "\n", sep = "")
},
interrupt = function(e) {
message("Aborted by user")
}
)
# Bad
if (y <0 && debug) {
message("y is negative")
}
if (y ==0)
{
if (x >0) {
log(x)
} else {
message("x is negative or zero")
}
} else {
y ^x}
2.4.2 行内语句
- 简单的一行以内的语句,可以省略花括号,只要不引起歧义。
# Good
y <- 10
x <- if (y < 20) "Too low" else "Too high"
- 对于调用函数的行,比如(return(), stop(), continue),不要省略花括号
# Good
if (y <0) {
stop("Y is negative")
}
find_abs <- function(x) {
if (x > 0) {
return(x)
}
x * -1
}
# Bad
if (y <0) stop("Y is negative")
find_abs <- function(x) {
if (x > 0) return(x)
x * -1
}
2.4.3 不明确的类型强制转换
- 避免在 if 语句中使用不明确的类型转换。比如数字到逻辑值的转换。
# Good
if (length(x) > 0) {
# do something
}
# Bad
if (length(x)) {
# do something
}
2.4.4 switch语句
- 避免使用基于位置的switch()语句,(即要使用名称)。
- 每个元素,另起一行
- Elements that fall through to the following element should have a space after =.
- 提供错误跳转,除非您以前验证过输入。
# Good
switch(x
a = ,
b = 1,
c = 2,
stop("Unknown `x`", call. = FALSE)
)
# Bad
switch(x, a = , b = 1, c = 2)
switch(y, 1, 2, 3)
2.5 过长的代码行
- 一行不要超过80个字符,方便以合适字体大小打印。
# Good
do_something_very_complicated(
something = "that",
requires = many,
arguments = "some of which may be long"
)
# Bad
do_something_very_complicated("that", requires, many, arguments,
"some of which may be long"
)
- 使用paste()或者stop()语句来生成字符串时,可以将生成一行字符的代码放在一行,便于阅读。
# Good
paste0(
"Requirement: ", requires, "\n",
"Result: ", result, "\n"
)
# Bad
paste0(
"Requirement: ", requires,
"\n", "Result: ",
result, "\n"
)
2.6 分号
- 不要在一行结尾使用分号
- 不要通过使用分号来将多行语句放在一行
- 总结就是,不要使用分号。
2.7 赋值
- 使用 <- 来赋值,不要使用 = 。
2.8 数据
2.8.1 字符串
- 使用双引号,而不是单引号。除非是字符串中出现嵌套引号时。
# Good
"Text"
'Text with "quotes"'
'<a href="http://style.tidyverse.org">A link</a>'
# Bad
'Text'
'Text with "double" and \'single\' quotes'
2.8.2 逻辑值
- 使用TRUE 和FALSE,而不使用T和F。
2.9 代码注释
-
注释行,以一个# 开头,# 后有一个空格。
-
短注释,可以放在代码后面,前面有两个空格#,然后是一个空格 (standford)。
-
数据分析的代码中,注释内容应该是记录重要发现,和分析决定。而不是解释代码在做什么,如果要添加注释来解释代码在做什么,考虑重写更简单的代码。
-
如果发现注释的内容,多余代码的内容,换R Markdown 或者jupyter notebook。
3. 函数
3.1 函数命名
- 动词,下划线分割单词
# Good
add_row()
permute()
# Bad
row_adder()
permutation()
3.2 过长代码行
- 当函数定义过长时,要跨越多行时,在函数定义开始的下一行开始缩进。
# Good
long_function_name <- function(a = "a long argument",
b = "another argument",
c = "another long argument") {
# As usual code is indented by two spaces.
}
3.3 return()语句
- 只在函数体的中间部分需要返回值时,才使用return()语句
- 其余情况下,则使用R的默认规则,返回最后一个表达值。
- return() 语句应该始终自己另起一行。因为return()语句对控制流有显著影响。
# Good
find_abs <- function(x) {
if (x > 0) {
return(x)
}
x * -1
}
add_two <- function(x, y) {
x + y
}
# Bad
add_two <- function(x, y) {
return(x + y)
}
- 如果你的函数,被调用的主要目的是利用其附加功能,比如打印,绘图或存储到硬盘。则该函数应该不可见地返回第一个参数。这样就可以使得这个函数可以应用在管道中。举例httr 中的一个打印示例
# Good
print.url <- function(x, ...) {
cat("Url: ", build_url(x), "\n", sep = "")
invisible(x)
}
3.4 函数注释
- 注释,只用来解释为什么,而不是解释做什么或者如何做。
- 注释,应该是一行的形式,且不使用句号。除非一行注释中有不止一句时,才使用句号。
3.5 函数说明(standford)
函数应该在函数定义行下面包含注释部分。注释应包括
- 对该函数功能的一句话描述,
- 函数参数的列表,用Args表示,
- 每个参数的描述(包括数据类型)
- 和返回值的描述,用Returns:表示。
- 注释应该具有足够的描述性,以便调用者可以在不读取函数代码的情况下使用该函数。
# 示例函数
CalculateSampleCovariance <- function(x, y, verbose = TRUE) {
# Computes the sample covariance between two vectors.
#
# Args:
# x: One of two vectors whose sample covariance is to be calculated.
# y: The other vector. x and y must have the same length, greater than one,
# with no missing values.
# verbose: If TRUE, prints sample covariance; if not, not. Default is TRUE.
#
# Returns:
# The sample covariance between x and y.
n <- length(x)
# Error handling
if (n <= 1 || n != length(y)) {
stop("Arguments x and y have invalid lengths: ",
length(x), " and ", length(y), ".")
}
if (TRUE %in% is.na(x) || TRUE %in% is.na(y)) {
stop(" Arguments x and y must not have missing values.")
}
covariance <- var(x, y)
if (verbose)
cat("Covariance = ", round(covariance, 4), ".\n", sep = "")
return(covariance)
}
3.6 函数语法(standford)
- 不要使用attach()语句
- 应该用stop()语句来处理报错
- 对象和方法:S语言有两个对象系统,S3和S4,它们都可以在R中使用。S3方法更具交互性和灵活性,而S4方法则更加正式和严格。
- 一般使用S3对象和方法,除非有充分的理由使用S4对象或方法。
- 使用S4 object 的一个主要情形是需要在c++代码中直接使用对象时。
- 使用S4 generic/method的主要情形是需要对两个参数进行分派时。
- 避免混合使用S3和S4:。因为S4方法会忽略S3继承,反之亦然。
4. 管道Pipes
4.1 管道简介
管道符 %>% 是用来强调一系列的操作,而不是强调操作要作用到的对象。
在以下的情况下,避免使用管道:
- 一次需要操作多于一个对象的时候。管道应只用于操作一个对象
- 操作过程中会产生多个有用的中间对象的时候。
4.2 空格
- %>% 前,始终有一个空格
- %>% 后,始终是另起一行
- 在第一步之后,每一行缩进两个空格
4.3 过长代码行
- 如果某一行操作中的函数参数,过长时,将每一个参数放在一个新行中,并使用缩进。
# Good
iris %>%
group_by(Species) %>%
summarise(
Sepal.Length = mean(Sepal.Length),
Sepal.Width = mean(Sepal.Width),
Species = n_distinct(Species)
)
4.4 短管道
- 只有一步操作的管道,可以写在一行里。不过依然建议按照常规方式,在 %>%后另起一行。
4.5 没有参数的管道节
- magittr 允许你在使用没有参数的函数时,省略() 。但是,不要这样,坚持使用()。
4.6 管道赋值
- 避免使用magittr 提供的 %<>% 符号来进行赋值
- 变量名和赋值新变量在不同行
- 或者,变量名和赋值新变量在同一行
- 或者在管道末尾使用 ->来赋值
iris_long <-
iris %>%
gather(measure, value, -Species) %>%
arrange(-value)
iris_long <- iris %>%
gather(measure, value, -Species) %>%
arrange(-value)
iris %>%
gather(measure, value, -Species) %>%
arrange(-value) ->
iris_long
- 个人推荐使用,第一种赋值方法,即变量名和赋值新变量在不同行。
5. ggplot2
5.1 ggplot2简介
对于ggplot中 + 号的使用规范,和%>% 在管道中的使用规范是十分相似的。
5.2 空格
- +前总有一个空格
- +后总是另起一行
- 每一行缩进两个空格
- 如果是在管道中,使用ggplot,+后面的语句缩进时,只需要缩进一个层级。就是说,在管道中,+就是看成一个%>%来缩进。
# Good
iris %>%
filter(Species == "setosa") %>%
ggplot(aes(x = Sepal.Width, y = Sepal.Length)) +
geom_point()
# Bad
iris %>%
filter(Species == "setosa") %>%
ggplot(aes(x = Sepal.Width, y = Sepal.Length)) +
geom_point()
5.3 过长代码行
- 如果ggplot中参数过多,超出一行,则将每一个参数放在一个新行中。