【问题标题】:将 data.frame 列从因子转换为字符
【发布时间】:2011-02-20 12:23:04
【问题描述】:

我有一个数据框。我们就叫他bob

> head(bob)
                 phenotype                         exclusion
GSM399350 3- 4- 8- 25- 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-
GSM399351 3- 4- 8- 25- 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-
GSM399352 3- 4- 8- 25- 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-
GSM399353 3- 4- 8- 25+ 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-
GSM399354 3- 4- 8- 25+ 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-
GSM399355 3- 4- 8- 25+ 44+ 11b- 11c- 19- NK1.1- Gr1- TER119-

我想连接这个数据框的行(这将是另一个问题)。但是看:

> class(bob$phenotype)
[1] "factor"

Bob 的列是因子。所以,例如:

> as.character(head(bob))
[1] "c(3, 3, 3, 6, 6, 6)"       "c(3, 3, 3, 3, 3, 3)"      
[3] "c(29, 29, 29, 30, 30, 30)"

我还没有开始理解这个,但我猜这些是bob 的柱子(国王宫廷的)因子水平的索引?不是我需要的。

奇怪的是我可以手动浏览bob的列,然后做

bob$phenotype <- as.character(bob$phenotype)

效果很好。而且,在一些输入之后,我可以得到一个 data.frame,它的列是字符而不是因子。所以我的问题是:我怎样才能自动做到这一点?如何将具有因子列的 data.frame 转换为具有字符列的 data.frame 而无需手动遍历每一列?

额外问题:为什么手动方法有效?

【问题讨论】:

  • 如果你能让问题可重现会很好,所以包括bob的结构。

标签: r dataframe


【解决方案1】:

只关注马特和德克。如果您想在不更改全局选项的情况下重新创建现有数据框,可以使用 apply 语句重新创建它:

bob <- data.frame(lapply(bob, as.character), stringsAsFactors=FALSE)

这会将所有变量转换为“字符”类,如果您只想转换因子,请参阅Marek's solution below

正如@hadley 指出的那样,以下内容更简洁。

bob[] <- lapply(bob, as.character)

在这两种情况下,lapply 都会输出一个列表;但是,由于 R 的神奇属性,在第二种情况下使用 [] 会保留 bob 对象的 data.frame 类,从而无需使用 as.data.frame 转换回 data.frame论据stringsAsFactors = FALSE

【讨论】:

  • Shane,这也会将数字列转换为字符。
  • @Dirk:确实如此,尽管目前尚不清楚这是否是个问题。显然,预先正确地创建事物是最好的解决方案。我认为跨数据框自动转换数据类型并不容易。一种选择是使用上述方法,然后在将所有内容转换为character 后使用type.convert,然后再次将factors 重新转换回character
  • 这似乎丢弃了行名。
  • @piccolbo 您在示例中使用的是bob[] &lt;- 还是bob &lt;- ?;第一个保留data.frame;第二个将 data.frame 更改为列表,删除行名。我会更新答案
  • 只使用匿名函数将因子列转换为字符的变体:iris[] &lt;- lapply(iris, function(x) if (is.factor(x)) as.character(x) else {x})
【解决方案2】:

仅替换因子:

i <- sapply(bob, is.factor)
bob[i] <- lapply(bob[i], as.character)

在包dplyrin version 0.5.0 new function mutate_if was introduced:

library(dplyr)
bob %>% mutate_if(is.factor, as.character) -> bob

...和in version 1.0.0 was replaced by across:

library(dplyr)
bob %>% mutate(across(where(is.factor), as.character)) -> bob

Package purrr from RStudio 提供了另一种选择:

library(purrr)
bob %>% modify_if(is.factor, as.character) -> bob

【讨论】:

  • 不适合我,很遗憾。不知道为什么。可能是因为我有 colnames?
  • @mohawkjohn 不应该是问题。出现错误或结果与预期不符?
  • 注意:purrr 行返回一个列表,而不是 data.frame!
  • 如果你已经有一个icolnames() 的向量,这也有效。
  • @RoyalTS 从一开始就应该是modify_if 而不是map_if :)
【解决方案3】:

全局选项

stringsAsFactors: data.frame 和 read.table 参数的默认设置。

可能是您想在启动文件中设置为 FALSE 的内容(例如 ~/.Rprofile)。请看help(options)

【讨论】:

  • 这样做的问题是,当您在缺少 .Rprofile 文件的环境中执行代码时,您会遇到错误!
  • 我倾向于在脚本的开头调用它,而不是在 .Rprofile 中设置。
【解决方案4】:

如果您了解因子的存储方式,则可以避免使用基于应用的函数来完成此操作。这并不意味着应用解决方案效果不佳。

因子的结构是与“级别”列表相关联的数字索引。如果将因子转换为数字,则可以看到这一点。所以:

> fact <- as.factor(c("a","b","a","d")
> fact
[1] a b a d
Levels: a b d

> as.numeric(fact)
[1] 1 2 1 3

最后一行返回的数字对应因子的水平。

> levels(fact)
[1] "a" "b" "d"

注意levels() 返回一个字符数组。您可以使用这个事实轻松而紧凑地将因子转换为字符串或数字,如下所示:

> fact_character <- levels(fact)[as.numeric(fact)]
> fact_character
[1] "a" "b" "a" "d"

这也适用于数值,前提是您将表达式包装在 as.numeric() 中。

> num_fact <- factor(c(1,2,3,6,5,4))
> num_fact
[1] 1 2 3 6 5 4
Levels: 1 2 3 4 5 6
> num_num <- as.numeric(levels(num_fact)[as.numeric(num_fact)])
> num_num
[1] 1 2 3 6 5 4

【讨论】:

  • 这个答案没有解决问题,这就是我如何将数据框中的 all 因子列转换为字符。 as.character(f),在可读性和效率上都优于levels(f)[as.numeric(f)]。如果你想聪明一点,你可以改用levels(f)[f]。请注意,在使用数值转换因子时,您确实可以从as.numeric(levels(f))[f] 中获得一些好处,例如as.numeric(as.character(f)),但这是因为您只需将级别转换为数字,然后再转换为子集。 as.character(f) 就这样就好了。
【解决方案5】:

如果您想要一个新的数据框bobc,其中bobf 中的每个 因子向量都被转换为字符向量,试试这个:

bobc <- rapply(bobf, as.character, classes="factor", how="replace")

如果你想把它转换回来,你可以创建一个逻辑向量,其中列是因子,并使用它来有选择地应用因子

f <- sapply(bobf, class) == "factor"
bobc[,f] <- lapply(bobc[,f], factor)

【讨论】:

  • +1 只做必要的事情(即不将整个 data.frame 转换为字符)。此解决方案对于包含混合类型的 data.frame 是稳健的。
  • 这个例子应该在 rapply 的“例子”部分,比如:stat.ethz.ch/R-manual/R-devel/library/base/html/rapply.html。有谁知道如何要求这样做?
  • 如果你想得到一个数据框,只需将 rapply 包装在一个 data.frame 调用中(使用 stringsAsFactors 设置为 FALSE 参数)
【解决方案6】:

我通常将此功能与我的所有项目分开。快速简单。

unfactorize <- function(df){
  for(i in which(sapply(df, class) == "factor")) df[[i]] = as.character(df[[i]])
  return(df)
}

【讨论】:

    【解决方案7】:

    另一种方法是使用 apply 进行转换

    bob2 <- apply(bob,2,as.character)
    

    还有一个更好的(前一个属于“矩阵”类)

    bob2 <- as.data.frame(as.matrix(bob),stringsAsFactors=F)
    

    【讨论】:

    • 跟随@Shane 的评论:为了得到data.frame,做as.data.frame(lapply(...
    【解决方案8】:

    更新:这是一个不起作用的例子。我认为它会,但我认为 stringsAsFactors 选项仅适用于字符串 - 它不理会因素。

    试试这个:

    bob2 <- data.frame(bob, stringsAsFactors = FALSE)
    

    一般来说,当您遇到应该是字符的因素时,有一个 stringsAsFactors 设置可以帮助您(包括全局设置)。

    【讨论】:

    • 这确实有效,如果他在创建 bob 开始时设置它(但不是事后)。
    • 对。只是想明确一点,这本身并不能解决问题 - 但感谢您注意到它确实阻止了它。
    【解决方案9】:

    或者你可以试试transform:

    newbob <- transform(bob, phenotype = as.character(phenotype))
    

    请务必将您想要转换为角色的所有因素都输入。

    或者你可以这样做,一击杀死所有害虫:

    newbob_char <- as.data.frame(lapply(bob[sapply(bob, is.factor)], as.character), stringsAsFactors = FALSE)
    newbob_rest <- bob[!(sapply(bob, is.factor))]
    newbob <- cbind(newbob_char, newbob_rest)
    

    不是像这样在代码中推送数据是个好主意,我可以单独执行sapply 部分(实际上,这样做要容易得多),但你明白了......我没有检查代码,因为我不在家,所以我希望它有效! =)

    但是,这种方法有一个缺点......您必须在之后重新组织列,而使用 transform 您可以做任何您喜欢的事情,但代价是“pedestrian-style-code-writting”嗯>...

    所以... =)

    【讨论】:

      【解决方案10】:

      在数据框的开头包含stringsAsFactors = FALSE 以忽略所有误解。

      【讨论】:

        【解决方案11】:

        如果您将 data.table 包用于 data.frame 上的操作,则问题不存在。

        library(data.table)
        dt = data.table(col1 = c("a","b","c"), col2 = 1:3)
        sapply(dt, class)
        #       col1        col2 
        #"character"   "integer" 
        

        如果您的数据集中已经有一个因子列并且您想将它们转换为字符,您可以执行以下操作。

        library(data.table)
        dt = data.table(col1 = factor(c("a","b","c")), col2 = 1:3)
        sapply(dt, class)
        #     col1      col2 
        # "factor" "integer" 
        upd.cols = sapply(dt, is.factor)
        dt[, names(dt)[upd.cols] := lapply(.SD, as.character), .SDcols = upd.cols]
        sapply(dt, class)
        #       col1        col2 
        #"character"   "integer" 
        

        【讨论】:

        • DT 绕过 Marek 提出的 sapply 修复:In [&lt;-.data.table(*tmp*, sapply(bob, is.factor), : Coerced 'character' RHS to 'double' to match the column's type. Either change the target column to 'character' first (by creating a new 'character' vector length 1234 (nrows of entire table) and assign that; i.e. 'replace' column), or coerce RHS to 'double' (e.g. 1L, NA_[real|integer]_, as.*, etc) to make your intent clear and for speed. Or, set the column type correctly up front when you create the table and stick to it, please. 修复 DF 并重新创建 DT 更容易。
        【解决方案12】:

        这对我有用——我终于想出了一个衬里

        df <- as.data.frame(lapply(df,function (y) if(class(y)=="factor" ) as.character(y) else y),stringsAsFactors=F)
        

        【讨论】:

          【解决方案13】:

          这个函数可以解决问题

          df <- stacomirtools::killfactor(df)
          

          【讨论】:

            【解决方案14】:

            您应该在hablar 中使用convert,它提供与tidyverse 管道兼容的可读语法:

            library(dplyr)
            library(hablar)
            
            df <- tibble(a = factor(c(1, 2, 3, 4)),
                         b = factor(c(5, 6, 7, 8)))
            
            df %>% convert(chr(a:b))
            

            给你:

              a     b    
              <chr> <chr>
            1 1     5    
            2 2     6    
            3 3     7    
            4 4     8   
            

            【讨论】:

              【解决方案15】:

              也许是一个更新的选项?

              library("tidyverse")
              
              bob <- bob %>% group_by_if(is.factor, as.character)
              

              【讨论】:

                【解决方案16】:

                使用dplyr-package 加载使用

                bob=bob%>%mutate_at("phenotype", as.character)
                

                如果您只想专门更改phenotype-列。

                【讨论】:

                  【解决方案17】:

                  在 dplyr version 1.0.0 中引入了新功能“跨越”。新函数将取代作用域变量(_if、_at、_all)。这里是官方documentation

                  library(dplyr)
                  bob <- bob %>% 
                         mutate(across(where(is.factor), as.character))
                  

                  【讨论】:

                  • 我在我的答案中包含了这个变化。感谢您引起我的注意。
                  • 没问题。我尝试编辑您的答案,但被审核团队拒绝。
                  【解决方案18】:

                  这可以将所有转换为字符,然后将数字转换为数字:

                  makenumcols<-function(df){
                    df<-as.data.frame(df)
                    df[] <- lapply(df, as.character)
                    cond <- apply(df, 2, function(x) {
                      x <- x[!is.na(x)]
                      all(suppressWarnings(!is.na(as.numeric(x))))
                    })
                    numeric_cols <- names(df)[cond]
                    df[,numeric_cols] <- sapply(df[,numeric_cols], as.numeric)
                    return(df)
                  }
                  

                  改编自:Get column types of excel sheet automatically

                  【讨论】:

                    猜你喜欢
                    • 2012-03-04
                    • 1970-01-01
                    • 2015-02-16
                    • 2018-05-17
                    • 2018-07-31
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多