numeric 与 integer:R 的两种数字类型
R 用两种方式存储数字。numeric(以双精度浮点数存储)是默认类型:除非你另有说明,你输入的每个数字都是 double。integer 是一种单独的精确整数类型,要用 L 后缀来指定:
实际上你很少需要在意这一点:R 会悄悄地在两者之间转换,而且 is.numeric() 对两者都返回 TRUE(完整的类型系统见数据类型)。唯一能看出区别的地方是除法:/ 总是返回 double,即使是两个整数相除:
integer 主要作为计数函数(length()、seq_len()、nrow())的输出和索引值出现。需要做整数除法时,R 有一个专门的运算符,下文会讲到。
取整函数家族
R 提供了五种截取数字的方式,每种都有不同的含义:
round(x, digits):保留指定的小数位数(默认是 0)。floor(x):向下取整到最近的整数,总是朝负无穷方向。ceiling(x):向上取整,总是朝正无穷方向。trunc(x):直接砍掉小数部分,总是朝零的方向。注意负数时的区别:trunc(-2.7)是-2,而floor(-2.7)是-3。signif(x, digits):保留指定的有效数字位数,而不是小数位数:signif(123456, 2)是120000。
还有一个著名的意外:根据 IEEE 754 标准,round() 对恰好是一半的数采用四舍六入五成双(银行家舍入):
它打印 0 2 2 4:每个一半都舍入到最近的偶数。这不是 bug,它能防止你对大量舍入后的值求和时产生系统性的向上偏差。如果报告需要课本式的四舍五入,可以加一个极小的偏移量,或者改在展示层做格式化。
常用数学函数
基础函数的用法和你猜的完全一样:
sqrt() 是平方根,abs() 是绝对值,^ 是乘方,exp(x) 是 e 的 x 次方,所以 exp(1) 就是欧拉数,约为 2.718282。
唯一容易让人栽跟头的是 log()。在 R 中,log() 是自然对数(以 e 为底),而不是以 10 为底:
第一行打印的大约是 4.60517,而不是习惯以 10 为底的人所期待的 2。要用这些底数时,请使用 log10() 和 log2(),或者显式传入 base =。(这一约定在统计学中是标准做法,自然对数就是默认的对数。)
取模 %% 与整数除法 %/%
有两个运算符负责余数运算:
%% 是取模(求余数):17 除以 5 等于 3 余 2。%/% 是整数除法:17 里能放下几个完整的 5。两者共同满足 x == (x %/% y) * y + (x %% y)。
%% 的经典用途是检查能否整除:
负数有一个细节:R 的 %% 结果的符号跟随除数(和 Python 一样,和 C 不同):
结果是 2,而不是 -1:R 回答的是“3 的某个倍数加上多少才能得到 -7?”,这样当除数为正时,结果总是在 0..2 之间。用来让索引循环回绕很方便;如果你是从 C 或 Java 转过来的,就会觉得意外。
特殊值:Inf、-Inf 与 NaN
R 的数字遵循 IEEE 754,所以有些运算会产生特殊值,而不是报错:
1/0 是 Inf(无穷大),-1/0 是 -Inf,而 0/0 这个真正无定义的量是 NaN,即“不是一个数”(not a number)。这个区别很重要:Inf 是一个答案(“比任何数都大”),NaN 则是没有答案。要用专门的函数检测它们,因为 == NaN 永远行不通:
注意最后一行:NaN 也算作 NA,所以 is.na() 能捕获它。这又是一个理由,说明 is.na() 为什么是检查“这个值能不能用?”的标准方式(详见缺失值)。
R 还原生支持读写科学计数法:2.5e3 就是 2500,非常小或非常大的数默认会以 e 记数法打印:
当报告需要普通的小数形式时,使用 format(x, scientific = FALSE)(或者 scipen 选项)。
浮点数的意外
所有以二进制存储小数的语言都有这个问题,R 也不例外:
结果是 FALSE,因为 0.1 + 0.2 实际上是 0.30000000000000004。0.1 和 0.2 都没有精确的二进制表示,微小的误差会累积起来。R 默认只显示 7 位有效数字,把这一点隐藏了起来,所以在某次 == 比较失败之前,你根本察觉不到这个问题。
规则是:永远不要用 == 比较计算出来的小数。 请使用 all.equal(),它会在合理的容差范围内进行比较:
之所以要用 isTRUE() 包起来,是因为值不相等时,all.equal() 返回的是一段描述差异的文字(而不是 FALSE)。对于需要精确的整数运算,integer 在约 21亿以内都是精确的,这也是计数代码使用整数类型的另一个原因。
要点回顾
- 你输入的每个数字都是 double;
42L才是 integer,而且无论如何/都返回 double。 round()采用四舍六入五成双;floor/ceiling/trunc/signif各有不同的截取方式,要清楚你想要的是哪一个。log()是自然对数;其他底数用log10()、log2()或base =。%%给出余数(符号跟随除数),%/%给出整数商。1/0是Inf,0/0是NaN,而且0.1 + 0.2 != 0.3:比较小数要用all.equal(),永远不要用==。
下一篇:日常数据的另一半,字符串,以及 R 为构造、格式化和搜索字符串提供的函数。
常见问题
R 语言中 numeric 和 integer 有什么区别?
numeric(double,双精度)是你输入的任何数字在 R 中的默认类型:42 虽然看起来是整数,其实是 double。integer 是一种单独的存储类型,要用 L 后缀来指定:42L。普通除法总是返回 double,即使是两个整数相除;要做整数除法,请用 %/%。
R 语言中的 log() 是自然对数吗?
是的,R 中的 log(x) 是自然对数(以 e 为底),而不是以 10 为底。以 10 为底用 log10(),以 2 为底用 log2(),任意底数用 log(x, base = b)。log(100) 约为 4.605,而不是 2。
R 语言中 round() 是怎么取整的?
round(x, digits) 会四舍五入到指定的小数位数,但恰好是一半的数会采用“四舍六入五成双”(银行家舍入):round(2.5) 是 2,round(3.5) 是 4。这遵循 IEEE 754 标准,能在对舍入后的值求和时减少偏差,但会让期待课本式四舍五入的人感到意外。
R 语言中 0.1 + 0.2 为什么不等于 0.3?
double 是以二进制存储的,而 0.1、0.2 和 0.3 都没有精确的二进制表示,所以 0.1 + 0.2 实际上是 0.30000000000000004。永远不要用 == 比较计算出来的小数;请用 isTRUE(all.equal(x, y)),或者检查 abs(x - y) < 1e-9。