Data Manipulation with R(Phil Spector) 阅读摘要
[推荐原因]
数据处理犹如大厨之备料阶段,重要性不言而喻,但是往往受到忽视,如果有自己动手分析数据的人,一定知道掌握这项基本功是多么的重要与必要。当你能够娴熟的随心所欲的操纵你的数据时,可能你对各种分析方法的应用应该也得心应手了。
如果瞧不起数据准备阶段的工作,请参考数寄屋桥次郎的小野二郎的寿司材料的准备过程。小野店里的学徒的经历艰难而漫长,所有的学徒要先从学会拧烫的手工毛巾开始。逐步着手处理和准备食材,10年过后,才会让你煎蛋。这种苦修一般的美食精神远非常人能及,“我练习煎蛋一天最多的时候有4个,3到4月后做了200个失败品。”直到最后二郎终于点头默许,学徒激动到泪下,“我终于被称为是一个真正的职人,这是我努力的最终成果。”http://baike.baidu.com/view/8667802.htm
所谓阅读摘要,就是通读全书,将自己认为有价值的信息摘录下来,以备查。(注:该书里面的左赋值都使用了=)
SAS 有两本关于数据准备的教程也值得系统读读,《Data.Preparation.for.Analytics.Using.SAS》《Data Preparation for Data Mining Using SAS》,最近喜新厌旧,《Data.Preparation.for.Analytics.Using.SAS》的阅读笔记估计要拖几个月了。突然有个想法,可以考虑用R将《Data.Preparation.for.Analytics.Using.SAS》里面的例子重写一遍。
目录:
Chapter 1 基本数据类型
Chapter 2 读写数据
Chapter 3 R与数据库
Chapter 4 日期
Chapter 5 因子
Chapter 6 下标
Chapter 7 字符操作
Chapter 8 数据聚集
R是面向对象的编程语言,适当了解类和对象对快速掌握R的基本思想具有一定的帮助。
几个基本概念:
类(Class) 对象(Object) 类型 (Mode) 属性(Attribute) 方法(Method)
mode():Get or set the type or storage mode of an object. mode是指对象存储的类型,共有四种基本类型数值型,字符型,复数型和逻辑型
class():Object Classes.类是对象的抽象,而对象是类的具体实例。
R,里面有几个基本对象要熟练掌握,因为大部分函数都选择这些对象作为参数之一。向量Vector、因子Factor、数组Array、矩阵Matrix、数据框Data Frame、列表List、时间序列Time Series。列表是R中最灵活的一个数据类型,许多函数用它来保存结果。
应掌握几种数据类型数据的创建方法、判断方法(is.XXX)、已经对象之间的转换(as.XXX)
通过 str() class() summary()等函数可以查看对象的相关信息
缺失值处理
判断是否是缺失值 is.na=T or is.na=F
大部分描述统计分析使用 na.rm=T or na.rm=F 确认分析是否包含缺失值
大部分统计模型分析使用 na.action=T or na.action=F
na.omit可以返回数据框包含那些没有被纳入分析的包含缺失值的数据
如果需要将缺失变量当作一个因子,使用exclude=NULL
在读入数据时候,有些数据库不是用na来标注缺失值,可以用na.string='999'来代表缺失符号
scan()
read.table()
header=TRUE 是否包含字段名
col.names= 列名
options(stringsAsFactors=FALSE) 字符变量不转换成因子
encoding= 设置字符编码格式
skip= 设置那些记录不读入
nrows=设置读入最大记录数
colClasses= 设置变量的类型(字符还是数值)对于数据量比较大的时候,这个参数的设置可以提高效率
sep="" 设置分割符
read.csv()
read.csv2()
read.delim()
read.fwf()
widths= 设定宽度向量
file()
pipe()
textConnection()
gzfile()
url()
....
readLines()
scan()
不需要将整个文件一次读入内存
2.8.1 序列
1:n
seq(from,to,by)
seq(from,by,length=)
生成因子类别 gl(x1,x2,x3) x1 因子类别数量;x2每个因子类别重复次数;x3(可选)输出向量的长度
expand.grid(a,b)产生不重复的所有因子组合。
2.8.2 随机数
通过设置set.seed()保证模拟实验可重复性
2.9.1随机抽样
sample(data,size=, replace=, prob=)
2.9.2排列组合
library(combinat):
perm()
fact()
library(sna):
numperm()
unique()提取序列中的不重复的值
duplicated()返回是否重复的逻辑值
rle() 可以解决更复杂的问题(run-length encoding,行程长度编码),可返回 数值values和重复次数lengths
which()可以计算某个数值所在的序列位置编号
cumsum()累计求和
例子
seq1 = c(1,3,5,2,4,2,2,2,7,6)
rle.seq1 = rle(seq1)
index = which(rle.seq1$values == 2 & rle.seq1$lengths >= 3)
cumsum(rle.seq1$lengths)[index]
[1] 8
2.11.1
library(RODBC):
ODBConnectExcel() 创建数据库连接
sqlTables() 获取Excel表名 tbls<=sqlTables(con); tbls$TABLE_NAME[1]
2.11.2
library(gdata):
read.xls()
save.image
save()
load()
wirite()
write.table()
连接数据库有两种方式:(1)RODBC;(2)使用特定数据库的DBI Package
一般默认情况使用第(2)种方法
以SQLite数据库为例,介绍第(2)方法的使用细节,剩下的就是码SQL语句了,没有什么技术难度。
library("RSQLite")
drv <- dbDriver("SQLite")
con <- dbConnect(drv, dbname = "d:/sqlitedb.s3db")
db_u<-dbGetQuery(con, "select * from table1" )
dbDisconnect()
as.Date 自带日期函数,可以处理日期,但不能处理时间
library(chron)可以处理日期、时间,但不能处理时区
POSIXct() 可以处理日期、时间、时区,以距离1970.1.1的天数或者秒数值为保存数据,除非特别考虑要使用list格式,一般都使用POSIXct格式
POSIXlt() 可以处理日期、时间、时区,以List的形式保存年 月 日 时 分 秒
Sys.Date()返回当前系统日期
weekdays()
months()
days()
quarters()
chron函数对日期和时间是分开处理的。因此,要分开读入。分割字符串函数strsplit(dtimes,' ')
>dtimes = c("2002-06-09 12:45:40","2003-01-29 09:30:40",
+ "2002-09-04 16:45:40","2002-11-13 20:00:40",
+ "2002-07-07 17:30:40")
>dtparts = t(as.data.frame(strsplit(dtimes,' ')))
>row.names(dtparts) = NULL
>thetimes = chron(dates=dtparts[,1],times=dtparts[,2],
+ format=c('y-m-d','h:m:s'))
> thetimes
[1] (02-06-09 12:45:40) (03-01-29 09:30:40) (02-09-04 16:45:40)
[4] (02-11-13 20:00:40) (02-07-07 17:30:40)
POSIX指(Portable Operating System Interface)
POSIX存储的格式为Date/Time格式,如果分析用到分钟数据,推荐使用POSIX格式。
> dts = c("2005-10-21 18:47:22","2005-12-24 16:39:58",+ "2005-10-28 07:30:05 PDT")
> as.POSIXlt(dts)
[1] "2005-10-21 18:47:22" "2005-12-24 16:39:58"[3] "2005-10-28 07:30:05"
一般数据读入R,日期时间都会被当作字符格式被读入,因此,需要将其转换成日期/时间格式
as.Date()
as.POSIXlt()
as.POSIXct()
difftime(t1,t2,units='') units=“auto”, “secs”, “mins”, “hours”, “days”, or “weeks”
seq(as.Date(’1976-7-4’),by=’days’,length=10)
seq(as.Date(’2000-6-1’),to=as.Date(’2000-8-1’),by=’2 weeks’)
table(format(rdates$Date,’%A’))
fdate = factor(format(rdates$Date,’%Y’))
因子常指类别变量,因子的一项重要应用就是用于统计模型,因为,在统计模型中处理连续变量和分类变量的方式是不一样的。
factor()创建因子
nlevels()返回因子数量
levels= 显示因子值
ordered=TRUE 对因子值进行排序
labels= 因子值的标签
relevel()调整因子顺序
因子在数据存储方面效率较高,因此read.table在读入数据时候自动将字符格式的变量转换成因子。可以在read.table函数中通过以下设置取消:
as.is=TRUE
stringsAsFactors=FALSE
或者在系统环境设置中设置 stringsAsFactorssystem 为FALSE.
举例:
InspectSprays数据集有两个变量count:num 和 sprays:factor
levels(InsectSprays$spray)
InsectSprays$spray = with(InsectSprays, reorder(spray,count,mean))
levels(InsectSprays$spray)
attr(InsectSprays$spray,’scores’)
如果数值变量被转换成因子,那么它将不能做一些甚至非常简单的计算。
如果要将数值因子转换回数值进行计算,则需要采用以下两种方法
> fert = c(10,20,20,50,10,20,10,50,20)
> fert = factor(fert,levels=c(10,20,50),ordered=TRUE)
> fert
(1)mean(as.numeric(levels(fert)[fert]))
(2)mean(as.numeric(as.character(fert)))
提取部分数据作因子频数统计,所有因子的类别都会出现,这给分析和视觉上都带来了麻烦。可以使用drop=TRUE 来过滤掉其他那些没有频数的因子
> lets = sample(letters,size=100,replace=TRUE)
> lets = factor(lets)
> table(lets[1:5])
> table(lets[1:5,drop=TRUE])
可以使用cut()函数
cut(data,n)将数据data切为n段
breaks= 设置分割方式
wfact = cut(women$weight,3,labels=c(’Low’,’Medium’,’High’))
wfact = cut(women$weight,quantile(women$weight,(0:4)/4))
方法(1)
everyday = seq(from=as.Date(’2005-1-1’),+ to=as.Date(’2005-12-31’),by=’day’)
cmonth = format(everyday,’%b’)
months = factor(cmonth,levels=unique(cmonth),ordered=TRUE)
table(months)
方法(2)
wks = cut(everyday,breaks=’week’)
qtrs = cut(everyday,"3 months",labels=paste(’Q’,1:4,sep=’’))
注意:cut 默认从星期一开始分割,如果要从星期天开始分割,要在cut()函数中设置start.on.monday=FALSE
通过interaction()可以创建多个分类变量的因子列表,drop=TRUE也可以使用
> data(CO2)
> newfact = interaction(CO2$Plant,CO2$Type,drop=TRUE)
> nlevels(newfact)
[1] 24
通过下标来访问向量、矩阵、数组、数据框和列表的元素。下标是R数据操作中非常重要的一个工具,需要熟练掌握。
正值是保留,负值是删除,要不全部正值,要不全部负值。常使用seq()
如果subscripttable对象被定义了,那么字符串或者字符向量就可以作为字符下标。负的字符下标是不允许的。如果要排除某些元素,可以使用grep()函数
> nums = c(12,9,8,14,7,16,3,2,9)> nums
> 10
> nums[nums>10]
which()函数 返回符合逻辑判断的元素的下标
> which(nums>10)
[1] 1 4 6
逻辑赋值,对符合逻辑的下标元素进行赋值
> nums[nums > 10] = 0
> nums
[1] 0 9 8 0 7 0 3 2 9
X是m*n矩阵,X[m,] 取第m行,X[,n] 取第n列,X[m,n]取第m行,第n列的值
row() 计算行数
col()计算列数
diag() 提取对角元素
lower.tri 提取下三角元素
upper.tri提取上三角元素
列表的使用最灵活,可以允许不同长度及元素类型。所以,其下标方式和矩阵和数组会有些不同
以下这个例子中,simple[2]看起来应该是一个向量,但实际上结果还是一个包含向量的list,因此对simple[2]进行向量的计算就会出错。R提供了两种解决办法直接提取向量元素:
(1)使用$符号,如simple$b
(2)使用[[]],如simple[[2]] 或者simple[[’b’]]
> simple = list(a=c(’fred’,’sam’,’harry’),b=c(24,17,19,22))
> mode(simple)
[1] "list"
> simple[2]
$b
[1] 24 17 19 22
> mode(simple[2])
[1] "list"
> mean(simple[2])
[1] NA
Warning message:argument is not numeric or logical:
注意:[[]]的引用方式,不能使用区间,比如simple[[1:2]]就会出错
矩阵和列表的下标方法都适用于数据框
当使用[]时,更接近列表使用方法,下标指向列。
当使用逻辑下标时,需要先移除缺失数据
subset()函数接受数据框、矩阵或者向量,该函数自动将缺失数据排除在外,如果数据框或者矩阵包含列名,则函数内部可以直接使用这些列名。
> dd = data.frame(a=c(5,9,12,15,17,11),b=c(8,NA,12,10,NA,15))
> dd[dd$b > 10,]
> dd[!is.na(dd$b) & dd$b > 10,]
> subset(dd,b>10)
选择/排除部分列
> some = subset(LifeCycleSavings,sr>10,select=c(pop15,pop75))
> life1 = subset(LifeCycleSavings,select=pop15:dpi)
> life1 = subset(LifeCycleSavings,select=1:3)
> life2 = subset(LifeCycleSavings,select=-c(2,3))
R的字符处理能力也非常强大,加上向量处理能力,R的很多函数能够媲美PERL和PHTHON这些脚本语言。
length() 用于计算变量的存储长度,不是每个字符串的长度
nchar()用于计算每个字符串的长度,nchar()支持向量运算
nchar(state.name)
cat()可以合并并显示字符串信息到屏幕或者文件中
> x = 7
> y = 10
> cat(’x should be greater than y, but x=’,x,’and y=’,y,’\n’)
x should be greater than y, but x= 7 and y= 10
paste() 提供更复杂的功能,如果传递给paste的变量不是字符型的将被自动转换成字符型,注意下面例子中sep= 与collapse=的使用区别,sep=单独对于向量运算不起作用。collapse=运行结果为一长串字符串。
> paste(’X’,1:5,sep=’’)
[1] "X1" "X2" "X3" "X4" "X5"
> paste(c(’one’,’two’,’three’,’four’),collapse=’ ’)
[1] "one two three four"
上例使用collapse=的结果变成了一个字符串
如果存在多个paste参数,将自动使用向量化运算,将短的向量循环变长。
> paste(c(’X’,’Y’),1:5,sep=’_’,collapse=’|’)
[1] "X_1|Y_2|X_3|Y_4|X_5"
> paste(c(’X’,’Y’),1:5,’^’,c(’a’,’b’),sep=’_’,collapse=’|’)
[1] "X_1_^_a|Y_2_^_b|X_3_^_a|Y_4_^_b|X_5_^_a"
substring()可以提取或者修改部分字符串的值,这个用法和SAS里面的substr()语法类似,但是功能强大多了,一方面它支持向量运算,另一方面它可以修改值,而SAS的substr()只能提取字符串的值。
substring(state.name,2,6)
> mystring = ’dog cat duck’
> substring(mystring,c(1,5,9),c(3,7,12))
[1] "dog" "cat" "duck"
> state = ’Mississippi’
> ll = nchar(state)
> ltrs = substring(state,1:ll,1:ll)
> ltrs[1] "M" "i" "s" "s" "i" "s" "s" "i" "p" "p" "i"
> which(ltrs == ’s’)
[1] 3 4 6 7
可以使用substring()来进行字符替换
> mystring = ’dog cat duck’
> substring(mystring,5,7) = ’feline’
> mystring
[1] "dog fel duck"
恶补基础知识
正则表达式在 strsplit,grep, sub, gsub, regexpr, gregexpr中都可以使用
strsplit()可以使用字符串或者正则表达式,将字符串进行分隔,输出结果为list类型
> sentence =+ ’R is a free software environment for statistical computing’
> parts = strsplit(sentence,’ ’)
比较下列两段程序,第二段程序使用了正则表达式,“ +”表示一个及以上前字符(空格)
> str = ’one two three four’
> strsplit(str,’ ’)
[[1]]
[1] "one" "" "two" "" "" "three" "four"
> strsplit(str,’ +’)
[[1]]
[1] "one" "two" "three" "four"
如果使用空字符串(注意,不是空格)作为分隔符,可以将字符串的每个字母提取出来。
> words = c(’one two’,’three four’)
> strsplit(words,’’)
[[1]]
[1] "o" "n" "e" " " "t" "w" "o"
grep() global search regular expression(RE) and print out the line
grep(规则表达式,字符串或者字符向量,value=TRUE)返回查询结果的坐标,如果value=TURE,返回值
掌握好正则表达式,对字符的操作就可以如鱼得水。
正则表达式的一个重要应用就是可以从数据框中提取变量名
> grep(’^pop’,names(LifeCycleSavings))
[1] 2 3
> grep(’^pop’,names(LifeCycleSavings),value=TRUE)
[1] "pop15" "pop75"
> head(LifeCycleSavings[,grep(’^pop’,names(LifeCycleSavings))])
如果只想提取出独立的dog单词
> inp = c(’run dog run’,’work doggedly’,’CAT AND DOG’)
> grep(’\\’,inp,ignore.case=TRUE)
[1] 1 3
基于正则表达式的替换,R提供了两个函数sub()和gsub()
其中一个重要的使用就是处理网页和金融数据,这些数据包含美元符号及逗号。
> values = c(’$11,317.35’,’$11,234.51’,’$11,275.89’,’$11,278.93’,’$11,294.94’)
> as.numeric(gsub(’[$,]’,’’,values))
[1] 11317.35 11234.51 11275.89 11278.93 11294.94
R提供了很多数组函数来聚集数据,简单的有列联表函数table(),对于更复杂的应用可以分为以下两大类别:
(1)为了提升数组和列表的效率的函数,如:apply(), sweep(), mapply(), sapply(), lapply()
(2)为了提升数据框的运算效率的函数,如:aggregate(), by()
两类函数会有些功能重叠,选择哪个函数主要看使用者的习惯。
table()函数的数据源可以是向量、列表或者数据框,结果是多维的数组,输出类型为table。默认来说table()不包含缺失变量,如果要包含缺失变量,可以使用exclude=NULL
可以使用as.data.frame()将输出结果转换为data.frame
如果一组数据包含多个维度,可以使用ftable()展现“扁平化”的结果,
当我们聚集数据的时候,一般面临以下三个问题:
(1)分组数据的类型(列表、向量还是数据框);
(2)将要分析的数据的特征
(3)结果的展现
如果对于分组数据数据是列表元素的,可以使用sapply(), lapply() ,详见8.3
如果分组数据是矩阵的行或者列,可以使用apply(),详见8.4
如果分组数据基于多个分组变量,最常使用aggregate(),因为,aggregate()总是返回一个数据框,以便于展示和二次分析,详见8.5
更复杂的问题可以使用reshape()
虽然R会自动处理向量中的每个元素,但对于列表,就未必了。因为,R的输出结果常常是List,因此需要设计一种方法来处理list,就像处理向量一样。
R提供了两个函数来解决这个问题,lapply,sapply。每个函数使用列表或者向量作为第一个参数,需要使用的函数作为第二个参数,laaply和sapply的区别在于,lapply返回的结果是list,而sapply返回的结果是向量或者矩阵
apply()的输入数据为矩阵或者数组,输出数据为向量或者矩阵。
apply() 常常和scale()函数结合使用,对矩阵的每列进行统计分析。
apply(data,1,fun) 对行进行操作
apply(data,2,fun)对列进行操作
apply实现的一些功能可以考虑使用rowSums colSums rowMeans colMeans这些函数,效率会更高些,另外,这些函数接受矩阵或者数据框作为数据源,na.rm=对缺失值进行设置。
有时候,对每行或者列的操作是不用的,针对这样的情况,可以采用sweep()函数,sweep()的前两个参数和apply()一样。(待补充)
aggregate()函数,第一个参数为待分析的数据框或者矩阵,第二个参数是分组变量list,第三个参数是统计函数
aggregate(iris[-5],iris[5],mean)
aggregate(ChickWeight$weight,ChickWeight[c(’Time’,’Diet’)],mean)
如果分析数据源是一个向量,可以使用tapply(),返回的结果是数组,可以使用as.data.frame转换为数据框
maxweight = tapply(PlantGrowth$weight,PlantGrowth$group,max)
超过一个分类变量
ranges1 = tapply(CO2$uptake,CO2[c(’Type’,’Treatment’)],range)
如果调用tapply(),而不设置第三个参数,则返回分组索引下标
by()函数可以用于整个数据框,
reshape2()是最新的包,专门用于数据聚集,功能强大。基本思路是,它采取先把数据melted揉成基本数据格式,再cast出所需的信息。详见相关说明
不推荐自己写循环,除非没有适合的函数。大部分循环可以使用向量运算形式完成。改变以前事必循环的编程习惯。
R函数的一个重要特点就是需要确定待分析的数据源格式是否适合函数要求。本章主要分析data.frame,因为它是大部分函数使用的数据格式
创建新变量
Loblolly$logheight = log(Loblolly$height)
Loblolly[’logheight’] = log(Loblolly[’height’])
with(Loblolly,log(height))
如果新变量要加入数据框可以使用transform(),系统的原始数据框不会被改变,当前工作空间中的数据框会改变
Loblolly = transform(Loblolly,logheight = log(height))
移除变量
可以使用subset(),详见6.8,也可以使用负下标,也可以结合sapply()使用
iris[,-5] = sapply(iris[,-5],function(x)x/2.54)
经常需要基于旧变量创建新变量,例如在列联表分析中,有时需要将不同变量值合并成新的变量值,如在logisitic回归中,需要将连续变量重新定义成分组变量。
bigsepal = iris$Sepal.Length > 6
sepalgroup = 1 + (iris$Sepal.Length >= 5)+ (iris$Sepal.Length >= 7)
有时ifelse()比直接操作逻辑运算更有效
newgroup = ifelse(group %in% c(1,5),1,2)
newgroup = ifelse(group %in% c(1,5),1, ifelse(group %in% c(2,4),2,3))
x = c(-1.2,-3.5,-2.8,-1.1,-0.7)
newx = ifelse(x > 0,log(x),abs(x))
newx
[1] 1.2 3.5 2.8 1.1 0.7
在car包里面有一个recode()函数可以实现赋值功能
newgroup = recode(group,’c(1,5)=1;c(2,4)=2;else=3’)
常常需要分析的数据可以在数据框中找到,但是其存在的形式不是我们所期待的。比如,多重分类是以列形势在数据表中显示,大部分的统计模型和图标函数无法使用此类格式,一般需要的格式是这些分类的值在一个变量中,而分类的属性在另外一个变量中。 stack() reshape()函数可以实现以上的转换。SAS里面也有类似的函数transpose(),EXCEL里面也有行列转置函数
reshape包提供了melt()函数来将数据“揉”成“长”数据类型,再用cast()函数来将数据“塑”成“宽”数据
最常用的方法是使用rbind() cbind()
但使用cbind()时候重复记录时不会被发现的
基于共同变量的合并可以使用merge(),注意不同类型的合并,inner join,left out join, right out join, full out join
有时候寻找合并数据向量的序列位置比合并数据更重要,merge()是使用match()函数来找到序列索引的。
(完)