为什么 ggplot2 不支持两个 y 轴?
写
在前面
“
大猫终于毕业了!!!
”
emmm……你们是不是还在怀疑为什么关注列表中还有一个这样的up主。在这里和大家汇报一下,大猫前几个月在奋斗自己的博士毕业论文,现在终于通关啦,而且再过十几天就要迎来毕业典礼!所以这个不靠谱的up主现在又可以重启这个公众号啦。
在这里要感谢所有给大猫留言和加大猫微信的读者,和你们的讨论让我受益良多~毕业之后等大猫发毕业照23333
问
题:为什么ggplot2有这个硬伤?
稍微对ggplot2
有所了解的小伙伴都知道,ggplot2
中是无法同一张图中画出两个y
轴的,也即,下面这张图用“正常”的ggplot
是画不出来的(大猫会在最后给出一个奇技淫巧的实现办法):
为什么强大的ggplot2
无法做到这种看似简单的任务呢?毕竟在几乎所有其他的统计软件中,两个y
轴都很好的支持。例如SAS
中,不仅可以画两个y
轴,通过offset
参数,这两个轴还可以拥有不同的起始高度!如下:
是ggplot2
的作者 Hadley 能力有限吗 ?当然不是,实际上,ggplot2
的这个功能缺失是 Hadley 有意为之的。
W
hy ?
StackOverflow 中有一个帖子,名字就叫作“请问如何在一幅图中左侧和右侧各画一个y轴?”如下:
非常幸运的,这个问题得到了 Hadley 本人的回答,他说道:
Hadley: 我认为在同一个图中画两个分开的
y
轴是“具有严重缺陷”的(fundamentally flawed)。具体而言:
- 它们是不可逆的。给定在图中的一点,你无法把它映射回原有的数据空间中。
- 和其他选项相比,它们阅读起来更加困难。具体可以参考“ A Study on Dual-scale Data Charts”这篇文章。
- 它们很容易受人操控、很容易误导他人,因为不同量纲之间的转换是主观的。
- 它们太arbitrary了。为什么是2个轴呢?难道3个、4个不行吗?
Hadley 说的似乎很有道理,并且得到了题主的认可!我们可以看看最终题主接受的这个答案是怎么样的:
有时候客户想要两个
y
轴,但是这样做只会让他们的论证变得千疮百孔。我尊重并喜欢ggplot2
对于“正确作图理念”的坚持。我相信ggplot2
正在向大家普及什么是符合规范的数据可视化技巧。
B
ut ...
但是故事没完!虽然上面这个答案得到了非常多的票数,但也不乏反对的声音,例如下面的这四个评论:
“你(Hadley)能再具体阐释下你的观点吗?我认为把许多独立的变量画在同一个图中是一种特别简洁的方法(a compact way)。这也是一项被许多人所要求,并且广为使用的功能。” “@hadley。你说的大多数我都同意,但是确实有种情况必须要同时用到两个
y
轴:当一个变量拥有两个metric的时候。例如对于温度,我想同时显示摄氏和华氏。” “你(Hadley)的这个回答并不对大家有什么帮助,因为你没有解释什么是“重大缺陷”(fundamental flow)。如果有文献,请你引用起来。” “如果一个绘图包强制用户必须遵守某个规则,那么他就是“重大缺陷”的” “(Hadley)的回答是胡说八道(nonsense)。我被这个他的这个答案还有那么多支持给震惊了。Hadley 严重误解了 API 的设计原则,即任何 API 都应该是可选的。也就是说,我认为,能够同时画两个y
轴是非常有用的功能。”
确实上面的这些批评意见很有道理——我可以不给客户看,但我自己在做数据分析的时候画给自己总行了吧?毕竟很多时候在做时间序列分析的时候,我们需要把多个不同量纲的变量画在同一个图中来快速获得变量之间的联动关系。退一万步讲:我可以不用,但你不能没有。
S
o ...
毕竟广大网友的智慧是无穷的,最终还是有人想出了一套使用ggplo2
来画dual-y-scales的方法。只不过这个办法需要用到gtable
和grid
两个包,而且代码非常非常的长。其中涉及到很多ggplot2
底层的知识,反正大猫自己是没有耐心看完全部代码,只是本着拿来主义直接就用。
由于代码比较复杂,大猫把代码不做讲解直接粘贴到文章中,感兴趣的小伙伴可以根据关键字在 StackOverflow 上搜索相关资料。
- 基础野:细说原码、反码和补码
- JavaScript循环读书笔记
- docker学习(2) mac中docker-machine使用vmware fusion以及配置国内镜像加速
- HttpClient(一)HttpClient抓取网页基本信息
- Httpd运维日志:通过apxs添加模块
- 探索客户端JavaScript
- docker学习(8) 在mac机上搭建私有仓库
- Jsoup(一)Jsoup详解(官方)
- Ajax几种常用模式
- dubbox升级spring到4.x及添加log4j2支持
- CSS魔法堂:Absolute Positioning就这个样
- MongoDB(一)环境搭建与初始配置
- AI进了直播间,这画风666
- CSS布局:水平居中
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 搞了这么多年终于知道接口和抽象类的应用场景了
- css实现页面加载动画
- 用Dota2“最强”算法PPO完成CarPole和四轴飞行器悬浮任务
- 【pyspark】广播变量のdestroy...
- Flink 参数配置和常见参数调优
- 利用InfluxDB+Grafana搭建Flink on YARN作业监控大屏
- Netty之美--零拷贝
- Netty对ThreadLocal的升级
- 【避坑指南】12.2 新功能优化器统计顾问(Optimizer Statistics Advisor)及常见问题和解决方法
- 初试 Open Service Mesh(OSM)
- K8S 生态周报| runc v1.0-rc92 发布
- 聊聊 Python 面试最常被问到的几种设计模式(上)
- 推荐一套个人文档管理系统。
- OpenCV图像读取(imread) 显示(imshow) 保存(imwrite)的冷知识点
- CodeReview实践-Gerrit自动触发JenkinsCI