R语言入门与实践
上QQ阅读APP看书,第一时间看更新

3.1 原子型向量

原子型向量就是最简单的包含数据的向量。其实,你已经见过它了,比如项目1所生成的die对象。在R中,可以将一组数据用c函数组合在一起,形成一个原子型向量。

        die <- c(1, 2, 3, 4, 5, 6)
        die
        ## 1 2 3 4 5 6
        is.vector(die) ➊
        ##  TRUE

➊ is.vector用来测试某个对象是否为原子型向量。如果是,is.vector将返回TRUE,如果不是则返回FALSE。

也可以生成只包含一个值的原子型向量。在这种情况下,R将这个单值存储为一个长度为1的原子型向量。

        five <-5
        five
        ## 5

        is.vector(five)
        ## TRUE

        length(five)
        ## 1
        length(die)
        ## 6

length

length函数返回原子型向量的长度。

每一个原子型向量都将其值存储在一个一维向量中,并且只能是一种类型的数据。可以使用R中其他不同类型的原子型向量存储不同类型的数据。概括来说,R可以识别六种基本类型的原子型向量,分别是:双整型(double)、整型(integer)、字符型(character)、逻辑型(logical)、复数类型(complex)以及原始类型(raw)。

为了生成一整副牌,你需要使用不同类型的原子型向量存储不同类型的信息(比如文字信息和数字信息)。这可以通过在键入数据时使用简单的R规范来实现。比如说,输入整数值后键入大写字母L就表示输入一个整型数值。如果要输入一个字符,只需给相应的字符加上双引号即可。

        int <-1L
        text <- "ace"

每种原子型向量都有自己的书写规范(下面会介绍)。R会根据相应的规范识别你的输入,并将其定义为对应类型的原子型向量。如果想生成一个包含多个元素的原子型向量,可以利用第2章介绍过的c函数把元素组合起来。在组合时,要注意书写规范的一致性。

        int <- c(1L, 5L)
        text <- c("ace", "hearts")

你可能会问:R为什么要使用好几种不同类型的向量呢?这是因为,每一种类型都帮助R定义了一个特定的对象,而不会有意外的情况发生。例如,针对包含数值的原子型向量,R可以进行运算,而针对包含字符型的原子型向量进行运算则是不合逻辑的。

        sum(int)
        ## 6

        sum(text)
        ## Error in sum(text) : invalid 'type' (character) of argument

接下来将介绍R中这六种原子型向量。

3.1.1 双整型

双整型向量用来存储普通的数值型数据。数值可正可负,可大可小,可包含小数部分,也可不包含。总之,你在R中键入的任何一个数值都会默认以双整型存储。例如,你在项目1中生成的die就是一个双整型向量。

        die <- c(1, 2, 3, 4, 5, 6)
        die
        ## 1 2 3 4 5 6

知道自己在使用何种类型的R对象并不难(因为有些是显而易见的),但有一个直接的方法,就是使用typeof函数查看某个对象到底是什么类型,如下所示。

        typeof(die)
        ##  "double"

有一些函数把双整型称作数值型(numeric),在之后的章节中我也会用这个代称。双整型是来源于计算机科学的术语。它指的是计算机在存储一个数值时所设定的字节数,但是我个人觉得在数据科学中,“数值型”更符合我们的逻辑。

3.1.2 整型

整型向量用来存储整型的数据。顾名思义,整型数据的数值不需要小数点成分。作为数据科学家,你并不需要经常与整型打交道,因为整型数据也可以被存储为双整型。在R中,明确设定整型的方法是在该数值之后加上大写字母L,如下所示。

        int <- c(-1L, 2L, 4L)
        int
        ## -1  2  4

        typeof(int)
        ## "integer"

记住,如果不明确加上L, R并不会将一个数值设定为整型。未加L的整数将被存储为双整型。4和4L之间的唯一区别就在于它们的存储方式。在计算机内存中,整型的定义方式要比双整型更加精确(除非该整数非常大或者非常小)。

那么为什么要把数据存储为整型而不是双整型呢?因为有些时候,精度上的微小差距会产生令人意想不到的效果。在R程序中,计算机会给双整型对象分配64字节的内存。这使得计算机可以非常精确地表示数值,但是有些数值不能用64字节精确表示,也就是不能用64个1和0来表达。例如,圆周率π是无限不循环小数。你的计算机必须将圆周率舍入到小数点后某一位以便交给内存存储,还要保证舍进后的值与真实的圆周率非常接近。很多类似的小数都有这个问题。

这就导致每一个双整型的数值都只会大约精确到小数点后16位,这便会带来一点小误差。虽然在很多场合,这种舍入误差都不会被察觉到。但是,有时却会造成令人意想不到的后果。例如,你可能认为下面示例代码中的返回结果一定是0,其实不然。

        sqrt(2)^2-2
        ## 4.440892e-16

根号2不能用16字节精确表示。因此,R必须进行一定量的舍入。上面表达式得到的结果非常接近零,但一定不等于零。

这样的舍入误差也叫作浮点(floating-point)误差,在这种情况下的运算称作浮点运算。浮点运算是计算机编程的一个特点,并非R所独有。通常来说,浮点运算并不会搅浑一锅好粥。但是应该记住,如果运算时出现了令人意外的结果,有可能与浮点运算有关。

为了避免浮点误差,可以限定只使用整型而不使用带有小数点的双整型。但是,这对于很多数据科学的应用场景来说不太现实。很多情况下,要想表达某个运算过程,仅仅使用整数是不够的。然而幸运的是,由浮点运算所导致的误差往往并不是非常显著(如果非常显著,也很容易看出来)。因此,从数据科学家的角度来看,大多数情况下你都会使用双整型,而不是整型。

3.1.3 字符型

字符型向量存储一小段文本。在R中,字符要加双引号,再组合起来构成一个字符型向量。

          text <- c("Hello",  "World")
          text
          ##  "Hello"  "World"

          typeof(text)
          ## "character"

          typeof("Hello")
          ## "character"

字符型向量中的单个元素称作字符串(string)。请注意,字符串不仅可以包含英文字母,也可以由数字或者符号组成。

练习

你能看出一个字符串与一个数值之间的区别吗?小测验:1、"1"和"one"中哪个是字符串,哪个是数值?

"1"和"one"都是字符串。字符串也可以包含数值型的字符,但这些数值字符并不是真正意义上的数值。它们本质上是字符串,只是字符串中包含的是一个数值元素而已。你可以区分字符串和数值,因为字符串总是被一对双引号所包围。在R中,任何加双引号的对象都会被当作字符串,无论双引号内是什么元素。

R中的字符串很容易与R对象混淆。为什么呢?因为在R代码中,字符串和R对象名看起来都只是一小段文本。举例来说,x是某个R对象“x”的名称,而"x"是某个包含字符“x”的字符串。这里,一个是包含原始数据的对象,而另一个则是原始数据本身。

在字符串键入和操作的时候,如果忘记键入双引号,就很容易导致程序出错。如果不加双引号,R很可能会去寻找一个不存在的R对象。

3.1.4 逻辑型

逻辑型向量用来存储TRUE(真)和FALSE(假),这是R中布尔数据的表现形式。在比对数据时,逻辑型会非常有用。

          3 > 4
          ## FALSE

只要在R中键入全部大写的TRUE或FALSE(不加双引号),就会被当作逻辑型数据。R也会默认把T和F分别当作TRUE和FALSE的简写。

        logic <- c(TRUE, FALSE, TRUE)
        logic
        ##   TRUE FALSE  TRUE

        typeof(logic)
        ## "logical"

        typeof(F)
        ## "logical"

3.1.5 复数类型和原始类型

双整型、整型、字符型和逻辑型是原子型向量中最常见的类型,但是R其实还可以识别另外两种类型的原子型向量,它们是:复数类型和原始类型。在分析数据时,基本不会用到这两个类型,但是为了论述的完整性,我们还是在这里介绍一下。

复数类型向量用来存储复数。要生成一个复数类型向量,只需要将某个数字与带i的虚数项相加即可。

        comp <- c(1 + 1i, 1 + 2i, 1 + 3i)
        comp
        ## 1+1i 1+2i 1+3i

        typeof(comp)
        ## "complex"

原始类型向量用来存储数据的原始字节。原始类型向量的生成较为复杂,但是如果要生成一个长度为n的空原始类型向量,可以用raw(n)。如果你正在处理这种类型的数据,不妨看一下raw函数的帮助页面,那里有更详尽的信息。

        raw(3)
        ## 00 00 00

        typeof(raw(3))
        ## "raw"

练习

生成一个原子型向量来存储皇家同花顺的牌面,例如,一张黑桃A、一张黑桃K、一张黑桃Q、一张黑桃J和一张黑桃10。在这一手牌当中,黑桃A的牌面是A(ace),花色是黑桃(spades)。

你会使用哪种类型的向量来存储这些名称信息呢?

字符型向量对于存储扑克牌的牌面名称信息应该最合适不过了。每张牌的牌面名称加双引号,再用c函数组合成一个向量,代码如下。

        hand <- c("ace", "king", "queen", "jack", "ten")
        hand
        ## "ace"   "king"   "queen" "jack" "ten"

        typeof(hand)
        ## "character"

这样便生成了一组扑克牌的牌面名称,并将它们放在了一个一维向量中,干得漂亮!接下来,我们再生成一个结构更加复杂的数据:一个二维的表格型数据,其中既有扑克牌牌面的信息,也有花色的信息。要想生成这样结构更为复杂的数据对象,我们将以原子型向量为基础,赋予它一些属性(attribute)和(class)。