63

What is the most effective (ie efficient / appropriate) way to clean up a factor containing multiple levels that need to be collapsed? That is, how to combine two or more factor levels into one.

Here's an example where the two levels "Yes" and "Y" should be collapsed to "Yes", and "No" and "N" collapsed to "No":

## Given: 
x <- c("Y", "Y", "Yes", "N", "No", "H")   # The 'H' should be treated as NA

## expectedOutput
[1] Yes  Yes  Yes  No   No   <NA>
Levels: Yes No  # <~~ NOTICE ONLY **TWO** LEVELS

One option is of course to clean the strings before hand using sub and friends.

Another method, is to allow duplicate label, then drop them

## Duplicate levels ==> "Warning: deprecated"
x.f <- factor(x, levels=c("Y", "Yes", "No", "N"), labels=c("Yes", "Yes", "No", "No"))

## the above line can be wrapped in either of the next two lines
factor(x.f)      
droplevels(x.f) 

However, is there a more effective way?


While I know that the levels and labels arguments should be vectors, I experimented with lists and named lists and named vectors to see what happens Needless to say, none of the following got me any closer to my goal.

  factor(x, levels=list(c("Yes", "Y"), c("No", "N")), labels=c("Yes", "No"))
  factor(x, levels=c("Yes", "No"), labels=list(c("Yes", "Y"), c("No", "N")))

  factor(x, levels=c("Y", "Yes", "No", "N"), labels=c(Y="Yes", Yes="Yes", No="No", N="No"))
  factor(x, levels=c("Y", "Yes", "No", "N"), labels=c(Yes="Y", Yes="Yes", No="No", No="N"))
  factor(x, levels=c("Yes", "No"), labels=c(Y="Yes", Yes="Yes", No="No", N="No"))
4

10 回答 10

83

更新 2:请参阅 Uwe 的答案,该答案显示了新的“tidyverse”方式,该方式正在迅速成为标准。

更新 1:现在确实允许重复标签(但不是级别!)(根据我上面的评论);见蒂姆的回答。

原始答案,但仍然有用且感兴趣:有一个鲜为人知的选项可以将命名列表传递给levels函数,正是为了这个目的。列表的名称应该是所需的级别名称,元素应该是应该重命名的当前名称。有些人(包括 OP,请参阅 Ricardo 对 Tim 的回答的评论)更喜欢这个以方便阅读。

x <- c("Y", "Y", "Yes", "N", "No", "H", NA)
x <- factor(x)
levels(x) <- list("Yes"=c("Y", "Yes"), "No"=c("N", "No"))
x
## [1] Yes  Yes  Yes  No   No   <NA>  <NA>
## Levels: Yes No

levels文档中所述;另见那里的例子。

value:对于'factor'方法,长度至少为'x'级别数的字符串向量,或指定如何重命名级别的命名列表。

这也可以在一行中完成,就像 Marek 在这里所做的那样:https ://stackoverflow.com/a/10432263/210673 ;levels<-这里解释了魔法https://stackoverflow.com/a/10491881/210673

> `levels<-`(factor(x), list(Yes=c("Y", "Yes"), No=c("N", "No")))
[1] Yes  Yes  Yes  No   No   <NA>
Levels: Yes No
于 2013-10-16T17:46:48.003 回答
29

由于问题的标题是清理因子级别(折叠多个级别/标签)forcats为了完整起见,这里也应该提到包。forcats2016 年 8 月出现在 CRAN 上。

有几个方便的功能可用于清理因子水平:

x <- c("Y", "Y", "Yes", "N", "No", "H") 

library(forcats)

将因子级别折叠到手动定义的组中

fct_collapse(x, Yes = c("Y", "Yes"), No = c("N", "No"), NULL = "H")
#[1] Yes  Yes  Yes  No   No   <NA>
#Levels: No Yes

手动更改因子水平

fct_recode(x, Yes = "Y", Yes = "Yes", No = "N", No = "No", NULL = "H")
#[1] Yes  Yes  Yes  No   No   <NA>
#Levels: No Yes

自动重新标记因子水平,必要时折叠

fun <- function(z) {
  z[z == "Y"] <- "Yes"
  z[z == "N"] <- "No"
  z[!(z %in% c("Yes", "No"))] <- NA
  z
}
fct_relabel(factor(x), fun)
#[1] Yes  Yes  Yes  No   No   <NA>
#Levels: No Yes

请注意,它fct_relabel()适用于因子水平,因此它需要一个因子作为第一个参数。其他两个函数fct_collapse()fct_recode()也接受一个字符向量,它是一个未记录的特征。

按首次出现重新排序因子水平

OP给出的预期输出是

[1] Yes  Yes  Yes  No   No   <NA>
Levels: Yes No

此处的级别按照x与默认值不同的顺序排列(?factor因子的级别默认为已排序)。

为了符合预期的输出,这可以通过fct_inorder() 折叠级别之前使用来实现:

fct_collapse(fct_inorder(x), Yes = c("Y", "Yes"), No = c("N", "No"), NULL = "H")
fct_recode(fct_inorder(x), Yes = "Y", Yes = "Yes", No = "N", No = "No", NULL = "H")

现在,两者都以相同的顺序返回预期的输出。

于 2017-04-13T06:27:55.870 回答
8

也许命名向量作为键可能有用:

> factor(unname(c(Y = "Yes", Yes = "Yes", N = "No", No = "No", H = NA)[x]))
[1] Yes  Yes  Yes  No   No   <NA>
Levels: No Yes

这看起来与您上次的尝试非常相似......但是这个有效:-)

于 2013-10-16T17:40:35.303 回答
5

另一种方法是制作一个包含映射的表:

# stacking the list from Aaron's answer
fmap = stack(list(Yes = c("Y", "Yes"), No = c("N", "No")))

fmap$ind[ match(x, fmap$values) ]
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: No Yes

# or...

library(data.table)
setDT(fmap)[x, on=.(values), ind ]
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: No Yes

我更喜欢这种方式,因为它留下了一个易于检查的对象来总结地图;并且 data.table 代码看起来就像该语法中的任何其他联接。


当然,如果您不想要fmap总结更改之类的对象,则可以是“单行”:

library(data.table)
setDT(stack(list(Yes = c("Y", "Yes"), No = c("N", "No"))))[x, on=.(values), ind ]
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: No Yes
于 2017-04-12T20:00:07.020 回答
5

从 R 3.5.0 (2018-04-23) 开始,您可以通过一条清晰简单的线路来做到这一点:

x = c("Y", "Y", "Yes", "N", "No", "H") # The 'H' should be treated as NA

tmp = factor(x, levels= c("Y", "Yes", "N", "No"), labels= c("Yes", "Yes", "No", "No"))
tmp
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: Yes No

1 行,将多个值映射到同一级别,为缺失级别设置 NA” – h/t @Aaron

于 2019-08-27T15:15:43.697 回答
3

我添加这个答案是为了演示在数据框中的特定因素上工作的公认答案,因为这最初对我来说并不明显(尽管它可能应该是)。

levels(df$var1)
# "0" "1" "Z"
summary(df$var1)
#    0    1    Z 
# 7012 2507    8 
levels(df$var1) <- list("0"=c("Z", "0"), "1"=c("1"))
levels(df$var1)
# "0" "1"
summary(df$var1)
#    0    1 
# 7020 2507
于 2018-12-02T02:12:18.833 回答
2

我不知道你的真实用例,但strtrim在这里会有用......

factor( strtrim( x , 1 ) , levels = c("Y" , "N" ) , labels = c("Yes" , "No" ) )
#[1] Yes  Yes  Yes  No   No   <NA>
#Levels: Yes No
于 2013-10-16T17:43:37.790 回答
2

首先让我们注意,在这种特定情况下,我们可以使用部分匹配:

x <- c("Y", "Y", "Yes", "N", "No", "H")
y <- c("Yes","No")
x <- factor(y[pmatch(x,y,duplicates.ok = TRUE)])
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: No Yes

在更一般的情况下,我会选择dplyr::recode

library(dplyr)
x <- c("Y", "Y", "Yes", "N", "No", "H")
y <- c(Y="Yes",N="No")
x <- recode(x,!!!y)
x <- factor(x,y)
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: Yes No

如果起点是一个因素,则略有改变:

x <- factor(c("Y", "Y", "Yes", "N", "No", "H"))
y <- c(Y="Yes",N="No")
x <- recode_factor(x,!!!y)
x <- factor(x,y)
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: Yes No
于 2018-11-08T23:35:57.400 回答
2

类似于@Aaron 的方法,但稍微简单一点的是:

x <- c("Y", "Y", "Yes", "N", "No", "H")
x <- factor(x)
# levels(x)  
# [1] "H"   "N"   "No"  "Y"   "Yes"
# NB: the offending levels are 1, 2, & 4
levels(x)[c(1,2,4)] <- c(NA, "No", "Yes")
x
# [1] Yes  Yes  Yes  No   No   <NA>
# Levels: No Yes
于 2016-06-09T22:10:47.860 回答
1

您可以使用以下函数来组合/折叠多个因素:

combofactor <- function(pattern_vector,
         replacement_vector,
         data) {
 levels <- levels(data)
 for (i in 1:length(pattern_vector))
      levels[which(pattern_vector[i] == levels)] <-
        replacement_vector[i]
 levels(data) <- levels
  data
}

例子:

初始化 x

x <- factor(c(rep("Y",20),rep("N",20),rep("y",20),
rep("yes",20),rep("Yes",20),rep("No",20)))

检查结构

str(x)
# Factor w/ 6 levels "N","No","y","Y",..: 4 4 4 4 4 4 4 4 4 4 ...

使用功能:

x_new <- combofactor(c("Y","N","y","yes"),c("Yes","No","Yes","Yes"),x)

重新检查结构:

str(x_new)
# Factor w/ 2 levels "No","Yes": 2 2 2 2 2 2 2 2 2 2 ...
于 2017-06-17T17:08:21.570 回答