避免在函数中创建重复的变量名

avoid creating duplicate variable names in function

我有一个 dataframe 和一个 function 创建一个新变量,将其添加到数据框,然后 assign 将数据框返回到全局环境。问题是,如果我重新运行函数,它会创建变量的副本。

library(tidyverse)
library(rms)
set.seed(10)
ds <- data.frame(
  ftime = rexp(200),
  fstatus = sample(0:1,200, replace = TRUE),
  x1 = runif(200),
  x2 = runif(200),
  x3 = factor(sample(LETTERS[1:3], size = 200, replace = TRUE)))
ds
#model
s <- Surv(ds$ftime, ds$fstatus == 1) 
fit <- cph(s ~ x1 + x2 + x3, data = ds, surv = TRUE, x = TRUE, y = TRUE)

#function to add prediction to dataset
pred_fun <- function(time_to_sur, model) {
  
  pred_data <- ds[, c("x1", "x2", "x3")] %>% 
    mutate(ftime = time_to_sur,
           fstatus = 1) %>%   
    as.data.frame()
  
  ds$pred_var_tmp <-
    rms::survest(model, times = time_to_sur,
                 newdata = pred_data,
                 se.fit = FALSE, what = "survival")$surv
  
  #rename variable
  pred_var <- paste0("pred_prob_", as.character(time_to_sur), "_rms")
  names(ds)[names(ds) == "pred_var_tmp"] <- pred_var
  
  #assign dataset back to global environment
  assign("ds", ds, env = .GlobalEnv) 
}

函数正常运行:

pred_fun(time_to_sur = 0.2, fit)
names(ds)
# [1] "ftime"             "fstatus"           "x1"               
# [4] "x2"                "x3"                "pred_prob_0.2_rms"

但是如果我再次运行它,它会创建变量的副本

pred_fun(time_to_sur = 0.2, fit)
names(ds)
# [1] "ftime"             "fstatus"           "x1"               
# [4] "x2"                "x3"                "pred_prob_0.2_rms"
# [7] "pred_prob_0.2_rms"

这是意料之中的,因为该函数首先创建一个具有不同名称的新变量,然后分配名称。我认为以下内容可能会在该函数中起作用,但它不起作用:

ds$eval(substitute(paste0("pred_prob_", as.character(tt), "_rms"))) <-
    rms::survest(model, times = time_to_sur,
                 newdata = pred_data,
                 se.fit = FALSE, what = "survival")$surv

我该如何解决这个问题以及在这种情况下的最佳做法是什么?

谢谢

1) Base R 这将覆盖现有的列(如果它已经存在)。这将用 11:16.

覆盖最初为 c(1, 2, 3, 4, 5, 7) 的时间
newName <- "Time" # duplicated column name
values <- 11:16
replace(BOD, newName, values)
##   Time demand
## 1   11    8.3
## 2   12   10.3
## ...

如果新列名不存在,则会创建一个新列。

newName <- "Time2" # new column name, not duplicate
values <- 11:16
replace(BOD, newName, values)
##   Time demand Time2
## 1    1    8.3    11
## 2    2   10.3    12
## ...

2) dplyr 如果你想为此使用 dplyr,那么:

library(dplyr)

newName <- "Time" # duplicated column name
values <- 11:16
mutate(BOD, {{newName}} := values)
##   Time demand
## 1   11    8.3
## 2   12   10.3
## ...

newName <- "Time2" # new column name, not duplicate
values <- 11:16
mutate(BOD, {{newName}} := values)
##   Time demand Time2
## 1    1    8.3    11
## 2    2   10.3    12
## ...

其他

R 的功能性质。 R 是一种函数式语言,通常编写函数以通过参数传递输入并将输出作为 return 值传递。这里 x 是输入,y 是输出。

# ok
f <- function(x) x + 1
y <- f(3)
y
## [1] 4

最好不要这样做:

#  not good
f <- function() assign("y",  x + 1, .GlobalEnv)
x <- 3
f()
y
## [1] 4

替换函数。尽管不经常使用,但 R 确实支持这样的替换函数和语法(参见 https://cran.r-project.org/doc/manuals/r-release/R-lang.html#Subset-assignment)。例如,names<- 是一个常用的替换函数。我们首先创建 BOD 的副本,这样我们就不会覆盖它。这会保留输入,使其更易于调试。

`f<-` <- function(x, value) replace(x, "Time", value)
BOD2 <- BOD
f(BOD2) <- 11:16
BOD2
##   Time demand
## 1   11    8.3
## 2   12   10.3
## ...

感谢@G。 Grothendieck 和@Limey,尽管我确实收到了 warning 消息,但以下简化仍然有效(pred_fun_final)。

#original function in OP
pred_fun_original <- function(time_to_sur, model) {
  
  pred_data <- ds[, c("x1", "x2", "x3")] %>% 
    mutate(ftime = time_to_sur,
           fstatus = 1) %>%   
    as.data.frame()
  
  ds$pred_var_tmp <-
    rms::survest(model, times = time_to_sur,
                 newdata = pred_data,
                 se.fit = FALSE, what = "survival")$surv
  
  #rename variable
  pred_var <- paste0("pred_prob_", as.character(time_to_sur), "_rms")
  names(ds)[names(ds) == "pred_var_tmp"] <- pred_var

  assign("ds", ds, env = .GlobalEnv) 
}
pred_fun_original(time_to_sur = 0.2, fit)

#save created variable
test1 <- ds$pred_prob_0.2_rms

#remove pred_prob_0.2_rms
ds <- ds %>% 
  select(-pred_prob_0.2_rms)

warning 的新功能:

#fixed function
pred_fun_final <- function(data, time_to_sur, model) {
  
  newName <- paste0("pred_prob_", as.character(time_to_sur), "_rms")
  pred_data <- data[, c("x1", "x2", "x3")] %>% 
    mutate(ftime = time_to_sur,
           fstatus = 1) %>%   
    as.data.frame()
  
  data <- data %>% 
    mutate({{newName}} := rms::survest(model, times = time_to_sur,
                                       newdata = pred_data,
                                       se.fit = FALSE, what = "survival")$surv)
  
  data
}
ds <- pred_fun_final(ds, time_to_sur = 0.2, fit)
# Warning message:
# Problem with `mutate()` column `pred_prob_0.2_rms`.
# i `pred_prob_0.2_rms = ...$NULL`.

#save variable
test2 <- ds$pred_prob_0.2_rms

这两个变量并不相同,但这是因为一个已命名而另一个未命名(as.numeric() 将解决此问题)。不过,它并没有解释 warning 消息。

identical(test1, test2)
#FALSE
str(test1)
#  num [1:200] 0.906 0.9 0.884 0.884 0.886 ...
str(test2)
#  Named num [1:200] 0.906 0.9 0.884 0.884 0.886 ...
#  - attr(*, "names")= chr [1:200] "1" "2" "3" "4" ...