R 语言初步 (3) 自定义函数、简单模拟与数据清洗

本章介绍 R 语言的函数定义方法,以及进行统计模拟、数据清洗的方法。function 关键字自定义函数;r* p* d* q* 分别进行各种分布的模拟;sample 函数进行简单随机抽样;数据清洗包括了缺失数据处理、按行排序、选取/删除部分数据、添加变量/特征和合并数据集操作。

代码和笔记存储在 GitHub 库 【持续更新中,建议 star!】

1 自定义函数

1.1 函数定义

定义函数的一般形式:

1
2
3
4
myfunction <- function(a, b, c) {
statement
return(object)
}
  • myfunction :自定义的函数名称
  • function() :关键字,告诉 R 语言这是自定义的函数
  • a, b, c :为函数的参数,表明函数需要传入的参数
  • statement :函数体,编写函数的操作
  • return(object) :函数的返回值

1.2 传参方式

  • 方式一:按照参数顺序逐个传入
1
myfunction(x1, x2, x3)
  • 方式二:指定参数
1
myfunction(a = x1, b = x2, c = x3)

1.3 示例

  • 函数名:my.data.ana
  • 输入:一个向量 x
  • 任务:计算向量的平均值、最大值、最小值
  • 输出:一个列表储存的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
my.data.ana <- function(x) {
y <- list(mean = mean(x), max = max(x), min = min(x))
return(y)
}

x <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
y <- my.data.ana(x)
y

# $mean
# [1] 5.5

# $max
# [1] 10

# $min
# [1] 1

2 简单模拟

2.1 有关统计分布的函数

r** 分布的随机数函数

d** 分布的密度函数

p** 分布的分布函数

q** 分布的分位数函数

例如:

  • 正态分布的相关函数 rnorm() dnorm() pnorm() qnorm()
  • Gamma 分布的相关函数 rgamma() dgamma() pgamma() qgamma()
  • 泊松分布相关函数 rpois() dpois() ppois() qpois()

2.1.1 正态分布

格式

1
2
3
4
5
6
7
8
9
10
11
# 传入分位数点,得到密度函数值
dnorm(x, mean = 0, sd = 1, log = FALSE)

# 传入分位数点,得到分布函数值/即概率
pnorm(q, mean = 0, sd = 1, lower.tail = TRUE, log.p = FALSE)

# 传入概率值,得到对应的分位数点值
qnorm(p, mean = 0, sd = 1, lower.tail = TRUE, log.p = FALSE)

# 返回 n 个随机数
rnorm(n, mean = 0, sd = 1)

参数

参数 含义
x q 分位数向量
p 概率值向量
n rnorm(n) 返回随机数的个数
mean 均值向量
sd 标准差向量
log log.p 是否取对数,为 TRUE ,则传入 log(p)
lower.tail TRUE ,则概率表达式为 P(Xx)P(X \leq x) 否则,P(X>x)P(X > x)

2.1.2 Poisson 分布

格式

1
2
3
4
5
6
7
8
9
10
11
# 计算概率密度函数 (PMF) 值
dpois(x, lambda, log = FALSE)

# 计算累积分布函数 (CDF) 值
ppois(q, lambda, lower.tail = TRUE, log.p = FALSE)

# 计算分位数(逆 CDF)
qpois(p, lambda, lower.tail = TRUE, log.p = FALSE)

# 生成随机数
rpois(n, lambda)

参数

参数 说明
x q 非负整数分位数点。若输入非整数,dpois 返回 0;ppois 自动取 floor(q) 计算。
p 概率值向量,范围必须在 [0, 1] 之间。
n 生成的随机数数量。
lambda 非负向量。若为负数,返回 NaN
log log.p 逻辑值。若为 TRUE,概率以自然对数形式输入/输出。
lower.tail 逻辑值。若为 TRUE(默认),计算 P(X ≤ x)P(X ≤ q);否则计算 P(X > x)P(X > q)

关键细节

  1. 离散性处理
    dpois(x, lambda) 仅在 x 为整数时返回非零值,否则返回 0。
    ppois(q, lambda) 会对非整数 q 向下取整(等效计算 P(X ≤ floor(q)))。

  2. 分位数函数逻辑
    qpois(p, lambda) 返回最小整数 k,使得 P(X ≤ k) ≥ p
    • 当 lower.tail = FALSE 时,寻找最小 k 使得 P(X > k) ≤ p(即右尾分位数)。

  3. 向量化运算
    所有参数均支持向量化输入,按元素循环计算。例如:

1
2
 # 分别计算 λ=2 时 x=1,λ=3 时 x=2,λ=4 时 x=3 的概率
dpois(x = c(1,2,3), lambda = c(2,3,4))

2.1.3 二项分布

格式

1
2
3
4
5
6
7
8
9
10
11
# 计算概率密度函数 (PMF) 值
dbinom(x, size, prob, log = FALSE)

# 计算累积分布函数 (CDF) 值
pbinom(q, size, prob, lower.tail = TRUE, log.p = FALSE)

# 计算分位数(逆 CDF)
qbinom(p, size, prob, lower.tail = TRUE, log.p = FALSE)

# 生成随机数
rbinom(n, size, prob)

参数

参数 说明
x q 整数分位数点。若输入非整数,dbinom 返回 0;pbinom 自动取 floor(q) 计算。
p 概率值向量,范围必须在 [0, 1] 之间。
n 生成的随机数数量。若输入向量(如 n = c(2,3)),实际生成 sum(n) 个随机数。
size 试验次数(非负整数),若为小数会自动截断为整数。
prob 单次试验成功概率,范围 [0, 1]。若输入负数或 >1 返回 NaN
log log.p 逻辑值。若为 TRUE,概率以自然对数形式输入/输出(例如 log(0.05))。
lower.tail 逻辑值。若为 TRUE(默认),计算 P(X ≤ x)P(X ≤ q);否则计算 P(X > x)P(X > q)

2.2 随机抽样函数 sample

格式

1
sample(x, size, replace = FALSE, prob = NULL)

参数

参数 说明
x 抽样来源。可以是向量或单个整数:若为向量,直接从中抽样;若为整数 n ,等价于从 1:n 抽样
size 抽样数量。必须为非负整数,且当 replace=FALSE 时,size ≤ length(x)
replace 是否放回抽样。默认 FALSE(无放回);TRUE 时为有放回抽样
prob 权重向量。指定每个元素的抽样概率(自动归一化),需与 x 等长。默认等概率抽样

关键细节

  1. x 为整数时的特殊行为
1
2
sample(5, 3)          # 等价于从 1:5 中无放回抽3个数(如 2,4,1)
sample(c(5), 3) # 从单元素向量 5 中有放回抽3次(需设置 replace=TRUE)
  1. 权重概率:若 prob 的和不为1,会自动归一化处理
1
sample(c("A","B"), size=5, replace=TRUE, prob=c(3,1))  # "A"出现概率为 3/(3+1)=75%
  1. 边界条件限制:size 在无放回时一定要比 x 的长度小
1
2
sample(1:10, size=15, replace=TRUE)   # 允许(有放回)
sample(1:10, size=15, replace=FALSE) # 报错

2.3 随机种子 set.seed()

在电脑上生成随机数字时,生成的数字并不是真正的随机数,它们叫做伪随机数 (pseudo random number)。当从任意分布模拟随机数字时,设置随机数字生成器种子 (seed) 是非常重要的。设置随机种子使得实验结果可复现。

1
2
# n 指定一个整数
set.seed(n)
  • 当设置随机种子后,随机结果将会相同
1
2
3
4
5
6
7
set.seed(1)
rnorm(3) # [1] 0.6328626 0.4042683 -0.1061245
rnorm(4) # [1] 1.5952808 0.3295078 -0.8204684 0.4874291

set.seed(1)
rnorm(3) # [1] 0.6328626 0.4042683 -0.1061245
rnorm(4) # [1] 1.5952808 0.3295078 -0.8204684 0.4874291

3 数据清洗

结构化数据:表格形式的数据。每一行代表一个样本/观测,每一列对应一个变量/特征。

创建示例数据表

1
2
3
4
5
6
7
8
9
10
11
12
manager <- c(1, 2, 3, 4, 5)
date0 <- c("10/24/08", "10/28/08", "10/1/08", "10/12/08", "5/1/09")
nationality <- rep(c("US", "UK"), c(2, 3))
gender <- c("M", "F", "F", "M", "F")
age <- c(32, 45, 25, 39, 99)
q1 <- c(5, 3, 3, 3, 2)
q2 <- c(4, 5, 5, 3, 2)
q3 <- c(5, 2, 5, 4, 1)
q4 <- c(5, 5, 5, NA, 2)
q5 <- c(5, 5, 2, NA, 1)

leadership <- data.frame(manager, date0, nationality, gender, age, q1, q2, q3, q4, q5)
1
2
3
4
5
6
7
leadership
# manager date0 nationality gender age q1 q2 q3 q4 q5
# 1 1 10/24/08 US M 32 5 4 5 5 5
# 2 2 10/28/08 US F 45 3 5 2 5 5
# 3 3 10/1/08 UK F 25 3 5 5 5 2
# 4 4 10/12/08 UK M 39 3 3 4 NA NA
# 5 5 5/1/09 UK F 99 2 2 1 2 1

3.1 缺失数据

is.na() 逐个元素比较是否为空 NA

1
is.na(leadership)

直接删除 na.omit 非替换操作,删除空值的行

1
newdata <- na.omit(leadership)
1
2
3
4
5
6
newdata
# manager date0 nationality gender age q1 q2 q3 q4 q5
# 1 1 10/24/08 US M 32 5 4 5 5 5
# 2 2 10/28/08 US F 45 3 5 2 5 5
# 3 3 10/1/08 UK F 25 3 5 5 5 2
# 5 5 5/1/09 UK F 99 2 2 1 2 1

或者采用均值填充 mean(leadership$age)

1
2
3
4
5
# 用中位数代替 q1 列的缺失值
leadership$q1[is.na(leadership$q1)] <- median(leadership$q1, na.rm = TRUE)

# 用均值代替 q2 列的缺失值
leadership$q2[is.na(leadership$q2)] <- mean(leadership$q2, na.rm = TRUE)

其中 na.rm = TRUE 表示在计算均值/中位数等统计量时忽略空值。

3.2 数据按行排序

操作 函数/方法 说明
对向量排序 sort() 返回排序后的值
获取排序索引 order() 返回排序后的索引值,用于按行排序
按列升序排序 data[order(data$column), ] 按指定列升序排列数据框
降序排序 order(x, decreasing = TRUE) 按指定列降序排列数据框
处理缺失值 na.last = TRUE/FALSE 控制缺失值在排序中的位置(默认放在最后)

例如:按照 age 升序排序

1
newdata <- leadership[order(leadership$age), ]

好的,用户需要整理关于R语言中选取和删除部分数据的笔记,特别是使用下标运算、列名以及subset()函数的方法。他们已经提供了一些之前的笔记结构,我需要保持一致的风格和格式。

首先,我需要回忆R语言中数据框的基本操作。下标运算[ , ]的使用是关键,参数可以是正数、负数、逻辑值或空。用户还提到数据框有行名和列名,可以按列名选取列。另外,subset()函数也是一个重要的方法。

接下来,我需要确定如何组织内容。之前的笔记有参数详解、关键细节、示例和注意事项,我应该沿用这个结构。需要解释不同的下标类型如何影响选取或删除数据,比如正数选择特定行或列,负数排除,逻辑值过滤,空表示全选。还要说明如何使用列名来选取列,以及subset()函数的参数和使用场景。

需要注意常见的陷阱,比如混合使用正负下标可能导致错误,逻辑值的回收机制可能引发意外结果,subset()函数在处理变量时的环境问题。还要提到drop参数的影响,避免数据框降维为向量。

然后,准备示例代码。例如,用正数选取行和列,用负数删除,用逻辑表达式过滤行,按列名选取,以及subset()的使用。同时提供输出结果,帮助理解。

最后,总结不同方法的适用场景,帮助用户根据需求选择合适的方式。确保内容清晰,结构合理,语言简洁,符合用户之前的笔记风格。

3.3 选取/删除部分数据

3.3.1 基础方法:下标运算符 [ ]

格式

1
数据框[行下标, 列下标]  # 行和列下标可为正数、负数、逻辑值或空(保留所有行/列)

参数

下标类型 说明
正数 选取指定位置的行/列(如 1:3 选取前3行或前3列)
负数 排除指定位置的行/列(如 -c(2,4) 删除第2和第4行/列)
逻辑值 按 TRUE/FALSE 筛选行/列(如 数据框$列名 > 5 筛选满足条件的行)
空值 保留所有行或列(如 数据框[, 2:4] 保留所有行,选取第2到4列)
列名 通过列名字符串筛选(如 数据框[, c("列名1", "列名2")]

示例

  • 按照数字索引取/删除
1
2
3
4
5
6
7
8
9
# 选取前3行和前2列
df[1:3, 1:2]

# 删除第2行和第3列
df[-2, -3]

# 选取所有行,仅保留第2列
df[, 2] # 结果可能降维为向量
df[, 2, drop=FALSE] # 保持数据框结构
  • 按逻辑条件筛选行
1
2
3
4
5
# 筛选年龄 >30 的行
df[df$age > 30, ]

# 筛选性别为女性且国籍为US的行
df[df$gender == "F" & df$country == "US", ]
  • 按列名选取列
1
2
3
4
5
# 选取指定列
df[, c("age", "gender")]

# 排除指定列
df[, !(colnames(df) %in% c("date", "q5"))]

3.3.2 subset() 函数

格式

1
subset(数据框, subset=行筛选条件, select=列筛选条件)

参数

参数 说明
subset 逻辑表达式,筛选符合条件的行(如 age > 30
select 指定要保留的列,支持列名或列位置(如 c("age", "gender")2:4

示例

  • 筛选年龄 > 30 且性别为男性的行,保留年龄和性别列
1
subset(df, subset = age > 30 & gender == "M", select = c("age", "gender"))

subset()select 参数支持负列名,表示删除/不取对应的列

1
subset(数据框, select = -c(列名1, 列名2, ...))

3.4 添加变量/特征

3.4.1 直接赋值法

通过 $ 符号直接创建新变量

1
2
3
4
5
# 计算 q1 到 q5 的总和 (qsum)
leadership$qsum <- leadership$q1 + leadership$q2 + leadership$q3 + leadership$q4 + leadership$q5

# 计算 qsum 的平均值 (qmean)
leadership$qmean <- leadership$qsum / 5

3.4.2 within() 函数

1
2
3
4
leadership <- within(leadership, {
qsum <- q1 + q2 + q3 + q4 + q5 # 计算总和
qmean <- qsum / 5 # 计算平均值
}

3.5 合并数据集

3.5.1 基础合并函数 cbind() rbind()

cbind():按列合并

  • 语法

    1
    cbind(数据框1, 数据框2)
  • 示例

    1
    2
    3
    df1 <- data.frame(ID = 1:3, Name = c("A", "B", "C"))
    df2 <- data.frame(Age = c(20, 25, 30), Score = c(85, 90, 88))
    combined_col <- cbind(df1, df2) # 合并后列数为 df1 列数 + df2 列数
ID Name Age Score
1 A 20 85
2 B 25 90
3 C 30 88

若行数不同,会报错;列名重复时自动添加后缀(如 ScoreScore.1

rbind():按行合并

  • 语法

    1
    rbind(数据框1, 数据框2)
  • 示例

    1
    2
    df3 <- data.frame(ID = 4:5, Name = c("D", "E"), Age = c(22, 28), Score = c(92, 80))
    combined_row <- rbind(combined_col, df3) # 合并后行数为原两数据框行数之和

    结果
    | ID | Name | Age | Score |
    | ---- | ---- | ---- | ----- |
    | 1 | A | 20 | 85 |
    | 2 | B | 25 | 90 |
    | 3 | C | 30 | 88 |
    | 4 | D | 22 | 92 |
    | 5 | E | 28 | 80 |

若列名不一致,会报错

3.5.2 高级合并函数 merge()

根据共同列(键)合并两个数据框。

  • 语法

    1
    merge(数据框1, 数据框2, by = "列名", all = FALSE)
  • 核心参数

参数 说明
by 指定合并的列名(如 by = "ID"),若未指定则自动匹配同名列。
all.x 是否保留左表所有行(左外连接),默认 FALSE
all.y 是否保留右表所有行(右外连接),默认 FALSE
all all = TRUE 表示全外连接(保留所有行)。

左/右/外连接:按照某列合并时,可能会存在其他列个数不匹配。采用左外连接,则保留左表所有行,右表不足则复制,填入空值;采用右外连接,则保留右表所有行,左表不足则复制,填入空值;采用全连接,则保留左右表所有行,不足则复制,填入空值。

  • 示例

    1
    2
    orders <- data.frame(OrderID = c(101, 102, 103), CustomerID = c(1, 2, 4))
    customers <- data.frame(CustomerID = 1:3, Name = c("A", "B", "C"))
1
2
# 内连接(默认)取交集,[1,2,4] 和 [1, 2, 3] 只有 [1, 2] 相交
inner_join <- merge(orders, customers, by = "CustomerID")

内连接结果

CustomerID OrderID Name
1 101 A
2 102 B
1
2
# 左外连接,左表的 [1, 2, 4] 都保留,则多出来的无法匹配的留空
left_join <- merge(orders, customers, by = "CustomerID", all.x = TRUE)

左外连接结果

CustomerID OrderID Name
1 101 A
2 102 B
4 103 NA