• R语言
  • 怎样使用 ggplot2 绘制双 Y 轴的图形

楼主先试着绘制一个折柱混合图,然而不知道为撒主Y轴的范围跟次Y轴一样,代码如下:

library(ggplot2)
library(data.table)

data(diamonds)

diamonds.new <- as.data.table(diamonds)
diamonds.new2 <-
  diamonds.new[, by = .(cut), .(y1 = median(carat), y2 = median(price))]

ggplot(data = diamonds.new2, aes(x = cut)) +
  geom_bar(aes(y = y1), stat = 'identity', position = 'dodge') +
  geom_line(aes(y = y2), group = 1) +
  scale_y_continuous(
    name = "主Y轴",
    sec.axis = sec_axis(~ . , name = "次Y轴")
  )

接着又试着干脆画两个双Y轴的柱状图,然后也不知为撒,只剩下一种柱子,代码如下:

ggplot(data = diamonds.new2, aes(x = cut)) +
  geom_bar(aes(y = y1, fill = 'red'), stat = 'identity', position = 'dodge') +
  geom_bar(aes(y = y2, fill = 'green'), stat = 'identity', position = 'dodge') +
  scale_y_continuous(name = "主Y轴", sec.axis = sec_axis( ~ . , name = "次Y轴"))

第二个Y轴的值太小了,需要参考主Y轴做一定的变换,比如这样

coeff <- max(diamonds.new2$y1) / max(diamonds.new2$y2)

ggplot(data = diamonds.new2, aes(x = cut)) +
  geom_bar(aes(y = y1), stat = 'identity', position = 'dodge') +
  geom_line(aes(y = y2 * coeff), group = 1) +
  scale_y_continuous(
    name = "主Y轴",
    sec.axis = sec_axis(~ . / coeff, name = "次Y轴")
  )

    第二个双柱的图,position = 'dodge'要求数据是长表的格式,两个geom_bar加上dodge并不能使柱子错开,具体可以参考下position_dodge的帮助文档


    试了下,不用dodge,用justwidth参数控制也可以

    ggplot(data = diamonds.new2, aes(x = cut)) +
      geom_bar(aes(y = y1), fill = 'red', just = -0.1, width = 0.3, stat = 'identity') +
      geom_bar(aes(y = y2 * coeff), fill = 'green', just = 1.1, width = 0.3, stat = 'identity') +
      scale_y_continuous(name = "主Y轴", sec.axis = sec_axis( ~ . / coeff, name = "次Y轴"))

      meeeeeeeeo
      啊,谢谢你,我来试试。话说,ggplot2 的文档实在太长了,函数又多,真是看得一头雾水。

        yuanfan 函数又多,真是看得一头雾水

        我写了个 ggplot2 入门 ,入门之后,就不会雾水了。至于,实际工作中需要的各种自定义不难的,ggplot2 资源十分丰富,经过搜索、查找、修改,我感觉绝大部分都可以满足。

          meeeeeeeeo
          话说关于对主Y轴和次Y轴坐标轴范围的变换,为撒不能改成下面这样呢?

          coeff <- max(diamonds.new2$y2) / max(diamonds.new2$y1)
          
          ggplot(data = diamonds.new2, aes(x = cut)) +
            geom_bar(aes(y = y1), stat = 'identity', position = 'dodge') +
            geom_line(aes(y = y2 * coeff), group = 1) +
            scale_y_continuous(name = "主Y轴",
                               sec.axis = sec_axis( ~ . * coeff, name = "次Y轴"))

          我以为在sec.axis = sec_axis( ~ . * coeff, name = "次Y轴")这里面的这部分~ . * coeff,这个.是指主 Y 轴的范围,那么除以一个极小的数不就等于乘以一个很大的数吗?

          俺找到原因了,如下是正常的。但是不明白为撒画图的时候要把次 Y 轴的数据先缩小到跟主 Y 轴一个范围,设置具体坐标轴范围时又重新放大?

          coeff <- max(diamonds.new2$y2) / max(diamonds.new2$y1)
          
          ggplot(data = diamonds.new2, aes(x = cut)) +
            geom_bar(aes(y = y1), stat = 'identity', position = 'dodge') +
            geom_line(aes(y = y2 / coeff), group = 1) +
            scale_y_continuous(name = "主Y轴",
                               sec.axis = sec_axis( ~ . * coeff, name = "次Y轴"))

          Cloud2016
          我今天还翻了翻你写的 ggplot2 食谱和作图经验,我现在学习 ggplot2 就像两年前学 echarts4r 一样从绘制柱状图开始,令我雾水的地方的在于,echarts4r 把图形分成了各种组件,比如修改 X Y 轴就是在e_x_axise_y_axix里面改参数,修改图例、标题就是在e_titlee_legend里面改参数, 操作起来就像是一个西瓜劈两半,两半互相之间没有关联,挨个啃即可;而 ggplot2 把图形的元素怎么划分的规律我还没搞明白,感觉 echarts4r 里面的组件概念换到 ggplot2 里面互相之间有关联,比如与坐标轴相关的函数就有好几个,每个函数只负责一部分。

            yuanfan 而 ggplot2 把图形的元素怎么划分的规律我还没搞明白

            就是一个一个的图层,把基础的搞明白,其它的很类似,多用用就会感受到这种规律了。

              Cloud2016
              另外,其实我学 ggplot2 的时候并不觉得难,就是函数多,感到繁杂无头绪,还没有梳理出来一条清晰的学习线路。而且选择学 ggplot2 是因为列出了好几个想学的,挑了认为最容易入门的,反正学到后面一定会由量变带来质变的……

              Cloud2016
              俺就是觉得图层的概念太抽象了,很容易误以为像是一层层图形元素的叠加。

                yuanfan 不知道你有没有用过 PS? 我当年是先学的 PS, 后接触的 ggplot2,因为图层的概念是相通的,所以我当时理解起来中间没有沟壑,只是,函数不熟悉。一层加数据、一层上色、一层加标签、一层调刻度等等,按一定顺序叠加在一起就行了。当然,每一层里面还有图形元素和数据的映射。

                  Cloud2016
                  没有接触过 PS,但是知道 PS 是指 photeshop,当年我大学室友学过,我一直很纳闷为撒统计学的学生会想到学 PS。

                  你安利我看的 ggplot2 入门,我看完了,关于7.2标签这小节,我发现了一个细节,但是 github 登不上去了,没法给你留言,记在这里。因为我刚还一边学一边写笔记,所以下面就直接复制粘贴刚写好的。

                  • 使用labs()函数来同时设置主标题、坐标轴标签、图例标题等的文字内容,各项位置都默认固定。需要注意的是如果在aes()中设定了 fill 参数,那么 labs() 函数中设定图例标题就用 fill 参数,同样如果在aes()中设定了 color 参数,那么 labs() 函数中设定图例标题就用 color 参数。
                  ggplot(data = diamonds) + geom_bar(mapping = aes(x = cut, fill = cut), stat = 'count') +
                    labs(
                      title = '图形主标题',
                      subtitle = '图形副标题',
                      caption = '图形备注',
                      tag = '不知道干撒用的标签',
                      x = 'x轴标题',
                      y = 'y轴标题',
                      fill = '图例标题')

                  另外,我想明白为撒画双 Y 轴的时候要对次 Y 轴做变形了,因为 ggplot2 的坐标系受限制,无法像这里税收弹性章节下的第一个图一样,画出来多个Y轴的图。

                    yuanfan 统计学的学生会想到学 PS。

                    那个时候,我还没学统计,读本科的时候玩的 PS 就是觉得很酷,盗版软件最初也是学长给的。后来,在微软教育先锋商城(现在没有了)买了正版,学生身份验证后买很便宜。

                    双轴图这种,echarts4r 或 plotly 这种画交互图的软件包会比较方便,而且因为动态,效果好。

                    双轴图是实际上是多图叠加,我不推荐在用静态图展示,容易混淆。

                    3-D bar plots are an abomination. Just because Excel can do them doesn’t mean you should. (Dismount pulpit).

                    — Berton Gunter
                    出自 https://stat.ethz.ch/pipermail/r-help/2007-October/142420.html

                    引用 Berton Gunter 的话说,就是有的复杂合成图在特定环境下是适合交互方式展示,而我们并不需要/应该将它们用静态的绘图工具画出来。尽管 ggplot2 可以画,但并不意味着应该画。如果是做练习,好玩,另当别论。

                      yuanfan 双轴完全独立的ggplot2也可以画,但是对这种图我记得Hadley曾经明确表示过反对,并且认为这种图易于拿来误导人,所以ggplot2包里一直没有直接地给出画法函数。甚至这种双轴有明确变换关系的,’sec_axis’也是后来才加入的。

                        我所认为 ggplot2 画双轴或如下多轴图不如 echarts4r 方便的原因是,在 ggplot2 里面画柱状图或条形图是在一个直角坐标系中画,而这个直角坐标系的 X、Y 轴是固定的,因此绘制双轴时,由于主 Y 轴固定,如果次 Y 轴跟主 Y 轴范围差异很大的话就需要将次 Y 轴的数据变形。而 echarts4r 本身是为交互诞生,它的坐标系和多个 Y 轴是互相独立的,因为指定的每个 Y轴可以随意改变范围,甚至把Y轴相对整个图形容器挪开。

                        我绘制上面的图时,是想展示尽管税收收入、M1、M2还有 GDP 的取值范围不一样,但是增长趋势是接近的。

                        fenguoerbian 俺明白了,原来 ggplot2 的一些特点来源于作者的偏好。

                          Cloud2016
                          我觉得你把双 Y 轴图跟 3D 柱状图并列,并且得出不“应该画”的结论是跑偏了。

                          yuanfan

                          "增长趋势接近“……既然多个Y轴是独立的,为什么不把M2的坐标轴最大值放到600, M1坐标轴最大值放到35,然后税收的坐标轴最大值放到180呢?得到的图像还会显示出”增长趋势接近“吗?现在图像上相似的增长趋势,真的不是更多来自于选择了这样的坐标轴范围吗。

                          多独立Y轴本身就是有这样容易误导/操纵数据直觉的缺陷,类似的讨论可以找到不少

                          https://blog.datawrapper.de/dualaxis/

                          https://stackoverflow.com/questions/3099219/ggplot-with-2-y-axes-on-each-side-and-different-scales

                          整个画布上,每个数据点都有自己唯一的x,y坐标,只是每个数据点选择的展现形式不同(可能是画不同形状的点,可能是向着某个坐标轴画柱子)。所以在你的画图代码中,y1y2代表的纵坐标值被认为是在同一个尺度上的。而目前ggplot2采用的sec_axis,你可以理解为增加的坐标轴只是用来辅助显示的,所以要求相对于原始坐标轴有明确的一一对应的线性变换。

                          那么在后面的代码中,你看起来做的是把两者调整到同样的数值范围上使得图片看起来不像原先那样”有问题“。但实际是你在决定这两个变量的数值上的对应关系(例如当前代码中,y2中的3282”等价“于y1中的1.0)。我个人觉得这其实应该在画图之前就确定下来,否则又会陷入”为了展示想要的结果(趋势)于是操作数据“的流程。

                          对于ggplot2,我建议可以先去看A Layered Grammar of Graphics这篇论文,整体的ggplot2设计思路就在里面,可以高屋建瓴的把握住这个包的脉络。

                            fenguoerbian
                            哦,那我明白你跟湘云在说撒了。谢谢你。你贴的两个链接我下班回去看看。关于一些统计方法会扭曲数据的问题,我之前还请教过三水(ps就是于淼),他给我讲不管用不用那些方法,结论很难做到完全客观公正。

                            那么当遇到量级相差很大的数据,应该怎样展示或者判断趋势是否接近呢?

                            趋势可以理解为一阶导数呀,增长率曲线可以画在一幅图里,然后他们看起来都比较平稳,说明趋势一致。