R语言纵向数据分析实战:从宽数据到长数据的3步转换与4种可视化
纵向数据在医学研究、心理学实验和社会学调查中无处不在。想象一下,你手头有一份记录了500名患者在不同时间点的抑郁评分数据,或者是一组学生在连续三个学期的考试成绩。这类数据最迷人的地方在于,它们能揭示个体随时间变化的轨迹——就像给每个研究对象拍摄了一段"成长纪录片"。
但现实往往骨感。当你兴奋地打开数据集,看到的可能是这样的结构:
| 患者ID | 性别 | 基线评分 | 3个月评分 | 6个月评分 |
|---|---|---|---|---|
| 001 | 男 | 23 | 18 | 15 |
| 002 | 女 | 28 | 22 | 19 |
这种"宽格式"数据就像把多部电影胶片横向拼接在一起,虽然节省空间,却让时间维度的分析变得棘手。本文将带你用R语言的tidyr和ggplot2工具包,完成从数据整理到可视化的完整工作流。
1. 数据转换:三步骤重塑时间维度
1.1 理解长宽数据差异
宽格式数据的特点是:
- 每个时间点的测量值占据单独一列
- 时间信息隐藏在列名中(如"基线评分"、"3个月评分")
- 适合人类阅读但不利于分析
长格式数据的优势在于:
- 每个时间点的测量值独占一行
- 显式的时间变量列
- 适合统计建模和可视化
1.2 核心转换代码
使用tidyr::pivot_longer()完成转换:
library(tidyverse) # 示例牙科生长数据集 dental_wide <- data.frame( id = 1:10, sex = rep(c("男", "女"), each=5), y8 = rnorm(10, 25, 2), y10 = rnorm(10, 28, 2), y12 = rnorm(10, 30, 2) ) dental_long <- dental_wide %>% pivot_longer( cols = starts_with("y"), # 选择需要转换的列 names_to = "age", # 新列名:存储原列名 values_to = "distance" # 新列名:存储原数值 ) %>% mutate( age = parse_number(age), # 从"y8"提取数字8 age_factor = factor(paste(age, "岁")) # 创建因子型年龄 )转换后的数据结构:
| id | sex | age | distance | age_factor |
|---|---|---|---|---|
| 1 | 男 | 8 | 24.5 | 8 岁 |
| 1 | 男 | 10 | 27.8 | 10 岁 |
1.3 处理常见问题
问题1:列名模式不一致当时间点列名没有统一前缀时:
# 不规则列名处理示例 df_long <- df_wide %>% pivot_longer( cols = c(基线评分, 三月随访, 六月随访), names_to = "时间点", values_to = "评分" )问题2:需要保留元数据添加变量标签保持数据可读性:
library(labelled) dental_long <- dental_long %>% set_variable_labels( age = "测量时年龄(岁)", distance = "牙齿生长距离(mm)" )2. 探索性分析:四类可视化揭示时间模式
2.1 意大利面条图(Spaghetti Plot)
展示个体轨迹的经典方法:
ggplot(dental_long, aes(x = age, y = distance, group = id)) + geom_line(alpha = 0.5) + labs(x = "年龄(岁)", y = "生长距离(mm)") + theme_minimal()当样本量较大时(>50),建议添加趋势线:
ggplot(dental_long, aes(x = age, y = distance)) + geom_line(aes(group = id), alpha = 0.2) + geom_smooth(method = "lm", se = FALSE, color = "red", size = 1.5) + facet_wrap(~ sex) # 按性别分面2.2 分组箱线图
比较不同时间点的分布差异:
ggplot(dental_long, aes(x = age_factor, y = distance, fill = sex)) + geom_boxplot() + geom_jitter(width = 0.1, alpha = 0.3) + # 添加数据点 scale_fill_manual(values = c("#E69F00", "#56B4E9")) + labs(x = "", y = "牙齿生长距离(mm)", fill = "性别")关键参数说明:
width: 控制箱线图宽度alpha: 调整透明度避免重叠position_dodge: 分组并列显示
2.3 相关性矩阵
检验不同时间点测量值的关联强度:
library(GGally) # 使用宽格式数据 ggpairs(dental_wide, columns = 3:5, # 选择需要分析的列 mapping = aes(color = sex), lower = list(continuous = wrap("smooth", alpha = 0.3)))输出解读:
- 对角线:变量分布直方图
- 下三角:散点图与平滑曲线
- 上三角:相关系数及显著性
2.4 时间轨迹图
展示群体平均变化趋势:
dental_long %>% group_by(sex, age) %>% summarise( mean = mean(distance), se = sd(distance)/sqrt(n()) ) %>% ggplot(aes(x = age, y = mean, color = sex)) + geom_line() + geom_point(size = 3) + geom_errorbar(aes(ymin = mean - 1.96*se, ymax = mean + 1.96*se), width = 0.2) + scale_x_continuous(breaks = unique(dental_long$age)) + labs(x = "年龄(岁)", y = "平均生长距离(mm)", color = "性别")3. 进阶技巧:提升分析深度的实用方法
3.1 处理缺失数据
纵向数据常见缺失模式及处理:
| 缺失类型 | R代码示例 | 适用场景 |
|---|---|---|
| 列表删除 | na.omit() | 缺失较少时 |
| 线性插补 | approx() | 连续变量 |
| 多重插补 | mice::mice() | 复杂缺失 |
# 线性插补示例 dental_complete <- dental_long %>% group_by(id) %>% mutate( distance = approx(age, distance, xout = age)$y ) %>% ungroup()3.2 添加参考线
增强可视化解释性:
ggplot(dental_long, aes(x = age, y = distance)) + geom_hline(yintercept = 25, linetype = "dashed", color = "gray") + geom_vline(xintercept = 10, linetype = "dotted", color = "blue") + # ...其他图层3.3 交互式可视化
用plotly创建动态图表:
library(plotly) p <- ggplot(dental_long, aes(x = age, y = distance, color = sex)) + geom_line(aes(group = id), alpha = 0.3) + geom_smooth(se = FALSE) ggplotly(p)4. 案例实战:牙科生长数据分析全流程
4.1 数据准备
加载内置数据集并转换:
data("Orthodont", package = "nlme") ortho_long <- Orthodont %>% mutate( Subject = as.character(Subject), age = age - 8 # 以8岁为基准 )4.2 可视化组合
创建多面板图形:
library(patchwork) p1 <- ggplot(ortho_long, aes(x = age, y = distance, group = Subject)) + geom_line(alpha = 0.5) p2 <- ggplot(ortho_long, aes(x = age, y = distance, fill = Sex)) + geom_boxplot() p3 <- ggplot(ortho_long, aes(x = distance, fill = Sex)) + geom_density(alpha = 0.5) + facet_wrap(~ age) (p1 + p2) / p3 # 使用patchwork组合图形4.3 统计摘要
生成专业统计表格:
ortho_long %>% group_by(Sex, age) %>% summarise( Mean = mean(distance), SD = sd(distance), N = n(), .groups = "drop" ) %>% knitr::kable(digits = 2)输出示例:
| Sex | age | Mean | SD | N |
|---|---|---|---|---|
| 男 | 0 | 22.88 | 1.52 | 16 |
| 男 | 1 | 23.81 | 1.60 | 16 |
在RMarkdown环境中,添加kableExtra::kable_styling()可以让表格更美观。