web-dev-qa-db-fra.com

Pourquoi utiliser as.factor () au lieu de factor ()

J'ai récemment vu Matt Dowle écrire du code avec as.factor(), en particulier

for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))

dans n commentaire à cette réponse .

J'ai utilisé cet extrait, mais je devais définir explicitement les niveaux de facteur pour m'assurer que les niveaux apparaissent dans l'ordre souhaité, ce qui m'a obligé à changer

as.factor(dt[[col]])

à

factor(dt[[col]], levels = my_levels)

Cela m'a fait penser: quel est l'avantage (le cas échéant) d'utiliser as.factor() par rapport à factor()?

44
Ben

as.factor Est un wrapper pour factor, mais permet un retour rapide si le vecteur d'entrée est déjà un facteur:

function (x) 
{
    if (is.factor(x)) 
        x
    else if (!is.object(x) && is.integer(x)) {
        levels <- sort(unique.default(x))
        f <- match(x, levels)
        levels(f) <- as.character(levels)
        if (!is.null(nx <- names(x))) 
        names(f) <- nx
        class(f) <- "factor"
        f
    }
else factor(x)
}

Commentaire de Frank : ce n'est pas un simple wrapper, car ce "retour rapide" laissera les niveaux de facteurs tels qu'ils sont alors que factor() ne sera pas:

f = factor("a", levels = c("a", "b"))
#[1] a
#Levels: a b

factor(f)
#[1] a
#Levels: a

as.factor(f)
#[1] a
#Levels: a b

Réponse élargie deux ans plus tard, comprenant les éléments suivants:

  • Que dit le manuel?
  • Performance: as.factor> factor lorsque l'entrée est un facteur
  • Performance: as.factor> factor lorsque l'entrée est un entier
  • Niveaux inutilisés ou niveaux NA
  • Attention lors de l'utilisation des fonctions de regroupement de R: surveillez les niveaux inutilisés ou NA

Que dit le manuel?

La documentation de ?factor Mentionne les éléments suivants:

‘factor(x, exclude = NULL)’ applied to a factor without ‘NA’s is a
 no-operation unless there are unused levels: in that case, a
 factor with the reduced level set is returned.

 ‘as.factor’ coerces its argument to a factor.  It is an
 abbreviated (sometimes faster) form of ‘factor’.

Performance: as.factor> factor lorsque l'entrée est un facteur

Le mot "non-opération" est un peu ambigu. Ne le prenez pas comme "ne rien faire"; en fait, cela veut dire "faire beaucoup de choses mais ne rien changer". Voici un exemple:

set.seed(0)
## a randomized long factor with 1e+6 levels, each repeated 10 times
f <- sample(gl(1e+6, 10))

system.time(f1 <- factor(f))  ## default: exclude = NA
#   user  system elapsed 
#  7.640   0.216   7.887 

system.time(f2 <- factor(f, exclude = NULL))
#   user  system elapsed 
#  7.764   0.028   7.791 

system.time(f3 <- as.factor(f))
#   user  system elapsed 
#      0       0       0 

identical(f, f1)
#[1] TRUE

identical(f, f2)
#[1] TRUE

identical(f, f3)
#[1] TRUE

as.factor Donne un retour rapide, mais factor n'est pas un vrai "non-op". Voyons le profil factor pour voir ce qu'il a fait.

Rprof("factor.out")
f1 <- factor(f)
Rprof(NULL)
summaryRprof("factor.out")[c(1, 4)]
#$by.self
#                      self.time self.pct total.time total.pct
#"factor"                   4.70    58.90       7.98    100.00
#"unique.default"           1.30    16.29       4.42     55.39
#"as.character"             1.18    14.79       1.84     23.06
#"as.character.factor"      0.66     8.27       0.66      8.27
#"order"                    0.08     1.00       0.08      1.00
#"unique"                   0.06     0.75       4.54     56.89
#
#$sampling.time
#[1] 7.98

Premièrement, sort les valeurs unique du vecteur d’entrée f, puis convertit f en un vecteur de caractère, utilise finalement factor pour coercer le vecteur de caractère à un facteur. Voici le code source de factor pour confirmation.

function (x = character(), levels, labels = levels, exclude = NA, 
    ordered = is.ordered(x), nmax = NA) 
{
    if (is.null(x)) 
        x <- character()
    nx <- names(x)
    if (missing(levels)) {
        y <- unique(x, nmax = nmax)
        ind <- sort.list(y)
        levels <- unique(as.character(y)[ind])
    }
    force(ordered)
    if (!is.character(x)) 
        x <- as.character(x)
    levels <- levels[is.na(match(levels, exclude))]
    f <- match(x, levels)
    if (!is.null(nx)) 
        names(f) <- nx
    nl <- length(labels)
    nL <- length(levels)
    if (!any(nl == c(1L, nL))) 
        stop(gettextf("invalid 'labels'; length %d should be 1 or %d", 
            nl, nL), domain = NA)
    levels(f) <- if (nl == nL) 
        as.character(labels)
    else paste0(labels, seq_along(levels))
    class(f) <- c(if (ordered) "ordered", "factor")
    f
}

Donc, la fonction factor est vraiment conçue pour fonctionner avec un vecteur de caractère et applique as.character À son entrée pour le garantir. Nous pouvons au moins apprendre deux problèmes liés à la performance ci-dessus:

  1. Pour un bloc de données, DF, lapply(DF, as.factor) est beaucoup plus rapide que lapply(DF, factor) pour la conversion de type, si plusieurs colonnes sont facilement des facteurs.
  2. Le fait que la fonction factor soit lente puisse expliquer pourquoi certaines fonctions R importantes sont lentes, par exemple table: R: la fonction de table est étonnamment lente

Performance: as.factor> factor lorsque l'entrée est un entier

Une variable facteur est le plus proche parent d'une variable entière.

unclass(gl(2, 2, labels = letters[1:2]))
#[1] 1 1 2 2
#attr(,"levels")
#[1] "a" "b"

storage.mode(gl(2, 2, labels = letters[1:2]))
#[1] "integer"

Cela signifie que la conversion d'un entier en facteur est plus facile que la conversion d'un caractère numérique/facteur en facteur. as.factor Prend juste soin de cela.

x <- sample.int(1e+6, 1e+7, TRUE)

system.time(as.factor(x))
#   user  system elapsed 
#  4.592   0.252   4.845 

system.time(factor(x))
#   user  system elapsed 
# 22.236   0.264  22.659 

Niveaux inutilisés ou niveaux NA

Voyons maintenant quelques exemples sur l'influence de factor et de as.factor Sur les niveaux de facteurs (si l'entrée est déjà un facteur). Frank en a donné un avec un niveau de facteur inutilisé, je vais en fournir un avec le niveau NA.

f <- factor(c(1, NA), exclude = NULL)
#[1] 1    <NA>
#Levels: 1 <NA>

as.factor(f)
#[1] 1    <NA>
#Levels: 1 <NA>

factor(f, exclude = NULL)
#[1] 1    <NA>
#Levels: 1 <NA>

factor(f)
#[1] 1    <NA>
#Levels: 1

Il existe une fonction (générique) droplevels qui peut être utilisée pour supprimer les niveaux inutilisés d'un facteur. Mais les niveaux de NA ne peuvent pas être supprimés par défaut.

## "factor" method of `droplevels`
droplevels.factor
#function (x, exclude = if (anyNA(levels(x))) NULL else NA, ...) 
#factor(x, exclude = exclude)

droplevels(f)
#[1] 1    <NA>
#Levels: 1 <NA>

droplevels(f, exclude = NA)
#[1] 1    <NA>
#Levels: 1

Attention lors de l'utilisation des fonctions de regroupement de R: surveillez les niveaux inutilisés ou NA

Les fonctions R effectuant des opérations de regroupement, comme split, tapply, s’attendent à ce que nous fournissions des variables de facteur comme des variables "de". Mais souvent, nous fournissons uniquement des variables de caractère ou numériques. Donc, en interne, ces fonctions doivent les convertir en facteurs et la plupart d'entre elles utiliseraient probablement as.factor En premier lieu (du moins c'est le cas pour split.default Et tapply). La fonction table ressemble à une exception et je repère factor à la place de as.factor À l'intérieur. Il peut y avoir une considération spéciale qui n’est malheureusement pas évidente lorsque j’inspecte son code source.

Etant donné que la plupart des fonctions R groupées utilisent as.factor, Si un facteur avec des niveaux inutilisés ou NA leur est attribué, ce groupe apparaîtra dans le résultat.

x <- c(1, 2)
f <- factor(letters[1:2], levels = letters[1:3])

split(x, f)
#$a
#[1] 1
#
#$b
#[1] 2
#
#$c
#numeric(0)

tapply(x, f, FUN = mean)
# a  b  c 
# 1  2 NA 

Fait intéressant, bien que table ne repose pas sur as.factor, Il conserve également les niveaux inutilisés:

table(f)
#a b c 
#1 1 0 

Parfois, ce genre de comportement peut être indésirable. Un exemple classique est barplot(table(f)):

enter image description here

Si cela n'est vraiment pas souhaité, nous devons supprimer manuellement les niveaux inutilisés ou NA de notre variable facteur, en utilisant droplevels ou factor.

Indice:

  1. split a un argument drop dont la valeur par défaut est FALSE d'où as.factor est utilisé; par drop = TRUE la fonction factor est utilisée à la place.
  2. aggregate s'appuie sur split, de sorte qu'il a également un argument drop et que sa valeur par défaut est TRUE.
  3. tapply n'a pas drop bien qu'il repose également sur split. En particulier, la documentation ?tapply Indique que as.factor Est (toujours) utilisé.
60
李哲源