1 简介
在 R 中,因子用于处理分类变量,即具有一组固定且已知的可能值的变量。当您想以非字母顺序显示字符向量时,它们也很有用。
1.1 加载包
为了处理因子,我们将使用forcats包,它是 tidyverse 的核心部分。
library(tidyverse)
2 因子的创建
假设有一个记录月份的变量:
x1 <- c("Dec", "Apr", "Jan", "Mar")
使用字符串记录这个变量有两个问题:
-
最多只有十二个月,而且有可能出现打字错误:
x2 <- c("Dec", "Apr", "Jam", "Mar")
-
排序的结果没有用处:
sort(x1) #> [1] "Apr" "Dec" "Jan" "Mar"
可以使用因子来解决这两个问题。要创建因子,必须首先创建一个有效等级列表:
month_levels <- c(
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
)
现在您可以创建一个因子:
y1 <- factor(x1, levels = month_levels)
y1
#> [1] Dec Apr Jan Mar
#> Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
sort(y1)
#> [1] Jan Mar Apr Dec
#> Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
任何不在集合中的值都将转换为 NA:
y2 <- factor(x2, levels = month_levels)
y2
#> [1] Dec Apr <NA> Mar
#> Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
如果你想要一个警告,你可以使用readr::parse_factor()
:
y2 <- parse_factor(x2, levels = month_levels)
#> Warning: 1 parsing failure.
#> row col expected actual
#> 3 -- value in level set Jam
如果省略了等级,它们将按字母顺序从数据中获得等级:
factor(x1)
#> [1] Dec Apr Jan Mar
#> Levels: Apr Dec Jan Mar
有时,您希望等级的顺序与数据中首次出现的顺序相匹配。您可以在创建因子时通过将级别设置为unique(x)
或事后使用fct_inorder()
:
f1 <- factor(x1, levels = unique(x1))
f1
#> [1] Dec Apr Jan Mar
#> Levels: Dec Apr Jan Mar
f2 <- x1 %>% factor() %>% fct_inorder()
f2
#> [1] Dec Apr Jan Mar
#> Levels: Dec Apr Jan Mar
如果您需要直接访问因子的等级,可以使用levels()
:
levels(f2)
#> [1] "Dec" "Apr" "Jan" "Mar"
3 常规信息
接下来我们将了解forcats::gss_cat
。这是来自General Social Survey的数据样本,这是一项由芝加哥大学独立研究组织 NORC 进行的长期美国调查。该调查包含数千个问题,因此我选择了gss_cat
一些问题来说明您在处理因子时会遇到的一些问题。
gss_cat
#> # A tibble: 21,483 x 9
#> year marital age race rincome partyid relig denom tvhours
#> <int> <fct> <int> <fct> <fct> <fct> <fct> <fct> <int>
#> 1 2000 Never mar… 26 White $8000 to … Ind,near r… Protesta… Souther… 12
#> 2 2000 Divorced 48 White $8000 to … Not str re… Protesta… Baptist… NA
#> 3 2000 Widowed 67 White Not appli… Independent Protesta… No deno… 2
#> 4 2000 Never mar… 39 White Not appli… Ind,near r… Orthodox… Not app… 4
#> 5 2000 Divorced 25 White Not appli… Not str de… None Not app… 1
#> 6 2000 Married 25 White $20000 - … Strong dem… Protesta… Souther… NA
#> # … with 21,477 more rows
当因子存储在列中时,您无法那么容易地看到它们的水平。可通过count()
查看:
gss_cat %>%
count(race)
#> # A tibble: 3 x 2
#> race n
#> <fct> <int>
#> 1 Other 1959
#> 2 Black 3129
#> 3 White 16395
或者使用条形图:
ggplot(gss_cat, aes(race)) +
geom_bar()
默认情况下,ggplot2 将删除没有任何值的等级。但是也可以强制显示它们:
ggplot(gss_cat, aes(race)) +
geom_bar() +
scale_x_discrete(drop = FALSE)
这些级别代表在此数据集中根本没有出现的有效值。
在处理因子时,两个最常见的操作是更改级别的顺序和更改级别的值。
4 修改因子顺序
在可视化中更改因子级别的顺序通常很有用。例如,假设您想探索不同宗教每天平均看电视上小时数:
relig_summary <- gss_cat %>%
group_by(relig) %>%
summarise(
age = mean(age, na.rm = TRUE),
tvhours = mean(tvhours, na.rm = TRUE),
n = n()
)
#> `summarise()` ungrouping output (override with `.groups` argument)
ggplot(relig_summary, aes(tvhours, relig)) + geom_point()
从整体上很难理解这个图形。我们可以通过使用fct_reorder()
重新排序relig
的级别来改进它。fct_reorder()
需要三个参数:
-
f
,要修改其等级的因子。 -
x
,一个数字向量,用它来重新排序级别。 - 可选,如果
fun
的x
每个值有多个值,则使用的函数f
。默认值为median
。
ggplot(relig_summary, aes(tvhours, fct_reorder(relig, tvhours))) +
geom_point()
重新排序宗教可以更容易地看到,“Don't know”类别的人看的电视多得多,而印度教和其他东方宗教的看的少得多。
开始进行更复杂的转换时,我建议将它们移出aes()
并且移入单独的mutate()
步骤。例如,您可以将上面的图重写为:
relig_summary %>%
mutate(relig = fct_reorder(relig, tvhours)) %>%
ggplot(aes(tvhours, relig)) +
geom_point()
如果我们创建一个类似的图来查看平均年龄随报告收入水平的变化会怎样?
rincome_summary <- gss_cat %>%
group_by(rincome) %>%
summarise(
age = mean(age, na.rm = TRUE),
tvhours = mean(tvhours, na.rm = TRUE),
n = n()
)
#> `summarise()` ungrouping output (override with `.groups` argument)
ggplot(rincome_summary, aes(age, fct_reorder(rincome, age))) + geom_point()
在这里,随意排序级别不是一个好主意!那是因为rincome
已经有一个我们不应该乱动的原则性命令。fct_reorder()
为任意等级排序的因素预留。
但是,将“Not applicable”拉到其他特殊级别的前面确实是有意义的。您可以使用fct_relevel()
. 它需要一个因子 ,f
是您想要移动到行前面的任意数量的级别。
ggplot(rincome_summary, aes(age, fct_relevel(rincome, "Not applicable"))) +
geom_point()
为什么认为“Not applicable”的平均年龄如此之高?
当您为绘图上的线条着色时,另一种类型的重新排序很有用。fct_reorder2()
按y
与x
最大值关联的值对因子重新排序。这使绘图更易于阅读,因为线条颜色与图例对齐。
by_age <- gss_cat %>%
filter(!is.na(age)) %>%
count(age, marital) %>%
group_by(age) %>%
mutate(prop = n / sum(n))
ggplot(by_age, aes(age, prop, colour = marital)) +
geom_line(na.rm = TRUE)
ggplot(by_age, aes(age, prop, colour = fct_reorder2(marital, age, prop))) +
geom_line() +
labs(colour = "marital")
最后,对于条形图,可以使用fct_infreq()
递增频率对级别进行排序:这是最简单的重新排序类型,因为它不需要任何额外的变量。您可能希望与fct_rev()
.
gss_cat %>%
mutate(marital = marital %>% fct_infreq() %>% fct_rev()) %>%
ggplot(aes(marital)) +
geom_bar()
5 修改因子级别
比更改级别更强大的是更改它们的值。这使您可以直接定义想要的级别。最通用和最强大的工具是fct_recode()
. 它允许您重新编码或更改每个级别的值。例如,使用gss_cat$partyid
:
gss_cat %>% count(partyid)
#> # A tibble: 10 x 2
#> partyid n
#> <fct> <int>
#> 1 No answer 154
#> 2 Don't know 1
#> 3 Other party 393
#> 4 Strong republican 2314
#> 5 Not str republican 3032
#> 6 Ind,near rep 1791
#> # … with 4 more rows
这些级别简洁且不一致。让我们将它们调整得更长,并使用平行结构。
gss_cat %>%
mutate(partyid = fct_recode(partyid,
"Republican, strong" = "Strong republican",
"Republican, weak" = "Not str republican",
"Independent, near rep" = "Ind,near rep",
"Independent, near dem" = "Ind,near dem",
"Democrat, weak" = "Not str democrat",
"Democrat, strong" = "Strong democrat"
)) %>%
count(partyid)
#> # A tibble: 10 x 2
#> partyid n
#> <fct> <int>
#> 1 No answer 154
#> 2 Don't know 1
#> 3 Other party 393
#> 4 Republican, strong 2314
#> 5 Republican, weak 3032
#> 6 Independent, near rep 1791
#> # … with 4 more rows
fct_recode()
将保留未明确提及的级别,如果引用了不存在的级别,则会警告您。
要合并组,您可以将多个旧级别分配给同一个新级别:
gss_cat %>%
mutate(partyid = fct_recode(partyid,
"Republican, strong" = "Strong republican",
"Republican, weak" = "Not str republican",
"Independent, near rep" = "Ind,near rep",
"Independent, near dem" = "Ind,near dem",
"Democrat, weak" = "Not str democrat",
"Democrat, strong" = "Strong democrat",
"Other" = "No answer",
"Other" = "Don't know",
"Other" = "Other party"
)) %>%
count(partyid)
#> # A tibble: 8 x 2
#> partyid n
#> <fct> <int>
#> 1 Other 548
#> 2 Republican, strong 2314
#> 3 Republican, weak 3032
#> 4 Independent, near rep 1791
#> 5 Independent 4119
#> 6 Independent, near dem 2499
#> # … with 2 more rows
如果您想折叠很多级别,可以使用fct_collapse()
。 对于每个新变量,您可以提供旧级别的向量:
gss_cat %>%
mutate(partyid = fct_collapse(partyid,
other = c("No answer", "Don't know", "Other party"),
rep = c("Strong republican", "Not str republican"),
ind = c("Ind,near rep", "Independent", "Ind,near dem"),
dem = c("Not str democrat", "Strong democrat")
)) %>%
count(partyid)
#> # A tibble: 4 x 2
#> partyid n
#> <fct> <int>
#> 1 other 548
#> 2 rep 5346
#> 3 ind 8409
#> 4 dem 7180
有时,您只想将所有组放在一起,以简化绘图或表格。使用fct_lump()
:
gss_cat %>%
mutate(relig = fct_lump(relig)) %>%
count(relig)
#> # A tibble: 2 x 2
#> relig n
#> <fct> <int>
#> 1 Protestant 10846
#> 2 Other 10637
默认行为是逐渐将最小的组混在一起,确保聚合仍然是最小的组。在这种情况下,它不是很有帮助:因为这项调查中的大多数美国人都是新教徒。
相反,我们可以使用n
参数来指定我们要保留的组数(不包括其他组):
gss_cat %>%
mutate(relig = fct_lump(relig, n = 10)) %>%
count(relig, sort = TRUE) %>%
print(n = Inf)
#> # A tibble: 10 x 2
#> relig n
#> <fct> <int>
#> 1 Protestant 10846
#> 2 Catholic 5124
#> 3 None 3523
#> 4 Christian 689
#> 5 Other 458
#> 6 Jewish 388
#> 7 Buddhism 147
#> 8 Inter-nondenominational 109
#> 9 Moslem/islam 104
#> 10 Orthodox-christian 95