【问题标题】:Generate a dummy matrix from multiple factor columns从多个因子列生成一个虚拟矩阵
【发布时间】:2018-03-28 04:20:29
【问题描述】:

我已经在网上搜索过,但没有找到答案。我有一个包含多列的大 data.frame。每列是一个因子变量。

我想转换 data.frame,使得因子变量的每个可能值都是一个变量,如果变量存在于因子列中,则该变量包含“1”,否则为“0”。

这是我的意思的一个例子。

labels <- c("1", "2", "3", "4", "5", "6", "7") 

#create data frame (note, not all factor levels have to be in the columns,
#NA values are possible)
input <- data.frame(ID = c(1, 2, 3), 
Cat1 = factor(c( 4, 1, 1), levels = labels), 
Cat2 = factor(c(2, NA, 4), levels = labels),
Cat3 = factor(c(7, NA, NA), levels = labels))

#the seven factor levels now are the variables of the data.frame
desired_output <- data.frame(ID = c(1, 2, 3),
Dummy1 = c(0, 1, 1),
Dummy2 = c(1, 0, 0),
Dummy3 = c(0, 0, 0),
Dummy4 = c(1, 0, 1),
Dummy5 = c(0, 0, 0),
Dummy6 = c(0, 0, 0),
Dummy7 = c(1, 0, 0))

input
ID Cat1 Cat2 Cat3
1    4    2    7
2    1 <NA> <NA>
3    1    4 <NA>

desired_output
ID Dummy1 Dummy2 Dummy3 Dummy4 Dummy5 Dummy6 Dummy7
1      0      1      0      1      0      0      1
2      1      0      0      0      0      0      0
3      1      0      0      1      0      0      0

我的实际 data.frame 有超过 3000 行和超过 100 个级别的因子。 希望您能帮我将输入转换为所需的输出。

问候 嗖嗖嗖

【问题讨论】:

  • 我认为有些不一致; Cat2 (id 2) 包含 4,Cat3 没有,但你的 desired_output (我认为) 有这些反转。如果我在这里错了,那我真的很困惑。此外,对于您的实际 data.frame,您是否期望得到一个包含 101 列的 data.frame?
  • 抱歉,代码存在一些格式问题。我纠正了它。我希望现在我的意思很清楚。是的,我想要一个包含 101 列的 data.frame。
  • 如果在每一列上使用 lapply?如果因子存在,则为 1,否则为 0?

标签: r dummy-variable


【解决方案1】:

几种方法,即模仿 Gregor 和 Aaron 的答案。

来自亚伦的。 factorsAsStrings=FALSE 保留因子变量,因此使用 dcast 时的所有标签

library(reshape2)
dcast(melt(input, id="ID", factorsAsStrings=FALSE), ID ~ value, drop=FALSE) 
  ID 1 2 3 4 5 6 7 NA
1  1 0 1 0 1 0 0 1  0
2  2 1 0 0 0 0 0 0  2
3  3 1 0 0 1 0 0 0  1

那么你只需要删除最后一列。

来自格雷戈尔的

na.replace <- function(x) replace(x, is.na(x), 0)
options(na.action='na.pass') # this keeps the NA's which are then converted to zero
Reduce("+", lapply(input[-1], function(x) na.replace(model.matrix(~ 0 + x))))
  x1 x2 x3 x4 x5 x6 x7
1  0  1  0  1  0  0  1
2  1  0  0  0  0  0  0
3  1  0  0  1  0  0  0

那你只需要cbindID

【讨论】:

  • 使用匿名函数的另一个好处是之后不需要重命名列。 Reduce 增加了很好的可扩展性。非常好!
  • 确实很好。它工作得很好。非常感谢
【解决方案2】:

一种方法是使用矩阵索引。您有数据指定输出矩阵中的哪些位置应为 1(其余应为零),因此我们将创建一个零矩阵,然后根据您的数据填充 1。为此,您的数据需要位于一个两列矩阵中,第一列是输出的行 (ID),第二列是列。

将输入数据放入长格式,去除缺失,将值转换为与标签匹配的整数,然后根据需要制作矩阵。

in2 <- reshape2::melt(input, id.vars="ID")
in2 <- subset(in2, !is.na(value))
in2$value <- match(in2$value, labels)
in2$variable <- NULL
in2 <- as.matrix(in2)

然后用全零制作新的输出矩阵,并使用该矩阵填充那些。

out <- matrix(0, nrow=nrow(input), ncol=length(labels))
colnames(out) <- labels
rownames(out) <- input$ID
out[in2] <- 1

out
##   1 2 3 4 5 6 7
## 1 0 1 0 1 0 0 1
## 2 1 0 0 0 0 0 0
## 3 1 0 0 1 0 0 0

【讨论】:

    【解决方案3】:

    这是一种使用model.matrix 的方法。我们将缺失值转换为 0,并指定 0 作为因子对比的参考水平。然后我们只需将各个模型矩阵相加并粘贴 ID:

    new_lab = as.character(0:7)
    for (i in 2:4) {
      temp = as.character(input[[i]])
      temp[is.na(temp)] = "0"
      input[[i]] = factor(temp, levels = new_lab)
    }
    
    mm = 
      model.matrix(~ Cat1, data = input) +
      model.matrix(~ Cat2, data = input) +
      model.matrix(~ Cat3, data = input)
    
    mm[, 1] = input$ID
    colnames(mm) = c("ID", paste0("Dummy", 1:(ncol(mm) - 1)))
    mm
    #   ID Dummy1 Dummy2 Dummy3 Dummy4 Dummy5 Dummy6 Dummy7
    # 1  1      0      1      0      1      0      0      1
    # 2  2      1      0      0      0      0      0      0
    # 3  3      1      0      0      1      0      0      0
    # attr(,"assign")
    # [1] 0 1 1 1 1 1 1 1
    # attr(,"contrasts")
    # attr(,"contrasts")$Cat1
    # [1] "contr.treatment"
    

    您可以将结果保留为模型矩阵,将其更改回数据框或其他任何内容。

    【讨论】:

      【解决方案4】:

      这应该适用于您的数据框。在运行 ifelse 语句之前,我将这些值转换为数字。希望它有效:

      # Make dummy df
      Cat1 = factor(c( 4, 1, 1))
      Cat2 = factor(c(2, NA, 4))
      Cat3 = factor(c(7, NA, NA))
      
      df <- data.frame(Cat1,Cat2,Cat3)
      
      # Specify columns
      cols <- c(1:length(df))
      
      # Convert Values To Numeric 
      df[,cols] %<>% lapply(function(x) as.numeric(as.character(x)))
      
      # Perform ifelse. If its NA print 0, else print 1
      df[,cols] %<>% lapply(function(x) ifelse(x == is.na(x) | (x) %in% NA, 0, 1))
      

      基于输入:

        Cat1 Cat2 Cat3
      1    4    2    7
      2    1 <NA> <NA>
      3    1    4 <NA>
      

      输出如下所示:

       Cat1 Cat2 Cat3
      1    1    1    1
      2    1    0    0
      3    1    1    0
      

      【讨论】:

      • 数字转换不是必需的,ifelse 也不是必需的,你可以只做df[, cols] = lapply(df[, cols], function(x) !is.na(x) + 0) - 但这不是 OP 想要的。
      • 打印逻辑表达式 TRUE, FALSE vs the 1,0
      • 抱歉,忘记括号了:df[, cols] = lapply(df[, cols], function(x) (!is.na(x)) + 0)
      • 酷 - 每天学习新东西(尽管我们有点​​离题了!)
      • 是的,这是我可能不应该使用的愚蠢技巧。 as.numeric() 会比 + 0 更清晰。如果考虑性能,ifelse 可能会有点慢,而这会更快一些。在三行上,差异可能是 0.00001 秒。
      猜你喜欢
      • 2018-08-01
      • 2014-12-31
      • 1970-01-01
      • 1970-01-01
      • 2018-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-10
      相关资源
      最近更新 更多