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 r 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 r 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:
- Correre
dput(mtcars)
in R
- Copia l'output
- 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