7.3 因子

因子是一个用于存储类别变量的特殊的变量类型。它有时像字符串,有时又像整数。

7.3.1 创建因子

当你用一列文本数据创建数据框时,R将文本默认为类别数据并进行转换。下例中的数据集包含了随机的10个成年人的身高数据:

  1. (heights <- data.frame(
  2. height_cm = c(153, 181, 150, 172, 165, 149, 174, 169, 198, 163),
  3. gender = c(
  4. "female", "male", "female", "male", "male",
  5. "female", "female", "male", "male", "female"
  6. )
  7. ))
  8. ## height_cm gender
  9. ## 1 153 female
  10. ## 2 181 male
  11. ## 3 150 female
  12. ## 4 172 male
  13. ## 5 165 male
  14. ## 6 149 female
  15. ## 7 174 female
  16. ## 8 169 male
  17. ## 9 198 male
  18. ## 10 163 female

通过检查gender一列的类,正如你所料,我们发现它不是一个字符向量,而是一个因子:

  1. class(heights$gender)
  2. ## [1] "factor"

把这列打印出来可看到它更多的本质信息:

  1. heights$gender
  2. ## [1] female male female male male female female male male female
  3. ## Levels: female male

因子中的每个值都是一个字符串,它们被限制为“female”、“male”或缺失值。如果我们把不同的字符串添加到genders中,此项约束则变得很明显:

  1. heights$gender[1] <- "Female" #注意是大写的"F"
  2. ## Warning: invalid factor level, NA generated
  3. heights$gender
  4. ## [1] <NA> male female male male female female male male female
  5. ## Levels: female male

选项“female”和“male”被称为因子水平,它能用levels函数查询到:

  1. levels(heights$gender)
  2. ## [1] "female" "male"

水平的级数(相当于因子levellength)可由nlevel函数查询到:

  1. nlevels(heights$gender)
  2. ## [1] 2

除了使用数据框在内部自动创建因子之外,也可以使用factor函数创建它。它的第一个参数(唯一的强制要求)是一个字符向量:

  1. gender_char <- c(
  2. "female", "male", "female", "male", "male",
  3. "female", "female", "male", "male", "female"
  4. )
  5. (gender_fac <- factor(gender_char))
  6. ## [1] female male female male male female female male male female
  7. ## Levels: female male

7.3.2 更改因子水平

我们可以通过指定levels参数来更改因子被创建时水平的先后顺序:

  1. factor(gender_char, levels = c("male", "female"))
  2. ## [1] female male female male male female female male male female
  3. ## Levels: male female

如果想在因子创建之后再改变因子水平的顺序,就再次使用factor函数,这时它的参数是当前的因子(而不是字符向量):

  1. factor(gender_char, levels = c("male", "female"))
  2. ## [1] female male female male male female female male male female
  3. ## Levels: male female

警告

不能使用levels函数直接改变因子的水平值。因为这将重新为每一个水平打上标签,更改数据值,这通常不是我们想要的。

在下例中,直接设置因子的水平值会将数据中的male变成female,female变成male,这并不是我们想要的:

  1. levels(gender_fac) <- c("male", "female")
  2. gender_fac
  3. ## [1] male female male female female male male female female male
  4. ## Levels: male female

relevel函数是另一种改变因子水平顺序的方法。在这种情况下,你只需指定第一个水平值。正如你所料,此功能的使用场景还是相当少的(在将不同类别和引用类别相比较的回归模型中,它很好用)。大多数时候,使用factor函数来设置水平值会更方便:

  1. relevel(gender_fac, "male")
  2. ## [1] male female male female female male male female female male
  3. ## Levels: male female

7.3.3 去掉因子水平

在数据集清理的过程中,最终你可能需要去掉所有与因子水平对应的数据。考虑以下数据集,它记录了上班途中所使用的交通工具的次数:

  1. getting_to_work <- data.frame(
  2. mode = c(
  3. "bike", "car", "bus", "car", "walk",
  4. "bike", "car", "bike", "car", "car"
  5. ),
  6. time_mins = c(25, 13, NA, 22, 65, 28, 15, 24, NA, 14)
  7. )

并非每次都有记录,所以我们的第一个任务是去掉那些time_minsNA的行 :

  1. (getting_to_work <- subset(getting_to_work, !is.na(time_mins)))
  2. ## mode time_mins
  3. ## 1 bike 25
  4. ## 2 car 13
  5. ## 4 car 22
  6. ## 5 walk 65
  7. ## 6 bike 28
  8. ## 7 car 15
  9. ## 8 bike 24
  10. ## 10 car 14

请看mode一列,其中只有三个不同值,而因子水平却是four。我们可以使用unique函数(当然,也可以用levels函数)看清这一点:

  1. unique(getting_to_work$mode)
  2. ## [1] bike car walk
  3. ## Levels: bike bus car walk

如果要删除未使用的水平因子,我们可以使用droplevels函数。它接受因子或是数据框作为参数。对后者来说,它将丢弃输入因子中所有未使用的水平。因为在我们的数据框里只有一个因子未使用,所以下例中的两行代码是等价的:

  1. getting_to_work$mode <- droplevels(getting_to_work$mode)
  2. getting_to_work <- droplevels(getting_to_work)
  3. levels(getting_to_work$mode)
  4. ## [1] "bike" "car" "walk"

7.3.4 有序因子

有些因子的水平在语义上大于或小于其他水平。这在多选择题的调查问题中很常见。例如,调查问题“你有多快乐?”可能的回答包括:“depressed”、“grumpy”、“so-so”、“cheery”和“ecstatic”2。结果是类别变量,所以我们可以创建一个拥有五个选项的因子。在此,使用sample函数来产生10000个随机回复:

2 有时这被称为李克特量(Likert scale)表。

  1. happy_choices <- c("depressed", "grumpy", "so-so", "cheery", "ecstatic")
  2. happy_values <- sample(
  3. happy_choices,
  4. 10000,
  5. replace = TRUE
  6. )
  7. happy_fac <- factor(happy_values, happy_choices)
  8. head(happy_fac)
  9. ## [1] grumpy depressed cheery ecstatic grumpy grumpy
  10. ## Levels: depressed grumpy so-so cheery ecstatic

在这种情况下,对调查者来说,五个选项其实是有顺序的:“grumpy”(暴躁的)比“depressed”(沮丧)更快乐些,而“so-so”(马马虎虎)又比“grumpy”更快乐些等。这意味着最好把回复存储在一个按顺序排列的因子中。使用ordered函数(或通过给factor传入order = TRUE参数)可实现这个功能:

  1. happy_ord <- ordered(happy_values, happy_choices)
  2. head(happy_ord)
  3. ## [1] grumpy depressed cheery ecstatic grumpy grumpy
  4. ## Levels: depressed < grumpy < so-so < cheery < ecstatic

一个有序的因子还是因子,但一般的因子却不一定是有序的:

  1. is.factor(happy_ord)
  2. ## [1] TRUE
  3. is.ordered(happy_fac)
  4. ## [1] FALSE

在大多数情况下,你无需为有序因子的使用担心,而仅会在某些模型中看到差别。不过,在分析调查数据时,它们比较有用。

7.3.5 将连续变量转换为类别

一个汇总数值变量的方法是计算有多少个值落入不同的“组”(bins)中,cut函数能将数值变量切成不同的块,然后返回一个因子。它通常使用table函数得到每组数字的总和。(hist函数能画出直方图,它也能实现这个功能,就像我们将在后面看到的包含在plyr包中的count一样。)

在下例中,我们随机地生成10 000名工人的年龄数据(从16到66,使用Beta分布),并将他们按每10年分组:

  1. ages <- 16 + 50 * rbeta(10000, 2, 3)
  2. grouped_ages <- cut(ages, seq.int(16, 66, 10))
  3. head(grouped_ages)
  4. ## [1] (26,36] (16,26] (26,36] (26,36] (26,36] (46,56]
  5. ## Levels: (16,26] (26,36] (36,46] (46,56] (56,66]
  6. table(grouped_ages)
  7. ## grouped_ages
  8. ## (16,26] (26,36] (36,46] (46,56] (56,66]
  9. ## 1844 3339 3017 1533 267

在本例中,大部分工人的年龄分布于26到36和36到46两个类别中(这正是使用Beta分布的结果) 。

请注意,ages是一个数字变量,而grouped_ages是一个因子:

  1. class(ages)
  2. ## [1] "numeric"
  3. class(grouped_ages)
  4. ## [1] "factor"

7.3.6 将类别变量转换为连续变量

与上面相反的情况是把因子转换成数值变量,这在数据清理中非常有用。如果你有一些脏数据,例如打错了的数字,在数据导入的过程中,R会将它们解释为字符串,并将其转换为因子。在下例中,其中一个数字有两个小数点。诸如read.table的导入函数(第12章将深入探讨)将无法把这样的字符串解析成数字格式,而会默认把这一列转换成字符向量:

  1. dirty <- data.frame(
  2. x = c("1.23", "4..56", "7.89")
  3. )

要想把x列转换为数字,显而易见的解决方法是调用as.numeric。可惜,它给出了错误的答案:

  1. as.numeric(dirty$x)
  2. ## [1] 1 2 3

as.numeric函数作用于因子上却看到了整数值,这说明因子使用了整数来存储数据。在一般情况下,可以从levels(f)[as.integer(f)]中重构出f因子。

为了正确地把因子转换为数字,可先把因子转换为字符向量,再转换为数值。第二个值是NA,因为4..56并不是一个真正的数字:

  1. as.numeric(as.character(dirty$x))
  2. ## Warning: NAs introduced by coercion
  3. ## [1] 1.23 NA 7.89

这不够高效,因为相同的值须被多次转换。正如FAQ on R中所指出的,更好的方法是将因子水平转换为数字,然后再像上面一样重建因子:

  1. as.numeric(levels(dirty$x))[as.integer(dirty$x)]
  2. ## Warning: NAs introduced by coercion
  3. ## [1] 1.23 NA 7.89

因为这不够直观,所以如果你经常使用它,就把它包装成一个函数以方便调用:

  1. factor_to_numeric <- function(f)
  2. {
  3. as.numeric(levels(f))[as.integer(f)]
  4. }

7.3.7 生成因子水平

为了平衡数据,使到在每个水平上的数据点的数目相等,可用gl函数来生成因子。它最简单的形式是:第一个整型参数为要生成的因子的水平数,第二个为每个水平需要重复的次数。通常你想为每个水平命名,这可以通过给label参数传入一个字符向量来实现。你还可以通过传入length参数以创建更复杂的水平排序,例如交替值(alternating value):

  1. gl(3, 2)
  2. ## [1] 1 1 2 2 3 3
  3. ## Levels: 1 2 3
  4. gl(3, 2, labels = c("placebo", "drug A", "drug B"))
  5. ## [1] placebo placebo drug A drug A drug B drug B
  6. ## Levels: placebo drug A drug B
  7. gl(3, 1, 6, labels = c("placebo", "drug A", "drug B")) #交替
  8. ## [1] placebo drug A drug B placebo drug A drug B
  9. ## Levels: placebo drug A drug B

7.3.8 合并因子

如果我们有多个类别变量,有时把它们合并成一个单一的因子是有用的,其中每个水平由各个变量之间的交叉合并(使用interaction函数)组成:

  1. treatment <- gl(3, 2, labels = c("placebo", "drug A", "drug B"))
  2. gender <- gl(2, 1, 6, labels = c("female", "male"))
  3. interaction(treatment, gender)
  4. ## [1] placebo.female placebo.male drug A.female drug A.male
  5. ## [5] drug B.female drug B.male
  6. ## 6 Levels: placebo.female drug A.female drug B.female ... drug B.male