在与同事讨论绩效、教学、发送错误报告或搜索邮件列表和Stack Overflow上的指导时,经常会询问一个可重复的示例,并且总是很有用。

你有什么建议来创建一个优秀的例子?如何以文本格式粘贴r中的数据结构?您还应包括哪些其他信息?

除了使用dput()、dump()或structure()之外,还有其他技巧吗?什么时候应该包含library()或require()语句?除了c、df、data等之外,应该避免哪些保留字。?

如何做出一个伟大的、可重复的例子?


当前回答

从R.2.14开始(我猜),您可以将数据文本表示直接输入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
") 

其他回答

指南:


你提出问题的主要目的应该是让读者尽可能容易地理解并在他们的系统上重现你的问题。为此:

提供输入数据提供预期输出简洁地解释您的问题如果您有超过20行的文本+代码,您可能可以回去简化尽可能简化代码,同时保留问题/错误

这确实需要一些工作,但这似乎是一种公平的权衡,因为你要求别人为你做工作。

提供数据:


内置数据集

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

虹膜地铁车厢ggplot2::钻石(外包装,但几乎每个人都有)

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

如果你能用内置的数据集重新表述你的问题,你就更有可能得到好的答案(和支持)。

自行生成的数据

如果您的问题是特定于现有数据集中未表示的数据类型,请提供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的输出通常相当笨拙,复制粘贴很烦人,并掩盖了您的其余问题。

提供预期输出:


有人曾经说过:

一张预期产出的图片值1000字--智者

如果您可以添加类似“我希望得到这个结果”的内容:

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

对于你的问题,人们更容易理解你想快速做什么。如果您的预期结果很大且难以处理,那么您可能还没有充分考虑如何简化您的问题(见下一页)。

简洁地解释您的问题


主要要做的是在提问之前尽可能简化问题。在这方面,重新构建问题框架以使用内置数据集将有很大帮助。你也会经常发现,仅仅通过简化的过程,你就能回答自己的问题。

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

内置数据集使用用户生成的数据

在这两种情况下,用户的问题几乎肯定与他们提供的简单示例无关。相反,他们抽象了问题的本质,并将其应用于一个简单的数据集,以提出问题。

为什么这个问题还有另一个答案?


这个答案侧重于我认为的最佳实践:使用内置数据集,并以最小的形式提供您期望的结果。最突出的答案侧重于其他方面。我不指望这个答案会上升到任何突出的位置;这只是为了让我可以在新手问题的评论中链接到它。

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

)

“小”这个词特别重要。您应该以最小的可重复示例为目标,这意味着数据和代码应该尽可能简单地解释问题。

编辑:漂亮的代码比难看的代码更容易阅读。使用样式指南。

可复制代码是获得帮助的关键。然而,许多用户可能对粘贴哪怕是一大块数据都持怀疑态度。例如,他们可能在处理敏感数据,或者在研究论文中使用收集的原始数据。

出于任何原因,我认为在公开粘贴数据之前,有一个方便的函数来“变形”我的数据会很好。SciencesPo包中的匿名化函数非常愚蠢,但对我来说,它与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

到目前为止,对于再现性部分,答案显然很好。这只是为了澄清,一个可复制的例子不能也不应该是问题的唯一组成部分。别忘了解释你希望它看起来是什么样子,以及你的问题的轮廓,而不仅仅是你迄今为止试图达到的目的。代码不够;你也需要语言。

这里有一个可重复的例子来说明应该避免做什么(从一个真实的例子中得出,为了保护无辜者而改变了名字):


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

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

我怎样才能做到这一点?


基本上,一个最小的可重复示例(MRE)应该能够让其他人在他们的机器上准确地再现您的问题。

请不要发布数据、代码或控制台输出的图像!

tl;博士

MRE包括以下项目:

演示问题所需的最小数据集再现问题所需的最小可运行代码,可在给定数据集上运行有关所用库、R版本和运行该库的操作系统的所有必要信息,可能是sessionInfo()在随机进程的情况下,一个种子(set by set.seed())使其他人能够复制与您完全相同的结果

有关良好MRE的示例,请参阅帮助页底部有关您正在使用的函数的“示例”部分。只需键入例如help(mean)或short?意味着进入你的R控制台。

提供最小数据集

通常,共享巨大的数据集是不必要的,而且可能会阻碍其他人阅读您的问题。因此,最好使用内置数据集或创建一个类似于原始数据的小“玩具”示例,这实际上是指最小值。如果出于某种原因,您确实需要共享原始数据,那么您应该使用一种方法,例如dput(),允许其他人获得数据的精确副本。

内置数据集

您可以使用内置数据集之一。使用data()可以看到内置数据集的全面列表。每个数据集都有简短的描述,可以获得更多信息,例如:?虹膜,用于R附带的“虹膜”数据集。安装的软件包可能包含其他数据集。

创建示例数据集

初步说明:有时您可能需要特殊格式(例如类),例如因子、日期或时间序列。对于这些,请使用以下函数:as.factor、as.Date、as.xts…例如:

d <- as.Date("2020-12-30")

哪里

class(d)
# [1] "Date"

矢量

x <- rnorm(10)  ## random vector normal distributed
x <- runif(10)  ## random vector uniformly distributed    
x <- sample(1:100, 10)  ## 10 random draws out of 1, 2, ..., 100    
x <- sample(LETTERS, 10)  ## 10 random draws out of built-in latin alphabet

矩阵

m <- matrix(1:12, 3, 4, dimnames=list(LETTERS[1:3], LETTERS[1:4]))
m
#   A B C  D
# A 1 4 7 10
# B 2 5 8 11
# C 3 6 9 12

数据帧

set.seed(42)  ## for sake of reproducibility
n <- 6
dat <- data.frame(id=1:n, 
                  date=seq.Date(as.Date("2020-12-26"), as.Date("2020-12-31"), "day"),
                  group=rep(LETTERS[1:2], n/2),
                  age=sample(18:30, n, replace=TRUE),
                  type=factor(paste("type", 1:n)),
                  x=rnorm(n))
dat
#   id       date group age   type         x
# 1  1 2020-12-26     A  27 type 1 0.0356312
# 2  2 2020-12-27     B  19 type 2 1.3149588
# 3  3 2020-12-28     A  20 type 3 0.9781675
# 4  4 2020-12-29     B  26 type 4 0.8817912
# 5  5 2020-12-30     A  26 type 5 0.4822047
# 6  6 2020-12-31     B  28 type 6 0.9657529

注意:虽然它被广泛使用,但最好不要将数据帧命名为df,因为df()是F分布的密度(即x点处曲线的高度)的R函数,您可能会与它发生冲突。

复制原始数据

如果您有特定的原因,或者数据很难从中构建示例,那么可以提供原始数据的一小部分,最好使用dput。

为什么使用dput()?

dput抛出在控制台上准确再现数据所需的所有信息。您可以简单地复制输出并将其粘贴到问题中。

调用dat(从上面)产生的输出仍然缺少关于变量类和其他特性的信息,如果您在问题中共享它。此外,type列中的空格使我们很难使用它。即使我们开始使用数据,我们也无法正确获取数据的重要特性。

  id       date group age   type         x
1  1 2020-12-26     A  27 type 1 0.0356312
2  2 2020-12-27     B  19 type 2 1.3149588
3  3 2020-12-28     A  20 type 3 0.9781675

子集数据

要共享子集,请使用head()、subset()或索引iris[1:4,]。然后将其包装到dput()中,以给其他人一些可以立即放入R中的东西。实例

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")), row.names = c(NA, 
4L), class = "data.frame")

使用dput时,您可能还希望只包含相关列,例如dput(mtcars[1:3,c(2,5,6)])

注意:如果数据帧具有多个级别的因子,则dput输出可能会很难处理,因为它仍然会列出所有可能的因子级别,即使它们不在数据的子集中。要解决此问题,可以使用droplevels()函数。注意下面的物种是一个只有一个等级的因素,例如dput(下降等级(虹膜[1:4,]))。dput的另一个警告是,它不适用于键控data.table对象或来自tidyverse的分组tbl_df(class grouped_df)。在这些情况下,您可以在共享之前转换回常规数据帧dput(如.data.frame(my_data))。

生成最小代码

结合最少的数据(见上文),您的代码应该通过简单的复制和粘贴在另一台机器上准确地再现问题。

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

示出了各种数据转换;确保提供的数据已经是正确的格式(当然,除非这是问题所在)复制粘贴在某个地方出现错误的整个脚本。尝试找出导致错误的确切行。通常情况下,你会发现问题出在自己身上。

您应该做什么:

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

提供必要信息

在大多数情况下,只有R版本和操作系统就足够了。当包发生冲突时,提供sessionInfo()的输出确实会有所帮助。当谈到与其他应用程序的连接(无论是通过ODBC还是其他任何方式)时,还应提供这些应用程序的版本号,如果可能,还应包括有关设置的必要信息。

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

如果您对特定的包有问题,您可能希望通过提供packageVersion(“包的名称”)的输出来提供包版本。

Seed

使用set.seed()可以指定seed1,即特定状态,R的随机数生成器是固定的。这使得随机函数(如sample()、rnorm()、runif()和其他许多函数)可以始终返回相同的结果,例如:

set.seed(42)
rnorm(3)
# [1]  1.3709584 -0.5646982  0.3631284

set.seed(42)
rnorm(3)
# [1]  1.3709584 -0.5646982  0.3631284

1注意:在R>3.6.0和以前的版本之间,set.seed()的输出不同。指定您在随机过程中使用的R版本,如果您在回答旧问题时得到的结果略有不同,请不要感到惊讶。为了在这种情况下获得相同的结果,可以在set.seed()之前使用RNGversion()-函数(例如:RNGversion“3.5.2”)。