Конвертировать столбцы data.frame из факторов в символы

352

У меня есть фрейм данных. Давайте позвоним ему 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 с символьными столбцами без необходимости вручную проходить через каждый столбец?

Бонусный вопрос: почему ручной подход работает?

Майк Дьюар
источник
3
было бы неплохо, если бы вы сделали вопрос воспроизводимым, поэтому включите структуру bob.
jangorecki

Ответы:

362

Просто следую за Мэттом и Дирком. Если вы хотите воссоздать существующий фрейм данных без изменения глобального параметра, вы можете воссоздать его с помощью оператора apply:

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

Это преобразует все переменные в класс «персонаж», если вы хотите преобразовать только факторы, см . Решение Марека ниже .

Как отмечает @hadley, следующее является более лаконичным.

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

В обоих случаях lapplyвыводит список; однако, благодаря магическим свойствам R, использование []во втором случае сохраняет класс data.frame bobобъекта, тем самым устраняя необходимость преобразования обратно в data.frame as.data.frameс использованием аргумента stringsAsFactors = FALSE.

Шейн
источник
27
Шейн, это также превратит числовые столбцы в характер.
Дирк Эддельбюттель
@Dirk: Это правда, хотя не ясно, является ли это проблемой здесь. Очевидно, что правильное создание вещей - это лучшее решение. Я не думаю, что легко автоматически конвертировать типы данных через фрейм данных. Один из вариантов - использовать вышеизложенное, но затем использовать type.convertпосле приведения всех к character, а затем factorsвыполнить characterповторный возврат .
Шейн
Это, кажется, отбрасывает имена строк.
пикколбо
2
@piccolbo вы использовали bob[] <- в примере или bob <- ? первый хранит data.frame; вторая изменяет data.frame в список, удаляя имена строк. Я буду обновлять ответ
Дэвид Лебауэр
6
Вариант, который только преобразует факторные столбцы в символ, используя анонимную функцию: iris[] <- lapply(iris, function(x) if (is.factor(x)) as.character(x) else {x})
Stefan F
313

Заменить только факторы:

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

В пакете dplyr в версии 0.5.0 mutate_ifбыла введена новая функция :

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

Пакетное мурлыканье от RStudio дает еще одну альтернативу:

library(purrr)
library(dplyr)
bob %>% map_if(is.factor, as.character) %>% as_tibble -> bob
Marek
источник
Не работает для меня, к сожалению. Не знаю почему. Наверное, потому что у меня есть названия?
Autumnsault
@mohawkjohn Не должно быть проблемой. Вы получили ошибку или результаты не так, как вы ожидали?
Марек
2
Примечание: purrrстрока возвращает список, а не a data.frame!
RoyalTS
Это также работает, если у вас уже есть iвектор colnames().
verbamour
39

Глобальный вариант

stringsAsFactors: настройка по умолчанию для аргументов data.frame и read.table.

может быть что-то, что вы хотите установить FALSEв ваших файлах запуска (например, ~ / .Rprofile). Пожалуйста, смотрите help(options).

Дирк Эддельбюттель
источник
5
Проблема в том, что когда вы выполняете свой код в среде, где этот файл .Rprofile отсутствует, вы получаете ошибки!
вафель
4
Я склонен называть это в начале скриптов, а не в настройках .Rprofile.
gregmacfarlane
22

Если вы понимаете, как хранятся факторы, вы можете избежать использования основанных на применении функций для этого. Что вовсе не означает, что применяемые решения не работают хорошо.

Факторы структурированы как числовые индексы, привязанные к списку «уровней». Это можно увидеть, если вы преобразуете коэффициент в число. Так:

> 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
Kikapp
источник
Этот ответ не решает проблему, как я могу преобразовать все столбцы фактора в моем фрейме данных в символ. as.character(f)лучше в удобочитаемости и эффективности levels(f)[as.numeric(f)]. Если вы хотите быть умным, вы можете использовать levels(f)[f]вместо этого. Обратите внимание, что при преобразовании коэффициента с числовыми значениями вы получаете некоторую выгоду от as.numeric(levels(f))[f], например, превышения, as.numeric(as.character(f))но это потому, что вам нужно только преобразовать уровни в числовые, а затем в подмножество. as.character(f)просто отлично, как есть.
De Novo
20

Если вам нужен новый фрейм данных, в bobcкотором каждый фактор-вектор bobfпреобразуется в символьный вектор, попробуйте следующее:

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

Если затем вы хотите преобразовать его обратно, вы можете создать логический вектор, столбцы которого являются факторами, и использовать его для выборочного применения коэффициента.

f <- sapply(bobf, class) == "factor"
bobc[,f] <- lapply(bobc[,f], factor)
scentoni
источник
2
+1 за выполнение только того, что было необходимо (т.е. не преобразование всего data.frame в символ). Это решение устойчиво к data.frame, который содержит смешанные типы.
Джошуа Ульрих
3
Этот пример должен быть в разделе «Примеры» для rapply, например, по адресу: stat.ethz.ch/R-manual/R-devel/library/base/html/rapply.html . Кто-нибудь знает, как просить, чтобы это было так?
mpettis
Если вы хотите получить фрейм данных, просто оберните rapply в вызов data.frame (используя для параметра
stringsAsFactors
13

Я обычно делаю эту функцию отдельно от всех своих проектов. Быстро и просто.

unfactorize <- function(df){
  for(i in which(sapply(df, class) == "factor")) df[[i]] = as.character(df[[i]])
  return(df)
}
by0
источник
8

Другой способ - конвертировать его с помощью apply.

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

И лучше (предыдущий класс «матрица»)

bob2 <- as.data.frame(as.matrix(bob),stringsAsFactors=F)
Джордж Донтас
источник
После комментария @ Шейна: чтобы получить data.frame, сделайтеas.data.frame(lapply(...
aL3xa
7

Обновление: вот пример чего-то, что не работает. Я думал, что будет, но я думаю, что опция stringsAsFactors работает только на символьных строках - она ​​оставляет факторы в покое.

Попробуй это:

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

В общем, всякий раз, когда у вас возникают проблемы с факторами, которые должны быть символами, есть stringsAsFactorsместо, где вам помогут (в том числе глобальные настройки).

Мэтт Паркер
источник
1
Это работает, если он устанавливает его при создании bobдля начала (но не по факту).
Шейн
Правильно. Я просто хотел прояснить, что это не решает проблему как таковую - но спасибо, что отметили, что это предотвращает это.
Мэтт Паркер
7

Или вы можете попробовать 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вы можете делать все что угодно, но за счет "написания кода в стиле пешехода" ...

Так что есть ... =)

aL3xa
источник
6

В начале вашего фрейма данных включите, stringsAsFactors = FALSEчтобы игнорировать все недоразумения.


источник
4

Если бы вы использовали data.tablepackage для операций над 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" 
jangorecki
источник
DT обходит исправление, предложенное Мареком: In [<-.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.
Мэтт Чемберс
2

Это работает для меня - я, наконец, понял, один вкладыш

df <- as.data.frame(lapply(df,function (y) if(class(y)=="factor" ) as.character(y) else y),stringsAsFactors=F)
user1617979
источник
2

Эта функция делает свое дело

df <- stacomirtools::killfactor(df)
Седрик
источник
2

Может быть, более новый вариант?

library("tidyverse")

bob <- bob %>% group_by_if(is.factor, as.character)
rachelette
источник
1

Вы должны использовать convertв hablarкотором дает читаемый синтаксис, совместимый с 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   
davsjob
источник
1

С dplyrзагруженным пакетом

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

если вы хотите phenotypeконкретно изменить -column.

nexonvantec
источник
0

Это работает, преобразовывая все в символ, а затем из числа в число:

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)
}

Адаптировано из: Получить типы столбцов таблицы Excel автоматически

Ferroao
источник