关于R语言中的”因子”变量类型(一)
使用R语言一段时间的用户绝对不会对R语言中的因子变量类型(factor)感到陌生,我想很多人与因子类型打交道最常见的渠道便是在使用read.table()
, read.csv()
等函数读取文件时一不小心将字符 串类型的数据转化为因子类型,导致后面的数据处理中出现各种潜在的问题,丈二和尚摸不着头脑.事实上.R语言自带读取函数中的这一设置为很多人所诟病,也正是这个原因,很多使用R一段时间的用户通常会在读取文件时设置stringsAsFactors = FALSE
这一选项来关闭在读取数据中默认的将字符型变量转化为因子型向量,而在新的readr
函数包中的read_csv()
等一系列读取函数则直接舍弃了将字符变量转化为因子变量的功能,来避免不必要的麻烦.那是不是R语言中的因子变量类型就没有用处了呢?绝非如此,从我自身的体会而言,因子变量类型可说是R语言的一个非常精妙的设计,但对于不熟悉其特性的用户而言,却常常为其所累,甚至苦不堪言.事实上很多使用R一两年的人可能也对因子变量类型的一些细节不甚清楚.综上所述,因子变量类型真可谓让人又爱又恨.本文的写作目的就是让更多R初学者或者已经熟悉R语言但对因子变量细节却不太了解的用户来更为深入的了解因子变量类型,让更多的人理解因子变量类型,爱上因子变量类型,真正用好因子变量类型.写到这,因子变量是不是应该感谢我让更多人了解它呢?哈哈哈哈!闲话少说,本文将从因子变量的特性以及对因子变量的常见操作两大部分展开.这一节我们介绍因子变量的特性.
如何构造一个因子变量
首先简要介绍以下因子变量的构造方法,事实上绝大多数实际使用过程中,你通常是使用as.factor()
函数将字符型变量转化为因子型变量得到的. 另一种方法是直接使用factor()
函数,facotr()
函数接受一个向量, 额外地,此时,你需要指定levels
参数,事实上levels
是因子变量的一个属性,具体细节后文介绍,此处仅展示两种因子变量的构造方法:
vars <- c("a", "b", "c")
f_vars <- as.factor(vars)
vars <- factor(c("a", "b", "c"), levels = c("a", "b","c"))
因子变量的主要特性
讲解因子变量(factor)的特性很多情况下需要与字符型变量(character)相对比进行介绍,因为这两者实在是太像了,但实际上却有着诸多不同之处,以下详细展开:
因子变量的有序性
谈到有序性,事实上,字符型变量也可以看作是有序的,如下例:
char <- c("Apple", "Orange", "4","Banana", "5a")
sort(char)
## [1] "4" "5a" "Apple" "Banana" "Orange"
上面我们展示了对一个字符型变量进行排序的结果,字符型排序的基本规则是先按照数字进行排序,继而根据英文字母顺序主义排序,因此上例中,第一个元素为4
,第二个元素为5a
,字符型变量的局限性在于其对元素的排序规则是内定的,无法改变的.因此,假设我们有一组表示一箱子苹果的数据,主要包括每个苹果的品种和大小.数据如下所示:
apple <- tibble(编号 = c(1, 2, 3),
品种 = c("红富士", "黄元帅", "秦冠"),
尺寸 = c("big", "small", "median"))
编号 | 品种 | 尺寸 |
---|---|---|
1 | 红富士 | big |
2 | 黄元帅 | small |
3 | 秦冠 | median |
如果我们想按照苹果的尺寸对其进行排序,结果如何呢,且看如下代码:
apple_sort <- apple%>%arrange(尺寸)
str(apple_sort)
## Classes 'tbl_df', 'tbl' and 'data.frame': 3 obs. of 3 variables:
## $ 编号: num 1 3 2
## $ 品种: chr "红富士" "秦冠" "黄元帅"
## $ 尺寸: chr "big" "median" "small"
编号 | 品种 | 尺寸 |
---|---|---|
1 | 红富士 | big |
3 | 秦冠 | median |
2 | 黄元帅 | small |
上例中,使用dplyr
包中的arrange
函数按照”尺寸”变量对数据由小到大排序,其中%>%
表示管道操作符,此处不是重点,感兴趣的用户可以自行了解wrapr
,magrittr
两个包进行学习.很明显,结果并未我们所期待的.由于尺寸为字符型变量,对尺寸变量的排序实际上是按照字母顺序进行的,而我们实际上期待的则是”small”->”median”->”big”由小到大的顺序.当字符型变量无法解决上述问题时,便是因子变量一展身手的时刻了.
区别于字符型变量,因子变量最大的特点在于其顺序可以由用户根据某种特定的意义进行指定.在不做特定指定时,因子型变量的顺序与字符型变量一样,是按照先数字,后字母,数字从小到达,字母按找先后顺序进行排序.由于因子变量顺序提供了用户自定义的余地,因此R语言专门为因子变量提供了levels
属性来指定其特定的顺序,正如我们下面将看到的:
apple_add_factor <- apple%>%
mutate(尺寸_因子 = as.factor(尺寸))
str(apple_add_factor)
## Classes 'tbl_df', 'tbl' and 'data.frame': 3 obs. of 4 variables:
## $ 编号 : num 1 2 3
## $ 品种 : chr "红富士" "黄元帅" "秦冠"
## $ 尺寸 : chr "big" "small" "median"
## $ 尺寸_因子: Factor w/ 3 levels "big","median",..: 1 3 2
编号 | 品种 | 尺寸 | 尺寸_因子 |
---|---|---|---|
1 | 红富士 | big | big |
2 | 黄元帅 | small | small |
3 | 秦冠 | median | median |
levels(apple_add_factor$尺寸_因子)
## [1] "big" "median" "small"
上面的代码中,我们新增了一列转化为因子后的尺寸变量,命名为”尺寸因子”,我们看到,在未作特殊指定时,尺寸因子变量的排列顺序和字符型变量的排列规则一样,因此不难想象,此时如果按照尺寸因子变量对苹果进行排序,仍然得到不我们想到的效果.因此,我们需要告诉尺寸因子变量,其从小到大的排序规则应当定义为”small”->”median”->”big”, 这可以通过修改变量的levels属性完成.
levels(apple_add_factor$尺寸_因子) <- c("small", "median", "big")
另一种可选的但较为繁琐的方法是重新构造一个新的因子变量:
apple_add_factor$尺寸_因子 <- factor(apple_add_factor$尺寸_因子,
levels = c("small", "median", "big"))
相较而言,第一种方法是原地修改变量的属性,第二中方法则重新构造了一个新的变量,第二种方法较为繁琐,但也有其适用场合,此处不再详解.上面的代码中,我们重新定义了尺寸_因子变量的从小到大排列规则, 此时,我们重新对变量进行排序:
apple_sort2 <- apple_add_factor%>%arrange(尺寸_因子)
编号 | 品种 | 尺寸 | 尺寸_因子 |
---|---|---|---|
1 | 红富士 | big | small |
3 | 秦冠 | median | median |
2 | 黄元帅 | small | big |
这一次,我们得到了想要的结果.因子变量没有令我们失望,在合适的场合发挥了它的作用.
因子变量实际上是由数值型变量构成的!
这一标题有没有令你感到惊讶呢!下面给你展示一个比较sao的操作,你先自行体会以下(运气好的人可能体会过,因为我看见有人在QQ群问过这种问题):
x <- c("10", "20", "30", "40")
as.numeric(x)
## [1] 10 20 30 40
y <- as.factor(x)
y
## [1] 10 20 30 40
## Levels: 10 20 30 40
as.numeric(y)
## [1] 1 2 3 4
看到了吗?我们对于x这一数值构成的字符型向量进行数值转化操作,得到了我们想要的结果.然而,当将x转化为因子型变量时,景观看起来外观没有什么大的差别,但我们进行数值转化操作的结果却并非我们想要的,而是一堆奇怪的1,2,3,4
.事实上1,2,3,4
恰恰表示的对应元素在因子型变量中的排序先后,即 levels
属性的先后.对照levels
属性或许让你能够更至关的进行观察:
order(levels(y))
## [1] 1 2 3 4
as.numeric(y)
## [1] 1 2 3 4
这也是为什么很多人看着眼前的数据似乎是一组字符型变量,想将其转化为数值型变量,但却发现为得到想要的结果,究其原因就在于这些变量并不是字符型变量,而是因子型变量.这一情况最容易出现在以下情形中:你的数据中某一列原本应当是数值型变量,但某几个取值缺失了,而缺失值的表示并不是简单的空值,而是其他如”-“等符号,此时在数据读取的过程中,这一列变量将会被自动转化为字符型向量,而如果你恰恰又不幸地忘记设置stringAsFactors = FALSE
, 那么,便会产生如上情形了.任何情况下,都要仔细检查变量类型.上述问题的解决方法是先使用as.characer()
将因子型变量转化为字符型变量,继而使用as.numeric()
函数将其转化为数值型变量.
别忘了,我们的标题是”因子型变量是由数值型变量构成的”,有了上面的背景,理解这一解释也将不再困难.我们使用unclass()
来观察以下因子型变量的本质.unclass()
函数可以简单理解为其去掉变量类型中的其他属性,只保留最核心部分.通俗一点的解释就是它脱下了变量的伪装衣,有些类型没有伪装衣,所以脱衣前后没有什么变化,例如字符型变量,而有些变量则披上了华丽的外衣,脱下伪装衣后我们便可以看到其真实容颜,这类变量包括因子变量以及POSIXlt型时间变量.此处,我们简要做一比较
x = c("Apple", "Orange", "melon")
unclass(x)
## [1] "Apple" "Orange" "melon"
x = as.factor(c("Apple", "Orange", "melon"))
unclass(x)
## [1] 1 3 2
## attr(,"levels")
## [1] "Apple" "melon" "Orange"
看到了吗?因子型变量在unclass()
函数的作用下,显示出了它真实的容颜,原来就是一个数值型变量,这也就是为什么对其进行数值型转化时会得到1,2,3,4
等一系列数值的原因.事实上,因子型变量的这种构造也造成了因子型变量存储方式的不同.对于字符串向量,R需要存储其所有元素的每一个取值.而对于因子型变量,R则存储其对应的水平的次序及有限的几个levels取值,以下例进行阐释:
x = sample(c("Apple", "Orange", "melon"), 100000, replace = TRUE)
y = as.factor(y)
上面的语句中我们构造了具有三个可能取值的字符向量x
,其长度为100000.同时,我们使用as.factor()
函数得到了一个转化为因子型变量的变量y
.R语言对于x的存储是存储了10万个”Apple”, “Orange”,”melo”这些字符串,而对于对应的因子型变量,其内部存储的则是每一元素对应的levels顺序,即10万个1,2,3这类数值,加上三个levels取值”Apple”, “Orange”,”melon”.不难理解,将字符变量转化为因子变量可以有效地减少变量占用内存大小,使用pryr::object_size()
函数比较两个变量的大小:
pryr::object_size(x)
## 800 kB
pryr::object_size(y)
## 640 B
结果显示,x
变量的大小是800kb,而y
的大小只有x
的千分之一不到.转化为因子后,变量的大小有效压缩.当然,随着电脑内存的不断增加,这一特性也显得不再特别重要.
因子型变量取值的有限性
何为有限性?直接解释略显乏力,我们还是通过对因子型变量与字符型变量的对比来进行展示:
x <- c("a", "b", "c")
y <- as.factor(x)
简单构造一个字符型向量和由其转化得到的因子型变量.现在假设我们想向其中第一个元素换为”d”,第二个元素换为”c”,试试下面的代码:
x[1] <- "d"
x[2] <- "c"
x
## [1] "d" "c" "c"
y[1] <- "d"
## Warning in `[<-.factor`(`*tmp*`, 1, value = "d"): invalid factor level, NA
## generated
y[2] <- "c"
y
## [1] <NA> c c
## Levels: a b c
对x元素结果的修改符合我们的预期,但观察y的结果,我们会发现,y[1]
的结果此时为NA
即缺失值.事实上,在因子变量被构建的那一刻起,其levels
属性就决定了它所有可能取值,当我们修改其某一元素为一个不存在于levels
属性中的值时,因子变量将用NA
填充该元素.
结论
以上就是本节的主要内容.回顾一下,我们讲解了因子类型变量的三个主要特性:因子变量的有序性,因子变量本质上是由数值型变量构成的以及因子变量取值的有限性.对以上内容如有疑惑,可以在评论区进行留言,下一节我们将介绍因子变量的常见操作.