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

  1. Copyright statement comment
  2. Author comment
  3. File description comment, including purpose of program, inputs, and outputs
  4. source() and library() statements
  5. Function definitions
  6. Executed statements, if applicable (e.g., print, plot). Unit tests should go in a separate file named originalfilename_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中参数过多,超出一行,则将每一个参数放在一个新行中。
posted @ 2020-06-04 11:19  songbiao  阅读(523)  评论(0编辑  收藏  举报