Comment faire un excellent exemple reproductible R

2473

Lorsque vous discutez de performances avec des collègues, enseignez, envoyez un rapport de bogue ou recherchez des conseils sur les listes de diffusion et ici sur Stack Overflow, un exemple reproductible est souvent demandé et toujours utile.

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

Y a-t-il d'autres astuces en plus d'utiliser dput(), dump()ou structure()? Quand devez-vous inclure library()ou des require()déclarations? Ce qui a des mots réservés doit - on éviter, en plus c, df, data, etc.?

Comment fait-on un grand exemple reproductible?

Hack-R
la source
34
Je suis confus quant à la portée de la question. Les gens semblent avoir sauté sur l'interprétation de l'exemple reproductible en posant des questions sur SO ou R-help (comment "reproduire l'erreur"). Qu'en est-il des exemples R reproductibles dans les pages d'aide? Dans les démos de packages? Dans des tutoriels / présentations?
baptiste
15
@baptiste: Le même moins l'erreur. Toutes les techniques que j'ai expliquées sont utilisées dans les pages d'aide du package, et dans les tutoriels et les présentations que je donne sur R
Joris Meys
33
Les données sont parfois le facteur limitant, car la structure peut être trop complexe à simuler. Pour produire des données publiques à partir de données privées: stackoverflow.com/a/10458688/742447 dans stackoverflow.com/questions/10454973/…
Etienne Low-Décarie

Réponses:

1727

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

  • un ensemble de données minimal, nécessaire pour démontrer le problème
  • le code exécutable minimal nécessaire pour reproduire l'erreur, qui peut être exécuté sur l'ensemble de données donné
  • les informations nécessaires sur les packages 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é 1

Pour des exemples de bons exemples reproductibles minimes , consultez les fichiers d'aide de la fonction que vous utilisez. En général, tout le code qui y est fourni répond aux exigences d'un exemple reproductible minimal: les données sont fournies, le code minimal est fourni et tout est exécutable. Consultez également les questions sur Stack Overflow avec beaucoup de votes positifs.

Produire un ensemble de données minimal

Pour la plupart des cas, cela peut être fait facilement en fournissant simplement un cadre vectoriel / de données avec quelques valeurs. Ou vous pouvez utiliser l'un des jeux de données intégrés, fournis avec la plupart des packages.
Une liste complète des jeux de données intégrés peut être consultée avec library(help = "datasets"). Il y a une brève description de chaque ensemble de données et plus d'informations peuvent être obtenues, par exemple avec ?mtcarsoù «mtcars» est l'un des ensembles de données dans la liste. D'autres packages peuvent contenir des jeux de données supplémentaires.

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

Quelques exemples:

  • valeurs aléatoires: x <- rnorm(10)pour une 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)

La création de trames de données peut être effectuée à l'aide de data.frame(). Il faut faire attention à nommer les entrées dans le bloc de données et à ne pas le rendre trop compliqué.

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 les fournis as.someTypefonctions: as.factor, as.Date, as.xts, ... Ces en combinaison avec le vecteur et / ou astuces trame de données.

Copiez vos données

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

> dput(iris[1:4, ]) # first four rows of the iris data set
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 trame de données a un facteur à plusieurs niveaux, la dputsortie peut être lourde car elle répertoriera toujours tous les niveaux de facteurs 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 la droplevels()fonction. Notez ci-dessous comment l'espèce est un facteur avec un seul niveau:

> dput(droplevels(iris[1: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")

Lors de l'utilisation dput, vous pouvez également vouloir inclure uniquement les colonnes pertinentes:

> dput(mtcars[1:3, c(2, 5, 6)]) # first three rows of columns 2, 5, and 6
structure(list(cyl = c(6, 6, 4), drat = c(3.9, 3.9, 3.85), wt = c(2.62, 
2.875, 2.32)), row.names = c("Mazda RX4", "Mazda RX4 Wag", "Datsun 710"
), class = "data.frame")

Une autre mise en garde dputest que cela ne fonctionnera pas pour les data.tableobjets à clés ou pour les groupes tbl_df(classes grouped_df) de dplyr. Dans ces cas , vous pouvez reconvertir en une trame de données régulière avant le partage, dput(as.data.frame(my_data)).

Dans le pire des cas, vous pouvez donner une représentation textuelle qui peut être lue en utilisant le textparamè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 ce n'est souvent pas le cas. Ce que vous ne devez pas faire, c'est:

  • ajoutez toutes sortes de conversions de données. Assurez-vous que les données fournies sont déjà dans le format correct (sauf si c'est bien sûr le problème)
  • copier-coller toute une fonction / un morceau de code qui donne une erreur. Tout d'abord, essayez de localiser les lignes qui entraînent exactement l'erreur. Plus souvent qu'autrement, vous découvrirez quel est le problème vous-même.

Ce que vous devez faire, c'est:

  • ajouter les packages à utiliser si vous en utilisez (en utilisant library())
  • si vous ouvrez des connexions ou créez des fichiers, ajoutez du code pour les fermer ou supprimez les fichiers (en utilisant unlink())
  • si vous changez les options, assurez-vous que le code contient une instruction pour les ramener à celles d'origine. (par exemple op <- par(mfrow=c(1,2)) ...some code... par(op))
  • testez 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.

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 packages, donner la sortie de sessionInfo()peut vraiment aider. Lorsque vous parlez de connexions à d'autres applications (que ce soit via ODBC ou autre), il convient également de fournir des numéros de version pour celles-ci et, si possible, les informations nécessaires sur la configuration.

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

Si vous rencontrez un problème avec un package spécifique, vous souhaiterez peut-être fournir la version du package en indiquant la sortie de packageVersion("name of the package").


1 Remarque: La sortie de set.seed()diffère entre R> 3.6.0 et les versions précédentes. Précisez la version R que vous avez utilisée pour le processus aléatoire et ne soyez pas surpris si vous obtenez des résultats légèrement différents lorsque vous suivez d'anciennes questions. Pour obtenir le même résultat dans de tels cas, vous pouvez utiliser la RNGversion()fonction-avant set.seed()(par exemple :) RNGversion("3.5.2").

Joris Meys
la source
6
Comment utilisez-vous dputsi la trame de données est très grande et que le problème est généré par le milieu de la trame de données? Existe-t-il un moyen de dputreproduire la partie médiane des données, par exemple les lignes 60 à 70?
BgnR
27
@BgnR Vous pouvez extraire une partie de la trame de données à l'aide d'indices, par exemple: tmp <- mydf[50:70,]suivi de dput(mydf). Si la trame de données est vraiment volumineuse, essayez d'isoler le problème et soumettez simplement les quelques lignes qui causent le problème.
Joris Meys
4
@JorisMeys: Existe-t-il un moyen de dire headou dputde limiter les données au niveau N de manière récursive? J'essaie de trouver un exemple reproductible et mes données sont une liste de trames de données. Donc, cela dput(head(myDataObj))ne semble pas suffisant, car il génère un fichier de sortie de 14 Mo.
Aleksandr Blekh
5
@JorisMeys: Just FYI - question publiée dans le commentaire ci-dessus en tant que question distincte: stackoverflow.com/questions/25127026/… .
Aleksandr Blekh
4
@Konrad La meilleure chose à faire est de créer un lien vers le fichier et de donner la commande minimale pour lire ce fichier. Ce sera moins compliqué que de copier-coller la sortie de dput () :)
Joris Meys
590

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

Comment écrire un exemple reproductible.

Vous êtes le plus susceptible d'obtenir une bonne aide avec votre problème R si vous fournissez un exemple reproductible. Un exemple reproductible permet à quelqu'un d'autre de recréer votre problème en copiant et collant simplement le code R.

Vous devez inclure quatre éléments pour rendre votre exemple reproductible: les packages requis, les données, le code et une description de votre environnement R.

  • Les packages doivent être chargés en haut du script, il est donc facile de voir ceux dont l'exemple a besoin.

  • Le moyen le plus simple d'inclure des données dans un e-mail ou une question de débordement de pile est d'utiliser dput()pour générer le code R pour le recréer. Par exemple, pour recréer l' mtcarsensemble de données dans R, j'effectuerais les étapes suivantes:

    1. Run dput(mtcars)in 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 à lire pour les autres:

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

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

  • Incluez la sortie de sessionInfo()dans un commentaire dans votre code. Cela résume votre environnement R et vous permet de vérifier facilement si vous utilisez un package obsolète.

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 sur Gist github . Cela donnera à votre code une belle coloration syntaxique et vous n'aurez pas à vous soucier de quoi que ce soit qui soit corrompu par le système de messagerie.

hadley
la source
24
reprexin tidyverseest un bon package pour produire un exemple minimal et reproductible: github.com/tidyverse/reprex
mt1022
19
Je reçois régulièrement des e-mails contenant du code. Je reçois même des e-mails avec des documents Word joints contenant du code. Parfois, je reçois même des e-mails avec des documents Word joints qui contiennent des CAPTURES D'ÉCRAN de code.
hadley
304

Personnellement, je préfère les doublures "one". Quelque chose le long des lignes:

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 des données doit imiter l'idée du problème de l'auteur et non la structure exacte du mot. J'apprécie vraiment quand les variables n'écrasent pas mes propres variables ou que Dieu ne plaise, les 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 packages spéciaux que vous utilisez.

Si vous essayez de dé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 rasterpackage, vous pouvez générer des données aléatoires. De nombreux exemples peuvent être trouvés dans la vignette du package, mais voici un petit 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)

Si vous avez besoin d'un objet spatial tel qu'implémenté dans sp, vous pouvez obtenir certains ensembles de données via des fichiers externes (comme le fichier de formes ESRI) dans des packages "spatiaux" (voir la vue spatiale dans les vues de 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")
Roman Luštrik
la source
1
À mon humble avis, lors de l'utilisation sampleou runifil est prudent de set.seed. C'est du moins la suggestion que j'ai reçue lors de la production d'exemples relayant l'échantillonnage ou la génération de nombres aléatoires.
Konrad
1
@Konrad Je suis d'accord, mais cela peut dépendre. Si vous essayez simplement de générer des nombres, une graine peut ne pas être nécessaire, mais si vous essayez de comprendre quelque chose de spécifique où des nombres fixes sont nécessaires, une graine serait obligatoire.
Roman Luštrik
1
C'est toujours mieux avec un imo de départ, il est plus facile de comparer sa propre solution au résultat attendu, de comparer les solutions entre eux, et de cette façon les utilisateurs qui ne connaissent pas (et n'ont pas besoin de connaître) des fonctions comme runifou samplene sont pas confuses qu'ils ne peuvent pas obtenir les mêmes données.
Moody_Mudskipper
2
@mikey avez-vous regardé le paquet usmap ?
Roman Luštrik
2
@mikey le package tigris télécharge les fichiers de formes du Census Bureau dans une variété de formats
camille
277

Inspiré par ce message, j'utilise maintenant une fonction pratique
reproduce(<mydata>)lorsque je dois publier sur StackOverflow.


INSTRUCTIONS RAPIDES

Si myDataest 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 un wrapper intelligent pour dput:

  • é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 une dputsortie
  • vous permet de spécifier qui 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 puis la coller dans 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 l'intégralité de la sortie est dans une belle ligne longue et non 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 d'octobre 2013:

Vous pouvez maintenant spécifier combien de lignes de sortie de texte occuperont (c'est-à-dire ce que vous collerez dans StackOverflow). Utilisez l' lines.out=nargument 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==
Ricardo Saporta
la source
196

Voici un bon guide .

Le point le plus important est: assurez-vous de créer 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 de très grandes données, vous voudrez peut-être créer un petit échantillon de données ou utiliser uniquement les 10 premières lignes environ.

ÉDITER:

Assurez-vous également que vous avez identifié le problème 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 (j'adore browser()) et Google, vous devriez être en mesure d'identifier vraiment où se trouve le problème et de reproduire un exemple trivial dans lequel la même chose ne va pas.

Sacha Epskamp
la source
165

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

Exemples: Parfois, il est utile de fournir un petit exemple que quelqu'un peut réellement exécuter. 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 cadre de données avec 8 lignes et trois colonnes nommées 'row', 'col' et 'value', qui ont les noms de dimension comme valeurs de 'row' et 'col', comme ceci:

  > x.df
     row col value
  1    A   x      1

...
(à laquelle 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 devez viser un exemple reproductible minimal , ce qui signifie que les données et le code doivent être aussi simples que possible pour expliquer le problème.

EDIT: Un joli code est plus facile à lire qu'un code laid. Utilisez un guide de style .

Richie Cotton
la source
164

Depuis R.2.14 (je suppose), vous pouvez alimenter votre représentation de texte de données directement pour read.table:

 df <- read.table(header=TRUE, 
  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
") 
Paolo
la source
3
@ sebastian-c comment est-ce bon pour faire un exemple reproductible ?? :)
TMS
@TMS En y réfléchissant sérieusement, si le demandeur a fourni les données et que le problème est faible (mais peut avoir quelques solutions), cela pourrait être plus rapide et vous pouvez toujours suivre toutes les étapes.
sebastian-c
146

Parfois, le problème n'est vraiment pas reproductible avec une plus petite quantité de données, peu importe vos efforts, et ne se produit 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 n'ont pas reproduit le problème, car cela exclut certaines hypothèses).

  • Il peut être nécessaire de publier les données sur le Web quelque part et de fournir une URL.
  • Si les données ne peuvent pas être divulguées au grand public mais peuvent être partagées, vous pourrez peut-être proposer de les envoyer par courrier électronique aux parties intéressées (même si cela réduira le nombre de personnes qui se donneront la peine de travailler). dessus).
  • Je n'ai pas vraiment vu cela fait, parce que les personnes qui ne peuvent pas divulguer leurs données sont sensibles à la divulgation de toute forme, mais il semblerait plausible que dans certains cas, on puisse toujours publier des données si elles étaient suffisamment anonymisées / brouillées / corrompues légèrement 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 ...

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

Ben Bolker
la source
1
Pour produire des ensembles de données synthétiques, les réponses à cette question donnent des exemples utiles, y compris les applications de fitdistret fitdistrplus.
Iterator
137

Jusqu'à présent, les réponses sont évidemment excellentes pour la partie reproductibilité. Il s'agit simplement de préciser qu'un exemple reproductible ne peut et ne doit pas être le seul élément d'une question. N'oubliez pas d'expliquer à quoi vous voulez qu'il ressemble et les contours de votre problème, pas seulement comment vous avez tenté d'y arriver jusqu'à présent. Le code ne suffit pas; vous avez aussi besoin de mots.

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


Voici des exemples 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 ?


Ari B. Friedman
la source
124

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'abord définir votre structure. Par exemple,

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

>fix(mydata)

Lorsque vous exécutez la commande «fix», vous obtiendrez cette boîte pop-up

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

jasmine_007
la source
18
... puisdput(mydata)
GSee
Quel est votre frontend? Ce serait bien d'avoir une réponse complète. Etc faire une donnée que vous pouvez directement boucler comme for (d in data) {...}.
Léo Léopold Hertz
119

Pour créer rapidement une dputde vos données, vous pouvez simplement copier (un morceau 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 modifier le sepdans ce dernier si nécessaire. Cela ne fonctionnera que si vos données sont dans le presse-papiers bien sûr.

JT85
la source
116

Des lignes directrices:


Votre objectif principal dans l'élaboration de vos questions devrait être de permettre aux lecteurs de comprendre et de reproduire votre problème aussi facilement que possible sur leurs systèmes. Faire cela:

  1. Fournir des données d'entrée
  2. Fournir la sortie attendue
  3. Expliquez succinctement votre problème
    • si vous avez plus de 20 lignes de texte + code, vous pouvez probablement revenir en arrière et simplifier
    • simplifiez votre code autant que possible tout en préservant le problème / l'erreur

Cela nécessite un certain travail, mais semble être un compromis équitable car vous demandez à d'autres de travailler pour vous.

Fournir des données:


Ensembles de données intégrés

La meilleure option est de loin de s'appuyer sur des jeux de données intégrés. Cela permet aux autres de travailler très facilement sur votre problème. Tapez data()à l'invite R pour voir quelles données sont disponibles pour vous. Quelques exemples classiques:

  • iris
  • mtcars
  • ggplot2::diamonds (paquet externe, mais presque tout le monde l'a)

Consultez ce SO QA pour savoir comment trouver des ensembles de données adaptés à votre problème.

Si vous êtes en mesure de reformuler votre problème pour utiliser les jeux de données intégrés, vous avez beaucoup plus de chances d'obtenir de bonnes réponses (et des votes positifs).

Données auto-générées

Si votre problème est très spécifique à un type de données qui n'est pas représenté dans les ensembles de données existants, fournissez le code R qui génère le plus petit ensemble de données possible sur lequel votre problème se manifeste. Par exemple

set.seed(1)  # important to make random data reproducible
myData <- data.frame(a=sample(letters[1:5], 20, rep=T), b=runif(20))

Maintenant, quelqu'un qui essaie de répondre à ma question peut copier / coller ces deux lignes et commencer à travailler sur le problème immédiatement.

dput

En dernier recours , vous pouvez utiliser dputpour transformer un objet de données en code R (par exemple dput(myData)). Je dis en "dernier recours" car la sortie de dputest souvent assez lourde, ennuyeuse à copier-coller et obscurcit le reste de votre question.

Fournir les résultats attendus:


Quelqu'un a dit un jour:

Une image de la sortie attendue vaut 1000 mots

- une personne très sage

Si vous pouvez ajouter quelque chose comme "je m'attendais à obtenir ce résultat":

   cyl   mean.hp
1:   6 122.28571
2:   4  82.63636
3:   8 209.21429

à votre question, les gens sont beaucoup plus susceptibles de comprendre rapidement ce que vous essayez de faire. Si votre résultat escompté est volumineux et peu maniable, vous n'avez probablement pas suffisamment réfléchi à la façon de simplifier votre problème (voir ci-dessous).

Expliquez votre problème de manière succincte


La principale chose à faire est de simplifier au maximum votre problème avant de poser votre question. Recadrer le problème pour travailler avec les jeux de données intégrés aidera beaucoup à cet égard. Vous constaterez également souvent qu'en suivant simplement le processus de simplification, vous répondrez à votre propre problème.

Voici quelques exemples de bonnes questions:

Dans les deux cas, les problèmes de l'utilisateur ne sont certainement pas liés aux exemples simples qu'ils fournissent. Ils ont plutôt résumé la nature de leur problème et l'ont appliqué à un simple ensemble de données pour poser leur question.

Pourquoi encore une autre réponse à cette question?


Cette réponse se concentre sur ce que je pense être la meilleure pratique: utiliser des ensembles de données intégrés et fournir ce que vous attendez en conséquence sous une forme minimale. Les réponses les plus importantes se concentrent sur d'autres aspects. Je ne m'attends pas à ce que cette réponse prenne de l'importance; c'est ici uniquement pour que je puisse y faire un lien dans les commentaires aux questions des débutants.

BrodieG
la source
113

Un code reproductible est essentiel pour obtenir de l'aide. Cependant, de nombreux utilisateurs pourraient être sceptiques à l'idée de coller même une partie de leurs données. Par exemple, ils pourraient travailler avec des données sensibles ou sur des données originales collectées pour être utilisées dans un document de recherche. Pour une raison quelconque, j'ai pensé qu'il serait bien d'avoir une fonction pratique pour "déformer" mes données avant de les coller publiquement. La anonymizefonction du paquet SciencesPoest très stupide, mais pour moi, cela fonctionne bien avec la dputfonction.

install.packages("SciencesPo")

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

> dt
   Z  X   Y
1  D  8  no
2  T  1 yes
3  J  7  no
4  K  6  no
5  U  2  no
6  A 10 yes
7  Y  5  no
8  M  9 yes
9  X  4 yes
10 Z  3  no

Ensuite, je l'anonymise:

> anonymize(dt)
     Z    X  Y
1   b2  2.5 c1
2   b6 -4.5 c2
3   b3  1.5 c1
4   b4  0.5 c1
5   b7 -3.5 c1
6   b1  4.5 c2
7   b9 -0.5 c1
8   b5  3.5 c2
9   b8 -1.5 c2
10 b10 -2.5 c1

On peut également vouloir échantillonner quelques variables au lieu des données entières avant d'appliquer l'anonymisation et la commande dput.

    # sample two variables without replacement
> anonymize(sample.df(dt,5,vars=c("Y","X")))
   Y    X
1 a1 -0.4
2 a1  0.6
3 a2 -2.4
4 a1 -1.4
5 a2  3.6
daniel
la source
102

Souvent, vous avez besoin de données pour un exemple, cependant, vous ne voulez pas publier vos données exactes. Pour utiliser certains data.frame existants dans la bibliothèque établie, utilisez la commande data pour l'importer.

par exemple,

data(mtcars)

puis faire le problème

names(mtcars)
your problem demostrated on the mtcars data set
userJT
la source
13
De nombreux ensembles de données intégrés (comme les ensembles populaires mtcarset les irisensembles de données) n'ont pas réellement besoin de l' dataappel pour être utilisés.
Gregor Thomas
92

Si vous avez un grand ensemble de données qui ne peut pas être facilement mis dans le script à l'aide de dput(), postez vos données dans pastebin et chargez-les en utilisant read.table:

d <- read.table("http://pastebin.com/raw.php?i=m1ZJuKLH")

Inspiré par @Henrik .

TMS
la source
90

Je développe le package wakefield pour répondre à ce besoin de partager rapidement des données reproductibles, dputfonctionne parfois bien pour les petits ensembles de données, mais bon nombre des problèmes que nous traitons sont beaucoup plus importants, le partage d'un si grand ensemble de données via dputn'est pas pratique.

À propos de:

wakefield permet à l'utilisateur de partager un minimum de code pour reproduire les données. L'utilisateur définitn(nombre de lignes) et spécifie n'importe quel nombre de fonctions variables prédéfinies (il y en a actuellement 70) qui imitent les données réelles si (des choses comme le sexe, l'âge, le revenu, etc.)

Installation:

Actuellement (2015-06-11), wakefield est un package GitHub mais ira finalement au CRAN après l'écriture des tests unitaires. Pour installer rapidement, utilisez:

if (!require("pacman")) install.packages("pacman")
pacman::p_load_gh("trinker/wakefield")

Exemple:

Voici un exemple:

r_data_frame(
    n = 500,
    id,
    race,
    age,
    sex,
    hour,
    iq,
    height,
    died
)

Cela produit:

    ID  Race Age    Sex     Hour  IQ Height  Died
1  001 White  33   Male 00:00:00 104     74  TRUE
2  002 White  24   Male 00:00:00  78     69 FALSE
3  003 Asian  34 Female 00:00:00 113     66  TRUE
4  004 White  22   Male 00:00:00 124     73  TRUE
5  005 White  25 Female 00:00:00  95     72  TRUE
6  006 White  26 Female 00:00:00 104     69  TRUE
7  007 Black  30 Female 00:00:00 111     71 FALSE
8  008 Black  29 Female 00:00:00 100     64  TRUE
9  009 Asian  25   Male 00:30:00 106     70 FALSE
10 010 White  27   Male 00:30:00 121     68 FALSE
.. ...   ... ...    ...      ... ...    ...   ...
Tyler Rinker
la source
72

Si vous avez une ou plusieurs factorvariables dans vos données que vous souhaitez rendre reproductibles dput(head(mydata)), envisagez d'y ajouter droplevels, de sorte que les niveaux de facteurs qui ne sont pas présents dans l'ensemble de données minimisé ne soient pas inclus dans votre dputsortie, afin de rendre l'exemple minimal :

dput(droplevels(head(mydata)))
docendo discimus
la source
65

Je me demande si un lien http://old.r-fiddle.org/ pourrait être un moyen très soigné de partager un problème. Il reçoit un ID unique comme et on pourrait même penser à l'intégrer dans SO.

CMichael
la source
47

Veuillez ne pas coller les sorties de votre console comme ceci:

If I have a matrix x as follows:
> 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
>

How can I turn it into a dataframe with 8 rows, and three
columns named `row`, `col`, and `value`, which have the
dimension names as the values of `row` and `col`, like this:
> x.df
    row col value
1    A   x      1
...
(To which the answer might be:
> 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")
)

Nous ne pouvons pas le copier-coller directement.

Pour que les questions et réponses soient correctement reproductibles, essayez de supprimer +& >avant de les publier et de soumettre #des sorties et des commentaires comme ceci:

#If I have a matrix x as follows:
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

# How can I turn it into a dataframe with 8 rows, and three
# columns named `row`, `col`, and `value`, which have the
# dimension names as the values of `row` and `col`, like this:

#x.df
#    row col value
#1    A   x      1
#...
#To which the answer might be:

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

Encore une chose, si vous avez utilisé une fonction de certains packages, mentionnez cette bibliothèque.

2100721
la source
2
supprimez-vous le >et ajoutez-le #manuellement ou existe-t-il un moyen automatique de le faire?
BCArg
3
@BCArg je supprime >manuellement. Mais, pour l'ajout de #, j'utilise un Ctrl+Shift+Craccourci dans l' RStudioéditeur.
user2100721
33

Vous pouvez le faire en utilisant reprex .

Comme l'a noté mt1022 , "... un bon package pour produire un exemple reproductible minimal est " reprex " from tidyverse ".

Selon Tidyverse :

Le but de "reprex" est d'empaqueter votre code problématique de telle manière que d'autres personnes puissent l'exécuter et ressentir votre douleur.

Un exemple est donné sur le site web de tidyverse .

library(reprex)
y <- 1:4
mean(y)
reprex() 

Je pense que c'est la façon la plus simple de créer un exemple reproductible.

andrii
la source
33

En dehors de toutes les réponses ci-dessus que j'ai trouvées très intéressantes, cela peut parfois être très facile car il est discuté ici: - COMMENT FAIRE UN EXEMPLE MINIMAL REPRODUCTIBLE POUR OBTENIR DE L'AIDE AVEC R

Il existe plusieurs façons de créer un vecteur aléatoire Créez un vecteur de 100 nombres avec des valeurs aléatoires en R arrondies à 2 décimales ou une matrice aléatoire en R

mydf1<- matrix(rnorm(20),nrow=20,ncol=5)

Notez qu'il est parfois très difficile de partager une donnée donnée pour diverses raisons telles que la dimension, etc. Cependant, toutes les réponses ci-dessus sont excellentes et très importantes à penser et à utiliser lorsque l'on veut faire un exemple de données reproductibles. Mais notez que pour rendre des données aussi représentatives que l'original (dans le cas où l'OP ne peut pas partager les données d'origine), il est bon d'ajouter des informations avec l'exemple de données comme (si nous appelons les données mydf1)

class(mydf1)
# this shows the type of the data you have 
dim(mydf1)
# this shows the dimension of your data

De plus, il faut connaître le type, la longueur et les attributs d'une donnée qui peut être une structure de données

#found based on the following 
typeof(mydf1), what it is.
length(mydf1), how many elements it contains.
attributes(mydf1), additional arbitrary metadata.

#If you cannot share your original data, you can str it and give an idea about the structure of your data
head(str(mydf1))
5947301
la source
28

Voici quelques-unes de mes suggestions:

  • Essayez d'utiliser les jeux de données R par défaut
  • Si vous avez votre propre jeu de données, incluez-les avec dput, afin que d'autres puissent vous aider plus facilement
  • Ne pas utiliser install.package()sauf si c'est vraiment nécessaire, les gens comprendront si vous utilisez requireoulibrary
  • Essayez d'être concis,

    • Avoir un ensemble de données
    • Essayez de décrire la sortie dont vous avez besoin aussi simplement que possible
    • Faites-le vous-même avant de poser la question
  • Il est facile de télécharger une image, alors téléchargez des tracés si vous avez
  • Incluez également toutes les erreurs que vous pourriez avoir

Tout cela fait partie d'un exemple reproductible.

TheRimalaya
la source
1
Vous n'avez vraiment rien ajouté de substantiel ici. dput()a été mentionné précédemment, et une grande partie de cela ne fait que réitérer les directives standard de SO.
Rich Scriven
1
J'avais un problème avec la install.packagefonction incluse dans l'exemple qui n'est pas vraiment nécessaire (à mon avis). De plus, l'utilisation de l'ensemble de données R par défaut faciliterait la reproductibilité. Les directives de l'OS n'ont pas parlé spécifiquement de ces sujets. De plus, il était censé donner mon avis et ce sont ceux que j'ai le plus rencontrés.
TheRimalaya
18

C'est une bonne idée d'utiliser les fonctions du testthatpackage pour montrer ce que vous attendez. Ainsi, d'autres personnes peuvent modifier votre code jusqu'à ce qu'il s'exécute sans erreur. Cela allège le fardeau de ceux qui souhaitent vous aider, car cela signifie qu'ils n'ont pas à décoder votre description textuelle. Par exemple

library(testthat)
# code defining x and y
if (y >= 10) {
    expect_equal(x, 1.23)
} else {
    expect_equal(x, 3.21)
}

est plus clair que "je pense que x serait égal à 1,23 pour y égal ou supérieur à 10, et 3,21 sinon, mais je n'ai obtenu aucun résultat". Même dans cet exemple stupide, je pense que le code est plus clair que les mots. L'utilisation testthatpermet à votre assistant de se concentrer sur le code, ce qui lui fait gagner du temps et lui permet de savoir qu'il a résolu votre problème avant de le publier.

humide
la source