题 如何制作一个很好的R可重复的例子?


在与同事讨论表现,教学,发送错误报告或在邮件列表上搜索指导时,在此处, 可重复的例子 经常被问及总是乐于助人。

您有什么建议创建优秀示例的提示?如何粘贴数据结构  以文本格式?您应该包括哪些其他信息?

除了使用之外还有其他技巧吗? dput()dump() 要么 structure()?你什么时候应该包括 library() 要么 require() 声明?除了之外,还应避免使用哪些保留字 cdfdata等等?

如何成为一个伟大的人  可重复的例子?


2381


起源


我对这个问题的范围感到困惑。人们似乎已经在提出有关SO或R-help(如何“重现错误”)的问题时跳过对可重复示例的解释。如何在帮助页面中重现R示例?在包演示中?在教程/演示中? - baptiste
@baptiste:同样减去错误。我解释的所有技术都用在包帮助页面中,在我给出的关于R的教程和演示中 - Joris Meys
数据有时是限制因素,因为结构可能太复杂而无法模拟。从私人数据生成公共数据: stackoverflow.com/a/10458688/742447 在 stackoverflow.com/questions/10454973/... - Etienne Low-Décarie


答案:


可重复性最小的示例包含以下项目:

  • 重现错误所必需的最小数据集
  • 最小的 可运行 重现错误所需的代码,可以在给定的数据集上运行。
  • 有关所使用的软件包,R版本及其运行的系统的必要信息。
  • 在随机过程的情况下,种子(由...设置) set.seed())为了再现性

查看已使用函数的帮助文件中的示例通常很有帮助。通常,那里给出的所有代码都满足最小可重复示例的要求:提供数据,提供最少的代码,并且一切都是可运行的。

生成最小数据集

对于大多数情况,只需提供带有某些值的矢量/数据帧即可轻松完成。或者您可以使用大多数软件包提供的内置数据集之一。
可以看到内置数据集的完整列表 library(help = "datasets")。每个数据集都有一个简短的描述,例如可以获得更多信息 ?mtcars 其中'mtcars'是列表中的数据集之一。其他包可能包含其他数据集。

制作矢量很容易。有时需要为它添加一些随机性,并且有许多功能可以实现。 sample() 可以随机化一个向量,或者给出一个只有几个值的随机向量。 letters 是一个包含字母表的有用矢量。这可以用于制作因素。

几个例子:

  • 随机值: x <- rnorm(10) 对于正态分布, x <- runif(10) 为了均匀分布,......
  • 一些值的排列: x <- sample(1:10) 对于矢量1:10的随机顺序。
  • 随机因素: x <- sample(letters[1:4], 20, replace = TRUE)

对于矩阵,可以使用 matrix(),例如:

matrix(1:10, ncol = 2)

制作数据帧可以使用 data.frame()。应注意在数据框中命名条目,并且不要使其过于复杂。

一个例子 :

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

对于某些问题,可能需要特定格式。对于这些,可以使用任何提供的 as.someType 功能 : as.factoras.Dateas.xts,...这些与矢量和/或数据框架技巧相结合。

复制您的数据

如果你有一些使用这些技巧难以构建的数据,那么你总是可以使用例如原始数据的子集。 head()subset()或指数。然后使用例如。 dput() 给我们一些可以立即放入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")

如果您的数据框具有多个级别的因子,则 dput 输出可能很难处理,因为它仍会列出所有可能的因子级别,即使它们不存在于数据子集中。要解决此问题,您可以使用 droplevels() 功能。请注意以下物种是如何只有一个水平的因素:

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

另一个警告 dput 是它不适用于键控 data.table 对象或分组 tbl_df (类 grouped_df)来自 dplyr。在这些情况下,您可以在共享之前转换回常规数据框, dput(as.data.frame(my_data))

在最坏的情况下,您可以提供可以使用的文本表示 text 的参数 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)

生成最少的代码

这应该是容易的部分,但往往不是。你不应该做的是:

  • 添加所有类型的数据转换。确保提供的数据格式正确(除非这是问题)
  • 复制粘贴一个产生错误的整个函数/代码块。首先,尝试找出确切导致错误的行。通常情况下,你会发现自己的问题是什么。

你应该做的是:

  • 添加使用任何包时应使用哪些包(使用 library()
  • 如果您打开连接或创建文件,添加一些代码来关闭它们或删除文件(使用 unlink()
  • 如果更改选项,请确保代码包含一个语句,以将其还原为原始语句。 (例如 op <- par(mfrow=c(1,2)) ...some code... par(op) )
  • 测试在新的空R会话中运行您的代码,以确保代码是可运行的。人们应该能够在控制台中复制粘贴您的数据和代码,并获得与您完全相同的信息。

提供额外信息

在大多数情况下,仅R版本和操作系统就足够了。当包与包含冲突时,给出输出 sessionInfo() 可以真的帮助。在谈论与其他应用程序的连接时(无论是通过ODBC还是其他任何应用程序),还应该为这些应用程序提供版本号,如果可能,还应提供有关设置的必要信息。

如果你正在运行R in R Studio 运用 rstudioapi::versionInfo() 可以帮助您报告您的RStudio版本。

如果您对特定包有问题,可能需要通过提供输出来提供包的版本 packageVersion("name of the package")


1454



你怎么用 dput 如果数据帧非常大并且问题是由数据帧的中间产生的?有没有办法使用 dput 重现数据的中间部分,比如60到70行? - BgnR
@BgnR您可以使用索引提取部分数据框,例如: tmp <- mydf[50:70,] 其次是 dput(mydf)。如果数据框非常大,请尝试隔离问题并提交导致问题的几行。 - Joris Meys
@JorisMeys:有没有办法告诉你 head 要么 dput 将数据限制为N级递归?我正在尝试提供可重现的示例,我的数据是一个数据框列表。所以, dput(head(myDataObj)) 似乎还不够,因为它生成了14MB大小的输出文件。 - Aleksandr Blekh
@JorisMeys:仅供参考 - 在上述评论中发布的问题是一个单独的问题: stackoverflow.com/questions/25127026/...。 - Aleksandr Blekh
@Konrad你能做的最好的事情是链接到文件并给出最小的命令来读取该文件。这比复制粘贴dput()的输出要麻烦得少:) - Joris Meys


(这是我的建议 如何编写可重现的示例 。我试着让它变短但很甜)

如何编写可重现的示例。

如果您提供可重现的示例,您最有可能获得R问题的良好帮助。一个可重现的示例允许其他人通过复制和粘贴R代码来重新创建您的问题。

为了使您的示例可重现,您需要包含四件事:所需的包,数据,代码和R环境的描述。

  • 应该加载在脚本的顶部,所以很容易 看看这个例子需要哪些。

  • 最简单的方法 数据 在电子邮件或Stack Overflow问题中使用 dput() 生成 R代码重新创建它。例如,要重新创建 mtcars R中的数据集, 我执行以下步骤:

    1. dput(mtcars) 在R
    2. 复制输出
    3. 在我可重现的脚本中,键入 mtcars <- 然后粘贴。
  • 花一点时间确保你的  别人容易 读:

    • 确保你已经使用了空格,你的变量名称很简洁,但是 信息

    • 使用注释来指出问题所在

    • 尽力删除与问题无关的所有内容。
      代码越短,理解起来就越容易。

  • 包括输出 sessionInfo() 在您的代码中的评论。这总结了你的 [R 环境 并且可以轻松检查您是否使用了过时的 包。

你可以通过启动一个新的R会话并粘贴你的脚本来检查你是否真的做了一个可重现的例子。

在将所有代码放入电子邮件之前,请考虑将其放入 要点github 。它将为您的代码提供良好的语法突出显示,并且您不必担心电子邮件系统会破坏任何内容。


514



reprex 在 tidyverse是一个很好的包,用于生成最小的,可重现的示例: github.com/tidyverse/reprex - mt1022
为什么有人会把代码放在电子邮件中? - Gilgamesh
我经常收到包含代码的电子邮件。我甚至收到附有包含代码的附加单词文档的电子邮件。有时我甚至会收到包含SCREENSHOTS代码的附加word文档的电子邮件。 - hadley


就个人而言,我更喜欢“一个”衬里。一些事情:

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)

数据结构应该模仿作者的问题而不是确切的逐字结构。当变量不覆盖我自己的变量或者上帝禁止函数(比如。)时,我真的很感激 df)。

或者,可以剪切几个角并指向预先存在的数据集,例如:

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

不要忘记提及您可能使用的任何特殊包装。

如果你试图在较大的物体上展示某些东西,你可以试试

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

如果您正在使用空间数据 raster 包,你可以生成一些随机数据。在包装插图中可以找到很多例子,但这里有一个小块。

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)

如果你需要一些空间对象,如实现的那样 sp,您可以通过“空间”包中的外部文件(如ESRI shapefile)获取一些数据集(请参阅任务视图中的空间视图)。

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

258



恕我直言,使用时 sample 要么 runif 这是谨慎的 set.seed。至少,这是我在制作关于采样或随机数生成的示例时收到的建议。 - Konrad
@Konrad我同意,但这可能取决于。如果您只是想生成一些数字,则可能不需要种子,但如果您想要了解需要固定数字的特定内容,则必须使用种子。 - Roman Luštrik


受到这篇文章的启发,我现在使用了一个方便的功能
reproduce(<mydata>) 当我需要发布到StackOverflow。


快速说明

如果 myData 是要重现的对象的名称,在R中运行以下命令:

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

reproduce(myData)

细节:

这个功能是一个智能包装器 dput 并执行以下操作:

  • 自动采样大型数据集(基于大小和类别。样本大小可以调整)
  • 创造一个 dput 产量
  • 允许您指定 哪一个 要导出的列
  • 追加到它的前面 objName <- ... 这样它可以轻松复制+粘贴,但......
  • 如果在Mac上工作,输出会自动复制到剪贴板,这样您只需运行它然后粘贴到您的问题。

来源可在此处获得:


例:

# 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约为100 x 102.我想抽样10行,以及一些特定的列

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

给出以下输出:

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

另请注意,输出的整体是一条漂亮的单行,长行,而不是一段很高的切割线。 这样可以更容易阅读SO问题帖子,也更容易复制+粘贴。


2013年10月更新:

您现在可以指定将占用多少行文本输出(即,您将粘贴到StackOverflow中的内容)。使用 lines.out=n 这个论点。例:

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

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





这是一个很好的指南:

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

但最重要的是:确保你制作了一小段我们可以运行的代码来查看问题所在。对此有用的功能是 dput(),但如果你有非常大的数据,你可能想要制作一个小的样本数据集或只使用前10行左右。

编辑:

还要确保您确定问题出在哪里。该示例不应该是整个R脚本,“On line 200存在错误”。如果您使用R中的调试工具(我喜欢 browser())和谷歌你应该能够真正确定问题所在,并重现一个琐碎的例子,其中同样的事情出错。


168





R-help邮件列表有一个 发布指南 其中包括提问和回答问题,包括生成数据的示例:

示例:有时它会有所帮助   提供一个小例子   实际上可以运行。例如:

如果我有一个矩阵x如下:

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

如何将其转换为数据帧   有8行,有3列命名   'row','col'和'value',它们都有   维度名称为'row'和'col'的值,如下所示:

  > x.df
     row col value
  1    A   x      1

...
  (答案可能是:

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

这个单词  特别重要。你应该瞄准一个 最小 可重现的例子,这意味着数据和代码应尽可能简单地解释问题。

编辑:漂亮的代码比丑陋的代码更容易阅读。用一个 时尚指南


142





从R.2.14(我猜)你可以直接将数据文本表示提供给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



你也可以使用 read.table("clipboard", header=TRUE)。 - sebastian-c
@ sebastian-c如何制作可重复的例子? :) - TMS
@TMS认真思考,如果提问者提供了数据且问题很小(但可能有一些解决方案),那么它可能会更快,你仍然可以按照所有步骤进行操作。 - sebastian-c


有时,无论您尝试多么努力,问题都无法通过较小的数据重现,并且合成数据不会发生(尽管显示您如何生成合成数据集很有用  重现问题,因为它排除了一些假设)。

  • 可能需要在某处将数据发布到Web并提供URL。
  • 如果数据无法向公众发布但可以共享,那么您可以通过电子邮件将其发送给感兴趣的各方(尽管这会减少需要工作的人数)在上面)。
  • 我实际上没有看到这样做,因为无法发布数据的人对于以任何形式发布数据都很敏感,但似乎有理由认为,如果数据被充分匿名化/加扰/损坏,仍然可以发布数据某种程度上来说。

如果您不能做其中任何一项,那么您可能需要聘请一名顾问来解决您的问题......

编辑:匿名/加扰的两个有用的SO问题:


126



为了生成合成数据集, 这个问题的答案 提供有用的例子,包括应用程序 fitdistr 和 fitdistrplus。 - Iterator


到目前为止,答案对于再现性部分来说显然是很好的。这仅仅是为了澄清一个可重复的例子不能也不应该是问题的唯一组成部分。不要忘记解释你想要它的样子和问题的轮廓,而不仅仅是你到目前为止试图到达那里的方式。代码还不够;你还需要单词。

这是一个可重复的例子,说明要避免做什么(从一个真实的例子中提取,名称改为保护无辜者):


以下是我遇到问题的示例数据和部分功能。

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

我怎样才能做到这一点?



115