R|ggplot2(二)|覆盖柱状图各种需求
目录
- 赋权和所有数据类型
- 分组作图与aes
- 隐含参数解释
- 分组条形图,三种展现形式,如不同组在一根柱子上堆叠还是并排放置
- 柱子高低顺序排列
- 正负条形图
- 横向条形图
- 饼图(这里没有专门画饼图的函数,饼图是柱状图的一种特例)
- 分面
- 柱子上标注文字
本文使用R自带数据集
mpg
。横轴使用
class
列,分组使用
cyl
列,数量使用
dyspl
列
赋权和所有数据类型
library(ggplot2)
ggplot(mpg,aes(x=class)) + geom_bar() # 函数内部自己数class中每种类别的个数
ggplot(mpg,aes(x=class,y=displ)) + geom_bar(stat="identity") # 使用第二种数据类型,接受两个参数
ggplot(mpg,aes(x=class)) + geom_bar(aes(weight=displ)) # 使用displ对class赋权
ggplot(mpg,aes(x=class)) + geom_bar(aes(weight=rep(1,length(class)))) # 第一种相当于赋权全为1
我们会发现14图形相同,23图形相同。这里涉及到的数据样式和之前说的有所不同。
之前我们说画柱状图可以接受两种数据样式
- 一种是 名字罗列
- 一种是 名字-频数
本文第一种做法便是所谓的名字罗列,函数内部会自动数出每一类有多少个,柱子有多高。第二种的数据却不太一样,因为 名字-频数 是已经统计出每一个名字总共的频数,而这个数据集展现的名字却有重复的,这其实相当于是 第三种数据样式 ,我暂且称之为赋权样式。
- 赋权样式可以当成 名字-频数 来处理,函数内部会自动将相同的名字合并。
- 也可以当成 名字罗列 的情况处理,同时指定权重,这就是第三个代码显示的方法。第四个图表示第一种只是查数,代表赋权全为1。
分组作图与aes
上一篇文章中,我们已经简单设置了柱状图中的
col fill width
等参数,它们都是直接放在geom_bar里面的,而有一些参数像x和y则是放在又套了一层aes,现在我们要对这个现象进行解释。
我们从分组作图入手,下面使用fill的5种尝试,3种不报错的形式中,只有最后一种实现了分组作图,我们来看一看他们之间的区别
# ggplot(mpg,aes(x=class)) + geom_bar(fill=cyl) # 报错
# ggplot(mpg,aes(x=class)) + geom_bar(fill=mpg$cyl) # 报错
ggplot(mpg,aes(x=class)) + geom_bar(fill=1:7) # 图一
ggplot(mpg,aes(x=class)) + geom_bar(aes(fill=cyl)) # 图二 未成功完成分组
ggplot(mpg,aes(x=class)) + geom_bar(aes(fill=factor(cyl))) # 图三 按照cyl分组
# 其他参数
ggplot(mpg,aes(x=class)) + geom_bar(aes(color=factor(cyl))) # 图四
ggplot(mpg,aes(x=class)) + geom_bar(aes(alpha=factor(cyl)))
上面的结果说明了以下几个问题(按顺序一个一个看)
- 第一次报错,在aes外面无法找到cyl对象,说明只有放在aes中,才能直接引用数据集的列名,而不需要使用$
- 第二行,加了$仍然报错,是因为在aes外面的fill指向的是那7根柱子,所以应该接一个长度为7的向量。只有在aes中,数据才能和原数据框的每一行相对应
- 知道了这个,读者就应该明白了上一部分赋权时,weight参数放在aes里面的原因了。
- 第三行使用了长度为7的颜色向量,实现了每根柱子颜色不一样,但是无法按照cyl分组展示
- 第四行没有实现分组,说明fill想要接分类变量(离散变量),需要是因子型数据
- 第五行成功分组显示,按照cyl值以颜色的形式,对样本进行分类,展示在图片中
- 第六七行表示除了fill,color和alpha等都可以接分组变量实现分组展示,只是区分展示方式不一样。如果是画点图的话,对应起来可以用颜色、大小、形状等来区分,也都是用相应参数接分类变量实现的
在这里,我们需要更深入地了解
aes
的本质——数据到图形的映射,或者说对应关系。了解了这个本质,你会感叹ggplot2包的设计理念是如此优雅!
我们一直将要用于作图的
x y
放到
aes
里,其实本质上和
fill
的使用是一样的。
aes
中列向参数的赋值,体现为数据到图形的映射。拿散点图来举例子,两行数据,x值不同,在图中的表示就是在x轴的位置不同,y也是同理;而
color
也是同理,
color
参数对应变量值不同,反映在图中就是点的颜色不同。
图形是为了描述数据而存在的,不相重合的点表示数据本身的差异,最常见的
x y
是通过空间位置来区分数据,而我们当然也可以通过颜色来区分数据,即产生了上面的这种用法。
除此之外,还需要强调一点。R语言设计上的数据类型,其实也是和真实数据相对应的
-
连续型数据——
numeric
数据类型 -
文本数据——
character
数据类型 -
连续型数据——
factor
数据类型
希望大家提高对
factor
数据类型的重视,将用于分类的离散型数据,全部转化为
factor
类型,或者说只使用
factor
进行分类用途,不要和
character
类型混淆。
接下来看下面这段代码
ggplot(mpg,aes(x=class)) + geom_bar(fill="blue") # 所有柱子显示蓝色
ggplot(mpg,aes(x=class)) + geom_bar(aes(fill="blue")) # 所有柱子显示粉红色
ggplot(mpg,aes(x=class)) + geom_bar(aes(fill="a")) # 所有柱子显示粉红色
三个函数反映了:在aes里面和外面定义颜色的区别。在aes外面定义蓝色就是蓝色,在aes里面其实
"blue"
被认为是一个分组的因子了,它本身是什么已经不重要了,它只是表示,所有柱子都被认为分到了同一组,使用同一个颜色,而颜色则是使用默认的
我们可以看出,我们没有指定具体的颜色,只是告诉函数,按照这个变量来分类,就会使用默认的颜色,同时生成图例。这样默认生成的图形本身就很大气美观,省去了很多优化的步骤,这是ggplot2包的一大优势
隐含参数解释
当我们使用 罗列名字 的数据来作图的时候,其实函数内部计算出了每个名字的数量,这样才能代表每个柱子的高度,这个数值不是我们输进去的,但是我们可以引用它,但是只有在aes中使用才有效
柱状图的参数有以下两个
-
..count..
每根柱子多高 -
..prop..
每根短柱子占整个长柱子的百分比,如果没进行分组,则每根柱子对应的..prop..
都是1
ggplot(mpg,aes(x=class)) + geom_bar() # 只接一个参数相当于第二个参数是..count..
ggplot(mpg,aes(x=class,y=..count..)) + geom_bar()
ggplot(mpg,aes(x=class,y=..prop..)) + geom_bar() # 每根柱子的prop都是1
# ggplot(mpg,aes(x=class)) + geom_bar(aes(fill=..count..)) # 连续性变量与后面因子型变量,看差别
ggplot(mpg,aes(x=class)) + geom_bar(aes(fill=factor(..count..)))
ggplot(mpg,aes(x=class)) + geom_bar(aes(group=factor(cyl),fill=..prop..))
(为了展示代码真实结果,图有点丑,比如坐标轴标注重叠可以用之后学到的内容再进行调整)
后两个例子解释一下
-
因为
..count..
数的是每根长柱子的高度,所以每个长柱子的下分短柱子对应的fill值没有差异,差异体现在长柱子与长柱子之间,所以结果便是每根柱子颜色不一样。 - 可以看到分类时用连续性变量,则使用的颜色是渐变的,而使用因子型变量则为分类颜色
-
第二个例子为了让
..prop..
不再为1,需要分组,group接分组变量可以实现分组,但是它不像fill表示颜色,alpha表示透明度,它没有展示的途径,所以一般即使用group分组,做出图形也看不出来组别之间的区别。但是对于..prop..
确是有影响的。这个例子只是为了展示..prop..
这个变量,真正使用的时候不会画这样的图
分组条形图,三种展现形式
上面我们看到的分组条形图中,每根柱子上的不同组别短柱子是堆叠起来的,我们还可以实现并排放置和填充比例展示,只要使用position参数调整
ggplot(mpg,aes(x=class)) + geom_bar(aes(fill=factor(cyl)),position="stack") # position默认,堆叠
ggplot(mpg,aes(x=class)) + geom_bar(aes(fill=factor(cyl)),position="dodge") # 并排放置
ggplot(mpg,aes(x=class)) + geom_bar(aes(fill=factor(cyl)),position="fill") # 填充显示比例
柱子高低顺序排列
我们使用两种方法
- 自己编写函数,根据factor原理来进行排序
- 使用包中自带的reorder函数
1.首先我们要理解ggplot2包中factor的使用
其实前面我们在画柱状图时,使用的变量并不规范,当横坐标是离散型变量时,像柱状图这样一根一根柱子这样,x参数接的应该是一个因子型数据,我们直接x=name在这里没显示出什么错误,但是看看如下例子
aa <- data.frame(a=sample(1:7,30,replace=T),
b=sample(1:3,30,replace=T))
ggplot(aa,aes(x=a)) + geom_bar()
ggplot(aa,aes(x=factor(a))) + geom_bar()
ggplot(aa,aes(x=factor(a))) + geom_bar(aes(fill=b))
ggplot(aa,aes(x=factor(a))) + geom_bar(aes(fill=factor(b)))
我们可以看出x没有用factor的时候,横轴没有把所有的标签全标上,这表示把横轴当成连续性变量来看了,所以只标了一部分标签以表示大小关系。
fill也是作为离散分类变量,也应该接一个factor,我们可以看到不加时,图例是一个连续性渐变颜色的形式,这无法达到我们分类的要求。
2.使用自己编写的函数
ggplot(mpg,aes(x=factor(class))) + geom_bar()
先看这张图,默认画图时横轴标度排列顺序为,按照首字母排序,这和因子型数据有关
mpg$class[1:5]
# [1] "compact" "compact" "compact" "compact" "compact"
fclass <- factor(mpg$class)
fclass[1:5]
# [1] compact compact compact compact compact
# Levels: 2seater compact midsize minivan pickup subcompact suv
这里我们可以看出,转化为因子型之后数据的level的排列方式,不是根据元素出现的前后顺序来排列的(否则第一根柱子应该是compact),而是按照首字母排列的(2seater)。
所以ggplot2柱状图横轴排列顺序的本质就是根据factor的levels,所以我们只要改变这个factor的level,就可以调整柱子排列顺序。我们通过定义一个函数来实现level的改变
reorder_size <- function(x) {
factor(x, levels = names(sort(table(x))))
ggplot(mpg, aes(reorder_size(class))) + geom_bar() # 柱子从低到高排列
这样柱子就从低向高排列了
3.使用包中的reorder函数
x <- sample(letters[1:3],30,replace = T)
y <- 1:30
reorder(x,y,sum)
我们可以看到reorder函数接3个参数,最后返回的向量是一个因子型向量,其主体还是x,只是改变了levels,改变的原则是:按照x对y进行分组,对每一组组成的向量计算后面的函数,最后根据计算结果从小到大指定x中元素的levels
使用这个函数来画条形图的原理:根据class对一个全是1的向量分组求和(相当于计算了class中每一个元素出现的个数),再根据求和结果指定levels
ggplot(mpg,aes(reorder(class,rep(1,length(class)),sum)))+geom_bar() # 作图结果和上面相同
正负条形图
只要数据是负数,就能画出往下方的条形图
d <- data.frame(a=letters[1:7], b=c(4,-6,5,-4,-3,6,4))
ggplot(d,aes(a,b)) + geom_bar(stat="identity")
ggplot(d,aes(a,b)) + geom_bar(aes(fill=factor((b>0)+1)),stat="identity")
横向条形图
ggplot(mpg,aes(x=class)) + geom_bar() + coord_flip()
coord_flip 表示横纵坐标位置互换
饼图
(这里没有专门画饼图的函数,饼图是柱状图的一种特例)
ggplot(mpg, aes(class))+geom_bar()+coord_polar(theta = "y")
ggplot(mpg, aes(class))+geom_bar()+coord_polar(theta = "x")
ggplot(mpg, aes(class))+geom_bar(aes(fill=drv))+coord_polar(theta = "y")
ggplot(mpg, aes(class))+geom_bar(aes(fill=drv))+coord_polar(theta = "x")
我们可以看出,这里所谓的饼图,只是把坐标轴做了圆形扭曲,只是theta参数接”x”和”y”不一样,扭曲的方式不一样
-
theta="y"
时,是把y轴方向扭曲了,柱子都变成了弯的 -
theta="x"
时,是把x轴方向扭曲了,柱子都从同一个中心出发
加上颜色分类变量之后,也只是在相应柱子上分了组
有的读者可能会疑惑,这里要讲的不是饼图吗,这里展示的都只是和圆形搭边,却不是正常我们看到的饼图。
其实正常形式的饼图是
theta="y"
时的一种特例:只有一根柱子
所以当我们拿到数据的时候,想要画饼图,对数据的思考和柱状图是不一样的。这一点和基础作图函数是不一样的。
比如还是class这一列
-
基础函数中,画饼图和画柱状图的思路是一样的,让
x=class
-
ggplot2的饼图中,则是
fill=class
,为了保持一根柱子,x=1
ggplot(mpg, aes(1, fill=class))+geom_bar()+coord_polar(theta = "y")
分面
分面条形图其实就是分组条形图的另外一种展现形式,也是拿用来分组的变量将数据拆分,为了更方便地使用分面功能,这里不局限在柱状图,我们使用点图来完成。
两个函数,两种思路
-
facet_grid
函数,按照一个指标分成m个,按照另一个指标分成n个,排列方式就是m*n式 -
facet_wrap
函数的思路和matrix创建的思路相似,现有一串,再指定每行放几个
看下面的帮助文档中的例子就大概懂了,其他参数细节可以自己查看帮助文档学习
p <- ggplot(mpg, aes(displ, cty)) + geom_point()
ggplot(mpg, aes(displ, cty)) + geom_point(aes(color=factor(cyl)))
p + facet_grid(. ~ cyl)
p + facet_grid(drv ~ .)
p + facet_grid(drv ~ cyl)
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_wrap(~class)
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_wrap(~class, nrow = 4)
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_wrap(~ cyl + drv)
ggplot(mpg, aes(displ, hwy)) +
geom_point() +
facet_wrap(c("cyl", "drv"))
柱子上标注文字
下面我们通过两个例子来解释如何在柱状图柱子上方标数字
df <- data.frame(
x = factor(c(1, 1, 2, 2)),
y = c(1, 3, 2, 1),
grp = c("a", "b", "a", "b")
ggplot(df, aes(x)) + geom_bar() +
geom_text(aes(label=..count..),