好的,咱们来聊聊 R 语言里,怎么把两个时间序列图画在一张图里。这事儿吧,听着简单,但有时候确实需要点技巧,尤其是在你想让这两条线都看得清楚,而且能一目了然地看出它们之间的关系时。
我这就把经验给你捋一捋,一步步来,保证讲得明明白白。
核心思路:合并数据,然后使用绘图函数
说白了,把两个时间序列画在一张图上,最核心的思路就是:
1. 把这两个时间序列的数据放在同一个“容器”里。 这个容器最好能清晰地标识出哪个数据属于哪个时间序列,并且还得带着时间这个维度。
2. 使用 R 的绘图函数,把这个“容器”里的数据,按照时间和对应的值,画出来。
听着好像挺虚的?别急,咱们用实际的 R 代码来说话,你会发现它比想象的要容易。
准备工作:让你的数据“站好队”
首先,你需要有你的两个时间序列数据。在 R 里,时间序列数据最常见、最方便的形式是 `ts` 对象,或者一个包含时间戳和对应数值的 `data.frame` / `tibble`。
场景一:你已经有了两个 `ts` 对象
假设你已经有两个命名好的 `ts` 对象,比如 `ts_data1` 和 `ts_data2`。
```R
假设这是你的第一个时间序列数据(例如,每月的销售额)
咱们随便生成点数据模拟一下
start_date1 < as.Date("20220101")
end_date1 < as.Date("20231231")
dates1 < seq(start_date1, end_date1, by = "month")
values1 < cumsum(rnorm(length(dates1), mean = 5, sd = 2)) 模拟一些随机波动
ts_data1 < ts(values1, start = c(2022, 1), frequency = 12) 频率为12表示月度
假设这是你的第二个时间序列数据(例如,每月的广告投入)
start_date2 < as.Date("20220201") 可能开始时间略有不同
end_date2 < as.Date("20231130") 结束时间也可能不同
dates2 < seq(start_date2, end_date2, by = "month")
values2 < cumsum(rnorm(length(dates2), mean = 2, sd = 1)) 模拟另一组数据
ts_data2 < ts(values2, start = c(2022, 2), frequency = 12)
```
怎么把它们放在一张图里?
最直接的方法是创建一个新的 `data.frame`,把它们整合起来。但 `ts` 对象直接转成 `data.frame` 的时候,时间信息不太直观。这时候,我们可以利用 `ts` 对象本身的 `time()` 函数来获取时间点,然后把数值提取出来。
关键一步:统一时间轴
这里有个小陷阱:如果两个时间序列的时间范围不完全一致,或者开始/结束时间点有差异,你需要决定如何处理。 通常的做法是:
取两个时间序列的并集: 包含所有存在数据的时间点。
取两个时间序列的交集: 只包含两个时间序列都有数据的时段。
以其中一个为准,另一个进行填充: 例如,以数据较全的为基础,另一个序列在没有数据的地方填充 `NA`。
这里我们为了方便演示,假设它们的时间点大部分重叠,并且我们想看到从最早到最晚的全部区间。
```R
1. 获取所有唯一的时间点
all_times < sort(unique(c(time(ts_data1), time(ts_data2))))
2. 创建一个数据框来存储整合后的数据
注意:time() 函数对于 ts 对象,会返回一个数值型的时间索引,
例如 2022.0, 2022.0833, 2022.1667... (表示年份+月份的比例)
如果需要实际的日期,我们可能需要转换一下。
plot_data < data.frame(
Time = all_times
)
3. 将两个时间序列的值加入数据框,并在没有数据的地方填充 NA
这里要小心,time() 返回的数值索引可能和我们的 all_times 不直接匹配,
所以直接用 all_times 索引 ts 对象的值会报错。
更稳妥的方式是,先将 ts 对象转换为带日期的 data.frame
```
推荐的更稳妥做法:将 `ts` 对象转换为带日期的数据框
R 的 `stats` 包里有个 `fortify.ts` 函数(通常是 `ggplot2` 包调用),或者我们自己手动转换:
```R
载入 ggplot2 包,它提供了很多方便的功能
library(ggplot2)
将 ts 对象转换为 tibble (一种现代化的 data.frame)
并且格式化时间
ts_to_tibble < function(ts_obj, series_name) {
data.frame(
time = time(ts_obj),
value = as.numeric(ts_obj)
) %>%
dplyr::mutate(
将数值型的时间索引转换为实际日期(假设 frequency=12 表示月度)
start(ts_obj)[1] 是起始年份,start(ts_obj)[2] 是起始月份
date = seq.Date(
from = as.Date(sprintf("%d%02d01", start(ts_obj)[1], start(ts_obj)[2])), "%Y%m%d"),
by = "month",
length.out = length(ts_obj)
),
series = series_name
) %>%
dplyr::select(date, value, series) 只保留我们需要的列
}
df1 < ts_to_tibble(ts_data1, "Series 1")
df2 < ts_to_tibble(ts_data2, "Series 2")
合并两个数据框
使用 bind_rows from `dplyr`,它能很好地处理列名相同但可能来自不同源的数据
all_data_df < dplyr::bind_rows(df1, df2)
检查一下合并后的数据
head(all_data_df)
tail(all_data_df)
```
场景二:你的数据本来就是 `data.frame` 格式
如果你的数据已经是一个 `data.frame`,比如这样:
```R
假设你有两个数据框,分别代表两个序列,但都包含日期列
data_frame1 < data.frame(
Date = seq.Date(as.Date("20220101"), by = "month", length.out = 24),
Value1 = cumsum(rnorm(24, mean = 10, sd = 3))
)
data_frame2 < data.frame(
Date = seq.Date(as.Date("20220301"), by = "month", length.out = 22),
Value2 = cumsum(rnorm(22, mean = 15, sd = 4))
)
```
处理方式类似:
1. 转换格式:将每个数据框转换为一个“长格式”数据框,包含 `Date`、`Value` 和 `Series` 三列。
2. 合并:使用 `bind_rows` 合并。
```R
library(dplyr) 确保你已经加载了 dplyr
df_long1 < data_frame1 %>%
tidyr::pivot_longer(cols = Date, names_to = "series", values_to = "value") %>%
mutate(series = "Series A") 给第一个序列命名
df_long2 < data_frame2 %>%
tidyr::pivot_longer(cols = Date, names_to = "series", values_to = "value") %>%
mutate(series = "Series B") 给第二个序列命名
合并
all_data_long < bind_rows(df_long1, df_long2)
检查一下
head(all_data_long)
tail(all_data_long)
```
注意:在上面的例子中,`pivot_longer` 是一个非常强大的工具,可以将宽格式数据(多列代表不同变量)转换为长格式数据(一列存储变量名,一列存储变量值)。如果你本来就只有一个数值列,可以直接创建 `series` 列。
绘图:让你的数据“活起来”
现在我们有了整洁的 `all_data_df` (或者 `all_data_long`),就可以用 R 的强大绘图功能来画图了。最常用也最灵活的莫过于 `ggplot2`。
使用 `ggplot2` 绘制
`ggplot2` 的核心思想是“语法化”,把绘图的各个元素(数据、映射、几何对象、统计变换、标度、坐标系、主题)分开处理。
```R
确保你已经加载了 ggplot2
library(ggplot2)
使用我们前面创建的 all_data_df (从 ts 对象转换来的)
ggplot(all_data_df, aes(x = date, y = value, color = series)) +
geom_line() + 画线图
labs(
title = "对比两个时间序列",
x = "日期",
y = "数值",
color = "序列名称" Legend 的标题
) +
theme_minimal() 使用一个简洁的图表风格
```
如果你使用的是 `all_data_long` (从 data.frame 转换来的)
```R
同样是 ggplot2
ggplot(all_data_long, aes(x = Date, y = value, color = series)) +
geom_line() +
labs(
title = "对比两个时间序列",
x = "日期",
y = "数值",
color = "序列"
) +
theme_minimal()
```
关键点解读:
`ggplot(all_data_df, aes(x = date, y = value, color = series))`:
`ggplot()`: 初始化一个 ggplot 对象,指定要使用的数据集 (`all_data_df`)。
`aes(x = date, y = value, color = series)`: 这是“美学映射”。
`x = date`: 把 `date` 列映射到 x 轴。
`y = value`: 把 `value` 列映射到 y 轴。
`color = series`: 把 `series` 列映射到颜色。这样,`ggplot2` 就会自动为不同的 `series` 赋予不同的颜色,并在图例中显示出来。
`+ geom_line()`: 这是一个“几何对象层”。`geom_line()` 告诉 `ggplot2` 使用线条来表示数据。因为我们在 `aes()` 里已经指定了 `color = series`,`geom_line()` 会根据 `series` 的不同值,画出不同的颜色线条。
`+ labs(...)`: 用来添加标题、轴标签和图例标题,让图表更易读。
`+ theme_minimal()`: 这是一个“主题”。`ggplot2` 提供了很多主题(如 `theme_bw()`, `theme_classic()` 等),可以快速改变图表的整体外观,去除一些不必要的背景元素,让图更干净。
进阶技巧:调整外观与添加辅助信息
1. 调整颜色和线型
如果你想手动指定颜色,或者让不同序列使用不同的线型(实线、虚线等),可以这样做:
```R
ggplot(all_data_df, aes(x = date, y = value, color = series, linetype = series)) +
geom_line() +
scale_color_manual(values = c("Series 1" = "blue", "Series 2" = "red")) + 手动指定颜色
scale_linetype_manual(values = c("Series 1" = "solid", "Series 2" = "dashed")) + 手动指定线型
labs(
title = "对比两个时间序列 (自定义颜色和线型)",
x = "日期",
y = "数值",
color = "序列",
linetype = "序列"
) +
theme_minimal()
```
2. 双 Y 轴(谨慎使用)
有时候,两个时间序列的数值范围差异很大,一条 Y 轴可能导致其中一个序列的细节看不清。这时可以考虑使用双 Y 轴。但是! 双 Y 轴容易让图表产生误导,建议在非常必要的情况下使用,并且要清晰地标明每个 Y 轴代表什么。
在 `ggplot2` 里实现双 Y 轴,通常需要自定义标度(scale)和转换。一个常见的方法是使用 `sec_axis`:
```R
假设 Series 2 的数值范围比 Series 1 大很多
我们在 Series 1 的右侧添加一个 Y 轴,并对 Series 2 的值进行缩放
ggplot(all_data_df, aes(x = date)) +
geom_line(aes(y = value, color = series), data = subset(all_data_df, series == "Series 1")) +
geom_line(aes(y = value 10, color = series), data = subset(all_data_df, series == "Series 2")) + 假设 Series 2 的值需要乘以 10 来匹配 Series 1 的范围
scale_y_continuous(
name = "Series 1 的数值",
sec.axis = sec_axis(~ . / 10, name = "Series 2 的数值") 右侧 Y 轴的标签和转换
) +
scale_color_manual(values = c("Series 1" = "blue", "Series 2" = "red")) +
labs(
title = "双 Y 轴对比时间序列 (谨慎使用)",
x = "日期",
color = "序列"
) +
theme_minimal()
```
注意:上面的 `value 10` 和 `~ . / 10` 是示例,实际使用时需要根据你的数据来调整缩放因子。`sec_axis` 的第一个参数 `~ . / 10` 是一个函数,它接收右侧 Y 轴上的数值,并进行逆向转换,以得到左侧 Y 轴上的实际值(或者反之)。
3. 添加垂直线或阴影区域
如果你想标出某个重要的时间点,比如一个事件发生的时间,可以在图上添加垂直线。
```R
假设一个重要事件发生在 2022 年 6 月 15 日
event_date < as.Date("20220615")
ggplot(all_data_df, aes(x = date, y = value, color = series)) +
geom_line() +
geom_vline(xintercept = event_date, linetype = "dashed", color = "gray40") + 添加垂直线
annotate("text", x = event_date + 30, y = max(all_data_df$value, na.rm = TRUE), label = "关键事件",hjust = 0, color = "gray40") + 添加文本说明
labs(
title = "对比两个时间序列,标记关键事件",
x = "日期",
y = "数值",
color = "序列"
) +
theme_minimal()
```
总结一下步骤
1. 获取你的时间序列数据。 无论是 `ts` 对象还是 `data.frame`。
2. 将数据转换为适合绘图的格式。
如果原始是 `ts` 对象,转换为一个包含 `date`, `value`, `series` 列的 `data.frame` 或 `tibble`。
如果原始就是 `data.frame`,确保它有 `Date`, `Value` 列,然后如果每个序列在不同列,先用 `pivot_longer` 转换成“长格式”。
3. 合并所有序列到一个数据框。 确保时间轴是统一的。
4. 使用 `ggplot2` 进行绘图。
`ggplot(data, aes(x = time_column, y = value_column, color = series_column))`
`+ geom_line()`
根据需要添加 `labs()`, `theme_()`, `scale_()` 等。
掌握了这些,你就可以灵活地把任何两个(或更多)时间序列画在同一张图上,并根据你的需求来调整它们的外观和信息展示。希望这个详细的说明能帮到你!