第2讲:数据类型与数据对象
2026年03月18日
read.csv() / write.csv(),read_excel() / write_xlsx()
提示
本讲代码量大,我们需要建一个 Quarto 文件,便于课后反复练习。
R 认识哪几种"数据"?
先看一个让初学者困惑的例子:
错误信息是:Error in x + y : non-numeric argument to binary operator
意思是:你在用"加号"操作一个不是数字的东西。
x 看起来像数字,但它是用引号包起来的——对 R 来说,引号里的东西是文字,不是数字,就像 "苹果" 一样,当然不能做加法。
重要
数据类型决定了数据能做什么操作。数字可以加减乘除,文字可以拼接搜索,逻辑值可以做真假判断。搞清楚类型,才能写出正确的代码。
R 有六种基本类型,日常最常用的是前三种:
| 类型 | 英文名 | 示例 | 用途 |
|---|---|---|---|
| 数值 | numeric / double |
3.14、100
|
计算、统计 |
| 整数 | integer |
1L、100L
|
整数计算(加 L 标记) |
| 字符 | character |
"张三"、"hello"
|
文本、标签 |
| 逻辑 | logical |
TRUE、FALSE
|
条件判断、筛选 |
| 复数 | complex | 3+2i |
数学(很少用到) |
| 原始 | raw | — | 底层字节(几乎用不到) |
注记
日常数据分析中,95% 以上的情况只涉及数值、字符、逻辑三种类型。掌握这三种就够用了。
class() 和 typeof() 查看类型[1] "numeric"
[1] "character"
[1] "logical"
[1] "integer"
[1] "numeric"
[1] "numeric"
字符型就是我们常说的"文本"或"字符串",必须用引号包起来:
[1] "张三 来自 成都"
[1] "张三来自成都"
[1] "张三——数据挖掘与R语言"
警告
"3" 和 3 是完全不同的东西!class("3") 是 "character",不能直接参与数学运算。用 as.numeric("3") 可以转换。
逻辑型只有两个值:TRUE(真)和 FALSE(假):
[1] "logical"
[1] FALSE
[1] FALSE
[1] TRUE
as.*() 函数族当类型不对时,用 as.*() 函数转换:
[1] 3.14
[1] 42
[1] "100"
[1] "3.14"
在运行之前,先猜猜每行的结果:
最基础的数据容器,深挖其用法
重要
向量(vector)是 R 中最基础的数据结构。
R 里几乎所有数据都是向量——包括你认为的"单个数字",实际上是长度为 1 的向量。理解向量,就理解了 R 的核心逻辑。
[1] 7
[1] "numeric"
注记
向量有一个重要约束:所有元素必须是同一种类型。如果混合了不同类型,R 会自动把它们统一成一种类型(称为"类型强制转换")。
[1] 1 3 5 7 9
[1] 0.0 0.2 0.4 0.6 0.8 1.0
向量要求所有元素同类型。如果你混合了类型,R 会悄悄帮你转换:
[1] "1" "2" "三"
[1] 1 0 3 4
[1] "TRUE" "hello"
强制转换的优先级
字符 > 数值 > 逻辑
也就是说,只要混入了字符,全都变字符;混入了数值(没有字符),逻辑值就变成 0 和 1。
这是 R 的"自救机制",但它可能悄悄引入 bug——所以要养成查看数据类型的习惯。
可以给向量的每个元素起个名字,方便按名字访问:
张三 李四 王五 赵六
85 92 78 96
张三 李四 王五 赵六
85 92 78 96
[ ] 取元素取向量中的元素,用方括号 [ ] 加上位置编号(R 从 1 开始计数,不是 0):
[1] 10
[1] 30
[1] 10 30
[1] 20 30 40
[1] 20 30 40 50
[1] 20 30 40
注记
R 的下标从 1 开始,这和 Python、JavaScript 等语言(从 0 开始)不同。如果你以前学过其他语言,要注意这个区别。
这是 R 中最常用、最强大的取数方式——用条件表达式筛选满足要求的元素:
[1] FALSE TRUE FALSE TRUE FALSE FALSE TRUE
[1] 92 96 91
[1] "李四" "赵六" "周九"
提示
逻辑索引是数据筛选的核心思路——先"打标记"(生成 TRUE/FALSE),再"按标记取值"。这个思路在数据框操作中同样适用。
[ ] 不只能取值,还能赋值:
[1] 10 20 999 40 50
[1] 0 20 999 40 0
向量化运算的意思是:对向量中每个元素自动逐一操作,无需写循环:
[1] 2 4 6 8 10
[1] 1 4 9 16 25
[1] 1.00 1.41 1.73 2.00 2.24
[1] 11 12 13 14 15
提示
如果你以前学过 Python 或 Java,你会习惯用 for 循环处理一组数据。在 R 里,向量化运算让你不需要写循环就能完成同样的任务,而且速度更快、代码更简洁。
[1] 603
[1] NA
[1] 86.1
[1] 88
[1] 8.15
[1] 66.5
用一个完整的例子把向量操作串起来:
# 某班10位同学的三次作业成绩(满分100)
姓名 <- c("A同学","B同学","C同学","D同学","E同学",
"F同学","G同学","H同学","I同学","J同学")
作业1 <- c(88, 72, 95, 61, 83, 90, 77, 68, 92, 85)
作业2 <- c(91, 68, 87, 75, 80, 95, 82, 71, 88, 90)
作业3 <- c(85, 80, 92, 70, 88, 88, 79, 65, 95, 83)
# 计算每人的平均分
均分 <- (作业1 + 作业2 + 作业3) / 3
round(均分, 1) # 保留1位小数 [1] 88.0 73.3 91.3 68.7 83.7 91.0 79.3 68.0 91.7 86.0
向量的二维升级
矩阵是二维的数据结构——有行,也有列:
[,1] [,2] [,3] [,4]
[1,] 1 2 3 4
[2,] 5 6 7 8
[3,] 9 10 11 12
注记
矩阵和向量一样,所有元素必须是同一种类型。矩阵本质上是"有形状的向量"——把向量按行列排列就得到矩阵。
语文 数学 英语
张三 85 92 78
李四 96 88 73
王五 91 84 79
语文 数学 英语
张三 170 184 156
李四 192 176 146
王五 182 168 158
语文 数学 英语
张三 90 97 83
李四 101 93 78
王五 96 89 84
张三 李四 王五
255 257 254
语文 数学 英语
90.7 88.0 76.7
注记
在数据分析实践中,矩阵主要用于数学计算(线性代数、机器学习算法);数据框才是日常数据分析的主力。矩阵了解即可,下一 Part 才是重头戏。
数据分析的主战场
现实中的数据长这样——每一列是不同类型的变量:
| 姓名(字符) | 年龄(数值) | 成绩(数值) | 是否通过(逻辑) |
|---|---|---|---|
| 张三 | 20 | 85 | TRUE |
| 李四 | 21 | 92 | TRUE |
| 王五 | 20 | 55 | FALSE |
矩阵要求所有元素同类型,放不下这种"混合"数据。数据框(data.frame) 专门解决这个问题:
姓名 年龄 成绩 是否通过
1 张三 20 85 TRUE
2 李四 21 92 TRUE
3 王五 20 55 FALSE
4 赵六 22 78 TRUE
str() 和 summary():快速了解数据拿到任何一份新数据,第一步先用这两个函数"扫描"一遍:
'data.frame': 4 obs. of 4 variables:
$ 姓名 : chr "张三" "李四" "王五" "赵六"
$ 年龄 : num 20 21 20 22
$ 成绩 : num 85 92 55 78
$ 是否通过: logi TRUE TRUE FALSE TRUE
有三种方式,用哪种都行,根据场合和习惯选择:
[1] 85 92 55 78
[1] "张三" "李四" "王五" "赵六"
[1] 20 21 20 22
[1] 85 92 55 78
[1] 85 92 55 78
注记
学生$成绩 和 学生[["成绩"]] 都返回向量;学生[, "成绩"] 通常也返回向量,但在某些情况下行为不同。推荐用 $ 或 [[]],更明确、更安全。
姓名 年龄 成绩 是否通过
1 张三 20 85 TRUE
姓名 年龄 成绩 是否通过
2 李四 21 92 TRUE
姓名 年龄 成绩 是否通过
1 张三 20 85 TRUE
2 李四 21 92 TRUE
提示
记住口诀:[ 行, 列 ]——逗号前是行,逗号后是列。留空表示"全部":学生[1, ] 是第1行所有列,学生[, 1] 是所有行第1列。
这是数据分析中最核心的操作之一——按条件筛选记录:
姓名 年龄 成绩 是否通过 等级
1 张三 20 85 TRUE 良好
2 李四 21 92 TRUE 优秀
3 王五 20 55 FALSE 不及格
4 赵六 22 78 TRUE 及格
提示
ifelse(条件, 值_如果TRUE, 值_如果FALSE) 是向量化的条件判断函数,类似 Excel 的 IF 函数,可以嵌套使用。
tibble [344 × 8] (S3: tbl_df/tbl/data.frame)
$ species : Factor w/ 3 levels "Adelie","Chinstrap",..: 1 1 1 1 1 1 1 1 1 1 ...
$ island : Factor w/ 3 levels "Biscoe","Dream",..: 3 3 3 3 3 3 3 3 3 3 ...
$ bill_length_mm : num [1:344] 39.1 39.5 40.3 NA 36.7 39.3 38.9 39.2 34.1 42 ...
$ bill_depth_mm : num [1:344] 18.7 17.4 18 NA 19.3 20.6 17.8 19.6 18.1 20.2 ...
$ flipper_length_mm: int [1:344] 181 186 195 NA 193 190 181 195 193 190 ...
$ body_mass_g : int [1:344] 3750 3800 3250 NA 3450 3650 3625 4675 3475 4250 ...
$ sex : Factor w/ 2 levels "female","male": 2 1 1 NA 1 2 1 2 NA NA ...
$ year : int [1:344] 2007 2007 2007 2007 2007 2007 2007 2007 2007 2007 ...
# A tibble: 5 × 3
species island body_mass_g
<fct> <fct> <int>
1 Gentoo Biscoe 6300
2 Gentoo Biscoe 6050
3 Gentoo Biscoe 6000
4 Gentoo Biscoe 6000
5 Gentoo Biscoe 5950
姓名 成绩
1 张三 85
2 李四 92
3 王五 78
4 赵六 96
灵活容器与分类变量
向量和矩阵要求所有元素类型相同;列表则没有这个限制——可以装任意类型、任意结构的东西:
List of 4
$ 姓名 : chr "张三"
$ 年龄 : num 20
$ 成绩 : num [1:3] 85 92 78
$ 联系方式:List of 2
..$ 电话: chr "138-xxxx-xxxx"
..$ 邮件: chr "zhangsan@email.com"
[1] "张三"
[1] 85 92 78
[1] 20
[1] 85 92 78
注记
[单括号] 和 [[双括号]] 的区别:
列表[1] 返回列表(把第1个元素装在一个列表里)列表[[1]] 返回第1个元素本身(取出来,不再是列表)日常取元素用 [[]] 或 $。
什么时候用列表?
列表在日常分析中不如数据框常见,但在以下场景非常重要:
t.test() 的返回值就是列表)[1] "htest"
[1] "statistic" "parameter" "p.value" "conf.int" "estimate"
[6] "null.value" "stderr" "alternative" "method" "data.name"
[1] 1.07e-10
[1] -4.87 -2.65
attr(,"conf.level")
[1] 0.95
因子(factor)是 R 专门为分类变量设计的数据类型:
[1] "character"
[1] "factor"
[1] 及格 优秀 良好 及格 优秀 不及格
Levels: 不及格 优秀 及格 良好
[1] "不及格" "优秀" "及格" "良好"
注记
注意 levels() 的排序:R 默认按字母顺序排列因子水平(对中文是按拼音)。"不及格"排在"良好"前面,这不符合逻辑——下一页讲如何自定义顺序。
[1] 及格 优秀 良好 及格 优秀 不及格
Levels: 不及格 < 及格 < 良好 < 优秀
[1] "不及格" "及格" "良好" "优秀"
[1] TRUE
[1] "factor"
[1] "Adelie" "Chinstrap" "Gentoo"
Adelie Chinstrap Gentoo
152 68 124
Biscoe Dream Torgersen
Adelie 44 56 52
Chinstrap 0 68 0
Gentoo 124 0 0
为什么不直接用字符?
因子 vs. 字符的核心区别:
一个很常见的 bug:忘记把分类变量转成因子,导致模型把它当成普通文字处理。
| 对象 | 维度 | 元素类型 | 典型用途 |
|---|---|---|---|
| 向量 | 1维 | 必须同类型 | 单列数据、中间计算 |
| 矩阵 | 2维 | 必须同类型 | 数学运算、机器学习算法 |
| 数据框 | 2维 | 每列可不同 | 日常数据分析(主力!) |
| 列表 | 任意 | 任意混合 | 函数返回值、JSON数据 |
企鹅数量: 344
变量数量: 8
缺失值总数: 19
# 4. 用列表汇总三种企鹅的统计结果
物种列表 <- levels(企鹅$species)
结果 <- lapply(物种列表, function(sp) {
子集 <- subset(企鹅, species == sp)
list(
物种 = sp,
数量 = nrow(子集),
平均体重 = round(mean(子集$body_mass_g), 0),
平均翼展 = round(mean(子集$flipper_length_mm), 1)
)
})
# 5. 输出结果
for (r in 结果) {
cat(r$物种, ":", r$数量, "只,",
"平均体重", r$平均体重, "g,",
"平均翼展", r$平均翼展, "mm\n")
}Adelie : 146 只, 平均体重 3706 g, 平均翼展 190 mm
Chinstrap : 68 只, 平均体重 3733 g, 平均翼展 196 mm
Gentoo : 119 只, 平均体重 5092 g, 平均翼展 217 mm
数据类型:数值(numeric)、字符(character)、逻辑(logical)是三种最常用的类型。用 class() 查看,用 as.*() 转换。"3" 和 3 是不同的——引号决定类型。
向量:R 的基础单元。用 c() 创建,[ ] 索引,逻辑索引是筛选数据的核心方式。向量化运算让你无需写循环就能处理一列数据。
矩阵:二维的向量,元素必须同类型。适合数学运算,apply() 可以按行或列汇总。
数据框:日常分析的主力。每列可以是不同类型,用 $ 取列,[行, 列] 取元素,subset() 筛选行,merge() 关联合并。
列表:什么都能装的灵活容器。函数返回的复杂结果通常是列表,用 $ 或 [[]] 取元素。
因子:分类变量的正确姿势。设定 levels 控制顺序,ordered = TRUE 允许大小比较。
data.frame() 创建一个包含5位同学信息的数据框(至少包含:姓名、性别、成绩三列),然后筛选出成绩前3名"良好" < "优秀" 是否为 TRUE第3讲:R语言中的控制语句及函数
第2讲:数据类型与数据对象
"数据结构选对了,问题就解决了一半。"
数据科学界流传的经验之谈
数据挖掘与R语言 | 第2讲:数据类型与数据对象