如何在R的reduce()中以引用传递dataframe以保留状态更新?
问题描述
我拥有示例设备数据machine_data,定义了包含do_nothing、repair、replace的设备维护动作集合,以及设备状态退化模型degrade_machine和单周期动作执行函数perform_action。通过purrr::reduce()在循环中处理动作集时,发现每次递归调用都使用原始machine_data值:例如动作集c('repair', 'do_nothing', 'repair')中,设备2初始Condition约为60.299,repair后更新为95并退化至85.5,但后续do_nothing动作仍使用原始值60.299退化到54.27,而非预期的85.5退化至76.95。想知道能否在reduce()中以引用方式传递dataframe,使每次递归使用更新后的状态?
问题根源
R语言中的dataframe默认是值传递机制,reduce()每次迭代时会传递数据的副本而非引用,导致前一次迭代的更新无法被后续迭代继承,所以才会出现始终使用原始数据的问题。
解决方案
1. 确保动作执行函数返回更新后的dataframe
R中大部分对象是不可变的,不能原地修改后直接生效,必须让perform_action函数返回修改并退化后的新dataframe。示例实现:
perform_action <- function(acc_df, action) { # 基于累积状态执行动作 updated_df <- switch(action, repair = { acc_df$Condition[acc_df$MachineID == 2] <- 95 acc_df }, do_nothing = acc_df, replace = { acc_df$Condition[acc_df$MachineID == 2] <- 100 acc_df }, acc_df # 默认 fallback ) # 执行状态退化并返回新数据 degrade_machine(updated_df) }
2. 正确调用purrr::reduce()
reduce()的核心是维护一个累积值(即上一次更新后的设备状态),每次迭代将累积值与当前动作传入处理函数。完整示例代码:
library(purrr) # 初始化设备数据 machine_data <- data.frame( MachineID = c(1, 2), Condition = c(75, 60.299) ) # 状态退化函数:模拟设备性能衰减 degrade_machine <- function(df) { df$Condition <- df$Condition * 0.9 df } # 动作执行函数(如上述定义) perform_action <- function(acc_df, action) { updated_df <- switch(action, repair = { acc_df$Condition[acc_df$MachineID == 2] <- 95 acc_df }, do_nothing = acc_df, replace = { acc_df$Condition[acc_df$MachineID == 2] <- 100 acc_df }, acc_df ) degrade_machine(updated_df) } # 处理动作序列 action_set <- c('repair', 'do_nothing', 'repair') final_state <- reduce(action_set, perform_action, .init = machine_data) # 查看最终状态 print(final_state)
运行后会得到正确的连续更新结果:设备2的状态会依次经历60.299 → 95 → 85.5 → 76.95 → 95 → 85.5,完全符合预期。
3. 可选:用data.table实现引用传递(慎用)
如果一定要用引用传递来避免数据副本,可以使用data.table包(支持原地修改)。但这种方式会修改原始数据,容易引发副作用,仅适用于特殊场景:
library(data.table) library(purrr) # 转成data.table machine_data <- data.table( MachineID = c(1, 2), Condition = c(75, 60.299) ) # 原地修改的退化函数 degrade_machine <- function(dt) { dt[, Condition := Condition * 0.9] } # 原地修改的动作执行函数 perform_action <- function(dt, action) { if (action == "repair") { dt[MachineID == 2, Condition := 95] } else if (action == "replace") { dt[MachineID == 2, Condition := 100] } degrade_machine(dt) } # 处理动作序列 action_set <- c('repair', 'do_nothing', 'repair') reduce(action_set, perform_action, .init = machine_data)
内容的提问来源于stack exchange,提问作者coolhand

