在数据分析过程中使用基础作图函数可以快速对数据进行概览,因此记住一些常用的基础作图函数是很有用的,比如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”做散点图(point),type=“l”做折线图(line),type=“b”做折线图加点两者(both),type=“o”也是线加点,但线在点上,type=“c”也是线加点,但不显示点(也就是线线段之端点不衔接,而折线图线段端点是连接的),type=“h”做直方图(histogram),type=“s”做阶梯图(steps),数据映射到竖线的顶端,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\"")
这里以展示男女性别比例或为例:
(NNBL <- table(SG$性别)) ## ## 男 女 ## 114 266
barplot(x)作柱状图
barplot(NNBL, main="学生身高调查数据男女人数", col=c("cyan", "purple"))
pie(x)作饼图
pie(NNBL, main="学生身高调查数据男女比例图")
boxplot作箱线图(盒形图),在数据分析的时候快速看数据分布情况很方便。boxplot(y~x),命令y映射到纵坐标,按照x来进行分组,x的值作为横坐标;boxplot(x),如果x为向量,则x映射到纵坐标,仅展示为一组,如果x是数据框或者矩阵,则每列一个个分组,列标题为横坐标。
boxplot(SG$身高~SG$性别, xlab=NULL, ylab="身高(cm)", col=c("blue", "pink"), main="学生身高调查")
boxplot(SG$身高, ylab="学生身高(cm)")
boxplot(SG[ , c("身高", "父亲身高", "母亲身高")], ylab="身高(cm)")
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)
qqnorm(x)作Q-Q图,横坐标为x在正态分布下期望的值,纵坐标为x。
qqnorm(SG$身高, sub="学生身高")
qqplot(x, y)作Q-Q图,横坐标为x,纵坐标为y。
qqplot((SG$父亲身高+SG$母亲身高)/2, SG$身高, xlab="Quantiles of Mid-parent Height (cm)", ylab="Quantiles of Adult-child Height (cm)", main="Q-Q Plot")
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)
arrows(x0, y0, x1, y1, angle= 30, code=2)添加箭头,code控制箭头位置点,code=1表示在点(x0, y0),code=2表示在(x1, y1),code=2表示两端都有箭头;angle则设置箭头边缘与轴线之间的角度。
abline(a, b)画一条斜率为b且截距为a的线,abline(h=y)画纵坐标为y的水平线,abline(v=x)画横坐标为x的竖线;
ggplot2是对作图语法的一种极好的实现,加上其强大的拓展, 是一个深受喜爱的数据可视化工具。可以参考其官方的介绍以及参考手册及更多。, 具备一定英文阅读能力的可以阅读ggplot2的作者Hadley Wickham的书ggplot2: Elegant Graphics for Data Analysis。我这尝试简要地介绍,以飨初学。
对于数据可视化需要考虑这些要素:用于作图的数据、数据的统计变换、图的类型、变量的视觉映射、映射的标尺、构图元素的位置调整、坐标系统、分面、图的主题风格等等各个方面。ggplot2的作图代码模版如下:
library(ggplot2) ggplot(data = <数据框>) + <几何函数>( mapping = aes(<视觉映射>), stat = <统计转换>, position = <位置调整> ) + <标尺函数> + <坐标系统函数> + <分面函数> + <主题风格函数>
data、mapping、stat、position、等等参数放在ggplot()函数里则控制为全局,设置到几何函数里则控制本几何函数。
实际用于作图的数据(Data),其中包含了需要被展示出来的变量,每个变量一个维度。数据对象类型为数据框,每个变量一列。
几何对象(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")
视觉映射(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 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查看了解详情。
标尺(Scales)控制数据到视觉属性的映射。通过scale_*_*序列函数(比如scale_x_continuous、scale_colour_discrete、……)来设置。
尺标(Guides)也就是坐标轴、图例(legend, 包括色带(colourbar)),使人们能够将视觉属性对应到数据。Guides是自动产生的,通过scale函数里的guide参数或者guides函数可以进行调整。
坐标系统(Coordinate systems)结合视觉对象的两个位置元素确定它在图上的位置,并且协同分面系统画坐标轴与面板背景。 标尺(Scales)控制坐标轴上的值以及从数据到位置的映射,而坐标系统则是实际将他们画出来,因此采用不同的坐标系统得到的图可能差异巨大。坐标系统包括:直角坐标(coord_cartesian)、极坐标(coord_polar)、地图坐标(coord_map)。直角坐标可以固定纵横比(coord_fixed)、横纵颠倒(coord_flip)、等等。
比如对右边这个散点图图,我们通过如下代码得到。使用scale和coord来截取其中一部分的效果不一样。
ggplot() + geom_line(aes(x=1:10,y=sin(1:10)))
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_grid)和封面型(facet_wrap)。
主题(theme)控制非数据元素的外观,比如背景、网格线、坐标字体的颜色、图例字号的大小、图例的位置、……。通过? theme查看theme可以包括的参数,可调控性非常强,比如精确控制很坐标刻度值的属性可以通过axis.text.x参数进行调整。这些参数的值往往通过element_*函数来设定,元素的类型包括:文本(element_text)、线条(element_line)、矩形(element_rect)、空白(element_blank)。
另外也可以使用一些完整的预设主题,比如theme_grey()、theme_bw()、theme_classic()、theme_void()、……。尝试看看个个主题的风格是什么样子的。比如试试:
ggplot() + geom_point(mapping=aes(x=1:5, y=1:5), stat="identity") + theme_classic()
theme里可以设置的选项非常多,可以实现对图细节的精准、细致的调节。其中legend部分一些选参数所指项目看名称不是很直观,我这里示例如下,以便同学参考:
以上示例对应的theme设置为:
ggplot(dat=data.frame(X = LETTERS[1:3], XX = letters[1:3], Y=c(1, 2, 1))) + geom_bar(aes(x = X, y = Y, fill = X), stat = "identity") + geom_line(aes(x = X, y = Y, group = 1)) + geom_point(aes(x = X, y = Y, shape = XX), size = 5) + scale_y_continuous(limits = c(0, 4), expand = c(0, 0)) + xlab(NULL) + ylab(NULL) + theme_bw() + theme( legend.box = "horizontal", legend.box.background = element_rect(fill = "lightblue", color = "red", linewidth = 2), legend.box.margin = margin(10, 10, 10, 10, unit = "mm"), legend.box.spacing = unit(35, unit = "mm"), legend.background = element_rect(fill = "darkred", color = "green", linewidth = 2), legend.margin = margin(10, 10, 10, 10, unit = "mm"), legend.spacing.x = unit(20, unit = "mm"), legend.key = element_rect(fill = "lightgreen", color = "blue", linewidth = 2), legend.key.size = unit(10, unit = "mm"), legend.key.spacing = unit(10, unit = "mm"), )
图片另存为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
这时候可以在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包,这个包把文本内容替换成图形,显示字体的效果,但是得到的pdf中的文字不再能够像文字一样被选择。
首先安装这个包:
install.packages("showtext")
最简单的是加载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()
library(showtext) 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)
图一,男女生身高均值比较:
dat01 <- ddply(dat, .(性别), summarize, Mean=mean(身高), SD=sd(身高)) g1 <- 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") g1
图二,男生身高对父母平均身高的回归直线:
dat02 <- dat dat02$父母平均身高 <- (dat02$父亲身高 + dat02$母亲身高)/2 g2 <- ggplot(dat02[dat02$性别 == "男", ], 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() g2
图三,亲代和子代身高分布图,区分性别:
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) g3 <- 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() ) g3
图片排版,将图二排在左边的2/3的页面,右边的1/3的页面上半部分排图一,下半部分排图三。效果如右图,代码如下:
# 打开一个新的页面,当前视窗为整个页面 grid.newpage() # 定义一个子视窗,中点位于父视窗x方向1/3、y方向1/2, # 宽是父视窗的2/3、高是父视窗的2/2 # 学习者如果不能直接想象到这个视窗区域, # 可以自己拿笔画一画,以便明白 vp <- viewport(x=1/3, y=1/2, width=2/3, height=1) # 进入相对于当前视窗的子视窗(且叫视窗A), # 进入的视窗是其父视窗中vp指定的区域 pushViewport(vp) # pushViewport之后,当前视窗是视窗A, # 在当前视窗里进行作图,先将ggplot图形转为grob # grob即graph object,是能够被grid操作的图形对象 grid.draw(ggplotGrob(g2)) # 也可以在子视窗中添加文字,这里已经到了子视窗里, # x=0且y=1对应的就是左上角那个点,hjust和vjust调整对齐, # hjust=0就是x轴平方向以最左边对齐, # vjust=1就是y轴方向以最上边对齐,设为0.5则是中心点对齐, # 这里我们将这个文字放在当前视窗(A视窗)最左上方 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()
使用cairo_pdf()得到的PDF文件就是已经嵌入了字体的。cairo_pdf()的用法与pdf()的一致,pdf()可能有更多的选项。但一般来说,cairo_pdf()也够用了。具体可以阅读这两个函数的使用手册。
? cairo_pdf ? pdf
使用grDevices包的embedFonts函数。需要先安装Ghostscript。
先告诉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")
本网页已被访问12181次。