R中函数cut和ifelse之间的奇怪行为

3

我正在使用R处理一个数据框,其中包括一个数值变量和一个字符变量。我的数据框DF长这样(最后一部分添加了dput版本):

   a1    b1
1   a 10.15
2   a 25.10
3   a 32.40
4   a 56.70
5   a 89.02
6   b 90.50
7   b 78.53
8   b 98.12
9   b 34.30
10  b 99.75 

DF 中,变量 a1 是一个分组变量,b1 是一个数值变量。然后出现了问题。我想通过使用 cut 函数并考虑保存在 a1 中的组来创建一个名为 c1 的新变量。因此,我在代码的下一行结合了 ifelse()cut() 两个函数:

DF$c1=ifelse(DF$a1=="a",
                cut(DF$b1,breaks = c(0,25,50,70,max(DF$b1)),right = TRUE,include.lowest = TRUE),
                ifelse(DF$a1=="b",
                       cut(DF$b1,breaks = c(0,50,max(DF$b1)),right = TRUE,include.lowest = TRUE),NA))

这行代码可以正常工作,但是在 c1 中创建的新值存在混淆的结果。与其显示因子,cut()返回整数。然后,我得到了这个结果:

table(DF$c1,exclude=NULL)

   1    2    3    4 <NA> 
   2    6    1    1    0

尽管创建了间隔,分配给的整数会改变结果。但是,如果不使用ifelse,这种情况就不会发生,但在这种情况下,我不同意对组的条件。例如,以下代码行返回此结果:

DF$c1=cut(DF$b1,breaks = c(0,25,50,70,max(DF$b1)),right = TRUE,include.lowest = TRUE)

table(DF$c1,exclude=NULL)

   [0,25]   (25,50]   (50,70] (70,99.8]      <NA> 
        1         3         1         5         0 

我想知道如何解决ifelse()cut()函数之间的行为,因为返回的整数会导致最终结果产生差异。在这个例子中,我只使用了a1变量的两个组,但是我有一个包含许多组的大型数据库。这就是我结合这些函数以获得每个组的不同分割的原因。此外,断点的值可能会发生变化,因此手动包含标签可能会很耗时。这两个函数的组合是否可能返回每个组(因子)的正确标签,而不是整数?我的数据框DFdput()版本如下:

DF<-structure(list(a1 = c("a", "a", "a", "a", "a", "b", "b", "b", 
"b", "b"), b1 = c(10.15, 25.1, 32.4, 56.7, 89.02, 90.5, 78.53, 
98.12, 34.3, 99.75)), .Names = c("a1", "b1"), row.names = c(NA, 
-10L), class = "data.frame")

感谢您的帮助!
3个回答

4
问题在于 cut() 函数输出的是一个因子(factor),但由于它们拥有不同的级别(levels),它们会被强制转换为整数。解决方法可能是在 cut() 函数外层套上 as.character(),这样就可以保留级别以进行强制转换,并将整个输出用 factor() 包裹:
DF$c1=factor(ifelse(DF$a1=="a",
             as.character(cut(DF$b1,breaks = c(0,25,50,70,max(DF$b1)),right = TRUE,include.lowest = TRUE)),
             ifelse(DF$a1=="b",
                    as.character(cut(DF$b1,breaks = c(0,50,max(DF$b1)),right = TRUE,include.lowest = TRUE)),NA)))

DF

   a1    b1        c1
1   a 10.15    [0,25]
2   a 25.10   (25,50]
3   a 32.40   (25,50]
4   a 56.70   (50,70]
5   a 89.02 (70,99.8]
6   b 90.50 (50,99.8]
7   b 78.53 (50,99.8]
8   b 98.12 (50,99.8]
9   b 34.30    [0,50]
10  b 99.75 (50,99.8]

是的,但是你如何对因子进行排序? - Alfredo G Marquez
你的意思是什么?是对数据框中的行进行排序,还是对水平进行排序? - scoa
当你转换为字符时,因子断点不会正确对齐,所以当你通过c1进行计算时,因子的顺序不正确。或者当你使用因子作为x变量进行图形绘制时,顺序完全错误。 - Alfredo G Marquez
好的。您需要通过向 factor() 添加一个 levels 参数来重新调整级别。由于我们无法确定确切的级别是什么,因此您需要进行一些尝试,但只需添加 DF$c1 <- factor(DF$c1, levels = sort(levels(DF$c1))) 就可以解决问题。 - scoa

2

@scoa 是正确的;你正试图将两个不同水平的因素结合在一起,这样你的结果就被强制转换为整数并且失去了水平。下面提供另一种更小的形式来解决这个问题,它会更具可扩展性。

首先,创建一个命名列表,其中包含所有的中断点:

breaks <- list('a' = c(0, 25, 50, 70, max(DF$b1)), 'b' = c(0, 50, max(DF$b1)))
breaks

> $a
>     0 25 50 70 99.75 
> $b
>     0 50 99.75 

然后使用 unlist(list(some, factors))(或在这种情况下,lapply),它可以整洁地合并因子,并保留所有级别。 (这有点神奇; 这是一个那些内置功能之一,它真的不太明显。)

DF$c1 <- unlist(lapply(1:length(breaks), 
                   function(x){cut(DF[DF$a1 == names(breaks[x]), 'b1'], 
                                   breaks = breaks[[x]], 
                                   right = TRUE, 
                                   include.lowest = TRUE)}
                   ))
DF

>    a1    b1        c1
> 1   a 10.15    [0,25]
> 2   a 25.10   (25,50]
> 3   a 32.40   (25,50]
> 4   a 56.70   (50,70]
> 5   a 89.02 (70,99.8]
> 6   b 90.50 (50,99.8]
> 7   b 78.53 (50,99.8]
> 8   b 98.12 (50,99.8]
> 9   b 34.30    [0,50]
> 10  b 99.75 (50,99.8]

这只需要两行代码,而且在更大、更复杂的数据集上应该是稳健的。


2
这不是你问题的直接答案,而是一种整体任务的替代方法。
因为你有“一个包含许多组并且每个组都有不同截断值的大型数据库”,所以我认为使用许多嵌套的ifelse代码很快就会变得混乱。也许是个人口味问题,但我认为,如果你将每个组的断点储存在单独的表中,那么代码将更易读和维护。
以下是使用data.table的方法:
library(data.table)
dt_brk <- data.table(grp = c("a", "a", "a", "a", "a", "b", "b", "b"),
                     brk = c(0, 25, 50, 70, Inf, 0, 50, Inf))

请注意,我使用Inf作为分段的上限,而不是max(your-values) 我们使用setDT将您的数据框“DF”转换为data.table。然后,对于“a1”的每个级别(by = a1),我们使用“dt_brk”的breakscut“b1”,其中“grp”等于“a1”(dt_brk[grp == a1, brk])。
setDT(DF)[, c1 := as.character(cut(b1, breaks = dt_brk[grp == a1, brk])), by = a1]

DF
#     a1    b1       c1
# 1:   a 10.15   (0,25]
# 2:   a 25.10  (25,50]
# 3:   a 32.40  (25,50]
# 4:   a 56.70  (50,70]
# 5:   a 89.02 (70,Inf]
# 6:   b 90.50 (50,Inf]
# 7:   b 78.53 (50,Inf]
# 8:   b 98.12 (50,Inf]
# 9:   b 34.30   (0,50]
# 10:  b 99.75 (50,Inf]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接