用ggplot来挑战数据可视化的上限~
ggplot2的图形语法,让数据可视化的形式再也不会受到工具的限制,换句话说,限制你的仅仅是思维,而再也不会是——工具!
今天演示一个曾经造网络上非常火爆的图表案例——经济学人2014年发布的当年年度全球国家政治选举大事件圆环图。
该图的最初版本是这样的:
然后经过各路大牛的仿制,网络上流行的Excel仿制版本是这样的:
该版本出自刘万祥老师的博客(感慨老师的Excel功力之深厚,我等后背只能望图兴叹了!)。
不过在ggplot系统中,该图的制作思路会比较特别,我没有使用纯坐标点来构建,而是用了柱形图+极坐标转换的思路,就是先构建好二维的柱形图和散点图,然后通过极坐标一次性转换得到最终的图表成果。
以下是小编自己通过ggplot2制作的最终圆环图成果:
下面讲述制图过程及代码:
library("ggplot2")
library(Cairo)
library(ggmap)
library(xlsx)
library(lubridate)
library(reshape2)
library(showtext)
library(grid)
library(plyr)
setwd("F:/微信公众号/公众号——数据小魔方/2017年4月/20170403")
导入当年的大事件时间表:
ECOdata<-read.xlsx("ECOCircle.xlsx",sheetName="Sheet1",header=T,encoding="UTF-8",stringsAsFactors=FALSE)
因为大事件分为四种不同类别,而我的平面坐标设置的坐标Y轴最大值是100,原数据中四个事件使用0,1来表示的,所以这里我做了一些转换。
整体思路是当年如果没有任何一种事件发生,则四个事件指标全部为0,否则有一个发生(即指标为1)则改事件指标为99,有两个则依次为99,91,三个则依次为99,91,84,四个则为99,91,84,77。使用一个循环完成数据重塑。
f<-c(99,91,84,77)
for (i in 1:nrow(ECOdata)){
m<-sum(ECOdata[i,3:6]==1)
h<-grep(1,ECOdata[i,3:6])
ECOdata[i,h+2]<-f[1:m]
}
以上这段代码是自己想到的配合本次案例数据特性而写的,对于本案例而言至关重要,但是思路可能通用性不强。
生成2014年的年度时间轴数据(间隔为天),目的是之后根据当年全年天数 综合分配圆周角度(360)。
newdate<-seq(from=as.Date("2014-01-01"),to=as.Date("2014-12-31"),by="1 day")
newmonth<-month(newdate)
mydata<-data.frame(newdate,newmonth);mydata$ID<-1:nrow(mydata)
mydata$Type<-ifelse(mydata$newmonth%%2==0,"A","B")
mydata$Scale<-100
a<-seq(from=90,to=-90,length=181);b<-seq(from=90,to=-90,length=184)
mydata$Circle<-c(a,b)
本段代码主要生成二维坐标轴下12个月份(不同颜色间隔),将奇偶月份分类映射即可。
mynewdata<-merge(mydata,ECOdata,by.x="newdate",by.y="date",all.x=T)
mynewdataone<-melt(na.omit(mynewdata), id.vars =names(mynewdata)[1:7],variable.name = "Class", value.name = "Fact")
将整理好的年度大事件的四分类数据由宽转长(当然如果你不转也是可以作图的,但是你要将散点图层映射四次,而转为长数据之后只需添加一个颜色分类映射参数即可完成所有四分类时间的全部映射过程)
circlemonth<-seq(15,345,length=12)
circlebj<-rep(c(-circlemonth[1:3],rev(circlemonth[1:3])),2)
这里构造的数据适用于添加12个月份的名称标签。
mynewdataoneA<-mynewdataone[mynewdataone$Fact!=0&mynewdataone$ID<180,]
mynewdataoneA<-mynewdataoneA[!duplicated(mynewdataoneA[,1]),]
mynewdataoneB<-mynewdataone[mynewdataone$Fact!=0&mynewdataone$ID>180,]
mynewdataoneB<-mynewdataoneB[!duplicated(mynewdataoneB[,1]),]
以上数据是为了构造当前大事件的名称,为了图调整好角度问题以及标签重复问题,这里将上半年和下半年的大事件名称分开添加,即运行两次geom_text图层函数。
作图函数:
font.add("myfont","msyhl.ttc")
CairoPNG(file="ECOCircle.png",width=1000,height=1050)
showtext.begin()
ggplot()+
geom_bar(data=mydata[mydata$Type=="A",],aes(x=ID,y=Scale),stat="identity",width=1,fill="#ECEDD1",col="#ECEDD1")+
geom_bar(data=mydata[mydata$Type=="B",],aes(x=ID,y=Scale),stat="identity",width=1,fill="#DFE0B1",col="#DFE0B1")+
geom_point(data=mynewdataone[mynewdataone$Fact!=0,],aes(x=ID,y=Fact,fill=Class),size=5,shape=21,col="white")+
ylim(-90,130)+
scale_fill_manual(limits=c("Legislative","Referendum","President","Primary"),values=c("#93A299","#CF543F","#B5AE53","#86825B"),labels=c("立法","公投","总统","初选"))+
geom_text(data=mynewdataoneA,aes(x=ID,y=max(mynewdataoneA$Fact)+20,label=State,angle=Circle),family="sans",size=5,hjust=0)+
geom_text(data=mynewdataoneB,aes(x=ID,y=max(mynewdataoneB$Fact)+20,label=State,angle=Circle),family="sans",size=5,hjust=1)+
geom_text(data=NULL,aes(x=circlemonth,y=30,label=paste0(1:12,"月"),angle=circlebj),family="myfont",size=7,hjust=.5,vjust=.5)+
guides(colour=guide_legend(title=NULL))+
coord_polar(theta="x")+
labs(title="2014年全球选举事件图",subtitle="这是一幅用心良苦的好图",caption="Source:Economics\nMake:EasyCharts",x="",y="",fill="")+
theme(
text=element_text(family="myfont"),
axis.text=element_blank(),
axis.ticks=element_blank(),
panel.background=element_blank(),
panel.grid=element_blank(),
panel.border=element_blank(),
legend.position=c(0.03,0.92),
legend.background=element_blank(),
legend.key=element_blank(),
legend.key.size=unit(1.55,'cm'),
legend.key.height=unit(1.2,'cm'),
legend.text=element_text(size=20,hjust=3,vjust=3,face="bold"),
plot.background=element_blank(),
plot.title=element_text(size=50),
plot.subtitle=element_text(size=35),
plot.caption=element_text(size=25,hjust=0),
plot.margin=unit(c(.5,.5,.5,.5),"lines"),
)
showtext.end()
dev.off()
大功告成。
作者简介:
欢迎关注魔方学院QQ群