北京大学R语言教程(李东风)第14章: 工作空间和变量赋值

工作空间

R把在命令行定义的变量都保存到工作空间中,
在退出R时可以选择是否保存工作空间。
这也是R与其他如C、Java这样的语言的区别之一。

ls()命令可以查看工作空间中的内容。

随着多次在命令行使用R,
工作空间的变量越来越多,
使得重名的可能性越来越大,
而且工作空间中变量太多也让我们不容易查看其内容。
在命令行定义的变量称为“全局变量”,
在编程实践中,
全局变量是需要慎用的。

可以用rm()函数删除工作空间中的变量,格式如

rm(d, h, name, rec, sex, x)

要避免工作空间杂乱,
最好的办法还是所有的运算都写到自定义函数中。
自定义函数中定义的变量都是临时的,
不会保存到工作空间中。
这样,仅需要时才把变量值在命令行定义,
这样的变量一般是读入的数据或自定义的函数
(自定义函数也保存在工作空间中)。

可以定义如下的sandbox()函数:

sandbox <- function(){
  cat('沙盘:接连的空行回车可以退出。\n')
  browser()
}

运行sandbox()函数,将出现如下的browser命令行:

沙盘:接连的空行回车可以退出。
Called from: sandbox()
Browse[1]> 

提示符变成了“Browser[n]”,其中n代表层次序号。
在这样的browser命令行中随意定义变量,
定义的变量不会保存到工作空间中。
用“Q”命令可以退出这个沙盘环境,
接连回车也可以退出。

非法变量名

R的变量名要求由字母、数字、下划线、小数点组成,
开头不能是数字、下划线、小数点,
中间不能使用空格、减号、井号等特殊符号,
变量名不能与ifNA等保留字相同。

有时为了与其它软件系统兼容,
需要使用不符合规则的变量名,
这只要将变量名两边用反向单撇号“`”保护,
如:

`max score` <- 100
99 + `max score`
## [1] 199

如果变量名(元素名、列名等)是以字符串形式使用,
就不需要用“`”保护。如:

x <- c("score a"=85, "score b"=66)
x
## score a score b 
##      85      66

变量赋值与绑定

本小节内容技术上比较复杂,
初学者可以略过。

在R中赋值本质上是把一个存储的对象与一个变量名“绑定”(bind)在一起,
比如:

并不是像C++、JAVA等语言那样,
x代表某个存储位置,
x <- c(1,2,3)”代表将1到3这些值存储到x所指向的存储位置。
实际上,<-右边的c(1,2,3)是一个表达式,
其结果为一个R对象(object),
x只是一个变量名,
并没有固定的类型、固定的存储位置,
赋值的结果是将x绑定到值为(1,2,3)的R对象上。
R对象有值,但不必有对应的变量名;
变量名必须经过绑定才有对应的值和存储位置。

这样,同一个R对象也可以被两个或多个变量名绑定。
对于基本的数据类型如数值型向量,
两个指向相同对象的变量当一个变量被修改时自动制作副本。
tracemem(x)可以显示变量名x绑定的地址并在其被制作副本时显示地址变化。
如:

x <- c(1,2,3)
cat(tracemem(x), "\n")
## <0000025765221598>
y <- x    # 这时y和x绑定到同一R对象
cat(tracemem(y), "\n")
## <0000025765221598>
## tracemem[0x0000025765221598 -> 0x000002576536c4c8]: eval eval eval_with_user_handlers withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir in_input_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
## [1] 1 2 3
## [1] 1 2 0
untracemem(x); untracemem(y)
rm(x, y)

可见y <- x并没有制作副本,
但是修改y[3]值时就对y制作了副本。

修改某个变量名所指向的对象可能会制作副本,如:

x <- c(1,2,3)
cat(tracemem(x), "\n")
## <00000257659A3998>
## tracemem[0x00000257659a3998 -> 0x0000025765a2d198]: eval eval eval_with_user_handlers withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir in_input_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
## [1] 1 2 0

在调用函数时,
如果函数内部不修改自变量的元素值,
输入的自变量并不制作副本,
而是直接被函数使用实参绑定的对象。
如:

x <- c(1,2,3)
cat(tracemem(x), "\n")
## <0000025765F54FD8>
f <- function(v){ return(v) }
z <- f(x)
cat(tracemem(z), "\n")
## <0000025765F54FD8>
untracemem(x); untracemem(z)

从上面的例子可以看出,
函数fx为实参,
但不修改x的元素,
不会生成x的副本,
返回的值是x指向的对象本身,
再次赋值给z
也不制作副本,
zx绑定到同一对象。

如果函数内部修改自变量的元素值,
则输入的自变量也会制作副本。
如:

x <- c(1,2,3)
cat(tracemem(x), "\n")
## <0000025766551E58>
f2 <- function(v){ v[1] <- -999; return(v) }
z <- f2(x)
## tracemem[0x0000025766551e58 -> 0x0000025766606dd8]: f2 eval eval eval_with_user_handlers withVisible withCallingHandlers handle timing_fn evaluate_call <Anonymous> evaluate in_dir in_input_dir eng_r block_exec call_block process_group.block process_group withCallingHandlers process_file <Anonymous> <Anonymous> do.call eval eval eval eval eval.parent local
## <0000025766606DD8>
untracemem(x); untracemem(z)

从程序输出看,
函数f2()x为实参,
并修改x的内部元素,
就制作了x的副本,
返回的结果赋给变量z
绑定的是修改后的副本。

如果在函数中对自变量重新赋值,
这实际是重新绑定,
也不会制作输入的实参的副本。

如果修改y的元素值时还修改了其存储类型,
比如整型改为浮点型,
则会先制作y的副本,
然后制作类型改变后的副本,
然后再修改其中的元素值。

在当前的R语言中,
一个对象的引用(如绑定的变量名)个数,
只区分0个、1个或多个这三种情况。
在没有引用时,
R的垃圾收集器会定期自动清除这些对象。
rm(x)只是删除绑定,
并不会马上清除x绑定的对象。
如果已经有多个引用,
即使是只有2个,
减少一个引用也还是“多个”状态,
不会变成1个。

垃圾收集器是在R程序要求分配新的对象空间时自动运行的,
R函数gc()可以要求马上运行垃圾收集器,
并返回当前程序用到的存储量;
lobstr包的mem_used()函数则报告当前会话内存字节数。

在上面的示例中,
用了基本类型的向量讲解是否制作副本。
考虑其它类型的复制。

如果x是一个有5个元素的列表,
y <- x使得yx指向同一个列表对象。
但是,
列表对象的每个元素实际上也相当于一个绑定,
每个元素指向一个元素值对象。
所以如果修改yy[[3]] <- 0
这时列表y首先被制作了副本,
但是每个元素指向的元素值对象不变,
仍与x的各个元素指向的对象相同;
然后,
y[[3]]指向的元素值进行了重新绑定,
不再指向x[[3]]
而是指向新的保存了值0的对象,
y的其它元素指向的对象仍与x公用。
列表的这种复制方法称为浅拷贝,
列表对象及各个元素绑定被复制,
但各个元素指向(保存)的对象不变。
这种做法节省空间也节省运行时间。
在R的3.1.0之前则用的深拷贝方法,
即复制列表时连各个元素保存的值也制作副本。

如果x是一个数据框,
这类似于一个列表,
每个变量相当于一个列表元素,
数据框的每一列实际绑定到一个对象上。
如果y <- x
则修改y的某一列会对y进行浅拷贝,
然后仅该列被制作了副本并被修改,
其它未修改的列仍与x共用值对象。

但是如果修改数据框y的一行,
因为这涉及到所有列,
所以整个数据框的所有列都会制作副本。

对于字符型向量,
实际上R程序的所有字符型常量都会建立一个全局字符串池,
这样有许多重复值时可以节省空间。

用lobstr包的obj_size()函数可以求变量的存储大小,
obj_size(x)
也可以求若干个变量的总大小,
obj_size(x,y)
因为各种绑定到同一对象的可能性,
所以变量的存储大小可能会比想象要少,
比如,
共用若干列的两个数据框,
字符型向量,
等等。
基本R软件的object.size()则不去检查是否有共享对象,
所以对列表等变量的存储大小估计可能会偏高。

从R 3.5.0版开始,1:n这种对象仅保存其开始值和结束值。

在自定义函数时,
自变量通常是按引用使用的,
函数内部仅使用自变量的值而不对其进行修改时不会制作副本,
但是如果函数内部修改了自变量的值,
就会制作副本,
所以如果自变量的存储量很大而且被修改后返回,
调用这个函数时就会造成运行速度缓慢。
在函数内应慎重修改自变量的值。

在循环中修改数据框的列,
也会造成反复的复制。

环境

前面讲的工作空间就是一个环境,
称为“全局环境”。
环境可以认为是包含了变量绑定和函数定义的一个对象,
对环境的修改都不会制作副本。
一般我们只需要用到全局环境,
在自定义函数的运行过程中会有一个局部的运行环境,
但是在函数内定义的嵌套函数可以带有其定义处的环境,
这样可以实现有历史记忆的函数,
类似于其它面向对象程序设计语言如C++等,
对象可以同时有状态(变量成员)和适用操作(方法成员)。

环境的应用将在“函数进阶”部分讲到内嵌函数、函数工厂时详细讲解。

韭菜热线原创版权所有,发布者:风生水起,转载请注明出处:https://www.9crx.com/77959.html

(0)
打赏
风生水起的头像风生水起普通用户
上一篇 2023年11月2日 00:45
下一篇 2023年11月3日 01:03

相关推荐

  • 如何在风险意识股票投资组合中捕捉人工智能创新

    如何在风险意识股票投资组合中捕捉人工智能创新 技术颠覆既创造了机遇,也带来了波动。但我们有办法在管理风险的同时抓住人工智能创新。人工智能革命的核心公司被广泛视为昂贵、快速增长的企业,通常不会被纳入防御性投资组合。然而,我们认为人工智能生态系统中的精选公司可以融入具有风险意识的股票配置,这种配置着眼于优质的长期增长来源和深思熟虑的投资组合构建策略。 寻求降低股…

    2024年5月31日
    5200
  • 了解不同类型的保险:人寿保险、健康保险、残疾保险和长期护理保险

    在一个充满不确定性的不断变化的世界中,保险是一个重要的安全工具,为个人和家庭提供财务保护和安心。不同类型的保单可满足特定需求并为各种生活事件提供承保。本综合指南旨在更深入地探讨四种基本保险类型的复杂性:人寿保险、健康保险、残疾保险和长期护理保险。 ‍ 1. 人寿保险 ‍ 什么是人寿保险? ‍ 人寿保险是个人与保险提供者之间签订的合同,保证在被保险人死亡时向受…

    2023年11月29日
    12500
  • 当后见之明变成先见之明,复制投资业绩可能吗?

    介绍 过去几年,我们分别分析了数十种公开和私募市场投资策略,例如并购套利和私募股权,并出现了一个共同的主题。300 多篇研究论文中描述的大多数产品只是通过复杂的包装提供了对股票市场的接触。一旦退潮,各地的风险暴露都是一样的。 我们可以用不同的方式来证明这种现象。最常见的方法是简单地运行因子暴露分析。标榜提供不相关回报的投资产品通常对股票市场表现出较高的贝塔系…

    2023年7月27日
    19100
  • 常见的金融骗局以及如何避免它们

    任何人都可能随时陷入金融欺诈。无论您多么小心,犯罪分子都会夜以继日地寻找和创造新的方法来从诚实、勤奋的人们那里窃取金钱。 从数据泄露和在线诈骗到阴暗的诈骗和非法计划,欺诈者瞄准毫无戒心的受害者,甚至欺骗最警惕的怀疑论者。随着技术越来越成为我们联系、开展业务和管理财务的方式不可或缺的一部分,诈骗者也变得越来越狡猾。 从高科技黑客到冷酷无情的欺骗,犯罪分子每年都…

    2023年7月16日
    10100
  • 中国量化投资者在股市动荡中进行大盘押注

    作者:Justina Lee,24 年 3 月 3 日 一群押注中国大型企业的系统性投资者在今年引发股市动荡和监管机构打击的“量化地震”中基本上毫发无伤。 投资于大型股票的基于规则的基金经受住了股市风暴,因为蓝筹股公司因避险需求和国家基金的购买而表现出色。从不透明的量化交易世界的角度来看,彭博社编制的一个对中国大型股进行多头和空头押注的投资组合今年截至周四的…

    2024年3月21日
    7100

发表回复

登录后才能评论
客服
客服
关注订阅号
关注订阅号
分享本页
返回顶部