Ik heb onlangs Matt Dowle een code met as.factor()
, specifiek
for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
in een opmerking op dit antwoord .
Ik heb dit fragment gebruikt, maar ik moest expliciet de factor niveaus instellen om ervoor te zorgen dat de niveaus in mijn gewenste volgorde verschijnen, dus ik moest veranderen
as.factor(dt[[col]])
Naar
factor(dt[[col]], levels = my_levels)
Dit heeft me denken: wat (indien aanwezig) is het voordeel voor het gebruik van as.factor()
versus gewoon factor()
?
Antwoord 1, Autoriteit 100%
as.factor
is een wikkel voor factor
, maar het maakt snelle retournering als de invoervector al een factor is:
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)
}
commentaar van Frank : het is geen wrapper, omdat deze “snelle return” factorniveaus zal verlaten zoals ze zijn terwijl factor()
niet:
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
Uitgebreid antwoord twee jaar later, inclusief het volgende:
- Wat zegt de handleiding?
- Prestaties:
as.factor
>factor
wanneer invoer een factor is - Prestaties:
as.factor
>factor
wanneer invoer een geheel getal is - Ongebruikte niveaus of NA niveaus
- Let op bij het gebruik van R’s group-by-functies: let op ongebruikte of NA-niveaus
Wat zegt de handleiding?
De documentatie voor ?factor
vermeldt het volgende:
‘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’.
Prestaties: as.factor
> factor
wanneer invoer een factor is
Het woord “geen bewerking” is een beetje dubbelzinnig. Vat het niet op als “niets doen”; in feite betekent het “veel dingen doen, maar in wezen niets veranderen”. Hier is een voorbeeld:
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
geeft wel een snel rendement, maar factor
is geen echte “no-op”. Laten we een profiel maken van factor
om te zien wat het heeft gedaan.
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
Eerst sort
de unique
waarden van de invoervector f
en converteert vervolgens f
naar een tekenvector , gebruikt ten slotte factor
om de tekenvector terug te dwingen tot een factor. Hier is de broncode van factor
ter bevestiging.
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
}
Dus functie factor
is echt ontworpen om te werken met een tekenvector en past as.character
toe op de invoer om dat te garanderen. We kunnen van bovenaf in ieder geval twee prestatiegerelateerde zaken leren:
- Voor een dataframe
DF
islapply(DF, as.factor)
veel sneller danlapply(DF, factor)
voor type conversie, als veel kolommen gemakkelijk factoren zijn. - Die functie
factor
traag is, kan verklaren waarom sommige belangrijke R-functies traag zijn, zegtable
: R: tafelfunctie verrassend traag
Prestaties: as.factor
> factor
wanneer invoer een geheel getal is
Een factorvariabele is de naaste verwant van een integer-variabele.
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"
Dit betekent dat het converteren van een geheel getal naar een factor eenvoudiger is dan het converteren van een numeriek / teken naar een factor. as.factor
zorgt hier gewoon voor.
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
Ongebruikte niveaus of NA niveaus
Laten we nu een paar voorbeelden bekijken van de invloed van factor
en as.factor
op factorniveaus (als de invoer al een factor is). Frankheeft er een gegeven met een ongebruikt factorniveau, ik zal er een geven met een 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
Er is een (generieke) functie droplevels
die gebruikt kan worden om ongebruikte niveaus van een factor te laten vallen. Maar NA
niveaus kunnen niet standaard worden verwijderd.
## "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
Let op bij het gebruik van R’s group-by-functies: let op ongebruikte of NA-niveaus
R-functies die group-by-bewerkingen uitvoeren, zoals split
, tapply
verwachten van ons dat we factorvariabelen leveren als “by”-variabelen. Maar vaak bieden we alleen karakter- of numerieke variabelen. Dus intern moeten deze functies ze omzetten in factoren en waarschijnlijk zouden de meeste van hen in de eerste plaats as.factor
gebruiken (dit is tenminste zo voor split.default
en tapply
). De functie table
ziet eruit als een uitzondering en ik zie factor
in plaats van as.factor
erin. Er kan een speciale overweging zijn die helaas niet duidelijk is voor mij wanneer ik de broncode inspecteer.
Aangezien de meeste group-by R-functies as.factor
gebruiken, zal een dergelijke groep in het resultaat verschijnen als ze een factor krijgen met ongebruikte of NA
-niveaus.
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
Interessant is dat, hoewel table
niet afhankelijk is van as.factor
, het ook die ongebruikte niveaus behoudt:
table(f)
#a b c
#1 1 0
Soms kan dit soort gedrag ongewenst zijn. Een klassiek voorbeeld is barplot(table(f))
:
Als dit echt ongewenst is, moeten we ongebruikte of NA
niveaus handmatig uit onze factorvariabele verwijderen, met behulp van droplevels
of factor
.
Hint:
split
Heeft een argumentdrop
die standaard is opFALSE
VENTEas.factor
WORDT GEBRUIKT; Metdrop = TRUE
FUNCTIONfactor
wordt in plaats daarvan gebruikt.aggregate
NAUWEN OPsplit
, dus het heeft ook eendrop
ARGUMENT EN HET STANDELINGEN METTRUE
.tapply
HEBT NIETdrop
Hoewel het ook afhankelijk is vansplit
. Met name de documentatie?tapply
zegt datas.factor
(altijd) wordt gebruikt.