数据分析 BY 伦大锤 阅读量 1,948
相对于基本数据管理,此处我们将接触到R中多种数学、统计和字符处理函数,学习如何自己编写函数,包括循环和条件执行语句,以及了解数据的整合和概述方法、重塑和重构方法。
要讨论数值和字符处理函数,不妨先考虑一个数据问题。一组学生参加了数学、科学和英语考试,需要按照某种成绩衡量指标将三门科目的成绩组合起来并排序,将前20%的学生评定为A,接下来20%的学生评定为B,依次类推。最后,将所有学生按照字母顺序进行排序并输出。
需要考虑的问题包括以下几点:
R中数据处理最为重要的函数包括数值(数学、统计、概率)函数和字符处理函数。
常用的数学函数包括:
常用的统计函数包括:
其中许多函数都提供了丰富的可选参数,可以进一步影响输出结果。例如以下截尾平均数,丢弃了最大5%和最小5%的数据和所有缺失值后得到算数平均值。
1
|
z
<
-
mean
(
x
,
trim
=
0.05
,
na
.
rm
=
TRUE
)
|
以下代码演示了计算某个数值向量均值和标准差的两种方式:
1
2
3
4
5
6
7
|
x
<
-
c
(
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
)
mean
(
x
)
sd
(
x
)
n
<
-
length
(
x
)
meanx
<
-
sum
(
x
)
/
n
css
<
-
sum
(
(
x
-
meanx
)
^
2
)
sdx
<
-
sqrt
(
css
/
(
n
-
1
)
)
|
不难发现,R中公式的写法和类似Matlab的矩阵运算语言有着许多共同之处。
使用以下代码对矩阵或数据框的数值列进行均值为1、标准差为0的标准化:
1
|
newdata
<
-
scale
(
mydata
)
|
或者任意均值和标准差:
1
|
newdata
<
-
scale
(
mydata
)
*
SD
+
M
|
如果仅对指定列处理,则使用transform()函数:
1
|
newdata
<
-
transform
(
mydata
,
myvar
=
scale
(
myvar
)
*
SD
+
M
)
|
概览函数和统计函数类似,但是通常用来生成特征已知的模拟数据,以及在用户编写的统计函数中计算概率值。
R中的每个概览函数都对应四个具体函数:d(密度函数)、p(分布函数)、q(分位数函数)和r(生成随机数)。
以正态分布为例,如果不指定均值和标准差,将会生成标准正态分布(均值为0,标准差为1),相应的密度函数(dnorm)、分布函数(pnorm)、分位数函数(qnorm)和随机生成函数(rnorm)分别如下。
1
2
3
4
5
6
7
8
9
|
x
<
-
pretty
(
c
(
-
3
,
3
)
,
30
)
y
<
-
dnorm
(
x
)
plot
(
x
,
y
,
type
=
"l"
,
xlab
=
"NormalDeviate"
,
ylab
=
"Density"
,
yaxs
=
"i"
)
#位于x=1.96左侧标准正态曲线下方的面积
pnorm
(
1.96
)
#均值为500,标准差为100正态分布的0.9分位点的值
qnorm
(
.
9
,
mean
=
500
,
sd
=
100
)
#生成50个均值为50,标准差为10的正态随机数
#rnorm(50,mean=50,sd=10)
|
在每次生成伪随机数的时候,函数都会使用一个不同的种子,因此也会产生不同的结果。可以通过函数set.seed()显式指定种子,使得之前的结果可以重现(reproducible)。重现数据有助于创建会在未来取用的,以及可与他人分享的随机示例数据。
使用MASS包中的mvrnorm()函数可以生成来自给定均值向量和协方差矩阵的多元正态分布,以下是一个生成满足指定三元正态分布的例子。
1
2
3
4
5
6
7
8
9
10
|
library
(
MASS
)
options
(
digits
=
3
)
set
.
seed
(
1234
)
mean
<
-
c
(
230.7
,
146.7
,
3.6
)
sigma
<
-
matrix
(
c
(
15360.8
,
6721.2
,
-
47.1
,
6721.2
,
4700.9
,
-
16.5
,
-
47.1
,
-
16.5
,
0.3
)
,
nrow
=
3
,
ncol
=
3
)
mydata
<
-
mvrnorm
(
500
,
mean
,
sigma
)
mydata
<
-
as
.
data
.
frame
(
mydata
)
names
(
c
(
"y"
,
"x1"
,
"x2"
)
)
dim
(
mydata
)
head
(
mydata
,
n
=
10
)
|
数学和统计函数用于处理数值型数据,而字符处理函数用于从文本型数据中抽取信息。
在R中,函数可以应用到一系列数据对象上,包括标量、向量、矩阵、数组和数据框(和Matlab类似)。如果希望函数应用于矩阵的各行或者各列,可以考虑apply()函数。
1
|
apply
(
x
,
MARGIN
,
FUN
,
.
.
.
)
|
MARGIN是维度的下标,1表示行、2表示列,FUN可以是内置函数或者你自己编写的函数,…为可选参数。
1
2
3
4
|
mydata
<
-
matrix
(
rnorm
(
30
)
,
nrow
=
6
)
apply
(
mydata
,
1
,
mean
)
apply
(
mydata
,
2
,
mean
)
apply
(
mydata
,
2
,
mean
,
trim
=
0.2
)
|
和apply()应用于矩阵一样,lapply()和sapply()则将函数应用于列表上。
回到我们之前的问题,组合三门成绩、按衡量指标排名、按区间分段打分、按姓名排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
options
(
digits
=
3
)
Student
<
-
c
(
"John Davis"
,
"Angela Williams"
,
"Bullwinkle Moose"
,
"David Jones"
,
"Janice Markhammer"
,
"Cheryl Cushing"
,
"Reuven Ytzrhak"
,
"Greg Knox"
,
"Joel England"
,
"Mary Rayburn"
)
Math
<
-
c
(
502
,
600
,
412
,
358
,
495
,
512
,
410
,
625
,
573
,
522
)
Science
<
-
c
(
95
,
99
,
80
,
82
,
75
,
85
,
80
,
95
,
89
,
86
)
English
<
-
c
(
25
,
22
,
18
,
15
,
20
,
28
,
15
,
30
,
27
,
18
)
roster
<
-
data
.
frame
(
Student
,
Math
,
Science
,
English
,
stringsAsFactors
=
FALSE
)
z
<
-
scale
(
roster
[
,
2
:
4
]
)
score
<
-
apply
(
z
,
1
,
mean
)
roster
<
-
cbind
(
roster
,
score
)
y
<
-
quantile
(
score
,
c
(
.
8
,
.
6
,
.
4
,
.
2
)
)
roster
$
grade
[
score
>=
y
[
1
]
]
<
-
"A"
roster
$
grade
[
score
<
y
[
1
]
&
score
>=
y
[
2
]
]
<
-
"B"
roster
$
grade
[
score
<
y
[
2
]
&
score
>=
y
[
3
]
]
<
-
"C"
roster
$
grade
[
score
<
y
[
3
]
&
score
>=
y
[
4
]
]
<
-
"D"
roster
$
grade
[
score
<
y
[
4
]
]
<
-
"F"
name
<
-
strsplit
(
(
roster
$
Student
)
,
" "
)
Lastname
<
-
sapply
(
name
,
"["
,
2
)
Firstname
<
-
sapply
(
name
,
"["
,
1
)
roster
<
-
cbind
(
Firstname
,
Lastname
,
roster
[
,
-
1
]
)
roster
<
-
roster
[
order
(
Lastname
,
Firstname
)
,
]
|
最后查看roster,你应当得到如下结果。
在正常情况下,R程序中的语句是从上至下执行的。当然有时候你需要控制程序的执行流,即使用条件和循环。
为了理解贯穿接下来内容的语法示例,请牢记以下概念:
循环结构重复地执行一个或一系列语句,直到某个条件不再为真,循环结构包括for循环和while循环。
1
2
|
for
(
var
in
seq
)
statement
while
(
cond
)
statement
|
在以下的例子中,单词Hello被输出了10次。
1
2
3
|
for
(
i
in
1
:
10
)
print
(
"Hello"
)
i
<
-
10
while
(
i
>
0
)
{
print
(
"Hello"
)
;
i
<
-
i
-
1
}
|
使用循环的时候,记得在循环里修改标记量,避免导致死循环。
在处理大数据集中的行和列时,R中的循环可能比较低效耗时,应该尽可能使用R中内建的数值/字符处理函数和apply()族函数。
在条件执行结构中,一条或一组语句仅在满足指定条件时执行。条件执行结构包括if-else、ifelse和switch。
1
2
3
4
|
if
(
cond
)
statement
if
(
cond
)
statement1
else
statement2
ifelse
(
cond
,
statement1
,
statement2
)
switch
(
expre
,
.
.
.
)
|
再给出一个使用switch的例子,虽然简单但清晰说明了switch的使用方法。
1
2
3
4
5
6
7
8
9
10
|
feelings
<
-
c
(
"sad"
,
"afraid"
)
for
(
i
in
feelings
)
print
(
switch
(
i
,
happy
=
"I'm glad you are happy"
,
afraid
=
"There is nothing to fear"
,
sad
=
"Cheer up"
,
angry
=
"Calm down now"
)
)
|
R最大的优点之一就是支持用户自行添加函数,R中许多函数也是基于已由函数构成的,一个函数的结构大概如下:
1
2
3
4
|
myfunction
<
-
function
(
arg1
,
arg2
,
.
.
.
)
{
statements
return
(
object
)
}
|
函数中的对象只在函数内部使用(记得{}的作用吗?),返回对象的数据类型是任意的,从标量到列表皆可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
mystats
<
-
function
(
x
,
parametric
=
TRUE
,
print
=
FALSE
)
{
if
(
parametric
)
{
center
<
-
mean
(
x
)
;
spread
<
-
sd
(
x
)
}
else
{
center
<
-
median
(
x
)
;
spread
<
-
mad
(
x
)
}
if
(
print
&
parametric
)
{
cat
(
"Mean="
,
center
,
"\n"
,
"SD="
,
spread
,
"\n"
)
}
else
if
(
print
&
!
parametric
)
{
cat
(
"Median="
,
center
,
"\n"
,
"MAD="
,
spread
,
"\n"
)
}
result
<
-
list
(
center
=
center
,
spread
=
spread
)
return
(
result
)
}
|
要查看此函数的运行情况,则需要生成一些测试数据并调用。
1
2
3
4
|
set
.
seed
(
1234
)
x
<
-
rnorm
(
500
)
y
<
-
mystats
(
x
)
z
<
-
mystats
(
x
,
parametric
=
FALSE
,
print
=
TRUE
)
|
在所得结果中,y$center为均值(0.00184),y$spread为标准差(1.03),并且没有输出结果;z$center为中位数(-0.0207),z$spread为绝对中位差(1.001),并且还会在屏幕上打印信息。
再来看一个使用了switch的用户自编函数,该函数可以让用户选择输出当天日期的格式。
1
2
3
4
5
6
7
8
9
10
11
|
mydate
<
-
function
(
type
=
"long"
)
{
switch
(
type
,
long
=
format
(
Sys
.
time
(
)
,
"%A %B %d %Y"
)
,
short
=
format
(
Sys
.
time
(
)
,
"%m-%d-%y"
)
,
cat
(
type
,
"is not a recognized type\n"
)
)
}
mydate
(
"long"
)
mydate
(
"short"
)
mydate
(
)
mydate
(
"medium"
)
|
switch中的最后一条语句给出了如何处理错误(或其他意料之外)的输入。除此之外,还有一些函数可以用来为函数添加错误捕获和纠正功能,如使用warning()生成一条错误提示信息,用message()生成一条诊断信息,用stop()停止当前表达式的执行并提示错误。如果希望了解更多关于调试程序的内容,请阅读Duncan Murdoch整理的“Debugging in R”。
R中提供了许多用于整合(aggregate)和重塑(reshape)数据的强大方法,整合数据是指将多组观测替换为根据这些观测计算的描述性统计量,重塑数据是指通过修改数据的结构(行和列)来决定数据的组织方式。
以下例子中,将会使用已包含在R基本安装中的数据框mtcars。该数据集从Motor Trend杂志(1974)提取,描述了34种车型的设计和性能特点(汽缸数、排量、马力、每加仑汽油行驶的英里数)。
使用函数t()即可对一个矩阵或数据框进行转置,对于后者,行名将成为列名。
1
2
3
|
cars
<
-
mtcars
[
1
:
5
,
1
:
4
]
cars
t
(
cars
)
|
在R中使用一个或多个by变量和一个预先定义好的函数来整合(collapse)数据十分容易。
1
|
aggregate
(
x
,
by
,
FUN
)
|
其中x为待整合的数据对象,by是一个变量名组成的列表,这些变量将被去掉以形成新的观测,FUN是用来计算描述性统计量的标量函数,它将被用来计算新观测中的值。以下代码根据汽缸数和档位数整合mtcars数据,并返回各个数值型变量的均值。
1
2
3
4
|
options
(
digits
=
3
)
attach
(
mtcars
)
aggdata
<
-
aggregate
(
mtcars
,
by
=
list
(
cyl
,
gear
)
,
FUN
=
mean
,
na
.
rm
=
TRUE
)
aggdata
|
将会得到以下结果。如何理解呢?例如第一行,拥有4个气缸和3个档位的车型,每加仑汽油行驶英里数(mpg)均值为21.5。需要注意的是,by中的参数必须写在一个列表中(即使只有一个参数)。
reshape包是一套重构和整合数据集的万能工具。由于reshape包并未内置在R的标准安装中,所以有必要通过install.packages(“reshape”)进行安装。
我们的操作大概包括两部分:融合(melt),使得每一行都是一个唯一的标识符和变量的组合;重铸(cast),将数据集变成任何需要的形状。接下来代码中,将处理以下样例数据。
融合使得每个测量变量独占一行,行中必须带有唯一确定该测量的标识符变量。
1
2
|
library
(
reshape
)
md
<
-
melt
(
mydata
,
id
=
c
(
"id"
,
"time"
)
)
|
注意,必须指定要唯一确定每个测量所需的变量(ID和Time),而表示测量变量名的变量(X1和X2)将由程序自动创建。
既然已经拥有了融合后的数据,现在便可以使用cast()函数将其重铸为任意形状了。
cast()函数读取已融合的数据,并使用提供的公式和一个(可选的)用于整合数据的函数将其重铸。
1
|
newdata
<
-
cast
(
md
,
formula
,
FUN
)
|
接受的公式形如:
1
|
rowvar1
+
rowvar2
+
.
.
.
+
~
colvar1
+
colvar2
+
.
.
.
|
rowvar1+rowvar2+…定义了要去掉的变量集合,以确定各行的内容;colvar1+colvar2+…定义了要去掉的变量集合,以确定各列的内容。下图给出了使用cast()函数处理样例数据的例子。
数据分析 BY 伦大锤 阅读量 1,059
分析数据要做的第一件事情,就是观察它。对于每个变量,哪些值是最常见的?值域是大是小?是否有异常观测?变量可以为连续型或类别型,我们将探索如何使用条形图、饼图、扇形图、直方图、核密度图、箱线图、小提琴图和点图等来分析和展示变量。
条形图通过垂直或水平的条形来展示类别型变量的分布(频数)。
1
|
barplot
(
height
)
|
其中的height是一个向量或一个矩阵。在接下来的例子中,将使用到vcd包中带有的Arthritis数据框,其描述了一项探索类风湿性关节炎新疗法研究的结果。
当height为一个向量时,向量值便确定了各条形的高度并绘制一幅垂直的条形图。使用参数horiz=TRUE则生成一幅水平条形图,还可以使用main、xlab和ylab等图形参数。
在关节炎研究中,变量Improved记录了对每位接受了安慰剂或药物治疗的病人的治疗效果。其中28人有了明显改善,14人有部分改善,而42人没有改善。
1
2
3
4
|
library
(
vcd
)
counts
<
-
table
(
Arthritis
$
Improved
)
barplot
(
counts
,
main
=
"Simple Bar Plot"
,
xlab
=
"Improvement"
,
ylab
=
"Frequency"
)
barplot
(
counts
,
main
=
"Horizontal Bar Plot"
,
ylab
=
"Improvement"
,
xlab
=
"Frequency"
,
horiz
=
TRUE
)
|
其实如果要绘制的类别型变量是一个因子或有序型因子,就可以直接使用函数plot()快速创建一幅垂直条形图。由于Arthritis$Improved是一个因子,因此以下代码也可以达到同样效果。
1
2
|
plot
(
Arthritis
$
Improved
,
main
=
"Simple Bar Plot"
,
xlab
=
"Improved"
,
ylab
=
"Frequency"
)
plot
(
Arthritis
$
Improved
,
horiz
=
TRUE
,
main
=
"Horizontal Bar Plot"
,
xlab
=
"Frequency"
,
ylab
=
"Improved"
)
|
如果height是一个矩阵而不是一个向量,则绘图结果将是一幅堆砌条形图或分组条形图。beside默认为FALSE表示堆砌,否则将分组。考虑治疗类型和改善情况的列联表:
1
2
3
4
|
library
(
vcd
)
counts
<
-
table
(
Arthritis
$
Improved
,
Arthritis
$
Treatment
)
barplot
(
counts
,
main
=
"Stacked Bar Plot"
,
xlab
=
"Treatment"
,
ylab
=
"Frequency"
,
col
=
c
(
"red"
,
"yellow"
,
"green"
)
,
legend
=
rownames
(
counts
)
)
barplot
(
counts
,
main
=
"Grouped Bar Plot"
,
xlab
=
"Treatment"
,
ylab
=
"Frequency"
,
col
=
c
(
"red"
,
"yellow"
,
"green"
)
,
legend
=
rownames
(
counts
)
,
beside
=
TRUE
)
|
第一个barplot函数绘制了一幅堆砌条形图,而第二个绘制了一幅分组条形图。图中图例和条形图叠加了,不过这可以通过格式化和放置图例的方法解决,故不用担心。
条形图并不一定要基于计数数据或频率数据,也可以使用数据整合函数并将结果传递给barplot()函数,来创建表示均值、中位数、标准差等条形图。
有多种方法可以微调条形图的外观:
以下代码中,旋转了条形的标签(las)、修改了标签文本、增加了y边界的大小(mar)、缩小了字体大小(cex.names)。
1
2
3
4
|
par
(
mar
=
c
(
5
,
8
,
4
,
2
)
)
par
(
las
=
1
)
counts
<
-
table
(
Arthritis
$
Improved
)
barplot
(
counts
,
main
=
"Treatment Outcomes"
,
horiz
=
TRUE
,
cex
.
names
=
0.8
,
names
.
arg
=
c
(
"No Improvement"
,
"Some Improvement"
,
"Marked Improvement"
)
)
|
还有另一种特殊的条形图:棘状图(spinogram)。棘状图对堆砌条形图进行了重缩放,使得每个条形的高度均为1,每一段的高度表示所占比例。棘状图可由vcd包中的spine()绘制:
1
2
3
4
|
library
(
vcd
)
attach
(
Arthritis
)
counts
<
-
table
(
Treatment
,
Improved
)
spine
(
counts
,
main
=
"Spinogram Example"
)
|
通过棘状图可以看出,治疗组和安慰剂组相比,获得显著改善的患者比例更高。
虽然饼图在商业世界中使用广泛,但是多数统计学家却并不支持它,因为相对于饼图的面积,人类对条形图或点图中的长度判断更加精确。也许是因为这个原因,R中饼图的选项和其他统计软件相比十分有限。
1
|
pie
(
x
,
labels
)
|
其中x是一个非负数值向量,表示各个扇形的面积,labels则是各扇形标签的字符型向量。以下代码绘制了三幅饼图,最简单的饼图、显示百分比的饼图和三维饼图。
1
2
3
4
5
6
7
8
9
|
par
(
mfrow
=
c
(
1
,
3
)
)
slices
<
-
c
(
10
,
12
,
4
,
16
,
8
)
lbs
<
-
c
(
"US"
,
"UK"
,
"Australia"
,
"Germany"
,
"France"
)
pie
(
slices
,
labels
=
lbs
,
main
=
"Simple Pie Chart"
)
pct
<
-
round
(
slices
/
sum
(
slices
)
*
100
)
lbs2
<
-
paste
(
lbs
,
" "
,
pct
,
"%"
,
sep
=
""
)
pie
(
slices
,
labels
=
lbs2
,
col
=
rainbow
(
length
(
lbs2
)
)
,
main
=
"Pie Chart with Percentages"
)
library
(
plotrix
)
pie3D
(
slices
,
labels
=
lbs
,
explode
=
0.1
,
main
=
"3D Pie Chart"
)
|
饼图让比较各扇形的值变得困难,除非这些值被附加在标签上。为了弥补这一缺点,产生了一种饼图的变种:扇形图。扇形图为用户提供了一种同时展示相对数量和相互差异的方法,各个扇形相互叠加并且拥有不同半径,从而使得所有扇形都是可见的。
1
2
3
4
|
library
(
plotrix
)
slices
<
-
c
(
10
,
12
,
4
,
16
,
8
)
lbs
<
-
c
(
"US"
,
"UK"
,
"Australia"
,
"Germany"
,
"France"
)
fan
.
plot
(
slices
,
labels
=
lbs
,
main
=
"Fan Plot"
)
|
直方图通过在X轴上将值域分割为一定数量的组,在Y轴上显示相应值的频数,展示了连续型变量的分布。使用以下函数创建直方图:
1
|
hist
(
x
)
|
x为一个由数据值组成的数值向量,参数freq=FALSE表示根据概率密度而不是频数绘制图形,参数breaks用于控制组的数量。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
par
(
mfrow
=
c
(
2
,
2
)
)
hist
(
mtcars
$
mpg
)
hist
(
mtcars
$
mpg
,
breaks
=
12
,
col
=
"red"
,
xlab
=
"Miles Per Gallon"
,
main
=
"Colored histogram with 12 bins"
)
hist
(
mtcars
$
mpg
,
freq
=
FALSE
,
breaks
=
12
,
col
=
"red"
,
xlab
=
"Miles Per Gallon"
,
main
=
"Histogram, rug plot ,density curve"
)
rug
(
jitter
(
mtcars
$
mpg
)
)
lines
(
density
(
mtcars
$
mpg
)
,
col
=
"blue"
,
lwd
=
2
)
x
<
-
mtcars
$
mpg
h
<
-
hist
(
x
,
breaks
=
12
,
col
=
"red"
,
xlab
=
"Miles Per Gallon"
,
main
=
"Histogram with normal curve and box"
)
xfit
<
-
seq
(
min
(
x
)
,
max
(
x
)
,
length
=
40
)
yfit
<
-
dnorm
(
xfit
,
mean
=
mean
(
x
)
,
sd
=
sd
(
x
)
)
yfit
<
-
yfit
*
diff
(
h
$
mids
[
1
:
2
]
)
*
length
(
x
)
lines
(
xfit
,
yfit
,
col
=
"blue"
,
lwd
=
2
)
box
(
)
|
第一幅直方图未指定任何选项,共创建了五个组并且显示了默认的标题和坐标轴标签;第二幅直方图共12个分组,并使用红色填充条形;第三幅直方图保留了第二幅图的内容,并叠加了一条密度曲线(density)和轴须图(rug),密度曲线是数据分布一个的核密度估计,而轴须图是实际数据值的一种一维呈现方式;第四幅图和第二幅类似,还有一条叠加的正态曲线和一个将图形围绕起来的盒型。
核密度估计是用于估计随机变量概率密度函数的一种非参数方法。
1
|
plot
(
density
(
x
)
)
|
其中x为一个数值型向量,由于plot()函数会创建一幅新的图形,所以要向一幅已经存在的图形上叠加一条密度曲线时,可以使用lines()函数。
1
2
3
4
5
6
|
par
(
mfrow
=
c
(
2
,
1
)
)
d
<
-
density
(
mtcars
$
mpg
)
plot
(
d
)
plot
(
d
,
main
=
"Kernel Density of Miles Per Gallon"
)
polygon
(
d
,
col
=
"red"
,
border
=
"blue"
)
rug
(
mtcars
$
mpg
,
col
=
"brown"
)
|
第一幅图是默认设置创建的最简图形,而第二幅图中添加了标题,并将曲线修改成蓝色、使用实心红色填充了曲线下方的区域、添加了棕色的轴须图。
使用sm包中的sm.density.compare()函数可以向图形叠加两组或更多核密度图,格式为:
1
|
sm
.
density
.
compare
(
x
,
factor
)
|
其中x为一个数值型向量,factor为一个分组变量。以下代码比较了拥有4个、6个或8个汽缸车型的每加仑汽油行驶英里数。
1
2
|