赋值表达式

赋值可能是所有编程语言中最基本的表达式了,它所做的就是将一个值(value)赋予
或者绑定到一个符号上,使得我们能够通过符号来访问这个值。
尽管编程语言之间有相似性,但 R 采用 <-符号来表示赋值。这和其他语言用 = 有点不
同,虽然在 R 中也可以用 = 进行赋值:
x <- 1
y <- c(1, 2, 3)
z <- list(x, y)
我们不需要在赋值前声明符号及其类型。如果环境中没有某个符号,赋值的同时就会
创建这个符号;反之,如果符号已经存在,也不会造成冲突,只是将值重新绑定到这个
符号上。
我们还可以使用一些其他可用且等效的运算符。x<-f(z)把 f(z) 绑定到符号 x 上,
相比之下,我们还可以用 -> 进行反向赋值:
2 -> x1
我们甚至可以将多个赋值运算符连接使用,使一组符号都取相同的值:
x3 <- x2 <- x1 <- 0
表达式 0 只被计算一次,就将相同的值同时赋予 3 个符号。为了验证它是如何运行的,
我们可以将 0 换成一个随机数生成器(random number generator):
x3 <- x2 <- x1 <- rnorm(1)
c(x1, x2, x3)
## [1] 1.585697 1.585697 1.585697
rnorm(1) 生成一个服从标准正态分布的随机数。如果上述赋值每次都重新调用该随
机数生成器,则每个符号会有不同的取值。然而,并没有发生这种情况。稍后,我们会解
释实际运行过程,这样你可以更好地理解。
像其他编程语言一样,= 也可以进行赋值:
x2 = c(1, 2, 3)
如果你熟悉其他流行编程语言,如 Python、Java 和 C#,可能就会发现,将 = 作为赋值
运算符几乎成为一个行业规范了,而 -> 这个需要两次键入操作的赋值符可能会让你感到不
方便。尽管二者都可以使用,且作为赋值运算符其作用是相同的,但是,谷歌的 R Style Guide
(https://google.github.io/styleguide/Rguide.xml#assignment)建议使用 <-而不是 =。
这里,对 <-和 = 的细微差别给出一个简单的说明。我们先创建一个带两个参数
的 f( ) 函数:
f <- function(input, data = NULL) {
cat("input:\n")
print(input)
cat("data:\n")
print(data)
}
这个函数就是输出两个参数的值。现在,我们用这个函数演示两个操作符的不同
之处:
x <- c(1, 2, 3)
y <- c("some", "text")
f(input = x)
## input:
## [1] 1 2 3
## data:
## NULL
上述代码同时使用了 <-和 =,但它们却扮演着不同的角色。前两行的 <-用作赋值运算
符,第 3 行的 = 为函数 f( ) 的参数 input 指定输入值。
具体来说,<-运算符计算它右侧的表达式 c(1,2,3),并将其值赋予左侧符号(变量)
x。而 = 并不用作赋值运算符,只是通过名称将函数参数与变量匹配起来。
我们知道 <-和 = 用作赋值运算符时,可以相互替代。因此,上述代码也可以等价地
写为:
x = c(1, 2, 3)
y = c("some", "text")
f(input = x)
## input:
## [1] 1 2 3
## data:
## NULL
这里我们只用了 = 运算符,但它有两种不同的用途:在前两行,= 执行赋值操作;而
在第 3 行,= 指定一个命名参数(将名为 input 的参数指定为符号 x)。
现在,让我们看看把每个 = 换成 <-将会发生什么:
x <- c(1, 2, 3)
y <- c("some", "text")
f(input <- x)
## input:
## [1] 1 2 3
## data:
## NULL
运行这段代码,你会发现输出结果是相似的。然而,如果你检查 R 的环境,就会看到
不同之处,环境中创建了一个新变量 input,取值为 c(1, 2, 3):
input
## [1] 1 2 3
那么,究竟发生了什么呢?实际上,第3 行代码做了两件事:第一,赋值操作 input <-x 将
一个新符号 input 引入到环境中,并取值为 x;第二,将 input 的值传递给函数 f( ) 的
第 1 个参数。换句话说,函数的第 1 个参数是通过位置而非名称建立的匹配关系。
为了更详细地进行描述,我们将进行更多试验。函数的标准用法如下:
f(input = x, data = y)
## input:
## [1] 1 2 3
## data:
## [1] "some" "text"
如果我们将上述两个 = 用 <-替代,结果看起来是一样的:
f(input <- x, data <- y)
## input:
## [1] 1 2 3
## data:
## [1] "some" "text"
使用 = 的那段代码,也可以交换函数参数的位置,而不改变结果:
f(data = y, input = x)
## input:
## [1] 1 2 3
## data:
## [1] "some" "text"
然而在这个例子中,如果我们将 = 换成 <-,那么 input 和 data 的值也交换了:
f(data <- y, input <- x)
## input:
## [1] "some" "text"
## data:
## [1] 1 2 3
以下代码与上述代码有相同的效果:
data <- y
input <- x
f(y, x)
## input:
## [1] "some" "text"
## data:
## [1] 1 2 3
这段代码相当于不仅调用了 f(y,x),还在当前环境中额外创建了两个不必要的变
量 data 和 input。
上述例子和试验给出了很清晰的演示。因此,为了减少歧义,可以用 <-或者 = 作为赋值
运算符,而仅用 = 为函数指定参数。为了提高 R 代码的可读性,正如 Google Style Guide 所建
议的,仅用 <-赋值,用 = 指定函数参数。


 使用带反引号的非标准名称


赋值运算符允许我们对一个变量(一个符号或名称)进行赋值。但是,直接赋值对符
号名称的格式有限制。名称只能包含从 a~z,A~Z 的字母(R 对大小写敏感)、下划线( _ )
和点( . ),不能有空格,也不能以下划线( _ )开头。
以下是一些有效的名称:
students <- data.frame()
us_population <- data.frame()
sales.2015 <- data.frame()
以下是一些违反命名规则的无效名称:
some data <- data.frame()
## Error: unexpected symbol in "some data"
_data <- data.frame()
## Error: unexpected input in "_"
Population(Millions) <- data.frame()
## Error in Population(Millions) <- data.frame() :
## object 'Millions' not found
上述名称以不同的方式违反了命名规则。some data 变量名中包含了空格,_data 以
下划线开头,Population(Millions) 不是一个符号名称而是一个函数调用。实践中,
很有可能一些无效的名称确实是一张数据表的列名,如第 3 个名称。
为了绕开对名称格式的限制,我们需要使用反引号来引用那些无效的名称,使其
有效:
`some data` <- c(1, 2, 3)
`_data` <- c(4, 5, 6)
`Population(Millions)` <- c(city1 = 50, city2 = 60)
要访问这些变量,也需要使用反引号,否则它们仍然会被认为是无效的:
`some data`
## [1] 1 2 3
`_data`
## [1] 4 5 6
`Population(Millions)`
## city1 city2
## 50 60
反引号可以用在任何创建符号的地方,包括函数:
`Tom's secret function` <- function(a, d) {
(a ^ 2 -d ^ 2) / (a ^ 2 + d ^ 2)
}
甚至适用于列表:
l1 <- list(`Group(A)` = rnorm(10), `Group(B)` = rnorm(10))
如果一个符号的名称无法被直接有效引用,我们也需要使用反引号来引用这个符号:
`Tom's secret function`(1,2)
## [1] -0.6
l1$`Group(A)`
## [1] -0.8255922 -1.1508127 -0.7093875 0.5977409 -0.5503219 -1.0826915
## [7] 2.8866138 0.6323885 -1.5265957 0.9926590
data.frame( ) 是一个例外:
results <- data.frame(`Group(A)` = rnorm(10), `Group(B)` = rnorm(10))
results
## Group.A. Group.B.
## 1 -1.14318956 1.66262403
## 2 -0.54348588 0.08932864
## 3 0.95958053 -0.45835235
## 4 0.05661183 -1.01670316
## 5 -0.03076004 0.11008584
## 6 -0.05672594 -2.16722176
## 7 -1.31293264 1.69768806
## 8 -0.98761119 -0.71073080
## 9 2.04856454 -1.41284611
## 10 0.09207977 -1.16899586
遗憾的是,尽管我们对含有其他符号的名称使用了反引号,生成的 data.frame 的变
量将那些符号用点代替,等价于使用函数 make.names( ),具体结果可以通过查
看 data.frame 的列名进行确认:
colnames(results)
## [1] "Group.A." "Group.B."
这常常发生在你导入一个数据表时,例如由某一实验得到的 CSV 数据:
ID,Category,Population(before),Population(after)
0,A,10,12
1,A,12,13
2,A,13,16
3,B,11,12
4,C,13,12
在 R 中,当你使用 read.csv( ) 读取 CSV 数据后,Population(before)
和 Population(after) 变量不会保持原来的名称,而是使用函数 make.names( ) 把
它们变成有效名称。我们运行以下命令,看看到底得到什么样的名称:
make.names(c("Population(before)", "Population(after)"))
## [1] "Population.before." "Population.after."
有时,我们并不希望程序自动执行这个操作。要使这个功能失效,可以在调
用 read.csv( )或 data.frame( ) 时,设置参数 check.names = FALSE:
results <- data.frame(
ID = c(0, 1, 2, 3, 4),
Category = c("A", "A", "A", "B", "C"),
`Population(before)` = c(10, 12, 13, 11, 13),
`Population(after)` = c(12, 13, 16, 12, 12),
stringsAsFactors = FALSE,
check.names = FALSE)
results
## ID Category Population(before) Population(after)
## 1 0 A 10 12
## 2 1 A 12 13
## 3 2 A 13 16
## 4 3 B 11 12
## 5 4 C 13 12
colnames(results)
## [1] "ID" "Category" "Population(before)"
## [4] "Population(after)"
在上述调用中,stringsAsFactors = FALSE 避免将字符向量转换为因子,
check.names=FALSE 避免对列名调用函数 make.names( )。设置了这两个参数,创建
的 data.frame 的变量将会最大限度地保留输入数据的格式。
正如上文提及的,要访问含特殊符号的列,需使用反引号引用列名:
results$`Population(before)`
## [1] 10 12 13 11 13
反引号使创建和访问带有特殊符号的变量成为可能,但这并不意味着我们建议使用这
样的命名方式。相反,它会降低代码可读性,也更易出错,并且难以使用针对严格命名规
则的外部工具。
总之,除非绝对必要,我们应避免使用反引号创建特殊变量名。

posted @ 2019-01-22 10:45  NAVYSUMMER  阅读(764)  评论(0编辑  收藏  举报
交流群 编程书籍