R语言

Lecture06 : R数据作图

罗益民

2024-09-20

R绘图概述

R语言的前身是S语言, S语言的设计目的就是交互式数据分析、绘图。 所以绘图是R的重要功能1

R有最初的基本绘图, 这是从S语言继承过来的, 还有一些功能更易用、更强大的绘图系统, 如lattice、ggplot2。

本节内容一览

  • Part01 R基本绘图
    • 常用图形
      • 条形图(bar)、盒形图(box)1
      • 直方图(hist)、核密度图(density)
      • 点图(dot)、Q-Q图(qq)
      • 线图(line)
      • 饼图(pie)
    • 图形辅助函数
    • 图形输出
  • Part02 ggplot绘图
    • ggplot作图介绍
    • ggplot作图详解

Part01 R基本绘图

条形图(bar plot)

条形图是一种常用的数据可视化工具,适用于以下场景

  • 展示分类数据
  • 展示频率分布
  • 展示时间序列数据的变化

内置函数barplot(),此函数较为灵活:

  1. 基于数值向量的条形图

基本用法barplot(height, ...),height 是一个数值向量,表示每个条形的高度。… 表示可以传递给函数的其他参数,用于定制条形图的外观和行为。

barplot(rnorm(10,mean = 1,sd=0.5))

  1. 基于公式接口的条形图

通常用于直接从数据框中提取数据并绘制条形图。允许你使用一个公式来指定分组变量和响应变量,这样可以更灵活地处理数据。

barplot(formula, data, subset, na.action, ...)。其中的formula是一个公式,指定了因变量和自变量之间的关系,例如 y ~ x。data即数据框。1

df <- data.frame(
  Category = c("A", "B", "C", "D"),
  Value = c(10, 15, 7, 20)
)
barplot(Value ~ Category, data = df, main = "Barplot with Formula Interface")

条形图举例

以R内置的Longley数据集为例,这个数据集包含了从1947年到1962年的宏观经济数据,共有16个观测值和7个变量。

head(longley)
     GNP.deflator     GNP Unemployed Armed.Forces Population Year Employed
1947         83.0 234.289      235.6        159.0    107.608 1947   60.323
1948         88.5 259.426      232.5        145.6    108.632 1948   61.122
1949         88.2 258.054      368.2        161.6    109.773 1949   60.171
1950         89.5 284.599      335.1        165.0    110.929 1950   61.187
1951         96.2 328.975      209.9        309.9    112.075 1951   63.221
1952         98.1 346.999      193.2        359.4    113.270 1952   63.639
# 使用barplot探索国民生产总值各个年份的变化
colors<-sample(colors(),length(longley))
barplot(
  GNP ~ Year, 
  data = longley,
  col=colors,
  xlab="年份",
  ylab="国民生产总值",
  main="1947~1962宏观经济数据",
  border="black",
  legend=TRUE
  )

条形图举例

直方图(hist plot)

直方图用于估计数据的概率分布。它通过将数据分组到有限数量的“箱子”中,并将这些箱子中的数据点数量可视化为条形图来实现。直方图通常用于连续数据。

使用hist()函数绘制直方图,基本形式:

hist(x, breaks, freq, probability, density, main, xlab, ylab, col, border)
  • x:要绘制直方图的数据向量。
  • breaks:可以是表示箱子边界的数值向量,或者是表示箱子数量的整数。
  • freq:逻辑值,表示y轴是显示频数(默认)还是概率密度。
  • density:如果指定了density的值,y轴将显示密度而不是频数,并且直方图的高度将按数据的总数进行缩放。

直方图

# 生成一组正态分布的随机数
data <- rnorm(100)
# 绘制直方图,自动选择箱子数量
hist(data, main = "Histogram of Normal Distribution", xlab = "Values", ylab = "Frequency")

# 指定箱子数量
hist(data, breaks = 50, col = "lightblue", main = "Histogram with 20 Bins")

# 显示概率密度而不是频数
hist(data, freq = FALSE, density = 25, col = "lightgreen", main = "Histogram with Density")

# 添加核密度估计曲线
hist(data, freq = FALSE, col = "lightblue", main = "Histogram with Density and KDE")
lines(density(data), col = "red", lwd = 2)

盒型图/箱线图(boxplot)

展示一组数据的分布情况的统计图表。箱线图可以显示数据的最小值、第一四分位数(Q1)、中位数(Q2)、第三四分位数(Q3)和最大值,以及可能的异常值。

使用boxplot()函数绘制箱线图,基本用法如下:

boxplot(x, data, range, names, main, xlab, ylab, col, border, horizontal, plot)
  • x:数据向量或矩阵,或者一个包含数据向量的列表。
  • data:一个数据框(data frame),其中包含要绘制的变量。
  • range:一个数值向量,指定了四分位数的取值范围,默认为1.5 * IQR(四分位距)。
  • names:一个向量,包含每个箱子的名称。
  • horizontal:逻辑值,如果为TRUE,则箱子水平绘制。

箱线图绘制

# 生成一组随机数据
set.seed(123)
data1 <- rnorm(50)
data2 <- rnorm(50, mean = 1, sd = 2)
# 使用数据向量创建箱线图
boxplot(data1, main = "Boxplot of Normal Distribution", xlab = "Data", ylab = "Values")

# 使用数据矩阵创建箱线图
data_matrix <- matrix(c(data1, data2), ncol = 2)
boxplot(data_matrix, main = "Boxplot of Two Groups", xlab = "Group", ylab = "Values", names = c("Group 1", "Group 2"))

# 针对数据框创建箱线图
boxplot(iris)

# 水平箱线图
boxplot(data1, horizontal = TRUE, main = "Horizontal Boxplot", xlab = "Values", ylab = "Data")

箱线图绘制

也可以采用方程式Y ~ X的形式,针对数据框绘制箱线图

# 使用数据框创建箱线图
data_frame <- data.frame(Group = rep(c("A", "B"), each = 50), Values = c(data1, data2))
boxplot(Values ~ Group, data = data_frame, main = "Boxplot with Data Frame", xlab = "Group", ylab = "Values", col = "lightblue")

# 根据气缸数量+档位类型分组绘制mpg箱线图
with(mtcars,boxplot(mpg ~ cyl + am))

点图/散点图(dot/scatter plot)

散点图(Scatter Plot)是一种常用的数据可视化方法,用于展示两个变量之间的关系。它通过在二维平面上绘制点来表示数据,每个点的横坐标代表一个变量的值,纵坐标代表另一个变量的值。散点图非常适合于观察变量之间的相关性,比如正相关、负相关或无相关。

使用场景:

  • 探索变量之间的相关性:通过观察散点图上的点是否大致沿着一条直线分布,可以判断两个变量之间是否存在线性相关。
  • 识别异常值:散点图可以直观地展示数据中的异常值,即那些远离其他数据点的点。
  • 展示数据分布:散点图可以展示数据在不同区间的分布情况。

使用plot()函数绘制散点图

with(iris,plot(Sepal.Length,Sepal.Width , main = "iris数据集散点图", xlab = "花萼宽度", ylab = "花萼长度", pch = 19, col = "blue"))

使用pairs()函数绘制散点图矩阵

pairs(iris[,1:4])

plot函数详解

plot()函数是一个通用的绘图函数,用于创建二维图表。这个函数非常灵活,支持多种参数来自定义图表的外观

plot(x, y = NULL, type = "p",  xlim = NULL, ylim = NULL,...)
  • x:x轴的数据。
  • y:y轴的数据。
  • type:图表类型,常用的值有:
    • “p” 或 “plot”:点图(默认)。
    • “l” 或 “line”:折线图。
    • “b” 或 “both”:点线图(点和线)。
    • “c” 或 “histogram”:直方图。
    • “n” 或 “none”:不绘制点或线,只设置坐标轴。

Q-Q图(Quantile-Quantile Plot)

Q-Q图是一种统计图表,用于比较两个概率分布,特别是用于检验数据集是否服从某一理论分布,如正态分布、指数分布等。它通过将两个分布的分位数进行比较,从而评估它们的相似性。在Q-Q图中,如果数据确实服从某一指定分布,则图上的点将大致分布在一条直线上。

适用范围:

  • 正态性检验:Q-Q图最常用的应用是检验数据是否服从正态分布。如果样本数据近似服从正态分布,则图上的点大致落在一条直线上,该直线的斜率为标准差,截距为均值。
  • 比较两个样本:Q-Q图也可以用来比较两个样本集是否来自同一分布。
  • 评估模型拟合:在回归分析等统计建模中,Q-Q图可以用来评估残差是否服从某一理论分布,从而判断模型的适用性 。

绘制Q-Q图

# 生成一组随机数据
data <- rnorm(100)
# 制作Q-Q图
qqnorm(data)
qqline(data,col='red')

线图(lines)

curve()函数接受一个函数, 或者一个以x为变量的表达式, 以及曲线的自变量的左、右端点, 绘制函数或者表达式的曲线图

curve(1 - 3*x - x^2, -4, 2)

curve(sin, -2*pi, 2*pi)

使用plot()函数绘制线图

在plot函数中使用 type=’l’参数可以作曲线图

x <- seq(0, 2*pi, length=200)
y <- sin(x)
plot(x,y, type='l')

除了仍可以用main, xlab, ylab, col等参数外, 还可以用lwd指定线宽度, lty指定虚线,

plot(x,y, type='l', lwd=2, lty=3)

饼图(pie plot)

饼图(Pie Chart)是一种圆形的统计图表,将圆形分割成扇形,每个扇形的角度和面积表示数据的比例。饼图通过将数据分组并显示每个组的比例,可以直观地展示数据的组成结构。

使用场景:

  • 部分与整体的关系:当数据集包含几个类别,并且你想要展示每个类别相对于总数的比例时。
  • 比较各部分的大小:当需要比较不同类别的相对大小时,饼图可以直观地展示哪些类别占比更大或更小。
  • 展示组成成分:例如,展示公司不同部门的员工比例,或者不同产品的销售占比。

饼图不适用于以下情况:

  • 数据类别过多,导致扇区太小难以区分。
  • 需要精确的数值比较,因为饼图更侧重于展示比例而非具体数值。
  • 数据类别之间的比较不直观,尤其是当某些类别的占比非常接近时。

绘制饼图

# 创建数据
labels <- c("部分A", "部分B", "部分C", "部分D")
sizes <- c(25, 35, 20, 20)
# 绘制饼图
pie(sizes, labels = labels, main = "Pie Chart in R", col = rainbow(length(labels)))

核密度图(Kernel Density Plot)

核密度图是一种用于估计数据分布的图形工具,它通过平滑数据点生成一个连续的概率密度函数,从而显示数据的分布情况。核密度图比直方图更为光滑,因为它不会依赖于具体的分箱选择。核密度图能够很好地显示数据的分布特征,如数据的多峰性和尾部行为。

可以使用plot函数绘制核密度图

# 创建数据
data <- rnorm(100)
# 计算核密度
kd <- density(data)
# 绘制核密度图
plot(kd, main='Kernel Density Plot of Data')

图形辅助函数

在R语言中,图形辅助函数是指那些用于增强和改进图形外观的函数。这些函数可以帮助你添加图形元素,如标题、轴标签、图例、文本注释等,以及调整图形的布局和外观。较为常用的有:

  • abline():增加直线
  • points():增加散点
  • lines():增加曲线
  • legend():增加图例
  • axis():绘制坐标轴
  • text():增加文字
  • par():显示多个图形

abline()

用abline函数在图中增加直线。 可以指定截距a和斜率b, 或为竖线指定横坐标(用参数v), 为水平线指定纵坐标(用参数h)

abline(a = NULL, b = NULL, h = NULL, v = NULL,...)

示例:

with(mtcars,{
  plot(hp, mpg)
  fit<-lm(mpg ~ hp) # 进行线性回归分析
  abline(fit, col="red", lwd=2)
  abline(v=summary(hp), col="darkgray")
  abline(h=summary(mpg), col="lightgray")
})

points()

用points函数增加散点:points(x, y = NULL, type = "p", ...)

x <- seq(0, 2*pi, length=200)
y <- sin(x)
special <- list(x=(0:4)*pi/2, y=sin((0:4)*pi/2))
plot(x, y, type='l')
points(special$x, special$y, col="red", pch=16, cex=2) 

lines()

用lines函数增加曲线lines(x, y = NULL, type = "l", ...)

x <- seq(0, 2*pi, length=200)
y1 <- sin(x)
y2 <- cos(x)
plot(x, y1, type='l', lwd=2, col="red")
lines(x, y2, lwd=2, col="blue")
abline(h=0, col='gray')

legend()

用legend函数增加标注legend(x, y = NULL, legend, fill = NULL, col = par("col"),...)

x <- seq(0, 2*pi, length=200)
y1 <- sin(x)
y2 <- cos(x)
plot(x, y1, type='l', lwd=2, col="red")
lines(x, y2, lwd=2, col="blue")
abline(h=0, col='gray')
legend(0, -0.5, col=c("red", "blue"),
       lty=c(1,1), lwd=c(2,2), 
       legend=c("sin", "cos"))

axis()

在plot()函数中用 axes=FALSE可以取消自动的坐标轴。 用box()函数画坐标边框。 用axis函数单独绘制坐标轴。 axis的第一个参数取1,2,3,4, 分别表示横轴、纵轴、上方和右方。 axis的参数at为刻度线位置,labels为标签。

# 创建数据
x <- 1:10
y <- rnorm(10)
# 绘制散点图,不包括轴
plot(x, y, type = "p",axes = FALSE)
# 添加x轴
axis(side = 1, at = x, labels = x, col = "blue", las = 1)
# 添加y轴
axis(side = 2, at = seq(min(y), max(y), length.out = 5), 
     labels = round(seq(min(y), max(y), length.out = 5), 2), col = "red", las = 1)

text()

text()在坐标区域内添加文字。 mtext()在边空处添加文字

with(mtcars,{
  plot(mpg,hp)
  text(30,300,"汽车马力和百公里油耗呈现反比关系")
  mtext("mpg ~ hp 散点图")
})

par()

par()函数是R语言中一个非常重要的函数,它用于设置或查询图形参数。这些参数控制着图形的各个方面,包括图形窗口的大小、布局、字体、颜色、线型等。通过par()函数,你可以自定义图形的外观,以满足特定的可视化需求。

par()最常用的使用场景是在一个图表上显示多个图形。

par函数指定图形参数并返回原来的参数值, 所以在修改参数值作图后通常应该恢复原始参数值。

# 设置图形参数,创建一个2×3的图形布局,并拿到修改前的原始参数
opar <- par(mfrow = c(2,3))
# 绘制mpg与其他参数的散点图
with(mtcars,{
  plot(mpg ~ cyl, main = "mpg vs cyl", xlab = "Cylinders", ylab = "Miles per Gallon")
  plot(mpg ~ disp, main = "mpg vs disp", xlab = "Displacement", ylab = "Miles per Gallon")
  plot(mpg ~ hp, main = "mpg vs hp", xlab = "Horsepower", ylab = "Miles per Gallon")
  plot(mpg ~ drat, main = "mpg vs drat", xlab = "Rear Axle Ratio", ylab = "Miles per Gallon")
  plot(mpg ~ wt, main = "mpg vs wt", xlab = "Weight", ylab = "Miles per Gallon")
})
# 绘制mpg的箱线图
boxplot(mtcars$mpg, main = "Boxplot of mpg", ylab = "Miles per Gallon")

par()

# 恢复默认的图形参数设置
par(opar)

图形输出

只要启用了绘图函数会自动选用当前绘图设备, 缺省为屏幕窗口。

输出为图片1

png(file='fig-mpg-hp.png', height=600, width=1000)
with(mtcars, plot(mpg, hp, main='汽车油耗与马力关系'))
dev.off()
png 
  2 

输出为pdf文件

pdf(file='fig-mpg-hp.pdf')
with(mtcars, plot(mpg, hp, main='汽车油耗与马力关系'))
dev.off()
png 
  2 

Part02 ggplot绘图

ggplot 作图介绍

Hadley Wickem

基本R的作图结果通常不够美观, 如果要将不同种类图形组合在一起比较困难, 对设计新的图形类型支持也不够好。

Hadley Wickem的ggplot2包是R的一个作图用的扩展包, 它实现了“图形的语法”, 将一个作图任务分解为若干个子任务, 只要完成各个子任务就可以完成作图。如果需要进一步控制图形细节, 只要继续调用其它函数, 就可以控制变量值的表现方式(scale)、图例、配色等。

与基本R中的作图系统相比, ggplot2的作图有规律可循, 作图结果直接达到出版印刷质量, 除了可以按照一些既定模式做出常见种类的图形, 也很容易将不同图形种类组合在一起, 或者设计新颖的图形。

ggplot 设计理念

ggplot在绘图上有着“分层”的概念,统一用于所有图形,保证了相同的语法:

ggplot作图一般步骤

  1. 准备数据 (data)
  2. 变量映射 (aes(mapping))
  3. 选择图表 (geom)
  4. 应用统计变换(statistics) - 可选
  5. 设定坐标 (coordinate) - 可选
  6. 设定其它 (main,labs,theme…) - 可选

ggplot2的作图代码模版

library(ggplot2)
ggplot(data = <数据框>) + 
  <几何函数>(
     mapping = aes(<视觉映射>),
     stat = <统计转换>, 
     position = <位置调整>
  ) +
  <坐标系统函数> +
  <刻面函数> + 
  <标尺函数> +
  <主题风格函数>

注意:data、mapping、stat、position、等等参数放在ggplot()函数里则控制为全局,设置到几何函数里则控制本几何函数。

加载必要包

运行本章示例之前请确保加载了以下包

library(tidyverse)
library(ggplot2)
library(patchwork)

ggplot作图:感受一下!

我们使用来自gapminder扩展包的gapminder数据集来体验ggplot作图。这个数据集有若干个国家不同年份的一些数据。包括所属洲、期望寿命、人口数、人均GDP。 有1704个观测和6个变量。

首先确保加载数据集:

library(gapminder)
head(gapminder)
# A tibble: 6 × 6
  country     continent  year lifeExp      pop gdpPercap
  <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
1 Afghanistan Asia       1952    28.8  8425333      779.
2 Afghanistan Asia       1957    30.3  9240934      821.
3 Afghanistan Asia       1962    32.0 10267083      853.
4 Afghanistan Asia       1967    34.0 11537966      836.
5 Afghanistan Asia       1972    36.1 13079460      740.
6 Afghanistan Asia       1977    38.4 14880372      786.

我们将做期望寿命(lifeExp)对人均GDP(gdpPercap)的散点图,并逐步进行完善, 每个国家的每个年份作为一个点。散点图最重要的映射是x轴与y轴两个维度。

首先调用ggplot()函数, 指定数据集,将人均GDP映射到x轴,将期望寿命映射到y轴, 结果保存为一个R变量:

library(ggplot2)
#1.准备数据 + 2. 变量映射
p <- ggplot(
  data = gapminder,
  mapping = aes(x = gdpPercap, y = lifeExp)
  )
# 简写形式:p <- ggplot(gapminder, aes(gdpPercap, lifeExp))

在如上指定了数据和映射后, 只要用geom_xxx()指定一个图形类型, 并与ggplot()的结果用加号连接就可以作图了

# 3. 选择图表
p + geom_point()

作图步骤之间用加号连接,这是ggplot包特有的语法。 例如, 用相同的映射做出散点图并叠加拟合曲线图1

p + geom_point() + geom_smooth()

在以上的图形中, x轴变量(人均GDP)分布非正态,严重右偏, 使得大多数散点重叠地分布在直角坐标系的左下角。 将x轴用对数刻度可以改善, 函数为scale_x_log10():

#4. 设定坐标
p + geom_point() +
  geom_smooth() +
  scale_x_log10()

x轴的标签文字不太友好,我们将其转换为美元符号1

p + geom_point() +
  geom_smooth() +
  scale_x_log10(labels=scales::dollar)

添加更多映射

在ggplot()函数的mapping参数的aes()设定中将变量映射到x、y轴, 颜色、符号、线型等图形元素类型,也可以作为图形设置将某些图形元素设置为固定值。

例如, 用不同颜色表示不同大洲, 就是将continent变量映射到color:

p <- ggplot(gapminder, aes(
    x = gdpPercap,
    y = lifeExp,
    color = continent))
p + geom_point() +
  geom_smooth() +
  scale_x_log10(labels=scales::dollar)

将置信区间阴影的颜色也用不同大洲区分, 方法是在aes()中将color和fill都指定为变量continent。并且更换图形主题

p <- ggplot(gapminder, mapping = aes(
    x = gdpPercap,
    y = lifeExp,
    color = continent,
    fill = continent))
p + geom_point() +
  geom_smooth() +
  scale_x_log10(labels=scales::dollar) +
  theme_bw()

在geom函数中映射变量

在必要时, 可以在geom_xxx()函数中单独用mapping = aes(<…>)单独指定变量映射。 例如, 下面的程序在geom_point()中将不同大洲映射为不同颜色, 而不影响geom_smooth()中的颜色以及分组:

p <- ggplot(gapminder, aes(
    x = gdpPercap,
    y = lifeExp))
p + geom_point(mapping = aes(color = continent)) +
  geom_smooth() +
  scale_x_log10(labels=scales::dollar)

也可以将一个分类变量映射到不同绘图符号。 例如,将pop(人口数)映射到点的大小,使得图形又多了一个维度:

p + geom_point(alpha=0.5,mapping = aes(color = continent,size=scale(pop))) +
  geom_smooth() +
  scale_x_log10(labels=scales::dollar)

使用Labs设定标题

labs()规定了上方的标题、小标题, x轴、y轴的标题, 右下方的标注(caption)。

ggplot(gapminder, aes(
  x = gdpPercap,
  y = lifeExp)) + 
  geom_point(alpha=0.5,mapping = aes(color = continent,size=scale(pop))) +
  geom_smooth() +
  scale_x_log10(labels=scales::dollar) +
  labs(
    x = "人均GDP",
    y = "期望寿命(年数)",
    title = "经济增长与期望寿命",
    subtitle = "数据点为每个国家每年",
    caption = "数据来源: gapminder"  
    )

# 使用ggsave()保存
ggsave("gdp-lifeExp.png")

# 保存为pdf
ggsave(filename="gdp-lifeExp.pdf",height=12, width=8, units="cm")

使用geom_text()geom_label()进行标注

asia <- gapminder |> filter(continent=="Asia")
china <- asia |> filter(country=="China")
ggplot(gapminder, aes(
  x = gdpPercap,
  y = lifeExp)) + 
  geom_point(alpha=0.5,mapping = aes(color = continent,size=scale(pop))) +
  geom_smooth() +
  scale_x_log10(labels=scales::dollar) +
  geom_text(aes(label=asia$country),data=asia) + 
  geom_label(aes(label=paste(china$country,china$year,sep = "-")),data=china,color="red" )

使用ggrepel解决重叠

##ggrepel包需单独安装
if(!require(ggrepel)){
   install.packages("ggrepel")
}
library(ggrepel)
ggplot(gapminder, aes(
  x = gdpPercap,
  y = lifeExp)) + 
  geom_point(alpha=0.5,mapping = aes(color = continent,size=scale(pop))) +
  geom_smooth() +
  scale_x_log10(labels=scales::dollar) + 
  geom_text_repel(aes(label=asia$country),data=asia) +
  geom_text_repel(aes(label=paste(china$country,china$year,sep = "-")),data=china,color="red")

刻面(facet)

在R中,使用facet_wrap()函数和facet_grid()函数根据选择的分类变量对数据分组,对所有分组后的子集创建并排显示的图形,称为刻面

ggplot(gapminder,  aes(x = gdpPercap,y = lifeExp)) + 
  geom_point(alpha=0.5) +  
  scale_x_log10(labels=scales::dollar) +
  facet_wrap(~ continent,ncol=3)

ggplot作图详解

数据(data)

实际用于作图的数据(Data),其中包含了需要被展示出来的变量,每个变量一个维度。数据对象类型为tibble或数据框,每个变量一列。

几何对象(geom)

几何对象(Geometric object),即图的类型,由geom_开头的几何函数定义。几何对象包括:散点图(geom_point)、折线图(geom_line)、柱状图(geom_bar)、直方图(geom_histogram)、盒形图(geom_boxplot)、密度曲线(geom_density)、……

ggplot(data=mtcars) +
  geom_point(mapping = aes(x = hp, y = mpg))

geom使用注意点

具体使用的时候,需要知道每个几何对象的适用场景,比如geom_bar默认情况下x和y只需要给一个参数,默认对其进行计数统计(即stat=“count”)。

以下代码在绘制柱状图时同时传入x和y,导致运行时报错(想一想为什么?):

ggplot(data=mtcars) +
  geom_bar(mapping = aes(x =cyl, y = mpg))

正确做法是只传入一个参数,让geom_bar函数自行统计count(默认stat=“count”)

ggplot(data=mtcars) +
  geom_bar(mapping = aes(x = cyl))

或设置stat=“identity”,告诉ggplot直接使用传入的y作为纵坐标

ggplot(data=mtcars) +
  geom_bar(mapping = aes(x = cyl,y = mpg),stat = "identity")

geom函数1

ggplot内置了众多geom_函数,每个都绘制独一无二的图形,比如geom_smooth用于在图形中添加平滑曲线或拟合线,默认线性回归线:

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  geom_smooth()

视觉映射(aes)

视觉映射(Aesthetic mappings)的意思是:一个变量的值可以映射到不同的视觉属性,由几何函数中mapping参数定义。包括:横坐标(x)、纵坐标(y)、颜色(colour)、填充(fill)、透明度(alpha)、形状(shape)、大小(size)、边框宽度(stroke)、线条类型(linetype)、分组(group)、…… 。

ggplot(data=mtcars) +
  geom_point(mapping = aes(x = hp,y = mpg,size=qsec, color = cyl,shape=as.factor(am)))

aes 使用注意点

观察以下代码的执行结果为何不同?为何出现这种不同?

p1 <- ggplot(data=mtcars) +  
  geom_point(aes(x = hp,y = mpg,size=3, color = "blue"))
p2 <- ggplot(data=mtcars) +  
  geom_point(aes(x = hp,y = mpg,size=3), color = "blue")
p1 + p2

把“blue”放到aes里,是把它作为一个变量进行了映射,但源数据中并不存在名为“blue”的变量,系统默认给映射到了红色,同时还会自动生成图例。

而把“blue”给了aes外边的color,是设置了点的颜色为“bule”。没有出现图例(没有映射),点的颜色被设置为蓝色。

aes映射的层次

mapping=aes()参数在两个层次进行变量映射,其一是在ggplot()定义的顶层(top level),其二是在各“geom_()”定义的底层,具体而言:

  • ggplot(data,mapping=aes(x,y,…)):是在顶层映射;
  • geom_line(mapping=aes())和geom_point(mapping=aes())等:都是在底层映射。

如果未在底层定义mapping,则它默认继承由顶层定义的值(可用?geox_xxx查看)。如果需要,可在各几何对象的底层,声明新需要的mapping。

以下两张图分别在全局和底层定义 aes映射,这种情况下两种写法完全一样

ggplot(data=mtcars,mapping=aes(x=hp,y=mpg)) +
  geom_point()

ggplot(data=mtcars) +
  geom_point(mapping=aes(x=hp,y=mpg))

一般情况下我们会把x,y放在全局映射中,把其它图形美学相关的放在geom_xxx函数中的映射中:

# x和y映射定义在全局,气缸数和车重映射到geom_point局部
ggplot(data=mtcars,mapping=aes(x=hp,y=mpg)) +
  geom_point(mapping=aes(color = cyl,size = wt)) +
  geom_line() 

只接受类型映射的aes参数

这段代码中,我们想把am列映射给点图的形状(shape),但代码执行报错,为什么?

ggplot(data=mtcars,mapping=aes(x=hp,y=mpg)) +
  geom_point(mapping=aes(color = cyl,size = wt,shape= am )) +
  geom_line()

shape必然接受一个分类数据,而am是一个数值型向量,因此不能映射,必须转为字符型或者因子:

# 将数值型向量转为字符型向量
ggplot(data=mtcars,mapping=aes(x=hp,y=mpg)) +
  geom_point(mapping=aes(color = cyl,size = wt,shape= as.character(am) )) +
  geom_line()

# 将数值型向量转为因子向量
ggplot(data=mtcars,mapping=aes(x=hp,y=mpg)) +
  geom_point(mapping=aes(color = cyl,size = wt,shape= as.factor(am) )) +
  geom_line()

统计变换 (stat)

ggplot的图形由一层层叠加的layer组成(有点像photoshop的图层)。每个图层都由两个部分定义:

  • 显示方式:几何图形(geom)
  • 显示内容:统计变换(stat)

统计变换和几何图形紧密联系,同等重要。实际上,每个几何图形内部都有默认的统计变换,因此不用额外定义。

p1 <- ggplot() + stat_identity(mapping = aes(x=1:5, y=1:5),geom = "point") 
p2 <- ggplot() + geom_point(mapping = aes(x=1:5, y=1:5),stat = "identity") #stat = "identity"是默认参数,可省略
p1+p2

以下的代码等价

# 创建一个数据框
data <- data.frame(x = 1:100,y = rnorm(100))
p1 <- ggplot(data, aes(x = x, y = y)) +
  geom_point() +
  geom_smooth(method = "lm",se = TRUE, color = "blue") 
p2 <- ggplot(data, aes(x = x, y = y)) +
  geom_point() +  # 添加散点图
  stat_smooth(method = "lm", se = TRUE, color = "blue") 
p1+p2

位置调整 (position)

位置调整(Position adjustments),微调图形对象的位置。默认为原位 (position_identity)

以下三种样式常用于柱状图

  • 堆叠(position_stack)
  • 比例堆叠(position_fill,堆叠且柱高归一)
  • 分列(position_dodge)

设置方式有:position=“stack”和position=position_stack()的方式,后者可以指定更多的参数。

# diamonds是ggplot2内置数据集,包括近54000颗钻石的价格和其他属性,共53940 行10个变量
dplot <- ggplot(diamonds, aes(color, fill = cut)) + theme(legend.position = "none")
p1 <- dplot + geom_bar() #默认position="identity"
p2 <- dplot + geom_bar(position = "fill")
p3 <- dplot + geom_bar(position = "dodge")
p1+p2+p3

以下三种样式多用于点图

  • position_nudge():按固定偏移量移动点。
  • position_jitter():在每个位置添加一点随机噪音偏移。
  • position_jitterdodge():避开组内的点,然后添加一些随机噪音偏移。
p1 <- ggplot(diamonds, aes(cut,price,colour=color )) + geom_point()
p2 <- ggplot(diamonds, aes(cut,price,colour=color )) + geom_point(position = "jitter" ) 
# 可以使用position_jitter()函数方式进一步自定义,默认width=0.4(40%),height=0.4(40%)
p3 <- ggplot(diamonds, aes(cut,price,colour=color )) + 
  geom_point(position = position_jitter(width = 0.2, height = 0.2) ) 
p1+p2+p3

简化写法:

ggplot(diamonds, aes(cut,price,colour=color )) + geom_jitter()

注意:jitter是一个较为细微的调整,通常用于分类变量上每组有大量数据的情况,用在连续变量上作用不明显

标尺(scale)

标尺(scale)控制数据到美学映射的转换细节。可以在其中覆盖默认的相关属性(如坐标轴缩放、标签、图例、颜色…),也可以完全生成新的美学映射。

标尺相关的函数众多,限于篇幅,这里仅讲解1个例子,我们使用mpg数据集(R内置的另一个汽车相关数据集)

mpg_99 <- mpg |> filter(year == 1999)
mpg_08 <- mpg |> filter(year == 2008)

base_99 <- ggplot(mpg_99, aes(displ, hwy)) + geom_point() 
base_08 <- ggplot(mpg_08, aes(displ, hwy)) + geom_point() 

base_99 + base_08

比较两张图不难发现,他们的y轴尺度是不一致的,左边的图取值范围比右边大。在横向比较时会带来一些麻烦。为了解决这个问题,可以对右图y轴进行坐标轴缩放:

base_08  <- ggplot(mpg_08, aes(displ, hwy)) + 
  geom_point() + 
  scale_y_continuous(limits = c(10, 45))
base_99 + base_08

坐标缩放非常普遍,ggplot提供了lims()内置函数:

base_08 <- ggplot(mpg_08, aes(displ, hwy)) + 
  geom_point() +
  lims(y = c(10, 45))
base_99 + base_08

一些常用的scale1

坐标系统(coord)

ggplot2中坐标系有两种类型。

  1. 线性坐标系:保留了几何图形的形状:
  • coord_cartesian():默认的笛卡尔坐标系,其中元素的二维位置由 x 和 y 位置的组合给出。

  • coord_flip():x 轴和 y 轴翻转的笛卡尔坐标系。

  • coord_fixed():具有固定长宽比的笛卡尔坐标系。

  1. 非线性坐标系:可以改变形状。直线可能不再是直线。两点之间最近的距离可能不再是直线。
  • coord_map()/ coord_quickmap()/ coord_sf():地图投影。

  • coord_polar():极坐标。

  • coord_trans():数据经过 stat 处理后,对 x 和 y 位置应用任意变换。

线性坐标系

线性坐标系coord_cartesian()也有xlimylim参数。同样也是限制x和y轴。它和scale有什么区别呢?

base <- ggplot(mpg, aes(displ, hwy)) + 
  geom_point() 
# 比例尺缩放限制
scaled <- base + 
  scale_x_continuous(limits = c(4, 6))
# 坐标轴限制
coorded <- base + 
  coord_cartesian(xlim = c(4, 6))

base + scaled+ coorded
Warning: Removed 153 rows containing missing values or values outside the scale range
(`geom_point()`).

可以看到scale方式抛出一个警告:有153个观测值被丢弃了。 两者关键的区别在于限制的工作方式:设置scale比例尺限制时,限制之外的任何数据都将被丢弃;但设置坐标系限制时,我们仍然使用所有数据,但我们只显示绘图的一小部分。设置坐标系限制就像在放大镜下查看绘图一样。

使用coord_flip()

p1 <- ggplot(mpg, aes(displ, cty)) + geom_point() + geom_smooth()
p2 <- ggplot(mpg, aes(cty, displ)) + geom_point() + geom_smooth()
p3 <- ggplot(mpg, aes(displ, cty)) + geom_point() + geom_smooth() + coord_flip()
p1+p2+p3

注意:虽然p2手动交换x和y和p3绘制的点一样。但是在调用geom_smooth方法后结果却不一样。

刻面(facet)

刻面(Facet) 根据某些变量将数据分成子集,在针对各个子集分别作图并排列在一个页面,包括:网格型(facet_grid)和包裹型(facet_wrap)。

facet wrap

使用vars()设置刻面依据

# 根据制造商绘制刻面
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(vars(manufacturer))

也可使用方程设置

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(~ manufacturer)

facet grid

ggplot(mpg, aes(displ, hwy)) + 
  geom_point() + 
  facet_grid(rows=vars(drv),cols= vars(cyl)) 

主题 (theme)

主题控制绘图中所有非数据元素的显示。可以使用完整主题覆盖所有设置,或者选择使用theme()和element_函数调整单个设置。使用theme_set()可修改活动主题,从而影响所有未来绘图。

使用?theme()查看能够定义的主题参数

内置的完整主题:theme_grey() theme_gray() theme_bw() theme_linedraw() theme_light() theme_dark() theme_minimal() theme_classic() theme_void() theme_test()

使用theme()自定义部分主题1

ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  labs(title = "Fuel economy declines as weight increases") +
  theme(
    plot.title = element_text(size = rel(2)),
    plot.background = element_rect(fill = "lightgreen")
    )

使用内置的完整主题

# create data
set.seed(123)
var=rnorm(1000)
 
# Without theme
plot1 <- qplot(var , fill=I(rgb(0.1,0.2,0.4,0.6)) )
 
# With themes
plot2 = plot1+theme_bw()+annotate("text", x = -1.9, y = 75, label = "bw()" , col="orange" , size=4)
plot3 = plot1+theme_classic()+annotate("text", x = -1.9, y = 75, label = "classic()" , col="orange" , size=4)
plot4 = plot1+theme_gray()+annotate("text", x = -1.9, y = 75, label = "gray()" , col="orange" , size=4)
plot5 = plot1+theme_linedraw()+annotate("text", x = -1.9, y = 75, label = "linedraw()" , col="orange" , size=4)
plot6 = plot1+theme_light()+annotate("text", x = -1.9, y = 75, label = "light()" , col="orange" , size=4)
plot7 = plot1+theme_dark()+annotate("text", x = -1.9, y = 75, label = "dark()" , col="orange" , size=4)
plot8 = plot1+theme_get()+annotate("text", x = -1.9, y = 75, label = "get()" , col="orange" , size=4)
plot9 = plot1+theme_minimal()+annotate("text", x = -1.9, y = 75, label = "minimal()" , col="orange" , size=4)

plot1+plot2+plot3+plot4+plot5+plot6+plot7+plot8+plot9

多图排版

不同于刻面facets(将数据分为多个子集进行展示),多图排版是将多个独立的图形按照一定规则分布在一张图上。ggplot本身并不具备这个功能。但有很多第三方包对此进行了支持。本章介绍一个比较流行的包patchwork(前面的多图排版均由此包完成),它的使用非常简单:+表示自动排列,|表示左右排列,/表示上下排列

library(ggplot2)
library(patchwork)

p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp))
p2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear))

p1 + p2

p3 <- ggplot(mtcars) + geom_smooth(aes(disp, qsec))
p4 <- ggplot(mtcars) + geom_bar(aes(carb))

(p1 | p2 | p3) /
      p4

ggplot的各种图形

图形 适用场景 代码示例
条形图 展示分类变量的频率或比例 geom_bar(mapping = aes(x = cut, fill = clarity))
直方图 展示连续变量的分布情况 geom_histogram(binwidth=.5)
箱线图 展示数据基本统计信息 ggplot(data = mpg, aes(x = class, y = hwy)) + geom_boxplot()
点图 展示连续变量的分布情况 ggplot(iris) + geom_point(aes(x=Sepal.Length, y=Sepal.Width))
折线图 展示随时间变化的数据趋势或两个连续变量之间的关系 ggplot(data = mtcars, aes(x = mpg, y = disp)) + geom_line(color = "red")
密度图 展示连续变量的概率分布 ggplot(dat, aes(x=rating)) + geom_density()
小提琴图 展示分类变量下数值变量的分布 ggplot(iris) + geom_violin(aes(x=Species, y=Petal.Length))
热力图 展示两个分类变量的交叉频次或比例 geom_tile(color="gray",size=0.5)

优秀的ggplot第三方扩展

  1. gganimate:动画库,让图形动起来

  1. ggdendro:绘制谱系图

  1. ggthemes:精美的预设主题

  1. ggpubr:ggplot傻瓜式插件,简便的方法生成出版质量图片

  1. patchwork:图片layout排版利器

  1. ggrepel:控制label和text间距,适用于大量文字标注的图形

附录(Appendix)

ggplot官网

全面了解ggplot的最佳去处

https://ggplot2.tidyverse.org/articles/ggplot2.html

ggplot2: Elegant Graphics for Data Analysis (3e)

Springer 出版的《ggplot2:用于数据分析的优雅图形》第三版的在线版本

https://ggplot2-book.org/

现代统计图形

这本书以浅显而有趣的图形示例将统计学和可视化的历史娓娓道来,是一本难得的以图形为主线、探讨统计学和统计可视化的高质量通识读物。书中内容无不体现作者对现代统计和统计图形的深刻思考,例如“神奇数字”和“作图原则”等章节。这本书既可作为高校师生的统计计算或者统计软件中可视化章节的主要参考书,也可作为严谨学术作图的指导手册。

https://bookdown.org/xiangyun/msg

ggplot cheatsheet

http://roypub.biovr.cc/data-visualization_zh.pdf