¿Cuál es el mayor R-gotcha que te has encontrado?

¿Hay un cierto R-gotcha que realmente te haya sorprendido algún día? Creo que todos nos beneficiaríamos compartiendo esto.

Aquí está el mío: en la indexación de listas, my.list[[1]] no es my.list[1] . Aprendí esto en los primeros días de R.

La eliminación de filas en un dataframe hará que se agreguen filas con nombres no exclusivos, que luego se descartarán:

 > a<-data.frame(c(1,2,3,4),c(4,3,2,1)) > a<-a[-3,] > a c.1..2..3..4. c.4..3..2..1. 1 1 4 2 2 3 4 4 1 > a[4,1]<-1 > a Error in data.frame(c.1..2..3..4. = c("1", "2", "4", "1"), c.4..3..2..1. = c(" 4", : duplicate row.names: 4 

Entonces, ¿qué está pasando aquí es:

  1. Se crea un data.frame de cuatro filas, por lo que los nombres de las filas son c (1,2,3,4)

  2. La tercera fila se elimina, por lo que los nombres de las filas son c (1,2,4)

  3. Se agrega una cuarta fila, y R automáticamente establece el nombre de la fila igual al índice, es decir, 4, por lo que los nombres de las filas son c (1,2,4,4). Esto es ilegal porque los nombres de las filas deben ser únicos. No veo por qué este tipo de comportamiento debería ser permitido por R. Me parece que R debería proporcionar un nombre de fila único.

[Hadley señaló esto en un comentario ]

Cuando se utiliza una secuencia como índice para la iteración, es mejor usar la función seq_along() lugar de algo como 1:length(x) .

Aquí creo un vector y ambos enfoques devuelven lo mismo:

 > x <- 1:10 > 1:length(x) [1] 1 2 3 4 5 6 7 8 9 10 > seq_along(x) [1] 1 2 3 4 5 6 7 8 9 10 

Ahora haz el vector NULL :

 > x <- NULL > seq_along(x) # returns an empty integer; good behavior integer(0) > 1:length(x) # wraps around and returns a sequence; this is bad [1] 1 0 

Esto puede causar cierta confusión en un bucle:

 > for(i in 1:length(x)) print(i) [1] 1 [1] 0 > for(i in seq_along(x)) print(i) > 

La creación automática de factores cuando carga datos. Sin pensarlo, trata una columna en un dataframe como caracteres, y esto funciona bien hasta que haga algo como tratar de cambiar un valor por uno que no sea un nivel. Esto generará una advertencia, pero deja tu dataframe con NA en él …

Cuando algo va inesperadamente mal en su secuencia de comandos R, compruebe que los factores no son los culpables.

Olvidar el argumento drop = FALSE en subconjuntos de matrices hasta una única dimensión y, por lo tanto, descartar también la clase de objeto:

 R> X <- matrix(1:4,2) R> X [,1] [,2] [1,] 1 3 [2,] 2 4 R> class(X) [1] "matrix" R> X[,1] [1] 1 2 R> class(X[,1]) [1] "integer" R> X[,1, drop=FALSE] [,1] [1,] 1 [2,] 2 R> class(X[,1, drop=FALSE]) [1] "matrix" R> 

Primero, permítanme decir que entiendo los problemas fundamentales de representar números en un sistema binario. Sin embargo, un problema que creo que podría mejorarse fácilmente es la representación de los números cuando el valor decimal está más allá del scope de presentación típico de R.

 x <- 10.2 * 100 x 1020 as.integer(x) 1019 

No me importa si el resultado se representa como un número entero cuando realmente se puede representar como un número entero. Por ejemplo, si el valor realmente era 1020, imprimirlo para x estaría bien. Pero algo tan simple como 1020.0 en este caso al imprimir x habría hecho más obvio que el valor no era un número entero ni representable como uno. R debe establecer un valor predeterminado para algún tipo de indicación cuando hay un componente decimal extremadamente pequeño que no se presenta.

Puede ser molesto tener que permitir combinaciones de NA , NaN e Inf . Se comportan de manera diferente, y las pruebas para uno no necesariamente funcionarán para los demás:

 > x <- c(NA,NaN,Inf) > is.na(x) [1] TRUE TRUE FALSE > is.nan(x) [1] FALSE TRUE FALSE > is.infinite(x) [1] FALSE FALSE TRUE 

Sin embargo, la manera más segura de probar cualquiera de estos alborotadores es:

 > is.finite(x) [1] FALSE FALSE FALSE 

¡Siempre prueba lo que sucede cuando tienes una NA !

Una cosa a la que siempre debo prestar especial atención (después de muchas experiencias dolorosas) son los valores de NA . Las funciones R son fáciles de usar, pero ninguna forma de progtwigción solucionará problemas con sus datos.

Por ejemplo, cualquier operación vectorial neta con una NA es igual a NA . Esto es “sorprendente” a primera vista:

 > x <- c(1,1,2,NA) > 1 + NA [1] NA > sum(x) [1] NA > mean(x) [1] NA 

Esto se extrapola a otras funciones de nivel superior.

En otras palabras, los valores perdidos frecuentemente tienen tanta importancia como los valores medidos por defecto . Muchas funciones tienen valores predeterminados na.rm=TRUE/FALSE ; vale la pena dedicar un tiempo a decidir cómo interpretar estas configuraciones predeterminadas.

Edición 1: Marek es un gran punto. NA valores de NA también pueden causar un comportamiento confuso en los índices. Por ejemplo:

 > TRUE && NA [1] NA > FALSE && NA [1] FALSE > TRUE || NA [1] TRUE > FALSE || NA [1] NA 

Esto también es cierto cuando intenta crear una expresión condicional (para una instrucción if):

 > any(c(TRUE, NA)) [1] TRUE > any(c(FALSE, NA)) [1] NA > all(c(TRUE, NA)) [1] NA 

Cuando estos valores NA terminan como sus índices vectoriales, pueden suceder muchas cosas inesperadas. Todo esto es un buen comportamiento para R, porque significa que debe tener cuidado con los valores perdidos. Pero puede causar dolores de cabeza importantes al principio.

Olvidando que strptime() y amigos devuelven POSIXt POSIXlt donde length() siempre es nueve: la conversión a POSIXct ayuda:

 R> length(strptime("2009-10-07 20:21:22", "%Y-%m-%d %H:%M:%S")) [1] 9 R> length(as.POSIXct(strptime("2009-10-07 20:21:22", "%Y-%m-%d %H:%M:%S"))) [1] 1 R> 

La función round siempre se redondea al número par.

 > round(3.5) [1] 4 > round(4.5) [1] 4 

Matemáticas en enteros es sutilmente diferente de los dobles (y a veces complejo es extraño también)

ACTUALIZACIÓN Repararon algunas cosas en R 2.15

 1^NA # 1 1L^NA # NA (1+0i)^NA # NA 0L %/% 0L # 0L (NA from R 2.15) 0 %/% 0 # NaN 4L %/% 0L # 0L (NA from R 2.15) 4 %/% 0 # Inf 

Me sorprende que nadie mencione esto, pero:

T & F puede anularse, TRUE y FALSE no hacerlo.

Ejemplo:

 x <- sample(c(0,1,NA), 100, T) T <- 0:10 mean(x, na.rm=T) # Warning in if (na.rm) x <- x[!is.na(x)] : # the condition has length > 1 and only the first element will be used # Calls: mean -> mean.default # [1] NA plot(rnorm(7), axes=T) # Warning in if (axes) { : # the condition has length > 1 and only the first element will be used # Calls: plot -> plot.default # Warning in if (frame.plot) localBox(...) : # the condition has length > 1 and only the first element will be used # Calls: plot -> plot.default 

[edit] ctrf+F me ctrf+F . Shane menciona esto en su comentario .

Leer datos puede ser más problemático de lo que piensas. Hoy descubrí que si usa read.csv () , si una línea en el archivo .csv está en blanco, read.csv () la omite automáticamente. Esto tiene sentido para la mayoría de las aplicaciones, pero si extrae automáticamente datos de (por ejemplo) la fila 27 de varios miles de archivos, y algunas de las filas anteriores pueden estar o no en blanco, si no tiene cuidado las cosas pueden ir horriblemente incorrecto.

Ahora uso

 data1 <- read.table(file_name, blank.lines.skip = F, sep = ",") 

Cuando está importando datos, verifique que está haciendo lo que cree que está haciendo una y otra vez ...

El comportamiento complicado de la función all.equal() .

Uno de mis errores continuos es comparar un conjunto de números de coma flotante. Tengo un CSV como:

 ... mu, tau, ... ... 0.5, 1.7, ... 

Leer el archivo y tratar de subconjuntar los datos a veces funciona, a veces falla, por supuesto, debido a caer una y otra vez en los pozos de la trampa de punto flotante. Al principio, los datos solo contienen valores enteros, luego se transforman en valores reales, usted conoce la historia. La comparación debe hacerse con la función all.equal() lugar del operador == , pero, por supuesto, el código que primero escribí utilizó el último enfoque.

Sí, genial, pero all.equal() devuelve TRUE para los mismos números, pero un mensaje de error textual si falla:

 > all.equal(1,1) [1] TRUE > all.equal(1:10, 1:5) [1] "Numeric: lengths (10, 5) differ" > all.equal(1:10, c(1:5,1:5)) [1] "Mean relative difference: 0.625" 

La solución está usando la función isTRUE() :

 if (!isTRUE(all.equal(x, y, tolerance=doubleErrorRate))) { ... } 

Cuántas veces tuve que leer la descripción de all.equals()

Esta me dolió tanto que pasé horas añadiendo comentarios a un informe de errores . No obtuve mi deseo, pero al menos la próxima versión de R generará un error.

 R> nchar(factor(letters)) [1] 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 

Actualización: A partir de R 3.2.0 (probablemente antes), este ejemplo ahora genera un mensaje de error. Como se menciona en los comentarios a continuación, un factor NO es un vector y nchar () requiere un vector.

 R> nchar(factor(letters)) Error in nchar(factor(letters)) : 'nchar()' requires a character vector R> is.vector(factor(letters)) [1] FALSE 
  1. enumerar accidentalmente el código fuente de una función al olvidar incluir paréntesis vacíos: por ejemplo, “ls” versus “ls ()”

  2. verdadero y falso no lo cortan como constantes predefinidas, como en Matlab, C ++, Java, Python; debe usar VERDADERO y FALSO

  3. valores de retorno invisibles: por ejemplo, “.packages ()” no devuelve nada, mientras que “(.packages ())” devuelve un vector de caracteres de los nombres de paquetes básicos

Por ejemplo, el número 3.14 es una constante numérica, pero las expresiones +3.14 y -3.14 son llamadas a las funciones + y - :

 > class(quote(3.14)) [1] "numeric" > class(quote(+3.14)) [1] "call" > class(quote(-3.14)) [1] "call" 

Ver la Sección 13.2 en el libro de John Chambers Software para Análisis de Datos – Progtwigción con R

Los vectores de longitud cero tienen algunas peculiaridades:

 R> kk=vector(mode="numeric",length=0) R> kk numeric(0) R> sum(kk) [1] 0 R> var(kk) [1] NA 

Concordancia parcial en el operador $ : Esto se aplica a listas, pero también a data.frame

 df1 <- data.frame(foo=1:10, foobar=10:1) df2 <- data.frame(foobar=10:1) df1$foo # Correctly gets the foo column df2$foo # Expect NULL, but this returns the foobar column!!! # So, should use double bracket instead: df1[["foo"]] df2[["foo"]] 

El [[ operador también tiene una bandera exact , pero afortunadamente es TRUE por defecto.

La coincidencia parcial también afecta attr :

 x1 <- structure(1, foo=1:10, foobar=10:1) x2 <- structure(2, foobar=10:1) attr(x1, "foo") # Correctly gets the foo attribute attr(x2, "foo") # Expect NULL, but this returns the foobar attribute!!! # So, should use exact=TRUE attr(x1, "foo", exact=TRUE) attr(x2, "foo", exact=TRUE) 

Trabajando con listas, hay un par de cosas no intuitivas:

Por supuesto, la diferencia entre [ y [[ lleva algo de tiempo acostumbrarse. Para listas, [ devuelve una lista de (potencialmente 1) elementos mientras que [[ devuelve el elemento dentro de la lista.

Creación de lista:

 # When you're used to this: x <- numeric(5) # A vector of length 5 with zeroes # ... this might surprise you x <- list(5) # A list with a SINGLE element: the value 5 # This is what you have to do instead: x <- vector('list', 5) # A vector of length 5 with NULLS 

Entonces, ¿cómo insertar NULL en una lista?

 x <- list("foo", 1:3, letters, LETTERS) # A sample list x[[2]] <- 1:5 # Put 1:5 in the second element # The obvious way doesn't work: x[[2]] <- NULL # This DELETES the second element! # This doesn't work either: x[2] <- NULL # This DELETES the second element! # The solution is NOT very intuitive: x[2] <- list(NULL) # Put NULL in the second element # Btw, now that we think we know how to delete an element: x <- 1:10 x[[2]] <- NULL # Nope, gives an ERROR! x <- x[-2] # This is the only way for atomic vectors (works for lists too) 

Finalmente algunas cosas avanzadas como la indexación a través de una lista anidada:

 x <- list(a=1:3, b=list(c=42, d=13, e="HELLO"), f='bar') x[[c(2,3)]] # HELLO (first selects second element and then it's third element) x[c(2,3)] # The second and third elements (b and f) 

Una de las grandes confusiones en R es que [i, drop = TRUE] no baja los niveles de factor, pero [i, j, drop = TRUE] ¡no!

 > df = data.frame(a = c("europe", "asia", "oceania"), b = c(1, 2, 3)) > df$a[1:2, drop = TRUE] [1] europe asia Levels: asia europe <---- drops factor levels, works fine > df[1:2,, drop = TRUE]$a [1] europe asia Levels: asia europe oceania <---- does not drops factor levels! 

Para obtener más información, vea: drop = TRUE no baja los niveles de factor en data.frame mientras está en vector lo hace

Repetición automática de vectores ( “reciclaje” ) utilizados como índices:

 R> all.numbers <- c(1:5) R> all.numbers [1] 1 2 3 4 5 R> good.idxs <- c(T,F,T) R> #note unfortunate length mismatch R> good.numbers <- all.numbers[good.idxs] R> good.numbers [1] 1 3 4 R> #wtf? R> #why would you repeat the vector used as an index R> #without even a warning? 

Viniendo del lenguaje comstackdo y de Matlab, de vez en cuando me he confundido acerca de un aspecto fundamental de las funciones en los lenguajes funcionales: ¡tienen que definirse antes de que se utilicen ! No es suficiente que el intérprete de R los analice. Esto en su mayoría asoma cuando usa funciones anidadas.

En Matlab puedes hacer:

 function f1() v1 = 1; v2 = f2(); fprintf('2 == %d\n', v2); function r1 = f2() r1 = v1 + 1 % nested function scope end end 

Si intenta hacer lo mismo en R, primero debe poner la función anidada, ¡o recibe un error! ¡Solo porque hayas definido la función, no está en el espacio de nombres hasta que se haya asignado a una variable! Por otro lado, la función puede referirse a una variable que aún no se ha definido.

 f1 <- function() { f2 <- function() { v1 + 1 } v1 <- 1 v2 = f2() print(sprintf("2 == %d", v2)) } 

El mío desde hoy: qnorm () toma Probabilidades y pnorm () toma Quantiles.

Para mí, es la manera contra intuitiva en la que cuando exportas un data.frame a un archivo de texto usando write.csv , luego para importarlo luego necesitas agregar un argumento adicional para obtener exactamente el mismo data.frame, como este:

 write.csv(m, file = 'm.csv') read.csv('m.csv', row.names = 1) # Note the row.names argument 

También publiqué esta pregunta en SO y se sugirió como respuesta a esta pregunta de @BenBolker.

El conjunto de funciones apply no solo funciona para matrices, sino que se amplía hasta arreglos multidimensionales. En mi investigación, a menudo tengo un conjunto de datos de, por ejemplo, la temperatura de la atmósfera. Esto se almacena en una matriz multidimensional con dimensiones x,y,level,time , a partir de ahora llamado multi_dim_array . Un ejemplo de maqueta sería:

 multi_dim_array = array(runif(96 * 48 * 6 * 100, -50, 50), dim = c(96, 48, 6, 100)) > str(multi_dim_array) # xy lev time num [1:96, 1:48, 1:6, 1:100] 42.4 16 32.3 49.5 24.9 ... 

Al usar apply uno puede obtener fácilmente lo siguiente:

 # temporal mean value > str(apply(multi_dim_array, 4, mean)) num [1:100] -0.0113 -0.0329 -0.3424 -0.3595 -0.0801 ... # temporal mean value per gridcell (x,y location) > str(apply(multi_dim_array, c(1,2), mean)) num [1:96, 1:48] -1.506 0.4553 -1.7951 0.0703 0.2915 ... # temporal mean value per gridcell and level (x,y location, level) > str(apply(multi_dim_array, c(1,2,3), mean)) num [1:96, 1:48, 1:6] -3.839 -3.672 0.131 -1.024 -2.143 ... # Spatial mean per level > str(apply(multi_dim_array, c(3,4), mean)) num [1:6, 1:100] -0.4436 -0.3026 -0.3158 0.0902 0.2438 ... 

Esto hace que el argumento de margin para apply parezca mucho menos intuitivo. Primero, ¿por qué no utilizar “fila” y “col” en lugar de 1 y 2. Pero el hecho de que también funciona para las matrices con más dimensiones deja en claro por qué es preferible utilizar un margin como este.