2017-05-26 2 views
2

Ich habe einen langen Vektor der Klasse Faktor, der NA Werte enthält.Effizient führen neue Ebene auf einem Faktor Vektor

# simple example 
x <- factor(c(NA,'A','B','C',NA), levels=c('A','B','C')) 

Zum Zwecke der Modellierung wünsche ich diese NA Werte mit einem Faktor neuen Ebene ersetzen (beispielsweise ‚Unknown‘), und diese Pegel als Referenzpegel gesetzt.

Da das Ersatzniveau ist nicht eine bestehende Ebene, ist ein einfacher Austausch nicht:

# this won't work, since the replacement value is not an existing level of the factor 
x[is.na(x)] <- '?' 
x # returns: [1] <NA> A B C <NA> -- the NAs remain 
# this doesn't work either: 
replace(x, NA,'?') 

ich mit ein paar Lösungen kam, aber beide sind so eine Art hässlich und überraschend langsam.

f1 <- function(x, uRep='?'){ 
    # convert to character, replace NAs with Unknown, and convert back to factor 
    stopifnot(is.factor(x)) 
    newLevels <- c(uRep,levels(x)) 
    x <- as.character(x) 
    x[is.na(x)] <- uRep 
    factor(x, levels=newLevels) 
} 

f2 <- function(x, uRep='?'){ 
    # add new level for Unknown, replace NAs with Unknown, and make Unknown first level 
    stopifnot(is.factor(x)) 
    levels(x) <- c(levels(x),uRep) 
    x[is.na(x)] <- uRep 
    relevel(x, ref=uRep) 
} 

f3 <- function(x, uRep='?'){ # thanks to @HongOoi 
    y <- addNA(x) 
    levels(y)[length(levels(y))]<-uRep 
    relevel(y, ref=uRep) 
} 

#test 
f1(x) # works 
f2(x) # works 
f3(x) # works 

Lösung # 2 ist die Bearbeitung nur die (relativ kleine) Menge von Ebenen, plus eine arithmetische op nivellieren. Ich hätte erwartet, dass das schneller als # 1 ist, was zum Charakter und zurück zum Faktor führt.

Allerdings ist # 2 doppelt so langsam auf einem Benchmark-Vektor von 10K Elementen mit 10 Ebenen und 10% NA.

x <- sample(factor(c(LETTERS[1:10],NA),levels=LETTERS[1:10]),10000,replace=TRUE) 
library(microbenchmark) 
microbenchmark(f1(x),f2(x),f3(x),times=500L) 
# Unit: microseconds 
# expr  min  lq  mean median  uq  max neval 
# f1(x) 271.981 278.1825 322.4701 313.0360 360.7175 609.393 500 
# f2(x) 651.728 703.2595 768.6756 747.9480 825.7800 1517.707 500 
# f3(x) 808.246 883.2980 966.2374 927.5585 1061.1975 1779.424 500 

Lösung # 3, mein Wrapper für den eingebauten in addNA (unten in der Antwort erwähnt) als entweder langsamer war. addNA führt einige Extra-Prüfungen für NA Werte durch und setzt die neue Ebene als letzte (die ich wieder aufheben muss) und benannt NA (die dann Umbenennung nach Index erfordert, da NA schwer zugänglich ist - relevel(addNA(x), ref=NA_character_)) funktioniert nicht) .

Gibt es eine effizientere Möglichkeit, dies zu schreiben, oder werde ich nur abgespritzt?

Antwort

1

Dafür gibt es eine eingebaute Funktion addNA.

Von Faktor:

addNA(x, ifany = FALSE) 
addNA modifies a factor by turning NA into an extra level (so that NA values are counted in tables, for instance). 
+0

einfach tun 'addNA' nicht die NA Ebene als Referenzpegel ist festgelegt, noch mit den gewünschten Niveau Namen ersetzt. Dies funktioniert nicht: 'relevel (addNA (x), ref = NA_character_)' – C8H10N4O2

2

können Sie fct_explicit_na von fct_relevel aus dem forcats Paket gefolgt verwenden, wenn Sie eine Pre-fab-Lösung wollen. Es ist langsamer als f1 Funktion, aber es läuft immer noch in einem Bruchteil einer Sekunde auf einem Vektor der Länge 100.000:

library(forcats) 

x <- factor(c(NA,'A','B','C',NA), levels=c('A','B','C')) 
[1] <NA> A B C <NA> 
Levels: A B C 
x = fct_relevel(fct_explicit_na(x, "Unknown"), "Unknown") 
[1] Unknown A  B  C  Unknown 
Levels: Unknown A B C 

Timings auf ein Vektor der Länge 100.000:

x <- sample(factor(c(LETTERS[1:10],NA), levels=LETTERS[1:10]), 1e5, replace=TRUE) 

microbenchmark(forcats = fct_relevel(fct_explicit_na(x, "Unknown"), "Unknown"), 
       f1 = f1(x), 
       unit="ms", times=100L) 
Unit: milliseconds 
    expr  min  lq  mean median  uq  max neval cld 
forcats 7.624158 10.634761 15.303339 12.162105 15.513846 250.0516 100 b 
     f1 3.568801 4.226087 8.085532 5.321338 5.995522 235.2449 100 a 
Verwandte Themen