【问题标题】:Automatically expanding an R factor into a collection of 1/0 indicator variables for every factor level自动将 R 因子扩展为每个因子水平的 1/0 指标变量的集合
【发布时间】:2011-06-30 05:41:02
【问题描述】:

我有一个 R 数据框,其中包含一个我想“扩展”的因子,因此对于每个因子级别,在一个新数据框中都有一个关联的列,其中包含一个 1/0 指示符。例如,假设我有:

df.original <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c(1,2,3,4))

我想要:

df.desired  <- data.frame(foo = c(1,1,0,0), bar=c(0,0,1,1), ham=c(1,2,3,4))

因为对于某些需要完全数字数据框的分析(例如,主成分分析),我认为可能会内置此功能。编写一个函数来执行此操作应该不会太难,但我可以预见与列名相关的一些挑战,如果已经存在某些东西,我宁愿使用它。

【问题讨论】:

    标签: r


    【解决方案1】:

    使用model.matrix函数:

    model.matrix( ~ Species - 1, data=iris )
    

    【讨论】:

    • 我可以补充一点,这种方法比我使用cast 快得多。
    • @GregSnow 我查看了?formula?model.matrix 的第二段,但不清楚(可能只是我对矩阵代数和模型公式的知识缺乏深度)。在深入挖掘之后,我已经能够收集到 -1 只是指定不包括“拦截”列。如果您忽略 -1,您将在输出中看到一个截距列 1,其中一个二进制列被忽略。您可以根据其他列的值为 0 的行来查看省略列的哪些值为 1。文档似乎很神秘——还有其他好的资源吗?
    • @RyanChase,有很多关于 R/S 的在线教程和书籍(一些在 r-project.org 网页上有简要说明)。我自己对 S 和 R 的学习相当不拘一格(而且时间很长),所以我不是最好就当前书籍/教程如何吸引初学者发表意见。然而,我是实验的粉丝。在新的 R 会话中尝试一些东西可能非常有启发性并且不危险(发生在我身上的最糟糕的事情是崩溃 R,而且这种情况很少发生,这会导致 R 的改进)。 Stackoverflow 是了解发生了什么的好资源。
    • 而如果要转换所有因子列,可以使用:model.matrix(~., data=iris)[,-1]
    • @colin,不是完全自动的,但您可以在使用na.exclude 后使用naresid 将缺失值放回原处。一个简单的例子:tmp &lt;- data.frame(x=factor(c('a','b','c',NA,'a'))); tmp2 &lt;- na.exclude(tmp); tmp3 &lt;- model.matrix( ~x-1, tmp2); tmp4 &lt;- naresid(attr(tmp2,'na.action'), tmp3)
    【解决方案2】:

    如果您的数据框仅由因子组成(或者您正在处理所有因子的变量子集),您还可以使用 ade4 包中的 acm.disjonctif 函数:

    R> library(ade4)
    R> df <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c("red","blue","green","red"))
    R> acm.disjonctif(df)
      eggs.bar eggs.foo ham.blue ham.green ham.red
    1        0        1        0         0       1
    2        0        1        1         0       0
    3        1        0        0         1       0
    4        1        0        0         0       1
    

    不完全是您描述的情况,但它也很有用......

    【讨论】:

    • 谢谢,这对我帮助很大,因为它使用的内存比 model.matrix 少!
    • 我喜欢变量命名的方式;当它们应该(恕我直言)只是逻辑时,我不喜欢它们作为存储饥饿的数字返回。
    【解决方案3】:

    使用reshape2 包的快捷方式:

    require(reshape2)
    
    > dcast(df.original, ham ~ eggs, length)
    
    Using ham as value column: use value_var to override.
      ham bar foo
    1   1   0   1
    2   2   0   1
    3   3   1   0
    4   4   1   0
    

    请注意,这会精确生成您想要的列名。

    【讨论】:

    • 好。但要小心火腿的副本。比如说,d
    • @Kohske,是的,但我假设 ham 是唯一的行 ID。如果ham 不是唯一ID,则必须使用其他一些唯一ID(或创建一个虚拟ID)并使用它代替ham。将分类标签转换为二进制指示符仅对唯一 ID 有意义。
    【解决方案4】:

    可能虚拟变量与您想要的相似。 那么,model.matrix 就很有用了:

    > with(df.original, data.frame(model.matrix(~eggs+0), ham))
      eggsbar eggsfoo ham
    1       0       1   1
    2       0       1   2
    3       1       0   3
    4       1       0   4
    

    【讨论】:

      【解决方案5】:

      来自nnet 包的后期条目class.ind

      library(nnet)
       with(df.original, data.frame(class.ind(eggs), ham))
        bar foo ham
      1   0   1   1
      2   0   1   2
      3   1   0   3
      4   1   0   4
      

      【讨论】:

        【解决方案6】:

        刚刚遇到这个旧线程,并认为我会添加一个函数,该函数利用 ade4 获取由因子和/或数字数据组成的数据帧,并返回一个包含因子作为虚拟代码的数据帧。

        dummy <- function(df) {  
        
            NUM <- function(dataframe)dataframe[,sapply(dataframe,is.numeric)]
            FAC <- function(dataframe)dataframe[,sapply(dataframe,is.factor)]
        
            require(ade4)
            if (is.null(ncol(NUM(df)))) {
                DF <- data.frame(NUM(df), acm.disjonctif(FAC(df)))
                names(DF)[1] <- colnames(df)[which(sapply(df, is.numeric))]
            } else {
                DF <- data.frame(NUM(df), acm.disjonctif(FAC(df)))
            }
            return(DF)
        } 
        

        让我们试试吧。

        df <-data.frame(eggs = c("foo", "foo", "bar", "bar"), 
                    ham = c("red","blue","green","red"), x=rnorm(4))     
        dummy(df)
        
        df2 <-data.frame(eggs = c("foo", "foo", "bar", "bar"), 
                    ham = c("red","blue","green","red"))  
        dummy(df2)
        

        【讨论】:

          【解决方案7】:

          这是一种更清晰的方法。我使用 model.matrix 创建虚拟布尔变量,然后将其合并回原始数据帧。

          df.original <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c(1,2,3,4))
          df.original
          #   eggs ham
          # 1  foo   1
          # 2  foo   2
          # 3  bar   3
          # 4  bar   4
          
          # Create the dummy boolean variables using the model.matrix() function.
          > mm <- model.matrix(~eggs-1, df.original)
          > mm
          #   eggsbar eggsfoo
          # 1       0       1
          # 2       0       1
          # 3       1       0
          # 4       1       0
          # attr(,"assign")
          # [1] 1 1
          # attr(,"contrasts")
          # attr(,"contrasts")$eggs
          # [1] "contr.treatment"
          
          # Remove the "eggs" prefix from the column names as the OP desired.
          colnames(mm) <- gsub("eggs","",colnames(mm))
          mm
          #   bar foo
          # 1   0   1
          # 2   0   1
          # 3   1   0
          # 4   1   0
          # attr(,"assign")
          # [1] 1 1
          # attr(,"contrasts")
          # attr(,"contrasts")$eggs
          # [1] "contr.treatment"
          
          # Combine the matrix back with the original dataframe.
          result <- cbind(df.original, mm)
          result
          #   eggs ham bar foo
          # 1  foo   1   0   1
          # 2  foo   2   0   1
          # 3  bar   3   1   0
          # 4  bar   4   1   0
          
          # At this point, you can select out the columns that you want.
          

          【讨论】:

            【解决方案8】:

            我需要一个更灵活的“分解”因子的函数,并基于 ade4 包中的 acm.disjonctif 函数制作了一个函数。 这允许您选择分解的值,即 acm.disjonctif 中的 0 和 1。它只会分解具有“很少”水平的因子。保留数字列。

            # Function to explode factors that are considered to be categorical,
            # i.e., they do not have too many levels.
            # - data: The data.frame in which categorical variables will be exploded.
            # - values: The exploded values for the value being unequal and equal to a level.
            # - max_factor_level_fraction: Maximum number of levels as a fraction of column length. Set to 1 to explode all factors.
            # Inspired by the acm.disjonctif function in the ade4 package.
            explode_factors <- function(data, values = c(-0.8, 0.8), max_factor_level_fraction = 0.2) {
              exploders <- colnames(data)[sapply(data, function(col){
                  is.factor(col) && nlevels(col) <= max_factor_level_fraction * length(col)
                })]
              if (length(exploders) > 0) {
                exploded <- lapply(exploders, function(exp){
                    col <- data[, exp]
                    n <- length(col)
                    dummies <- matrix(values[1], n, length(levels(col)))
                    dummies[(1:n) + n * (unclass(col) - 1)] <- values[2]
                    colnames(dummies) <- paste(exp, levels(col), sep = '_')
                    dummies
                  })
                # Only keep numeric data.
                data <- data[sapply(data, is.numeric)]
                # Add exploded values.
                data <- cbind(data, exploded)
              }
              return(data)
            }
            

            【讨论】:

              【解决方案9】:

              (问题是10岁,但为了完整起见......)

              fixest 包中的函数 i() 正是这样做的。

              除了从类似因子的变量创建设计矩阵之外,您还可以非常轻松地即时做两件额外的事情:

              • 分箱值(使用参数“bin”),
              • 排除一些因子值(使用参数ref)。

              而且由于它是为此任务而设计的,如果您的变量恰好是数字,则不需要用 factor(x_num) 包装它(与 model.matrix 解决方案相反)。

              这是一个例子:

              library(fixest)
              data(airquality)
              table(airquality$Month)
              #>  5  6  7  8  9 
              #> 31 30 31 31 30
              
              head(i(airquality$Month))
              #>      5 6 7 8 9
              #> [1,] 1 0 0 0 0
              #> [2,] 1 0 0 0 0
              #> [3,] 1 0 0 0 0
              #> [4,] 1 0 0 0 0
              #> [5,] 1 0 0 0 0
              #> [6,] 1 0 0 0 0
              
              #
              # Binning (check out the help, there are many many ways to bin)
              #
              
              colSums(i(airquality$Month, bin = 5:6)))
              #>  5  7  8  9 
              #> 61 31 31 30 
              
              #
              # References
              #
              
              head(i(airquality$Month, ref = c(6, 9)), 3)
              #>      5 7 8
              #> [1,] 1 0 0
              #> [2,] 1 0 0
              #> [3,] 1 0 0
              

              这是一个扩展所有非数字变量的小包装器(默认情况下):

              library(fixest)
              
              # data: data.frame
              # var: vector of variable names // if missing, all non numeric variables
              # no argument checking
              expand_factor = function(data, var){
                  
                  if(missing(var)){
                      var = names(data)[!sapply(data, is.numeric)]
                      if(length(var) == 0) return(data)
                  }
                  
                  data_list = unclass(data)
                  new = lapply(var, \(x) i(data_list[[x]]))
                  data_list[names(data_list) %in% var] = new
                  
                  do.call("cbind", data_list)
              }
              
              my_data = data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c(1,2,3,4))
              
              expand_factor(my_data)
              #>      bar foo ham
              #> [1,]   0   1   1
              #> [2,]   0   1   2
              #> [3,]   1   0   3
              #> [4,]   1   0   4
              

              最后,对于那些想知道的人来说,时间相当于model.matrix 解决方案。

              library(microbenchmark)
              my_data = data.frame(x = as.factor(sample(100, 1e6, TRUE)))
              
              microbenchmark(mm = model.matrix(~x, my_data),
                             i = i(my_data$x), times = 5)
              #> Unit: milliseconds
              #>  expr      min       lq     mean   median       uq      max neval
              #>    mm 155.1904 156.7751 209.2629 182.4964 197.9084 353.9443     5
              #>     i 154.1697 154.7893 159.5202 155.4166 163.9706 169.2550     5
              
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-04-12
                • 1970-01-01
                • 2020-11-13
                相关资源
                最近更新 更多