2012-04-09 15 views
21

Tengo un dataframe y me gustaría aplicar una función que tome los valores de tres columnas y calcule la diferencia mínima entre los tres valores.use varias columnas como variables con sapply

#dataset 
df <- data.frame(a= sample(1:100, 10),b = sample(1:100, 10),c= sample(1:100, 10)) 

#function 
minimum_distance <- function(a,b,c) 
{ 
    dist1 <- abs(a-b) 
    dist2 <- abs(a-c) 
    dist3 <- abs(b-c) 
    return(min(dist1,dist2,dist3)) 
} 

Busco algo como:

df$distance <- sapply(df, function(x) minimum_distance(x$a,x$b,x$c)) 
## errormessage 
Error in x$a : $ operator is invalid for atomic vectors 

Mientras que puedo utilizar ddply:

df2 <- ddply(df,.(a),function(r) {data.frame(min_distance=minimum_distance(r$a,r$b, r$c))}, .drop=FALSE) 

Esto no impide que todas las columnas. ¿Alguna sugerencia?

Edit: terminó usando:

df$distance <- mapply(minimum_distance, df$a, df$b, df$c) 

Respuesta

38

Trate mapply():

qq <- mapply(minimum_distance, df$a, df$b, df$c) 
+0

simple y elegante. gracias – zach

+0

¿Cuál es el más rápido? o más eficiente? – Bharath

6

intente esto:

do.call("mapply", c(list(minimum_distance), df)) 

pero se puede escribir versión vectorizada:

pminimum_distance <- function(a,b,c) 
{ 
dist1 <- abs(a-b) 
dist2 <- abs(a-c) 
dist3 <- abs(b-c) 
return(pmin(dist1,dist2,dist3)) 
} 
pminimum_distance(df$a, df$b, df$c) 

# or 
do.call("pminimum_distance", df) 
+0

esto es inteligente pero un poco menos directo gracias mapply. – zach

4

Sé que esto ha sido contestada pero en realidad tomaría un enfoque diferente que toma cualquier número de columnas y es más generalizable utilizando un enfoque de afuera;

vdiff <- function(x){ 
    y <- outer(x, x, "-") 
    min(abs(y[lower.tri(y)])) 
} 

apply(df, 1, vdiff) 

Creo que esto es un poco más limpio y flexible.

EDIT: Por los comentarios de zach, propongo esta función más formalizada que también funciona en marcos de datos con columnas no numéricas quitándolos y actuando solo en las columnas numéricas.

cdif <- function(dataframe){ 
    df <- dataframe[, sapply(dataframe, is.numeric)] 
    vdiff <- function(x){ 
     y <- outer(x, x, "-") 
     min(abs(y[lower.tri(y)])) 
    } 
    return(apply(df, 1, vdiff)) 
} 

#TEST it out 
set.seed(10) 
(df <- data.frame(a = sample(1:100, 10), b = sample(1:100, 10), 
    c = sample(1:100, 10), d = LETTERS[1:10])) 

cdif(df) 
+0

buena idea. mi marco de datos real no es una matriz, sin embargo, ¿podría modificarse para usar en un marco de datos con columnas de texto? algo así como externo (x, x, "-", drop_string = T)? – zach

+0

La función 'outer' no significa necesariamente que estás trabajando en una matriz. Simplemente toma dos vectores y una función y hace una matriz de todas las combinaciones posibles para esos dos vectores. Aquí solo proporciono el mismo vector (la fila) al exterior dos veces y la resta de función operador '-'. Agregué un poco a mi solución para hacer una función independiente que actúe sobre marcos de datos y excluya todo lo que no sea numérico. 'outer' puede ser muy poderoso. Desearía poder recordar usarlo más. En cuanto a drop_string = T? No hay suerte, pero 'sapply' con una consulta' is.numeric' funciona bien. –

+0

muy agradable. Estoy de acuerdo en que lo externo es bastante poderoso y que para una matriz más grande este sería el camino a seguir en lugar de especificar cada columna o valor. – zach

0

Su mejor escribir una función y luego usar mapply en los vectores:

f1 <- function(a,b,c){ 
d =abs(a-b) 
e =abs(b-c) 
f= abs(c-a) 
return(pmin(d,e,f)) 
} 

qq <- mapply(f1, df$a, df$b, df$c)