Waarom gebruiken als.factor () in plaats van alleen factor ()

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.factoris 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> factorwanneer invoer een factor is
  • Prestaties: as.factor> factorwanneer 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 ?factorvermeldt 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> factorwanneer 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.factorgeeft wel een snel rendement, maar factoris geen echte “no-op”. Laten we een profiel maken van factorom 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 sortde uniquewaarden van de invoervector fen converteert vervolgens fnaar een tekenvector , gebruikt ten slotte factorom de tekenvector terug te dwingen tot een factor. Hier is de broncode van factorter 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 factoris echt ontworpen om te werken met een tekenvector en past as.charactertoe op de invoer om dat te garanderen. We kunnen van bovenaf in ieder geval twee prestatiegerelateerde zaken leren:

  1. Voor een dataframe DFis lapply(DF, as.factor)veel sneller dan lapply(DF, factor)voor type conversie, als veel kolommen gemakkelijk factoren zijn.
  2. Die functie factortraag is, kan verklaren waarom sommige belangrijke R-functies traag zijn, zeg table: R: tafelfunctie verrassend traag

Prestaties: as.factor> factorwanneer 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.factorzorgt 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 factoren as.factorop 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 droplevelsdie gebruikt kan worden om ongebruikte niveaus van een factor te laten vallen. Maar NAniveaus 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, tapplyverwachten 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.factorgebruiken (dit is tenminste zo voor split.defaulten tapply). De functie tableziet eruit als een uitzondering en ik zie factorin plaats van as.factorerin. 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.factorgebruiken, 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 tableniet 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 NAniveaus handmatig uit onze factorvariabele verwijderen, met behulp van droplevelsof factor.

Hint:

  1. splitHeeft een argument dropdie standaard is op FALSEVENTE as.factorWORDT GEBRUIKT; Met drop = TRUEFUNCTION factorwordt in plaats daarvan gebruikt.
  2. aggregateNAUWEN OP split, dus het heeft ook een dropARGUMENT EN HET STANDELINGEN MET TRUE.
  3. tapplyHEBT NIET dropHoewel het ook afhankelijk is van split. Met name de documentatie ?tapplyzegt dat as.factor(altijd) wordt gebruikt.

Other episodes