Автоматическое расширение R-фактора в коллекцию из 1/0 индикаторных переменных для каждого уровня фактора


108

У меня есть фрейм данных 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))

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

Ответы:


131

Используйте model.matrixфункцию:

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

1
Могу я просто добавить, что этот метод был намного быстрее, чем я использовал cast.
Мэтт Веллер

3
@GregSnow я рассмотрел 2 - й абзац ?formula, а также ?model.matrix, но было неясно , (может быть просто мое отсутствие глубины знаний в матричной алгебре и формулировке модели). Покопав больше, я смог понять, что -1 просто указывает не включать столбец «перехват». Если вы не укажете -1, вы увидите в выходных данных столбец перехвата, состоящий из единиц, с одним оставленным двоичным столбцом. Вы можете увидеть, какие значения в пропущенном столбце равны 1, на основе строк, в которых значения других столбцов равны 0. Документация кажется загадочной - есть ли еще один хороший ресурс?
Райан Чейз

1
@RyanChase, существует множество онлайн-руководств и книг по R / S (некоторые из них имеют краткое описание на веб-странице r-project.org). Мое собственное изучение S и R было довольно эклектичным (и долгим), поэтому я не лучший вариант, чтобы высказать мнение о том, насколько современные книги / учебные пособия нравятся новичкам. Однако я фанат экспериментов. Попытка чего-либо в новом сеансе R может быть очень поучительной и не опасной (худшее, что случилось со мной, - это сбой R, и это редко, что приводит к улучшениям в R). Тогда Stackoverflow - хороший ресурс для понимания того, что произошло.
Грег Сноу

7
И если вы хотите преобразовать все факторные столбцы, вы можете использовать:model.matrix(~., data=iris)[,-1]
user890739 05

1
@colin, Не полностью автоматический, но вы можете использовать, naresidчтобы вернуть недостающие значения после использования na.exclude. Краткий пример:tmp <- data.frame(x=factor(c('a','b','c',NA,'a'))); tmp2 <- na.exclude(tmp); tmp3 <- model.matrix( ~x-1, tmp2); tmp4 <- naresid(attr(tmp2,'na.action'), tmp3)
Грег Сноу

17

Если ваш фрейм данных состоит только из факторов (или вы работаете с подмножеством переменных, которые все являются факторами), вы также можете использовать acm.disjonctifфункцию из ade4пакета:

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!
Сергей

Мне нравится, как называются переменные; Мне не нравится, что они возвращаются как требовательные к памяти числовые, когда они должны (ИМХО) быть просто логическими.
dsz

9

Быстрый способ использования 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 <- data.frame (egg = c ("foo", "bar", "foo"), ham = c (1,2,1)); dcast (d, ham ~ яйца, length) делает foo = 2.
kohske

@Kohske, правда, но я предполагал, hamчто это уникальный идентификатор строки. Если hamэто не уникальный идентификатор, нужно использовать какой-то другой уникальный идентификатор (или создать фиктивный) и использовать его вместо ham. Преобразование категориальной метки в бинарный индикатор имеет смысл только для уникальных идентификаторов.
Прасад Чаласани

6

вероятно, фиктивная переменная похожа на то, что вы хотите. Тогда пригодится 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

6

Поздний вход class.indиз nnetпакета

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

4

Просто наткнулся на этот старый поток и подумал, что добавлю функцию, которая использует 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)

3

Вот более понятный способ сделать это. Я использую 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.

0

Мне нужна была функция для "разнесения" факторов, которая была бы немного более гибкой, и я сделал ее на основе функции acm.disjonctif из пакета ade4. Это позволяет вам выбрать разнесенные значения, которые равны 0 и 1 в acm.disjonctif. Он взрывает только те факторы, которые имеют «несколько» уровней. Числовые столбцы сохраняются.

# 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)
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.