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 :
- 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.
- 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))
:
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 :
-
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.
-
aggregate
s'appuie sur split
Il a donc aussi un drop
et la valeur par défaut est TRUE
.
-
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é.
5 votes
La cohérence des noms est un élément important. Presque toutes les classes courantes ont un
as.class
fonction.