【问题标题】:R: Generate new data frame column based on a mapping of multiple (logical) columnsR:基于多个(逻辑)列的映射生成新的数据框列
【发布时间】:2013-09-28 10:40:04
【问题描述】:

在帖子底部澄清“地图”或“排序”

假设我们有一个包含多个逻辑列的数据框,以及一个“映射”,对于这些逻辑列的特定组合,它会给出一个值。

计算与数据框每一行关联的值的最佳/最有效方法是什么。

我有以下三种可能的解决方案:ifelse()、merge() 和 table()。我将不胜感激任何 cmets 或替代解决方案。

[抱歉,一篇比较长的帖子]

考虑以下示例数据框:

# Generate example
#N <- 15
#Data <- data.frame(A=sample(c(FALSE,TRUE),N,TRUE,c(8,2)),
#               B=sample(c(FALSE,TRUE),N,TRUE,c(6,4)),
#               C=sample(c(FALSE,TRUE),N,TRUE,c(7,3)),
#               D=sample(c(FALSE,TRUE),N,TRUE,c(7,3)))

# Specific example used in this question
Data <- structure(list(A = c(FALSE, FALSE, FALSE, TRUE, FALSE, FALSE,
  FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE),
  B = c(FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE,
  FALSE, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE), C = c(FALSE,
  FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE,
  FALSE, TRUE, FALSE, FALSE, FALSE), D = c(TRUE, FALSE, FALSE,
  FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE,
  FALSE, TRUE, FALSE)), .Names = c("A", "B", "C", "D"), 
  class = "data.frame", row.names   = c(NA,-15L))


        A     B     C     D
 1  FALSE FALSE FALSE  TRUE
 2  FALSE FALSE FALSE FALSE
 3  FALSE  TRUE FALSE FALSE
 4   TRUE FALSE FALSE FALSE
 5  FALSE FALSE FALSE FALSE
 6  FALSE  TRUE FALSE FALSE
 7  FALSE  TRUE FALSE FALSE
 8  FALSE FALSE FALSE FALSE
 9  FALSE FALSE FALSE FALSE
 10  TRUE FALSE  TRUE  TRUE
 11 FALSE  TRUE FALSE  TRUE
 12 FALSE FALSE  TRUE FALSE
 13 FALSE  TRUE FALSE FALSE
 14 FALSE FALSE FALSE  TRUE
 15 FALSE FALSE FALSE FALSE

结合以下地图:

# A -> B -> C                                                                                                                                                                                                        
#   \_  D  

### To clarify, if someone has both B & D TRUE (with C FALSE), D is higher than B
### i.e. there can be no ties

这定义了逻辑列的顺序。我想要的最终值是每行中的“最高”列。这样,如果列 C 为真,我们总是返回 C。只有当 C 为 FALSE 且 D 为真时,我们才返回“D”。

最简单的做法是嵌套 ifelse 语句:

Data$Highest <- with(Data, ifelse( C, "C",
                                  ifelse( D, "D",
                                         ifelse( B, "B",
                                                ifelse( A, "A", "none")
                                                )
                                         )
                                  )
                     )

但该代码难以阅读/维护,并且对于具有多列的复杂排序变得非常复杂。

我可以快速生成从列组合到所需输出的映射:

Map <- expand.grid( lapply( lapply( Data[c("A","B","C","D")], unique ), sort ) )

Map$Value <- factor(NA, levels=c("A","B","C","D","none"))
Map$Value[which(Map$A)] <- "A"
Map$Value[which(Map$B)] <- "B"
Map$Value[which(Map$D)] <- "D"
Map$Value[which(Map$C)] <- "C"
Map$Value[which(is.na(Map$Value))] <- "none"

        A     B     C     D Value
 1  FALSE FALSE FALSE FALSE  none
 2   TRUE FALSE FALSE FALSE     A
 3  FALSE  TRUE FALSE FALSE     B
 4   TRUE  TRUE FALSE FALSE     B
 5  FALSE FALSE  TRUE FALSE     C
 6   TRUE FALSE  TRUE FALSE     C
 7  FALSE  TRUE  TRUE FALSE     C
 8   TRUE  TRUE  TRUE FALSE     C
 9  FALSE FALSE FALSE  TRUE     D
 10  TRUE FALSE FALSE  TRUE     D
 11 FALSE  TRUE FALSE  TRUE     D
 12  TRUE  TRUE FALSE  TRUE     D
 13 FALSE FALSE  TRUE  TRUE     C
 14  TRUE FALSE  TRUE  TRUE     C
 15 FALSE  TRUE  TRUE  TRUE     C
 16  TRUE  TRUE  TRUE  TRUE     C

可以和merge()一起使用:

merge( Data, Map, by=c("A","B","C","D"), all.y=FALSE )

        A     B     C     D Highest Value
 1  FALSE FALSE FALSE FALSE    none  none
 2  FALSE FALSE FALSE FALSE    none  none
 3  FALSE FALSE FALSE FALSE    none  none
 4  FALSE FALSE FALSE FALSE    none  none
 5  FALSE FALSE FALSE FALSE    none  none
 6  FALSE FALSE FALSE  TRUE       D     D
 7  FALSE FALSE FALSE  TRUE       D     D
 8  FALSE FALSE  TRUE FALSE       C     C
 9  FALSE  TRUE FALSE FALSE       B     B
 10 FALSE  TRUE FALSE FALSE       B     B
 11 FALSE  TRUE FALSE FALSE       B     B
 12 FALSE  TRUE FALSE FALSE       B     B
 13 FALSE  TRUE FALSE  TRUE       D     D
 14  TRUE FALSE FALSE FALSE       A     A
 15  TRUE FALSE  TRUE  TRUE       C     C

但是,merge() 函数当前不保留行顺序。不过有办法解决这个问题。

我的最终想法是使用一个 4 维表格,其中包含对应于地图的字符条目:

Map2 <- table( lapply( Data[c("A","B","C","D")], unique ) )

Map2[] <- "none"
Map2["TRUE",,,] <- "A"
Map2[,"TRUE",,] <- "B"
Map2[,,,"TRUE"] <- "D"
Map2[,,"TRUE",] <- "C"

但我发现上面的行不清楚(也许有更好的方法来制作表格?我认为可以将 Map 变成 Map2,但我看不到如何)。

然后我们使用矩阵索引来拉出对应的值:

BOB <- as.matrix(Data[c("A","B","C","D")])
cBOB <- matrix(as.character(BOB),nrow=NROW(BOB),ncol=NCOL(BOB),dimnames=dimnames(BOB))

Data$Alt.Highest <- Map2[cBOB]


        A     B     C     D Highest Alt.Highest
 1  FALSE FALSE FALSE  TRUE       D           D
 2  FALSE FALSE FALSE FALSE    none        none
 3  FALSE  TRUE FALSE FALSE       B           B
 4   TRUE FALSE FALSE FALSE       A           A
 5  FALSE FALSE FALSE FALSE    none        none
 6  FALSE  TRUE FALSE FALSE       B           B
 7  FALSE  TRUE FALSE FALSE       B           B
 8  FALSE FALSE FALSE FALSE    none        none
 9  FALSE FALSE FALSE FALSE    none        none
 10  TRUE FALSE  TRUE  TRUE       C           C
 11 FALSE  TRUE FALSE  TRUE       D           D
 12 FALSE FALSE  TRUE FALSE       C           C
 13 FALSE  TRUE FALSE FALSE       B           B
 14 FALSE FALSE FALSE  TRUE       D           D
 15 FALSE FALSE FALSE FALSE    none        none

所以综上所述,有没有更好的方法来实现这种“映射”类型的操作以及对这些方法的效率有什么想法?

对于我感兴趣的应用程序,我有九列和一个包含三个分支的排序图,以应用于 3000 行。本质上,我正在尝试基于笨拙的数据存储格式构建一个因素。因此,代码的清晰性是我的第一要务,速度/内存效率是我的第二要务。

提前致谢。

附:也欢迎提出修改问题标题的建议。

澄清

真正的应用涉及一个包含 9 个问题的问卷,询问受访者是否达到了给定的教育/资格水平。这些是二进制是/否响应。

我们想要的是生成一个新变量“获得的最高资格”。

问题在于这 9 个级别不能形成一个简单的堆栈。例如,无需上大学即可获得专业资格(尤其是年长的受访者)。

我们设计了一个“地图”或“排序”,这样,对于每个响应组合,我们都有一个“最高资格”(这个排序是主观的,因此希望能够简单地实施替代排序)。

# So given the nine responses: A, B, C, D, E, F, G, H, I
# we define an ordering as:
# D > C > B > A
# F > E
# E > A
# E == B
# I > H
# H == B
# G == B

# which has a set of order relationships. There is equality in this example

#    A -> B -> C -> D
#      \_ E -> F
#      \_ H -> I
#      \_ G

# 0  1    2    3    4     
# We could then have five levels in out final 'highest' ordered factor: none, 1, 2, 3, 4   

# Or we could decide to add more levels to break certain ties.

R 问题是,给定一个将逻辑列组合映射到“最高实现”值的排序(以及如何处理平局)。如何最好地在 R 中实现这一点。

【问题讨论】:

  • 仅供参考,“地图”已经是 R 函数。
  • @Frank,好地方。所以“映射”可能不是我想要的词。
  • 不,我猜是;我只是说你不想给已经在使用的 R 对象命名。
  • @Frank...您当然是对的,这是不好的做法。一开始我误解了你的意思。我觉得有趣的是,在使用 R 5 年多之后,我仍然在base 中找到我从未听说过的功能。也破坏了我总是用大写字母开始用户对象以避免这种情况的计划。
  • 是的,我很惊讶地发现,R 中的任何内容也都以大写字母开头。如果您查看?Map,您会看到更多。也许他们想使用我听说过的 Map-Reduce 方法中的确切名称。

标签: r dataframe


【解决方案1】:

我想我可能不理解您的“订购”概念。如果是不允许平局的情况,并且您确切地知道每个字母与所有其他字母的比较,这意味着存在严格的排序,可以将其分解为从最高到最低的简单向量。如果这不是真的,那么也许你可以举一个更难的例子。如果这是真的,那么你可以很容易地编写这样的代码:

order<-c('C','D','B','A')
reordered.Data<-Data[order]
Data$max<-
  c(order,'none')[apply(reordered.Data,1,function(x) min(which(c(x,TRUE))))]

#        A     B     C     D  max
# 1  FALSE FALSE FALSE  TRUE    D
# 2  FALSE FALSE FALSE FALSE none
# 3  FALSE  TRUE FALSE FALSE    B
# 4   TRUE FALSE FALSE FALSE    A
# 5  FALSE FALSE FALSE FALSE none
# 6  FALSE  TRUE FALSE FALSE    B
# 7  FALSE  TRUE FALSE FALSE    B
# 8  FALSE FALSE FALSE FALSE none
# 9  FALSE FALSE FALSE FALSE none
# 10  TRUE FALSE  TRUE  TRUE    C
# 11 FALSE  TRUE FALSE  TRUE    D
# 12 FALSE FALSE  TRUE FALSE    C
# 13 FALSE  TRUE FALSE FALSE    B
# 14 FALSE FALSE FALSE  TRUE    D
# 15 FALSE FALSE FALSE FALSE none

我想我现在理解了您的“订购”概念。但是,我认为您一开始可以放心地忽略它。例如,GB 处于同一级别。但是GB 永远不会被比较;您只能拥有{B,E,H,G} 之一。因此,只要每个“分支”的顺序正确,就没有关系。如果您为新分支提供了一些示例数据,我可以对此进行测试,但请尝试以下操作:

order<-c(D,C,F,I,B,E,H,G,A)
levs<-c(4,3,3,3,2,2,2,2,1)
names(levs)<-order
reordered.Data<-Data[order]
Data$max<-
  c(order,'none')[apply(reordered.Data,1,function(x) min(which(c(x,TRUE))))]
Data$lev<-levs[Data$max]

【讨论】:

  • 已尝试澄清问题中的“排序”。但是,我确实喜欢你的回答......如果在我的情况下没有关系,那就特别干净 - 更一般地说,如果有关系,这种方法是行不通的。让它变得更好的是,要更改“地图”,您只需更改 order 向量。
  • @Simon 检查我的更新。我认为您可以放心地忽略“等式”,因为永远不会比较这些值。
【解决方案2】:

这是一个 data.table 方法:

require(data.table)
DT <- data.table(Data)

valord <- c('none','A','B','D','C')
DT[,val:={
    vals <- c('none'=TRUE,unlist(.SD))[valord]
    names(vals)[max(which(vals))]
},by=1:nrow(DT)]

结果是

        A     B     C     D  val
 1: FALSE FALSE FALSE  TRUE    D
 2: FALSE FALSE FALSE FALSE none
 3: FALSE  TRUE FALSE FALSE    B
 4:  TRUE FALSE FALSE FALSE    A
 5: FALSE FALSE FALSE FALSE none
 6: FALSE  TRUE FALSE FALSE    B
 7: FALSE  TRUE FALSE FALSE    B
 8: FALSE FALSE FALSE FALSE none
 9: FALSE FALSE FALSE FALSE none
10:  TRUE FALSE  TRUE  TRUE    C
11: FALSE  TRUE FALSE  TRUE    D
12: FALSE FALSE  TRUE FALSE    C
13: FALSE  TRUE FALSE FALSE    B
14: FALSE FALSE FALSE  TRUE    D
15: FALSE FALSE FALSE FALSE none

如果你跑了

class(DT) # [1] "data.table" "data.frame"

您会看到这是一个 data.frame,就像您的“数据”一样,可以对其应用相同的功能。

【讨论】:

  • 正如所写,这并不完全正确。鉴于“排序”C > D,所以val 列中应该有两个“C”。我认为使用@nograpes 答案(即重新排序列)可以完成这项工作。但是,我对 data.table 不是很熟悉。所以我不会因为代码的清晰度而降低这个答案(只是因为我不明白)。
  • @Simon 好的,谢谢。我没有意识到你没有按照字母顺序。就平等而言,我建议提出一个新的 SO 问题。我的方法是创建一个新变量并将其映射到您提到的那些“级别”,如下所示:stackoverflow.com/questions/8057113/…
  • 没关系。我完全接受了另一个答案,因为我(目前)不熟悉 data.table。不过,感谢您的回答,当我访问 data.table 时需要考虑一些事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-10
  • 1970-01-01
  • 2022-07-18
  • 2021-07-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多