R语言编程 第一讲 变量与赋值

  • R语言的变量名
  • 赋值符号 <- 与 = 的区别
  • 赋值符号 <- 的更多细节
    • Copy-on-Modify与Modify-in-Place
    • 函数调用
    • 列表
    • 数据框(Dataframe)
    • 字符
  • 存储空间

这个系列将系统性介绍R语言的理论与实践,R语言是专注应用统计与数据分析领域的最热门的开源语言,兼具函数编程与面向对象编程的特点。R语言的使用门槛非常低,如果只是用来估计特定模型,那么只需要输入输出会调包就可以了,但总要有人去写以及优化这些包,所以我们在使用R语言之前,有必要系统性学习一下R语言编程。

这个系列会先介绍一些R语言编程的基础,比如变量、向量、向量切片、分支与循环、函数、可视化等,然后再分别介绍R语言的函数编程与面向对象编程的内容,最后介绍元编程与其他编程技巧。希望接下来的这个学年能够完成这个系列!

R语言的变量名

  1. 变量名可以包含字母、数字、下划线_ 和点 .
  2. 变量名只能以字母或点开头,字母区分大小写
  3. 变量不能用关键字,比如if、for等
  4. 给任意符号串加上``可以无视一切规则

例1 奇怪但正确的变量名

> .c = 3
> `_fff` = 5
> `for` = 3
> for = 3
Error: unexpected '=' in "for ="
> _fff = 5
Error: unexpected input in "_"

赋值符号 <- 与 = 的区别

R语言中 <- 与 = 都是非常常用的赋值符号,但它们是有很大的区别的:

  1. x <- Obj 表示创造一个对象Obj,然后让变量x指向Obj;
  2. y = Obj 表示创造一个对象Obj,并把它赋值给变量y;

例2 <- 与 = 的区别

library(lobstr) # 使用lobstr包可以分析R语言运行的细节x <- 1
y <- x
obj_addr(x)
obj_addr(y)a = 1
b = 1
obj_addr(a)
obj_addr(b)

第一段代码创造了一个取值为1的对象,然后让变量x指向这个对象;并且让变量y指向变量x指向的地址,于是变量y也指向了这个对象,函数obj_addr()可以返回一个对象的地址,输出为

obj_addr(x)
[1] “0x2e164f19ae0”
obj_addr(y)
[1] “0x2e164f19ae0”

这说明变量x与变量y指向同一个地址,也就是取值为1的这个对象的地址。

第二段代码将1赋值给变量a,然后将1赋值给变量b,这两个1是分别创造的两个不同的对象,因此函数obj_addr()的输出为

obj_addr(a)
[1] “0x2e1663493b8”
obj_addr(b)
[1] “0x2e166349348”

这说明变量a与变量b并没有指向同一个地址。

大家可以自行尝试,把第一段代码中的y <- x改为y = x,效果和第二段代码就是一样的。

赋值符号 <- 的更多细节

Copy-on-Modify与Modify-in-Place

  1. Copy-on-Modify:多个变量指向同一个对象时,对其中一个变量进行修改时,R语言会在另一个内存地址上复制这个对象,然后在新对象上进行修改,最后让这个变量指向新内存地址;
  2. Modify-in-Place:只有一个变量指向一个对象时,对这个变量进行修改会直接在这个对象上进行修改;

例3 Copy-on-Modify vs Modify-in-Place

x <- c(1,2,3,4)
y <- x
y[[2]] <- 1
x
obj_addr(x)
y
obj_addr(y)

我们先来分析这段代码,首先用c()创造了一个向量对象,并让变量x指向这个对象,然后让变量y也指向这个对象;接下来把变量y指向的对象中的第二个位置的值改为1,神奇的事情就发生了!我们来看一下输出

> x
[1] 1 2 3 4
> obj_addr(x)
[1] "0x2e16fcc0558"

这两行的输出说明x指向的对象取值没有变;

> y
[1] 1 1 3 4
> obj_addr(y)
[1] "0x2e16fcc0648"

这两行的输出说明y指向了一个新的对象,并且新的对象取值变了。这个现象的学名叫做Copy-on-Modify,它其实是由R语言的一种保护机制所导致的,因为变量x和y指向同一个对象,为了避免对变量y做的修改引起变量x的变化,R语言实质上复制了一个取值相同的对象,修改这个对象的取值,然后让变量y指向这个新的对象,原来的对象会被Garbage Collector收集起来删除释放内存。这种保护机制的好处在于它可以避免同时修改指向同一个对象的变量,缺点在于增加了运算需要的时间(复制+修改+重新指向)。大家可以自行尝试,把 y[[2]] <- 1 改为 y[[2]] = 1 也会出现Copy-on-Modify的现象。

x <- c(1,2,3,4)
cat(tracemem(x), "\n")
y <- x
y[[2]] <- 1
untracemem(x)

这一段代码可以帮助我们更清楚地理解地址的变化,tracemem()的作用是追踪内存地址的变换,untracemem()的作用是停止追踪。cat()的作用就是输出一个对象,"\n"表示换行输出。我们先分析第一个输出:

> x <- c(1,2,3,4)
> cat(tracemem(x), "\n")
<000002E16F72B890>

这个是第一条语句创造的向量对象的内存地址;下面看第二条输出,

> y <- x
y[[2]] <- 1
tracemem[0x000002e16f72b890 -> 0x000002e16f728310]:

这个输出表示在执行y[[2]] <- 1,R语言在0x000002e16f728310这个位置复制了一个位于0x000002e16f72b890的对象,然后把位于0x000002e16f728310的对象第二个元素改为1,最后让y指向0x000002e16f728310。

x <- c(1,2,3,4)
y <- x
obj_addr(y)
y[[2]] <- 1
obj_addr(y)
y[[3]] <- 1
obj_addr(y)

下面分析这一段代码,如果在修改了一次之后再修改一次变量y的取值,会发现第二次修改后变量y的例子就不会再发生改变了:

> x <- c(1,2,3,4)
> y <- x
> obj_addr(y)
[1] "0x2e16f724ca0"
> y[[2]] <- 1
> obj_addr(y)
[1] "0x2e16f724e30"
> y[[3]] <- 1
> obj_addr(y)
[1] "0x2e16f724e30"

原因很简单,现在指向对象0x2e16f724e30的变量只有y一个,因此对变量y的取值的修改是直接在内存位置0x2e16f724e30进行的,这种现象叫做Modify-in-Place。

Copy-on-Modify比Modify-in-Place需要更多的时间,在优化代码的时候可以尝试规避用不同的变量指向同一个对象。

函数调用

当函数调用某一个变量的时候,传递到函数中的是指针而不是值。

例4 R语言函数调用时进行指针传递

f <- function(a){a
}
x <- c(1,2,3,4)
cat(tracemem(x), "\n")
z1 <- f(x)
z2 = f(x)
untracemem(x)y = c(1,2,3,4)
cat(tracemem(y), "\n")
w <- f(y)
untracemem(y)

执行第一段代码,tracemem()没有监测到内存地址的变化,因此函数f引用的是变量x的指针,而不是赋值的变量x的值,并且这个性质与把函数值赋值给另一个变量时用的方式无关;执行第二段代码会发现同样的结论,说明这个性质与被引用对象的赋值方式无关。

列表

R语言的列表比上面的数值和向量更为复杂,比如

L1 <- list(1,2,3,4)

这个语句实际上执行了三步操作:

  1. 创建1、2、3、4这四个对象;
  2. 创建一个列表对象,每个位置对应填入1、2、3、4这四个对象的地址;
  3. 让变量L1指向这个列表对象

也就是说列表中装的是地址而不是值!因此变量L1指向一个地址,它的四个元素分别指向四个不同的地址:

> obj_addr(L1)
[1] "0x143ec3855a0"
> obj_addr(L1[[1]])
[1] "0x143ed747f08"
> obj_addr(L1[[2]])
[1] "0x143ed747ed0"
> obj_addr(L1[[3]])
[1] "0x143ed747e98"
> obj_addr(L1[[4]])
[1] "0x143ed747e60"

修改列表中某个元素的值时实质上是创建一个新对象,然后更改列表对应位置的地址。实际上R语言中用c()定义的向量也具有类似的性质,不同元素是存在不同的地址的:

> x <- c(1,2,3,4)
> obj_addr(x)
[1] "0x143ec843630"
> obj_addr(x[[1]])
[1] "0x143ed8be8d0"
> obj_addr(x[[2]])
[1] "0x143ed8be6a0"
> obj_addr(x[[3]])
[1] "0x143ed8be470"
> obj_addr(x[[4]])
[1] "0x143ed8be240"

数据框(Dataframe)

R语言中的数据框结构比列表更复杂,但我们可以简单理解为它是列表的列表,并且每一个位置存的都是对应元素的地址。

> D1 <- data.frame(x=c(1,2,3,4),y=c(1,2,3,4))
> obj_addr(D1)
[1] "0x143ec2e3950"
> obj_addr(D1$x)
[1] "0x143ecc4e148"
> obj_addr(D1$y)
[1] "0x143ecc4de28"
> obj_addr(D1$x[[1]])
[1] "0x143ed911550"
> obj_addr(D1$y[[1]])
[1] "0x143ed9112b0"

创建数据框对象时,执行了下面的操作:

  1. 创建了两组1、2、3、4对象;
  2. 创建两个列表,对应位置分别指向一个1、2、3、4对象,然后分别让x,y指向这两个列表;
  3. 创建一个列表,列表的两个元素分别存x、y指向的地址,并让D1指向这个列表;

字符

R语言有一个global string pool,所有的英文字符和简单字符串保存在其中,因此字符型的变量或列表的指针是指向global string pool中某一个字符的。

> x <- c("a", "a", "abc", "d")
> ref(x, character = TRUE)
o [1:0x143ed294f28] <chr>
+-[2:0x143e34cd7a0] <string: "a">
+-[2:0x143e34cd7a0]
+-[3:0x143e62f5830] <string: "abc">
\-[4:0x143e369d0a0] <string: "d">

ref()函数的作用是返回字符型变量引用的global string pool中的字符的地址,从上面的结果来看,相同的字符"a"在global string pool中其实是同一个对象,不同的字符在global string pool是单独存放的。

存储空间

使用obj-size()可以查看一个对象的大小。得益于R语言赋值采用指针的形式,重复引用一个对象多次并不会显著增加内存使用:

> x <- runif(1e6)
> obj_size(x)
8,000,048 B
> y <- list(x, x, x)
> obj_size(y)
8,000,128 B
> obj_size(list(NULL, NULL, NULL))
80 B

在创建y时,实质上只是把指向x的指针重复了三次,因此增加的内存消耗就是一个空列表的内存。并且内存计算有一个神奇的规律:

  1. 如果x包含于y,则obj_size(x, y)=obj_size(y);
  2. 如果x与y不相交,则obj_size(x, y)=obj_size(x)+obj_size(y);
> obj_size(x,y)
8,000,128 B

R语言存储还有一个很有趣的地方,当存一个步长为1的数列时,只用存第一项和最后一项:

> obj_size(1:3)
680 B
> obj_size(1:300)
680 B
> obj_size(1:30000)
680 B
> obj_size(1:3000000)
680 B

R语言编程 第一讲 变量与赋值相关推荐

  1. R语言编程艺术(3)R语言编程基础

    本文对应<R语言编程艺术> 第7章:R语言编程结构: 第9章:面向对象的编程: 第13章:调试 ============================================== ...

  2. r intersect()_30分钟掌握R语言编程基础

    R语言是世界上最广泛使用的统计编程语言.本文的目的是使得读者快速上手R语言编程. 1 R语言概述 R语言是世界上最广泛使用的统计编程语言.有人认为,它是数据科学家的第一选择(人生苦短,我选Python ...

  3. r语言remarkdown展示图_十个超级好用的R语言编程技巧,一般人绝不知道!

    全文共3997字,预计学习时长8分钟 图片来源:pexels.com/@pixabay 由于R语言生态系统内容繁复并在不断发展,人们往往容易忽视一些切实有用的知识.这些技巧往往非常简单,但对于完成工作 ...

  4. rmd中无法打开链结r_十个超级好用的R语言编程技巧,一般人绝不知道!

    全文共3997字,预计学习时长8分钟 图片来源:pexels.com/@pixabay 由于R语言生态系统内容繁复并在不断发展,人们往往容易忽视一些切实有用的知识.这些技巧往往非常简单,但对于完成工作 ...

  5. R语言编程入门--replicate()函数比较有意思!

    I. 导论 简单来讲,编程是借助计算机来解决某个问题.学习编程的就是训练我们解决问题的能力.有这样一种说法:在未来,不会编程的人即是文盲. 1 为什么要学习R编程 大部分情况下解决某些问题还需要依赖一 ...

  6. r语言 siar 代码_平滑转换自回归(STAR)模型的R语言编程实现详解

    总体讲,ST(Smooth transition)模型,这块的code混乱的比较狗血,文献中的做法也是千奇百怪, 单变量的有 :STAR, LSTAR, ESTAR, 多变量的有:STVAR, LST ...

  7. 数据科学家们,请补齐你的短板,如何提升R语言编程能力

    前言 这个世界每天都在源源不断地生产数据,而人们尤其是商界往往希望从这些数据中获取到有价值的信息.而这一点也促使很多试图从数据中提取有用信息的数据科学家们(或被叫做数据分析师.数据挖掘者等等听起来不错 ...

  8. r语言 悲观剪枝_《R语言编程—基于tidyverse》新书信息汇总

    我之前预告过的 R 语言新书,起名为<R语言编程-基于tidyverse>,本书的目的是为了在国内推广 R 语言和 R 语言最新技术,电子版将始终跟踪最新并免费分享.本书非常适合新手 R ...

  9. R语言编程艺术(4)R对数据、文件、字符串以及图形的处理

    本文对应<R语言编程艺术> 第8章:数学运算与模拟: 第10章:输入与输出: 第11章:字符串操作: 第12章:绘图 =================================== ...

最新文章

  1. 不自动切换eclipse视图
  2. 关于使用eclipse maven UpdateProject时报错,无法更新本地仓库的问题解决方案
  3. UA MATH564 概率论 公式与定理总结
  4. QT 默认环境路径配置方法
  5. clientHeight、offsetHeight 和 scrollHeight
  6. 前端学习(686):for循环
  7. mysql5.6.22编译安装教程_Linux CentOS6.0下编译安装MySQL 5.6.22
  8. 的write方法有哪些参数_Python笔记13:文件操作三件套:read,write,seek
  9. Binary String Constructing(CodeForces - 1003B)
  10. Linux字体库ttc还是ttf,几种操作系统字体格式:otf/ttf/ttc格式字体的区别
  11. Dmp文件导入(Imp命令)
  12. Android混淆篇 ijkplayer混淆
  13. AUC(Area under Curve Roc曲线下面积)计算方法总结
  14. python中转义符的用法_19.Python转义字符及用法
  15. 四个技巧教你解决电磁干扰问题
  16. TI AWR1642学习笔记7 TSW1400采集原始数据
  17. 图灵专访:郭霖的成长之路
  18. SQL1 从 Customers 表中检索所有的 ID
  19. Kafka安装(windows)
  20. qt获取文件 图片大小

热门文章

  1. 64位Linux下JVM内存调设遇到GC问题的备忘
  2. Java实现Redis分布锁
  3. eclipse集成tomcat运行web时提示引入jar包的类找不到的解决办法
  4. linux下eclipse cdt主函数main参数值传递设置
  5. Numpy数组常用函数汇总(数学运算、三角函数、位运算、比较运算及其它)
  6. atomic底层实现是基于无锁算法cas
  7. esp8266 wifi信号强度设置
  8. poj 3579 Median 中间值(二分搜索)
  9. 第八周项目实践2 建立连串算法库
  10. sdut-2732 小鑫の日常系列故事(一)——判断对错