如何制作一个优秀的R可重现示例

2466

当与同事讨论性能、教学、发送错误报告或在邮件列表和Stack Overflow上寻求指导时,通常会要求提供可重现的示例,这总是很有帮助的。

您创建优秀示例的建议是什么?如何将的数据结构粘贴到文本格式中?还应包括哪些其他信息?

除了使用dput()dump()structure()之外,是否还有其他技巧?何时应包含library()require()语句?除了cdfdata等之外,还应避免使用哪些保留字?

如何创建一个优秀的可重现示例?


34
我对问题的范围感到困惑。人们似乎在 SO 或 R-help 上询问问题时,已经理解了“重现错误”的含义。那么关于帮助页面中可重现的 R 示例呢?关于包演示?关于教程/演讲? - baptiste
15
相同之处在于减去误差。我解释的所有技术都被用在包帮助页面以及我关于R的教程和演示中。 - Joris Meys
33
有时候数据是限制因素,因为结构可能过于复杂难以模拟。生成公共数据要从私有数据开始:https://dev59.com/HGkv5IYBdhLWcg3wpCXJ#10458688 在 https://dev59.com/HGkv5IYBdhLWcg3wpCXJ#10458688 - Etienne Low-Décarie
23个回答

138

指南:


您在撰写问题时的主要目标应该是尽可能地让读者理解并在他们的系统上重现您的问题。为此,请遵循以下步骤:
  1. 提供输入数据
  2. 提供预期输出
  3. 简洁地解释您的问题
    • 如果您有超过20行文本+代码,则可以返回并简化
    • 尽可能简化代码,同时保留问题/错误

这需要一些工作,但似乎是一个公平的交换,因为您要求别人为您工作。

提供数据:


内置数据集

迄今为止最好的选择是依赖于内置数据集。这使得其他人很容易处理您的问题。在R提示符下键入data()以查看可用的数据。一些经典的例子:

  • iris
  • mtcars
  • ggplot2::diamonds(外部包,但几乎每个人都有它)

检查内置数据集以找到适合您问题的数据集。

如果您可以重新构思问题以使用内置数据集,则更有可能获得良好的答案(和赞同)。

自行生成的数据

如果您的问题特定于现有数据集中未表示的某种数据类型,请提供生成问题最小数据集的R代码。例如:

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

如果有人想回答我的问题,可以复制/粘贴这两行代码并立即开始解决问题。

dput

作为最后的手段,您可以使用dput将数据对象转换为R代码(例如dput(myData))。我之所以说作为“最后的手段”,是因为dput的输出通常相当笨重,难以复制/粘贴,并且会遮盖您问题的其余部分。

提供期望的输出:


曾经有人说:

一张期望输出的图片胜过千言万语

-- 一位智者

如果你能添加类似于“我期望得到这个结果”的东西:

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

针对你的问题,如果你能够简明扼要地表达出来,人们很可能会更快地理解你想做什么。如果你期望的结果过于庞大或者难以掌控,那么你可能还没有考虑如何简化问题(请参见下一部分)。

简明扼要地解释你的问题


在提问之前,最重要的是尽可能地简化你的问题。将问题重新构建为适用于内置数据集的形式可以在很大程度上帮助解决这个问题。此外,仅通过简化过程,您通常会发现自己已经回答了自己的问题。

以下是一些好问题的示例:

在这两种情况下,用户的问题几乎肯定不是针对他们提供的简单示例。相反,他们将其问题的本质抽象出来,并应用到一个简单的数据集中来提问。

为什么需要另一个关于这个问题的答案?


这个答案关注我认为是最佳实践的内容:使用内置数据集,并以最小形式提供预期结果。最突出的答案关注其他方面。我不希望这个答案获得任何重要性;这里只是为了我可以在针对新手问题的评论中链接到它。

138
为了快速创建数据的dput,您可以将数据的(一部分)复制到剪贴板中,并在R中运行以下内容:
对于Excel中的数据:
dput(read.table("clipboard", sep="\t", header=TRUE))

对于一个 .txt 文件中的数据:

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

如果需要,您可以更改后面的sep。当然,这仅在您的数据在剪贴板中时才有效。

136
可重现的代码是获取帮助的关键。然而,有许多用户可能对粘贴自己数据的一部分持怀疑态度。例如,他们可能正在处理敏感数据或原始数据,这些数据被收集用于撰写研究论文。
出于任何原因,我认为在将数据公开粘贴之前,拥有一个方便的“变形”函数会很好。来自SciencesPo包的anonymize函数非常愚蠢,但对我来说,它与dput函数配合得很好。
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

然后我对其进行匿名处理:
> 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

在应用匿名化和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

117

通常你需要一些数据作为例子,但是你又不想公开你的确切数据。为了使用已有库中的某个现有数据框架,可以使用data命令导入它。

e.g.,

data(mtcars)

然后解决问题

names(mtcars)
your problem demostrated on the mtcars data set

15
许多内置数据集(例如流行的mtcarsiris数据集)实际上不需要使用data函数调用。 - Gregor Thomas

108

如果您有一个大型数据集,无法使用dput()轻松地将其放入脚本中,请将您的数据发布到Pastebin并使用read.table加载它们:

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

Henrik的启发。


102

我正在开发 wakefield,以解决快速共享可重现数据的需求。虽然 dput 对于较小的数据集效果很好,但我们处理的许多问题都涉及更大的数据集,在 dput 中共享如此大的数据集是不切实际的。

关于:

wakefield 允许用户分享最少的代码来重现数据。用户设置行数(n)并指定任意数量的预设变量函数(当前有70个),这些函数模拟真实的数据,例如性别、年龄、收入等。

安装:

目前(2015-06-11),wakefield 是一个 GitHub 包,但在编写单元测试后将最终进入 CRAN。要快速安装,请使用:

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

示例:

这里有一个示例:

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

这将产生:

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

85

如果您的数据中有一个或多个factor变量,您希望用dput(head(mydata))使其可复制,请考虑添加droplevels,以便在最小化数据集中不存在的因子水平不包括在您的dput输出中,以便使示例尽可能小:

dput(droplevels(head(mydata)))

74

原帖提到了现已退役的DataCamp r-fiddle服务。它已被重新品牌为DataCamp Light,不能像我的答案所示那样轻松地嵌入。

我想知道是否可以使用http://old.r-fiddle.org/链接来分享问题,这可能是一个非常简洁的方法。它接收一个唯一的ID,甚至可以考虑将其嵌入到 Stack Overflow 中。


1
这个网站看起来非常慢。 - Julien
你能解释一下它是如何工作的吗? - Julien
他们已经停止了该服务,它不再有用。为此更新答案。 - CMichael

56
请勿像这样粘贴您的控制台输出:

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

我们不能直接复制粘贴它。

为了使问题和答案正常复现,请在发布前尝试删除+>,并将输出和评论用#标出来,例如:

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

还有一件事,如果您使用了某个包中的任何函数,请提到该库。


3
你是手动删除 > 并添加 # 吗?还是有自动的方式来完成这个操作? - BCArg
4
我手动删除了">",但是为了添加"#",我在RStudio编辑器中使用了"Ctrl+Shift+C"快捷键。 - user2100721
@user2100721 在Rstudio编辑器中,你可以直接将所有的>替换为#。只需将文本粘贴到编辑器中,然后点击编辑器窗格顶部的搜索图标。在搜索输入框中输入>,在替换输入框中输入# ,然后点击“全部替换”按钮即可。 - Abednego Nasila

48

您可以使用reprex来完成这个任务。

正如mt1022指出的那样,"... 用于生成最小可重现示例的好包是来自tidyverse"的"reprex"

根据Tidyverse

"reprex"的目标是以这样的方式打包您的问题代码,以便其他人可以运行它并感受到您的痛苦。

tidyverse网站上提供了一个示例。

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

我认为这是创建可重复示例的最简单方法


1
当我使用的函数不是来自基本R时,我会收到一个错误,这是否是预期的? - Diego
3
你是否在reprex中加载了你的库?否则,代码就无法独立复制。 - Arthur Yip
1
我知道已经有一段时间了。无论如何,提醒一下:在键入并输入 reprex() 之前,您需要将代码复制到剪贴板中,因为这是它获取代码的位置。 - Dr_Be

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接