Question Comment faire un grand exemple R reproductible?


Lorsque vous discutez de la performance avec des collègues, de l'enseignement, de l'envoi d'un rapport de bug ou de la recherche de conseils sur les listes de diffusion et ici sur le SO, un exemple reproductible est souvent demandé et toujours utile.

Quels sont vos conseils pour créer un excellent exemple? Comment collez-vous les structures de données à partir de  dans un format de texte? Quelles autres informations devriez-vous inclure?

Y a-t-il d'autres astuces en plus d'utiliser dput(), dump() ou structure()? Quand devriez-vous inclure library() ou require() déclarations? Quels mots réservés faut-il éviter, en plus de c, df, data, etc?

Comment fait-on un bon  exemple reproductible?


2373


origine


Réponses:


Un exemple minimal reproductible comprend les éléments suivants:

  • un ensemble de données minimal, nécessaire pour reproduire l'erreur
  • le minimum runnable code nécessaire pour reproduire l'erreur, qui peut être exécuté sur l'ensemble de données donné.
  • les informations nécessaires sur les paquets utilisés, la version R et le système sur lequel il est exécuté.
  • dans le cas de processus aléatoires, une graine (définie par set.seed()) pour la reproductibilité

Regarder les exemples dans les fichiers d'aide des fonctions utilisées est souvent utile. En général, tout le code donné répond aux exigences d'un exemple minimal reproductible: des données sont fournies, un code minimal est fourni et tout est exécutable.

Produire un ensemble de données minimal

Pour la plupart des cas, cela peut être facilement effectué en fournissant simplement un vecteur / cadre de données avec certaines valeurs. Ou vous pouvez utiliser l'un des ensembles de données intégrés, qui sont fournis avec la plupart des paquets.
Une liste complète des ensembles de données intégrés peut être vu avec library(help = "datasets"). Il y a une courte description de chaque jeu de données et plus d'informations peuvent être obtenues par exemple avec ?mtcars où 'mtcars' est l'un des jeux de données de la liste. D'autres paquets peuvent contenir des ensembles de données supplémentaires.

Faire un vecteur est facile. Parfois, il est nécessaire d'ajouter un certain caractère aléatoire, et il y a un certain nombre de fonctions pour le faire. sample() peut randomiser un vecteur, ou donner un vecteur aléatoire avec seulement quelques valeurs. letters est un vecteur utile contenant l'alphabet. Cela peut être utilisé pour faire des facteurs.

Quelques exemples:

  • valeurs aléatoires: x <- rnorm(10) pour la distribution normale, x <- runif(10) pour une distribution uniforme, ...
  • une permutation de certaines valeurs: x <- sample(1:10) pour le vecteur 1:10 dans un ordre aléatoire.
  • un facteur aléatoire: x <- sample(letters[1:4], 20, replace = TRUE)

Pour les matrices, on peut utiliser matrix(), par exemple :

matrix(1:10, ncol = 2)

Faire des cadres de données peut être fait en utilisant data.frame(). Il faut faire attention à nommer les entrées dans la trame de données, et ne pas trop compliquer.

Un exemple :

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

Pour certaines questions, des formats spécifiques peuvent être nécessaires. Pour ceux-ci, on peut utiliser l'un des as.someType les fonctions : as.factor, as.Date, as.xts, ... Ceux-ci en combinaison avec le vecteur et / ou tricks de trame de données.

Copiez vos données

Si vous avez des données qui seraient trop difficiles à construire à l'aide de ces conseils, vous pouvez toujours créer un sous-ensemble de vos données d'origine, en utilisant par exemple head(), subset()ou les indices. Ensuite, utilisez par exemple. dput() pour nous donner quelque chose qui peut être mis en R immédiatement:

> 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 votre cadre de données a un facteur avec plusieurs niveaux, le dput La sortie peut être lourde car elle répertorie tous les niveaux de facteur possibles, même s'ils ne sont pas présents dans le sous-ensemble de vos données. Pour résoudre ce problème, vous pouvez utiliser le droplevels() fonction. Notez ci-dessous comment l'espèce est un facteur avec un seul niveau:

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

Une autre mise en garde pour dput est que cela ne fonctionnera pas pour les clés data.table objets ou pour groupés tbl_df (classe grouped_df) de dplyr. Dans ce cas, vous pouvez revenir à un cadre de données normal avant de partager, dput(as.data.frame(my_data)).

Dans le pire des cas, vous pouvez donner une représentation textuelle qui peut être lue text paramètre 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)

Produire un code minimal

Cela devrait être la partie facile mais n'est souvent pas. Ce que vous ne devriez pas faire, c'est:

  • ajouter toutes sortes de conversions de données. Assurez-vous que les données fournies sont déjà dans le bon format (à moins que ce soit le problème bien sûr)
  • copier-coller une fonction entière / morceau de code qui donne une erreur. Tout d'abord, essayez de localiser les lignes qui résultent exactement de l'erreur. Plus souvent qu'autrement, vous découvrirez quel est le problème vous-même.

Ce que vous devriez faire, c'est:

  • ajouter quels paquets devraient être utilisés si vous en utilisez library())
  • si vous ouvrez des connexions ou makefiles, ajoutez du code pour les fermer ou supprimez les fichiers (en utilisant unlink())
  • Si vous modifiez des options, assurez-vous que le code contient une instruction pour les rétablir à celles d'origine. (par exemple op <- par(mfrow=c(1,2)) ...some code... par(op) )
  • test exécutez votre code dans une nouvelle session R vide pour vous assurer que le code est exécutable. Les gens devraient pouvoir simplement copier-coller vos données et votre code dans la console et obtenir exactement la même chose que vous avez.

Donner des informations supplémentaires

Dans la plupart des cas, seule la version R et le système d'exploitation suffiront. Lorsque des conflits surviennent avec des paquets, donner la sortie de sessionInfo() peut vraiment aider. Lorsque vous parlez de connexions à d'autres applications (que ce soit via ODBC ou toute autre chose), vous devez également fournir des numéros de version pour ceux-ci, et si possible également les informations nécessaires sur l'installation.

Si vous utilisez R dans R Studio en utilisant rstudioapi::versionInfo() peut être utile pour signaler votre version de RStudio.

Si vous avez un problème avec un paquet spécifique, vous pouvez fournir la version du paquet en donnant la sortie de packageVersion("name of the package").


1450



(Voici mon conseil de Comment écrire un exemple reproductible . J'ai essayé de le rendre court mais doux)

Comment écrire un exemple reproductible.

Si vous fournissez un exemple reproductible, vous avez plus de chances d'obtenir de l'aide pour votre problème. Un exemple reproductible permet à quelqu'un d'autre de recréer votre problème simplement en copiant et en collant le code R.

Il y a quatre choses que vous devez inclure pour rendre votre exemple reproductible: les paquets requis, les données, le code et une description de votre environnement R.

  • Paquetsdevrait être chargé en haut du script, il est donc facile de voir lesquels l'exemple a besoin.

  • Le moyen le plus simple d'inclure Les données dans un e-mail ou une question Stack Overflow est d'utiliser dput() générer le code R pour le recréer. Par exemple, pour recréer le mtcars ensemble de données dans R, J'effectuerais les étapes suivantes:

    1. Courir dput(mtcars) en R
    2. Copiez la sortie
    3. Dans mon script reproductible, tapez mtcars <- puis collez.
  • Passez un peu de temps à vous assurer que votre code est facile pour les autres lis:

    • assurez-vous que vous avez utilisé des espaces et que vos noms de variables sont concis, mais informatif

    • utiliser des commentaires pour indiquer où se situe votre problème

    • faites de votre mieux pour supprimer tout ce qui n'est pas lié au problème.
      Plus votre code est court, plus il est facile à comprendre.

  • Inclure la sortie de sessionInfo() dans un commentaire dans votre code. Cela résume votre R environnement et il est facile de vérifier si vous utilisez un out-of-date paquet.

Vous pouvez vérifier que vous avez réellement créé un exemple reproductible en démarrant une nouvelle session R et en collant votre script.

Avant de mettre tout votre code dans un e-mail, pensez à le mettre en Github Gist . Cela donnera à votre code une coloration syntaxique agréable, et vous n'aurez pas à vous soucier de ce que le système de messagerie risque de le déformer.


514



Personnellement, je préfère "one" liners. Quelque chose dans le genre:

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 structure de données doit imiter l'idée du problème de l'auteur et non la structure verbatim exacte. J'apprécie vraiment quand les variables ne remplacent pas mes propres variables ou dieu interdisent, fonctions (comme df).

Alternativement, on pourrait couper quelques coins et pointer vers un ensemble de données préexistant, quelque chose comme:

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

N'oubliez pas de mentionner les paquets spéciaux que vous pourriez utiliser.

Si vous essayez de montrer quelque chose sur des objets plus grands, vous pouvez essayer

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

Si vous travaillez avec des données spatiales via le raster package, vous pouvez générer des données aléatoires. Beaucoup d'exemples peuvent être trouvés dans la vignette du paquet, mais voici une petite pépite.

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 vous avez besoin d'un objet spatial implémenté dans sp, vous pouvez obtenir des jeux de données via des fichiers externes (comme le fichier de formes ESRI) dans des paquetages "spatiaux" (voir la vue spatiale dans les vues des tâches).

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

258



Inspiré par ce même post, j'utilise maintenant une fonction pratique
reproduce(<mydata>) quand je dois poster sur StackOverflow


INSTRUCTIONS RAPIDES

Si myData est le nom de votre objet à reproduire, exécutez ce qui suit dans R:

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

reproduce(myData)

Détails:

Cette fonction est une enveloppe intelligente à dput et fait ce qui suit:

  • échantillonne automatiquement un grand ensemble de données (en fonction de la taille et de la classe, la taille de l'échantillon peut être ajustée)
  • crée un dput sortie
  • vous permet de spécifier lequel colonnes à exporter
  • ajoute à l'avant de celui-ci objName <- ... afin qu'il puisse être facilement copié + collé, mais ...
  • Si vous travaillez sur un mac, la sortie est automatiquement copiée dans le presse-papiers, de sorte que vous pouvez simplement l'exécuter et ensuite coller à votre question.

La source est disponible ici:


Exemple:

# 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 est d'environ 100 x 102. Je veux échantillonner 10 lignes, et quelques colonnes spécifiques

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

Donne la sortie suivante:

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

Notez également que la totalité de la sortie est dans une belle ligne simple et longue, pas un grand paragraphe de lignes hachées. Cela rend plus facile à lire sur les messages de questions SO et aussi plus facile à copier + coller.


Mise à jour Oct 2013:

Vous pouvez maintenant spécifier le nombre de lignes de texte à prendre en compte (c'est-à-dire, ce que vous allez coller dans StackOverflow). Utilisez le lines.out=n argument pour cela. Exemple:

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

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



Voici un bon guide:

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

Mais le plus important est: Assurez-vous juste que vous faites un petit morceau de code que nous pouvons exécuter pour voir quel est le problème. Une fonction utile pour cela est dput(), mais si vous avez des données très volumineuses, vous pouvez créer un petit jeu de données ou utiliser uniquement les 10 premières lignes.

MODIFIER:

Assurez-vous également que vous avez identifié où le problème est vous-même. L'exemple ne doit pas être un script R complet avec "Sur la ligne 200 il y a une erreur". Si vous utilisez les outils de débogage dans R (I love browser()) et google vous devriez être capable de vraiment identifier où le problème est et de reproduire un exemple trivial dans lequel la même chose se passe mal.


168



La liste de diffusion R-help a un guide d'affichage qui couvre à la fois les questions posées et les réponses aux questions, y compris un exemple de génération de données:

Exemples: Parfois, cela aide à   fournir un petit exemple que quelqu'un   peut réellement courir. Par exemple:

Si j'ai une matrice x comme suit:

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

comment puis-je le transformer en un dataframe   avec 8 lignes et trois colonnes nommées   'row', 'col' et 'value', qui ont   les noms de dimension comme les valeurs de 'row' et 'col', comme ceci:

  > x.df
     row col value
  1    A   x      1

...
  (À qui la réponse pourrait être:

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

)

Le mot petit est particulièrement important. Vous devriez viser un minimal exemple reproductible, ce qui signifie que les données et le code doivent être aussi simples que possible pour expliquer le problème.

EDIT: Pretty code est plus facile à lire que le code laid. Utiliser un guide de style.


142



Depuis R.2.14 (je suppose), vous pouvez envoyer directement votre représentation de texte de données à 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



Parfois, le problème n'est vraiment pas reproductible avec un plus petit morceau de données, peu importe comment vous essayez, et cela n'arrive pas avec des données synthétiques (bien qu'il soit utile de montrer comment vous avez produit des ensembles de données synthétiques qui ont fait ne pas reproduire le problème, car il exclut certaines hypothèses).

  • Publier les données sur le Web quelque part et fournir une URL peut être nécessaire.
  • Si les données ne peuvent pas être divulguées au grand public, mais pourraient être partagées, vous pourriez être en mesure d'offrir de les envoyer par courriel aux parties intéressées (bien que cela réduise le nombre de personnes qui prendront la peine de travailler dessus).
  • Je n'ai pas vraiment vu cela, parce que les gens qui ne peuvent pas publier leurs données sont sensibles à la libération de n'importe quelle forme, mais il semblerait plausible que dans certains cas, on pourrait publier des données si elles étaient suffisamment anonymisées / brouillées / corrompues en quelque sorte.

Si vous ne pouvez pas faire l'un ou l'autre, vous devrez probablement engager un consultant pour résoudre votre problème ...

modifier: Deux questions SO utiles pour l'anonymisation / brouillage:


126



Les réponses à ce jour sont évidemment excellentes pour la partie reproductibilité. Il s'agit simplement de clarifier qu'un exemple reproductible ne peut et ne doit pas être la seule composante d'une question. N'oubliez pas d'expliquer à quoi vous voulez ressembler et les contours de votre problème, pas seulement comment vous avez essayé d'y arriver jusqu'à présent. Le code ne suffit pas; vous avez besoin de mots aussi.

Voici un exemple reproductible de ce qu'il faut éviter de faire (tiré d'un exemple réel, les noms changés pour protéger l'innocent):


Ce qui suit est un échantillon de données et une partie de la fonction avec laquelle j'ai des problèmes.

code
code
code
code
code (40 or so lines of it)

Comment puis-je atteindre cet objectif ?



115



Pour créer rapidement un dput de vos données, vous pouvez simplement copier (une partie de) les données dans votre presse-papiers et exécuter ce qui suit dans R:

pour les données dans Excel:

dput(read.table("clipboard",sep="\t",header=TRUE))

pour les données dans un fichier txt:

dput(read.table("clipboard",sep="",header=TRUE))

Vous pouvez changer le sep dans ce dernier si nécessaire. Cela ne fonctionnera que si vos données sont dans le presse-papiers bien sûr.


100



J'ai un moyen très simple et efficace de faire un exemple R qui n'a pas été mentionné ci-dessus. Vous pouvez définir votre structure en premier lieu. Par exemple,

mydata <- data.frame(a=character(0), b=numeric(0),  c=numeric(0), d=numeric(0))

>fix(mydata)

when you execute 'fix' command,you will get this pop-up box 

Ensuite, vous pouvez entrer vos données manuellement. Ceci est efficace pour les petits exemples plutôt que pour les grands.


95