[R-es] Encontrar la primera columna no NA
Adolfo Álvarez
adalvarez en gmail.com
Vie Oct 28 13:48:25 CEST 2016
Hola a todos, me ha gustado mucho la solución de Carlos, muy eficiente y
muy ingeniosa al utilizar la funcion col() que o no la conocia o no me
acordaba de ella.
La parte mas "lenta" sigue siendo el apply que en el fondo no es mas que un
ciclo for a traves de las filas, asi que inspirado por el metodo de Carlos
pense que podria ser mas rapido si iteramos a traves de las columnas por lo
que en general seran menos iteraciones. He incluido esta modificacion en el
benchmark, es un poco menos elegante que la original de Carlos pero algo
mas rapida. Seguro que aun se puede mejorar un poco mas en R base o
incorporar Rcpp, pero creo que al menos por mi parte llego hasta aqui.
Muy interesante tanto el problema como las soluciones propuestas, un saludo!
Adolfo.
library(microbenchmark)
library(data.table)
library(dplyr)
library(tidyr)
set.seed(123456)
numero <- 1e5
N <- 1e1
tabla <-
microbenchmark(
JVG ={
dat <-
data.table( Uno = sample( c(runif(numero) , rep(NA , numero /2e0
)) , size = numero ) ,
dos = sample( c(runif(numero) , rep(NA , numero /1e1
)) , size = numero ) ,
tres = sample( c(runif(numero) , rep(NA , numero /2e1
)) , size = numero ) ,
cuatro = sample( c(runif(numero) , rep(NA , numero /1e2
)) , size = numero ) ,
cinco = sample( c(runif(numero) , rep(NA , numero /2e2
)) , size = numero ) ,
seis = sample( c(runif(numero) , rep(NA , numero /1e3
)) , size = numero )
)
First_month <-
apply(X = dat, MARGIN = 1, FUN =
function(x){
return( min( which( !is.na(x) ), na.rm = TRUE ) )
}
)
dat[ , First_month := First_month]
N_for <- length( unique(First_month ))
for( j in 1:N_for){
x <- dat[ First_month == j, j, with = FALSE]
dat[ First_month == j , Value_First_month := x ]
}
},
Olivier ={
dat <-
data.table( Uno = sample( c(runif(numero) , rep(NA , numero /2e0
)) , size = numero ) ,
dos = sample( c(runif(numero) , rep(NA , numero /1e1
)) , size = numero ) ,
tres = sample( c(runif(numero) , rep(NA , numero /2e1
)) , size = numero ) ,
cuatro = sample( c(runif(numero) , rep(NA , numero /1e2
)) , size = numero ) ,
cinco = sample( c(runif(numero) , rep(NA , numero /2e2
)) , size = numero ) ,
seis = sample( c(runif(numero) , rep(NA , numero /1e3
)) , size = numero )
)
dat[,First_month := apply(X = .SD,MARGIN = 1,FUN = function(x)
colnames(.SD)[min(which(!is.na(x)))])]
dat[,Value_First_month := apply(X = .SD,MARGIN = 1,FUN = function(x)
x[min(which(!is.na(x)))])]
},
Olivier2={
dat <-
data.table( Uno = sample( c(runif(numero) , rep(NA , numero /2e0
)) , size = numero ) ,
dos = sample( c(runif(numero) , rep(NA , numero /1e1
)) , size = numero ) ,
tres = sample( c(runif(numero) , rep(NA , numero /2e1
)) , size = numero ) ,
cuatro = sample( c(runif(numero) , rep(NA , numero /1e2
)) , size = numero ) ,
cinco = sample( c(runif(numero) , rep(NA , numero /2e2
)) , size = numero ) ,
seis = sample( c(runif(numero) , rep(NA , numero /1e3
)) , size = numero )
)
dat[,jugador:=1:.N]
dat2=melt(dat,id.vars="jugador")
setkey(dat2,jugador)
dat2[,index:=min(which(!is.na(value))),by=jugador]
dat3 <- dat2[,list(First_month_Olivier
=variable[index[1]],Value_First_month_Olivier
=value[index[1]]),by=jugador]
setkey(x = dat, jugador)
dat0 <- merge( x = dat, y = dat3, all.x = TRUE, all.y = FALSE)
},
Adolfo = {
dat <-
data.table( Uno = sample( c(runif(numero) , rep(NA , numero /2e0
)) , size = numero ) ,
dos = sample( c(runif(numero) , rep(NA , numero /1e1
)) , size = numero ) ,
tres = sample( c(runif(numero) , rep(NA , numero /2e1
)) , size = numero ) ,
cuatro = sample( c(runif(numero) , rep(NA , numero /1e2
)) , size = numero ) ,
cinco = sample( c(runif(numero) , rep(NA , numero /2e2
)) , size = numero ) ,
seis = sample( c(runif(numero) , rep(NA , numero /1e3
)) , size = numero )
)
# 1) Creamos una columna con la informacion de los jugadores,
# Como es un jugador por fila, hacemos 1:nrow.
step1 <- dat %>%
mutate(player = 1:nrow(dat))
#2) Convertimos las columnas de tiempo (uno, dos, tres, ...) en dos
# columnas, mes y numero de juegos. (Ojo, asumimos que en los datos
las
# columnas estan ordenadas como en
el ejemplo, es decir uno, dos, tres y no
# tres, uno, dos)
#
step2 <- gather(step1, month, games, -player)
#y 3) Filtramos los meses con NA y por cada jugador nos quedamos con
# el primer dato:
step3 <- step2 %>%
filter(!is.na(games)) %>%
group_by(player) %>%
slice(1)
},
Olivier3 = {
dat <-
data.table( Uno = sample( c(runif(numero) , rep(NA , numero /2e0
)) , size = numero ) ,
dos = sample( c(runif(numero) , rep(NA , numero /1e1
)) , size = numero ) ,
tres = sample( c(runif(numero) , rep(NA , numero /2e1
)) , size = numero ) ,
cuatro = sample( c(runif(numero) , rep(NA , numero /1e2
)) , size = numero ) ,
cinco = sample( c(runif(numero) , rep(NA , numero /2e2
)) , size = numero ) ,
seis = sample( c(runif(numero) , rep(NA , numero /1e3
)) , size = numero )
)
M=as.matrix(dat)
index <- which(!is.na(M)) - 1
meses<-colnames(M)
M2<- data.table(columna=index %/% nrow(M) +1L, jugador=index %%
nrow(M) +1L , valor=M[index+1L])
setkey(M2,jugador,columna)
M2[,.(First_month=meses[columna[1]],Value_First_month=valor[1]),by=jugador]
},
GilBellosta = {
dat <-
data.frame( Uno = sample( c(runif(numero) , rep(NA , numero /2e0
)) , size = numero ) ,
dos = sample( c(runif(numero) , rep(NA , numero /1e1
)) , size = numero ) ,
tres = sample( c(runif(numero) , rep(NA , numero /2e1
)) , size = numero ) ,
cuatro = sample( c(runif(numero) , rep(NA , numero /1e2
)) , size = numero ) ,
cinco = sample( c(runif(numero) , rep(NA , numero /2e2
)) , size = numero ) ,
seis = sample( c(runif(numero) , rep(NA , numero /1e3
)) , size = numero )
)
tmp <- (as.matrix(dat))
cols <- col(tmp)
cols[is.na(tmp)] <- Inf
my.cols <- apply(cols, 1, min)
my.values <- tmp[cbind(1:nrow(tmp), my.cols)]
},
Adolfo2 = {
dat <-
data.frame( Uno = sample( c(runif(numero) , rep(NA , numero /2e0
)) , size = numero ) ,
dos = sample( c(runif(numero) , rep(NA , numero /1e1
)) , size = numero ) ,
tres = sample( c(runif(numero) , rep(NA , numero /2e1
)) , size = numero ) ,
cuatro = sample( c(runif(numero) , rep(NA , numero /1e2
)) , size = numero ) ,
cinco = sample( c(runif(numero) , rep(NA , numero /2e2
)) , size = numero ) ,
seis = sample( c(runif(numero) , rep(NA , numero /1e3
)) , size = numero )
)
tmp <- (as.matrix(dat))
cols <- col(tmp)
cols[is.na(tmp)] <- NA
my.cols <- cols[,ncol(cols)]
for (j in (ncol(cols)-1):1){
my.cols <- ifelse(is.na(cols[,j]), my.cols, cols[,j])
}
my.values <- tmp[cbind(1:nrow(tmp), my.cols)]
},
times = N, unit = "s")
> tabla
Unit: seconds
expr min lq mean median uq max
neval
JVG 1.0458327 1.3045354 1.3660296 1.3486868 1.4004353 2.0389759
10
Olivier 4.4031746 4.6501372 4.9638930 4.9841975 5.2855783 5.5569627
10
Olivier2 1.7937688 2.1531256 2.4749540 2.5052893 2.8389349 3.0933835
10
Adolfo 0.3520900 0.3615358 0.4764479 0.3942295 0.5072621 1.0266727
10
Olivier3 0.3936536 0.4454847 0.5254894 0.4784246 0.5269834 0.8900983
10
GilBellosta 0.2721629 0.3097020 0.3901691 0.3466332 0.4294069 0.7126116
10
Adolfo2 0.1110292 0.1611071 0.1812212 0.1639743 0.2007791 0.2948245
10
[[alternative HTML version deleted]]
Más información sobre la lista de distribución R-help-es