关于r:将data.frame列从因子转换为字符

Convert data.frame columns from factors to characters

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

1
2
3
4
5
6
7
8
> 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-

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

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

bob的列是因子。例如:

1
2
3
> 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的柱子,然后

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

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

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


跟着马特和德克。如果要在不更改全局选项的情况下重新创建现有数据帧,可以使用APPLY语句重新创建它:

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

这将把所有变量转换为类"character",如果只想转换因子,请参见下面的marek解决方案。

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

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

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


仅替换因素:

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

在0.5.0版的包dplyr中,引入了新的函数mutate_if

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

rstudio的package purr提供了另一种选择:

1
2
3
library(purrr)
library(dplyr)
bob %>% map_if(is.factor, as.character) %>% as_data_frame -> bob

(记住这是新包装)


全球选择权

stringsAsFactors:
The default setting for arguments of data.frame and read.table.

可能是您希望在启动文件(例如~/.rprofile)中设置为FALSE的内容。请参阅help(options)


如果您了解如何存储因子,可以避免使用基于应用的函数来完成这一任务。这并不意味着应用解决方案不能很好地工作。

因子被构造为与"级别"列表关联的数字索引。如果将系数转换为数字,则可以看到这一点。所以:

1
2
3
4
5
6
7
> 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

最后一行中返回的数字对应于因子的级别。

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

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

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

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

1
2
3
4
5
6
7
> 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


如果需要一个新的数据帧bobc,其中bobf中的每个因子向量都转换为字符向量,请尝试以下操作:

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

如果您想将其转换回原样,您可以创建一个逻辑向量,其中列是因子,并使用该向量有选择地应用因子。

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


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

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


另一种方法是使用apply转换它

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

一个更好的(前一个是"矩阵"类)

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


或者你也可以试试transform

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

一定要把你想转换成角色的所有因素都放进去。

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

1
2
3
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您可以做任何您喜欢的事情,但代价是"编写步行风格的代码"…

所以…=)


更新:这里有一个不起作用的例子。我想是的,但我认为stringsasFactors选项只对字符串起作用——它只保留这些因素。

试试这个:

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

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


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


如果在data.frame上使用data.table包进行操作,则问题不存在。

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

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

1
2
3
4
5
6
7
8
9
10
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"


这个函数起作用

1
df <- stacomirtools::killfactor(df)

这对我有用-我终于想出了一个办法

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


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

1
2
3
4
5
6
7
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))

这给了你:

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