清理因子水平(折叠多个水平/标签)

Cleaning up factor levels (collapsing multiple levels/labels)

提问人:Ricardo Saporta 提问时间:10/17/2013 最后编辑:Will NessRicardo Saporta 更新时间:9/11/2023 访问量:49588

问:

清理包含多个需要折叠的多个水平的因子的最有效(即有效/适当)方法是什么?也就是说,如何将两个或多个因子水平合并为一个。

下面是一个示例,其中两个级别“Yes”和“Y”应折叠为“Yes”,而“No”和“N”应折叠为“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

当然,一种选择是在手前清洁琴弦和朋友。sub

另一种方法是允许重复的标签,然后删除它们

## 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) 

但是,有没有更有效的方法呢?


虽然我知道 and 参数应该是向量,但我尝试了列表、命名列表和命名向量,看看会发生什么 毋庸置疑,以下任何一项都没有让我更接近我的目标。levelslabels

  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"))
因子 R- 常见问题

评论

2赞 Aaron left Stack Overflow 4/26/2018
尚未对此进行测试,但 R 3.5.0 (2018-04-23) 发行说明说“factor(x, levels, labels) 现在允许重复的标签(不是重复的级别!因此,您可以直接将 x 的不同值映射到同一级别。

答:

8赞 A5C1D2H2I1M1N2O1R2T1 10/17/2013 #1

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

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

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

评论

0赞 Ricardo Saporta 10/17/2013
谢谢阿难。这是个好主意。对于我的应用程序,我可能可以取消......这可能会带来蛋糕unname
0赞 Frank 4/13/2017
多年后重温......这将降低未显示的水平,这可能不可取,例如,结果中仅显示“否”水平。x="N"
0赞 A5C1D2H2I1M1N2O1R2T1 4/13/2017
@Frank,通过向步骤添加显式不是很容易解决吗?levelsfactor
1赞 Frank 4/13/2017
啊,很酷的东西:)是的,添加显式级别是有效的,尽管您必须再次键入列表,将列表保存在某个地方或做一些管道或像 eh 一样的功能。c(Y = "Yes", Yes = "Yes", N = "No", No = "No", H = NA) %>% { factor(unname(.[x]), levels = unique(.)) }
1赞 Uwe 4/13/2017
@frank 更酷的东西,还有一个额外的好处,那就是它按照预期的水平排序:、.YesNo
2赞 Simon O'Hanlon 10/17/2013 #2

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

factor( strtrim( x , 1 ) , levels = c("Y" , "N" ) , labels = c("Yes" , "No" ) )
#[1] Yes  Yes  Yes  No   No   <NA>
#Levels: Yes No
90赞 Aaron left Stack Overflow 10/17/2013 #3

更新2:参见Uwe的回答,它显示了新的“整洁”方式,它正在迅速成为标准。

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

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

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;巫术在这里解释 https://stackoverflow.com/a/10491881/210673levels<-

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

评论

0赞 Simon O'Hanlon 10/17/2013
+1 更强大,我想比我的尝试安全得多。
0赞 Ricardo Saporta 10/17/2013
谢谢 Aaron,我喜欢这种方法,因为它至少避免了与此相关的警告,但我仍然对任何更直接的方法感到好奇。例如:如果可以在通话中使用)droplevles(factor(x, ...))levels=<a named list>factor(.)
2赞 Aaron left Stack Overflow 10/17/2013
同意这不能在内部完成是很奇怪的;我不知道有更直接的方法,除了使用像阿难的解决方案或匹配的东西。factor
1赞 asnr 11/17/2015
这也适用于,并且折叠的级别在提供时进行排序,例如产生排序。ordereda = ordered(c(1, 2, 3)); levels(a) = list("3" = 3, "1,2" = c(1, 2))Levels: 3 < 1,2
2赞 gung - Reinstate Monica 6/10/2016 #4

与 @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
5赞 Frank 4/13/2017 #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

评论

0赞 Frank 4/13/2017
另一个例子:franknarf1.github.io/r-tutorial/_book/tables.html#dt-recode
34赞 Uwe 4/13/2017 #6

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

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

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")

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

1赞 Nikhil 6/18/2017 #7

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

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 ...
4赞 moodymudskipper 11/9/2018 #8

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

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
3赞 Karl Baker 12/2/2018 #9

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

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
9赞 tim 8/27/2019 #10

从 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

评论

0赞 Ricardo Saporta 8/29/2019
有用的更新,但命名列表对需要阅读代码的任何人都更友好