transformar recodificar numericas discretizar datos convertir commander categorizar categoricas agrupar r r-factor recode

recodificar - Idioma para la recodificación de estilo ifelse para múltiples categorías



transformar variables en r (12)

Me encuentro con esto con la frecuencia suficiente para darme cuenta de que tiene que haber un buen lenguaje para ello. Supongamos que tengo un data.frame con un montón de atributos, incluido "producto". También tengo una llave que traduce los productos a la marca + tamaño. Los códigos de producto 1-3 son Tylenol, 4-6 son Advil, 7-9 son Bayer, 10-12 son genéricos.

¿Cuál es la forma más rápida (en términos de tiempo humano) de codificar esto?

Tiendo a usar los ifelse anidados si hay 3 o menos categorías, y escribo la tabla de datos y la fusiono si hay más de 3. ¿Alguna idea mejor? Stata tiene un comando de recode que es bastante ingenioso para este tipo de cosas, aunque creo que promueve la mezcla de códigos de datos un poco demasiado.

dat <- structure(list(product = c(11L, 11L, 9L, 9L, 6L, 1L, 11L, 5L, 7L, 11L, 5L, 11L, 4L, 3L, 10L, 7L, 10L, 5L, 9L, 8L)), .Names = "product", row.names = c(NA, -20L), class = "data.frame")


A menudo utilizo la técnica a continuación:

key <- c() key[1:3] <- "Tylenol" key[4:6] <- "Advil" key[7:9] <- "Bayer" key[10:12] <- "Generic"

Entonces,

> key[dat$product] [1] "Generic" "Generic" "Bayer" "Bayer" "Advil" "Tylenol" "Generic" "Advil" "Bayer" "Generic" [11] "Advil" "Generic" "Advil" "Tylenol" "Generic" "Bayer" "Generic" "Advil" "Bayer" "Bayer"


Algo más legible que los ifelse anidados:

unlist(lapply(as.character(dat$product), switch, `1`=,`2`=,`3`=''tylenol'', `4`=,`5`=,`6`=''advil'', `7`=,`8`=,`9`=''bayer'', `10`=,`11`=,`12`=''generic''))

Advertencia: no es muy eficiente.


El "enfoque de base de datos" es mantener una tabla separada (un data.frame) para las definiciones de las claves de su producto. Tiene más sentido ya que dice que las claves de sus productos se traducen no solo en una marca, sino también en un tamaño:

product.keys <- read.table(textConnection(" product brand size 1 Tylenol small 2 Tylenol medium 3 Tylenol large 4 Advil small 5 Advil medium 6 Advil large 7 Bayer small 8 Bayer medium 9 Bayer large 10 Generic small 11 Generic medium 12 Generic large "), header = TRUE)

Luego, puedes unir tus datos usando merge :

merge(dat, product.keys, by = "product") # product brand size # 1 1 Tylenol small # 2 3 Tylenol large # 3 4 Advil small # 4 5 Advil medium # 5 5 Advil medium # 6 5 Advil medium # 7 6 Advil large # 8 7 Bayer small # 9 7 Bayer small # 10 8 Bayer medium # 11 9 Bayer large # 12 9 Bayer large # 13 9 Bayer large # 14 10 Generic small # 15 10 Generic small # 16 11 Generic medium # 17 11 Generic medium # 18 11 Generic medium # 19 11 Generic medium # 20 11 Generic medium

Como puede observar, el orden de las filas no se conserva mediante la merge . Si esto es un problema, el paquete plyr tiene una función de join que mantiene el orden:

library(plyr) join(dat, product.keys, by = "product") # product brand size # 1 11 Generic medium # 2 11 Generic medium # 3 9 Bayer large # 4 9 Bayer large # 5 6 Advil large # 6 1 Tylenol small # 7 11 Generic medium # 8 5 Advil medium # 9 7 Bayer small # 10 11 Generic medium # 11 5 Advil medium # 12 11 Generic medium # 13 4 Advil small # 14 3 Tylenol large # 15 10 Generic small # 16 7 Bayer small # 17 10 Generic small # 18 5 Advil medium # 19 9 Bayer large # 20 8 Bayer medium

Finalmente, si sus tablas son grandes y la velocidad es un problema, considere usar data.tables (del paquete data.table ) en lugar de data.frames.


Este requiere algunos tipos de escritura, pero si realmente tiene un gran conjunto de datos, este puede ser el camino a seguir. Bryangoodrich y Dason en talkstats.com me enseñaron este. Está utilizando una tabla hash o creando un entorno que contiene una tabla de consulta. De hecho, mantengo este en mi. Perfil (la función hash que es) para consultas de tipo de diccionario.

Repliqué tus datos 1000 veces para hacerlos un poco más grandes.

################################################# # THE HASH FUNCTION (CREATES A ENW ENVIRONMENT) # ################################################# hash <- function(x, type = "character") { e <- new.env(hash = TRUE, size = nrow(x), parent = emptyenv()) char <- function(col) assign(col[1], as.character(col[2]), envir = e) num <- function(col) assign(col[1], as.numeric(col[2]), envir = e) FUN <- if(type=="character") char else num apply(x, 1, FUN) return(e) } ################################### # YOUR DATA REPLICATED 1000 TIMES # ################################### dat <- dat <- structure(list(product = c(11L, 11L, 9L, 9L, 6L, 1L, 11L, 5L, 7L, 11L, 5L, 11L, 4L, 3L, 10L, 7L, 10L, 5L, 9L, 8L)), .Names = "product", row.names = c(NA, -20L), class = "data.frame") dat <- dat[rep(seq_len(nrow(dat)), 1000), , drop=FALSE] rownames(dat) <-NULL dat ######################### # CREATE A LOOKUP TABLE # ######################### med.lookup <- data.frame(val=as.character(1:12), med=rep(c(''Tylenol'', ''Advil'', ''Bayer'', ''Generic''), each=3)) ######################################## # USE hash TO CREATE A ENW ENVIRONMENT # ######################################## meds <- hash(med.lookup) ############################## # CREATE A RECODING FUNCTION # ############################## recoder <- function(x){ x <- as.character(x) #turn the numbers to character rc <- function(x){ if(exists(x, env = meds))get(x, e = meds) else NA } sapply(x, rc, USE.NAMES = FALSE) } ############# # HASH AWAY # ############# recoder(dat[, 1])

En este caso, el hashing es lento, pero si tiene más niveles para recodificar, aumentará la velocidad sobre otros.


Me gusta la función de recode en el paquete de car :

library(car) dat$brand <- recode(dat$product, recodes="1:3=''Tylenol'';4:6=''Advil'';7:9=''Bayer'';10:12=''Generic''") # > dat # product brand # 1 11 Generic # 2 11 Generic # 3 9 Bayer # 4 9 Bayer # 5 6 Advil # 6 1 Tylenol # 7 11 Generic # 8 5 Advil # 9 7 Bayer # 10 11 Generic # 11 5 Advil # 12 11 Generic # 13 4 Advil # 14 3 Tylenol # 15 10 Generic # 16 7 Bayer # 17 10 Generic # 18 5 Advil # 19 9 Bayer # 20 8 Bayer


Otra versión, que funcionaría en este caso:

c("Tylenol","Advil","Bayer","Generic")[(dat$product %/% 3.1) + 1]


Para completar (y, probablemente, la solución más rápida y sencilla), se puede crear un vector denominado y usarlo para la búsqueda. Crédito: http://adv-r.had.co.nz/Subsetting.html#applications

product.code <- c(1=''Tylenol'', 2=''Tylenol'', 3=''Tylenon'', 4=''Advil'', 5 =''Advil'', 6=''Advil'', 7=''Bayer'', 8=''Bayer'', 9=''Bayer'', 10=''Generic'', 11=''Generic'', 12=''Generic'')

Para obtener la salida

$unname(product.code[dat$product])

Bench-mark para velocidad con las mejores soluciones.

$microbenchmark( named_vector = unname(product.code[dat$product]), find.key = find.key(dat$product, brands), levels = `levels<-`(factor(dat$product),brands)) Unit: microseconds expr min lq mean median uq max neval named_vector 11.777 20.4810 26.12832 23.0410 28.1610 207.360 100 find.key 34.305 55.8090 58.75804 59.1370 65.5370 130.049 100 levels 143.361 224.7685 234.02545 247.5525 255.7445 338.944 100

Esta solución es muy similar a la solución de @Kohske pero funcionaría para búsquedas no numéricas.


Podría convertir su variable en un factor y cambiar sus niveles por levels<- función. En una orden podría ser como:

`levels<-`( factor(dat$product), list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12) )

En pasos:

brands <- factor(dat$product) levels(brands) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)


Si tiene códigos en grupos secuenciales como en el ejemplo, esto puede cut la mostaza:

cut(dat$product,seq(0,12,by=3),labels=c("Tylenol","Advil","Bayer","Generic")) [1] Generic Generic Bayer Bayer Advil Tylenol Generic Advil Bayer [10] Generic Advil Generic Advil Tylenol Generic Bayer Generic Advil [19] Bayer Bayer Levels: Tylenol Advil Bayer Generic


También hay arules:discretize , pero me gusta menos porque te hace separar las etiquetas del rango de valores:

library(arules) discretize( dat$product, method = "fixed", categories = c( 1,3,6,9,12 ), labels = c("Tylenol","Advil","Bayer","Generic") ) [1] Generic Generic Generic Generic Bayer Tylenol Generic Advil Bayer Generic Advil Generic Advil Advil Generic Bayer Generic Advil Generic Bayer Levels: Tylenol Advil Bayer Generic


Tiendo a usar esta función:

recoder <- function (x, from = c(), to = c()) { missing.levels <- unique(x) missing.levels <- missing.levels[!missing.levels %in% from] if (length(missing.levels) > 0) { from <- append(x = from, values = missing.levels) to <- append(x = to, values = missing.levels) } to[match(x, from)] }

Como en:

recoder(x = dat$product, from = 1:12, to = c(rep("Product1", 3), rep("Product2", 3), rep("Product3", 3), rep("Product4", 3)))


Uno podría usar una lista como una matriz asociativa para definir la brand -> product code mapeo de brand -> product code , es decir:

brands <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)

Una vez que tenga esto, puede invertirlo para crear un product code -> brand lista de product code -> brand (podría necesitar mucha memoria), o simplemente usar una función de búsqueda:

find.key <- function(x, li, default=NA) { ret <- rep.int(default, length(x)) for (key in names(li)) { ret[x %in% li[[key]]] <- key } return(ret) }

Estoy seguro de que hay mejores formas de escribir esta función (¡el bucle for me está molestando!), Pero al menos está vectorizado, por lo que solo se requiere un solo paso a través de la lista.

Usarlo sería algo como:

> dat$brand <- find.key(dat$product, brands) > dat product brand 1 11 Generic 2 11 Generic 3 9 Bayer 4 9 Bayer 5 6 Advil 6 1 Tylenol 7 11 Generic 8 5 Advil 9 7 Bayer 10 11 Generic 11 5 Advil 12 11 Generic 13 4 Advil 14 3 Tylenol 15 10 Generic 16 7 Bayer 17 10 Generic 18 5 Advil 19 9 Bayer 20 8 Bayer

Las soluciones de recode y levels<- son muy agradables, pero también son mucho más lentas que esta (y una vez que hayas encontrado la find.key es más fácil para los humanos que recode y estar a la par con los levels<- ):

> microbenchmark( recode=recode(dat$product,recodes="1:3=''Tylenol'';4:6=''Advil'';7:9=''Bayer'';10:12=''Generic''"), find.key=find.key(dat$product, brands), levels=`levels<-`(factor(dat$product),brands)) Unit: microseconds expr min lq median uq max 1 find.key 64.325 69.9815 76.8950 83.8445 221.748 2 levels 240.535 248.1470 274.7565 306.8490 1477.707 3 recode 1636.039 1683.4275 1730.8170 1855.8320 3095.938

(No puedo lograr que la versión del switch realice una evaluación comparativa correctamente, pero parece ser más rápida que todas las anteriores, aunque es aún peor para los humanos que la solución de recode ).