《Machine Learning forHackers》一书的合著者John Myles White近日接受了一个访谈。在访谈中他提到了自己在R中常用的几个扩展包,其中包括用ggplot2包来绘图,用glmnet包做回归,用tm包进行文本挖掘,用plyr、reshape、lubridate和stringr包进行数据预处理。这些包本博客大部分都有所介绍,今天就来看看这个遗漏的stringr包。
#总结:
ggplot2 绘图
Glmnet做回归
Tm做文本挖掘
Plyr, reshape, lubridate,stringr做预处理,这四个包要好好学习
(1)从名字就看得出,stringr包是用来处理字符串的。R语言本身的字符处理能力已经不错了,但使用起来并不是很方便。stringr包将原本的字符处理函数进行了打包,统一了函数名和参数。在增强功能基础上,还能处理向量化数据并兼容非字符数据。stringr包号称能让处理字符的时间减少95%。下面将其中的一些主要函数罗列一下。
library(stringr)
# 合并字符串
fruit <- c("apple","banana","pear","pinapple")
res <- str_c(1:4,fruit,sep=' ',collapse=' ')
str_c('I wantto buy ',res,collapse=' ')
# 计算字符串长度
str_length(c("i","like","programmingR",123,res))
#按位置取子字符串
str_sub(fruit,1,3)
# 子字符串重新赋值
capital <-toupper(str_sub(fruit,1,1))
str_sub(fruit,rep(1,4),rep(1,4)) <- capital
# 重复字符串
str_dup(fruit,c(1,2,3,4))
# 加空白
str_pad(fruit,10,"both")
# 去除空白
str_trim(fruit)
# 根据正则表达式检验是否匹配
str_detect(fruit,"a$")
str_detect(fruit,"[aeiou]")
# 找出匹配的字符串位置
str_locate(fruit, "a")
# 提取匹配的部分
str_extract(fruit,"[a-z]+")
str_match(fruit,"[a-z]+")
# 替换匹配的部分
str_replace(fruit,"[aeiou]","-")
# 分割
str_split(res," ")
#下面的文章里,我对base包里的一般的处理数据的函数和stringr包里的函数做了一些对比,大家可以体会下stringr包里的函数的优点。
尽管R语言的主要处理对象是数字,而字符串有时候也会在数据分析中占到相当大的份量。特别是在文本数据挖掘日趋重要的背景下,在数据预处理阶段你需要熟练的操作字符串对象。当然如果你擅长其它的处理软件,比如Python,可以让它来负责前期的脏活。
获取字符串长度:nchar()能够获取字符串的长度,它也支持字符串向量操作。注意它和length()的结果是有区别的。
字符串粘合:paste()负责将若干个字符串相连结,返回成单独的字符串。其优点在于,就算有的处理对象不是字符型也能自动转为字符型。
Paste()和str_c()差不多,有个区别,paste()的sep默认是空格,str_c()的默认是无空格。
字符串分割:strsplit()负责将字符串按照某种分割形式将其进行划分,它正是paste()的逆操作。
和str_split()差不多。
字符串截取:substr()能对给定的字符串对象取出子集,其参数是子集所处的起始和终止位置。
和str_sub()差不多,这个时候你可以看多,stringr包其实很好的统一了方法的名称和参数。
字符串替代:gsub()负责搜索字符串的特定表达式,并用新的内容加以替代。sub()函数是类似的,但只替代第一个发现结果。
Gsub()和str_replace()很像,但是
Gsub(pattern, replacement, x)
Str_replace(x, pattern, replacement)
Gsub对所有的符合pattern的全部替换,str_replace默认只替换第一个遇到的pattern
这个意义上,str_replace()和sub()是一样的效果,这个意义上,gsub()和str_replace_all()是一样的效果
字符串匹配:grep()负责搜索给定字符串对象中特定表达式 ,并返回其位置索引。grepl()函数与之类似,但其后面的"l"则意味着返回的将是逻辑值。
Grep()和str_locate()类似,只是grep(pattern,x),str_locate(x,pattern),并且grep()返回 vector,str_locate()返回matrix
Grepl()是:有pattern则true,无则false
一个例子:
我们来看一个处理邮件的例子,目的是从该文本中抽取发件人的地址。该文本在此可以下载到。邮件的全文如下所示:
----------------------------
Return-Path: [email protected]
Delivery-Date: Sat Sep 7 05:46:01 2002
From: [email protected] (Skip Montanaro)
Date: Fri, 6 Sep 2002 23:46:01 -0500
Subject: [Spambayes] speed
Message-ID: <[email protected]>
If the frequency of my laptop's disk chirps are any indication, I'd say
hammie is about 3-5x faster than SpamAssassin.
Skip
----------------------------
# 用readLines函数从本地文件中读取邮件全文。
data <-readLines('data')
#readLines是可以读取newdata.txt这样的文件的,读进来之后是一个A#character vector of length the number of lines read.
# 判断对象的类,确定是一个文本型向量,每行文本是向量的一个元素。
class(data)
# 从这个文本向量中找到包括有"From:"字符串的那一行
email <- data[grepl('From:',data)]
#将其按照空格进行分割,分成一个包括四个元素的字符串向量。
from <- strsplit(email,' ')
# 上面的结果是一个list格式,转成向量格式。
#unlist的作用: list à vector
from <- unlist(from)
# 最后搜索包含'@'的元素,即为发件人邮件地址。
from <- from[grepl('@',from)]
在字符串的复杂操作中通常会包括正则表达式(RegularExpressions),关于这方面内容可以参考?regex
apply族函数是R语言中很有特色的一类函数,包括了apply、sapply、lapply、tapply、aggregate等等。在这篇博文里 对它们进行了简略的说明。这一类函数本质上是将数据进行分割、计算和整合。它们在数据分析的各个阶段都有很好的用处。例如在数据准备阶段,我们可以按某个标准将数据分组,然后获得各组的统计描述。或是在建模阶段,为不同组的数据建立模型并比较建模结果。apply族函数与Google提出的 mapreduce策略有着一致的思路。因为mapreduce的思路也是将数据进行分割、计算和整合。只不过它是将分割后的数据分发给多个处理核心进行运算。如果你熟悉了apply族函数,那么将数据转为并行运算是轻而易举的事情。plyr包则可看作是apply族函数的扩展,使之更容易运用,功能更为强大。
plyr包的主函数是**ply形式的,其中首字母可以是(d、l、a),第二个字母可以是 (d、l、a、_),不同的字母表示不同的数据格式,d表示数据框格式,l表示列表,a表示数组,_则表示没有输出。第一个字母表示输入的待处理的数据格式,第二个字母表示输出的数据格式。例如ddply函数,即表示输入一个数据框,输出也是一个数据框。
下面首先来用一个简单的例子说明一下用法。还是用iris数据集,其中包括了一个分类变量和四个数值变量。我们希望数据按不同类别,分别计算数值变量的均值。下面我们分别用三种方法来得到同样的结果。
library(plyr)
library(reshape2)
# 用aggregate函数进行数据汇总
aggregate(iris[1:4],list(iris$Species),mean)
# 用reshape2包进行数据汇总
data.melt <- melt(iris,id=c('Species'))
dcast(data.melt,Species~variable,mean)
# 用ddply函数进行数据汇总
ddply(iris,.(Species),function(df)mean(df[1:4]))
初看起来plyr包所具备的功能并不很出彩,下面我们看一个略为复杂例子。还是用iris数据,我们希望对每一种花做一个简单回归。
# 首先定义回归函数
model <- function(x){
lm(Petal.Length~Petal.Width,data=x)
}
# 如果用普通的函数则需要如下的分割、计算、整合三个步骤共四条命令
pieces <- split(iris,list(iris$Species))
models <- lapply(pieces,model)
result <- lapply(models,coef)
do.call('rbind',result)
# 用plyr包只用下面两个函数,每个函数都内置了分割、计算、整合的功能。
result1 <- dlply(iris,.(Species),model)
result2 <- ldply(result1,function(x)coef(x))
plyr包中还有两个比较特别的函数,分别是r*ply和m*ply,它们分别对应的是replicate和mapply函数。
replicate(20,mean(runif(100)))
rdply(20,mean(runif(100)))
mapply(rnorm,mean=1:5,sd=1:5, n=2)
mdply(data.frame(mean = 1:5,sd =1:5),rnorm, n =2)
最后我们来看一个mdply函数的应用,我们希望用神经网络包来为不同的花进行分类,使用BP神经网络需要的一个参数就是隐藏层神经元的个数。我们来尝试 用1到10这十个参数运行模型十次,并观察十个建模结果的预测准确率。但我们并不需要手动运行十次。而是使用mdply函数来完成这个任务。
library(nnet)
# 确定建模函数
nnet.m <- function(...){
nnet(Species~.,data=iris,trace=F,...)
}
# 确定输入参数
opts <- data.frame(size=1:10,maxiter=50)
# 建立预测准确率的函数
accuracy <-function(mod,true){
pred <- factor(predict(mod,type='class'),levels=levels(true))
tb <- table(pred,true)
sum(diag(tb))/sum(tb)
}
# 用mlply函数建立包括10个元素的列表,每个元素包括了一个建模结果
models <- mlply(opts,nnet.m)
# 再用ldply函数读取列表,计算后得到最终结果
ldply(models,'accuracy',true=iris$Species)
参考资料:
http://plyr.had.co.nz/09-user/
http://www.jstatsoft.org/v40/i01/paper
和matlab一样,R语言以向量为基本运算对象。也就是说,当输入的对象为向量时,对其中的每个元素分别进行处理,然后以向量的形式输出。R语言中基本 上所有的数据运算均能允许向量操作。不仅如此,R还包含了许多高效的向量运算函数,这也是它不同于其它软件的一个显著特征。向量化运算的好处在于避免使用循环,使代码更为简洁、高效和易于理解。本文来对apply族函数作一个简单的归纳,以便于大家理解其中的区别所在。
所谓apply族函数包括了apply,sapply,lappy,tapply等函数,这些函数在不同的情况下能高效的完成复杂的数据处理任务,但角色定位又有所不同。
apply()函数的处理对象是矩阵或数组,它逐行或逐列的处理数据,其输出的结果将是一个向量或是矩阵。下面的例子即对一个随机矩阵求每一行的均值。要注意的是apply与其它函数不同,它并不能明显改善计算效率,因为它本身内置为循环运算。
m.data <- matrix(rnorm(100),ncol=10)
apply(m.data,1,mean)
lappy()的处理对象是向量、列表或其它对象,它将向量中的每个元素作为参数,输入到处理函数中,最后生成结果的格式为列表。在R中数据框是一种特殊的列表,所以数据框的列也将作为函数的处理对象。下面的例子即对一个数据框按列来计算中位数与标准差。
f.data <- data.frame(x=rnorm(10),y=runif(10))
lapply(f.data,FUN=function(x)list(median=median(x),sd=sd(x)))
sapply()可能是使用最为频繁的向量化函数了,它和lappy()是非常相似的,但其输出格式则是较为友好的矩阵格式。
#sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
#lapply(X, FUN, ...)
sapply(f.data,FUN=function(x)list(median=median(x),sd=sd(x)))
class(test)
tapply()的功能则又有不同,它是专门用来处理分组数据的,其参数要比sapply多一个。我们以iris数据集为例,可观察到Species列中存放了三种花的名称,我们的目的是要计算三种花瓣萼片宽度的均值。其输出结果是数组格式。
#tapply(X, INDEX, FUN = NULL, ..., simplify = TRUE)
|
list of factors, each of same length as |
head(iris)
attach(iris)
tapply(Sepal.Width,INDEX=Species,FUN=mean)
与tapply功能非常相似的还有aggregate(),其输出是更为友好的数据框格式。而by()和上面两个函数是同门师兄弟。
By()的输出是类型”by”
Aggregate()返回data frame
aggregate(Sepal.Length, list(Species),mean) #list一定要加
Group.1 x
1 setosa 5.006
2 versicolor 5.936
3 virginica 6.588
> test <- aggregate(Sepal.Length,list(Species), mean)
> class(test)
[1]"data.frame"
by(Sepal.Length,Species, mean)
Species: setosa
[1] 5.006
---------------------------------------------
Species: versicolor
[1] 5.936
---------------------------------------------
Species: virginica
[1] 6.588
> test <- by(Sepal.Length,Species,mean)
> class(test)
[1] "by"
> mode(test)
[1] "numeric"
另外还有一个非常有用的函数replicate(),它可以将某个函数重复运行N次,常常用来生成较复杂的随机数。下面的例子即先建立一个函数,模拟扔两个骰子的点数之和,然后重复运行10000次。
game <- function(){
n <- sample(1:6,2,replace=T)
return(sum(n))
}
replicate(n=10000,game())
最后一个有趣的函数Vectorize(),它能将一个不能进行向量化运算的函数进行转化,使之具备向量化运算功能。
Verctorize()这个V一定要大写
人生有一道难题,那就是如何使一寸光阴等于一寸生命。在数据分析中也有一道难题,那就是如何自如的操作时间数据。R语言的基础包中提供了两种类型的时间数 据,一类是Date日期数据,它不包括时间和时区信息,另一类是POSIXct/POSIXlt类型数据,其中包括了日期、时间和时区信息。一般来讲,R 语言中建立时序数据是通过字符型转化而来,但由于时序数据形式多样,而且R中存贮格式也是五花八门,例如Date/ts/xts/zoo/tis/fts等等。用户很容易被一系列的数据格式所迷惑,所以时序数据的转化和操作并不是非常方便。所幸的是,我们有了lubridate包。lubridate包主要有两类函数,一类是处理时点数据(time instants),另一类是处理时段数据(timespans)。
时点类函数,它包括了解析、抽取、修改。
# 从字符型数据解析时间,会自动识别各种分隔符
> x <- ymd('2010-04-08')
# 观察x日期是一年中的第几天
> yday(x)
# 修改x日期中的月份为5月
> month(x)<-5
时段类函数,它可以处理三类对象,分别是:
有了时点和时段数据,就可以进行各种计算了。
# 从两个时点生成一个interval时段数据
> y <- new_interval(x,now())
# 从interval格式转为duration格式
> as.duration(y)
# 时点+时段生成一个新的时点
> now() + as.duration(y)
# 10天后的时间数据
> now() + ddays(10)
# 下面的两条语句很容易看出duration和period的区别,dyears(1)表示duration对象的一年,它永远是365天。而years(1)表示period对象的一年,它识别出2012是闰年,它有366天,所以得到正确的时点。
> ymd('20120101') + dyears(1)
[1]"2012-12-31UTC"
> ymd('20120101') + years(1)
[1]"2013-01-01UTC"
为了处理时区信息,lubridate包提供了三个函数:
# 输入欧洲杯决赛在乌克兰的开场时间,再转为北京时间
eurotime <- ymd_hms('2012-07-01 21:45:00',tz='EET')
with_tz(eurotime,tzone='asia/shanghai')
最后来玩玩股票指数作为结束吧,在金融市场中谣传着一种日历效应。即在 一周的第一天或者一年的第一个月份,股票会出现不错的上涨。让我们用热图来观察一下。首先是获取上证指数数据,然后根据不同的月份和星期数,将收益率汇集到不同的组中。将该组收益率的中位数映射到图形颜色上去。可以从下图看到,似乎并不存在明显周一效应或是一月效应。
library(quantmod)
library(ggplot2)
library(lubridate)
# 读取上证指数历史数据
getSymbols('^SSEC',src='yahoo',from ='1997-01-01')
time <- ymd(as.character(index(SSEC)))
open <-as.numeric(Op(SSEC))
high <- as.numeric(Hi(SSEC))
low <- as.numeric(Lo(SSEC))
close <-as.numeric(Cl(SSEC))
volume <-as.numeric(Vo(SSEC))
# 根据收盘和开盘计算当日收益率
profit <- (close-open)/open
# 提取时间数据中的周数和月份
wday <- wday(time)-1
mday <-month(time)
data <-data.frame(time,wday,mday,profit)
p <- ggplot(data,aes(factor(mday),factor(wday),z=profit))
# 收益率的热图,图中颜色越浅,表示汇集到这个组中的收益率中位数越高
p +stat_summary2d(fun=function(x)median(x))+
opts(legend.position= "top")+labs(x='月份',y='星期')
参考资料:
http://statistics.berkeley.edu/classes/s133/dates.html
http://www.jstatsoft.org/v40/i03/paper
http://rlogs.sinaapp.com/post/r-package-lubridate.html
在数据分析过程中,利用各种图表进行数据探索是必要的前期工作。描述性统计中就包括了直方图、散点图等工具来探索连续数据,对于分类数据,则可以采用条形图、交叉分组表等工具。Excel中所谓的“数据透视表”,其实就是一个交互式的交叉分组表。在R语言中可以很容易的用table()等函数得到相应的结果。对于一些更为复杂的任务,就需要其它的函数或包来完成。本例先以iris数据集为研究对象示范一些基本函数的用法,再介绍reshape包的强大功能。
iris数据集中有五个变量,其中Species表示鸢尾属花的子类,其它四个变量分别是花瓣和萼片的长度和宽度。你可以用head(iris)来观察原始数据的一些样本。我们的第一个任务是想计算不同种类花在四个指标上的平均值。用到的函数有tapply,by及aggregate。这篇文章对它们有所涉及。
将数据解包后,先用tapply函数尝试,但会发现该函数一次只允许输入一个变量。如果要完全四个变量的计算可能得用到循环。放弃这个函数来试试用by函 数,该函数可以一次输入多个变量,但输出结果为一个list格式,还需要用do.call函数进行整合,有点麻烦。最方便友好的还是aggregate函数,直接输出为数据框格式。另外它还允许用公式来设置分组因子。
attach(iris)
names(iris)
tapply(X=Sepal.Length,INDEX=Species,FUN=mean)
temp <-by(data=iris[,1:4],INDICES=Species,FUN=mean)
do.call(rbind,temp)
aggregate(x=iris[,1:4],by=list(Species),FUN=mean)
aggregate(.~ Species,data =iris,mean)
aggreagate函数表现已然不错,但还不够强大。比如说它没法直接得出表格的边际值,所以下面就请出本场的主角,即reshape包中的两员大将:melt与cast。这两个通常是配合使用,melt专门负责“融合”原始数据,形成长型(long)数据结构。cast则专职将融合后的数据“重铸”为新的形式(让人想起了“铁索连环”)。基本上只要有这两个函数,就能统一解决所有的汇总问题。
还是以上面的问题为例子,先加载reshape包,然后用melt函数进行融合数据,其中参数id指定了用Species为编号变量,measure参数用来指定分析变量(即被融合的变量),本例中只指定了参数id,所以原始数据中未包括在id中的其它变量均指定为分析变量。你可以观察到新的数据 iris.melt其实就是堆叠(stack)后的数据。然后我们再用cast来重铸,cast函数中可以使用公式,波浪号左侧变量将纵列显示,右侧变量 将以横行显示。margins参数设定了以列作为边际汇总方向。如果希望在计算中只包括两种花,可以使用subset参数。
library(reshape)
iris.melt <- melt(iris,id='Species')
cast(Species~variable,data=iris.melt,mean,margins="grand_row")
cast(Species~variable,data=iris.melt,mean,
subset=Species %in% c('setosa','versicolor'),
margins='grand_row')
reshape包的作者也是ggplot2包的开发者,这个牛人是个完美主义者,在reshape包推出五年后,他重构代码推出了新的reshape2包。这个新包的特性在于:
下面我们以diamonds数据为例,来完成一个略为复杂的任务。我们希望计算不同切工和不同纯净度条件下,钻石的单位平均价格,并加以比较。首先加载 reshape2包和ggplot2包,然后取子集。将原始数据融合,以切工、颜色和净度为编号变量。再利用dcast函数重铸数据,得到汇总结果。计算 出单位价格,最后用条形图表现结果。
library(reshape2)
library(ggplot2)
data <-diamonds[1:7]
data.melt <- melt(data,id=c('cut','color','clarity'))
diam.sum <- dcast(data.melt,cut+clarity~variable,
subset=.(variable %in%c('price','carat')),mean)
diam.sum$average <- diam.sum$price/diam.sum$carat
p <- ggplot(diam.sum,aes(cut,average,fill=clarity))
p + geom_bar(position='dodge')
除了reshape包以外,R语言中还有stack、unstack、reshape等函数能完成类似的工作,但论功能的强大,还是首推reshape包中的哼哈二将。
前几天放出来的那个R的展示中, 有说到其实学R的过程更多的就是熟悉各种函数的过程(学习统计模型不在此列...我个人还是倾向于不要借助软件来学习理论知识,虽然可以直接看 codes...笔和纸上的推导还是不可或缺的基本功),然后各种基础函数熟悉了之后很多被打包好的函数就是缩短代码长度的利器了。
excel里面有神奇的“数据透视表(pivot table)”,其实很多时候真的已经很神奇了....不过我还是喜欢R,喜欢R直接输出csv或者xlsx的简洁。揉数据呢(学名貌似叫数据整理),我 也还是喜欢写出来代码的形式,而不是直接向excel那样面对结果。只是感觉更加不容易出错吧。
揉数据,顾名思义,就是在原有的数据格式基础上,变化出来其他的形式。比如,长长的时间序列变成宽一点的~当然这个可以简单的借助reshape()
函数了。可惜我还是不死心,想找一个更好用的,于是就自然而然的看到了reshape2这个包。
这个包里面函数精华在melt()
和*cast()
。说实话melt()
耗了我一段时间来理解,尤其是为什么需要先melt再cast...后来发现这个步骤简直是无敌啊,什么样的形状都变得更加容易揉了,大赞。
warm-up完毕,还是回到正题吧,怎么用reshape2揉数据呢?虽然reshape2支持array, list和data.frame,但是我一般还是习惯于用data.frame,所以还是说说这东西怎么揉吧。揉数据的第一步就是调用melt()
函数,不用担心你的input是什么格式,这个函数array, list和data.frame通吃。然后,要告诉他哪些变量是(唯一)识别一个个体的,这句话是什么意思呢?我们先看melt()
的参数:
melt(data, id.vars, measure.vars,
variable.name = "variable", ..., na.rm = FALSE,
value.name = "value")
其中id.vars可以指定一系列变量,然后measure.vars就可以留空了,这样生成的新数据会保留id.vars的所有列,然后增加两个 新列:variable和value,一个存储变量的名称一个存储变量值。这样就相当于面板数据的长格式了。直接拷一个作者给出的例子:
原数据:
head(airquality)
ozone solar.r wind temp month day
1 41 190 7.4 67 5 1
2 36 118 8.0 72 5 2
3 12 149 12.6 74 5 3
4 18 313 11.5 62 5 4
5 NA NA 14.3 56 5 5
6 28 NA 14.9 66 5 6
dim(airquality)
[1] 153 6
然后我们将month和day作为识别个体记录的变量,调用melt(airquality, id=c("month", "day"))
:
head(melt(airquality, id=c("month", "day")))
month day variable value
1 5 1 ozone 41
2 5 2 ozone 36
3 5 3 ozone 12
4 5 4 ozone 18
5 5 5 ozone NA
6 5 6 ozone 28
dim(melt(airquality, id=c("month", "day")))
[1] 612 4
嗯,这样数据就变长了~然后,就可以随意的cast了...dcast()
会给出宽格式的数据,比如我们想把day作为唯一的识别,那么:
names(airquality) <- tolower(names(airquality))
aqm <- melt(airquality, id=c("month", "day"), na.rm=TRUE)
head(dcast(aqm, day ~ variable+month))
day ozone_5 ozone_6 ozone_7 ozone_8 ozone_9 solar.r_5 solar.r_6 solar.r_7 solar.r_8 solar.r_9 wind_5 wind_6 wind_7 wind_8 wind_9 temp_5 temp_6
1 1 41 NA 135 39 96 190 286 269 83 167 7.4 8.6 4.1 6.9 6.9 67 78
2 2 36 NA 49 9 78 118 287 248 24 197 8.0 9.7 9.2 13.8 5.1 72 74
3 3 12 NA 32 16 73 149 242 236 77 183 12.6 16.1 9.2 7.4 2.8 74 67
4 4 18 NA NA 78 91 313 186 101 NA 189 11.5 9.2 10.9 6.9 4.6 62 84
5 5 NA NA 64 35 47 NA 220 175 NA 95 14.3 8.6 4.6 7.4 7.4 56 85
6 6 28 NA 40 66 32 NA 264 314 NA 92 14.9 14.3 10.9 4.6 15.5 66 79
temp_7 temp_8 temp_9
1 84 81 91
2 85 81 92
3 81 82 93
4 84 86 93
5 83 85 87
6 83 87 84
或者对于每个月,求平均数:
head(dcast(aqm, month ~ variable, mean, margins = c("month", "variable")))
month ozone solar.r wind temp (all)
1 5 23.61538 181.2963 11.622581 65.54839 68.70696
2 6 29.44444 190.1667 10.266667 79.10000 87.38384
3 7 59.11538 216.4839 8.941935 83.90323 93.49748
4 8 59.96154 171.8571 8.793548 83.96774 79.71207
5 9 31.44828 167.4333 10.180000 76.90000 71.82689
6 (all) 42.12931 185.9315 9.957516 77.88235 80.05722
当然还有更强大的acast()
,配合.函数:
library(plyr) # needed to access . function
acast(aqm, variable ~ month, mean, subset = .(variable == "ozone"))
5 6 7 8 9
ozone 23.61538 29.44444 59.11538 59.96154 31.44828
嗯,基本上数据就可以这么揉来揉去了...哈哈。怎么感觉有点像数据透视表捏?只是更加灵活,还可以自定义函数。
此外还有recast()
可以一步到位,只是返回的是list;colsplit()
可以分割变量名...函数不多,却精华的很啊。
--------------------
题外废话:我的小册子哎~只能这样零零碎碎的写一些了,事后再统一整理进去好了。不要鄙视...
所有R用户接受的第一个“莫名其妙”的原则就是:
不要在R中写显式循环...
不要写显式循环...
不要写循环...
不循环...
不...
我第一次接受到这个“黄金律”,就跟当年从basic语言转到C语言的时候,老师说:
不要写go to...
不goto...
不...
一样的,好震撼。往往对于R用户来说,R基本上不可能是他们学习的第一门计算机语言,什么C啊Java啊甚至matlab或者VBA都可能排在R前 面。所以,循环,无论是for还是while,好像都是再家常便饭不过的事情了。换句话说,不准写循环,我要你计算机还辛辛苦苦的码代码干啥?你丫不就是一免费精确的重复劳动力么!
带着一种到处循环的思维,接触R的初期我是各种不适应不适应啊。循环不让写???后来习惯了去搜R的各种稀奇古怪的函数,发现基本上我想用的功能都被其他大牛们实现了,只需要知道怎么调用那些函数和参数就可以了。这个,挺好的嘛,适合我这种懒人。可是,总有一些时刻需要写循环的嘛...呜啊。
后来,lijian哥给我不断的潜移默化各种展示sapply等apply类函数的强大,越来越体会到一种思维习惯的变化——不再是循环,而是向量操作。这就好比以前只知道求和公式的孩子一朝学习了矩阵乘法,各种惊讶膜拜。其实,从这个角度来讲,R里面很多东西都是更希望借助向量来做而不是自己一个一个的写循环。嗯啊,果然思维方式是有很大提升的。
在痛苦的跟apply类函数纠结了一阵子之后,惊讶的在stackoverflow.com上看到许多人用一个莫名其妙的ddply函数或者ldply函数来实现类似sapply的功能,一时之间难免好奇。于是按图索骥,找到了神奇的plyr包。于是,开启了一扇门(顿时想到叶诗文拿到第二枚金牌的时候,两位央视解说员激情四射的即时附和)。相映成趣啊。plyr的解释只有一句:The split-apply-combine strategyfor R。嗯,超级符合其作者一贯的风格...
简单来说,这个包就是用来简化apply类函数的使用的。作者给出了一个稳健回归的例子(原文载于JSS):
已有函数:
deseasf <- function(value) rlm(value ~ month - 1)
循环版:
models <-as.list(rep(NA, 24 * 24))
dim(models) <-c(24, 24)
deseas <-array(NA, c(24, 24, 72))
dimnames(deseas)<- dimnames(ozone)
for (i inseq_len(24)) {
for(j inseq_len(24)) {
mod <-deseasf(ozone[i, j, ])
models[[i, j]]<- mod
deseas[i, j, ]<- resid(mod)
}
}
非循环版:
models <-apply(ozone, 1:2, deseasf)
resids_list <-lapply(models, resid)
resids <-unlist(resids_list)
dim(resids) <-c(72, 24, 24)
deseas <-aperm(resids, c(2, 3, 1))
dimnames(deseas)<- dimnames(ozone)
plyr版
models <-dlply(ozonedf, .(lat, long), deseasf_df)
deseas <-ldply(models, resid)
嗯,代码长度上可以看出来显著差别了吧,嘻嘻。基本上,plyr就是一步步的从split()到lapply()最后rbind()结果嗯。我个人 是怎么用的呢?小小剧透一下,最近在处理一堆XML数据,虽然自认对HTML很熟,但是对XML还是各种两眼一抹黑。为了把XML转为方便的 data.frame格式,网上一通乱搜最终找到了简洁的解决方案:
## xml_names中含有一系列的XML文件地址,为字符串向量。
xml_df <- ldply(xml_names,
function(x) {
as.data.frame(t(xmlToList(x)$weibo_fans))
}
)
调用XML包的xmlToList()函数之后,就可以用ldply方便的开始揉数据了。嘻嘻,然后加一个 print()函数,就可以舒舒服服的见证屏幕上几千个XML文件被慢慢刷成自己想要的格式的过程了。爽死了。
从数据输入上来看,支持三大类——array,list和dataframe。我个人最偏爱dataframe,虽然list有时候更方便灵活。另外还有几个方便的函数可以用,比如:
each():each(min, max)等价于function(x) c(min = min(x), max = max(x))。
colwise():colwise(median)将计算列的中位数。
arrange():超级顺手的函数,可以方便的给dataframe排序。
rename():又是一个handy的函数,按变量名而不是变量位置重命名。
count():返回unique值,等价于length(unique(**))。
match_df():方便的配合count()等,选出符合条件的行,有点像merge(...,all=F)的感觉。
join():对于习惯SQL的童鞋,可能比merge()用起来更顺手吧(当然也更快一点),不过灵活性还是比不上merge()嗯。
好吧,看出这位作者Hadley的风格了吧,基本上能save your life的函数都给预备好了。现在我的办公桌上常年挂着stringr的简短说明,然后习惯ggplot2画图,reshape2揉数据...这算不算Hadley依赖症捏?
------Happy Hour (欢乐时光模式开启)------
正儿八经的R包介绍说完了之后,开始一点欢乐的时间。常来的读者们大都知道,我最近成功的由“对外”的consultant角色转变为“对内”的 analyst,嗯,工作的重点大不一样了(交流+理解 -> 理解+实现)。当然这更多是一种个人选择,希望接触到更好玩的数据,进一步实现个人价值××××(废话官话连篇就不赘述了)。可是,总得让人有个吐槽的时 刻嘛。附上“现实体”漫画,嘻嘻。暴走的数据分析师 - 点击查看全图
grid.arrange(g1, g2, nrow = 1)
二、R中的包
dplyr
plyr
ggplot1\2
reshape2
knitr
slidify
data.table
制网页的包:Shiny、rApache, Rhttpd, Rack, Rook