第16讲:ggplot2 图形语法入门

数据挖掘与R语言

2026年10月27日

本讲导览

  • 一、前课测验 5′
  • 二、ggplot2 与骨架 12′
  • 三、三件套与叠图层 10′
  • 四、柱状图与排序 18′
  • 五、折线图 10′
  • 六、综合实战 12′

重要

上节课我们用 base R 画了几种图,也立好了“坐标诚实、标注完整、颜色有意义”的规矩。今天换一套语法:把“先画图、再补装饰”换成“数据 + 映射 + 图层,用一个 + 叠起来”。

学习目标:本讲学完你应该能够

  • 说出 ggplot2 是什么,以及 data、aes()、geom_*() 各负责什么
  • 解释 aes() 是“数据与视觉属性的映射描述”
  • 用 + 叠加图层:从空白画布开始,数据、映射、图层配齐,图才出来
  • 先判断“要数个数还是画现成数值”,再选 geom_bar() 或 geom_col()
  • 用 reorder() 把类别按数值排序,交换 x 和 y 就能把竖柱变成横条
  • 用 geom_line() 先画单条折线(分类轴上用 group = 1 连线),再画多条折线,并说清多条折线里 color 与 group 各自管什么
  • 每张图都用 labs() 补齐标题与带单位的轴说明

一、前课测验

第 15 讲:看懂图,画对图

1.1 选择题

1. 要比较两组汽车的平均油耗谁高,按人眼读图精度排序,最可靠的是哪种视觉编码? A. 颜色深浅 B. 柱子的长度 C. 饼图扇形的角度 D. 气泡的面积

2. 柱状图的数值轴为什么通常要从 0 开始? A. 画起来省事 B. 柱子是长度编码,不从 0 起会夸大组间差距 C. R 的默认要求 D. 为了放得下标题

1.2 填空题

3. base R 里,plot()、barplot() 这类高级函数负责 ______,title()、legend() 这类低级函数负责 ______。

4. 拿到一个分析任务,选图要先看两件事:变量的 ______ 和变量的 ______。

提示

开场一问:base R 想加标题、加轴名、给柱子染色,要回头补 main=、xlab=、col= 等参数——图的零件散在参数里。今天的 ggplot2 走另一条路:每个零件写成一层,用 + 往上摞。

先看一个熟悉的例子:画条形图

同样是统计不同车型的数量,Base R 和 ggplot2 的思路有什么不同?

Base R:先统计,再绘图

▶️ 查看代码
barplot(                    # base R 画条形图的高级函数
  table(mpg$class)          # 先统计:数出每个车型有几辆
)                           # 注意顺序:先算好,再交给函数画

ggplot2:直接从原始数据绘图

▶️ 查看代码
ggplot(mpg, aes(x = class)) +  # 1. 数据 + 2. 映射:横轴是车型
  geom_bar()                   # 3. 图层:数个数、画成柱子

二、ggplot2 是什么,先看骨架

一个包,一套语法

2.1 认识 ggplot2

  • ggplot2 是 R 中最常用的绘图包之一,属于 tidyverse 家族。
  • 名字来自 Grammar of Graphics(图形语法):把一张图拆成数据、映射、几何对象等基本成分,再按规则组合。
  • 第一次使用要先安装、再加载,每次新开 R 都要重新 library():
▶️ 查看代码
install.packages("ggplot2")   # 只需安装一次
library(ggplot2)              # 每次使用前加载

注记

安装与加载属于“准备工作”,不是画图语法——把它当成“打开工具箱”。

小提琴+箱线图:比较分布

时序图:规模、趋势与波动

玫瑰图(南丁格尔图)

甜甜圈图

2.2 ggplot2基本结构

ggplot(data, mapping) + geom + scale + coord + facet + labels + theme

▶️ 查看代码
ggplot(
  data = mpg,                          # 1. 数据
  mapping = aes(x = class, fill = drv)  # 2. 映射
) +                                    # 用 + 往上叠图层
  geom_bar() +                          # 3. geom_* 几何对象
  # stat 和 position 可以在图层中设置
  # geom_bar(stat = "count", position = "stack") +
  scale_fill_brewer(palette = "Set2") + # 4. 标度
  labs(
    title = "各车型的车辆数量",          # 5. 标签:图标题
    x = "车型类别",                      #    横轴名
    y = "车辆数(辆)",                  #    纵轴名(带单位)
    fill = "驱动方式"                    #    图例标题
  ) +                                   # 5. 标签
  theme_minimal()                       # 6. 主题

重要

  • data、aes() 和 geom_*(): 构成理解 ggplot2 的基本主线:数据决定使用哪些观测,映射决定数据如何对应视觉属性,几何对象决定用什么形状呈现数据。

  • stat 和 position: 分别涉及统计变换与位置调整;

  • scale、coord、facet 和 theme: 控制视觉映射、坐标系统、图形布局和整体外观。这些组件不必每次都显式写出,ggplot2 会根据具体图层和绘图设置使用相应的默认行为。

  • 本讲先学 data、aes() 和 geom_*() 三件套;stat、position、标度、分面和主题等内容留待后续课程展开。

2.3 mapping =是什么

ggplot() 的第二个参数叫 mapping(映射)。完整写法是:

▶️ 查看代码
ggplot(data = mpg, mapping = aes(x = class))  # 两个参数名都写全

mapping = 可以省略,R 按位置匹配,所以常见写法是 ggplot(mpg, aes(x = class))。初学时建议先保留完整写法,看清每个参数的角色。

2.4 aes()是什么

aes 是 aesthetics(美学属性)的缩写,指图形上可以被看见的属性:横纵坐标位置、颜色、形状、大小、透明度等。

aes() 不是在设定一个具体的值,而是在声明:“数据表里的某一列,对应图形的某一个视觉属性。”

数据列 对应的视觉属性 写法
displ 横轴位置 x = displ
hwy 纵轴位置 y = hwy
drv 颜色 fill = drv 或 color = drv

2.5 geom_*():几何对象图层

  • ggplot() 管“用什么数据、怎么映射”,geom_*() 管“把数据画成什么形状”:柱子、条、折线……
  • geom 后的星号 * 是占位符,具体写成图形名;本讲用“三兄弟”,先按要回答的问题选:
你要…… 用哪个 geom 轴的写法
数个数 geom_bar() 只给一个轴
画现成数值 geom_col() 给两个轴
看先后顺序上的走势 geom_line() 给两个轴
▶️ 查看代码
ggplot(mpg, aes(x = class)) +   # 数据 + 映射:横轴看车型
  geom_bar(fill = "steelblue")  # geom 图层:数个数,画成蓝色柱子

提示

geom 图层就是画图的“笔”:同一个 ggplot(mpg, aes(x = class)),换 geom_col()、geom_line() 就换一种图——第四节、第五节逐个上手。

三、三件套与叠图层

一张 ggplot2 图 = 数据 + 映射 + 图层

3.1 只有数据和映射:空白画布

▶️ 查看代码
ggplot(mpg, aes(x = class))      # 只有 data + aes,还没有 geom

提示

映射只说明“用哪一列或几列”的数据,不说明“画什么图形”。 没有图层,就只剩一张空白的坐标轴。这就是为什么 geom_*() 是必不可少的一层。

3.2 加一层 geom:几何图形出现

▶️ 查看代码
ggplot(mpg, aes(x = class)) +    # 数据 + 映射
  geom_bar()                     # 图层:柱子

提示

柱子的高低是 R 数出来的个数,y 轴标签叫 count,数据框里并没有这一列。

3.3 再加颜色和标签:让图表更容易读

▶️ 查看代码
ggplot(mpg, aes(x = class)) +         # 数据 + 映射:横轴是车型
  geom_bar(fill = "steelblue") +      # 图层:数个数,柱子固定蓝色
  labs(title = "各车型的车辆数量",     # 标签:图标题
       x = "车型类别",                 #       横轴名
       y = "车辆数(辆)")             #       纵轴名(带单位)

重要

labs() 补充标题、轴名与图例标题,也用 + 添加。它不是画图的必需条件,但正式分析里,标题清楚、轴名准确、单位完整,是图表规范的重要部分。

3.4 【练习】换变量,补标题

把 3.3 节的代码照抄一遍(模仿),然后把 x 换成 drv,drv 的含义是:4 为四驱,f 为前驱,r 为后驱。把标题与轴名改成对应的内容(练习)。

画完回答:三种驱动方式各有多少辆车?

四、geom_bar() 与 geom_col()

4.1 创建示范数据

表里已有现成的数值列,就用这十二家公司的虚构财务数据(教学虚构):

▶️ 查看代码
company_data <- data.frame(         # 建一张教学数据表(教学虚构)
  company = c("锦城科技", "蜀汉制造", "天府物流", "青羊金融",
              "锦江生物", "浣花实业", "武侯地产", "龙泉食品",
              "新都电子", "双流航运", "温江农科", "郫都建材"),  # 第1列:公司名
  revenue = c(1300, 980, 1560, 760, 2100, 890,
              1180, 540, 1720, 1420, 360, 680)  # 第2列:全年营收,万元
)                                   # 12 行 = 12 家公司

再补三列——成本、信用评级,以及利润 profit = revenue - cost:

▶️ 查看代码
company_data <- company_data %>%    # 在同一个表上继续加工
  mutate(
    cost = c(1000, 650, 900, 500, 900, 620,     # 新增列:全年成本,万元
             850, 380, 1180, 1010, 260, 540),
    rating = c("A", "B", "B", "C", "A", "C", "B",  # 新增列:信用评级
               "C", "C", "B", "C", "C")
  ) %>%
  mutate(profit = revenue - cost)   # 新增列:利润 = 营收 − 成本

4.2 数个数:geom_bar() 画评级分布

▶️ 查看代码
company_data %>%                    # 数据:十二家公司那张表
  ggplot(aes(x = rating)) +         # 映射:横轴是信用评级
  geom_bar(fill = "steelblue") +    # 图层:数每个评级有几家
  labs(title = "各信用评级的公司数",  # 标签:图标题
       x = "信用评级", y = "公司数(家)")  # 轴名(带单位)

提示

数出来:A 级 2 家、B 级 4 家、C 级 6 家。rating 这一列只有字母,没有计数——个数是 geom_bar() 现场数出来的。

4.3 画数值:geom_col() 画公司营收

revenue 是现成的数值列,这时要给 两个轴:x 放公司,y 放营收。

▶️ 查看代码
ggplot(company_data,                # 数据:十二家公司那张表
       aes(x = company, y = revenue)) +  # 映射:两个轴都要给
  geom_col(fill = "steelblue") +    # 图层:画现成的营收数值
  labs(title = "十二家公司全年营收",  # 标签:图标题
       x = "公司", y = "全年营收(万元)")  # 轴名(带单位)

重要

口诀:数个数用 geom_bar()(只给一个轴),画数值用 geom_col()(给两个轴)。 先问自己:这列数据是现成的数值,还是要让 R 去数?

4.4 排序与横向:交换 x 和 y

横向柱状图只要交换 x 和 y,再用 reorder() 把公司按营收排序。

▶️ 查看代码
ggplot(company_data,                # 数据:十二家公司那张表
       aes(x = revenue,             # 映射:数值放 x、类别放 y,
           y = reorder(company, revenue))) +  # 就变横向;reorder 按营收排序
  geom_col() +                      # 图层:画现成的营收数值
  labs(title = "十二家公司全年营收(从小到大)",  # 标签
       x = "全年营收(万元)", y = "公司")  # 轴名(带单位)

重要

reorder() 只改变类别的顺序,不改数据本身。横向图交换 x 和 y即可;旧写法 coord_flip() 官方已不推荐,我们统一用对调。

4.5 【练习】数个数还是画数值

  • ① 用 mpg 画“每种驱动方式各有几辆车”的柱状图(drv),用 geom_bar(),补标题与轴名;
  • ② 用 mpg 画“每种车型的平均高速油耗”的柱状图(class),先用 summarise() 算出平均值,再用 geom_col() 画;
  • ③ 换②的变量并改横向:用 mpg 画“各厂牌平均城市油耗”的横向柱状图(cty 按 manufacturer 求均值),用 reorder() 排序。

三张图都要标题与带单位的轴名(油耗单位是英里/加仑)。做完想一想:为什么①不需要 summarise(),②③一定需要?

五、折线图

折线要说清“谁跟谁连”

5.1 模仿:先造一张季度营收表

▶️ 查看代码
quarter_wide <- data.frame(         # 造季度营收表(教学虚构)
  company = rep(c("锦城科技", "蜀汉制造", "天府物流"), each = 4),
                                    # 每家公司连续出现 4 行
  quarter = rep(c("Q1", "Q2", "Q3", "Q4"), times = 3),
                                    # 季度按 3 家公司重复 3 轮
  revenue = c(300, 330, 305, 365, 250, 260, 250,
              220, 290, 330, 400, 540)  # 当季营收,万元
)                                   # 12 行 = 3 家公司 × 4 个季度

自查:12 行 = 3 家公司 × 4 个季度;加总 1300、980、1560,与 4.1 一致。

5.2 单条折线:group = 1 连成一条

▶️ 查看代码
quarter_wide %>%                    # 数据:季度明细表
  group_by(quarter) %>%             # 按季度分组
  summarise(revenue = sum(revenue)) %>%  # 每季度把三家加总
  ggplot(aes(x = quarter,           # 映射:横轴季度
             y = revenue,           #       纵轴合计营收
             group = 1)) +          # 单条折线:所有点连成一条
  geom_line(linewidth = 1) +        # 图层:折线,线宽 1
  geom_point(size = 2) +            # 图层:再叠上数据点
  labs(title = "三家公司合计季度营收走势",  # 标签:图标题
       x = "季度", y = "营收(万元)")  # 轴名(带单位)

提示

先把三家公司的每个季度加总成 4 行小表,再画折线。横轴 quarter 是分类变量,ggplot2 不确定哪些点该连线,group = 1 就是告诉它:所有点连成一条线。读数:季度合计 840 → 920 → 955 → 1125,逐季走高。

5.3 多条折线:color 管颜色,group 管连线

要看清每家公司各自的走势,就不该加总——直接画多条折线。

▶️ 查看代码
ggplot(quarter_wide,                # 数据:不汇总,直接用明细
       aes(x = quarter,             # 映射:横轴季度
           y = revenue,             #       纵轴营收
           color = company,         #       颜色区分公司
           group = company)) +      #       每家公司各自连一条线
  geom_line(linewidth = 1) +        # 图层:折线,线宽 1
  geom_point(size = 2) +            # 图层:再叠上数据点
  labs(title = "三家公司季度营收走势",  # 标签:图标题
       x = "季度", y = "营收(万元)")  # 轴名(带单位)

提示

5.2 画一条线,用 group = 1 把点全连起来;这里每家公司一条线,分组就要按 company。color = company 区分公司、生成图例;group = company 指定哪些点连成同一条线——两者作用不同,即使这里都是 company。

5.4 【练习】用内置数据画趋势

用 ggplot2 内置的 economics 画失业人数的折线图:横轴 date,纵轴 unemploy(单位:千人),补标题与轴名。

画完说一句:哪段时间拐头向上、哪段回落?

六、综合实战——画两张能交差的图

选图 → 画图 → 加字 → 验收

6.1 两个任务(约 6 分钟)

任务 A(排名):用 mpg 画“各厂牌平均高速油耗”的横向柱状图,最高的在最上面,补齐标题、带单位的轴名与 caption(数据来源:ggplot2::mpg)。

任务 B(趋势):用 economics 画个人储蓄率(psavert,单位:%)的折线图:横轴 date,补齐标题、带单位的轴名与 caption(数据来源:ggplot2::economics)。配一句话:它适合回答什么问题?

6.2 四条验收标准

  • 骨架对:data、aes()、geom_*() 三件套齐全,层与层之间用 + 连接,没忘写
  • 图型对:数个数 geom_bar(),画数值 geom_col(),看趋势 geom_line()
  • 文字全:标题 + 带单位的轴名,必要时 caption 写来源
  • 读得出来:一图一句话,说清它回答了什么问题

6.3 讲评:任务 A 的参考做法

6.4 讲评:任务 B 的参考做法

课后作业

ggplot2 语法与柱状图、折线图综合练习

除第 3 题复用课堂自造的 company_data 外,其余只用 R 内置数据(mpg、economics),代码与结果都保留在可见代码块中:

  1. 验证:mpg 共 234 行,按 class 分组计数加总应等于 234。
  2. 用 geom_bar() 画一张“各厂牌车辆数”的图,用 geom_col() 画一张“各厂牌平均城市油耗”的图,两张都配 labs() 标题与带单位的轴名,数据来源写进 caption。

综合练习(续)

  1. 用课堂上的 company_data:画一张“各信用评级的公司数”(geom_bar()),再画一张“各公司全年利润、从小到大”的横向柱状图(profit 列,geom_col() + reorder()),两张都补齐标题与带单位的轴名。
  2. 用 economics 画一张失业人数折线图;用 reorder() 把 mpg 中各车型的平均高速油耗排序,再画一张横向版(交换 x 与 y)。

下讲预告

第17讲:常见图表类型(二)——直方图、散点图和箱形图

  • 直方图 geom_histogram():一个数值变量的分布形状
  • 散点图 geom_point(): 两个数值型变量的关系
  • 箱线图 geom_boxplot():多组分布的中位数、离散程度与异常值
  • 颜色与图例:把“颜色有意义”这条底线落到实处

提示

课前准备:留着你的 mpg 与 company_data。下节课要在数值变量上直接画分布图——今天学的 aes() 映射与 labs() 规范,那节课会变成配色与图例的基本功。

谢谢!

第16讲:ggplot2 图形语法入门——柱状图与折线图

"The charts you choose shape the conclusion you leave behind." 语法不是为好看,而是让同一份数据能被反复追问:数据、映射、图层各归其位,换一种图只需换一个 geom。准确传达信息永远优先于视觉效果。

朱 奇 | 锦城大学 · 财会学院