在数据分析过程中使用基础作图函数可以快速对数据进行概览,因此记住一些常用的基础作图函数是很有用的,比如
plot
、
hist
、
boxplot
、
pie
等函数。R的基础作图函数分为高阶作图函数和低阶作图函数,高阶作图函数能直接作出完整图形,而低阶作图函数只能在已有图形上添加内容。作图函数可以通过一些参数来进行作图效果的修饰:
main
、
sub
、
xlab
、
ylab
参数依次分别设置主标题、副标题、x轴标题、y轴标题;
xlim
、
ylim
分别限定横轴、纵轴的范围;
add
参数确定是否作在前一个作图函数的结果上(默认
add=FALSE
);
axes
设定对否画出坐标轴和边框(默认
axes=TRUE
);
asp
参数设置y/x的比例。
另外还有一些参数可以通过par(……)来进行全局设置,有的可以直接放到作图函数中。如adj、bg、bty、cex(字体大小)、col(颜色)、font(字体风格)、las、lty(线的类型)、lwd(线的粗细)、mar、mfcol、mfrow、pch(点的类型)、ps、pty、tck、tcl、xaxt、yaxt、等等的,详情查看R的说明文档。
这些参数一般来说了解一下即可
,通常只用基础作图快速概览,不需要对图进行精细调节。要做出精美的图,还是使用
ggplot2
更顺手和可控一些。
这里以一个身高数据作为示例。
SG <- read.delim("ftp://ftp.biolab.wang/data/height_inheritance.tsv", skip=1, fileEncoding="UTF-8")
head(SG, n=2)
## 序号 性别 年龄 身高 父亲身高 母亲身高
## 1 1 女 18 157 173 160
## 2 2 女 20 155 165 154
高阶作图函数
plot(x, y)
将x映射到横轴,y映射到y轴。可以通过type参数设置图的类型,
type=“p”
做散点图(
p
oint),
type=“l”
做折线图(
l
ine),
type=“b”
做折线图加点两者(
b
oth),
type=“o”
也是线加点,但线在点上,
type=“c”
也是线加点,但不显示点(也就是线线段之端点不衔接,而折线图线段端点是连接的),
type=“h”
做直方图(
h
istogram),
type=“s”
做阶梯图(
s
teps),数据映射到竖线的顶端,
type=“S”
也是阶梯图,但数据映射到竖线的下端,
type=“n”
表示不将图plot出来而只是把坐标系绘制出来;
plot(SG$父亲身高, SG$母亲身高, xlab="父亲身高", ylab="母亲身高", main="学生父亲身高与母亲身高对照图")
plot(1:10, 1:10, type="s", xlab="横轴标题", ylab="纵轴标题", main="type=\"s\"")
plot(1:10, 1:10, type="S", xlab="横轴标题", ylab="纵轴标题", main="type=\"S\"")
boxplot
作箱线图(盒形图),在数据分析的时候快速看数据分布情况很方便。
boxplot(y~x)
,命令y映射到纵坐标,按照x来进行分组,x的值作为横坐标;
boxplot(x)
,如果x为向量,则x映射到纵坐标,仅展示为一组,如果x是数据框或者矩阵,则每列一个个分组,列标题为横坐标。
boxplot(SG$身高~SG$性别, xlab=NULL, ylab="身高(cm)", col=c("blue", "pink"), main="学生身高调查")
sunflowerplot(x, y)
也是作散点图,但是具有相似坐标的点将会被做成一个带短线(像花一样)的点,由花瓣数代表数据点的个数。
set.seed(2020); X <- sample(1:10, 100, replace=TRUE)
set.seed(2022); Y <- sample(1:10, 100, replace=TRUE)
sunflowerplot(X, Y)
points(x, y)
添加点;
lines(x, y)
添加线;
text(x, y, labels, …)
添加文字;
legend(x, y, legend)
添加图例;
axis(side, vect)
添加坐标轴;
plot((SG$父亲身高+SG$母亲身高)/2, SG$身高, type="n", axes=FALSE, xlab="父母平均身高(cm)", ylab="学生身高(cm)")
text((SG$父亲身高+SG$母亲身高)/2, SG$身高, labels=SG$性别, cex=0.5, col=c(男="cyan", 女= "purple")[SG$性别])
axis(side=3)
axis(side=4)
ggplot2
是对作图语法的一种极好的实现,加上其强大的
拓展
, 是一个深受喜爱的数据可视化工具。具备一定英文阅读能力的可以阅读ggplot2的作者Hadley Wickham的书
ggplot2: Elegant Graphics for Data Analysis
。我这尝试简要地介绍,以飨初学。
对于数据可视化需要考虑这些要素:用于作图的数据、数据的统计变换、图的类型、变量的视觉映射、映射的标尺、构图元素的位置调整、坐标系统、分面、图的主题风格等等各个方面。ggplot2的作图代码模版如下:
library(ggplot2)
ggplot(data = <数据框>) +
<几何函数>(
mapping = aes(<视觉映射>),
stat = <统计转换>,
position = <位置调整>
<标尺函数> +
<坐标系统函数> +
<分面函数> +
<主题风格函数>
data、mapping、stat、position、等等参数放在
ggplot()
函数里则控制为全局,设置到
几何函数
里则控制本几何函数。
几何对象(Geometric object),即图的类型,由geom_开头的
几何函数
定义。几何对象包括:散点图(geom_point)、折线图(geom_line)、柱状图(geom_bar)、直方图(geom_histogram)、盒形图(geom_boxplot)、密度曲线(geom_density)、……
几何对象分为个体几何对象(Individual geoms)和群体几何对象(Collective Geoms),个体几何对象为数据框中的每一行绘制一个图形对象,比如散点图(point geom)将,而群体几何对象将多个观测值展示到一个几何对象。
具体使用的时候,需要知道每个几个对象的适用场景,比如geom_bar默认情况下x和y只需要给的一个参数,默认对其进行计数统计(即
stat=“count”
)。比如这里使用柱状图展示两个班级的人数:
默认情况下如果同时给了x和y参数,就会报错:
ggplot(data=data.frame(班级=c("1班", "2班"), 人数=c(32, 23))) +
geom_bar(aes(x = 班级, y=人数))
这时候设置
stat=“identity”
则直接使用y作为纵坐标
ggplot(data=data.frame(班级=c("1班", "2班"), 人数=c(32, 23))) +
geom_bar(aes(x = 班级, y=人数), stat="identity")
视觉映射 (aes)
视觉映射(Aesthetic mappings) ,一个变量的值可以映射到不同的视觉属性,由几何函数中mapping参数定义。包括:横坐标(x)、纵坐标(y)、颜色(colour)、填充(fill)、透明度(alpha)、形状(shape)、大小(size)、 边框宽度(stroke)、线条类型(linetype)、分组(group)、…… 。不同几何对象能够映射的视觉属性有差异,而不同属性间的叠加也因属性值不同而会有不同。
初学者需要留意的是,这些视觉的元素放在aes函数里的效果是不同的。设定在
aes
里给mapping是以此作为一个变量进行映射,而放在
aes
函数外则是使用其值作为参数。比如自己运行如下两个作图代码试试看点的颜色有何不同,图片还有些什么差异:
ggplot() + geom_point(mapping=aes(x=1:5, y=1:5, colour="blue"))
ggplot() + geom_point(mapping=aes(x=1:5, y=1:5), colour="blue")
把“blue”放到aes里,是把它作为一个变量进行了映射,系统默认给映射到了红色,同时还会自动生成了图例。而把“blue”给了aes外边的colour,是设置了点的颜色为“bule”。
(dat <- data.frame(S1=1:5, S2=1:5, C1=letters[1:5],
C2=c("blue", "cyan", "green", "pink", "red")))
## S1 S2 C1 C2
## 1 1 1 a blue
## 2 2 2 b cyan
## 3 3 3 c green
## 4 4 4 d pink
## 5 5 5 e red
ggplot(dat) +
geom_point(aes(x=S1, y=S2, color=C1)) +
ggtitle("图1")
ggplot(dat) +
geom_point(aes(x=S1, y=S2, color=C2)) +
ggtitle("图2")
ggplot(dat) +
geom_point(aes(x=S1, y=S2), color=dat$C2) +
ggtitle("图3")
统计变换(Statistical transformations),对数据进行的转换,包括本身(stat_identity)、计数(stat_count)、……。统计变换和几何对象有一定的对应关系或者配合关系。
以stat_identity为例,设置可以可以是
stat=“identity”
或者
stat=stat_identity()
的方式,后者可以添加调整的参数,包括mapping指定变量映射以及geom指定几何对象。比如:
ggplot() +
stat_identity(
mapping = aes(x=1:5, y=1:5),
geom = "point")
效果等同于如下代码,都得到右图:
ggplot() +
geom_point(
mapping=aes(x=1:5, y=1:5),
stat="identity")
位置调整 (position)
位置调整(Position adjustments),调整图形对象的位置。包括:原位(position_identity)、堆叠(position_stack)、比例堆叠(position_fill,堆叠且柱高归一)、分列(position_dodge)、加噪(position_jitter, 随机微量加减以避免点间重叠)、加噪分列(position_jitterdodge)、偏距(position_nudge,调整指定的距离,常用于是的文字偏离坐标一些)。
设置方式同样有:
position=“stack”
和
position=position_stack()
的方式,后者可以指定更多的参数。比如:
ggplot(data=data.frame(班级=c("1班", "1班", "2班", "2班"), 成绩=c("合格", "挂科", "合格", "挂科"), 人数=c(45, 5, 40, 6))) +
geom_bar(mapping=aes(x=班级, y=人数, fill=成绩),
stat="identity",
position=position_dodge(width=1)) +
geom_text(mapping=aes(x=班级, y=人数, label=人数, group=成绩),
position=position_dodge(width=1), vjust=0)
这里通过设置同样的position参数,是的文字的标记正好出现在柱子的上方。试试看,把这两个width改得不一样会怎么样。这里对text不映射颜色,所以使用了group来进行分组,以便进行dodge排列,如果设置了colour映射则可省group。试试看,如果去掉了group参数会怎么样?
ggplot(data=data.frame(班级=c("1班", "1班", "2班", "2班"), 成绩=c("合格", "挂科", "合格", "挂科"), 人数=c(45, 5, 40, 6))) +
geom_bar(mapping=aes(x=班级, y=人数, fill=成绩),
stat="identity",
position="stack") +
geom_text(mapping=aes(x=班级, y=人数, label=人数, group=成绩),
position=position_stack(vjust=0.5))
这里使用position_stack,在使用堆叠排列的时候,使得文字和柱子对应,使用vjust使之居中。vjust是纵向上的位置调整,默认值为1。还有一个reverse参数,可使用
?position_stack
查看了解详情。
ggplot() +
geom_line(aes(x=1:10,y=sin(1:10))) +
scale_x_continuous(limits = c(2.5, 7.5))
而使用coord来设置,则相当于只是截取了图片的这部分,而不影响数据。
ggplot() +
geom_line(aes(x=1:10,y=sin(1:10))) +
coord_cartesian(xlim = c(2.5, 7.5))
分面(facet)
分面(Facet) 根据某些变量将数据分成子集,在针对各个子集分别作图并排列在一个页面,包括:网格型(facet_grid)和封面型(facet_wrap)。
另外也可以使用一些完整的预设主题,比如theme_grey()、theme_bw()、theme_classic()、theme_void()、……。尝试看看个个主题的风格是什么样子的。比如试试:
ggplot() + geom_point(mapping=aes(x=1:5, y=1:5), stat="identity") + theme_classic()
图片另存为pdf时,如果不指定字体,那么汉字无法显示。
pdf("test01.pdf")
plot(x=1, y=1, main="好", sub="I think that's OK")
## Warning messages:
## 1: In title(...) : 'mbcsToSbcs'里转换'好'出错:<e5>代替了dot
## 2: In title(...) : 'mbcsToSbcs'里转换'好'出错:<a5>代替了dot
## 3: In title(...) : 'mbcsToSbcs'里转换'好'出错:<bd>代替了dot
## 4: In title(...) : 'mbcsToSbcs'里转换'好'出错:<e5>代替了dot
## 5: In title(...) : 'mbcsToSbcs'里转换'好'出错:<a5>代替了dot
## 6: In title(...) : 'mbcsToSbcs'里转换'好'出错:<bd>代替了dot
dev.off()
## null device
## 1
使用GB1字体
这时候可以在pdf设备设置family参数
pdf("test02.pdf", family="GB1")
plot(x=1, y=1, main="好", sub="I think that's OK")
dev.off()
## null device
## 1
也可以在绘图命令中指定family参数
pdf("test03.pdf")
plot(x=1, y=1, main="好", sub="I think that's OK", family= "GB1")
dev.off()
## null device
## 1
可以看到,中文正常显示了,但是英文的显示效果似乎有些不太好。
最简单的是加载showtext包之后执行
showtext_auto()
命令,这样就能自动使用showtext来显示字体,如果不想用showtext了,可以用
showtext_auto(FALSE)
命令关闭。
library(showtext)
showtext_auto(TRUE)
pdf("test04.pdf")
plot(x=1, y=1, main="好", sub="I think that's OK")
dev.off()
## null device
## 1
可以看到,这时候汉字和英文的显示效果都不错,但是这些字无法向文字一样被选择和复制。这里使用了showtext包内置的文泉驿微米黑(WenQuanYi Micro Hei family)字体。
showtext包使用sysfonts包的font_add函数添加的字体,加载showtext的时候,sysfonts包也会被加载。比如,我们这里添加宋体、黑体、楷体、隶书,叫做SHKL。
library(showtext)
showtext_auto(TRUE)
font_add(
family = "SHKL",
regular = "C:/Windows/Fonts/simsun.ttc",
bold = "C:/Windows/Fonts/simhei.ttf",
italic = "C:/Windows/Fonts/simkai.ttf",
bolditalic = "C:/Windows/Fonts/SIMLI.TTF"
pdf('test05.pdf', height=3, width=7)
plot(c(0,1), c(0.1,0.9), type='n', axes=FALSE, xlab='', ylab='')
text(0.5, 0.8, 'regular(正体) 对应 宋体', family="SHKL", font=1)
text(0.5, 0.6, 'bold(粗体) 对应 黑体', family="SHKL", font=2)
text(0.5, 0.4, 'italic(斜体) 对应 楷体', family="SHKL", font=3)
text(0.5, 0.2, 'bolditalic(粗斜体) 对应 隶书', family="SHKL", font=4)
dev.off()
showtext_auto
(
TRUE
)
font_add
(
family
=
"宋体"
, regular
=
"C:/Windows/Fonts/simsun.ttc"
)
font_add
(
family
=
"黑体"
, regular
=
"C:/Windows/Fonts/simhei.ttf"
)
font_add
(
family
=
"楷体"
, regular
=
"C:/Windows/Fonts/simkai.ttf"
)
font_add
(
family
=
"隶书"
, regular
=
"C:/Windows/Fonts/SIMLI.TTF"
)
pdf
(
'test06.pdf'
, height
=
3
, width
=
2
)
plot
(
c
(
0
,
1
)
,
c
(
0.1
,
0.9
)
, type
=
'n'
, axes
=
FALSE, xlab
=
''
, ylab
=
''
)
text
(
0.5
,
0.8
,
'这是宋体'
,
family
=
"宋体"
)
text
(
0.5
,
0.6
,
'这是黑体'
,
family
=
"黑体"
)
text
(
0.5
,
0.4
,
'这是楷体'
,
family
=
"楷体"
)
text
(
0.5
,
0.2
,
'这是隶书'
,
family
=
"隶书"
)
dev.
off
(
)
使用grid包的Viewports可以精确地控制图片在页面上的位置。
比如,我们现在有一组数据,记录一些学生的身高以及他们父母的身高。我们用这个数据做三个图,然后将三个图排布在一个页面。
URL <- "ftp://ftp.biolab.wang/data/height_inheritance.tsv";
dat <- read.delim(URL, skip=1, encoding="UTF-8")
library(grid)
library(ggplot2)
library(plyr)
library(ggpubr)
library(reshape)
图一,男女生身高均值比较:
ggplot
(
dat01
)
+
geom_bar
(
aes
(
x
=
性别, y
=
Mean, fill
=
性别
)
,
stat
=
"identity"
)
+
geom_errorbar
(
aes
(
x
=
性别, y
=
Mean,
ymin
=
Mean
-
SD, ymax
=
Mean
+
SD, colour
=
性别
)
,
width
=
0.25
, linewidth
=
2
xlab
(
NULL
)
+
ylab
(
"Height"
)
+
scale_x_discrete
(
labels
=
c
(
"Male"
,
"Female"
)
)
+
scale_y_continuous
(
limits
=
c
(
0
,
200
)
,
expand
=
c
(
0
,
0
)
)
+
theme_classic
(
)
+
theme
(
legend.
position
=
"none"
)
图二,男生身高对父母平均身高的回归直线:
aes
(
x
=
父母平均身高, y
=
身高
)
)
+
geom_point
(
)
+
geom_smooth
(
method
=
'lm'
,
formula
=
y~x,
se
=
FALSE
)
+
ggpubr
::
stat_regline_equation
(
aes
(
label
=
paste
(
"~"
, after_stat
(
eq.
label
)
,
after_stat
(
rr.
label
)
, sep
=
"~~~~"
)
)
,
formula
=
y~x, size
=
3
,
label.
x
.
npc
=
0.1
, label.
y
.
npc
=
0.1
scale_x_continuous
(
limits
=
c
(
150
,
175
)
,
expand
=
c
(
0
,
0
)
, breaks
=
seq
(
150
,
175
,
5
)
)
+
scale_y_continuous
(
limits
=
c
(
150
,
190
)
,
expand
=
c
(
0
,
0
)
, breaks
=
seq
(
150
,
190
,
5
)
)
+
xlab
(
"Midparental Height"
)
+
ylab
(
"Height"
)
+
theme_classic
(
)
+
theme
(
)
图三,亲代和子代身高分布图,区分性别:
dat03 <- dat[ , c("序号", "性别", "身高")]
dat03$代次 <- "子代"
dat04 <- dat[ , c("序号", "父亲身高", "母亲身高")]
names(dat04)[2:3] <- c("男", "女")
dat04 <- reshape::melt(dat04, id=1)
names(dat04)[2:3] <- c("性别", "身高")
dat04$代次 <- "亲代"
dat05 <- rbind(dat03, dat04)
ggplot(dat05) +
geom_boxplot(aes(x=性别, y=身高, colour=代次)) +
scale_y_continuous(limits=c(140, 190),
expand=c(0, 0), breaks=seq(140, 190, 5)) +
scale_x_discrete(labels=c("Male", "Female")) +
xlab(NULL) +
ylab("Height") +
theme_classic() +
theme(legend.position=c(1, 1),
legend.justification=c(1, 1),
legend.box.background = element_blank(),
legend.key = element_blank()
图片排版,将图二排在左边的2/3的页面,右边的1/3的页面上半部分排图一,下半部分排图三。
grid.newpage() # 打开一个新的页面,当前视窗为整个页面
vp <- viewport(x=1/3, y=1/2, width=2/3, height=1)
pushViewport(vp) # 进入子视窗,然后当前视窗为vp指定的区域
grid.draw(ggplotGrob(g2))
grid.text("A", x=0, y=1, hjust=0, vjust=1,
gp=gpar(fontsize=15, col="blue"))
upViewport() # 做完图之后,回到父视窗,又回到了整个页面
vp <- viewport(x=5/6, y=3/4, width=1/3, height=1/2)
pushViewport(vp)
grid.draw(ggplotGrob(g1))
grid.text("B", x=0, y=1, hjust=0, vjust=1,
gp=gpar(fontsize=15, col="blue"))
upViewport()
vp <- viewport(x=5/6, y=1/4, width=1/3, height=1/2)
pushViewport(vp)
grid.draw(ggplotGrob(g3))
grid.text("C", x=0, y=1, hjust=0, vjust=1,
gp=gpar(fontsize=15, col="blue"))
upViewport()
vp ← viewport(x=5/6, y=1/4, width=1/3, height=1/2)
中的x和y是子视窗(Viewport)中心在父视窗中的位置,1/2就是中心处;而width和height是子视窗相对于父视窗的宽度和高度。pushViewport
进入到当前视窗的子视窗,而upViewport
则回到当前视窗的父视窗。
先告诉R Ghostscript的安装路径,这个根据自己实际来决定,比如我这里安装在D盘,使用的是64位的:
Sys.setenv(R_GSCMD="D:/Program/gs/gs9.18/bin/gswin64.exe")
然后直接使用embedFonts函数即可。
embedFonts(file="file_name.pdf", outfile = "new_file_name.pdf")