什么是最有用的R语言技巧?

88
为了分享一些有关 R 的技巧和窍门,你认为最有用的单一功能或技巧是什么?聪明的向量化?数据输入/输出?可视化和图形?统计分析?特殊函数?交互式环境本身?
每个帖子一个功能或技巧,我们将通过投票来决定哪个最有用。
[编辑25-Aug 2008]:经过一周的投票,似乎简单的 str() 赢得了比赛。由于我自己也喜欢推荐它,所以这是一个容易接受的答案。

8
@Dirk说的“community wiki”意思是“社区所有”,并不是“投票问题”的同义词。不要听取“社区所有警察”的话。 - Juliet
4
根据http://meta.stackexchange.com/questions/11740/what-are-community-wiki-posts-on-stack-overflow/11743#11743的内容,这应该是社区 Wiki 帖子。 - dmckee --- ex-moderator kitten
8
再次发生了欺负事件。我会以这个为基础,提出一个问题:http://meta.stackexchange.com/questions/392/should-the-community-wiki-police-be-shut-down - ars
13
@ars:这是一个没有明确答案的问题。因此,把它设为社区维护(CW)。 - dmckee --- ex-moderator kitten
2
@JD Long的评论很有趣。不幸的是,它被隐藏在折叠后面。我的意思是回答困难的R问题并不能真正增加Stack-rep的价值。所以如果那些提出好问题并让R更为广泛的人最终得到一些认可,对我来说也没关系。此外,这对于R用户来说肯定比一个“你最喜欢的C技巧是什么”问题对于C程序员更有用... - Matt Bannert
对我来说它有效... - Dirk Eddelbuettel
34个回答

64

我经常使用的一个非常有用的函数是dput(),它允许您以R代码的形式导出对象。

# Use the iris data set
R> data(iris)
# dput of a numeric vector
R> dput(iris$Petal.Length)
c(1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4, 1.5, 1.4, 1.5, 1.5, 1.6, 
1.4, 1.1, 1.2, 1.5, 1.3, 1.4, 1.7, 1.5, 1.7, 1.5, 1, 1.7, 1.9, 
1.6, 1.6, 1.5, 1.4, 1.6, 1.6, 1.5, 1.5, 1.4, 1.5, 1.2, 1.3, 1.4, 
1.3, 1.5, 1.3, 1.3, 1.3, 1.6, 1.9, 1.4, 1.6, 1.4, 1.5, 1.4, 4.7, 
4.5, 4.9, 4, 4.6, 4.5, 4.7, 3.3, 4.6, 3.9, 3.5, 4.2, 4, 4.7, 
3.6, 4.4, 4.5, 4.1, 4.5, 3.9, 4.8, 4, 4.9, 4.7, 4.3, 4.4, 4.8, 
5, 4.5, 3.5, 3.8, 3.7, 3.9, 5.1, 4.5, 4.5, 4.7, 4.4, 4.1, 4, 
4.4, 4.6, 4, 3.3, 4.2, 4.2, 4.2, 4.3, 3, 4.1, 6, 5.1, 5.9, 5.6, 
5.8, 6.6, 4.5, 6.3, 5.8, 6.1, 5.1, 5.3, 5.5, 5, 5.1, 5.3, 5.5, 
6.7, 6.9, 5, 5.7, 4.9, 6.7, 4.9, 5.7, 6, 4.8, 4.9, 5.6, 5.8, 
6.1, 6.4, 5.6, 5.1, 5.6, 6.1, 5.6, 5.5, 4.8, 5.4, 5.6, 5.1, 5.1, 
5.9, 5.7, 5.2, 5, 5.2, 5.4, 5.1)
# dput of a factor levels
R> dput(levels(iris$Species))
c("setosa", "versicolor", "virginica")

当你寻求帮助时,发布易于重现的数据块或编辑/重新排序因子级别可以非常有用。


64

str()函数可以展示对象的结构。


Python使用dir() - 更有意义。 - Hamish Grubijan
17
哦,"str" 在许多语言中也是代表 "字符串(string)" 的缩写。 - Hamish Grubijan
为什么不使用 class() 呢?它似乎可以显示类似的信息。为什么会有两个如此相似的命令? - hhh
1
class()只是str()显示的信息的一小部分。 - hadley

42

使用head()和tail()函数来获取数据框、向量、矩阵、函数等的前几行和最后几行。尤其是对于大型数据框,这是一种快速检查是否已经正确加载的方法。


38

一个很好的特性:读取数据使用连接,这些连接可以是本地文件、通过http访问的远程文件、来自其他程序的管道或更多。

作为一个简单的例子,考虑从random.org获取N=10个介于min=100和max=200之间的随机整数的访问(该网站提供基于大气噪声而不是伪随机数生成器的真正随机数):

R> site <- "http://random.org/integers/"         # base URL
R> query <- "num=10&min=100&max=200&col=2&base=10&format=plain&rnd=new"
R> txt <- paste(site, query, sep="?")            # concat url and query string
R> nums <- read.table(file=txt)                  # and read the data
R> nums                                          # and show it
   V1  V2
1 165 143
2 107 118
3 103 132
4 191 100
5 138 185
R>

顺便说一下,random包提供了多个便利函数来访问random.org


顺便说一下 - 如果您及时发布自我回答并且不将问题标记为社区wiki,我建议您应该将自我回答标记为社区wiki。否则看起来有点像您试图操纵声望系统。你的情况可能会有所不同。 - dmckee --- ex-moderator kitten
1
这不是操纵系统,只是开始事情的方式。他仍然可以自由地接受任何其他答案。 - ars
2
@ars:他可以自由选择接受这个建议。如果他不采纳我的建议,我也不会试图强迫他去维基上查找。但是,我不会在没有标记为维基的情况下发布准备好的自我回答,也不会投票支持没有标记为维基的回答。你可以根据这个价值观来看待它。 - dmckee --- ex-moderator kitten
好的观点。一些自我回答对于讨论很有帮助。我们在现实生活中也是这样做的。 - JD Long
4
@Dirk:自己回答自己的问题是完全可以接受的,甚至被Jeff和Joel鼓励。没有任何要求,甚至包括非正式的要求,要将您的答案标记为社区 wiki。您显然没有操纵系统。再一次,请忽略社区wiki警察。 - Juliet
8
我必须承认,该网站的部分目的是为了提供常见问题的最佳答案和一个综合性资源。提出问题并提供好的答案可以帮助加强一个主题。这在新的/小的标签下特别有用,比如R。 - kpierce8

35

我发现自己越来越多地使用with()within()。不再在代码中到处散落$,也不需要将对象附加到搜索路径上。更重要的是,我发现with()等函数可以使我的数据分析脚本的意图更加清晰。

> df <- data.frame(A = runif(10), B = rnorm(10))
> A <- 1:10 ## something else hanging around...
> with(df, A + B) ## I know this will use A in df!
 [1]  0.04334784 -0.40444686  1.99368816  0.13871605 -1.17734837
 [6]  0.42473812  2.33014226  1.61690799  1.41901860  0.8699079

with()设定一个环境,用于评估R表达式。 within()也是这样做的,但允许您修改用于创建该环境的数据对象。

> df <- within(df, C <- rpois(10, lambda = 2))
> head(df)
           A          B C
1 0.62635571 -0.5830079 1
2 0.04810539 -0.4525522 1
3 0.39706979  1.5966184 3
4 0.95802501 -0.8193090 2
5 0.76772541 -1.9450738 2
6 0.21335006  0.2113881 4

我第一次使用within()时没有意识到的一点是,在进行表达式求值的过程中,必须执行一个赋值操作 并且 将返回的对象分配给变量(如上所示),才能获得期望的效果。


34

使用RGoogleDocs包进行数据输入技巧

http://www.omegahat.org/RGoogleDocs/

我发现谷歌电子表格是让所有协作者都在同一页上的绝佳方式。此外,谷歌表单允许您从受访者那里捕获数据,并轻松地将其写入谷歌电子表格中。由于数据频繁更改且几乎永远不会是最终版本,因此直接读取谷歌电子表格比下载CSV文件并读取它们要好得多。

# Get data from google spreadsheet
library(RGoogleDocs)
ps <-readline(prompt="get the password in ")
auth = getGoogleAuth("me@gmail.com", ps, service="wise")
sheets.con <- getGoogleDocsConnection(auth)
ts2=getWorksheets("Data Collection Repos",sheets.con)
names(ts2)
init.consent <-sheetAsMatrix(ts2$Sheet1,header=TRUE, as.data.frame=TRUE, trim=TRUE)

我记不清是哪一个或两个以下的命令需要几秒钟的时间。

  1. getGoogleAuth

  2. getGoogleDocsConnection

  3. getWorksheets


27

使用反引号来引用非标准名称。

> df <- data.frame(x=rnorm(5),y=runif(5))
> names(df) <- 1:2
> df
           1         2
1 -1.2035003 0.6989573
2 -1.2146266 0.8272276
3  0.3563335 0.0947696
4 -0.4372646 0.9765767
5 -0.9952423 0.6477714
> df$1
Error: unexpected numeric constant in "df$1"
> df$`1`
[1] -1.2035003 -1.2146266  0.3563335 -0.4372646 -0.9952423
在这种情况下,df[,"1"]也可以起作用。但是反引号在公式内工作!
> lm(`2`~`1`,data=df)

Call:
lm(formula = `2` ~ `1`, data = df)

Coefficients:
(Intercept)          `1`  
     0.4087      -0.3440  

[编辑] Dirk问为什么会给出无效的名称?我不知道!但我在实践中经常遇到这个问题。例如,使用hadley的reshape包:

> library(reshape)
> df$z <- c(1,1,2,2,2)
> recast(df,z~.,id.var="z")
Aggregation requires fun.aggregate: length used as default
  z (all)
1 1     4
2 2     6
> recast(df,z~.,id.var="z")$(all)
Error: unexpected '(' in "recast(df,z~.,id.var="z")$("
> recast(df,z~.,id.var="z")$`(all)`
Aggregation requires fun.aggregate: length used as default
[1] 4 6

好的,但是你为什么需要用无效的名称(如1或2)替换语法上有效的名称(如x或y),从而需要使用反引号呢? - Dirk Eddelbuettel
3
check.names 为 false 时,在 read.table 中使用它也很有用 - 即当您想使用原始列名进行操作时。 - hadley

25

不知道这个是否广为人知,但我肯定已经利用了环境的按引用传递的能力。

zz <- new.env()
zz$foo <- c(1,2,3,4,5)
changer <- function(blah) {
   blah$foo <- 5
}
changer(zz)
zz$foo

对于这个例子而言,使用它可能没有太多意义,但是如果你需要传输大型对象,那么它会很有用。


23

我最新喜欢的东西是 foreach 库。它让你可以做所有好用的 apply 操作,但语法更容易一些:

list_powers <- foreach(i = 1:100) %do% {
  lp <- x[i]^i
  return (lp)
}

最棒的是,如果你正在做一些实际需要大量时间的任务,你可以切换从%do%%dopar%(使用适当的后端库)以立即并行化,甚至在集群中也可以。非常流畅。


19

我经常进行基本数据操作,因此这里介绍了两个内置函数(transformsubset)和一个库(sqldf),它们是我每天使用的工具。

创建示例销售数据

sales <- expand.grid(country = c('USA', 'UK', 'FR'),
                     product = c(1, 2, 3))
sales$revenue <- rnorm(dim(sales)[1], mean=100, sd=10)

> sales
  country product   revenue
1     USA       1 108.45965
2      UK       1  97.07981
3      FR       1  99.66225
4     USA       2 100.34754
5      UK       2  87.12262
6      FR       2 112.86084
7     USA       3  95.87880
8      UK       3  96.43581
9      FR       3  94.59259

使用transform()添加列

## transform currency to euros
usd2eur <- 1.434
transform(sales, euro = revenue * usd2eur)

>
  country product   revenue     euro
1     USA       1 108.45965 155.5311
2      UK       1  97.07981 139.2125
3      FR       1  99.66225 142.9157
...

使用subset()来对数据进行切片

subset(sales, 
       country == 'USA' & product %in% c(1, 2), 
       select = c('product', 'revenue'))

>
  product  revenue
1       1 108.4597
4       2 100.3475

使用sqldf()函数通过SQL对数据进行切片和聚合

sqldf包提供了一个可以将R数据框转换成SQL表格,并通过SQL语句进行数据操作的接口。

##  recast the previous subset() expression in SQL
sqldf('SELECT product, revenue FROM sales \
       WHERE country = "USA" \
       AND product IN (1,2)')

>
  product  revenue
1       1 108.4597
2       2 100.3475

执行聚合或按组分组

sqldf('select country, sum(revenue) revenue \ 
       FROM sales \
       GROUP BY country')

>
  country  revenue
1      FR 307.1157
2      UK 280.6382
3     USA 304.6860

如果想要在数据框上进行更复杂的类似于Map-Reduce的功能,请查看plyr包。如果你发现自己想要拔光头发,我建议查看Data Manipulation with R


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