75 votes

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

J'ai récemment vu Matt Dowle écrire du code avec as.factor() et plus particulièrement

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

sur un commentaire à cette réponse .

J'ai utilisé ce snippet, mais j'avais besoin de définir explicitement les niveaux de facteurs pour m'assurer que les niveaux apparaissent dans l'ordre que je souhaite, donc j'ai dû changer

as.factor(dt[[col]])

à

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

Cela m'a fait réfléchir : quel est l'avantage (s'il y en a un) d'utiliser les services de as.factor() contre juste factor() ?

5 votes

La cohérence des noms est un élément important. Presque toutes les classes courantes ont un as.class fonction.

102voto

李哲源 Points 45737

as.factor est une enveloppe pour factor mais il 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 il ne s'agit pas d'un simple habillage, puisque ce "retour rapide" laissera les niveaux de facteurs tels qu'ils sont alors que factor() ne le fera 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, incluant les éléments suivants :

  • Que dit le manuel ?
  • Performance : as.factor > factor quand l'apport est un facteur
  • Performance : as.factor > factor lorsque l'entrée est un nombre entier
  • Niveaux non utilisés ou niveaux NA
  • Attention à l'utilisation des fonctions group-by de R : attention aux niveaux inutilisés ou NA.

Que dit le manuel ?

La documentation pour ?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 quand l'apport est un facteur

Le mot "no-operation" est un peu ambigu. Ne le prenez pas comme "ne rien faire" ; en fait, il signifie "faire beaucoup de choses mais ne rien changer essentiellement". 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 rendement rapide, mais factor n'est pas un véritable "no-op". Faisons 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

Il a d'abord sort le site unique des valeurs du vecteur d'entrée f puis convertit f à un vecteur de caractères, et enfin utilise factor pour ramener le vecteur de caractères à 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 fonction factor est vraiment conçu pour travailler avec un vecteur de caractères et il applique as.character à son entrée pour s'en assurer. Nous pouvons au moins tirer deux enseignements de ce qui précède en matière de performances :

  1. Pour un cadre de données DF , lapply(DF, as.factor) est beaucoup plus rapide que lapply(DF, factor) pour la conversion de type, si de nombreuses colonnes sont facilement facteurs.
  2. Cette fonction factor est lent peut expliquer pourquoi certaines fonctions importantes de R sont lentes, par exemple table : R : la fonction table est étonnamment lente

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

Une variable factorielle est le parent le plus proche 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 numérique / caractère en facteur. as.factor s'occupe de ça.

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 non utilisés ou niveaux NA

Voyons maintenant quelques exemples sur factor et as.factor L'influence de l'intrant sur les niveaux des facteurs (si l'intrant est déjà un facteur). Frank en a donné un avec un niveau de facteur non utilisé, je vais en donner un avec NA niveau.

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é pour faire tomber les niveaux non utilisés d'un facteur. Mais NA ne peuvent pas être abandonné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 à l'utilisation des fonctions group-by de R : attention aux niveaux inutilisés ou NA.

Les fonctions R qui effectuent des opérations groupées, comme split , tapply s'attendent à ce que nous fournissions des variables de facteurs comme variables "by". Mais souvent, nous ne fournissons que des variables de type caractère ou numérique. En interne, ces fonctions doivent donc les convertir en facteurs et la plupart d'entre elles utiliseront probablement la fonction as.factor en premier lieu (c'est du moins le cas pour les split.default et tapply ). Le site table ressemble à une exception et je repère factor au lieu de as.factor à l'intérieur. Il pourrait y avoir des considérations particulières qui ne sont malheureusement pas évidentes pour moi lorsque j'inspecte son code source.

Étant donné que la plupart des fonctions R de group-by utilisent as.factor si l'on leur donne un facteur non utilisé ou NA un tel 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 

Il est intéressant de noter que, bien que table ne s'appuie pas sur as.factor il préserve également ces niveaux non utilisés :

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

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

enter image description here

Si cela est vraiment indésirable, nous devons supprimer manuellement les éléments inutilisés ou NA de notre variable factorielle, en utilisant droplevels ou factor .

Un conseil :

  1. split a un argument drop qui a pour valeur par défaut FALSE d'où as.factor est utilisé ; par drop = TRUE fonction factor est utilisé à la place.
  2. aggregate s'appuie sur split Il a donc aussi un drop et la valeur par défaut est TRUE .
  3. tapply n'a pas drop bien qu'il s'appuie également sur split . En particulier, la documentation ?tapply dit que as.factor est (toujours) utilisé.

3 votes

Le code source de la fonction factor dans la réponse est sous R 3.4.4. Le code source a beaucoup changé depuis R 3.5.0, mais toutes les conclusions de la réponse sont toujours valables.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X