¿Cómo escribo una función en r para hacer cacluciones en un registro?

Nov 08 2020

En C # estoy acostumbrado al concepto de conjunto de datos y registro actual. Me resultaría fácil escribir una función de precio calculado complicada con condiciones en el registro actual.

Tengo problemas para entender cómo hacer esto en r.

Intenté lo siguiente

   train <- read.csv("Train.csv" )
   df <- as.data.frame.matrix(train)
   v = c(  df$Fuel.Type ,df$No.Gears)
   names(v ) <- c( "FuelType" ,"NoGears")
   df$FEType = FEType( v)

Donde mi función se define como

FEType <- function(v    ){
  ret="Low"
  if (v["FuelType"]=='G') {
    ret ="High"
  }
  return(ret)
}

Esto no funciona como esperaba y cuando examino v veo que contiene totales agregados en lugar de la fila actual que esperaba.

¿Dónde me equivoco?

En la pregunta aquí veo algunas sugerencias en el último párrafo.

Para reproducir el problema, indicando lo que quiero hacer, tengo

IsPretty <-function(PetalWidth){
  if (PetalWidth  >0.3) return("Y")
  return("N")
}

df <- iris
df$Pretty = IsPretty(df$Petal.Width)
    

Esto da el error

la condición tiene una longitud> 1 y solo se utilizará el primer elemento

Lo que me llevó a buscar vectores. Pero no estoy seguro de que sea la dirección correcta.

[Actualizar]

Estoy acostumbrado a pensar en tablas y registros actuales. Por eso estaba pensando que

df$Pretty = IsPretty(df$Petal.Width)

tendría el efecto de agregar una columna a mi marco de datos con la propiedad isPretty calculada

¿Por qué no puedo incluir condiciones if en mi cálculo?

Respuestas

4 GregorThomas Nov 09 2020 at 03:28

La vectorización es una de las cosas más fundamentales (e inusuales) a las que deberá acostumbrarse en R. Muchas (¿la mayoría?) De las operaciones R están vectorizadas. Pero algunas cosas no lo son, y if(){}else{}es una de las cosas no vectorizadas. Se utiliza para controlar el flujo (ya sea para ejecutar o no un bloque de código), no para operaciones vectoriales. ifelse()es una función separada que se usa para vectores, donde el primer argumento es una "prueba", y el segundo y tercer argumento son los resultados "si es así" y "si no". La prueba es un vector y el valor devuelto es el resultado sí / no apropiado para cada elemento de la prueba. El resultado tendrá la misma duración que la prueba .

Entonces escribiríamos su IsPrettyfunción así:

IsPretty <- function(PetalWidth){
  return(ifelse(PetalWidth > 0.3, "Y", "N"))
}

df <- iris
df$Pretty = IsPretty(df$Petal.Width)

A diferencia de un if(){...}else{...}bloque donde la condición de prueba es de longitud uno, y se puede ejecutar código arbitrario en el ...- puede devolver un resultado mayor que la prueba, o un resultado menor o ningún resultado - podría modificar otros objetos ... cualquier cosa dentro if(){}else(), pero la condición de prueba debe tener una longitud de 1.

Puede usar su IsPrettyfunción una fila a la vez; funcionará bien para cualquier fila. Así que podríamos ponerlo en un ciclo como se muestra a continuación, verificando una fila a la vez, dando if()una prueba a la vez, asignando resultados uno a la vez. Pero R está optimizado para la vectorización, y esto será notablemente más lento y es un mal hábito.

IsPrettyIf <-function(PetalWidth){
  if (PetalWidth  >0.3) return("Y")
  return("N")
}

for(i in 1:nrow(df)) {
  df$PrettyLoop[i] = IsPrettyIf(df$Petal.Width[i])
}

Un punto de referencia a continuación muestra que la versión vectorizada es 50 veces más rápida. Este es un caso tan simple y con datos tan pequeños que no importa mucho, pero en datos más grandes o con operaciones más complejas, la diferencia entre código vectorizado y no vectorizado puede ser de minutos frente a días.

microbenchmark::microbenchmark(
  loop = {
    for(i in 1:nrow(df)) {
      df$PrettyLoop[i] = IsPrettyIf(df$Petal.Width[i])
    }
  },
  vectorized = {
    df$Pretty = IsPretty(df$Petal.Width)    
  }
)
Unit: microseconds
       expr    min     lq     mean median      uq     max neval
       loop 3898.9 4365.6 5880.623 5442.3 7041.10 11344.6   100
 vectorized   47.7   59.6  112.288   67.4   83.85  1819.4   100

Este es un problema común para los estudiantes de R: puede encontrar muchas preguntas en Stack Overflow donde las personas usan if(){}else{}cuando lo necesitan ifelse()o viceversa. ¿Por qué no puedo ifelsedevolver vectores? es una pregunta frecuente que proviene del lado opuesto del problema.


¿Qué sucede en tu intento?

df <- iris

## The condition has length equal to the number of rows in the data frame
df$Petal.Width > 0.3 #> [1] FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE #> [13] FALSE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE TRUE FALSE TRUE ## ... truncated ## R warns us that only the first value (which happens to be FALSE) is used result = if(df$Petal.Width > 0.3) {"Y"} else {"N"}
#> Warning in if (df$Petal.Width > 0.3) {: the condition has length > 1 and only #> the first element will be used ## So the result is a single "N" result #> [1] "N" length(result) #> [1] 1 ## R "recycles" inputs that are of insufficient length ## so we get a full column of "N" df$Pretty = result
head(df)
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species Pretty
#> 1          5.1         3.5          1.4         0.2  setosa      N
#> 2          4.9         3.0          1.4         0.2  setosa      N
#> 3          4.7         3.2          1.3         0.2  setosa      N
#> 4          4.6         3.1          1.5         0.2  setosa      N
#> 5          5.0         3.6          1.4         0.2  setosa      N
#> 6          5.4         3.9          1.7         0.4  setosa      N

Creado el 2020-11-08 por el paquete reprex (v0.3.0)

KirstenGreed Nov 09 2020 at 06:32

Para mis propias notas sobre la respuesta de Gregor

IsPrettyIf <-function(row){
 ret ="N"  
 if(row$Petal.Width > 0.3) { ret="Y"} return(ret) } df <- iris df$PrettyLoop ="" # add a column and initialize all the cells to be empty
for(i in 1:5) {
  df$PrettyLoop[i] = IsPrettyIf(df[i,]) cat("Row",i, "is Pretty?",df$PrettyLoop[i],"\n")
}

Lo que me hace tropezar es esa fila$PrettyLoop is like a cell and df$PrettyLoop es como una columna, pensando con la analogía de la hoja de cálculo.