R 语言入门实战|第五章 对象改值:从 “调整扑克牌点数” 学透数据修改

发布于:2025-09-15 ⋅ 阅读:(17) ⋅ 点赞:(0)

引言:为什么 “对象改值” 是数据处理的核心?

在数据分析中,“拿到数据→修改数据→分析数据” 是常见流程 —— 比如根据不同游戏规则调整扑克牌点数(War 游戏中 A(14) 比 K(13) 大,Hearts 游戏中黑桃 Q 分值特殊)、清洗数据中的错误值、处理缺失信息等。《R 语言入门与实践》第五章 “对象改值”,正是围绕这些实际需求展开,通过 “扑克牌” 这一直观案例,教你如何精准、高效地修改 R 对象(尤其是数据框)中的值,为后续数据清洗和分析打下基础。

本章的核心目标是:掌握 “就地改值” 语法、用逻辑值取子集实现条件改值、处理缺失值(NA),所有知识点都围绕 “扑克牌数据框deck” 展开,学完就能直接应用到真实数据处理场景。

一、就地改值:用索引精准修改数据

“就地改值” 指直接在原对象中修改指定位置的值,无需创建新对象,核心语法是 **“索引 + 赋值符<-”**。这是 R 中最基础也最常用的数据修改方式,尤其适合已知目标位置的场景。

1.1 基础语法:定位→赋值

语法格式:对象[行索引, 列索引] <- 新值

  • 行 / 列索引:可通过正整数、名称、逻辑值指定(第四章学过的索引方式都适用);
  • 新值:单个值或与索引长度匹配的向量(R 会自动循环短向量)。
# 使用<-赋值符号更改这些值,R会在原始对象内部对这些值修改
vec <- c(0,0,0,0,0,0,0)

vec[1] <- 1000
vec
# [1] 1000    0    0    0    0    0    0
#再次对同一个索引处赋值,将覆盖原值
vec[c(1:3)] <- c(1,1,1)
vec
# [1] 1 1 1 0 0 0 0

vec[c(1,3,5)] <- c(1,1,1)
vec
# [1] 1 1 1 1 1 1 0
vec[4:6] <- vec[4:6] + 1
vec
# [1] 1 1 1 2 2 2 0

#也可以创建原先对象中不存在的新值,R会自动延伸长度以适应这个新值
vec[8] <- 8
vec
# [1] 1 1 1 2 2 2 0 8

1.2 实战:修改 War 游戏的 A 点数(从 1→14)

在 War 游戏中,A(ace)是最大牌,点数需从默认的 1 改为 14。观察deck数据框,A 位于每 13 张牌的最后一行(行号 13、26、39、52),代码如下:

# 1. 复制原始deck(避免修改原数据)
deck_war <- deck

# 2. 定位A的位置,修改点数为14
# 行索引:13、26、39、52(4种花色的A);列索引:value列
deck_war$value[c(13, 26, 39, 52)] <- 14

# 3. 验证结果(查看黑桃A的点数)
deck_war[13, ]  # 输出:face=ace, suit=spades, value=14

1.3 批量改值与新增列

  • 批量改值:若需修改连续行,用:生成索引,如deck_war$value[1:12] <- 10
  • 新增列:直接用$赋值新列名,如deck_war$new_col <- 1:52(新增一列 1~52 的序号);
  • 删除列:将列赋值为NULL,如deck_war$new_col <- NULL

二、逻辑值取子集:按条件改值(数据分析核心技能)

当不知道目标数据的具体位置时(比如洗牌后 A 的位置变了),就需要用 “逻辑值取子集”—— 通过逻辑测试生成TRUE/FALSE向量,自动定位目标数据。这是 R 中最灵活、最高效的数据筛选与修改方式

2.1 第一步:逻辑测试(生成筛选条件)

逻辑测试通过 “逻辑运算符” 实现,返回长度与原向量一致的逻辑向量(TRUE= 符合条件,FALSE= 不符合)。常用运算符如下:

运算符 作用 示例(基于 deck 数据框) 结果
== 等于 deck$face == "ace" 所有 A 返回 TRUE
!= 不等于 deck$suit != "spades" 非黑桃牌返回 TRUE
>/< 大于 / 小于 deck$value > 10 人头牌(J/Q/K)返回 TRUE
>=/<= 大于等于 / 小于等于 deck$value <= 5 2~5 点牌返回 TRUE
%in% 是否在指定集合中 deck$face %in% c("king", "queen") K/Q 返回 TRUE

示例:筛选洗牌后所有 A 的位置

# 1. 洗牌(打乱deck行顺序)
deck_shuffled <- shuffle(deck)  # shuffle()是第四章写的洗牌函数

# 2. 逻辑测试:哪些行是A
is_ace <- deck_shuffled$face == "ace"  # 返回长度52的逻辑向量

# 3. 取子集:提取所有A
deck_shuffled[is_ace, ]  # 无论A在哪个位置,都能精准提取

2.2 第二步:布尔运算符(组合多条件)

当需要多个筛选条件时,用 “布尔运算符” 组合逻辑测试,常用运算符及函数如下:

运算符 / 函数 作用 核心参数 / 语法 示例(筛选黑桃 Q)
& 逻辑与(所有条件为真) 条件 1 & 条件 2 deck$face == "queen" & deck$suit == "spades"
` ` 逻辑或(至少一个为真) 条件 1 条件 2 `deck$value == 10 deck$face == "ace"`(10 点或 A)
! 逻辑非(反转结果) ! 条件 !is_ace(非 A 牌)
any() 至少一个条件为真? any(条件1, 条件2, ...) any(deck$value == 14)(是否有 14 点牌)
all() 所有条件为真? all(条件1, 条件2, ...) all(deck$value > 0)(所有牌点数 > 0?)
新函数详解:any()all()

这两个函数是逻辑判断的核心工具,尤其适合验证数据完整性:

函数 核心参数 作用说明 示例与结果
any() ...:多个逻辑条件 检测是否至少一个条件为TRUE,返回单个逻辑值 any(deck$face == "ace") → TRUE
all() ...:多个逻辑条件 检测是否所有条件为TRUE,返回单个逻辑值 all(deck$value == 1) → FALSE
实战:修改 Hearts 游戏点数

Hearts 游戏规则:红桃牌 1 点,黑桃 Q(queen of spades)13 点,其他 0 点。用逻辑值取子集实现:

# 1. 复制原始deck
deck_hearts <- deck

# 2. 初始化所有点数为0
deck_hearts$value <- 0

# 3. 条件1:红桃牌改1点(用%in%匹配花色)
is_hearts <- deck_hearts$suit %in% "hearts"
deck_hearts$value[is_hearts] <- 1

# 4. 条件2:黑桃Q改13点(组合两个条件)
is_queen_spades <- deck_hearts$face == "queen" & deck_hearts$suit == "spades"
deck_hearts$value[is_queen_spades] <- 13

# 5. 验证结果
deck_hearts[is_hearts | is_queen_spades, ]  # 查看红桃和黑桃Q

三、缺失信息处理:NA 的正确用法

在数据中,“缺失值”(如 Blackjack 游戏中 A 的点数不确定,可能 1 也可能 11)用NA表示。R 对NA有特殊处理规则,需掌握is.na()函数和na.rm参数。

3.1 NA 的特性:“传染性”

NA 与任何值运算结果仍为 NA,避免因缺失值导致错误计算:

1 + NA  # 输出:NA
mean(c(1, 2, NA))  # 输出:NA(未处理NA)

3.2 新函数:is.na()—— 检测缺失值

is.na()是识别 NA 的核心函数,返回与输入对象长度一致的逻辑向量(TRUE=NA,FALSE= 非 NA)。

函数 核心参数 作用说明 示例
is.na() x:待检测对象 检测对象中哪些元素是 NA,返回逻辑向量 is.na(deck$value) → 识别点数为 NA 的牌

示例:定位 Blackjack 游戏中的 A(设为 NA)

# 1. 复制原始deck
deck_blackjack <- deck

# 2. 人头牌(K/Q/J)改10点(用%in%匹配)
is_facecard <- deck_blackjack$face %in% c("king", "queen", "jack")
deck_blackjack$value[is_facecard] <- 10

# 3. A的点数设为NA(不确定是1还是11)
is_ace <- deck_blackjack$face == "ace"
deck_blackjack$value[is_ace] <- NA

# 4. 检测NA位置(A的位置)
which(is.na(deck_blackjack$value))  # 输出A的行号

3.3 关键参数:na.rm—— 移除 NA 再计算

很多统计函数(如mean()sum()max())都有na.rm参数,控制是否忽略 NA 后计算,默认na.rm = FALSE(保留 NA,结果为 NA)。

函数 na.rm参数作用 示例(计算所有非 NA 点数的均值) 结果
mean() TRUE= 移除 NA;FALSE= 保留 mean(deck_blackjack$value, na.rm = TRUE) 约 7.5(人头牌 10 点后的均值)
sum() 同上 sum(deck_blackjack$value, na.rm = TRUE) 所有非 A 牌的点数总和

示例:计算 Blackjack 牌堆的平均点数(忽略 A 的 NA)

mean(deck_blackjack$value)  # 输出:NA(未移除NA)
mean(deck_blackjack$value, na.rm = TRUE)  # 输出:~7.5(正确计算)

四、本章新函数 / 参数汇总(表格版)

为方便查阅,将本章核心新函数和参数整理如下:

类型 函数 / 参数 核心参数 作用说明 实战示例
逻辑判断 any(...) ...:多个逻辑条件 至少一个条件为真?返回单个逻辑值 any(deck$suit == "hearts") → TRUE
逻辑判断 all(...) ...:多个逻辑条件 所有条件为真?返回单个逻辑值 all(deck$value > 0) → TRUE
缺失值检测 is.na(x) x:待检测对象 检测 x 中 NA 的位置,返回逻辑向量 is.na(deck_blackjack$value)
统计函数参数 na.rm TRUE/FALSE 统计计算时是否移除 NA(默认 FALSE) sum(deck$value, na.rm = TRUE)

五、综合实战:打造多规则扑克牌系统

结合本章所有知识点,实现一个能切换三种游戏规则的扑克牌函数:

# 定义函数:根据游戏规则修改扑克牌点数
adjust_poker <- function(game = c("War", "Hearts", "Blackjack")) {
  # 输入:game-游戏名称;输出:修改后的deck数据框
  deck_adjust <- deck  # 复制原始deck
  
  if (game == "War") {
    # War规则:A=14,其他不变
    is_ace <- deck_adjust$face == "ace"
    deck_adjust$value[is_ace] <- 14
  } else if (game == "Hearts") {
    # Hearts规则:红桃=1,黑桃Q=13,其他=0
    deck_adjust$value <- 0
    is_hearts <- deck_adjust$suit == "hearts"
    is_queen_spades <- deck_adjust$face == "queen" & deck_adjust$suit == "spades"
    deck_adjust$value[is_hearts] <- 1
    deck_adjust$value[is_queen_spades] <- 13
  } else if (game == "Blackjack") {
    # Blackjack规则:人头牌=10,A=NA
    is_facecard <- deck_adjust$face %in% c("king", "queen", "jack")
    is_ace <- deck_adjust$face == "ace"
    deck_adjust$value[is_facecard] <- 10
    deck_adjust$value[is_ace] <- NA
  }
  
  return(deck_adjust)
}

# 测试:生成Hearts规则的牌堆
deck_hearts_final <- adjust_poker("Hearts")
head(deck_hearts_final[deck_hearts_final$value != 0, ])  # 查看有分值的牌

六、第五章核心小结

  1. 就地改值是基础:用 “索引 + 赋值” 精准修改数据,适合已知位置的场景,语法对象[行, 列] <- 新值
  2. 逻辑值取子集是核心:通过逻辑测试(==%in%)和布尔运算符(&|)组合条件,实现 “按规则筛选 + 改值”,是数据分析中最常用的技能;
  3. 缺失值处理要注意:用is.na()检测 NA,用na.rm参数在统计计算时忽略 NA,避免 “NA 传染性” 导致错误;
  4. 实战是关键:结合扑克牌案例理解改值逻辑,后续处理真实数据(如清洗错误值、调整指标口径)时可直接复用本章思路。

下一章(第六章)将学习 “R 的环境系统”,解决 “发牌后如何让牌堆记住已发的牌” 这类状态管理问题,进一步提升代码的实用性!