ggplot2双坐标轴的解决方案

本来没有打算写这一篇的,因为在一幅图表中使用双坐标轴确实不是一个很好地习惯,无论是信息传递的效率还是数据表达的准确性而言。

但是最近有好几个小伙伴儿跟我咨询关于ggplot2的次坐标轴问题,平时的一些业务分析中,有些场景出于数据呈现的需要,或者阅读习惯等,往往需要在一幅图中呈现两个量级不等的坐标。

所以我觉得这一篇推送很有必要,确实在最新版的ggplot2(ggplot 2.2.0以上版本)中,已经加入了次坐标轴参数,通过这个次坐标轴的转换,我们可以模拟出不同数量级的次坐标轴效果。

因为其中用到了英文月份简写,这里对系统日期显示格式做了特殊设置:

lct <- Sys.getlocale("LC_TIME")  
#备份本地默认日期显示格式

Sys.setlocale("LC_TIME", "C")    
#指定标准日期显示格式

Sys.setlocale("LC_TIME",lct)     
#这一句是恢复默认系统日期显示格式
#(记得要在使用完下面的month函数之后再运行这一句,否则月份返回的是中文)

加载包:
library("lubridate")
library("ggplot2")
library("scales")
library("magrittr")
library("tidyr")

生成作图数据

作图数据1——单序列柱形图

data1 <- data.frame(
      Month = seq(from = as.Date('2017-01-01'),to=as.Date('2017-06-01'),by='1 month') %>% month(label=TRUE),
      Value = runif(6,10,50) %>% round()
     )
  Month Value
1   Jan    39
2   Feb    38
3   Mar    50
4   Apr    33
5   May    18
6   Jun    49

作图数据2——二分类折线图(带散点)

data2 <- data.frame(
      Month = seq(from = as.Date('2017-01-01'),to=as.Date('2017-06-01'),by='1 month') %>% month(label=TRUE),
      Categroy1 = runif(6,0.1,0.5) %>% round(2),
      Categroy2 = runif(6,0.1,0.5) %>% round(2)
     ) %>% gather(Category,Value,-1)
   Month  Category Value
1    Jan Categroy1  0.49
2    Feb Categroy1  0.23
3    Mar Categroy1  0.10
4    Apr Categroy1  0.38
5    May Categroy1  0.34
6    Jun Categroy1  0.13
7    Jan Categroy2  0.48
8    Feb Categroy2  0.38
9    Mar Categroy2  0.48
10   Apr Categroy2  0.15
11   May Categroy2  0.40
12   Jun Categroy2  0.16

以下是整个过程代码,基本是司空见惯的内容,这里不做过多解释,仅提示其中两处重点,注意第二行geom_line内的y参数赋值以及第四行的scale_y_continuous语句:

ggplot() +
  geom_col( data = data1,aes(x = Month,y = Value),fill="#6794a7") +
  geom_line(data = data2,aes(x = Month,y = rescale(Value,c(0,55)),colour=Category,group=Category),size=1.5) +
  geom_point(data = data2,aes(x = Month,y = rescale(Value,c(0,55)),colour=Category),shape=21,fill="white",size=4)+
  scale_y_continuous(breaks=pretty_breaks(5),sec.axis = sec_axis( ~rescale(.,c(0,0.5)),name = "Categroy",labels=sprintf("%d%%",(0:5)*10)))+
  scale_color_manual(label = c("Categroy1", "Categroy2"),values = c("#ee8f71","#C10534")) +
  labs(
       title="This is a Title!",
       subtitle="This is a Subtitle",
       caption="This is a Caption"
     )+
  theme_minimal(base_size=16) %+replace% 
  theme(
  plot.caption = element_text(hjust=0),
  plot.margin = unit(c(1,0.5,1,0.5), "lines")
  )

这段代码与我们经常用的有两点不同:

第一次自定义映射——折线度量数据的映射转换:

geom_line(geom_point,因为点图是附属于折线图,仅做修饰之用,这里只重点说折线图层)中的y参数指定的对象使用了一个统计变换函数,rescale函数其实很好理解,就是将一个数值向量按照给定的另一个数值向量的极差(range),等比例标准化。

如果你知道如何将一组向量按照0~1标准化的话,那么这个函数就不难理解 ,其实就是将标准化的尺度给了一个自定义的范围。

因为在ggplot2标度系统中,不容许在一个图形中出现两个量级不等的标度(一山不容二虎),但是想要提供度量不等的次坐标轴,折中的方法就是,将次坐标轴的所有量级按照主坐标轴的量级进行缩放(如果次坐标轴量级大于主坐标轴,那么就是等比例放大,如果比主坐标轴量级大则缩小)。

针对本例而言,就是将折线图的数据源量级(0.0~0.5)放大到0~35的区间上,所有的单个指标的缩放比例都是相同的,这样你在图上就不会感受到太大的视角误差。

value1<-data1$Value
value21 <- data2[data2$Category == 'Categroy1',"Value"]
value22 <- data2[data2$Category == 'Categroy2',"Value"]

mydata <- data.frame(value1,value21,value22)

mydata$value31 <- rescale(mydata$value21,c(0,50))
mydata$value32 <- rescale(mydata$value22,c(0,50))

  value1 value21 value22   value31   value32
1     39    0.49    0.48 50.000000 50.000000
2     38    0.23    0.38 16.666667 34.848485
3     50    0.10    0.48  0.000000 50.000000
4     33    0.38    0.15 35.897436  0.000000
5     18    0.34    0.40 30.769231 37.878788
6     49    0.13    0.16  3.846154  1.515152

这是最终的折现结果,在geom_line中使用rescale函数实际上就是做的这种度量重新自定义映射的过程。

第二次自定义映射——次坐标轴刻度标签转换:

仅仅做以上步骤还不够,因为这只能保障次坐标轴的数据点位置相对于整个坐标系统而言,不会出现太大的视觉误差,但是现在的问题是这个图形对象中有两套不同的度量,所以必须声明不同的y轴度量标准,也就是y轴的刻度线及刻度标签,刻度标签的定义就是本案例的第二个重点,它仍然是通过rescale函数进行了一次度量的重新映射。

不过这次映射的过程刚好是相反的操作,即将之前已经被标准化到0~50区间内的原始度量标签通过rescale函数再次标准化到0~0.5区间内,这样保障显示在次坐标轴上的度量是符合原始数据极差范围呢。

说的有些拗口了,实际上以上过程思路很简单,就是先将数据映射到正确的位置,然后将词作败欧洲刻度线再按照真实极差进行分布,一虚一实,正好达到了模拟效果。

scale_y_continuous(
          breaks=pretty_breaks(5),                   #创建主坐标轴的刻度区间(这里是5个区间6个刻度点)         
          sec.axis = sec_axis( ~rescale(.,c(0,0.5)), #对次坐标轴刻度标签的二次映射(极差范围指定真实极差即可)  
          name = "Categroy",                         #次坐标轴名称
          labels=sprintf("%d%%",(0:5)*10))           #刻度标签显示格式(这里是百分号)
          )

思路大体上就是这样子,希望这一篇文章可以帮到大家!

往期案例数据请移步本人GitHub: https://github.com/ljtyduyu/DataWarehouse/tree/master/File

原文发布于微信公众号 - 数据小魔方(datamofang)

原文发表时间:2017-12-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AI科技大本营的专栏

无人驾驶汽车系统入门:基于深度学习的实时激光雷达点云目标检测及ROS实现

近年来,随着深度学习在图像视觉领域的发展,一类基于单纯的深度学习模型的点云目标检测方法被提出和应用,本文将详细介绍其中一种模型——SqueezeSeg,并且使用...

31810
来自专栏玉树芝兰

如何用 Python 和循环神经网络(RNN)做中文文本分类?

本文为你展示,如何使用 fasttext 词嵌入预训练模型和循环神经网络(RNN), 在 Keras 深度学习框架上对中文评论信息进行情感分类。

27040
来自专栏Python中文社区

基于RNN自动生成古诗

專 欄 ❈ 作者:yonggege,Python中文社区专栏作者 GitHub地址:https://github.com/wzyonggege ❈ 0. ch...

30850
来自专栏CreateAMind

代码解读Top-down Neural Attention by Excitation Backprop及模型架构图

41930
来自专栏机器之心

GPU捉襟见肘还想训练大批量模型?谁说不可以

2018 年的大部分时间我都在试图训练神经网络时克服 GPU 极限。无论是在含有 1.5 亿个参数的语言模型(如 OpenAI 的大型生成预训练 Transfo...

43630
来自专栏机器学习算法与理论

调用Dlib库进行人脸关键点标记

       昨天调试了人脸识别(classifier_webcam)这个程序,效果不错,响应速度也挺快。按照http://blog.csdn.net/u011...

50590
来自专栏鸿的学习笔记

在hadoop2.0上实现深度学习

我承认我又偷懒了,只是大概写了下提纲,和完成了第一章节的部分写作。不睡午觉的恶果啊,原本已经写好草稿,讲讲语言和信息的关系,结果,实在是回家后好困。

11520
来自专栏新智元

谷歌发布 TensorFlow Fold,支持动态计算图,GPU 增速 100 倍

【新智元导读】谷歌官方博客最新发布TensorFlow Fold,通过为每个输入构建单独的计算图解决由于输入的大小和结构不同导致的问题。此外,通过动态批处理,实...

39990
来自专栏深度学习入门与实践

【深度学习系列】PaddlePaddle可视化之VisualDL

  上篇文章我们讲了如何对模型进行可视化,用的keras手动绘图输出CNN训练的中途结果,本篇文章将讲述如何用PaddlePaddle新开源的VisualDL来...

45290
来自专栏about云

TensorFlow ML cookbook 第一章7、8节 实现激活功能和使用数据源

问题导读: 1、TensorFlow中有哪些激活函数? 2、如何运行激活函数? 3、TensorFlow有哪些数据源? 4、如何获得及使用数据源? 上...

51580

扫码关注云+社区

领取腾讯云代金券