Expandir automaticamente um fator R em uma coleção de variáveis ​​indicadoras 1/0 para cada nível de fator


108

Eu tenho um quadro de dados R contendo um fator que desejo "expandir" para que, para cada nível de fator, haja uma coluna associada em um novo quadro de dados, que contém um indicador 1/0. Por exemplo, suponha que eu tenha:

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

Eu quero:

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

Porque para certas análises para as quais você precisa ter um quadro de dados completamente numérico (por exemplo, análise de componente principal), pensei que esse recurso poderia ser integrado. Escrever uma função para fazer isso não deve ser muito difícil, mas posso prever alguns desafios relacionados aos nomes das colunas e se algo já existir, prefiro usar isso.

Respostas:


131

Use a model.matrixfunção:

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

1
Posso apenas acrescentar que este método foi muito mais rápido do que usar castpara mim.
Matt Weller

3
@GregSnow Eu revisei o segundo parágrafo de ?formulatambém ?model.matrix, mas não estava claro (pode ser apenas minha falta de conhecimento profundo em álgebra de matrizes e formulação de modelos). Depois de cavar mais, fui capaz de concluir que o -1 está apenas especificando para não incluir a coluna "interceptar". Se você omitir -1, verá uma coluna de interceptação de 1 na saída com uma coluna binária omitida. Você pode ver quais valores a coluna omitida são 1s com base nas linhas em que os valores das outras colunas são 0s. A documentação parece enigmática - existe outro bom recurso?
Ryan Chase

1
@RyanChase, há muitos tutoriais e livros online sobre R / S (vários com breves descrições na página da Web r-project.org). Meu próprio aprendizado de S e R tem sido bastante eclético (e longo), então não sou o melhor para dar uma opinião sobre como os livros / tutoriais atuais atraem os iniciantes. Sou, no entanto, um fã de experimentação. Tentar algo em uma nova sessão de R pode ser muito esclarecedor e não perigoso (o pior que me aconteceu é travar o R, e isso raramente, o que leva a melhorias no R). Stackoverflow é um bom recurso para entender o que aconteceu.
Greg Snow,

7
E se você quiser converter todas as colunas de fator, pode usar:model.matrix(~., data=iris)[,-1]
user890739

1
@colin, Não totalmente automático, mas você pode usar naresidpara colocar os valores ausentes de volta após o uso na.exclude. Um exemplo rápido: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)
Greg Snow

17

Se seu quadro de dados for feito apenas de fatores (ou se você estiver trabalhando em um subconjunto de variáveis ​​que são todos fatores), você também pode usar a acm.disjonctiffunção do ade4pacote:

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

Não é exatamente o caso que você está descrevendo, mas também pode ser útil ...


Obrigado, isso me ajudou muito, pois usa menos memória do que o model.matrix!
Serhiy

Gosto da forma como as variáveis ​​são nomeadas; Não gosto que eles sejam retornados como numéricos que exigem muito armazenamento quando deveriam (IMHO) ser apenas lógicos.
dsz

9

Uma maneira rápida de usar o reshape2pacote:

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

Observe que isso produz precisamente os nomes de coluna que você deseja.


Boa. Mas tome cuidado com a duplicata de presunto. digamos, d <- data.frame (eggs = c ("foo", "bar", "foo"), ham = c (1,2,1)); dcast (d, presunto ~ ovos, comprimento) torna foo = 2.
kohske

@Kohske, é verdade, mas eu estava assumindo que hamé um ID de linha exclusivo Se hamnão for um id único, deve-se usar algum outro id único (ou criar um fictício) e usá-lo no lugar de ham. Converter um rótulo categórico em um indicador binário só faria sentido para IDs únicos.
Prasad Chalasani

6

provavelmente a variável fictícia é semelhante ao que você deseja. Então, model.matrix é útil:

> 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

Uma entrada tardia class.inddo nnetpacote

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

Acabei de encontrar este velho tópico e pensei em adicionar uma função que utiliza ade4 para pegar um dataframe consistindo em fatores e / ou dados numéricos e retornar um dataframe com fatores como códigos fictícios.

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

Vamos tentar.

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

Aqui está uma maneira mais clara de fazer isso. Eu uso model.matrix para criar as variáveis ​​booleanas fictícias e, em seguida, mesclá-las de volta ao dataframe original.

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

Eu precisava de uma função para 'explodir' fatores que fosse um pouco mais flexível, e fiz uma baseada na função acm.disjonctif do pacote ade4. Isso permite que você escolha os valores explodidos, que são 0 e 1 em acm.disjonctif. Explode apenas fatores que têm 'poucos' níveis. As colunas numéricas são preservadas.

# 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)
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.