前言

数据清洗与预处理是数据分析中最重要但也最耗时的环节。据统计,数据科学家约80%的时间都花在数据清洗上。本教程将系统介绍R语言中数据清洗与预处理的各项技术,帮助你高效地处理各种数据问题。

本教程涵盖以下内容:

章节 主题 核心内容
第一章 数据探索与概览 了解数据的基本情况
第二章 缺失值处理 识别和处理缺失数据
第三章 重复值与异常值处理 数据质量问题解决
第四章 数据类型转换与变量重编码 数据格式规范化
第五章 数据筛选与排序 数据子集选择
第六章 数据变换与计算 创建新变量和汇总统计
第七章 数据合并与连接 多数据源整合
第八章 数据重塑 长宽格式转换
第九章 字符与日期处理 特殊数据类型处理
第十章 分组与聚合操作 分组计算
第十一章 数据采样与分割 数据集划分
第十二章 数据清洗实战技巧 综合应用

在Rstudio中安装本节课所有需要的包,运行以下指令:

install.packages(c(
  "dplyr", "tidyr", "psych", "VIM", "visdat", 
  "janitor", "zoo", "mice", "DescTools", "forcats",
  "Hmisc", "caret", "rsample", "caTools", "fastDummies",
  "data.table", "fuzzyjoin", "stringr", "lubridate", 
  "xts", "readxl", "writexl", "DBI", "tidylog",
  "validate", "assertthat"
))

第一章:数据探索与概览

在开始任何数据分析之前,我们首先需要对数据有一个整体的了解。本章介绍各种数据探索方法,帮助你快速掌握数据的基本情况。

1.1 查看数据维度

了解数据的大小是第一步,我们需要知道数据有多少行(观测)和多少列(变量)。

# 加载必要的包
library(dplyr)

# 创建一个示例数据框
# 这个数据框模拟了一个简单的学生信息表
student_data <- data.frame(
  id = 1:10,                          # 学生ID,从1到10
  name = c("张三", "李四", "王五", "赵六", "钱七", 
           "孙八", "周九", "吴十", "郑一", "陈二"),  # 学生姓名
  age = c(20, 21, 19, 22, 20, 21, 19, 23, 20, 21),  # 年龄
  score = c(85, 90, 78, 92, 88, 76, 95, 89, 82, 91), # 成绩
  gender = c("M", "F", "M", "F", "M", "F", "M", "F", "M", "F")  # 性别
)

# dim()函数返回数据的维度(行数和列数)
# 第一个数字是行数,第二个数字是列数
dim(student_data)
## [1] 10  5
# nrow()函数只返回行数(观测数量)
nrow(student_data)
## [1] 10
# ncol()函数只返回列数(变量数量)
ncol(student_data)
## [1] 5

小结dim()nrow()ncol()是最基础的数据维度查看函数,可以快速了解数据规模。

1.2 查看数据结构

数据结构告诉我们每个变量的类型(数值型、字符型、因子型等)以及数据的整体组织方式。

# str()函数显示数据的详细结构
# 包括:变量名、变量类型、前几个值
str(student_data)
## 'data.frame':    10 obs. of  5 variables:
##  $ id    : int  1 2 3 4 5 6 7 8 9 10
##  $ name  : chr  "张三" "李四" "王五" "赵六" ...
##  $ age   : num  20 21 19 22 20 21 19 23 20 21
##  $ score : num  85 90 78 92 88 76 95 89 82 91
##  $ gender: chr  "M" "F" "M" "F" ...
# glimpse()函数来自dplyr包,显示更紧凑的结构信息
# 它以横向方式展示,更适合宽表格
glimpse(student_data)
## Rows: 10
## Columns: 5
## $ id     <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
## $ name   <chr> "张三", "李四", "王五", "赵六", "钱七", "孙八", "周九", "吴十", "郑一", "陈二"…
## $ age    <dbl> 20, 21, 19, 22, 20, 21, 19, 23, 20, 21
## $ score  <dbl> 85, 90, 78, 92, 88, 76, 95, 89, 82, 91
## $ gender <chr> "M", "F", "M", "F", "M", "F", "M", "F", "M", "F"

小结str()是R内置函数,glimpse()来自dplyr包,两者都能快速了解数据结构。

1.3 查看变量名称

知道变量名称可以帮助我们后续选择和操作特定列。

# names()函数返回所有变量名称(列名)
names(student_data)
## [1] "id"     "name"   "age"    "score"  "gender"
# colnames()函数与names()功能相同,专门用于数据框
colnames(student_data)
## [1] "id"     "name"   "age"    "score"  "gender"
# rownames()函数返回行名
rownames(student_data)
##  [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10"

小结names()colnames()可以互换使用,都能获取列名。

1.4 查看数据前/后几行

快速预览数据的前几行或后几行,可以了解数据的实际内容。

# head()函数默认显示前6行
head(student_data)
##   id name age score gender
## 1  1 张三  20    85      M
## 2  2 李四  21    90      F
## 3  3 王五  19    78      M
## 4  4 赵六  22    92      F
## 5  5 钱七  20    88      M
## 6  6 孙八  21    76      F
# 可以指定显示的行数,例如显示前3行
head(student_data, n = 3)
##   id name age score gender
## 1  1 张三  20    85      M
## 2  2 李四  21    90      F
## 3  3 王五  19    78      M
# tail()函数默认显示后6行
tail(student_data)
##    id name age score gender
## 5   5 钱七  20    88      M
## 6   6 孙八  21    76      F
## 7   7 周九  19    95      M
## 8   8 吴十  23    89      F
## 9   9 郑一  20    82      M
## 10 10 陈二  21    91      F
# 显示后4行
tail(student_data, n = 4)
##    id name age score gender
## 7   7 周九  19    95      M
## 8   8 吴十  23    89      F
## 9   9 郑一  20    82      M
## 10 10 陈二  21    91      F

小结head()tail()是快速预览数据的利器,建议在导入数据后立即使用。

1.5 数据摘要统计

摘要统计帮助我们了解数值变量的分布情况,包括最小值、最大值、均值、中位数等。

# summary()函数提供每个变量的基本统计摘要
# 对于数值变量:显示最小值、四分位数、均值、最大值
# 对于字符变量:显示长度和类型
summary(student_data)
##        id            name                age           score      
##  Min.   : 1.00   Length:10          Min.   :19.0   Min.   :76.00  
##  1st Qu.: 3.25   Class :character   1st Qu.:20.0   1st Qu.:82.75  
##  Median : 5.50   Mode  :character   Median :20.5   Median :88.50  
##  Mean   : 5.50                      Mean   :20.6   Mean   :86.60  
##  3rd Qu.: 7.75                      3rd Qu.:21.0   3rd Qu.:90.75  
##  Max.   :10.00                      Max.   :23.0   Max.   :95.00  
##     gender         
##  Length:10         
##  Class :character  
##  Mode  :character  
##                    
##                    
## 
# describe()函数来自psych包,提供更详细的统计信息
# 包括:变量数量、均值、标准差、中位数、偏度、峰度等
# 如果没有安装psych包,先运行:install.packages("psych")
library(psych)
describe(student_data)
## student_data 
## 
##  5  Variables      10  Observations
## --------------------------------------------------------------------------------
## id 
##        n  missing distinct     Info     Mean  pMedian      Gmd      .05 
##       10        0       10        1      5.5      5.5    3.667     1.45 
##      .10      .25      .50      .75      .90      .95 
##     1.90     3.25     5.50     7.75     9.10     9.55 
##                                                   
## Value        1   2   3   4   5   6   7   8   9  10
## Frequency    1   1   1   1   1   1   1   1   1   1
## Proportion 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1
## --------------------------------------------------------------------------------
## name 
##        n  missing distinct 
##       10        0       10 
##                                                                       
## Value       陈二  李四  钱七  孙八  王五  吴十  张三  赵六  郑一  周九
## Frequency    1     1     1     1     1     1     1     1     1     1  
## Proportion 0.1   0.1   0.1   0.1   0.1   0.1   0.1   0.1   0.1   0.1  
## --------------------------------------------------------------------------------
## age 
##        n  missing distinct     Info     Mean  pMedian      Gmd 
##       10        0        5    0.945     20.6     20.5    1.467 
##                               
## Value       19  20  21  22  23
## Frequency    2   3   3   1   1
## Proportion 0.2 0.3 0.3 0.1 0.1
## --------------------------------------------------------------------------------
## score 
##        n  missing distinct     Info     Mean  pMedian      Gmd      .05 
##       10        0       10        1     86.6       87    7.333    76.90 
##      .10      .25      .50      .75      .90      .95 
##    77.80    82.75    88.50    90.75    92.30    93.65 
##                                                   
## Value       76  78  82  85  88  89  90  91  92  95
## Frequency    1   1   1   1   1   1   1   1   1   1
## Proportion 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1
## --------------------------------------------------------------------------------
## gender 
##        n  missing distinct 
##       10        0        2 
##                   
## Value        F   M
## Frequency    5   5
## Proportion 0.5 0.5
## --------------------------------------------------------------------------------

小结summary()是R内置函数,describe()来自psych包,提供更丰富的统计信息。

1.6 缺失值整体概览

缺失值是数据清洗中常见的问题,首先需要了解数据中缺失值的整体情况。

# 创建一个包含缺失值的数据框
# 用于演示缺失值处理
data_with_na <- data.frame(
  id = 1:8,
  value1 = c(10, NA, 30, 40, NA, 60, 70, 80),      # value1有2个缺失值
  value2 = c(NA, 2, 3, NA, 5, 6, NA, 8),           # value2有3个缺失值
  category = c("A", "B", NA, "A", "B", NA, "A", "B") # category有2个缺失值
)

# is.na()函数检测每个值是否为缺失值
# 返回一个与原数据维度相同的逻辑矩阵
is.na(data_with_na)
##         id value1 value2 category
## [1,] FALSE  FALSE   TRUE    FALSE
## [2,] FALSE   TRUE  FALSE    FALSE
## [3,] FALSE  FALSE  FALSE     TRUE
## [4,] FALSE  FALSE   TRUE    FALSE
## [5,] FALSE   TRUE  FALSE    FALSE
## [6,] FALSE  FALSE  FALSE     TRUE
## [7,] FALSE  FALSE   TRUE    FALSE
## [8,] FALSE  FALSE  FALSE    FALSE
# sum(is.na())计算缺失值的总数量
# TRUE被当作1,FALSE被当作0
sum(is.na(data_with_na))
## [1] 7
# colSums(is.na())计算每列的缺失值数量
colSums(is.na(data_with_na))
##       id   value1   value2 category 
##        0        2        3        2
# 计算每列缺失值的比例
# 用每列缺失值数量除以总行数
colSums(is.na(data_with_na)) / nrow(data_with_na)
##       id   value1   value2 category 
##    0.000    0.250    0.375    0.250

小结is.na()检测缺失值,配合sum()colSums()可以快速统计缺失情况。

1.7 可视化缺失模式

可视化方法可以更直观地展示缺失值的分布模式。

# VIM包提供了丰富的缺失值可视化功能
# 如果没有安装,先运行:install.packages("VIM")
library(VIM)

# aggr()函数绘制缺失值聚合图
# 左图显示每列缺失比例,右图显示缺失值组合模式
aggr(data_with_na, 
     numbers = TRUE,      # 显示具体数值
     prop = TRUE,         # 显示比例而非数量
     combined = FALSE)    # 分开展示两个图

# visdat包提供了另一种可视化方式
# 如果没有安装,先运行:install.packages("visdat")
library(visdat)

# vis_miss()函数创建缺失值热力图
# 黑色表示缺失,灰色表示存在
vis_miss(data_with_na)

# vis_dat()函数显示数据类型和缺失情况
vis_dat(data_with_na)

小结:VIM包和visdat包提供了直观的缺失值可视化方法,推荐在数据探索阶段使用。

1.8 数据分布概览

了解数值变量的分布情况,可以帮助发现异常值和数据特征。

# hist()函数绘制直方图,展示数值分布
# main参数设置标题,xlab参数设置x轴标签
hist(student_data$score, 
     main = "学生成绩分布直方图",
     xlab = "成绩",
     col = "lightblue",
     breaks = 5)  # breaks设置分组数量

# boxplot()函数绘制箱线图
# 可以直观展示中位数、四分位数和异常值
boxplot(student_data$score,
        main = "学生成绩箱线图",
        ylab = "成绩",
        col = "lightgreen")

# 按性别分组绘制箱线图
# 公式写法:y ~ group
boxplot(score ~ gender, data = student_data,
        main = "不同性别学生成绩分布",
        xlab = "性别",
        ylab = "成绩",
        col = c("lightblue", "lightpink"))

小结:直方图展示整体分布,箱线图展示分布特征和异常值,两者配合使用效果更好。

1.9 数据类型概览

了解每个变量的数据类型是数据处理的基础。

# class()函数返回对象的类
class(student_data)        # 数据框类型
## [1] "data.frame"
class(student_data$name)   # 字符型
## [1] "character"
class(student_data$age)    # 数值型(整数)
## [1] "numeric"
class(student_data$score)  # 数值型(实数)
## [1] "numeric"
# typeof()函数返回对象在内存中的存储类型
# 比class()更底层
typeof(student_data$id)    # "integer"整数
## [1] "integer"
typeof(student_data$score) # "double"双精度浮点数
## [1] "double"
typeof(student_data$name)  # "character"字符
## [1] "character"
# 查看所有列的类型
sapply(student_data, class)
##          id        name         age       score      gender 
##   "integer" "character"   "numeric"   "numeric" "character"

小结class()返回高级类型,typeof()返回底层存储类型,两者配合使用可以全面了解数据类型。


第一章总结

函数 用途 返回值
dim() 查看维度 行数和列数
nrow() 查看行数 行数
ncol() 查看列数 列数
str() 查看结构 变量类型和前几值
glimpse() 查看结构(dplyr) 紧凑的结构信息
names() 查看列名 列名向量
head() 查看前几行 前n行数据
tail() 查看后几行 后n行数据
summary() 摘要统计 统计摘要
is.na() 检测缺失值 逻辑矩阵
class() 查看类型 对象类
typeof() 查看存储类型 存储类型

第二章:缺失值处理

缺失值是现实数据中最常见的问题之一。正确处理缺失值对于保证分析结果的准确性至关重要。本章介绍缺失值的识别、统计和各种处理方法。

2.1 缺失值识别

首先,我们需要识别数据中的缺失值位置。

# 创建一个包含多种缺失情况的示例数据
sales_data <- data.frame(
  product = c("A", "B", "C", "D", "E", "F", "G", "H"),
  price = c(100, NA, 200, 150, NA, 180, 220, NA),
  quantity = c(10, 20, NA, 15, 25, NA, 30, 12),
  revenue = c(1000, NA, NA, 2250, 3000, NA, 6600, NA)
)

# is.na()检测每个值是否为NA
# 返回逻辑向量,TRUE表示缺失
is.na(sales_data$price)
## [1] FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE
# which()配合is.na()找出缺失值的位置索引
which(is.na(sales_data$price))
## [1] 2 5 8
# complete.cases()函数识别完整的行(没有缺失值的行)
# 返回逻辑向量,TRUE表示该行完整
complete.cases(sales_data)
## [1]  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE
# 查看完整的行
sales_data[complete.cases(sales_data), ]
##   product price quantity revenue
## 1       A   100       10    1000
## 4       D   150       15    2250
## 7       G   220       30    6600
# 查看有缺失值的行
# 用感叹号取反
sales_data[!complete.cases(sales_data), ]
##   product price quantity revenue
## 2       B    NA       20      NA
## 3       C   200       NA      NA
## 5       E    NA       25    3000
## 6       F   180       NA      NA
## 8       H    NA       12      NA

小结is.na()检测单个值,complete.cases()识别完整行,两者配合可以精确定位缺失值。

2.2 缺失值统计

了解缺失值的数量和比例,有助于决定处理策略。

# 计算每列缺失值数量
colSums(is.na(sales_data))
##  product    price quantity  revenue 
##        0        3        2        4
# 计算每列缺失值比例
colSums(is.na(sales_data)) / nrow(sales_data)
##  product    price quantity  revenue 
##    0.000    0.375    0.250    0.500
# 计算每行缺失值数量
rowSums(is.na(sales_data))
## [1] 0 2 2 0 1 2 0 2
# 计算总缺失值数量
sum(is.na(sales_data))
## [1] 9
# 计算总缺失值比例
sum(is.na(sales_data)) / (nrow(sales_data) * ncol(sales_data))
## [1] 0.28125
# 创建一个缺失值统计汇总表
missing_summary <- data.frame(
  variable = names(sales_data),
  missing_count = colSums(is.na(sales_data)),
  missing_percent = round(colSums(is.na(sales_data)) / nrow(sales_data) * 100, 2)
)
missing_summary
##          variable missing_count missing_percent
## product   product             0             0.0
## price       price             3            37.5
## quantity quantity             2            25.0
## revenue   revenue             4            50.0

小结:统计缺失值数量和比例,可以帮助判断哪些变量需要重点处理。

2.3 删除缺失值

当缺失值较少时,可以直接删除含有缺失值的行或列。

# na.omit()函数删除含有缺失值的行
# 返回只包含完整行的数据框
na.omit(sales_data)
##   product price quantity revenue
## 1       A   100       10    1000
## 4       D   150       15    2250
## 7       G   220       30    6600
# 使用dplyr包的drop_na()函数
# 功能更灵活,可以指定列
library(dplyr)

# 删除所有含有缺失值的行
drop_na(sales_data)
## drop_na: removed 5 rows (62%), 3 rows remaining
##   product price quantity revenue
## 1       A   100       10    1000
## 2       D   150       15    2250
## 3       G   220       30    6600
# 只删除price列有缺失值的行
drop_na(sales_data, price)
## drop_na: removed 3 rows (38%), 5 rows remaining
##   product price quantity revenue
## 1       A   100       10    1000
## 2       C   200       NA      NA
## 3       D   150       15    2250
## 4       F   180       NA      NA
## 5       G   220       30    6600
# 删除price和quantity列都有缺失值的行
drop_na(sales_data, c(price, quantity))
## drop_na: removed 5 rows (62%), 3 rows remaining
##   product price quantity revenue
## 1       A   100       10    1000
## 2       D   150       15    2250
## 3       G   220       30    6600

小结na.omit()简单直接,drop_na()更灵活,可以根据需要选择使用。

2.4 删除全为缺失的行或列

有时数据中存在完全为空的行或列,需要清理。

# 创建一个含有全空行和列的数据框
messy_data <- data.frame(
  x = c(1, NA, 3, NA),
  y = c(NA, NA, NA, NA),  # 全为NA的列
  z = c(5, 6, NA, 8)
)
messy_data <- rbind(messy_data, c(NA, NA, NA))  # 添加全为NA的行

# janitor包提供了便捷的清理函数
# 如果没有安装,先运行:install.packages("janitor")
library(janitor)

# remove_empty()删除全为NA的行和列
remove_empty(messy_data)
## value for "which" not specified, defaulting to c("rows", "cols")
##    x  z
## 1  1  5
## 2 NA  6
## 3  3 NA
## 4 NA  8
# 只删除全为NA的列
remove_empty(messy_data, which = "cols")
##    x  z
## 1  1  5
## 2 NA  6
## 3  3 NA
## 4 NA  8
## 5 NA NA
# 只删除全为NA的行
remove_empty(messy_data, which = "rows")
##    x  y  z
## 1  1 NA  5
## 2 NA NA  6
## 3  3 NA NA
## 4 NA NA  8

小结janitor::remove_empty()可以快速清理空白行列,非常实用。

2.5 缺失值填补——均值/中位数/众数填补

当缺失值较多时,删除会损失大量信息,此时可以考虑填补。

# 创建一个用于演示填补的数据
fill_data <- data.frame(
  id = 1:10,
  value = c(10, NA, 30, 25, NA, 35, 40, NA, 45, 50)
)

# 计算均值(忽略缺失值)
mean_value <- mean(fill_data$value, na.rm = TRUE)
mean_value
## [1] 33.57143
# 用均值填补缺失值
fill_data$value_mean <- ifelse(
  is.na(fill_data$value),           # 条件:是否为缺失值
  mean_value,                        # 如果是,用均值填补
  fill_data$value                    # 如果不是,保持原值
)

# 用中位数填补
median_value <- median(fill_data$value, na.rm = TRUE)
fill_data$value_median <- ifelse(
  is.na(fill_data$value),
  median_value,
  fill_data$value
)

# 查看填补结果
fill_data
##    id value value_mean value_median
## 1   1    10   10.00000           10
## 2   2    NA   33.57143           35
## 3   3    30   30.00000           30
## 4   4    25   25.00000           25
## 5   5    NA   33.57143           35
## 6   6    35   35.00000           35
## 7   7    40   40.00000           40
## 8   8    NA   33.57143           35
## 9   9    45   45.00000           45
## 10 10    50   50.00000           50
# 对于分类变量,可以用众数填补
# 创建一个含有缺失值的分类变量
category_data <- c("A", "B", "A", NA, "A", "B", NA, "A")

# 计算众数(出现次数最多的值)
# 使用table()统计频次
freq_table <- table(category_data, useNA = "ifany")
mode_value <- names(freq_table[which.max(freq_table)])
mode_value
## [1] "A"
# 用众数填补
category_data_filled <- ifelse(is.na(category_data), mode_value, category_data)
category_data_filled
## [1] "A" "B" "A" "A" "A" "B" "A" "A"

小结:均值适合正态分布数据,中位数适合偏态分布,众数适合分类变量。

2.6 缺失值填补——前向/后向填补

时间序列数据中,常用前后值来填补缺失值。

# 创建时间序列数据
time_data <- data.frame(
  day = 1:10,
  value = c(10, NA, NA, 40, 50, NA, 70, 80, NA, 100)
)

# 使用tidyr包的fill()函数进行前向/后向填补
library(tidyr)

# 前向填补:用前一个非缺失值填补
fill(time_data, value, .direction = "down")
## fill: changed 4 values (40%) of 'value' (4 fewer NAs)
##    day value
## 1    1    10
## 2    2    10
## 3    3    10
## 4    4    40
## 5    5    50
## 6    6    50
## 7    7    70
## 8    8    80
## 9    9    80
## 10  10   100
# 后向填补:用后一个非缺失值填补
fill(time_data, value, .direction = "up")
## fill: changed 4 values (40%) of 'value' (4 fewer NAs)
##    day value
## 1    1    10
## 2    2    40
## 3    3    40
## 4    4    40
## 5    5    50
## 6    6    70
## 7    7    70
## 8    8    80
## 9    9   100
## 10  10   100
# 双向填补:先向后再向前(或相反)
fill(time_data, value, .direction = "downup")
## fill: changed 4 values (40%) of 'value' (4 fewer NAs)
##    day value
## 1    1    10
## 2    2    10
## 3    3    10
## 4    4    40
## 5    5    50
## 6    6    50
## 7    7    70
## 8    8    80
## 9    9    80
## 10  10   100

小结fill()函数简单易用,特别适合时间序列数据的缺失值填补。

2.7 缺失值填补——线性插值

对于连续型数据,线性插值是一种更精确的填补方法。

# 使用approx()函数进行线性插值
# 创建一个包含缺失值的向量
x <- 1:10
y <- c(1, NA, NA, 4, 5, NA, 7, 8, NA, 10)

# approx()进行线性插值
# x参数是自变量,y参数是因变量
interp_result <- approx(x = x[!is.na(y)],    # 非缺失位置的x
                        y = y[!is.na(y)],    # 非缺失位置的y
                        xout = x)            # 需要插值的位置

# 查看插值结果
interp_result$y
##  [1]  1  2  3  4  5  6  7  8  9 10
# zoo包提供了更便捷的插值函数
# 如果没有安装,先运行:install.packages("zoo")
library(zoo)

# na.approx()进行线性插值
na.approx(y)
##  [1]  1  2  3  4  5  6  7  8  9 10
# na.spline()进行样条插值(更平滑)
na.spline(y)
##  [1]  1  2  3  4  5  6  7  8  9 10

小结:线性插值假设数据点之间呈线性关系,适合变化趋势平稳的数据。

2.8 缺失值填补——预测模型填补

当变量之间存在相关性时,可以用其他变量预测缺失值。

# mice包(Multivariate Imputation by Chained Equations)
# 是最常用的多重插值包
# 如果没有安装,先运行:install.packages("mice")
library(mice)

# 创建示例数据
model_data <- data.frame(
  x1 = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
  x2 = c(2, 4, NA, 8, 10, 12, NA, 16, 18, 20),
  x3 = c(3, 6, 9, NA, 15, 18, 21, NA, 27, 30)
)

# 查看缺失模式
md.pattern(model_data)

# 使用mice进行多重插值
# m = 5 表示生成5个插值数据集
# method = "pmm" 表示预测均值匹配法
imp <- mice(model_data, m = 5, method = "pmm", seed = 123)

# 查看插值结果
imp$imp$x2  # x2变量的5次插值结果

# 获取完整的插值数据集(取第一个)
complete_data <- complete(imp, 1)
complete_data

小结mice包提供了多种插值方法,适合复杂数据的缺失值处理。

2.9 缺失值填补——KNN填补

K近邻填补使用相似样本的值来填补缺失值。

# VIM包提供了kNN()函数
library(VIM)

# 创建示例数据
knn_data <- data.frame(
  a = c(1, 2, NA, 4, 5, 6, 7, 8),
  b = c(2, 4, 6, 8, 10, NA, 14, 16),
  c = c(3, 6, 9, 12, 15, 18, NA, 24)
)

# kNN填补
# k = 5 表示使用5个最近邻
knn_result <- kNN(knn_data, k = 5)

# 查看填补结果
knn_result

小结:KNN填补基于相似性原理,适合样本之间有相似性的数据。

2.10 缺失值处理——虚拟变量指示

有时缺失本身可能包含信息,可以创建缺失指示变量。

# 创建缺失指示变量
sales_data$price_missing <- as.integer(is.na(sales_data$price))
sales_data$quantity_missing <- as.integer(is.na(sales_data$quantity))

# 查看结果
sales_data
##   product price quantity revenue price_missing quantity_missing
## 1       A   100       10    1000             0                0
## 2       B    NA       20      NA             1                0
## 3       C   200       NA      NA             0                1
## 4       D   150       15    2250             0                0
## 5       E    NA       25    3000             1                0
## 6       F   180       NA      NA             0                1
## 7       G   220       30    6600             0                0
## 8       H    NA       12      NA             1                0
# 使用dplyr批量创建缺失指示变量
library(dplyr)

sales_data %>%
  mutate(
    across(everything(), 
           ~ as.integer(is.na(.)), 
           .names = "{.col}_missing")
  )
## mutate: new variable 'product_missing' (integer) with one unique value and 0% NA
##         new variable 'revenue_missing' (integer) with 2 unique values and 0% NA
##         new variable 'price_missing_missing' (integer) with one unique value and 0% NA
##         new variable 'quantity_missing_missing' (integer) with one unique value and 0% NA
##   product price quantity revenue price_missing quantity_missing product_missing
## 1       A   100       10    1000             0                0               0
## 2       B    NA       20      NA             1                0               0
## 3       C   200       NA      NA             0                1               0
## 4       D   150       15    2250             0                0               0
## 5       E    NA       25    3000             1                0               0
## 6       F   180       NA      NA             0                1               0
## 7       G   220       30    6600             0                0               0
## 8       H    NA       12      NA             1                0               0
##   revenue_missing price_missing_missing quantity_missing_missing
## 1               0                     0                        0
## 2               1                     0                        0
## 3               1                     0                        0
## 4               0                     0                        0
## 5               0                     0                        0
## 6               1                     0                        0
## 7               0                     0                        0
## 8               1                     0                        0

小结:缺失指示变量可以捕捉”缺失”这一信息,在某些分析中很有价值。


第二章总结

方法 适用场景 优点 缺点
删除法 缺失比例小 简单直接 损失信息
均值/中位数填补 数值变量 保持样本量 降低方差
前向/后向填补 时间序列 保持时序性 不适合随机缺失
线性插值 连续变量 较精确 假设线性关系
预测模型填补 变量相关 利用变量关系 计算复杂
KNN填补 样本相似 利用样本相似性 需要定义距离

第三章:重复值与异常值处理

数据质量直接影响分析结果的可靠性。本章介绍如何识别和处理重复值与异常值。

3.1 重复行识别

重复数据会导致分析结果偏差,需要识别并处理。

# 创建包含重复行的数据
dup_data <- data.frame(
  id = c(1, 2, 3, 2, 4, 3, 5),
  name = c("A", "B", "C", "B", "D", "C", "E"),
  value = c(10, 20, 30, 20, 40, 30, 50)
)

# duplicated()函数识别重复行
# 返回逻辑向量,TRUE表示该行是重复的
duplicated(dup_data)
## [1] FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE
# 查看重复的行
dup_data[duplicated(dup_data), ]
##   id name value
## 4  2    B    20
## 6  3    C    30
# 查看所有重复行(包括第一次出现的)
# 使用条件:该行出现次数大于1
dup_data[duplicated(dup_data) | duplicated(dup_data, fromLast = TRUE), ]
##   id name value
## 2  2    B    20
## 3  3    C    30
## 4  2    B    20
## 6  3    C    30
# dplyr的distinct()识别唯一行
distinct(dup_data)
## distinct: removed 2 rows (29%), 5 rows remaining
##   id name value
## 1  1    A    10
## 2  2    B    20
## 3  3    C    30
## 4  4    D    40
## 5  5    E    50

小结duplicated()标记重复行,distinct()直接返回唯一行。

3.2 删除重复行

识别重复行后,可以选择删除。

# 使用unique()删除重复行
unique(dup_data)
##   id name value
## 1  1    A    10
## 2  2    B    20
## 3  3    C    30
## 5  4    D    40
## 7  5    E    50
# 使用dplyr的distinct()删除重复行
distinct(dup_data)
## distinct: removed 2 rows (29%), 5 rows remaining
##   id name value
## 1  1    A    10
## 2  2    B    20
## 3  3    C    30
## 4  4    D    40
## 5  5    E    50
# distinct()可以指定列来判断重复
# 只根据id和name判断重复
distinct(dup_data, id, name, .keep_all = TRUE)
## distinct: removed 2 rows (29%), 5 rows remaining
##   id name value
## 1  1    A    10
## 2  2    B    20
## 3  3    C    30
## 4  4    D    40
## 5  5    E    50

小结unique()简单直接,distinct()更灵活,可以指定判断重复的列。

3.3 基于特定列的去重

有时只需要根据特定列判断重复。

# 创建数据:id相同但其他列不同
partial_dup <- data.frame(
  id = c(1, 1, 2, 2, 3),
  name = c("A", "A", "B", "B2", "C"),
  value = c(10, 20, 30, 40, 50)
)

# 根据id去重,保留第一个出现的
distinct(partial_dup, id, .keep_all = TRUE)
## distinct: removed 2 rows (40%), 3 rows remaining
##   id name value
## 1  1    A    10
## 2  2    B    30
## 3  3    C    50
# 根据id和name去重
distinct(partial_dup, id, name, .keep_all = TRUE)
## distinct: removed one row (20%), 4 rows remaining
##   id name value
## 1  1    A    10
## 2  2    B    30
## 3  2   B2    40
## 4  3    C    50
# 使用duplicated()基于特定列
partial_dup[!duplicated(partial_dup$id), ]
##   id name value
## 1  1    A    10
## 3  2    B    30
## 5  3    C    50

小结:基于特定列去重时,要注意保留哪一条记录。

3.4 异常值识别——基于统计量

异常值检测是数据分析的重要环节。

# 创建包含异常值的数据
outlier_data <- c(10, 12, 11, 13, 10, 100, 11, 12, -50, 14, 11, 13)

# 方法1:Z-score方法
# Z-score = (x - mean) / sd
# 通常|Z| > 3被认为是异常值
z_scores <- (outlier_data - mean(outlier_data)) / sd(outlier_data)
abs(z_scores) > 3  # 标记异常值
##  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
# 查看被标记为异常值的点
outlier_data[abs(z_scores) > 3]
## numeric(0)
# 方法2:IQR方法(四分位距方法)
# IQR = Q3 - Q1
# 异常值定义:小于Q1-1.5*IQR 或 大于Q3+1.5*IQR
Q1 <- quantile(outlier_data, 0.25)
Q3 <- quantile(outlier_data, 0.75)
IQR_value <- Q3 - Q1

lower_bound <- Q1 - 1.5 * IQR_value
upper_bound <- Q3 + 1.5 * IQR_value

# 识别异常值
outlier_data < lower_bound | outlier_data > upper_bound
##  [1] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE
# 查看异常值
outlier_data[outlier_data < lower_bound | outlier_data > upper_bound]
## [1] 100 -50

小结:Z-score适合正态分布,IQR方法对非正态分布更稳健。

3.5 异常值识别——可视化方法

可视化是识别异常值的直观方法。

# 箱线图识别异常值
# 箱线图自动标记超出1.5*IQR范围的点
boxplot(outlier_data, main = "箱线图识别异常值")

# 获取箱线图识别的异常值
boxplot_stats <- boxplot.stats(outlier_data)
boxplot_stats$out  # 异常值
## [1] 100 -50
# 散点图识别异常值
plot(outlier_data, main = "散点图识别异常值",
     xlab = "索引", ylab = "值")
abline(h = upper_bound, col = "red", lty = 2)  # 上边界
abline(h = lower_bound, col = "red", lty = 2)  # 下边界

小结:箱线图是最常用的异常值可视化方法,直观且自动计算边界。

3.6 异常值识别——领域知识规则

有时需要根据业务规则识别异常值。

# 创建年龄数据
age_data <- c(25, 30, 150, 28, -5, 35, 200, 40)

# 根据领域知识:年龄应该在0-120之间
# 定义合理范围
min_age <- 0
max_age <- 120

# 识别异常值
age_data < min_age | age_data > max_age
## [1] FALSE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE
# 查看异常值
age_data[age_data < min_age | age_data > max_age]
## [1] 150  -5 200
# 创建标记列
age_df <- data.frame(age = age_data)
age_df$is_outlier <- age_df$age < min_age | age_df$age > max_age
age_df
##   age is_outlier
## 1  25      FALSE
## 2  30      FALSE
## 3 150       TRUE
## 4  28      FALSE
## 5  -5       TRUE
## 6  35      FALSE
## 7 200       TRUE
## 8  40      FALSE

小结:领域知识规则需要结合具体业务场景,是最实用的异常值识别方法。

3.7 异常值处理——截尾

截尾(Winsorize)将极端值替换为边界值,而不是删除。

# 手动实现Winsorize截尾处理
# 将最小的5%和最大的5%截尾

# 计算边界值
lower_bound <- quantile(outlier_data, 0.05)
upper_bound <- quantile(outlier_data, 0.95)

# 进行截尾处理
winsorize_result <- pmin(pmax(outlier_data, lower_bound), upper_bound)

# 比较原始数据和截尾后数据
data.frame(
  original = outlier_data,
  winsorized = winsorize_result
)
##    original winsorized
## 1        10       10.0
## 2        12       12.0
## 3        11       11.0
## 4        13       13.0
## 5        10       10.0
## 6       100       52.7
## 7        11       11.0
## 8        12       12.0
## 9       -50      -17.0
## 10       14       14.0
## 11       11       11.0
## 12       13       13.0

小结:截尾保留了样本量,同时减少了极端值的影响。

3.8 异常值处理——转换

数据转换可以减少异常值的影响。

# 创建右偏数据(有极端大值)
skewed_data <- c(1, 2, 3, 5, 8, 10, 15, 20, 50, 100)

# 对数转换
log_data <- log(skewed_data)

# 平方根转换
sqrt_data <- sqrt(skewed_data)

# 比较转换前后的分布
par(mfrow = c(1, 3))  # 设置1行3列的图形布局
hist(skewed_data, main = "原始数据", col = "lightblue")
hist(log_data, main = "对数转换", col = "lightgreen")
hist(sqrt_data, main = "平方根转换", col = "lightpink")

par(mfrow = c(1, 1))  # 恢复默认布局

小结:对数转换适合右偏数据,可以压缩大值的影响。

3.9 异常值处理——替换为边界值

将异常值替换为上下边界值。

# 使用IQR方法计算边界
replace_data <- outlier_data

# 替换低于下界的值为下界
replace_data[replace_data < lower_bound] <- lower_bound

# 替换高于上界的值为上界
replace_data[replace_data > upper_bound] <- upper_bound

# 比较原始和处理后的数据
data.frame(
  original = outlier_data,
  replaced = replace_data
)
##    original replaced
## 1        10     10.0
## 2        12     12.0
## 3        11     11.0
## 4        13     13.0
## 5        10     10.0
## 6       100     52.7
## 7        11     11.0
## 8        12     12.0
## 9       -50    -17.0
## 10       14     14.0
## 11       11     11.0
## 12       13     13.0

小结:替换为边界值保留了数据方向,同时限制了极端值。

3.10 异常值处理——删除

当异常值确实是错误数据时,可以选择删除。

# 删除异常值
clean_data <- outlier_data[outlier_data >= lower_bound & outlier_data <= upper_bound]

# 比较删除前后的数据
cat("原始数据长度:", length(outlier_data), "\n")
## 原始数据长度: 12
cat("删除后数据长度:", length(clean_data), "\n")
## 删除后数据长度: 10
cat("删除的异常值:", setdiff(outlier_data, clean_data))
## 删除的异常值: 100 -50

小结:删除异常值要谨慎,确保删除的确实是错误数据而非真实极端值。


第三章总结

问题类型 检测方法 处理方法
重复行 duplicated() unique()distinct()
异常值(统计) Z-score、IQR 截尾、替换、删除
异常值(可视化) 箱线图、散点图 转换、替换
异常值(业务) 领域规则 根据业务决定

第四章:数据类型转换与变量重编码

正确的数据类型是数据分析的基础。本章介绍各种数据类型转换和变量重编码方法。

4.1 数据类型转换

R中常见的数据类型包括数值型、字符型、因子型等。

# 创建示例数据
type_data <- data.frame(
  id = 1:5,
  age = c("25", "30", "28", "35", "40"),     # 字符型年龄
  score = c("85.5", "90.0", "78.5", "92.0", "88.0"),  # 字符型分数
  gender = c("M", "F", "M", "F", "M"),       # 字符型性别
  stringsAsFactors = FALSE
)

# 查看当前类型
str(type_data)
## 'data.frame':    5 obs. of  4 variables:
##  $ id    : int  1 2 3 4 5
##  $ age   : chr  "25" "30" "28" "35" ...
##  $ score : chr  "85.5" "90.0" "78.5" "92.0" ...
##  $ gender: chr  "M" "F" "M" "F" ...
# as.numeric()转换为数值型
type_data$age_num <- as.numeric(type_data$age)
type_data$score_num <- as.numeric(type_data$score)

# as.character()转换为字符型
num_vector <- c(1, 2, 3, 4, 5)
char_vector <- as.character(num_vector)
char_vector
## [1] "1" "2" "3" "4" "5"
# as.factor()转换为因子型
type_data$gender_factor <- as.factor(type_data$gender)
levels(type_data$gender_factor)  # 查看因子水平
## [1] "F" "M"
# as.logical()转换为逻辑型
num_to_logic <- c(0, 1, 2, 0, 1)
as.logical(num_to_logic)  # 0为FALSE,非0为TRUE
## [1] FALSE  TRUE  TRUE FALSE  TRUE
# as.Date()转换为日期型
date_string <- c("2024-01-01", "2024-06-15", "2024-12-31")
date_vector <- as.Date(date_string)
date_vector
## [1] "2024-01-01" "2024-06-15" "2024-12-31"

小结as.*()系列函数是类型转换的基础,注意转换可能产生NA。

4.2 因子变量重编码

因子变量需要正确设置水平和标签。

# 创建因子变量
gender_factor <- factor(c("M", "F", "M", "F", "M"))

# 查看当前水平
levels(gender_factor)
## [1] "F" "M"
# 使用levels()重命名水平
levels(gender_factor) <- c("Female", "Male")
gender_factor
## [1] Male   Female Male   Female Male  
## Levels: Female Male
# 使用forcats包进行因子操作
# 如果没有安装,先运行:install.packages("forcats")
library(forcats)

# fct_recode()重新编码水平
gender_new <- fct_recode(gender_factor,
                         "女性" = "Female",
                         "男性" = "Male")
levels(gender_new)
## [1] "女性" "男性"
# fct_relevel()调整水平顺序
# 将"男性"设为第一个水平
fct_relevel(gender_new, "男性")
## [1] 男性 女性 男性 女性 男性
## Levels: 男性 女性
# fct_infreq()按频率排序水平
city_data <- factor(c("北京", "上海", "北京", "广州", "北京", "上海"))
fct_infreq(city_data)  # 出现频率高的排前面
## [1] 北京 上海 北京 广州 北京 上海
## Levels: 北京 上海 广州

小结forcats包提供了丰富的因子操作函数,推荐使用。

4.3 数值变量离散化

将连续变量转换为分类变量。

# 创建连续变量
continuous_data <- c(18, 22, 35, 45, 55, 65, 75, 85)

# 使用cut()函数离散化
# breaks指定分割点
age_group <- cut(continuous_data, 
                 breaks = c(0, 30, 50, 100),  # 分割点
                 labels = c("青年", "中年", "老年"),  # 标签
                 right = FALSE)  # 左闭右开区间
age_group
## [1] 青年 青年 中年 中年 老年 老年 老年 老年
## Levels: 青年 中年 老年
# 使用Hmisc包的cut2()函数
# 可以按分位数分割
# 如果没有安装,先运行:install.packages("Hmisc")
library(Hmisc)

# 按分位数分成3组
age_group2 <- cut2(continuous_data, g = 3)
age_group2
## [1] [18,45) [18,45) [18,45) [45,75) [45,75) [45,75) [75,85] [75,85]
## Levels: [18,45) [45,75) [75,85]

小结cut()需要手动指定分割点,cut2()可以自动按分位数分割。

4.4 连续变量标准化/归一化

标准化使不同量纲的变量可比。

# 创建示例数据
scale_data <- data.frame(
  height = c(160, 165, 170, 175, 180),  # 身高(cm)
  weight = c(50, 55, 60, 65, 70)        # 体重(kg)
)

# scale()函数进行Z-score标准化
# 标准化后均值为0,标准差为1
scale_result <- scale(scale_data$height)
scale_result
##            [,1]
## [1,] -1.2649111
## [2,] -0.6324555
## [3,]  0.0000000
## [4,]  0.6324555
## [5,]  1.2649111
## attr(,"scaled:center")
## [1] 170
## attr(,"scaled:scale")
## [1] 7.905694
# 对整个数据框标准化
scale_all <- as.data.frame(scale(scale_data))
scale_all
##       height     weight
## 1 -1.2649111 -1.2649111
## 2 -0.6324555 -0.6324555
## 3  0.0000000  0.0000000
## 4  0.6324555  0.6324555
## 5  1.2649111  1.2649111
# 使用caret包进行预处理
# 如果没有安装,先运行:install.packages("caret")
library(caret)

# 创建预处理对象
preproc <- preProcess(scale_data, method = c("center", "scale"))

# 应用预处理
predict(preproc, scale_data)
##       height     weight
## 1 -1.2649111 -1.2649111
## 2 -0.6324555 -0.6324555
## 3  0.0000000  0.0000000
## 4  0.6324555  0.6324555
## 5  1.2649111  1.2649111
# 归一化(缩放到0-1范围)
preproc_range <- preProcess(scale_data, method = "range")
predict(preproc_range, scale_data)
##   height weight
## 1   0.00   0.00
## 2   0.25   0.25
## 3   0.50   0.50
## 4   0.75   0.75
## 5   1.00   1.00

小结scale()简单直接,caret::preProcess()提供更多预处理选项。

4.5 创建虚拟变量

将分类变量转换为数值型虚拟变量。

# 创建示例数据
dummy_data <- data.frame(
  id = 1:6,
  color = c("red", "blue", "green", "red", "blue", "green"),
  size = c("S", "M", "L", "M", "L", "S")
)

# 使用model.matrix()创建虚拟变量
# 第一个参数是公式,~ 表示用所有变量
dummy_matrix <- model.matrix(~ color + size, data = dummy_data)
dummy_matrix
##   (Intercept) colorgreen colorred sizeM sizeS
## 1           1          0        1     0     1
## 2           1          0        0     1     0
## 3           1          1        0     0     0
## 4           1          0        1     1     0
## 5           1          0        0     0     0
## 6           1          1        0     0     1
## attr(,"assign")
## [1] 0 1 1 2 2
## attr(,"contrasts")
## attr(,"contrasts")$color
## [1] "contr.treatment"
## 
## attr(,"contrasts")$size
## [1] "contr.treatment"
# 使用fastDummies包
# 如果没有安装,先运行:install.packages("fastDummies")
library(fastDummies)

# dummy_cols()创建虚拟变量列
dummy_cols(dummy_data, select_columns = c("color", "size"))
##   id color size color_blue color_green color_red size_L size_M size_S
## 1  1   red    S          0           0         1      0      0      1
## 2  2  blue    M          1           0         0      0      1      0
## 3  3 green    L          0           1         0      1      0      0
## 4  4   red    M          0           0         1      0      1      0
## 5  5  blue    L          1           0         0      1      0      0
## 6  6 green    S          0           1         0      0      0      1

小结model.matrix()是R内置方法,fastDummies包更直观易用。

4.6 变量重命名

修改变量名称使其更清晰。

# 创建示例数据
rename_data <- data.frame(
  a = 1:5,
  b = c("A", "B", "C", "D", "E"),
  c = c(10, 20, 30, 40, 50)
)

# 使用dplyr的rename()函数
rename_data <- rename(rename_data,
                      id = a,           # 新名 = 旧名
                      name = b,
                      value = c)
## rename: renamed 3 variables (id, name, value)
rename_data
##   id name value
## 1  1    A    10
## 2  2    B    20
## 3  3    C    30
## 4  4    D    40
## 5  5    E    50
# 使用data.table的setnames()函数
library(data.table)
setnames(rename_data, old = c("id", "name", "value"),
         new = c("编号", "名称", "数值"))
rename_data
##   编号 名称 数值
## 1    1    A   10
## 2    2    B   20
## 3    3    C   30
## 4    4    D   40
## 5    5    E   50

小结rename()语法清晰,setnames()可以直接修改原数据。

4.7 变量重新排序

调整变量的顺序。

# 创建示例数据
order_data <- data.frame(
  z = 1:5,
  a = letters[1:5],
  m = rnorm(5)
)

# 使用dplyr的select()调整顺序
# 按字母顺序排列
select(order_data, sort(names(order_data)))
## select: columns reordered (a, m, z)
##   a           m z
## 1 a  0.04389663 1
## 2 b -0.47753100 2
## 3 c  1.22973494 3
## 4 d -0.83978589 4
## 5 e -0.88992089 5
# 手动指定顺序
select(order_data, a, m, z)
## select: columns reordered (a, m, z)
##   a           m z
## 1 a  0.04389663 1
## 2 b -0.47753100 2
## 3 c  1.22973494 3
## 4 d -0.83978589 4
## 5 e -0.88992089 5
# 将某列移到最前面
select(order_data, z, everything())
## select: no changes
##   z a           m
## 1 1 a  0.04389663
## 2 2 b -0.47753100
## 3 3 c  1.22973494
## 4 4 d -0.83978589
## 5 5 e -0.88992089
# 使用relocate()函数(dplyr新版本)
relocate(order_data, a, .before = z)
## relocate: columns reordered (a, z, m)
##   a z           m
## 1 a 1  0.04389663
## 2 b 2 -0.47753100
## 3 c 3  1.22973494
## 4 d 4 -0.83978589
## 5 e 5 -0.88992089

小结select()relocate()都可以调整变量顺序。

4.8 变量取对数、平方根等变换

数学变换可以改变数据分布。

# 创建示例数据
transform_data <- data.frame(
  x = c(1, 10, 100, 1000, 10000)
)

# 对数变换
transform_data$log_x <- log(transform_data$x)      # 自然对数
transform_data$log10_x <- log10(transform_data$x)  # 以10为底的对数
transform_data$log2_x <- log2(transform_data$x)    # 以2为底的对数

# 平方根变换
transform_data$sqrt_x <- sqrt(transform_data$x)

# 平方变换
transform_data$square_x <- transform_data$x^2

# 倒数变换
transform_data$reciprocal_x <- 1 / transform_data$x

# 查看结果
transform_data
##       x    log_x log10_x    log2_x     sqrt_x square_x reciprocal_x
## 1     1 0.000000       0  0.000000   1.000000    1e+00        1e+00
## 2    10 2.302585       1  3.321928   3.162278    1e+02        1e-01
## 3   100 4.605170       2  6.643856  10.000000    1e+04        1e-02
## 4  1000 6.907755       3  9.965784  31.622777    1e+06        1e-03
## 5 10000 9.210340       4 13.287712 100.000000    1e+08        1e-04

小结:对数变换适合右偏数据,平方根变换适合计数数据。

4.9 变量交互项创建

交互项用于建模中捕捉变量间的交互作用。

# 创建示例数据
interact_data <- data.frame(
  x = 1:5,
  y = c(2, 4, 6, 8, 10)
)

# 创建交互项(乘积)
interact_data$xy <- interact_data$x * interact_data$y

# 使用mutate()创建
library(dplyr)
interact_data <- interact_data %>%
  mutate(xy_interaction = x * y)
## mutate: new variable 'xy_interaction' (double) with 5 unique values and 0% NA
# 在模型中自动创建交互项
# lm(y ~ x1 * x2, data) 会自动包含x1、x2和它们的交互项

小结:交互项在回归分析中很重要,可以手动创建或让模型自动创建。

4.10 变量分箱

将连续变量分成若干组。

# 创建示例数据
bin_data <- data.frame(
  score = c(45, 55, 65, 75, 85, 95, 100, 35, 72, 88)
)

# 使用cut()分箱
bin_data$grade <- cut(bin_data$score,
                      breaks = c(0, 60, 70, 80, 90, 100),
                      labels = c("F", "D", "C", "B", "A"),
                      include.lowest = TRUE)
bin_data
##    score grade
## 1     45     F
## 2     55     F
## 3     65     D
## 4     75     C
## 5     85     B
## 6     95     A
## 7    100     A
## 8     35     F
## 9     72     C
## 10    88     B
# 等宽分箱(每箱范围相等)
bin_data$equal_width <- cut(bin_data$score, 
                            breaks = 5,  # 分成5箱
                            labels = c("E", "D", "C", "B", "A"))
bin_data
##    score grade equal_width
## 1     45     F           E
## 2     55     F           D
## 3     65     D           C
## 4     75     C           B
## 5     85     B           B
## 6     95     A           A
## 7    100     A           A
## 8     35     F           E
## 9     72     C           C
## 10    88     B           A
# 等频分箱(每箱数量相等)
# 使用quantile()计算分位数
breaks <- quantile(bin_data$score, probs = seq(0, 1, 0.2))
bin_data$equal_freq <- cut(bin_data$score, 
                           breaks = breaks,
                           include.lowest = TRUE)
bin_data
##    score grade equal_width equal_freq
## 1     45     F           E    [35,53]
## 2     55     F           D  (53,69.2]
## 3     65     D           C  (53,69.2]
## 4     75     C           B  (69.2,79]
## 5     85     B           B  (79,89.4]
## 6     95     A           A (89.4,100]
## 7    100     A           A (89.4,100]
## 8     35     F           E    [35,53]
## 9     72     C           C  (69.2,79]
## 10    88     B           A  (79,89.4]

小结:分箱策略有等宽、等频和自定义,根据数据特点选择。


第四章总结

操作 函数 说明
类型转换 as.numeric() 转换数据类型
因子重编码 fct_recode() 修改因子水平
离散化 cut()cut2() 连续转分类
标准化 scale() Z-score标准化
虚拟变量 dummy_cols() 分类转数值
重命名 rename() 修改变量名
重排序 select()relocate() 调整变量顺序
变换 log()sqrt() 数学变换

第五章:数据筛选与排序

数据筛选和排序是数据处理中最常用的操作。本章介绍各种筛选和排序方法。

5.1 按行筛选

根据条件选择满足要求的行。

# 创建示例数据
filter_data <- data.frame(
  id = 1:10,
  name = c("张三", "李四", "王五", "赵六", "钱七", 
           "孙八", "周九", "吴十", "郑一", "陈二"),
  age = c(25, 30, 28, 35, 40, 22, 45, 33, 29, 31),
  department = c("销售", "技术", "销售", "技术", "财务",
                 "销售", "技术", "财务", "销售", "技术"),
  salary = c(5000, 8000, 5500, 9000, 7000, 4500, 10000, 7500, 5200, 8500)
)

# 使用dplyr的filter()筛选
library(dplyr)

# 筛选年龄大于30的员工
filter(filter_data, age > 30)
## filter: removed 5 rows (50%), 5 rows remaining
##   id name age department salary
## 1  4 赵六  35       技术   9000
## 2  5 钱七  40       财务   7000
## 3  7 周九  45       技术  10000
## 4  8 吴十  33       财务   7500
## 5 10 陈二  31       技术   8500
# 筛选技术部门的员工
filter(filter_data, department == "技术")
## filter: removed 6 rows (60%), 4 rows remaining
##   id name age department salary
## 1  2 李四  30       技术   8000
## 2  4 赵六  35       技术   9000
## 3  7 周九  45       技术  10000
## 4 10 陈二  31       技术   8500
# 筛选年龄大于30且薪资大于8000的员工
filter(filter_data, age > 30 & salary > 8000)
## filter: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  4 赵六  35       技术   9000
## 2  7 周九  45       技术  10000
## 3 10 陈二  31       技术   8500
# 使用base R的subset()函数
subset(filter_data, age > 30)
##    id name age department salary
## 4   4 赵六  35       技术   9000
## 5   5 钱七  40       财务   7000
## 7   7 周九  45       技术  10000
## 8   8 吴十  33       财务   7500
## 10 10 陈二  31       技术   8500

小结filter()语法简洁,subset()是R内置函数,两者功能相似。

5.2 按列筛选

选择需要的变量列。

# 使用dplyr的select()选择列

# 按名称选择
select(filter_data, name, age)
## select: dropped 3 variables (id, department, salary)
##    name age
## 1  张三  25
## 2  李四  30
## 3  王五  28
## 4  赵六  35
## 5  钱七  40
## 6  孙八  22
## 7  周九  45
## 8  吴十  33
## 9  郑一  29
## 10 陈二  31
# 选择连续的列
select(filter_data, id:age)
## select: dropped 2 variables (department, salary)
##    id name age
## 1   1 张三  25
## 2   2 李四  30
## 3   3 王五  28
## 4   4 赵六  35
## 5   5 钱七  40
## 6   6 孙八  22
## 7   7 周九  45
## 8   8 吴十  33
## 9   9 郑一  29
## 10 10 陈二  31
# 排除某些列
select(filter_data, -id, -name)
## select: dropped 2 variables (id, name)
##    age department salary
## 1   25       销售   5000
## 2   30       技术   8000
## 3   28       销售   5500
## 4   35       技术   9000
## 5   40       财务   7000
## 6   22       销售   4500
## 7   45       技术  10000
## 8   33       财务   7500
## 9   29       销售   5200
## 10  31       技术   8500
# 按位置选择
select(filter_data, 2, 3)
## select: dropped 3 variables (id, department, salary)
##    name age
## 1  张三  25
## 2  李四  30
## 3  王五  28
## 4  赵六  35
## 5  钱七  40
## 6  孙八  22
## 7  周九  45
## 8  吴十  33
## 9  郑一  29
## 10 陈二  31
# 按条件选择(包含"e"的列名)
select(filter_data, contains("e"))
## select: dropped 2 variables (id, salary)
##    name age department
## 1  张三  25       销售
## 2  李四  30       技术
## 3  王五  28       销售
## 4  赵六  35       技术
## 5  钱七  40       财务
## 6  孙八  22       销售
## 7  周九  45       技术
## 8  吴十  33       财务
## 9  郑一  29       销售
## 10 陈二  31       技术
# 按正则表达式选择
select(filter_data, matches("^[a-z]{4}$"))
## select: dropped 4 variables (id, age, department, salary)
##    name
## 1  张三
## 2  李四
## 3  王五
## 4  赵六
## 5  钱七
## 6  孙八
## 7  周九
## 8  吴十
## 9  郑一
## 10 陈二
# 选择特定类型的列
select(filter_data, where(is.numeric))  # 选择数值列
## select: dropped 2 variables (name, department)
##    id age salary
## 1   1  25   5000
## 2   2  30   8000
## 3   3  28   5500
## 4   4  35   9000
## 5   5  40   7000
## 6   6  22   4500
## 7   7  45  10000
## 8   8  33   7500
## 9   9  29   5200
## 10 10  31   8500
select(filter_data, where(is.character))  # 选择字符列
## select: dropped 3 variables (id, age, salary)
##    name department
## 1  张三       销售
## 2  李四       技术
## 3  王五       销售
## 4  赵六       技术
## 5  钱七       财务
## 6  孙八       销售
## 7  周九       技术
## 8  吴十       财务
## 9  郑一       销售
## 10 陈二       技术

小结select()支持多种选择方式,灵活强大。

5.3 去重筛选

筛选唯一值。

# 选择部门列的唯一值
distinct(filter_data, department)
## distinct: removed 7 rows (70%), 3 rows remaining
##   department
## 1       销售
## 2       技术
## 3       财务
# 选择部门和年龄的唯一组合
distinct(filter_data, department, age)
## distinct: no rows removed
##    department age
## 1        销售  25
## 2        技术  30
## 3        销售  28
## 4        技术  35
## 5        财务  40
## 6        销售  22
## 7        技术  45
## 8        财务  33
## 9        销售  29
## 10       技术  31
# 保留所有列,但只保留唯一行
distinct(filter_data, department, .keep_all = TRUE)
## distinct: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  2 李四  30       技术   8000
## 3  5 钱七  40       财务   7000

小结distinct()可以快速获取唯一值或唯一组合。

5.4 随机抽样

从数据中随机抽取样本。

# 设置随机种子,保证结果可重复
set.seed(123)

# sample_n()随机抽取n行
sample_n(filter_data, 3)  # 随机抽取3行
## sample_n: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  3 王五  28       销售   5500
## 2 10 陈二  31       技术   8500
## 3  2 李四  30       技术   8000
# sample_frac()随机抽取一定比例
sample_frac(filter_data, 0.3)  # 随机抽取30%
## sample_frac: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  2 李四  30       技术   8000
## 2  6 孙八  22       销售   4500
## 3  3 王五  28       销售   5500
# slice_sample()是新版推荐函数
slice_sample(filter_data, n = 3)  # 抽取3行
## slice_sample: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  5 钱七  40       财务   7000
## 2  4 赵六  35       技术   9000
## 3  6 孙八  22       销售   4500
slice_sample(filter_data, prop = 0.3)  # 抽取30%
## slice_sample: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  6 孙八  22       销售   4500
## 2  9 郑一  29       销售   5200
## 3  2 李四  30       技术   8000

小结slice_sample()是最新推荐的随机抽样函数。

5.5 排序

对数据进行排序。

# 使用dplyr的arrange()排序

# 按年龄升序排列
arrange(filter_data, age)
##    id name age department salary
## 1   6 孙八  22       销售   4500
## 2   1 张三  25       销售   5000
## 3   3 王五  28       销售   5500
## 4   9 郑一  29       销售   5200
## 5   2 李四  30       技术   8000
## 6  10 陈二  31       技术   8500
## 7   8 吴十  33       财务   7500
## 8   4 赵六  35       技术   9000
## 9   5 钱七  40       财务   7000
## 10  7 周九  45       技术  10000
# 按年龄降序排列
arrange(filter_data, desc(age))
##    id name age department salary
## 1   7 周九  45       技术  10000
## 2   5 钱七  40       财务   7000
## 3   4 赵六  35       技术   9000
## 4   8 吴十  33       财务   7500
## 5  10 陈二  31       技术   8500
## 6   2 李四  30       技术   8000
## 7   9 郑一  29       销售   5200
## 8   3 王五  28       销售   5500
## 9   1 张三  25       销售   5000
## 10  6 孙八  22       销售   4500
# 按多个变量排序
arrange(filter_data, department, desc(salary))
##    id name age department salary
## 1   7 周九  45       技术  10000
## 2   4 赵六  35       技术   9000
## 3  10 陈二  31       技术   8500
## 4   2 李四  30       技术   8000
## 5   8 吴十  33       财务   7500
## 6   5 钱七  40       财务   7000
## 7   3 王五  28       销售   5500
## 8   9 郑一  29       销售   5200
## 9   1 张三  25       销售   5000
## 10  6 孙八  22       销售   4500
# 使用base R的order()函数
filter_data[order(filter_data$age), ]  # 升序
##    id name age department salary
## 6   6 孙八  22       销售   4500
## 1   1 张三  25       销售   5000
## 3   3 王五  28       销售   5500
## 9   9 郑一  29       销售   5200
## 2   2 李四  30       技术   8000
## 10 10 陈二  31       技术   8500
## 8   8 吴十  33       财务   7500
## 4   4 赵六  35       技术   9000
## 5   5 钱七  40       财务   7000
## 7   7 周九  45       技术  10000
filter_data[order(filter_data$age, decreasing = TRUE), ]  # 降序
##    id name age department salary
## 7   7 周九  45       技术  10000
## 5   5 钱七  40       财务   7000
## 4   4 赵六  35       技术   9000
## 8   8 吴十  33       财务   7500
## 10 10 陈二  31       技术   8500
## 2   2 李四  30       技术   8000
## 9   9 郑一  29       销售   5200
## 3   3 王五  28       销售   5500
## 1   1 张三  25       销售   5000
## 6   6 孙八  22       销售   4500
# 使用sort()对向量排序
sort(filter_data$age)
##  [1] 22 25 28 29 30 31 33 35 40 45
sort(filter_data$age, decreasing = TRUE)
##  [1] 45 40 35 33 31 30 29 28 25 22

小结arrange()语法简洁,order()是R内置函数。

5.6 按组排序

分组后在各组内排序。

# 按部门分组,在每个部门内按薪资降序排列
filter_data %>%
  group_by(department) %>%
  arrange(desc(salary), .by_group = TRUE)
## group_by: one grouping variable (department)
## # A tibble: 10 × 5
## # Groups:   department [3]
##       id name    age department salary
##    <int> <chr> <dbl> <chr>       <dbl>
##  1     7 周九     45 技术        10000
##  2     4 赵六     35 技术         9000
##  3    10 陈二     31 技术         8500
##  4     2 李四     30 技术         8000
##  5     8 吴十     33 财务         7500
##  6     5 钱七     40 财务         7000
##  7     3 王五     28 销售         5500
##  8     9 郑一     29 销售         5200
##  9     1 张三     25 销售         5000
## 10     6 孙八     22 销售         4500
# 或者使用arrange()配合group_by()
filter_data %>%
  group_by(department) %>%
  arrange(department, desc(salary))
## group_by: one grouping variable (department)
## # A tibble: 10 × 5
## # Groups:   department [3]
##       id name    age department salary
##    <int> <chr> <dbl> <chr>       <dbl>
##  1     7 周九     45 技术        10000
##  2     4 赵六     35 技术         9000
##  3    10 陈二     31 技术         8500
##  4     2 李四     30 技术         8000
##  5     8 吴十     33 财务         7500
##  6     5 钱七     40 财务         7000
##  7     3 王五     28 销售         5500
##  8     9 郑一     29 销售         5200
##  9     1 张三     25 销售         5000
## 10     6 孙八     22 销售         4500

小结:分组排序常用于组内排名等场景。

5.7 条件筛选

使用各种条件进行筛选。

# 与条件(AND)
filter(filter_data, age > 30, salary > 7000)  # 逗号分隔表示AND
## filter: removed 6 rows (60%), 4 rows remaining
##   id name age department salary
## 1  4 赵六  35       技术   9000
## 2  7 周九  45       技术  10000
## 3  8 吴十  33       财务   7500
## 4 10 陈二  31       技术   8500
filter(filter_data, age > 30 & salary > 7000)  # 或使用&
## filter: removed 6 rows (60%), 4 rows remaining
##   id name age department salary
## 1  4 赵六  35       技术   9000
## 2  7 周九  45       技术  10000
## 3  8 吴十  33       财务   7500
## 4 10 陈二  31       技术   8500
# 或条件(OR)
filter(filter_data, department == "销售" | department == "技术")
## filter: removed 2 rows (20%), 8 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  2 李四  30       技术   8000
## 3  3 王五  28       销售   5500
## 4  4 赵六  35       技术   9000
## 5  6 孙八  22       销售   4500
## 6  7 周九  45       技术  10000
## 7  9 郑一  29       销售   5200
## 8 10 陈二  31       技术   8500
# 非条件(NOT)
filter(filter_data, !(department == "财务"))
## filter: removed 2 rows (20%), 8 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  2 李四  30       技术   8000
## 3  3 王五  28       销售   5500
## 4  4 赵六  35       技术   9000
## 5  6 孙八  22       销售   4500
## 6  7 周九  45       技术  10000
## 7  9 郑一  29       销售   5200
## 8 10 陈二  31       技术   8500
# %in%运算符:判断值是否在集合中
filter(filter_data, department %in% c("销售", "技术"))
## filter: removed 2 rows (20%), 8 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  2 李四  30       技术   8000
## 3  3 王五  28       销售   5500
## 4  4 赵六  35       技术   9000
## 5  6 孙八  22       销售   4500
## 6  7 周九  45       技术  10000
## 7  9 郑一  29       销售   5200
## 8 10 陈二  31       技术   8500
# between()函数:判断值是否在范围内
filter(filter_data, between(age, 25, 35))
## filter: removed 3 rows (30%), 7 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  2 李四  30       技术   8000
## 3  3 王五  28       销售   5500
## 4  4 赵六  35       技术   9000
## 5  8 吴十  33       财务   7500
## 6  9 郑一  29       销售   5200
## 7 10 陈二  31       技术   8500
# 判断是否为NA
filter(filter_data, !is.na(age))
## filter: no rows removed
##    id name age department salary
## 1   1 张三  25       销售   5000
## 2   2 李四  30       技术   8000
## 3   3 王五  28       销售   5500
## 4   4 赵六  35       技术   9000
## 5   5 钱七  40       财务   7000
## 6   6 孙八  22       销售   4500
## 7   7 周九  45       技术  10000
## 8   8 吴十  33       财务   7500
## 9   9 郑一  29       销售   5200
## 10 10 陈二  31       技术   8500

小结%in%是常用的集合判断运算符,简洁高效。

5.8 筛选非缺失

筛选没有缺失值的行。

# 创建含缺失值的数据
na_filter_data <- filter_data
na_filter_data$salary[c(2, 5, 8)] <- NA

# 筛选salary非缺失的行
filter(na_filter_data, !is.na(salary))
## filter: removed 3 rows (30%), 7 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  3 王五  28       销售   5500
## 3  4 赵六  35       技术   9000
## 4  6 孙八  22       销售   4500
## 5  7 周九  45       技术  10000
## 6  9 郑一  29       销售   5200
## 7 10 陈二  31       技术   8500
# 筛选所有列都非缺失的行(使用基础R方法)
na_filter_data[complete.cases(na_filter_data), ]
##    id name age department salary
## 1   1 张三  25       销售   5000
## 3   3 王五  28       销售   5500
## 4   4 赵六  35       技术   9000
## 6   6 孙八  22       销售   4500
## 7   7 周九  45       技术  10000
## 9   9 郑一  29       销售   5200
## 10 10 陈二  31       技术   8500
# 使用drop_na()
drop_na(na_filter_data)
## drop_na: removed 3 rows (30%), 7 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  3 王五  28       销售   5500
## 3  4 赵六  35       技术   9000
## 4  6 孙八  22       销售   4500
## 5  7 周九  45       技术  10000
## 6  9 郑一  29       销售   5200
## 7 10 陈二  31       技术   8500
drop_na(na_filter_data, salary)  # 只考虑salary列
## drop_na: removed 3 rows (30%), 7 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  3 王五  28       销售   5500
## 3  4 赵六  35       技术   9000
## 4  6 孙八  22       销售   4500
## 5  7 周九  45       技术  10000
## 6  9 郑一  29       销售   5200
## 7 10 陈二  31       技术   8500

小结drop_na()是筛选非缺失值的便捷方法。

5.9 按行号筛选

根据行号选择数据。

# 使用slice()按行号选择

# 选择第1到3行
slice(filter_data, 1:3)
## slice: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  2 李四  30       技术   8000
## 3  3 王五  28       销售   5500
# 选择第1、3、5行
slice(filter_data, c(1, 3, 5))
## slice: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  3 王五  28       销售   5500
## 3  5 钱七  40       财务   7000
# 选择最后一行
slice_tail(filter_data, n = 1)
## slice_tail: removed 9 rows (90%), one row remaining
##   id name age department salary
## 1 10 陈二  31       技术   8500
# 选择前3行
slice_head(filter_data, n = 3)
## slice_head: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  1 张三  25       销售   5000
## 2  2 李四  30       技术   8000
## 3  3 王五  28       销售   5500
# 选择最大/最小的行
slice_max(filter_data, salary, n = 3)  # 薪资最高的3人
## slice_max: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  7 周九  45       技术  10000
## 2  4 赵六  35       技术   9000
## 3 10 陈二  31       技术   8500
slice_min(filter_data, age, n = 3)     # 年龄最小的3人
## slice_min: removed 7 rows (70%), 3 rows remaining
##   id name age department salary
## 1  6 孙八  22       销售   4500
## 2  1 张三  25       销售   5000
## 3  3 王五  28       销售   5500

小结slice()系列函数提供了灵活的行选择方式。


第五章总结

操作 函数 说明
行筛选 filter() 按条件筛选行
列筛选 select() 选择列
去重 distinct() 唯一值
随机抽样 slice_sample() 随机抽取
排序 arrange() 排序
行号筛选 slice() 按位置选择

第六章:数据变换与计算

数据变换是创建新变量和进行计算的核心操作。本章介绍各种数据变换方法。

6.1 添加新列

在数据框中创建新变量。

# 创建示例数据
transform_data <- data.frame(
  product = c("A", "B", "C", "D", "E"),
  price = c(100, 200, 150, 300, 250),
  quantity = c(10, 5, 8, 3, 6)
)

# 使用mutate()添加新列
library(dplyr)

transform_data <- transform_data %>%
  mutate(
    revenue = price * quantity,           # 收入
    unit_cost = price * 0.6,              # 单位成本
    profit = revenue - unit_cost * quantity  # 利润
  )
## mutate: new variable 'revenue' (double) with 4 unique values and 0% NA
##         new variable 'unit_cost' (double) with 5 unique values and 0% NA
##         new variable 'profit' (double) with 4 unique values and 0% NA
transform_data
##   product price quantity revenue unit_cost profit
## 1       A   100       10    1000        60    400
## 2       B   200        5    1000       120    400
## 3       C   150        8    1200        90    480
## 4       D   300        3     900       180    360
## 5       E   250        6    1500       150    600
# transmute()只保留新创建的列
transmute(transform_data,
          product = product,
          revenue = price * quantity)
## transmute: dropped 4 variables (price, quantity, unit_cost, profit)
## transmute: dropped 4 variables (price, quantity, unit_cost, profit)
##   product revenue
## 1       A    1000
## 2       B    1000
## 3       C    1200
## 4       D     900
## 5       E    1500

小结mutate()保留所有列,transmute()只保留指定列。

6.2 条件创建新列

根据条件创建新变量。

# 使用ifelse()创建条件列
transform_data$price_level <- ifelse(
  transform_data$price > 200,    # 条件
  "高价",                        # 条件为TRUE时的值
  "低价"                         # 条件为FALSE时的值
)

# 使用case_when()处理多条件
transform_data <- transform_data %>%
  mutate(
    category = case_when(
      price >= 250 ~ "高端",           # 条件1
      price >= 150 ~ "中端",           # 条件2
      TRUE ~ "低端"                    # 默认值
    )
  )
## mutate: new variable 'category' (character) with 3 unique values and 0% NA
transform_data
##   product price quantity revenue unit_cost profit price_level category
## 1       A   100       10    1000        60    400        低价     低端
## 2       B   200        5    1000       120    400        低价     中端
## 3       C   150        8    1200        90    480        低价     中端
## 4       D   300        3     900       180    360        高价     高端
## 5       E   250        6    1500       150    600        高价     高端

小结ifelse()适合二分类,case_when()适合多分类。

6.3 窗口函数

窗口函数用于计算相对位置或累计值。

# 创建时间序列数据
window_data <- data.frame(
  month = 1:12,
  sales = c(100, 120, 150, 130, 160, 180, 
            200, 190, 210, 230, 250, 280)
)

# lag()获取前一个值
window_data$prev_sales <- lag(window_data$sales)

# lead()获取后一个值
window_data$next_sales <- lead(window_data$sales)

# 计算环比增长
window_data$growth <- (window_data$sales - window_data$prev_sales) / 
                       window_data$prev_sales * 100

# cumsum()累计求和
window_data$cum_sales <- cumsum(window_data$sales)

# cummean()累计均值
window_data$cum_avg <- cummean(window_data$sales)

# cummax()累计最大值
window_data$cum_max <- cummax(window_data$sales)

# cummin()累计最小值
window_data$cum_min <- cummin(window_data$sales)

window_data
##    month sales prev_sales next_sales     growth cum_sales  cum_avg cum_max
## 1      1   100         NA        120         NA       100 100.0000     100
## 2      2   120        100        150  20.000000       220 110.0000     120
## 3      3   150        120        130  25.000000       370 123.3333     150
## 4      4   130        150        160 -13.333333       500 125.0000     150
## 5      5   160        130        180  23.076923       660 132.0000     160
## 6      6   180        160        200  12.500000       840 140.0000     180
## 7      7   200        180        190  11.111111      1040 148.5714     200
## 8      8   190        200        210  -5.000000      1230 153.7500     200
## 9      9   210        190        230  10.526316      1440 160.0000     210
## 10    10   230        210        250   9.523810      1670 167.0000     230
## 11    11   250        230        280   8.695652      1920 174.5455     250
## 12    12   280        250         NA  12.000000      2200 183.3333     280
##    cum_min
## 1      100
## 2      100
## 3      100
## 4      100
## 5      100
## 6      100
## 7      100
## 8      100
## 9      100
## 10     100
## 11     100
## 12     100

小结:窗口函数在时间序列分析中非常有用。

6.4 分组汇总

按组进行汇总统计。

# 创建分组数据
group_data <- data.frame(
  department = c("销售", "技术", "销售", "技术", "财务",
                 "销售", "技术", "财务", "销售", "技术"),
  employee = c("张三", "李四", "王五", "赵六", "钱七",
               "孙八", "周九", "吴十", "郑一", "陈二"),
  salary = c(5000, 8000, 5500, 9000, 7000,
             4500, 10000, 7500, 5200, 8500)
)

# 按部门分组计算平均薪资
group_data %>%
  group_by(department) %>%
  summarise(
    avg_salary = mean(salary),
    total_salary = sum(salary),
    count = n(),
    max_salary = max(salary),
    min_salary = min(salary)
  )
## group_by: one grouping variable (department)
## summarise: now 3 rows and 6 columns, ungrouped
## # A tibble: 3 × 6
##   department avg_salary total_salary count max_salary min_salary
##   <chr>           <dbl>        <dbl> <int>      <dbl>      <dbl>
## 1 技术             8875        35500     4      10000       8000
## 2 财务             7250        14500     2       7500       7000
## 3 销售             5050        20200     4       5500       4500

小结group_by()配合summarise()是分组汇总的标准模式。

6.5 汇总统计

常用的汇总统计函数。

# 常用汇总函数
group_data %>%
  group_by(department) %>%
  summarise(
    n = n(),                          # 计数
    n_distinct_emp = n_distinct(employee),  # 唯一值计数
    sum_sal = sum(salary),            # 求和
    mean_sal = mean(salary),          # 均值
    median_sal = median(salary),      # 中位数
    sd_sal = sd(salary),              # 标准差
    var_sal = var(salary),            # 方差
    min_sal = min(salary),            # 最小值
    max_sal = max(salary),            # 最大值
    range_sal = max(salary) - min(salary),  # 极差
    first_emp = first(employee),      # 第一个值
    last_emp = last(employee)         # 最后一个值
  )
## group_by: one grouping variable (department)
## summarise: now 3 rows and 13 columns, ungrouped
## # A tibble: 3 × 13
##   department     n n_distinct_emp sum_sal mean_sal median_sal sd_sal var_sal
##   <chr>      <int>          <int>   <dbl>    <dbl>      <dbl>  <dbl>   <dbl>
## 1 技术           4              4   35500     8875       8750   854. 729167.
## 2 财务           2              2   14500     7250       7250   354. 125000 
## 3 销售           4              4   20200     5050       5100   420. 176667.
## # ℹ 5 more variables: min_sal <dbl>, max_sal <dbl>, range_sal <dbl>,
## #   first_emp <chr>, last_emp <chr>

小结n()统计行数,n_distinct()统计唯一值数量。

6.6 多个汇总同时计算

使用across()对多列应用相同函数。

# 创建多列数据
multi_data <- data.frame(
  group = c("A", "A", "B", "B", "C", "C"),
  x = c(1, 2, 3, 4, 5, 6),
  y = c(10, 20, 30, 40, 50, 60),
  z = c(100, 200, 300, 400, 500, 600)
)

# 对所有数值列计算均值
multi_data %>%
  group_by(group) %>%
  summarise(
    across(where(is.numeric), mean, .names = "mean_{.col}")
  )
## group_by: one grouping variable (group)
## summarise: now 3 rows and 4 columns, ungrouped
## # A tibble: 3 × 4
##   group mean_x mean_y mean_z
##   <chr>  <dbl>  <dbl>  <dbl>
## 1 A        1.5     15    150
## 2 B        3.5     35    350
## 3 C        5.5     55    550
# 对指定列计算多个统计量
multi_data %>%
  group_by(group) %>%
  summarise(
    across(c(x, y), 
           list(mean = mean, sd = sd), 
           .names = "{.col}_{.fn}")
  )
## group_by: one grouping variable (group)
## summarise: now 3 rows and 5 columns, ungrouped
## # A tibble: 3 × 5
##   group x_mean  x_sd y_mean  y_sd
##   <chr>  <dbl> <dbl>  <dbl> <dbl>
## 1 A        1.5 0.707     15  7.07
## 2 B        3.5 0.707     35  7.07
## 3 C        5.5 0.707     55  7.07

小结across()可以批量处理多列,非常高效。

6.7 聚合后合并回原数据

将汇总结果合并回原数据框。

# 使用add_count()添加计数列
group_data %>%
  add_count(department, name = "dept_count")
## add_count: new variable 'dept_count' (integer) with 2 unique values and 0% NA
##    department employee salary dept_count
## 1        销售     张三   5000          4
## 2        技术     李四   8000          4
## 3        销售     王五   5500          4
## 4        技术     赵六   9000          4
## 5        财务     钱七   7000          2
## 6        销售     孙八   4500          4
## 7        技术     周九  10000          4
## 8        财务     吴十   7500          2
## 9        销售     郑一   5200          4
## 10       技术     陈二   8500          4
# 使用add_tally()添加计数列(需要先分组)
group_data %>%
  group_by(department) %>%
  add_tally(name = "dept_count")
## group_by: one grouping variable (department)
## add_tally (grouped): new variable 'dept_count' (integer) with 2 unique values and 0% NA
## # A tibble: 10 × 4
## # Groups:   department [3]
##    department employee salary dept_count
##    <chr>      <chr>     <dbl>      <int>
##  1 销售       张三       5000          4
##  2 技术       李四       8000          4
##  3 销售       王五       5500          4
##  4 技术       赵六       9000          4
##  5 财务       钱七       7000          2
##  6 销售       孙八       4500          4
##  7 技术       周九      10000          4
##  8 财务       吴十       7500          2
##  9 销售       郑一       5200          4
## 10 技术       陈二       8500          4
# 计算组内占比
group_data %>%
  group_by(department) %>%
  mutate(
    dept_total = sum(salary),
    dept_avg = mean(salary),
    salary_ratio = salary / dept_total
  )
## group_by: one grouping variable (department)
## mutate (grouped): new variable 'dept_total' (double) with 3 unique values and 0% NA
##                   new variable 'dept_avg' (double) with 3 unique values and 0% NA
##                   new variable 'salary_ratio' (double) with 10 unique values and 0% NA
## # A tibble: 10 × 6
## # Groups:   department [3]
##    department employee salary dept_total dept_avg salary_ratio
##    <chr>      <chr>     <dbl>      <dbl>    <dbl>        <dbl>
##  1 销售       张三       5000      20200     5050        0.248
##  2 技术       李四       8000      35500     8875        0.225
##  3 销售       王五       5500      20200     5050        0.272
##  4 技术       赵六       9000      35500     8875        0.254
##  5 财务       钱七       7000      14500     7250        0.483
##  6 销售       孙八       4500      20200     5050        0.223
##  7 技术       周九      10000      35500     8875        0.282
##  8 财务       吴十       7500      14500     7250        0.517
##  9 销售       郑一       5200      20200     5050        0.257
## 10 技术       陈二       8500      35500     8875        0.239

小结add_count()add_tally()可以快速添加计数列。

6.8 滚动计算

在滑动窗口内进行计算。

# 使用zoo包进行滚动计算
library(zoo)

# 创建时间序列数据
ts_data <- data.frame(
  day = 1:10,
  value = c(10, 12, 15, 11, 18, 20, 16, 22, 19, 25)
)

# 滚动平均(3天窗口)
ts_data$rolling_mean <- rollmean(ts_data$value, k = 3, fill = NA, align = "right")

# 滚动求和
ts_data$rolling_sum <- rollsum(ts_data$value, k = 3, fill = NA, align = "right")

# 滚动标准差
ts_data$rolling_sd <- rollapply(ts_data$value, width = 3, FUN = sd, fill = NA, align = "right")

ts_data

小结zoo包提供了丰富的滚动计算函数,适合时间序列分析。

6.9 跨行计算

对每行的多列进行计算。

# 创建多列数据
row_data <- data.frame(
  id = 1:5,
  score1 = c(80, 85, 90, 75, 88),
  score2 = c(75, 82, 88, 80, 85),
  score3 = c(85, 88, 92, 78, 90)
)

# 使用rowwise()进行跨行计算
row_data %>%
  rowwise() %>%
  mutate(
    total = sum(c_across(starts_with("score"))),
    average = mean(c_across(starts_with("score"))),
    max_score = max(c_across(starts_with("score")))
  )
## mutate: new variable 'total' (double) with 5 unique values and 0% NA
##         new variable 'average' (double) with 5 unique values and 0% NA
##         new variable 'max_score' (double) with 5 unique values and 0% NA
## # A tibble: 5 × 7
## # Rowwise: 
##      id score1 score2 score3 total average max_score
##   <int>  <dbl>  <dbl>  <dbl> <dbl>   <dbl>     <dbl>
## 1     1     80     75     85   240    80          85
## 2     2     85     82     88   255    85          88
## 3     3     90     88     92   270    90          92
## 4     4     75     80     78   233    77.7        80
## 5     5     88     85     90   263    87.7        90
# 使用rowSums()和rowMeans()(更高效)
row_data$total <- rowSums(row_data[, c("score1", "score2", "score3")])
row_data$average <- rowMeans(row_data[, c("score1", "score2", "score3")])
row_data
##   id score1 score2 score3 total  average
## 1  1     80     75     85   240 80.00000
## 2  2     85     82     88   255 85.00000
## 3  3     90     88     92   270 90.00000
## 4  4     75     80     78   233 77.66667
## 5  5     88     85     90   263 87.66667

小结rowwise()灵活但较慢,rowSums()等函数效率更高。

6.10 累加、累乘

累计计算。

# 创建数据
cum_data <- data.frame(
  month = 1:6,
  revenue = c(100, 150, 120, 180, 200, 160)
)

# 累计求和
cum_data$cum_revenue <- cumsum(cum_data$revenue)

# 累计乘积
cum_data$cum_product <- cumprod(cum_data$revenue / 100 + 1)

# 累计最大值
cum_data$cum_max <- cummax(cum_data$revenue)

# 累计最小值
cum_data$cum_min <- cummin(cum_data$revenue)

cum_data
##   month revenue cum_revenue cum_product cum_max cum_min
## 1     1     100         100        2.00     100     100
## 2     2     150         250        5.00     150     100
## 3     3     120         370       11.00     150     100
## 4     4     180         550       30.80     180     100
## 5     5     200         750       92.40     200     100
## 6     6     160         910      240.24     200     100

小结:累计函数在财务分析和时间序列中常用。


第六章总结

操作 函数 说明
添加列 mutate() 创建新变量
条件列 ifelse()case_when() 条件创建
窗口函数 lag()lead() 前后值
累计函数 cumsum()cumprod() 累计计算
分组汇总 group_by() + summarise() 分组统计
批量处理 across() 多列操作

第七章:数据合并与连接

实际分析中,数据往往分散在多个表格中。本章介绍如何合并和连接数据。

7.1 按行合并

将多个数据框纵向合并(增加行)。

# 创建两个数据框
df1 <- data.frame(
  id = 1:3,
  name = c("张三", "李四", "王五")
)

df2 <- data.frame(
  id = 4:6,
  name = c("赵六", "钱七", "孙八")
)

# 使用rbind()合并
rbind(df1, df2)
##   id name
## 1  1 张三
## 2  2 李四
## 3  3 王五
## 4  4 赵六
## 5  5 钱七
## 6  6 孙八
# 使用dplyr的bind_rows()
library(dplyr)
bind_rows(df1, df2)
##   id name
## 1  1 张三
## 2  2 李四
## 3  3 王五
## 4  4 赵六
## 5  5 钱七
## 6  6 孙八
# bind_rows()可以合并列名不完全相同的数据框
df3 <- data.frame(
  id = 7:8,
  name = c("周九", "吴十"),
  age = c(25, 30)  # 新增列
)

bind_rows(df1, df3)  # 自动填充NA
##   id name age
## 1  1 张三  NA
## 2  2 李四  NA
## 3  3 王五  NA
## 4  7 周九  25
## 5  8 吴十  30

小结bind_rows()rbind()更灵活,可以处理列名不完全相同的情况。

7.2 按列合并

将多个数据框横向合并(增加列)。

# 创建两个数据框
df_a <- data.frame(
  id = 1:3,
  name = c("张三", "李四", "王五")
)

df_b <- data.frame(
  score = c(85, 90, 78),
  grade = c("B", "A", "C")
)

# 使用cbind()合并
cbind(df_a, df_b)
##   id name score grade
## 1  1 张三    85     B
## 2  2 李四    90     A
## 3  3 王五    78     C
# 使用dplyr的bind_cols()
bind_cols(df_a, df_b)
##   id name score grade
## 1  1 张三    85     B
## 2  2 李四    90     A
## 3  3 王五    78     C

小结:按列合并时,两个数据框的行数必须相同,且行顺序要对应。

7.3 键值连接——内连接

只保留两个表中都有匹配的行。

# 创建两个数据框
employees <- data.frame(
  emp_id = c(1, 2, 3, 4, 5),
  name = c("张三", "李四", "王五", "赵六", "钱七"),
  dept_id = c(101, 102, 101, 103, 102)
)

departments <- data.frame(
  dept_id = c(101, 102, 104),
  dept_name = c("销售部", "技术部", "财务部")
)

# 内连接:只保留两个表都有匹配的记录
inner_join(employees, departments, by = "dept_id")
## inner_join: added one column (dept_name)
##             > rows only in employees   (1)
##             > rows only in departments (1)
##             > matched rows              4
##             >                          ===
##             > rows total                4
##   emp_id name dept_id dept_name
## 1      1 张三     101    销售部
## 2      2 李四     102    技术部
## 3      3 王五     101    销售部
## 4      5 钱七     102    技术部

小结:内连接会丢失没有匹配的记录。

7.4 键值连接——左连接

保留左表所有行,右表无匹配则为NA。

# 左连接:保留左表(employees)所有行
left_join(employees, departments, by = "dept_id")
## left_join: added one column (dept_name)
##            > rows only in employees    1
##            > rows only in departments (1)
##            > matched rows              4
##            >                          ===
##            > rows total                5
##   emp_id name dept_id dept_name
## 1      1 张三     101    销售部
## 2      2 李四     102    技术部
## 3      3 王五     101    销售部
## 4      4 赵六     103      <NA>
## 5      5 钱七     102    技术部

小结:左连接是最常用的连接方式,保留主表所有记录。

7.5 键值连接——右连接

保留右表所有行,左表无匹配则为NA。

# 右连接:保留右表(departments)所有行
right_join(employees, departments, by = "dept_id")
## right_join: added one column (dept_name)
##             > rows only in employees   (1)
##             > rows only in departments  1
##             > matched rows              4    (includes duplicates)
##             >                          ===
##             > rows total                5
##   emp_id name dept_id dept_name
## 1      1 张三     101    销售部
## 2      2 李四     102    技术部
## 3      3 王五     101    销售部
## 4      5 钱七     102    技术部
## 5     NA <NA>     104    财务部

小结:右连接等价于调换表顺序的左连接。

7.6 键值连接——全连接

保留两个表所有行。

# 全连接:保留两个表所有行
full_join(employees, departments, by = "dept_id")
## full_join: added one column (dept_name)
##            > rows only in employees    1
##            > rows only in departments  1
##            > matched rows              4
##            >                          ===
##            > rows total                6
##   emp_id name dept_id dept_name
## 1      1 张三     101    销售部
## 2      2 李四     102    技术部
## 3      3 王五     101    销售部
## 4      4 赵六     103      <NA>
## 5      5 钱七     102    技术部
## 6     NA <NA>     104    财务部

小结:全连接保留所有记录,无匹配处填充NA。

7.7 键值连接——半连接

只保留左表中在右表有匹配的行,不添加右表列。

# 半连接:只返回employees中在departments有匹配的行
semi_join(employees, departments, by = "dept_id")
## semi_join: added no columns
##            > rows only in employees   (1)
##            > rows only in departments (1)
##            > matched rows              4
##            >                          ===
##            > rows total                4
##   emp_id name dept_id
## 1      1 张三     101
## 2      2 李四     102
## 3      3 王五     101
## 4      5 钱七     102

小结:半连接用于筛选,不添加新列。

7.8 键值连接——反连接

只保留左表中在右表没有匹配的行。

# 反连接:返回employees中在departments没有匹配的行
anti_join(employees, departments, by = "dept_id")
## anti_join: added no columns
##            > rows only in employees    1
##            > rows only in departments (1)
##            > matched rows             (4)
##            >                          ===
##            > rows total                1
##   emp_id name dept_id
## 1      4 赵六     103

小结:反连接常用于找出缺失的记录。

7.9 基于多个键的连接

使用多个列作为连接键。

# 创建示例数据
sales1 <- data.frame(
  year = c(2023, 2023, 2024, 2024),
  quarter = c(1, 2, 1, 2),
  sales = c(100, 150, 120, 180)
)

sales2 <- data.frame(
  year = c(2023, 2023, 2024, 2024),
  quarter = c(1, 2, 1, 2),
  profit = c(20, 30, 25, 40)
)

# 基于year和quarter两个键连接
left_join(sales1, sales2, by = c("year", "quarter"))
## left_join: added one column (profit)
##            > rows only in sales1  0
##            > rows only in sales2 (0)
##            > matched rows         4
##            >                     ===
##            > rows total           4
##   year quarter sales profit
## 1 2023       1   100     20
## 2 2023       2   150     30
## 3 2024       1   120     25
## 4 2024       2   180     40

小结:多键连接可以更精确地匹配记录。

7.10 模糊匹配连接

当键值不完全匹配时,可以使用模糊匹配。

# 使用fuzzyjoin包进行模糊匹配
# 如果没有安装,先运行:install.packages("fuzzyjoin")
library(fuzzyjoin)

# 创建示例数据
company1 <- data.frame(
  name = c("阿里巴巴", "腾讯科技", "百度公司")
)

company2 <- data.frame(
  name = c("阿里巴巴集团", "腾讯", "百度"),
  stock_code = c("BABA", "0700", "BIDU")
)

# 字符串差异连接
stringdist_left_join(company1, company2, 
                     by = "name", 
                     max_dist = 3,  # 最大编辑距离
                     distance_col = "dist")

小结fuzzyjoin包提供了多种模糊匹配方法,适合名称不完全一致的数据。


第七章总结

连接类型 函数 保留记录
内连接 inner_join() 两表都有匹配的
左连接 left_join() 左表全部
右连接 right_join() 右表全部
全连接 full_join() 两表全部
半连接 semi_join() 左表有匹配的
反连接 anti_join() 左表无匹配的

第八章:数据重塑(长宽转换)

数据重塑是数据整理的重要技能。本章介绍长宽格式转换和数据处理技巧。

8.1 宽表转长表

将宽格式数据转换为长格式。

# 创建宽格式数据
wide_data <- data.frame(
  name = c("张三", "李四", "王五"),
  math = c(85, 90, 78),
  english = c(88, 85, 92),
  physics = c(90, 88, 85)
)

# 使用pivot_longer()转换为长格式
library(tidyr)

long_data <- pivot_longer(
  wide_data,
  cols = c(math, english, physics),  # 需要转换的列
  names_to = "subject",               # 新列名(存储原列名)
  values_to = "score"                 # 新列名(存储原值)
)
## pivot_longer: reorganized (math, english, physics) into (subject, score) [was
## 3x4, now 9x3]
long_data
## # A tibble: 9 × 3
##   name  subject score
##   <chr> <chr>   <dbl>
## 1 张三  math       85
## 2 张三  english    88
## 3 张三  physics    90
## 4 李四  math       90
## 5 李四  english    85
## 6 李四  physics    88
## 7 王五  math       78
## 8 王五  english    92
## 9 王五  physics    85

小结pivot_longer()gather()的现代替代,语法更清晰。

8.2 长表转宽表

将长格式数据转换为宽格式。

# 使用pivot_wider()转换为宽格式
pivot_wider(
  long_data,
  names_from = subject,   # 列名来源
  values_from = score     # 值来源
)
## pivot_wider: reorganized (subject, score) into (math, english, physics) [was
## 9x3, now 3x4]
## # A tibble: 3 × 4
##   name   math english physics
##   <chr> <dbl>   <dbl>   <dbl>
## 1 张三     85      88      90
## 2 李四     90      85      88
## 3 王五     78      92      85
# 旧版gather()和spread()函数
# gather():宽转长
gather(wide_data, key = "subject", value = "score", math:physics)
## gather: reorganized (math, english, physics) into (subject, score) [was 3x4,
## now 9x3]
##   name subject score
## 1 张三    math    85
## 2 李四    math    90
## 3 王五    math    78
## 4 张三 english    88
## 5 李四 english    85
## 6 王五 english    92
## 7 张三 physics    90
## 8 李四 physics    88
## 9 王五 physics    85
# spread():长转宽
spread(long_data, key = subject, value = score)
## spread: reorganized (subject, score) into (english, math, physics) [was 9x3,
## now 3x4]
## # A tibble: 3 × 4
##   name  english  math physics
##   <chr>   <dbl> <dbl>   <dbl>
## 1 李四       85    90      88
## 2 王五       92    78      85
## 3 张三       88    85      90

小结pivot_wider()spread()的现代替代,功能更强大。

8.3 分隔列

将一列拆分为多列。

# 创建需要分隔的数据
split_data <- data.frame(
  id = 1:3,
  info = c("张三_25_北京", "李四_30_上海", "王五_28_广州")
)

# 使用separate()分隔列
separate(
  split_data,
  col = info,                    # 要分隔的列
  into = c("name", "age", "city"),  # 新列名
  sep = "_"                      # 分隔符
)
##   id name age city
## 1  1 张三  25 北京
## 2  2 李四  30 上海
## 3  3 王五  28 广州
# 使用extract()通过正则表达式提取
extract(
  split_data,
  col = info,
  into = c("name", "age", "city"),
  regex = "(.+)_(.+)_(.+)"
)
##   id name age city
## 1  1 张三  25 北京
## 2  2 李四  30 上海
## 3  3 王五  28 广州

小结separate()按分隔符拆分,extract()用正则表达式提取。

8.4 合并列

将多列合并为一列。

# 创建需要合并的数据
merge_data <- data.frame(
  name = c("张三", "李四", "王五"),
  age = c(25, 30, 28),
  city = c("北京", "上海", "广州")
)

# 使用unite()合并列
unite(
  merge_data,
  col = "info",           # 新列名
  name, age, city,        # 要合并的列
  sep = "_"               # 分隔符
)
##           info
## 1 张三_25_北京
## 2 李四_30_上海
## 3 王五_28_广州
# 删除原列(默认行为)
unite(merge_data, "info", name:city, sep = "-", remove = TRUE)
##           info
## 1 张三-25-北京
## 2 李四-30-上海
## 3 王五-28-广州

小结unite()separate()的逆操作,用于合并多列。

8.5 嵌套与取消嵌套

处理嵌套数据结构。

# 创建嵌套数据
nested_data <- data.frame(
  group = c("A", "A", "B", "B"),
  value = c(1, 2, 3, 4)
)

# nest()将数据嵌套为列表列
nested <- nested_data %>%
  group_by(group) %>%
  nest()

# 查看嵌套结构
nested

# unnest()取消嵌套
unnest(nested, cols = c(data))

小结:嵌套数据适合分组处理和函数式编程。


第八章总结

操作 函数 说明
宽转长 pivot_longer() 宽格式转长格式
长转宽 pivot_wider() 长格式转宽格式
分隔列 separate() 一列拆多列
合并列 unite() 多列合一列
嵌套 nest() 创建嵌套数据
取消嵌套 unnest() 展开嵌套数据

第九章:字符与日期处理

字符和日期是数据分析中常见的数据类型。本章介绍字符和日期的处理方法。

9.1 字符去除空格

处理字符串中的多余空格。

# 创建含有空格的字符串
text_data <- c("  hello  ", "world  ", "  R语言")

# trimws()去除首尾空格
trimws(text_data)
## [1] "hello" "world" "R语言"
# stringr包提供更多字符串处理函数
library(stringr)

# str_trim()去除首尾空格
str_trim(text_data)
## [1] "hello" "world" "R语言"
# str_squish()去除首尾空格并压缩内部多余空格
text_with_space <- c("hello   world", "R  语言")
str_squish(text_with_space)
## [1] "hello world" "R 语言"

小结trimws()是R内置函数,stringr包功能更丰富。

9.2 字符替换

替换字符串中的内容。

# 创建示例字符串
text <- "Hello World, Hello R"

# gsub()全局替换
gsub("Hello", "Hi", text)
## [1] "Hi World, Hi R"
# sub()只替换第一个匹配
sub("Hello", "Hi", text)
## [1] "Hi World, Hello R"
# stringr包的str_replace()
str_replace(text, "Hello", "Hi")  # 替换第一个
## [1] "Hi World, Hello R"
# str_replace_all()替换所有
str_replace_all(text, "Hello", "Hi")
## [1] "Hi World, Hi R"

小结gsub()替换所有匹配,sub()只替换第一个。

9.3 字符分割

将字符串分割成多个部分。

# 创建示例字符串
text <- "apple,banana,orange"

# str_split()分割字符串
str_split(text, ",")
## [[1]]
## [1] "apple"  "banana" "orange"
# 限制分割次数
str_split(text, ",", n = 2)
## [[1]]
## [1] "apple"         "banana,orange"
# str_split_fixed()返回矩阵
str_split_fixed(text, ",", n = 3)
##      [,1]    [,2]     [,3]    
## [1,] "apple" "banana" "orange"
# separate_rows()将分割结果展开为多行
library(tidyr)
df <- data.frame(id = 1, fruits = "apple,banana,orange")
separate_rows(df, fruits, sep = ",")
## # A tibble: 3 × 2
##      id fruits
##   <dbl> <chr> 
## 1     1 apple 
## 2     1 banana
## 3     1 orange

小结str_split()返回列表,str_split_fixed()返回矩阵。

9.4 字符提取

从字符串中提取内容。

# 创建示例字符串
text <- c("apple123", "banana456", "orange789")

# str_extract()提取第一个匹配
str_extract(text, "[0-9]+")  # 提取数字
## [1] "123" "456" "789"
# str_extract_all()提取所有匹配
str_extract_all(text, "[a-z]+")  # 提取字母
## [[1]]
## [1] "apple"
## 
## [[2]]
## [1] "banana"
## 
## [[3]]
## [1] "orange"
# str_sub()按位置提取
str_sub(text, start = 1, end = 5)
## [1] "apple" "banan" "orang"
# 负数表示从末尾开始
str_sub(text, start = -3)  # 提取最后3个字符
## [1] "123" "456" "789"

小结str_extract()使用正则表达式,str_sub()按位置提取。

9.5 字符大小写转换

转换字符串的大小写。

# 创建示例字符串
text <- c("Hello World", "R LANGUAGE")

# toupper()转大写
toupper(text)
## [1] "HELLO WORLD" "R LANGUAGE"
# tolower()转小写
tolower(text)
## [1] "hello world" "r language"
# str_to_title()首字母大写
str_to_title("hello world")
## [1] "Hello World"
# str_to_upper()和str_to_lower()
str_to_upper(text)
## [1] "HELLO WORLD" "R LANGUAGE"
str_to_lower(text)
## [1] "hello world" "r language"

小结:R内置函数简单直接,stringr函数支持本地化设置。

9.6 字符拼接

将多个字符串连接在一起。

# paste()和paste0()拼接字符串
paste("Hello", "World")        # 默认用空格分隔
## [1] "Hello World"
paste("Hello", "World", sep = "_")  # 自定义分隔符
## [1] "Hello_World"
paste0("Hello", "World")       # 无分隔符
## [1] "HelloWorld"
# str_c()拼接字符串
str_c("Hello", "World", sep = " ")
## [1] "Hello World"
# 拼接向量
fruits <- c("apple", "banana", "orange")
paste(fruits, collapse = ", ")  # 将向量元素合并
## [1] "apple, banana, orange"
# str_flatten()展平字符串向量
str_flatten(fruits, collapse = ", ")
## [1] "apple, banana, orange"

小结paste()是R内置函数,str_c()来自stringr包。

9.7 模式匹配与检测

检测字符串是否匹配模式。

# 创建示例字符串
text <- c("apple", "banana", "orange", "grape")

# str_detect()检测是否匹配
str_detect(text, "a")  # 是否包含"a"
## [1] TRUE TRUE TRUE TRUE
# grepl()检测是否匹配(R内置)
grepl("a", text)
## [1] TRUE TRUE TRUE TRUE
# 返回匹配的位置
str_which(text, "a")
## [1] 1 2 3 4
# 返回匹配的数量
str_count(text, "a")
## [1] 1 3 1 1

小结str_detect()返回逻辑向量,str_which()返回位置。

9.8 字符串长度

获取字符串的长度。

# 创建示例字符串
text <- c("Hello", "世界", "R语言")

# nchar()获取字符数
nchar(text)
## [1] 5 2 3
# str_length()获取字符数
str_length(text)
## [1] 5 2 3
# str_width()获取显示宽度
str_width(text)
## [1] 5 4 5

小结nchar()是R内置函数,注意中文字符的长度计算。

9.9 日期格式解析

将字符串转换为日期。

# 创建日期字符串
date_strings <- c("2024-01-15", "2024-06-20", "2024-12-31")

# as.Date()转换为日期
as.Date(date_strings)
## [1] "2024-01-15" "2024-06-20" "2024-12-31"
# 指定格式
as.Date("15/01/2024", format = "%d/%m/%Y")
## [1] "2024-01-15"
# lubridate包提供更便捷的函数
library(lubridate)

# ymd()解析年月日格式
ymd("2024-01-15")
## [1] "2024-01-15"
ymd("20240115")
## [1] "2024-01-15"
ymd("2024/01/15")
## [1] "2024-01-15"
# mdy()解析月日年格式
mdy("01-15-2024")
## [1] "2024-01-15"
# dmy()解析日月年格式
dmy("15-01-2024")
## [1] "2024-01-15"

小结lubridate包的函数更直观,能自动识别多种格式。

9.10 日期提取成分

从日期中提取年、月、日等成分。

# 创建日期
date <- ymd("2024-06-15")

# 提取年份
year(date)
## [1] 2024
# 提取月份
month(date)
## [1] 6
month(date, label = TRUE)  # 返回月份名称
## [1] 6月
## 12 Levels: 1月 < 2月 < 3月 < 4月 < 5月 < 6月 < 7月 < 8月 < 9月 < ... < 12月
# 提取日
day(date)
## [1] 15
# 提取星期
wday(date)
## [1] 7
wday(date, label = TRUE)  # 返回星期名称
## [1] 周六
## Levels: 周日 < 周一 < 周二 < 周三 < 周四 < 周五 < 周六
# 提取季度
quarter(date)
## [1] 2
# 提取一年中的第几天
yday(date)
## [1] 167
# 提取一周中的第几天
wday(date)
## [1] 7

小结lubridate包提供了丰富的日期成分提取函数。

9.11 日期计算

进行日期的加减运算。

# 创建日期
date <- ymd("2024-06-15")

# 加减天数
date + 1
## [1] "2024-06-16"
date - 7
## [1] "2024-06-08"
# 加减月数
date %m+% months(1)
## [1] "2024-07-15"
date %m-% months(2)
## [1] "2024-04-15"
# 加减年数
date + years(1)
## [1] "2025-06-15"
# 计算日期差
date1 <- ymd("2024-01-01")
date2 <- ymd("2024-06-15")

# difftime()计算时间差
difftime(date2, date1, units = "days")
## Time difference of 166 days
# 使用减号计算
date2 - date1
## Time difference of 166 days
# 计算间隔
interval(date1, date2)
## [1] 2024-01-01 UTC--2024-06-15 UTC
# 计算间隔的时长
interval(date1, date2) / days(1)
## [1] 166

小结difftime()计算时间差,lubridate提供更丰富的日期运算。

9.12 时间序列类基础转换

处理时间序列数据。

# 创建时间序列数据
ts_data <- c(10, 20, 30, 40, 50, 60)

# ts()创建时间序列对象
ts_obj <- ts(ts_data, start = c(2024, 1), frequency = 12)  # 月度数据
ts_obj

# xts包提供更强大的时间序列功能
# 如果没有安装,先运行:install.packages("xts")
library(xts)

# 创建xts对象
dates <- ymd(c("2024-01-01", "2024-02-01", "2024-03-01"))
xts_obj <- xts(ts_data[1:3], order.by = dates)
xts_obj

# zoo包的基础转换
library(zoo)
zoo_obj <- zoo(ts_data[1:3], order.by = dates)
zoo_obj

小结ts是R内置时间序列类,xtszoo提供更强大的功能。


第九章总结

操作 函数 说明
去空格 trimws()str_trim() 去除首尾空格
替换 gsub()str_replace() 替换字符串
分割 str_split() 分割字符串
提取 str_extract()str_sub() 提取子串
拼接 paste()str_c() 连接字符串
日期解析 ymd()as.Date() 字符串转日期
日期提取 year()month() 提取日期成分
日期计算 difftime()interval() 日期运算

第十章:分组与聚合操作

分组操作是数据分析的核心技能。本章介绍分组后的各种操作。

10.1 分组操作基础

使用group_by()创建分组数据。

# 创建示例数据
group_data <- data.frame(
  department = c("销售", "技术", "销售", "技术", "财务",
                 "销售", "技术", "财务", "销售", "技术"),
  employee = c("张三", "李四", "王五", "赵六", "钱七",
               "孙八", "周九", "吴十", "郑一", "陈二"),
  salary = c(5000, 8000, 5500, 9000, 7000,
             4500, 10000, 7500, 5200, 8500)
)

# 创建分组数据框
grouped <- group_by(group_data, department)
## group_by: one grouping variable (department)
# 查看分组信息
group_vars(grouped)
## [1] "department"
n_groups(grouped)
## [1] 3
# 使用ungroup()取消分组
ungroup(grouped)
## ungroup: no grouping variables remain
## # A tibble: 10 × 3
##    department employee salary
##    <chr>      <chr>     <dbl>
##  1 销售       张三       5000
##  2 技术       李四       8000
##  3 销售       王五       5500
##  4 技术       赵六       9000
##  5 财务       钱七       7000
##  6 销售       孙八       4500
##  7 技术       周九      10000
##  8 财务       吴十       7500
##  9 销售       郑一       5200
## 10 技术       陈二       8500

小结group_by()创建分组,ungroup()取消分组。

10.2 分组后筛选

在组内进行筛选。

# 筛选每个部门薪资最高的员工
group_data %>%
  group_by(department) %>%
  filter(salary == max(salary))
## group_by: one grouping variable (department)
## filter (grouped): removed 7 rows (70%), 3 rows remaining (removed 0 groups, 3 groups remaining)
## # A tibble: 3 × 3
## # Groups:   department [3]
##   department employee salary
##   <chr>      <chr>     <dbl>
## 1 销售       王五       5500
## 2 技术       周九      10000
## 3 财务       吴十       7500
# 筛选薪资高于部门平均值的员工
group_data %>%
  group_by(department) %>%
  filter(salary > mean(salary))
## group_by: one grouping variable (department)
## filter (grouped): removed 5 rows (50%), 5 rows remaining (removed 0 groups, 3 groups remaining)
## # A tibble: 5 × 3
## # Groups:   department [3]
##   department employee salary
##   <chr>      <chr>     <dbl>
## 1 销售       王五       5500
## 2 技术       赵六       9000
## 3 技术       周九      10000
## 4 财务       吴十       7500
## 5 销售       郑一       5200
# 筛选每个部门的前2名
group_data %>%
  group_by(department) %>%
  slice_max(salary, n = 2)
## group_by: one grouping variable (department)
## slice_max (grouped): removed 4 rows (40%), 6 rows remaining (removed 0 groups, 3 groups remaining)
## # A tibble: 6 × 3
## # Groups:   department [3]
##   department employee salary
##   <chr>      <chr>     <dbl>
## 1 技术       周九      10000
## 2 技术       赵六       9000
## 3 财务       吴十       7500
## 4 财务       钱七       7000
## 5 销售       王五       5500
## 6 销售       郑一       5200

小结:分组后的筛选操作会在每个组内独立进行。

10.3 分组后排序

在组内进行排序。

# 每个部门内按薪资降序排列
group_data %>%
  group_by(department) %>%
  arrange(desc(salary), .by_group = TRUE)
## group_by: one grouping variable (department)
## # A tibble: 10 × 3
## # Groups:   department [3]
##    department employee salary
##    <chr>      <chr>     <dbl>
##  1 技术       周九      10000
##  2 技术       赵六       9000
##  3 技术       陈二       8500
##  4 技术       李四       8000
##  5 财务       吴十       7500
##  6 财务       钱七       7000
##  7 销售       王五       5500
##  8 销售       郑一       5200
##  9 销售       张三       5000
## 10 销售       孙八       4500

小结.by_group = TRUE保证结果按组排列。

10.4 分组后计算排名

计算组内排名。

# 计算组内薪资排名
group_data %>%
  group_by(department) %>%
  mutate(
    rank = rank(desc(salary)),           # 平均排名
    min_rank = min_rank(desc(salary)),   # 最小排名
    dense_rank = dense_rank(desc(salary)), # 紧凑排名
    row_num = row_number(desc(salary))   # 行号排名
  )
## group_by: one grouping variable (department)
## mutate (grouped): new variable 'rank' (double) with 4 unique values and 0% NA
##                   new variable 'min_rank' (integer) with 4 unique values and 0% NA
##                   new variable 'dense_rank' (integer) with 4 unique values and 0% NA
##                   new variable 'row_num' (integer) with 4 unique values and 0% NA
## # A tibble: 10 × 7
## # Groups:   department [3]
##    department employee salary  rank min_rank dense_rank row_num
##    <chr>      <chr>     <dbl> <dbl>    <int>      <int>   <int>
##  1 销售       张三       5000     3        3          3       3
##  2 技术       李四       8000     4        4          4       4
##  3 销售       王五       5500     1        1          1       1
##  4 技术       赵六       9000     2        2          2       2
##  5 财务       钱七       7000     2        2          2       2
##  6 销售       孙八       4500     4        4          4       4
##  7 技术       周九      10000     1        1          1       1
##  8 财务       吴十       7500     1        1          1       1
##  9 销售       郑一       5200     2        2          2       2
## 10 技术       陈二       8500     3        3          3       3

小结:不同排名方法处理并列的方式不同。

10.5 分组后计算比例

计算组内比例。

# 计算薪资占部门总薪资的比例
group_data %>%
  group_by(department) %>%
  mutate(
    dept_total = sum(salary),
    salary_prop = salary / dept_total * 100
  )
## group_by: one grouping variable (department)
## mutate (grouped): new variable 'dept_total' (double) with 3 unique values and 0% NA
##                   new variable 'salary_prop' (double) with 10 unique values and 0% NA
## # A tibble: 10 × 5
## # Groups:   department [3]
##    department employee salary dept_total salary_prop
##    <chr>      <chr>     <dbl>      <dbl>       <dbl>
##  1 销售       张三       5000      20200        24.8
##  2 技术       李四       8000      35500        22.5
##  3 销售       王五       5500      20200        27.2
##  4 技术       赵六       9000      35500        25.4
##  5 财务       钱七       7000      14500        48.3
##  6 销售       孙八       4500      20200        22.3
##  7 技术       周九      10000      35500        28.2
##  8 财务       吴十       7500      14500        51.7
##  9 销售       郑一       5200      20200        25.7
## 10 技术       陈二       8500      35500        23.9
# 使用prop.table()计算比例
group_data %>%
  group_by(department) %>%
  mutate(prop = prop.table(salary))
## group_by: one grouping variable (department)
## mutate (grouped): new variable 'prop' (double) with 10 unique values and 0% NA
## # A tibble: 10 × 4
## # Groups:   department [3]
##    department employee salary  prop
##    <chr>      <chr>     <dbl> <dbl>
##  1 销售       张三       5000 0.248
##  2 技术       李四       8000 0.225
##  3 销售       王五       5500 0.272
##  4 技术       赵六       9000 0.254
##  5 财务       钱七       7000 0.483
##  6 销售       孙八       4500 0.223
##  7 技术       周九      10000 0.282
##  8 财务       吴十       7500 0.517
##  9 销售       郑一       5200 0.257
## 10 技术       陈二       8500 0.239

小结:组内比例常用于分析贡献度。

10.6 分组后计算累计值

计算组内累计值。

# 创建时间序列数据
time_data <- data.frame(
  department = c("A", "A", "A", "B", "B", "B"),
  month = c(1, 2, 3, 1, 2, 3),
  sales = c(100, 150, 120, 80, 90, 110)
)

# 计算组内累计销售额
time_data %>%
  group_by(department) %>%
  arrange(month) %>%
  mutate(
    cum_sales = cumsum(sales),
    cum_avg = cummean(sales)
  )
## group_by: one grouping variable (department)
## mutate (grouped): new variable 'cum_sales' (double) with 6 unique values and 0% NA
##                   new variable 'cum_avg' (double) with 6 unique values and 0% NA
## # A tibble: 6 × 5
## # Groups:   department [2]
##   department month sales cum_sales cum_avg
##   <chr>      <dbl> <dbl>     <dbl>   <dbl>
## 1 A              1   100       100   100  
## 2 B              1    80        80    80  
## 3 A              2   150       250   125  
## 4 B              2    90       170    85  
## 5 A              3   120       370   123. 
## 6 B              3   110       280    93.3

小结:分组累计常用于时间序列分析。

10.7 分组后填充缺失值

在组内填充缺失值。

# 创建含缺失值的分组数据
na_group_data <- data.frame(
  group = c("A", "A", "A", "B", "B", "B"),
  value = c(10, NA, 30, NA, 50, NA)
)

# 在组内前向填充
na_group_data %>%
  group_by(group) %>%
  fill(value, .direction = "down")
## group_by: one grouping variable (group)
## fill (grouped): changed 2 values (33%) of 'value' (2 fewer NAs)
## # A tibble: 6 × 2
## # Groups:   group [2]
##   group value
##   <chr> <dbl>
## 1 A        10
## 2 A        10
## 3 A        30
## 4 B        NA
## 5 B        50
## 6 B        50
# 在组内双向填充
na_group_data %>%
  group_by(group) %>%
  fill(value, .direction = "downup")
## group_by: one grouping variable (group)
## fill (grouped): changed 3 values (50%) of 'value' (3 fewer NAs)
## # A tibble: 6 × 2
## # Groups:   group [2]
##   group value
##   <chr> <dbl>
## 1 A        10
## 2 A        10
## 3 A        30
## 4 B        50
## 5 B        50
## 6 B        50

小结:分组填充确保填充只在同组内进行。

10.8 分组后自定义函数

在分组后应用自定义函数。

# 定义自定义函数
salary_stats <- function(x) {
  c(
    mean = mean(x),
    sd = sd(x),
    cv = sd(x) / mean(x)  # 变异系数
  )
}

# 应用自定义函数
group_data %>%
  group_by(department) %>%
  summarise(
    stats = list(salary_stats(salary))
  ) %>%
  unnest_wider(stats)
## group_by: one grouping variable (department)
## summarise: now 3 rows and 2 columns, ungrouped
## # A tibble: 3 × 4
##   department  mean    sd     cv
##   <chr>      <dbl> <dbl>  <dbl>
## 1 技术        8875  854. 0.0962
## 2 财务        7250  354. 0.0488
## 3 销售        5050  420. 0.0832
# 使用group_modify()应用函数
group_data %>%
  group_by(department) %>%
  group_modify(~ {
    .x %>%
      summarise(
        avg = mean(salary),
        max = max(salary)
      )
  })
## group_by: one grouping variable (department)
## summarise: now one row and 2 columns, ungrouped
## summarise: now one row and 2 columns, ungrouped
## summarise: now one row and 2 columns, ungrouped
## # A tibble: 3 × 3
## # Groups:   department [3]
##   department   avg   max
##   <chr>      <dbl> <dbl>
## 1 技术        8875 10000
## 2 财务        7250  7500
## 3 销售        5050  5500

小结:自定义函数可以实现复杂的分组计算。


第十章总结

操作 函数 说明
分组 group_by() 创建分组
取消分组 ungroup() 取消分组
组内筛选 filter() 组内条件筛选
组内排名 rank()row_number() 组内排名
组内比例 prop.table() 组内比例
组内累计 cumsum() 组内累计

第十一章:数据采样与分割

数据采样和分割是机器学习中的重要步骤。本章介绍各种采样和分割方法。

11.1 随机抽样

从数据中随机抽取样本。

# 创建示例数据
sample_data <- data.frame(
  id = 1:20,
  value = rnorm(20)
)

# 设置随机种子
set.seed(123)

# sample()随机抽样
indices <- sample(1:nrow(sample_data), size = 10, replace = FALSE)
sample_data[indices, ]
##    id       value
## 15 15 -0.33491276
## 19 19 -0.14539355
## 14 14 -0.21586539
## 3   3  1.69018435
## 10 10  0.82553984
## 2   2 -1.72727063
## 6   6  0.54909674
## 11 11 -0.05568601
## 5   5  2.52833655
## 4   4  0.50381245
# 使用dplyr的slice_sample()
library(dplyr)
slice_sample(sample_data, n = 10)
## slice_sample: removed 10 rows (50%), 10 rows remaining
##    id       value
## 1  14 -0.21586539
## 2   5  2.52833655
## 3   9  1.29476325
## 4   3  1.69018435
## 5   8 -1.04889314
## 6  10  0.82553984
## 7   7  0.23821292
## 8  15 -0.33491276
## 9  18  1.07061054
## 10 17 -0.08542326
slice_sample(sample_data, prop = 0.5)  # 抽取50%
## slice_sample: removed 10 rows (50%), 10 rows remaining
##    id       value
## 1   4  0.50381245
## 2  14 -0.21586539
## 3  17 -0.08542326
## 4  11 -0.05568601
## 5   7  0.23821292
## 6   5  2.52833655
## 7  12 -0.78438222
## 8  10  0.82553984
## 9  16 -1.08569914
## 10  9  1.29476325

小结sample()是R内置函数,slice_sample()是dplyr的现代函数。

11.2 分层抽样

按比例从各组抽取样本。

# 创建分组数据
strata_data <- data.frame(
  group = rep(c("A", "B", "C"), each = 10),
  value = rnorm(30)
)

# 分层抽样:每组抽取3个
strata_data %>%
  group_by(group) %>%
  slice_sample(n = 3)
## group_by: one grouping variable (group)
## slice_sample (grouped): removed 21 rows (70%), 9 rows remaining (removed 0 groups, 3 groups remaining)
## # A tibble: 9 × 2
## # Groups:   group [3]
##   group   value
##   <chr>   <dbl>
## 1 A     -0.295 
## 2 A      1.25  
## 3 A     -1.69  
## 4 B     -0.695 
## 5 B     -0.208 
## 6 B     -0.306 
## 7 C     -1.12  
## 8 C     -0.0834
## 9 C     -0.0429
# 分层抽样:每组抽取30%
strata_data %>%
  group_by(group) %>%
  slice_sample(prop = 0.3)
## group_by: one grouping variable (group)
## slice_sample (grouped): removed 21 rows (70%), 9 rows remaining (removed 0 groups, 3 groups remaining)
## # A tibble: 9 × 2
## # Groups:   group [3]
##   group   value
##   <chr>   <dbl>
## 1 A      1.25  
## 2 A     -1.14  
## 3 A     -0.625 
## 4 B     -0.0619
## 5 B     -1.27  
## 6 B     -0.695 
## 7 C     -1.12  
## 8 C      1.21  
## 9 C     -0.0285

小结:分层抽样确保每个组都有代表性样本。

11.3 训练集/测试集划分

将数据划分为训练集和测试集。

# 创建示例数据
ml_data <- data.frame(
  id = 1:100,
  x = rnorm(100),
  y = rnorm(100),
  label = sample(c(0, 1), 100, replace = TRUE)
)

# 方法1:随机划分
set.seed(123)
train_indices <- sample(1:nrow(ml_data), size = 0.7 * nrow(ml_data))
train_set <- ml_data[train_indices, ]
test_set <- ml_data[-train_indices, ]

# 方法2:使用caTools包
# 如果没有安装,先运行:install.packages("caTools")
library(caTools)

set.seed(123)
split <- sample.split(ml_data$label, SplitRatio = 0.7)
train_set <- subset(ml_data, split == TRUE)
test_set <- subset(ml_data, split == FALSE)

# 方法3:使用rsample包
# 如果没有安装,先运行:install.packages("rsample")
library(rsample)

set.seed(123)
data_split <- initial_split(ml_data, prop = 0.7)
train_set <- training(data_split)
test_set <- testing(data_split)

小结rsample包提供了更现代的数据分割方法。

11.4 交叉验证折的生成

创建交叉验证数据集。

# 使用caret包创建交叉验证折
library(caret)

set.seed(123)
folds <- createFolds(ml_data$label, k = 5)  # 5折交叉验证

# 查看每折的索引
str(folds)
## List of 5
##  $ Fold1: int [1:20] 1 3 16 20 23 28 35 37 46 49 ...
##  $ Fold2: int [1:20] 5 6 9 13 15 17 19 25 27 30 ...
##  $ Fold3: int [1:20] 8 21 22 29 32 36 41 43 44 45 ...
##  $ Fold4: int [1:20] 2 4 12 14 26 34 38 39 47 58 ...
##  $ Fold5: int [1:20] 7 10 11 18 24 31 33 40 48 53 ...
# 使用rsample包
library(rsample)

set.seed(123)
cv_folds <- vfold_cv(ml_data, v = 5)  # 5折交叉验证
cv_folds
## #  5-fold cross-validation 
## # A tibble: 5 × 2
##   splits          id   
##   <list>          <chr>
## 1 <split [80/20]> Fold1
## 2 <split [80/20]> Fold2
## 3 <split [80/20]> Fold3
## 4 <split [80/20]> Fold4
## 5 <split [80/20]> Fold5
# 查看每折的训练集和测试集
cv_folds$splits[[1]]
## <Analysis/Assess/Total>
## <80/20/100>

小结:交叉验证是评估模型性能的重要方法。

11.5 重采样

Bootstrap重采样。

# 使用rsample包进行bootstrap
library(rsample)

set.seed(123)
boot_samples <- bootstraps(ml_data, times = 100)  # 100次bootstrap
boot_samples
## # Bootstrap sampling 
## # A tibble: 100 × 2
##    splits           id          
##    <list>           <chr>       
##  1 <split [100/41]> Bootstrap001
##  2 <split [100/38]> Bootstrap002
##  3 <split [100/42]> Bootstrap003
##  4 <split [100/40]> Bootstrap004
##  5 <split [100/30]> Bootstrap005
##  6 <split [100/30]> Bootstrap006
##  7 <split [100/36]> Bootstrap007
##  8 <split [100/35]> Bootstrap008
##  9 <split [100/32]> Bootstrap009
## 10 <split [100/36]> Bootstrap010
## # ℹ 90 more rows
# 查看单个bootstrap样本
boot_samples$splits[[1]]
## <Analysis/Assess/Total>
## <100/41/100>
analysis(boot_samples$splits[[1]])  # bootstrap样本
##     id            x            y label
## 1   31 -0.083988713 -0.707607151     1
## 2   79 -1.277450766 -1.472158964     0
## 3   51 -0.260725893  0.286289929     0
## 4   14 -0.374800093 -0.867566116     1
## 5   67 -0.392553554  0.499276876     1
## 6   42 -0.230262235 -0.462879040     0
## 7   50 -1.567675132  2.708168284     1
## 8   43  1.397426653 -1.911151255     1
## 9   14 -0.374800093 -0.867566116     1
## 10  25 -0.627951658  0.736194775     1
## 11  90 -1.595484898 -0.264595238     1
## 12  91  0.851709321 -0.947298279     0
## 13  69 -2.494835444  0.923129239     0
## 14  91  0.851709321 -0.947298279     0
## 15  57  1.871418005 -1.049021530     1
## 16  92 -0.713560806  0.739521328     0
## 17   9 -0.006198262  1.844838671     1
## 18  93  1.066430344  0.896778709     0
## 19  99 -0.564269541  0.489080318     1
## 20  72 -0.084140473 -3.310678727     0
## 21  26 -0.046916726  1.365776554     0
## 22   7 -1.427685784 -0.283702696     0
## 23  42 -0.230262235 -0.462879040     0
## 24   9 -0.006198262  1.844838671     1
## 25  83  0.837455146  0.795805155     0
## 26  36  1.176220124 -0.126160381     1
## 27  78  0.440692091  0.971703576     1
## 28  81  0.902505834  2.454400353     1
## 29  43  1.397426653 -1.911151255     1
## 30  76 -0.263992919 -1.196351747     0
## 31  15 -0.319393809 -0.501187592     0
## 32  32  0.410363449 -1.275543372     1
## 33   7 -1.427685784 -0.283702696     0
## 34   9 -0.006198262  1.844838671     1
## 35  41  0.526855669 -0.870464442     1
## 36  74 -1.093413799  0.892485867     0
## 37  23  0.206372695  1.900900074     1
## 38  27  0.162618115 -0.576263893     1
## 39  60 -0.620326487 -1.570135523     0
## 40  53  0.853895464 -0.989678019     1
## 41   7 -1.427685784 -0.283702696     0
## 42  53  0.853895464 -0.989678019     1
## 43  27  0.162618115 -0.576263893     1
## 44  96 -1.828626634  0.464941753     0
## 45  38 -0.945617940  0.469902192     1
## 46  89 -0.088685452 -1.388245289     1
## 47  34  1.778741618 -1.093589748     1
## 48  93  1.066430344  0.896778709     0
## 49  69 -2.494835444  0.923129239     0
## 50  72 -0.084140473 -3.310678727     0
## 51  76 -0.263992919 -1.196351747     0
## 52  63  1.117486481 -1.151603163     0
## 53  13 -0.778997240  0.975142936     1
## 54  82 -1.261304177 -1.649345480     1
## 55  97 -1.813533356 -1.951030610     1
## 56  91  0.851709321 -0.947298279     0
## 57  25 -0.627951658  0.736194775     1
## 58  38 -0.945617940  0.469902192     1
## 59  21  0.300279118  1.797140441     0
## 60  79 -1.277450766 -1.472158964     0
## 61  41  0.526855669 -0.870464442     1
## 62  47  0.151611371 -0.284001390     1
## 63  90 -1.595484898 -0.264595238     1
## 64  60 -0.620326487 -1.570135523     0
## 65  95  0.535917056 -1.782057068     0
## 66  16  0.084543768  0.785591163     1
## 67  94 -0.536242590 -0.346000887     0
## 68   6 -0.735026156 -0.331284485     0
## 69  72 -0.084140473 -3.310678727     0
## 70  86 -0.047867742 -0.922960815     1
## 71  86 -0.047867742 -0.922960815     1
## 72  39 -0.665188639  0.960382357     0
## 73  31 -0.083988713 -0.707607151     1
## 74  81  0.902505834  2.454400353     1
## 75  50 -1.567675132  2.708168284     1
## 76  34  1.778741618 -1.093589748     1
## 77   4 -0.228895815 -1.177967499     1
## 78  13 -0.778997240  0.975142936     1
## 79  69 -2.494835444  0.923129239     0
## 80  25 -0.627951658  0.736194775     1
## 81  52  0.961810352 -0.032510251     0
## 82  22  0.074856824 -0.810326258     1
## 83  89 -0.088685452 -1.388245289     1
## 84  32  0.410363449 -1.275543372     1
## 85  25 -0.627951658  0.736194775     1
## 86  87 -2.399197708  0.009898130     1
## 87  35  0.037682847  0.192441582     0
## 88  40  0.452030192 -0.005141288     0
## 89  30  0.305463227  0.791547909     1
## 90  12 -0.782730275 -0.204667665     0
## 91  31 -0.083988713 -0.707607151     1
## 92  30  0.305463227  0.791547909     1
## 93  64  1.184930635 -0.160788483     1
## 94  99 -0.564269541  0.489080318     1
## 95  14 -0.374800093 -0.867566116     1
## 96  93  1.066430344  0.896778709     0
## 97  96 -1.828626634  0.464941753     0
## 98  71  0.628568823 -0.139500325     0
## 99  67 -0.392553554  0.499276876     1
## 100 23  0.206372695  1.900900074     1
assessment(boot_samples$splits[[1]])  # 未被抽中的样本(OOB)
##     id            x           y label
## 1    1  0.412022275 -0.01863398     1
## 2    2  0.793585308  0.36237035     1
## 3    3 -0.152410633  2.01130559     1
## 4    5 -0.900791751 -0.75517048     0
## 5    8  0.619283535  0.31415290     1
## 6   10 -0.685706846 -0.98191715     1
## 7   11 -0.279333528  2.19600376     0
## 8   17 -0.768473603 -2.10224732     0
## 9   18 -0.625910913 -0.04220493     1
## 10  19 -0.900870855 -0.40480941     1
## 11  20  0.663728670 -0.11276597     1
## 12  24 -0.488922835  0.70895444     1
## 13  28  1.292305915 -0.80473232     1
## 14  29 -0.463556502 -0.53506581     0
## 15  33  0.183678241  2.37546446     1
## 16  37 -0.558535810 -1.38843214     0
## 17  44  1.763653031  0.36981604     1
## 18  45  0.485601354 -0.46228806     0
## 19  46 -0.265738944  0.32195589     0
## 20  48  1.376609813  1.09185644     0
## 21  49 -0.180394314  0.16021636     0
## 22  54  0.418796702  0.21180993     1
## 23  55  0.339956513 -0.97201753     0
## 24  56  0.596425059  0.54922401     0
## 25  58  0.602870370  0.44913393     0
## 26  59 -0.766168804 -0.38585565     1
## 27  61  0.790190251  1.22694260     1
## 28  62 -0.241976510  0.66669440     0
## 29  65  1.646474838 -0.97291378     1
## 30  66  0.192996186 -0.61046166     1
## 31  68  0.007051323  0.87936414     1
## 32  70 -0.977296362  0.69313397     0
## 33  73  0.382131608  0.53846580     1
## 34  75 -0.755463961  0.48946204     1
## 35  77 -0.752462697  0.46273785     0
## 36  80  1.177192046  0.52896494     1
## 37  84 -2.348290308 -0.34129317     1
## 38  85  0.610971137 -1.40525380     1
## 39  88 -0.019318956 -0.40885977     1
## 40  98  1.372613707 -0.51611334     0
## 41 100  0.970311227  0.90202473     0

小结:Bootstrap重采样用于估计统计量的分布。


第十一章总结

操作 函数/包 说明
随机抽样 sample()slice_sample() 随机抽取样本
分层抽样 group_by() + slice_sample() 按组抽样
数据划分 caToolsrsample 训练/测试集划分
交叉验证 createFolds()vfold_cv() 创建CV折
Bootstrap bootstraps() 重采样

第十二章:数据清洗实战技巧

本章介绍数据清洗的实战技巧和最佳实践。

12.1 管道操作在清洗中的应用

使用管道操作符使代码更清晰。

# 创建示例数据
raw_data <- data.frame(
  id = c(1, 2, 3, 4, 5),
  name = c("张三 ", " 李四", "王五", "赵六", "钱七"),
  age = c(25, 30, NA, 35, 40),
  salary = c(5000, 6000, 7000, NA, 9000)
)

# 使用管道进行数据清洗
clean_data <- raw_data %>%
  # 去除姓名中的空格
  mutate(name = trimws(name)) %>%
  # 用均值填充缺失值
  mutate(
    age = ifelse(is.na(age), mean(age, na.rm = TRUE), age),
    salary = ifelse(is.na(salary), mean(salary, na.rm = TRUE), salary)
  ) %>%
  # 添加新列
  mutate(salary_level = ifelse(salary > 6000, "高", "低"))
## mutate: changed 2 values (40%) of 'name' (0 new NAs)
## mutate: changed one value (20%) of 'age' (one fewer NA)
##         changed one value (20%) of 'salary' (one fewer NA)
## mutate: new variable 'salary_level' (character) with 2 unique values and 0% NA
clean_data
##   id name  age salary salary_level
## 1  1 张三 25.0   5000           低
## 2  2 李四 30.0   6000           低
## 3  3 王五 32.5   7000           高
## 4  4 赵六 35.0   6750           高
## 5  5 钱七 40.0   9000           高
# 使用R 4.1+的原生管道符 |>
clean_data_native <- raw_data |>
  dplyr::mutate(name = trimws(name))

小结:管道操作使数据处理流程清晰可读。

12.2 清洗流程的自动化

封装清洗函数实现自动化。

# 定义数据清洗函数
clean_employee_data <- function(data) {
  data %>%
    # 去除空格
    mutate(across(where(is.character), trimws)) %>%
    # 填充数值型缺失值
    mutate(across(where(is.numeric), 
                  ~ ifelse(is.na(.), mean(., na.rm = TRUE), .))) %>%
    # 删除全空的行
    filter(complete.cases(.))
}

# 应用清洗函数
clean_data <- clean_employee_data(raw_data)
## mutate: changed 2 values (40%) of 'name' (0 new NAs)
## mutate: changed one value (20%) of 'age' (one fewer NA)
##         changed one value (20%) of 'salary' (one fewer NA)
## filter: no rows removed
clean_data
##   id name  age salary
## 1  1 张三 25.0   5000
## 2  2 李四 30.0   6000
## 3  3 王五 32.5   7000
## 4  4 赵六 35.0   6750
## 5  5 钱七 40.0   9000

小结:封装函数可以实现清洗流程的复用。

12.3 处理特定数据源

处理Excel多表格数据。

# 使用readxl包读取Excel
library(readxl)

# 读取单个工作表
data <- read_excel("data.xlsx", sheet = "Sheet1")

# 读取所有工作表
all_sheets <- excel_sheets("data.xlsx")
all_data <- lapply(all_sheets, function(sheet) {
  read_excel("data.xlsx", sheet = sheet)
})
names(all_data) <- all_sheets

# 合并所有工作表
combined_data <- bind_rows(all_data, .id = "sheet_name")

小结readxl包可以方便地处理Excel文件。

12.4 处理大数据的内存问题

使用data.table处理大数据。

# data.table是处理大数据的高效工具
library(data.table)

# 读取大数据
big_data <- fread("large_file.csv")

# data.table的语法
# DT[i, j, by] 表示:[行筛选, 列操作, 分组]

# 示例:按组汇总
result <- big_data[, .(mean_value = mean(value), count = .N), by = group]

# 链式操作
result <- big_data[
  value > 0,                    # 筛选行
  .(total = sum(value)),        # 计算汇总
  by = group                    # 按组
][order(-total)]                # 排序

小结data.table语法高效,适合处理大数据。

12.5 清洗日志记录

使用tidylog包记录清洗过程。

# tidylog包可以显示dplyr操作的结果统计
# 如果没有安装,先运行:install.packages("tidylog")
library(tidylog)

# 现在dplyr操作会显示详细统计
raw_data %>%
  filter(!is.na(age)) %>%
  select(id, name, age) %>%
  distinct()
# 会显示:filter removed 1 row, select kept 3 columns等

小结tidylog包帮助追踪数据清洗过程。

12.6 数据验证

使用validate包验证数据质量。

# validate包用于数据验证
# 如果没有安装,先运行:install.packages("validate")
library(validate)

# 定义验证规则
rules <- validator(
  age >= 0,                    # 年龄非负
  age <= 120,                  # 年龄上限
  salary > 0,                  # 薪资为正
  !is.na(name)                 # 姓名不能为空
)

# 执行验证
results <- confront(raw_data, rules)

# 查看验证结果
summary(results)

小结validate包可以系统地检查数据质量。

12.7 检查数据一致性

检查数据中的冲突和矛盾。

# 创建示例数据(包含一些不一致的记录)
check_data <- data.frame(
  id = 1:5,
  start_date = as.Date(c("2024-01-01", "2024-02-01", "2024-01-15", "2024-03-01", "2024-02-15")),
  end_date = as.Date(c("2024-01-31", "2024-01-15", "2024-02-15", "2024-03-31", "2024-02-01")),
  amount = c(100, -50, 200, 150, 300)
)

# 检查日期一致性:结束日期应该大于开始日期
invalid_records <- check_data %>%
  mutate(
    date_valid = end_date > start_date,
    amount_valid = amount >= 0
  ) %>%
  filter(!date_valid | !amount_valid)
## mutate: new variable 'date_valid' (logical) with 2 unique values and 0% NA
##         new variable 'amount_valid' (logical) with 2 unique values and 0% NA
## filter: removed 3 rows (60%), 2 rows remaining
# 显示不一致的记录
invalid_records
##   id start_date   end_date amount date_valid amount_valid
## 1  2 2024-02-01 2024-01-15    -50      FALSE        FALSE
## 2  5 2024-02-15 2024-02-01    300      FALSE         TRUE
# 使用assertthat包进行断言检查
library(assertthat)

# 先检查是否存在问题记录,再进行断言
if (nrow(invalid_records) > 0) {
  message("发现 ", nrow(invalid_records), " 条不一致的记录,需要处理")
}
## 发现 2 条不一致的记录,需要处理
# 正确的断言方式:检查清洗后的数据
clean_check_data <- check_data %>%
  filter(end_date > start_date, amount >= 0)
## filter: removed 2 rows (40%), 3 rows remaining
# 对清洗后的数据进行断言
assert_that(nrow(clean_check_data) > 0, msg = "清洗后数据为空")
## [1] TRUE
assert_that(all(clean_check_data$end_date > clean_check_data$start_date), 
            msg = "存在结束日期早于开始日期的记录")
## [1] TRUE

小结:一致性检查可以发现数据中的逻辑错误。

12.8 最终数据输出

将清洗后的数据输出到各种格式。

# 输出为CSV
write_csv(clean_data, "clean_data.csv")

# 输出为Excel
library(writexl)
write_xlsx(clean_data, "clean_data.xlsx")

# 输出为RDS(保留R对象结构)
write_rds(clean_data, "clean_data.rds")

# 输出为RData(可保存多个对象)
save(clean_data, raw_data, file = "data.RData")

# 写入数据库
library(DBI)
con <- dbConnect(RSQLite::SQLite(), "database.db")
dbWriteTable(con, "clean_data", clean_data)
dbDisconnect(con)

小结:根据用途选择合适的输出格式。


第十二章总结

技巧 工具 说明
管道操作 %>%|> 流程清晰
自动化 自定义函数 复用清洗流程
大数据 data.table 高效处理
日志记录 tidylog 追踪过程
数据验证 validate 检查质量
数据输出 write_csv 保存结果

结语

恭喜你完成了R语言数据清洗与预处理的学习!数据清洗是数据分析的基础,掌握这些技能将使你的分析工作更加高效和专业。

记住数据清洗的核心原则:

  1. 先探索后处理:了解数据再动手
  2. 保留原始数据:始终备份原始数据
  3. 记录清洗过程:使用脚本和注释
  4. 验证清洗结果:检查数据质量
  5. 选择合适方法:根据数据特点选择方法