在与同事讨论绩效、教学、发送错误报告或搜索邮件列表和Stack Overflow上的指导时,经常会询问一个可重复的示例,并且总是很有用。
你有什么建议来创建一个优秀的例子?如何以文本格式粘贴r中的数据结构?您还应包括哪些其他信息?
除了使用dput()、dump()或structure()之外,还有其他技巧吗?什么时候应该包含library()或require()语句?除了c、df、data等之外,应该避免哪些保留字。?
如何做出一个伟大的、可重复的例子?
在与同事讨论绩效、教学、发送错误报告或搜索邮件列表和Stack Overflow上的指导时,经常会询问一个可重复的示例,并且总是很有用。
你有什么建议来创建一个优秀的例子?如何以文本格式粘贴r中的数据结构?您还应包括哪些其他信息?
除了使用dput()、dump()或structure()之外,还有其他技巧吗?什么时候应该包含library()或require()语句?除了c、df、data等之外,应该避免哪些保留字。?
如何做出一个伟大的、可重复的例子?
当前回答
这是一个很好的指南。
最重要的一点是:制作一小段代码,我们可以运行它来了解问题所在。一个有用的函数是dput(),但是如果您有非常大的数据,那么您可能需要制作一个小样本数据集,或者只使用前10行左右。
编辑:
此外,确保您确定了问题所在。示例不应该是一个完整的R脚本,其中包含“在第200行出现错误”。如果您使用R(我爱浏览器())和Google中的调试工具,那么您应该能够真正确定问题所在,并重现一个同样错误的小例子。
其他回答
基本上,一个最小的可重复示例(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”)。
可复制代码是获得帮助的关键。然而,许多用户可能对粘贴哪怕是一大块数据都持怀疑态度。例如,他们可能在处理敏感数据,或者在研究论文中使用收集的原始数据。
出于任何原因,我认为在公开粘贴数据之前,有一个方便的函数来“变形”我的数据会很好。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
如果您有一个大数据集,无法使用dput()轻松放入脚本,请将数据发布到pastebin并使用read.table加载它们:
d <- read.table("http://pastebin.com/raw.php?i=m1ZJuKLH")
灵感来自Henrik。
就我个人而言,我更喜欢“一”行。大致如下:
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))
如果通过光栅包处理空间数据,则可以生成一些随机数据。在包装小插曲中可以找到很多例子,但这里有一个小亮点。
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")
受到这篇文章的启发,我现在使用了一个方便的功能,当我需要发布到堆栈溢出时,repeat(<mydata>)。
快速说明
如果myData是要复制的对象的名称,请在R中运行以下命令:
install.packages("devtools")
library(devtools)
source_url("https://raw.github.com/rsaporta/pubR/gitbranch/reproduce.R")
reproduce(myData)
细节:
此函数是dput的智能包装器,执行以下操作:
自动对大型数据集进行采样(基于大小和类别。可以调整采样大小)创建dput输出允许您指定要导出的列在前面附加objName<-。。。,这样它可以很容易地复制和粘贴,但是。。。如果在Mac上工作,输出会自动复制到剪贴板,这样您就可以简单地运行它,然后将其粘贴到问题中。
可在以下位置获得来源:
GitHub-pubR/repeat.R
例子:
# 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==
还要注意,整个输出都是一个漂亮的单行,而不是一段高高的分段。这使得在Stack Overflow问题帖子上更容易阅读,也更容易复制和粘贴。
2013年10月更新:
现在,您可以指定将占用多少行文本输出(即,将粘贴到堆栈溢出中的内容)。为此,请使用lines.out=n参数。例子:
复制(DF,列=c(1:3,17,23),行.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==