¿Paquete R para combinar niveles de factores para minería de datos?

10

¿Se pregunta si alguien se ha encontrado con un paquete / función en R que combine niveles de un factor cuya proporción de todos los niveles en un factor sea inferior a algún umbral? Específicamente, uno de los primeros pasos en la preparación de datos que realizo es colapsar niveles dispersos de factores (digamos en un nivel llamado 'Otro') que no constituyen al menos, digamos, 2% del total. Esto se hace sin supervisión y se hace cuando el objetivo es modelar alguna actividad en marketing (no detección de fraude, donde esos casos muy pequeños podrían ser extremadamente importantes). Estoy buscando una función que colapsará los niveles hasta que se alcance alguna proporción umbral.

ACTUALIZAR:

Gracias a estas excelentes sugerencias, escribí una función con bastante facilidad. Sin embargo, me di cuenta de que era posible colapsar los niveles con una proporción <el mínimo y todavía tener ese nivel recodificado ser <el mínimo, lo que requiere la adición del nivel más bajo con la proporción> el mínimo. Probablemente puede ser más eficiente, pero parece funcionar. La próxima mejora sería descubrir cómo capturar las "reglas" para aplicar la lógica de colapso a los nuevos datos (un conjunto de validación o datos futuros).

collapseFactors<- function(tableName,minPercent=5,fillIn ="RECODED" )
{
    for (i in 1:ncol(tableName))
        {   

            if(is.factor(tableName[,i]) == TRUE) #process just factors
            {


                sortedTable<-sort(prop.table(table(tableName[,i])))
                numberToCollapse<-length(sortedTable[sortedTable<(minPercent/100)])

                if (sum(sortedTable[1:numberToCollapse])<(minPercent/100))
                    {
                        numberToCollapse=numberToCollapse+1 #add next level if < minPercent
                    }

                if(numberToCollapse>1) #if not >1 then nothing to collapse
                {
                    lf <- names(sortedTable[1:numberToCollapse])
                    levels(tableName[,i])[levels(tableName[,i]) %in% lf] <- fillIn
                }
            }#end if a factor


        }#end for loop

    return(tableName)

}#end function
B_Miner
fuente
Para otro enfoque: stats.stackexchange.com/questions/227125/…
kjetil b halvorsen

Respuestas:

11

Parece que es solo una cuestión de "reenviar" el factor; no es necesario calcular sumas parciales o hacer una copia del vector original. P.ej,

set.seed(101)
a <- factor(LETTERS[sample(5, 150, replace=TRUE, 
                           prob=c(.1, .15, rep(.75/3,3)))])
p <- 1/5
lf <- names(which(prop.table(table(a)) < p))
levels(a)[levels(a) %in% lf] <- "Other"

Aquí, los niveles de factores originales se distribuyen de la siguiente manera:

 A  B  C  D  E 
18 23 35 36 38 

y luego se convierte

Other     C     D     E 
   41    35    36    38 

Puede estar convenientemente envuelto en una función. Hay una combine_factor()función en el paquete de remodelación , así que supongo que también podría ser útil.

Además, como parece interesado en la minería de datos, puede echar un vistazo al paquete de intercalación . Tiene muchas características útiles para el preprocesamiento de datos, incluidas funciones nearZeroVar()que permiten marcar predictores con una distribución muy desequilibrada de los valores observados (consulte la viñeta, datos de ejemplo, funciones de preprocesamiento, visualizaciones y otras funciones , p. 5, por ejemplo de uso).

chl
fuente
@CHI Gracias. He estudiado el paquete caret y lo he usado para ajustar los metaparámetros. ¡muy útil!.
B_Miner
@chl +1, buena. Escribí mi función únicamente porque el código a [niveles (a)% en% lf] <- "Otro" no funciona, así que asumí que el cambio de nivel de factor es un asunto complicado. Como de costumbre se vio después, de que R no es complicado, estoy :)
mpiktas
@mpiktas Thx. Se puede trabajar a nivel vectorial con, por ejemplo, a[as.character(a) %in% lf] <- lf[1]; a <- factor(droplevels(a), labels=c("Other",LETTERS[3:5])).
chl
+1. a [niveles (a)% en% lf] <- "Otro" seguro ahorra una tonelada de líneas de código. ¡Inteligente y eficiente!
Christopher Aden
Pero tenga en cuenta que un [a == "a"] <- "Otro" no funcionará, lo que para mí es bastante natural suponer que debería. Especialmente porque a [a == "a"] es perfectamente válido.
mpiktas
5

El único problema con la respuesta de Christopher es que mezclará el orden original del factor. Aquí está mi solución:

 Merge.factors <- function(x, p) {
     t <- table(x)
     levt <- cbind(names(t), names(t)) 
     levt[t/sum(t)<p, 2] <- "Other"
     change.levels(x, levt)
 }

¿Dónde change.levelsestá la siguiente función? Lo escribí hace algún tiempo, por lo que sospecho que podría haber mejores formas de lograr lo que hace.

 change.levels <- function(f, levt) {
     ##Change the the names of the factor f levels from
     ##substitution table levt.
     ## In the first column there are the original levels, in
     ## the second column -- the substitutes
     lv <- levels(f)
     if(sum(sort(lv) != sort(levt[, 1]))>0)
     stop ("The names from substitution table does not match given level names")
     res <- rep(NA, length(f))

     for(i in lv) {
          res[f==i] <- as.character(levt[levt[, 1]==i, 2])
     }
     factor(res)
}
mpiktas
fuente
4

Escribí una función rápida que logrará este objetivo. Soy un usuario novato de R, por lo que puede ser lento con tablas grandes.

Merge.factors <- function(x, p) { 
    #Combines factor levels in x that are less than a specified proportion, p.
    t <- table(x)
    y <- subset(t, prop.table(t) < p)
    z <- subset(t, prop.table(t) >= p)
    other <- rep("Other", sum(y))
    new.table <- c(z, table(other))
    new.x <- as.factor(rep(names(new.table), new.table))
    return(new.x)
}

Como ejemplo de ello en acción:

> a <- rep("a", 100)
> b <- rep("b", 1000)
> c <- rep("c", 1000)
> d <- rep("d", 1000)
> e <- rep("e", 400)
> f <- rep("f", 100)
> x <- factor(c(a, b, c, d, e, f))
> summary(x)
   a    b    c    d    e    f 
 100 1000 1000 1000  400  100 
> prop.table(table(x))
x
         a          b          c          d          e          f 
0.02777778 0.27777778 0.27777778 0.27777778 0.11111111 0.02777778 
> 
> w <- Merge.factors(x, .05)
> summary(w)
    b     c     d     e Other 
 1000  1000  1000   400   200 
> class(w)
[1] "factor"
Christopher Aden
fuente
Gracias por la observación, John. Lo he cambiado un poco para que sea un factor. Sin embargo, todo lo que hice fue rehacer el vector original de la tabla, por lo que si hay una manera de omitir ese paso, será más rápido.
Christopher Aden
Gracias a todos los que respondieron. Mi R es débil, pero la capacidad de hacerlo con tan pocas líneas de código es un testimonio de lo poderoso que es y me hace querer aprender.
B_Miner