Extension automatique d'un facteur R dans une collection de variables indicatrices 1/0 pour chaque niveau de facteur

108

J'ai une trame de données R contenant un facteur que je veux "développer" de sorte que pour chaque niveau de facteur, il y ait une colonne associée dans une nouvelle trame de données, qui contient un indicateur 1/0. Par exemple, supposons que j'ai:

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

Je voudrais:

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

Parce que pour certaines analyses pour lesquelles vous avez besoin d'une base de données entièrement numérique (par exemple, l'analyse des composants principaux), j'ai pensé que cette fonctionnalité pourrait être intégrée. Ecrire une fonction pour faire cela ne devrait pas être trop difficile, mais je peux en prévoir défis liés aux noms de colonnes et si quelque chose existe déjà, je préfère l'utiliser.

John Horton
la source

Réponses:

131

Utilisez la model.matrixfonction:

model.matrix( ~ Species - 1, data=iris )
Greg Snow
la source
1
Puis-je simplement ajouter que cette méthode était tellement plus rapide que de l'utiliser castpour moi.
Matt Weller
3
@GregSnow J'ai passé en revue le 2ème paragraphe de ?formulaainsi que ?model.matrix, mais ce n'était pas clair (pourrait simplement être mon manque de connaissances en algèbre matricielle et en formulation de modèles). Après avoir creusé davantage, j'ai pu comprendre que le -1 spécifie simplement de ne pas inclure la colonne "intercepter". Si vous omettez le -1, vous verrez une colonne d'interception de 1 dans la sortie avec une colonne binaire laissée de côté. Vous pouvez voir quelles valeurs la colonne omise sont des 1 en fonction des lignes où les valeurs des autres colonnes sont des 0. La documentation semble cryptique - existe-t-il une autre bonne ressource?
Ryan Chase
1
@RyanChase, il existe de nombreux tutoriels en ligne et des livres sur R / S (plusieurs ont de brèves descriptions sur la page Web r-project.org). Mon propre apprentissage de S et R a été plutôt éclectique (et long), je ne suis donc pas le meilleur pour donner une opinion sur la façon dont les livres / tutoriels actuels attirent les débutants. Je suis cependant fan de l'expérimentation. Essayer quelque chose dans une nouvelle session R peut être très instructif et pas dangereux (le pire qui m'est arrivé est de planter R, et cela rarement, ce qui conduit à des améliorations dans R). Stackoverflow est alors une bonne ressource pour comprendre ce qui s'est passé.
Greg Snow
7
Et si vous souhaitez convertir toutes les colonnes de facteurs, vous pouvez utiliser:model.matrix(~., data=iris)[,-1]
user890739
1
@colin, pas entièrement automatique, mais vous pouvez utiliser naresidpour remettre les valeurs manquantes après utilisation na.exclude. Un exemple rapide: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

Si votre bloc de données est uniquement composé de facteurs (ou que vous travaillez sur un sous-ensemble de variables qui sont tous des facteurs), vous pouvez également utiliser la acm.disjonctiffonction du ade4package:

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

Ce n'est pas exactement le cas que vous décrivez, mais cela peut aussi être utile ...

Juba
la source
Merci, cela m'a beaucoup aidé car il utilise moins de mémoire que model.matrix!
Serhiy
J'aime la façon dont les variables sont nommées; Je n'aime pas qu'ils soient retournés en tant que numériques gourmands en stockage alors qu'ils devraient (à mon humble avis) être simplement logiques.
dsz
9

Un moyen rapide d'utiliser le reshape2package:

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

Notez que cela produit précisément les noms de colonne souhaités.

Prasad Chalasani
la source
Bien. Mais attention au double de jambon. dis, d <- data.frame (oeufs = c ("toto", "bar", "toto"), ham = c (1,2,1)); dcast (d, jambon ~ oeufs, longueur) fait foo = 2.
kohske
@Kohske, c'est vrai, mais je supposais qu'il s'agissait d' hamun identifiant de ligne unique. Si ce hamn'est pas un identifiant unique, il faut utiliser un autre identifiant unique (ou en créer un factice) et l'utiliser à la place de ham. La conversion d'une étiquette catégorielle en indicateur binaire n'aurait de sens que pour les identifiants uniques.
Prasad Chalasani
6

probablement la variable factice est similaire à ce que vous voulez. Ensuite, model.matrix est utile:

> 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
kohske
la source
6

Une entrée tardive class.inddu nnetpackage

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
mnel
la source
4

Je viens de tomber sur ce vieux fil et j'ai pensé ajouter une fonction qui utilise ade4 pour prendre une trame de données composée de facteurs et / ou de données numériques et renvoie une trame de données avec des facteurs sous forme de codes factices.

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

Essayons.

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)
Tyler Rinker
la source
3

Voici une manière plus claire de le faire. J'utilise model.matrix pour créer les variables booléennes factices, puis les fusionner dans le dataframe d'origine.

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.
stackoverflowuser2010
la source
0

J'avais besoin d'une fonction pour «exploser» les facteurs qui soit un peu plus flexible, et j'en ai créé une basée sur la fonction acm.disjonctif du paquet ade4. Cela vous permet de choisir les valeurs éclatées, qui sont 0 et 1 dans acm.disjonctif. Cela n'explose que les facteurs qui ont «peu» de niveaux. Les colonnes numériques sont conservées.

# 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)
}
rakensi
la source