今天来教大家怎么在用使用ggplot2
画散点图的时候将坐标轴居中,即将两个坐标轴交叉位置设置为原点(0,0)。
1、需求描述
默认情况下,在使用ggplot2
画散点图时,x 坐标轴位于图片最底部,y 坐标轴位于图片最左边,比如我们生成一个示例数据来画散点图,代码如下:
library(ggplot2)
set.seed(6)# 设置随机种子,当随机种子固定后,生成的随机数将不会发生变化
df<-data.frame(x=rnorm(50),y=rnorm(50))
## 绘制 x 和 y 的散点图
ggplot(df, aes(x=x,y=y))+
geom_point()+
theme_classic()# 一种 ggplot2 主题,会仅显示坐标轴,并且不显示网格
上述代码所绘制的散点图如下所示:
从图中可以发现,x 和 y 坐标轴分别位于图片的最底部和最左边。
但是有时候我们想要将 x 和 y 坐标轴的交叉点放到(0, 0)的位置,这样可以更加清晰地分辨出四个象限,也会容易观察出数据的 pattern。
然而ggplot2
中并没有合适的函数可以实现这个需求,因此我们需要自定义函数来实现该功能。
2、需求实现
由于ggplot2
中的坐标轴本质上就是两条线,并且ggplot2
提供了函数(geom_line
)来绘制直线,也提供了函数(annotate
)在图中添加注释,因此我们可以借助这些函数来绘制坐标轴直线、坐标轴刻度和标签。
下面定义一个函数(theme_with_xy_centered
)来将 x 和 y 坐标轴居中(代码有些长,并添加了较为详细的注释):
theme_with_xy_centered <- function(
xlimit, ylimit, xgeo = 0,
ygeo = 0, color = "black",
xlab = "x", ylab = "y",
num_ticks = 10, lab_textsize = 4,
tick_textsize = 3, linewidth = 1,
epsilon = max(xlimit,ylimit)/50) {
# modified from https://stackoverflow.com/questions/17753101/center-x-and-y-axis-with-ggplot2
# 参数说明: # xlimit: x坐标轴的范围
# ylimit: y坐标轴的范围 # xgeo : y轴向x轴偏移的距离,为0时表示不偏移,即y轴位于 x=0 直线上
# ygeo : x轴向y轴偏移的距离,为0时表示不偏移,即x轴位于 y=0 直线上 # color: 坐标轴的颜色,默认是黑色
# xlab : x 坐标轴的标签 # ylab : y 坐标轴的标签
# num_ticks: 在每个坐标轴上添加的刻度数目,默认是10个刻度 # lab_textsize: x和y轴标签的大小
# tick_textsize: x和y轴刻度上的标签的大小 # linewidth: 坐标轴和刻度的宽度
# epsilon: 该参数通常设置为很小的值,表示坐标轴刻度的长度
# 新的坐标轴的位置 xaxis <- data.frame(x_ax = c(-xlimit, xlimit), y_ax = rep(ygeo,2))
yaxis <- data.frame(x_ax = rep(xgeo, 2), y_ax = c(-ylimit, ylimit))
# 添加新的坐标轴 theme.list <- list(
theme_void(), # 将当前图片的主题设置为空,避免影响后续主题设置 geom_line(aes(x = x_ax, y = y_ax), color = color, data = xaxis, linewidth=linewidth),
geom_line(aes(x = x_ax, y = y_ax), color = color, data = yaxis, linewidth=linewidth), # 添加x轴标签
annotate("text", x = xlimit + 2*epsilon, y = ygeo, label = xlab, size = lab_textsize, vjust=-0.5, hjust=1), # 添加y轴标签
annotate("text", x = xgeo, y = ylimit + 4*epsilon, label = ylab, size = lab_textsize, vjust=-0.3), xlim(-xlimit - 7*epsilon, xlimit + 7*epsilon), # 在坐标轴添加范围限制
ylim(-ylimit - 7*epsilon, ylimit + 7*epsilon) # 在坐标轴添加范围限制 )
# 设置坐标轴刻度的位置
ticks_x <- round(seq(-xlimit, xlimit, length.out = num_ticks), 2) ticks_y <- round(seq(-ylimit, ylimit, length.out = num_ticks), 2)
# 接下来循环添加x和y轴的刻度
nlist <- length(theme.list)
for (k in 1:num_ticks){ # 生成x坐标轴刻度的数据
xtick <- data.frame(xt = rep(ticks_x[k], 2), yt = c(xgeo + epsilon, xgeo))
# 生成y坐标轴刻度的数据 ytick <- data.frame(xt = c(ygeo + epsilon, ygeo),
yt = rep(ticks_y[k], 2))
# 添加x坐标轴的刻度 theme.list[[nlist + 4*k-3]] <- geom_line(aes(x = xt, y = yt),
data = xtick, color = color,
linewidth=linewidth)
# 添加x坐标轴刻度的标签 theme.list[[nlist + 4*k-2]] <- annotate("text",
x = ticks_x[k], y = ygeo - epsilon,
size = tick_textsize, vjust = 1,
label = paste(ticks_x[k]))
# 添加y坐标轴的刻度 theme.list[[nlist + 4*k-1]] <- geom_line(aes(x = xt, y = yt),
data = ytick, color = color,
linewidth=linewidth)
# 添加y坐标轴刻度的标签 theme.list[[nlist + 4*k]] <- annotate("text",
x = xgeo - epsilon, y = ticks_y[k],
size = tick_textsize, hjust = 1,
label = paste(ticks_y[k])) }
# 返回该主题list,该list可以直接用在ggplot创建的图层中,通过加号 (+) 连接
return(theme.list)}
下面主要讲一下该函数的思路和功能,具体参数的含义请参考函数内部的注释。
该函数的主要思路是创建一个新的ggplot
主题(theme
),该主题实质上是R
语言中的列表(list
),而list
可以直接添加在ggplot
创建的图层中。
在创建该主题的list
时,首先使用了geom_line
和annotate
函数来创建 x 和 y 坐标轴及标签,然后计算需要在坐标轴上绘制的刻度的坐标,再采用循环的方式给坐标轴添加刻度及标签,在循环中仍然使用了geom_line
和annotate
这两个函数。
下面看一下使用了该函数以后的散点图,运行如下代码:
set.seed(6)# 设置随机种子,当随机种子固定后,生成的随机数将不会发生变化
df<-data.frame(x=rnorm(50),y=rnorm(50))
## 将 xy 轴居中,放到原点 (0,0) 上
xlimit<-max(abs(df$x),abs(df$y))
ylimit<-xlimit
ggplot(df)+
geom_point(aes(x=x,y=y))+
theme_with_xy_centered(
xlimit=xlimit,ylimit=ylimit,
linewidth=0.4
)
绘制的散点图如下:
可以发现,此时 x 坐标轴与 y=0 直线重合了,而 y 坐标轴与 x=0 直线重合了,并且可以清晰地分辨出四个象限。
此外,theme_with_xy_centered
函数有很多参数可以设置,这里列举几个常用的参数,全部参数请查看该函数的源代码。
•xlimit
:坐标轴的范围,一般为绘图数据的最大值•ylimit
:坐标轴的范围,一般为绘图数据的最大值•color
:设置坐标轴颜色•xlab
:设置 x 坐标轴的标签,默认是 x•ylab
:设置 y 坐标轴的标签,默认是 y•num_ticks
:在每个坐标轴上添加的刻度数目,默认是 10 个刻度
3、本文参考链接
https://stackoverflow.com/questions/17753101/center-x-and-y-axis-with-ggplot2