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()
?
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
as.factor
> factor
lorsque l'entrée est un facteuras.factor
> factor
lorsque l'entrée est un entierLa 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’.
as.factor
> factor
lorsque l'entrée est un facteurLe 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:
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.factor
soit lente puisse expliquer pourquoi certaines fonctions R importantes sont lentes, par exemple table
: R: la fonction de table est étonnamment lenteas.factor
> factor
lorsque l'entrée est un entierUne 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
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
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))
:
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:
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.aggregate
s'appuie sur split
, de sorte qu'il a également un argument drop
et que sa valeur par défaut est TRUE
.tapply
n'a pas drop
bien qu'il repose également sur split
. En particulier, la documentation ?tapply
Indique que as.factor
Est (toujours) utilisé.