Pregunta ¿Cómo hacer un gran ejemplo reproducible de R?


Al hablar sobre el rendimiento con colegas, la enseñanza, el envío de un informe de errores o la búsqueda de orientación sobre listas de correo y aquí en SO, una ejemplo reproducible a menudo se pregunta y siempre es útil.

¿Cuáles son tus consejos para crear un excelente ejemplo? Cómo pegar estructuras de datos de  en un formato de texto? ¿Qué otra información debe incluir?

¿Hay otros trucos además del uso dput(), dump() o structure()? Cuándo deberías incluir library() o require() declaraciones? Qué palabras reservadas se deben evitar, además de c, df, data, etc.?

¿Cómo hace uno un gran  ejemplo reproducible?


2373


origen


Respuestas:


Un ejemplo reproducible mínimo consta de los siguientes elementos:

  • un conjunto de datos mínimo, necesario para reproducir el error
  • el mínimo ejecutable código necesario para reproducir el error, que se puede ejecutar en el conjunto de datos dado.
  • la información necesaria sobre los paquetes usados, la versión R y el sistema en el que se ejecuta.
  • en el caso de procesos aleatorios, una semilla (establecida por set.seed()) para reproducibilidad

Ver los ejemplos en los archivos de ayuda de las funciones utilizadas suele ser útil. En general, todo el código proporcionado allí cumple los requisitos de un ejemplo reproducible mínimo: se proporcionan datos, se proporciona un código mínimo y todo es ejecutable.

Producir un conjunto de datos mínimo

Para la mayoría de los casos, esto puede hacerse fácilmente simplemente proporcionando un vector / marco de datos con algunos valores. O puede usar uno de los conjuntos de datos integrados, que se proporcionan con la mayoría de los paquetes.
Se puede ver una lista completa de conjuntos de datos integrados con library(help = "datasets"). Hay una breve descripción de cada conjunto de datos y se puede obtener más información, por ejemplo, con ?mtcars donde 'mtcars' es uno de los conjuntos de datos en la lista. Otros paquetes pueden contener conjuntos de datos adicionales.

Hacer un vector es fácil. A veces es necesario agregarle algo de aleatoriedad, y hay un número entero de funciones para hacer eso. sample() puede aleatorizar un vector, o dar un vector aleatorio con solo unos pocos valores. letters es un vector útil que contiene el alfabeto. Esto se puede usar para hacer factores.

Algunos ejemplos :

  • valores aleatorios: x <- rnorm(10) para distribución normal, x <- runif(10) para una distribución uniforme, ...
  • una permutación de algunos valores: x <- sample(1:10) para el vector 1:10 en orden aleatorio.
  • un factor aleatorio: x <- sample(letters[1:4], 20, replace = TRUE)

Para matrices, uno puede usar matrix(), p.ej :

matrix(1:10, ncol = 2)

Hacer marcos de datos se puede hacer usando data.frame(). Uno debe prestar atención para nombrar las entradas en el marco de datos, y no hacerlo demasiado complicado.

Un ejemplo :

set.seed(1)
Data <- data.frame(
    X = sample(1:10),
    Y = sample(c("yes", "no"), 10, replace = TRUE)
)

Para algunas preguntas, se pueden necesitar formatos específicos. Para estos, uno puede usar cualquiera de los proporcionados as.someType funciones: as.factor, as.Date, as.xts, ... Estos en combinación con el vector y / o los trucos del marco de datos.

Copia tus datos

Si tiene algunos datos que serían muy difíciles de construir con estos consejos, siempre puede hacer un subconjunto de sus datos originales, utilizando, por ejemplo, head(), subset()o los índices. Entonces use por ej. dput() para darnos algo que se puede poner en R inmediatamente:

> dput(head(iris,4))
structure(list(Sepal.Length = c(5.1, 4.9, 4.7, 4.6), Sepal.Width = c(3.5, 
3, 3.2, 3.1), Petal.Length = c(1.4, 1.4, 1.3, 1.5), Petal.Width = c(0.2, 
0.2, 0.2, 0.2), Species = structure(c(1L, 1L, 1L, 1L), .Label = c("setosa", 
"versicolor", "virginica"), class = "factor")), .Names = c("Sepal.Length", 
"Sepal.Width", "Petal.Length", "Petal.Width", "Species"), row.names = c(NA, 
4L), class = "data.frame")

Si su marco de datos tiene un factor con muchos niveles, el dput La salida puede ser difícil de manejar porque seguirá enumerando todos los posibles niveles de factor, incluso si no están presentes en el subconjunto de sus datos. Para resolver este problema, puede usar droplevels() función. Observe a continuación cómo la especie es un factor con un solo nivel:

> dput(droplevels(head(iris, 4)))
structure(list(Sepal.Length = c(5.1, 4.9, 4.7, 4.6), Sepal.Width = c(3.5, 
3, 3.2, 3.1), Petal.Length = c(1.4, 1.4, 1.3, 1.5), Petal.Width = c(0.2, 
0.2, 0.2, 0.2), Species = structure(c(1L, 1L, 1L, 1L), .Label = "setosa",
class = "factor")), .Names = c("Sepal.Length", "Sepal.Width", 
"Petal.Length", "Petal.Width", "Species"), row.names = c(NA, 
4L), class = "data.frame")

Otra advertencia para dput es que no funcionará para clave data.table objetos o para agruparse tbl_df (clase grouped_df) de dplyr. En estos casos, puede convertir de nuevo a un marco de datos normal antes de compartir, dput(as.data.frame(my_data)).

En el peor de los casos, puede dar una representación de texto que pueda leerse utilizando el text parámetro de read.table :

zz <- "Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa"

Data <- read.table(text=zz, header = TRUE)

Produciendo código mínimo

Esta debería ser la parte fácil pero a menudo no lo es. Lo que no debes hacer es:

  • agregue todo tipo de conversiones de datos. Asegúrese de que los datos proporcionados ya estén en el formato correcto (a menos que ese sea el problema, por supuesto)
  • copiar y pegar toda una función / porción de código que da un error. Primero, trate de localizar qué líneas resultan exactamente en el error. La mayoría de las veces descubrirá cuál es el problema usted mismo.

Lo que debes hacer es:

  • agregue qué paquetes se deben usar si usa alguno (usando library())
  • si abre conexiones o archivos make, agregue algún código para cerrarlos o eliminar los archivos (usando unlink())
  • si cambia las opciones, asegúrese de que el código contenga una declaración para revertirlas a las originales. (p.ej op <- par(mfrow=c(1,2)) ...some code... par(op) )
  • pruebe ejecutar su código en una nueva sesión R vacía para asegurarse de que el código sea ejecutable. Las personas deberían poder copiar y pegar sus datos y su código en la consola y obtener exactamente lo mismo que usted.

Dar información adicional

En la mayoría de los casos, solo la versión R y el sistema operativo serán suficientes. Cuando surgen conflictos con paquetes, dando la salida de sessionInfo() realmente puede ayudar. Cuando se habla de conexiones a otras aplicaciones (ya sea a través de ODBC o cualquier otra cosa), también se deben proporcionar los números de versión y, si es posible, también la información necesaria sobre la configuración.

Si está ejecutando R en R Studio utilizando rstudioapi::versionInfo() puede ser útil para informar su versión de RStudio.

Si tiene un problema con un paquete específico, puede proporcionar la versión del paquete dando el resultado de packageVersion("name of the package").


1450



(Aquí está mi consejo de Cómo escribir un ejemplo reproducible . Traté de hacerlo corto pero dulce)

Cómo escribir un ejemplo reproducible.

Es más probable que obtenga una buena ayuda con su problema R si proporciona un ejemplo reproducible. Un ejemplo reproducible le permite a otra persona recrear su problema simplemente copiando y pegando el código R.

Hay cuatro cosas que debe incluir para que su ejemplo sea reproducible: paquetes obligatorios, datos, códigos y una descripción de su entorno R.

  • Paquetesdebe cargarse en la parte superior del script, por lo que es fácil ver cuáles necesita el ejemplo.

  • La forma más fácil de incluir datos en una pregunta de correo electrónico o desbordamiento de pila es utilizar dput() para generar el código R para recrearlo. Por ejemplo, para recrear el mtcars conjunto de datos en R, Realizaría los siguientes pasos:

    1. correr dput(mtcars) en R
    2. Copia la salida
    3. En mi script reproducible, escriba mtcars <- luego pega.
  • Pase un poco de tiempo asegurándose de que su código es fácil para otros leer:

    • asegúrese de haber usado espacios y sus nombres de variables son concisos, pero informativo

    • usa los comentarios para indicar dónde está tu problema

    • haz tu mejor esfuerzo para eliminar todo lo que no esté relacionado con el problema.
      Cuanto más corto sea tu código, más fácil será de entender.

  • Incluye la salida de sessionInfo() en un comentario en tu código. Esto resume tu R ambiente y hace que sea fácil verificar si está usando un dispositivo desactualizado paquete.

Puede comprobar que realmente ha hecho un ejemplo reproducible iniciando una nueva sesión R y pegando su script.

Antes de poner todo su código en un correo electrónico, considere ponerlo en Gith Github . Le dará a su código un buen resaltado de sintaxis y no tendrá que preocuparse de que el sistema de correo electrónico afecte algo.


514



Personalmente, prefiero los revestimientos "uno". Algo en la línea:

my.df <- data.frame(col1 = sample(c(1,2), 10, replace = TRUE),
        col2 = as.factor(sample(10)), col3 = letters[1:10],
        col4 = sample(c(TRUE, FALSE), 10, replace = TRUE))
my.list <- list(list1 = my.df, list2 = my.df[3], list3 = letters)

La estructura de datos debe imitar la idea del problema del escritor y no la estructura textual exacta. Realmente lo aprecio cuando las variables no sobrescriben mis propias variables o Dios prohíbe, funciones (como df)

Alternativamente, uno podría cortar algunas esquinas y señalar un conjunto de datos preexistentes, algo así como:

library(vegan)
data(varespec)
ord <- metaMDS(varespec)

No olvide mencionar cualquier paquete especial que pueda estar usando.

Si intentas demostrar algo en objetos más grandes, puedes intentar

my.df2 <- data.frame(a = sample(10e6), b = sample(letters, 10e6, replace = TRUE))

Si está trabajando con datos espaciales a través del raster paquete, puede generar algunos datos aleatorios. Se pueden encontrar muchos ejemplos en la viñeta del paquete, pero aquí hay una pequeña pepita.

library(raster)
r1 <- r2 <- r3 <- raster(nrow=10, ncol=10)
values(r1) <- runif(ncell(r1))
values(r2) <- runif(ncell(r2))
values(r3) <- runif(ncell(r3))
s <- stack(r1, r2, r3)

Si necesita algún objeto espacial como se implementa en sp, puede obtener algunos conjuntos de datos a través de archivos externos (como el archivo shape de ESRI) en paquetes "espaciales" (consulte la vista Espacial en Vistas de tareas).

library(rgdal)
ogrDrivers()
dsn <- system.file("vectors", package = "rgdal")[1]
ogrListLayers(dsn)
ogrInfo(dsn=dsn, layer="cities")
cities <- readOGR(dsn=dsn, layer="cities")

258



Inspirado por este mismo post, ahora uso una función práctica
reproduce(<mydata>) cuando necesito publicar en StackOverflow.


INSTRUCCIONES RÁPIDAS

Si myData es el nombre de tu objeto para reproducir, ejecuta lo siguiente en R:

install.packages("devtools")
library(devtools)
source_url("https://raw.github.com/rsaporta/pubR/gitbranch/reproduce.R")

reproduce(myData)

Detalles:

Esta función es una envoltura inteligente para dput y hace lo siguiente:

  • muestrea automáticamente un gran conjunto de datos (según el tamaño y la clase. Se puede ajustar el tamaño de la muestra)
  • crea un dput salida
  • te permite especificar cual columnas para exportar
  • se agrega al frente del mismo objName <- ... para que pueda copiarse y pegarse fácilmente, pero ...
  • Si trabaja en un mac, la salida se copia automáticamente en el portapapeles, por lo que simplemente puede ejecutarlo y luego pegarlo a su pregunta.

La fuente está disponible aquí:


Ejemplo:

# sample data
DF <- data.frame(id=rep(LETTERS, each=4)[1:100], replicate(100, sample(1001, 100)), Class=sample(c("Yes", "No"), 100, TRUE))

DF es de aproximadamente 100 x 102. Quiero muestrear 10 filas y algunas columnas específicas

reproduce(DF, cols=c("id", "X1", "X73", "Class"))  # I could also specify the column number. 

Da el siguiente resultado:

This is what the sample looks like: 

    id  X1 X73 Class
1    A 266 960   Yes
2    A 373 315    No            Notice the selection split 
3    A 573 208    No           (which can be turned off)
4    A 907 850   Yes
5    B 202  46   Yes         
6    B 895 969   Yes   <~~~ 70 % of selection is from the top rows
7    B 940 928    No
98   Y 371 171   Yes          
99   Y 733 364   Yes   <~~~ 30 % of selection is from the bottom rows.  
100  Y 546 641    No        


    ==X==============================================================X==
         Copy+Paste this part. (If on a Mac, it is already copied!)
    ==X==============================================================X==

 DF <- structure(list(id = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 25L, 25L, 25L), .Label = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y"), class = "factor"), X1 = c(266L, 373L, 573L, 907L, 202L, 895L, 940L, 371L, 733L, 546L), X73 = c(960L, 315L, 208L, 850L, 46L, 969L, 928L, 171L, 364L, 641L), Class = structure(c(2L, 1L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 1L), .Label = c("No", "Yes"), class = "factor")), .Names = c("id", "X1", "X73", "Class"), class = "data.frame", row.names = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 98L, 99L, 100L)) 

    ==X==============================================================X==

Observe también que la totalidad de la salida se encuentra en una línea única, larga y agradable, no en un alto párrafo de líneas cortadas. Esto hace que sea más fácil leer en las publicaciones de preguntas de SO y también es más fácil copiar y pegar.


Actualización de octubre de 2013:

Ahora puede especificar cuántas líneas de salida de texto ocuparán (es decir, qué pegará en StackOverflow). Utilizar el lines.out=n argumento para esto Ejemplo:

reproduce(DF, cols=c(1:3, 17, 23), lines.out=7) rendimientos:

    ==X==============================================================X==
         Copy+Paste this part. (If on a Mac, it is already copied!)
    ==X==============================================================X==

 DF <- structure(list(id = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 25L,25L, 25L), .Label
      = c("A", "B", "C", "D", "E", "F", "G", "H","I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U","V", "W", "X", "Y"), class = "factor"),
      X1 = c(809L, 81L, 862L,747L, 224L, 721L, 310L, 53L, 853L, 642L),
      X2 = c(926L, 409L,825L, 702L, 803L, 63L, 319L, 941L, 598L, 830L),
      X16 = c(447L,164L, 8L, 775L, 471L, 196L, 30L, 420L, 47L, 327L),
      X22 = c(335L,164L, 503L, 407L, 662L, 139L, 111L, 721L, 340L, 178L)), .Names = c("id","X1",
      "X2", "X16", "X22"), class = "data.frame", row.names = c(1L,2L, 3L, 4L, 5L, 6L, 7L, 98L, 99L, 100L))

    ==X==============================================================X==

241



Aquí hay una buena guía:

http://www.r-bloggers.com/three-tips-for-posting-good-questions-to-r-help-and-stack-overflow/

Pero lo más importante es: solo asegúrate de crear un pequeño código que podamos ejecutar para ver cuál es el problema. Una función útil para esto es dput(), pero si tiene datos muy grandes, puede hacer un pequeño conjunto de datos de muestra o solo usar las primeras 10 líneas más o menos.

EDITAR:

También asegúrese de identificar dónde está el problema usted mismo. El ejemplo no debe ser un script R completo con "En la línea 200 hay un error". Si usa las herramientas de depuración en R (me encanta browser()) y google debería ser capaz de identificar realmente dónde está el problema y reproducir un ejemplo trivial en el que lo mismo ocurre.


168



La lista de correo de R-help tiene un guía de publicación que cubre preguntas de preguntas y respuestas, incluido un ejemplo de generación de datos:

Ejemplos: a veces ayuda a   dar un pequeño ejemplo de que alguien   puede funcionar realmente Por ejemplo:

Si tengo una matriz x de la siguiente manera:

  > x <- matrix(1:8, nrow=4, ncol=2,
                dimnames=list(c("A","B","C","D"), c("x","y"))
  > x
    x y
  A 1 5
  B 2 6
  C 3 7
  D 4 8
  >

¿Cómo puedo convertirlo en un marco de datos?   con 8 filas y tres columnas nombradas   'row', 'col', y 'value', que tienen   los nombres de dimensión como los valores de 'fila' y 'col', como este:

  > x.df
     row col value
  1    A   x      1

...
  (A lo que la respuesta podría ser:

  > x.df <- reshape(data.frame(row=rownames(x), x), direction="long",
                    varying=list(colnames(x)), times=colnames(x),
                    v.names="value", timevar="col", idvar="row")

)

La palabra pequeña es especialmente importante. Deberías apuntar a un mínimo Ejemplo reproducible, lo que significa que los datos y el código deben ser lo más simples posible para explicar el problema.

EDITAR: el código bonito es más fácil de leer que el código feo. Usar una guía de estilo.


142



Desde R.2.14 (supongo) puede alimentar su representación de texto de datos directamente a read.table:

df <- read.table(header=T, text="Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa
") 

136



En ocasiones, el problema no es reproducible con datos más pequeños, sin importar cuánto lo intente, y no ocurre con datos sintéticos (aunque es útil mostrar cómo produjo conjuntos de datos sintéticos que sí lo hicieron). no reproduzca el problema, porque descarta algunas hipótesis).

  • Postear los datos en la web en alguna parte y proporcionar una URL puede ser necesario.
  • Si los datos no se pueden divulgar al público en general, pero podrían compartirse, entonces puede ofrecerse para enviarlos por correo electrónico a las partes interesadas (aunque esto reducirá el número de personas que se molestarán en trabajar). en eso).
  • En realidad, no lo he visto porque las personas que no pueden divulgar sus datos son sensibles a la posibilidad de publicarlo de cualquier forma, pero parece plausible que en algunos casos todavía se puedan publicar datos si se ha anonimizado / codificado / corrompido lo suficiente. de alguna manera.

Si no puede hacer ninguno de estos, probablemente necesite contratar un consultor para resolver su problema ...

editar: Dos preguntas SO útiles para la anonimización / aleatorización:


126