R画图时局部压缩坐标轴

R

Posted by dulunar on July 2, 2022 | 访问量:

前言

​ 用 R 画图的时候,如果 y 轴存在个别非常大或非常小的值,或者当中的数值存在非常大差异的时候,画出的图很容易产生误导效果,使人忽略当中某一部分信息。

​ 比如,下面这张 GWAS 曼哈顿图中(来自https://doi.org/10.1371/journal.pgen.1006594.g001 ),y 轴的值是由每一个 SNP 进行关联分析算出的 p 值再进行 -log10(P) 转换后得到的。图中存在非常多显著的 SNPs,当中最显著的 -log10(P) 甚至达到了 150 左右。不过,这样画图的话,红线附近会有很多显著的 SNPs 会因为 y 轴太大而显得不怎么显著。

解决

​ 下面展示了如何压缩/移除ggplot2中的部分y轴。当y值非常大或非常小并且未使用某些范围时,压缩/移除部分y轴非常有用。

压缩

​ 压缩y轴不同于完全移除y轴的一部分。如果要压缩或重缩放y轴的一部分,可以使用scale_y_continue()和tran_new()函数。另一方面,要移除y轴的一部分,可以使用facet_grid()函数。

在引入方法之前,制作了最小的示例数据集。

require(ggplot2)
## Loading required package: ggplot2

require(scales)
## Loading required package: scales

## Warning: package 'scales' was built under R version 3.5.3
dat <- data.frame(group=rep(c('A', 'B', 'C', 'D'), each = 10), 
                 value=c(rnorm(10), rnorm(10)+100)
                 )

以GROUP为x轴、VALUE为Y轴绘制dat将产生以下图形。Y轴上的值要么很大,要么很小,很难查看每组中的值。

ggplot(dat, aes(x=group, y=value)) + geom_point()

img

解决不同范围问题的一种方法是压缩y轴上未使用的部分,方法是在y轴上定义一个新的转换函数,该函数被封装在trans_new()方法中。

require(ggplot2)
squash_axis <- function(from, to, factor) { 
  # Args:
  #   from: left end of the axis
  #   to: right end of the axis
  #   factor: the compression factor of the range [from, to]
  trans <- function(x) {    
    # get indices for the relevant regions
    isq <- x > from & x < to & !is.na(x)
    ito <- x >= to & !is.na(x)
    
    # apply transformation
    x[isq] <- from + (x[isq] - from)/factor
    x[ito] <- from + (to - from)/factor + (x[ito] - to)
    return(x)
  }
  
  inv <- function(x) {
    # get indices for the relevant regions
    isq <- x > from & x < from + (to - from)/factor & !is.na(x)
    ito <- x >= from + (to - from)/factor & !is.na(x)
    
    # apply transformation
    x[isq] <- from + (x[isq] - from) * factor
    x[ito] <- to + (x[ito] - (from + (to - from)/factor))
    
    return(x)
  }
  # return the transformation
  return(trans_new("squash_axis", trans, inv))
}

使用新函数将y轴未使用的部分按10倍压缩,新的曲线图如下所示。

PS:老版的教程中,使用 scale_y_continuous()进行转换,会出现错误:

 Error in x[isq] <- from + (x[isq] - from) * factor : 
  NAs are not allowed in subscripted assignments

主要原因是在早期的版本中,转换的时候没有添加 & !is.na(x) 添加以后就可以使用scale_y_continuous进行调用了

require(ggplot2)
ggplot(dat,aes(x=group,y=value))+
  geom_point()+
  scale_y_continuous(trans = squash_axis(5, 95, 10))

# 也可以在 ggplot 画图时的 coord_trans 使用这个函数。参数 from 和 to 是要压缩的范围, factor 是要压缩的倍率。比如要把 5 到 95 范围的 y 轴压缩 10倍:
ggplot(dat,aes(x=group,y=value))+
  geom_point()+
  coord_trans(y = squash_axis(5, 95, 10))

# 把 1 到 99 范围的 y 轴压缩 30 倍:
ggplot(dat,aes(x=group,y=value))+
  geom_point()+
  coord_trans(y = squash_axis(5, 95, 30))

下载 (1)

在新图中,可以清楚地看到每个组内的值差异。但是,这种方法有两个缺点。首先,用户需要手动定义压缩和压缩因子的范围,这可能是许多测试。其次,对于某些读者来说,无标记的Y轴可能会产生误导。

另一种方法是完全移除y轴上未使用的部分。这可以通过计算y值的bin,然后逐个bin使用facet_grid()来实现。下面是一个例子:

# compute bins (using tidyverse packages tidyr and dplyr)
require(dplyr)
## Loading required package: dplyr
## Warning: package 'dplyr' was built under R version 3.5.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
dat %>%
    mutate(bin = value > 50) %>%

# plot the data using a facet_grid with free y scales
    ggplot(aes(x=group, y=value)) +
        facet_grid(bin ~ ., scale='free_y') +
        geom_point()

请注意,在使用此方法时,务必使用Scale=‘Free_y’,以便小平面具有单独的y标度。

可以通过更改主题来隐藏小平面标签。下面是另一个例子:

dat %>%
    mutate(bin = value > 50) %>%

# plot the data using a facet_grid with free y scales
    ggplot(aes(x=group, y=value)) +
        facet_grid(bin ~ ., scale='free_y') +
        geom_point() +
        theme(strip.text.y = element_blank())

使用组属性作为标签也是可行的,这样每个组都有一个单独的y轴。然而,这将导致独立和分离的y轴,这可能会令人困惑。请谨慎使用此方法。下面是一个糟糕的例子,它有四个独立的轴,它们没有合理地对齐。

# a bad example
dat %>%
    ggplot(aes(x=group, y=value)) +
        facet_grid(group ~ ., scale='free_y') +
        geom_point() +
        theme(strip.text.y = element_blank())

总而言之,在ggmap()中,我们能够压缩或移除部分y轴,以突出显示类之间和类内的差异。

References

Stackoverflow: How can I remove part of y-axis and reverse the axis in ggplot2

RPub: squash/remove part of y-axis in ggplot2

R画图y轴范围太大时,如何局部压缩坐标轴

How to make that crazy Fox News y axis chart with ggplot2 and scales