按组 r 创建具有最小(第一)因素水平的列

Create column with minimum (first) level of factors by group r

我有一个混合了患者和预约信息的数据框。每个患者可能已经参加了多次预约。每次预约时都会收集一些患者信息,结果有些患者信息相互矛盾,有些则缺失。

我想根据其他预约中记录的数据为未记录的预约填写缺失的患者信息。并且(这就是我卡住的地方)我想采用 'minimum' 级别(级别顺序中最早的级别)记录的因素,这些因素对同一患者具有冲突的信息。 (在下面的示例中,因子水平按字母顺序排列,但情况并非总是如此)。

这与 this question 类似,但我使用的是因子而不是字符,并且我有多个因子需要最小值,因此无法按行过滤。

例如。我有

df.have <- data.frame(
  grp_id = rep(1:3, each = 2),
  grpvar1 = factor(c("B", "A", "B", "C", NA, "A")),
  grpvar2 = factor(c("a", "b", "c", NA, NA, "x")),
  appt_id = 1:6)

我要

grp_id grpvar1   grpvar2 appt_id
     1       A         a       1
     1       A         a       2
     2       B         c       3
     2       B         c       4
     3       A         x       5
     3       A         x       6

或至少

grp_id grpvar1 grpvar1.1
     1       A         a
     2       B         c
     3       A         x

我们可以试试summarise_each。由于我们需要 'grpvar' 个变量中的第一个 level,因此我们需要确保在执行此操作之前删除未使用的级别(使用 droplevels)。

df.have %>% 
     group_by(grp_id) %>% 
     summarise_each(funs(first(levels(droplevels(.)))), grpvar1:grpvar2)
#   grp_id grpvar1 grpvar2
#   <int>   <chr>   <chr>
#1      1       A       a
#2      2       B       c
#3      3       A       x

或者如果我们使用mutate_each,我们得到第一个输出

df.have %>% 
    group_by(grp_id) %>%
    mutate_each(funs(levels(droplevels(.))[1]), grpvar1:grpvar2)    
#  grp_id grpvar1 grpvar2 appt_id
#   <int>   <chr>   <chr>   <int>
#1      1       A       a       1
#2      1       A       a       2
#3      2       B       c       3
#4      2       B       c       4
#5      3       A       x       5
#6      3       A       x       6

如果我们需要 'grpvar' 秒的 factor 列输出。

 df.have %>% 
    group_by(grp_id) %>%
    mutate_each(funs(factor(levels(droplevels(.))[1])), grpvar1:grpvar2)    
 #   grp_id grpvar1 grpvar2 appt_id
 #    <int>  <fctr>  <fctr>   <int>
 #1      1       A       a       1
 #2      1       A       a       2
 #3      2       B       c       3
 #4      2       B       c       4
 #5      3       A       x       5
 #6      3       A       x       6

或使用data.table

library(data.table)
setDT(df.have)[, lapply(.SD, function(x) levels(droplevels(x))[1]) , 
                grp_id, .SDcols = grpvar1:grpvar2]

在为具有更大数据集的不同项目重新访问时,我意识到将因子转换为数字、取最小值,然后重新转换回因子的效率要高得多(尽管需要更多的击键)。

library(data.table)
library(dplyr)

set.seed(1)
n <- 100L

dat <- data.table(
  grp_id = rep(1:n/10, each = 10),
  grpvar1 = factor(sample(c(LETTERS, NA), n, replace = TRUE), levels = LETTERS),
  grpvar2 = factor(sample(c(letters, NA), n, replace = TRUE), levels = letters),
  appt_id = 1:n)

cols <- c("grpvar1","grpvar2")

dplyr_fct <- function(data, cols) {
  data %>% 
    group_by(grp_id) %>%
    mutate_each(funs(factor(levels(droplevels(.))[1])), one_of(cols))
}

dt_fct <- function(data, cols) {
  data[, lapply(.SD, function(x) levels(droplevels(x))[1]), grp_id, .SDcols = cols]}

dt_nmbr <- function(data, cols) {
  dat_out <- copy(data)
  v_lvl = lapply(dat_out[, .SD, .SDcols = cols], levels)
  # Convert factors to numeric
  for(col in cols) set(dat_out, j = col, value = as.numeric(dat_out[[col]]))
  # Select highest value
  dat_out[, (cols):= lapply(.SD, min, na.rm = TRUE), by = grp_id, .SDcols = cols]
  # Convert back to factor
  for(col in cols) set(dat_out, j = col, 
                       value = factor(dat_out[[col]], levels = 1:length(v_lvl[[col]]), labels = v_lvl[[col]]))
  assign("dat_out", dat_out, envir = .GlobalEnv)
}

mbm <- microbenchmark::microbenchmark(
  mbm_dplyr_fct = dplyr_fct(dat, cols),
  mbm_dt_fct    = dt_fct(dat, cols),
  mbm_dt_nmbr   = dt_nmbr(dat, cols)
)

mbm

Unit: milliseconds
          expr       min        lq      mean    median        uq       max neval cld
 mbm_dplyr_fct 84.487484 85.829834 90.988740 87.015878 91.159178 120.22171   100   c
    mbm_dt_fct 56.768529 58.007094 60.988083 58.831850 60.269427  87.11799   100  b 
   mbm_dt_nmbr  4.181538  4.406392  4.540248  4.557948  4.619757   6.04197   100 a