Domanda Come fare un grande esempio riproducibile di R?


Quando si parla di prestazioni con colleghi, insegnamento, invio di una segnalazione di bug o ricerca di indicazioni sulle mailing list e qui su SO, a esempio riproducibile viene spesso chiesto e sempre utile.

Quali sono i tuoi consigli per creare un esempio eccellente? Come incollare le strutture dati da  in un formato di testo? Quali altre informazioni dovresti includere?

Ci sono altri trucchi oltre all'utilizzo dput(), dump() o structure()? Quando dovresti includere library() o require() affermazioni? Quali parole riservate dovrebbero evitare, oltre a c, df, data, eccetera?

Come si fa un grande  esempio riproducibile?


2373


origine


risposte:


Un esempio riproducibile minimo è costituito dai seguenti elementi:

  • un set di dati minimo, necessario per riprodurre l'errore
  • il minimo eseguibile codice necessario per riprodurre l'errore, che può essere eseguito sul set di dati specificato.
  • le informazioni necessarie sui pacchetti usati, la versione R e il sistema su cui è in esecuzione.
  • nel caso di processi casuali, un seme (impostato da set.seed()) per riproducibilità

Guardare gli esempi nei file di aiuto delle funzioni utilizzate è spesso utile. In generale, tutto il codice fornito soddisfa i requisiti di un esempio riproducibile minimo: i dati vengono forniti, viene fornito un codice minimo e tutto è eseguibile.

Produrre un set di dati minimo

Per la maggior parte dei casi, questo può essere fatto facilmente semplicemente fornendo un vettore / frame dati con alcuni valori. Oppure puoi utilizzare uno dei set di dati incorporati, che sono forniti con la maggior parte dei pacchetti.
È possibile visualizzare un elenco completo di set di dati incorporati library(help = "datasets"). C'è una breve descrizione per ogni set di dati e ulteriori informazioni possono essere ottenute per esempio con ?mtcars dove 'mtcars' è uno dei set di dati nell'elenco. Altri pacchetti potrebbero contenere set di dati aggiuntivi.

Fare un vettore è facile. A volte è necessario aggiungere un po 'di casualità ad esso, e ci sono un numero intero di funzioni per farlo. sample() può randomizzare un vettore o dare un vettore casuale con pochi valori. letters è un vettore utile che contiene l'alfabeto. Questo può essere usato per fare fattori.

Alcuni esempi:

  • valori casuali: x <- rnorm(10) per la normale distribuzione, x <- runif(10) per una distribuzione uniforme, ...
  • una permutazione di alcuni valori: x <- sample(1:10) per il vettore 1:10 in ordine casuale.
  • un fattore casuale: x <- sample(letters[1:4], 20, replace = TRUE)

Per le matrici, si può usare matrix(), per esempio :

matrix(1:10, ncol = 2)

Realizzare i frame di dati può essere fatto usando data.frame(). Si dovrebbe prestare attenzione a nominare le voci nel riquadro dati e non renderlo eccessivamente complicato.

Un esempio :

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

Per alcune domande, possono essere necessari formati specifici. Per questi, è possibile utilizzare uno dei forniti as.someType funzioni: as.factor, as.Date, as.xts, ... Questi in combinazione con i trucchi vettoriali e / o frame dati.

Copia i tuoi dati

Se hai alcuni dati che sarebbero troppo difficili da costruire usando questi suggerimenti, puoi sempre creare un sottoinsieme dei tuoi dati originali, usando ad esempio head(), subset()o gli indici. Quindi usa eg. dput() per darci qualcosa che possa essere messo immediatamente in R:

> 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")

Se il tuo frame di dati ha un fattore con molti livelli, il dput l'output può essere ingombrante perché elenca ancora tutti i possibili livelli di fattore anche se non sono presenti nel sottoinsieme dei dati. Per risolvere questo problema, puoi utilizzare il droplevels() funzione. Si noti sotto come le specie sono un fattore con un solo livello:

> 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")

Un altro avvertimento per dput è che non funzionerà per keyed data.table oggetti o raggruppati tbl_df (classe grouped_df) a partire dal dplyr. In questi casi è possibile riconvertire in un normale frame di dati prima di condividerli, dput(as.data.frame(my_data)).

Nella peggiore delle ipotesi, puoi dare una rappresentazione testuale che può essere letta usando il text parametro di 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)

Produrre codice minimo

Questa dovrebbe essere la parte facile, ma spesso non lo è. Quello che non dovresti fare è:

  • aggiungi tutti i tipi di conversione dei dati. Assicurati che i dati forniti siano già nel formato corretto (a meno che non sia questo il problema, ovviamente)
  • copia-incolla un'intera funzione / pezzo di codice che dà un errore. Innanzitutto, cerca di individuare le righe che determinano esattamente l'errore. Molto spesso scoprirai qual è il problema te stesso.

Cosa dovresti fare, è:

  • aggiungi quali pacchetti dovrebbero essere usati se ne usi (usando library())
  • se apri connessioni o makefile, aggiungi del codice per chiuderli o eliminare i file (usando unlink())
  • se si modificano le opzioni, assicurarsi che il codice contenga un'istruzione per ripristinarle su quelle originali. (per esempio op <- par(mfrow=c(1,2)) ...some code... par(op) )
  • prova esegui il tuo codice in una nuova sessione R vuota per assicurarti che il codice sia eseguibile. Le persone dovrebbero essere in grado di copiare e incollare i dati e il codice nella console e ottenere esattamente la stessa cosa che hai.

Dare informazioni extra

Nella maggior parte dei casi, saranno sufficienti solo la versione R e il sistema operativo. Quando sorgono conflitti con i pacchetti, dando l'output di sessionInfo() può davvero aiutare. Quando si parla di connessioni ad altre applicazioni (tramite ODBC o altro), è necessario fornire anche i numeri di versione e, se possibile, anche le informazioni necessarie sulla configurazione.

Se stai eseguendo R in Studio R utilizzando rstudioapi::versionInfo() può essere utile segnalare la versione di RStudio.

Se hai un problema con un pacchetto specifico, potresti voler fornire la versione del pacchetto dando l'output di packageVersion("name of the package").


1449



(Ecco il mio consiglio da Come scrivere un esempio riproducibile . Ho provato a renderlo breve ma dolce)

Come scrivere un esempio riproducibile.

È più probabile che tu abbia un buon aiuto con il tuo problema R se fornisci un esempio riproducibile. Un esempio riproducibile consente a qualcun altro di ricreare il tuo problema semplicemente copiando e incollando il codice R.

Ci sono quattro cose che devi includere per rendere riproducibile il tuo esempio: pacchetti richiesti, dati, codice e una descrizione del tuo ambiente R.

  • Pacchettidovrebbe essere caricato nella parte superiore dello script, quindi è facile vedere quali hanno bisogno l'esempio.

  • Il modo più semplice per includere dati in una e-mail o la domanda Stack Overflow deve essere utilizzata dput() generare il codice R per ricrearlo. Ad esempio, per ricreare il mtcars set di dati in R, Vorrei eseguire i seguenti passaggi:

    1. Correre dput(mtcars) in R
    2. Copia l'output
    3. Nel mio script riproducibile, digita mtcars <- quindi incollare.
  • Passa un po 'di tempo a garantire che il tuo codice è facile per gli altri leggere:

    • assicurati di aver usato spazi e i nomi delle variabili siano concisi, ma Informativo

    • usa i commenti per indicare dove si trova il tuo problema

    • fai del tuo meglio per rimuovere tutto ciò che non è correlato al problema.
      Più corto è il tuo codice, più facile è capire.

  • Includere l'output di sessionInfo() in un commento nel tuo codice. Questo riassume il tuo R ambiente e rende facile verificare se stai usando un dato obsoleto pacchetto.

Puoi verificare se hai effettivamente fatto un esempio riproducibile avviando una nuova sessione R e incollando il tuo script in.

Prima di inserire tutto il tuo codice in un'email, prova a metterlo su Gist Github . Darà al tuo codice una bella evidenziazione della sintassi, e non dovrai preoccuparti di qualcosa che viene manomesso dal sistema di posta elettronica.


514



Personalmente, preferisco le fodere "una". Qualcosa in questo modo:

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 struttura dei dati dovrebbe imitare l'idea del problema dello scrittore e non l'esatta struttura verbale. Lo apprezzo molto quando le variabili non sovrascrivono le mie variabili o Dio non voglia, funzioni (come df).

In alternativa, è possibile tagliare alcuni angoli e puntare a un set di dati preesistente, ad esempio:

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

Non dimenticare di menzionare eventuali pacchetti speciali che potresti utilizzare.

Se stai cercando di dimostrare qualcosa su oggetti più grandi, puoi provare

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

Se stai lavorando con dati spaziali tramite il raster pacchetto, è possibile generare alcuni dati casuali. Un sacco di esempi possono essere trovati nella vignetta del pacchetto, ma ecco un piccolo nugget.

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)

Se hai bisogno di qualche oggetto territoriale come implementato in sp, è possibile ottenere alcuni set di dati tramite file esterni (come lo shapefile ESRI) in pacchetti "spaziali" (vedere Vista spaziale in Viste attività).

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

258



Ispirato da questo stesso post, ora uso una comoda funzione
reproduce(<mydata>) quando ho bisogno di pubblicare su StackOverflow.


ISTRUZIONI RAPIDE

Se myData è il nome del tuo oggetto da riprodurre, esegui quanto segue in R:

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

reproduce(myData)

Dettagli:

Questa funzione è un wrapper intelligente per dput e fa quanto segue:

  • campiona automaticamente un set di dati di grandi dimensioni (in base a dimensioni e classe. La dimensione del campione può essere regolata)
  • crea a dput produzione
  • ti permette di specificare quale colonne da esportare
  • si aggiunge alla parte anteriore di esso objName <- ... in modo che possa essere facilmente copiato + incollato, ma ...
  • Se lavori su un Mac, l'output viene copiato automaticamente negli Appunti, in modo che tu possa semplicemente eseguirlo e quindi incollarlo nella tua domanda.

La fonte è disponibile qui:


Esempio:

# 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 è di circa 100 x 102. Voglio campionare 10 righe e alcune colonne specifiche

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

Fornisce il seguente risultato:

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==

Si noti inoltre che l'interezza dell'output è in una bella singola, lunga linea, non in un alto paragrafo di linee tritate. In questo modo è più facile leggere i post delle domande SO e anche più facile copiare e incollare.


Aggiornamento ottobre 2013:

Ora puoi specificare il numero di righe di testo in uscita (ovvero, cosa incollerai in StackOverflow). Usa il lines.out=n argomento per questo. Esempio:

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

    ==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



Ecco una buona guida:

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

Ma la cosa più importante è: assicurati di creare una piccola porzione di codice che possiamo eseguire per vedere qual è il problema. Una funzione utile per questo è dput(), ma se hai dati molto grandi potresti voler creare un piccolo set di dati campione o usare solo le prime 10 linee o giù di lì.

MODIFICARE:

Assicurati anche di aver identificato dove si trova il problema. L'esempio non dovrebbe essere un intero script R con "Sulla riga 200 c'è un errore". Se usi gli strumenti di debug in R (Mi piace browser()) e Google dovrebbe essere in grado di identificare realmente dove si trova il problema e riprodurre un esempio banale in cui la stessa cosa va storta.


168



La mailing list di R-help ha un guida di pubblicazione che copre sia domande che risposte, incluso un esempio di generazione di dati:

Esempi: a volte aiuta   fornire un piccolo esempio a qualcuno   può effettivamente funzionare. Per esempio:

Se ho una matrice x come segue:

  > 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
  >

come posso trasformarlo in un dataframe   con 8 righe e tre colonne di nome   'riga', 'colonna' e 'valore', che hanno   i nomi delle dimensioni come valori di 'row' e 'col', come questo:

  > x.df
     row col value
  1    A   x      1

...
  (A cui la risposta potrebbe essere:

  > 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 parola piccolo è particolarmente importante. Dovresti mirare a minimo esempio riproducibile, il che significa che i dati e il codice dovrebbero essere il più semplici possibile per spiegare il problema.

EDIT: grazioso codice è più facile da leggere rispetto al codice brutto. Usare un guida di stile.


142



Dal momento che R.2.14 (immagino) è possibile alimentare la rappresentazione del testo dei dati direttamente in 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



A volte il problema in realtà non è riproducibile con una porzione di dati più piccola, non importa quanto duramente ci provi, e non accade con dati sintetici (anche se è utile mostrare come hai prodotto set di dati sintetici che non riprodurre il problema, perché esclude alcune ipotesi).

  • Potrebbe essere necessario pubblicare i dati sul Web da qualche parte e fornire un URL.
  • Se i dati non possono essere divulgati al pubblico in generale ma possono essere condivisi, è possibile che tu possa offrire di inviarli tramite e-mail alle parti interessate (anche se questo ridurrà il numero di persone che si preoccuperanno di lavorare su di essa).
  • In realtà non l'ho visto, perché le persone che non possono rilasciare i propri dati sono sensibili a rilasciare qualsiasi forma, ma sembrerebbe plausibile che in alcuni casi si possano ancora postare dati se fossero sufficientemente anonimizzati / codificati / corrotti leggermente in qualche modo.

Se non riesci a fare nessuno di questi, probabilmente dovrai assumere un consulente per risolvere il tuo problema ...

modificare: Due utili domande su SO per anonimato / rimescolamento:


126