dplyr 创建一个具有其他列的复杂用户定义函数的新列

Posted

技术标签:

【中文标题】dplyr 创建一个具有其他列的复杂用户定义函数的新列【英文标题】:dplyr create a new column with a complex user defined function of other columns 【发布时间】:2021-02-28 09:37:57 【问题描述】:

我有一个大型数据框,其中包含 40 个问题的回答(代表以下 3 个问题),并且需要计算一个新列,该列是这 40 个回答的复杂函数。由于几乎不可能在mutate 中写出函数,因此我尝试创建一个可以在mutate 中使用的函数f

df <- data.frame(Sex = c(rep("F", 5), rep("M", 5)),
                 Q1  = sample(0:10, 10, replace=T),
                 Q2  = sample(0:10, 10, replace=T),
                 Q3  = sample(0:10, 10, replace=T)
)

f <- function(q1, q2, q3)
  y <- q1 + (q2^2) - (q3^3)
  return(y)
 

现在使用mutate 创建一个新列可以正常工作。:

df %>%
   mutate(newcol = f(Q1, Q2, Q3))

  Sex Q1 Q2 Q3 newcol
1    F 10  6  3     19
2    F  0  9  9   -648
3    F  8  1  2      1
4    F  0  4  7   -327
5    F  6  4  1     21
6    M  8  3  3    -10
7    M  2  2  0      6
8    M 10  0  3    -17
9    M  6  9  3     60
10   M  1  7  2     42

也一样

 df$newcol <- mapply(f, df$Q1, df$Q2, df$Q3)

但如果我在f 中包含一个简单的if atatement,如下所示

f <- function(q1, q2, q3)
  y <- q1 + (q2^2) - (q3^3)
  if(y<0)
    y <- -y
  
  return(y)
 

我的手上立刻就有了灾难:

df %>%
+   mutate(newcol = f(Q1, Q2, Q3))
   Sex Q1 Q2 Q3 newcol
1    F 10  6  3     19
2    F  0  9  9   -648
3    F  8  1  2      1
4    F  0  4  7   -327
5    F  6  4  1     21
6    M  8  3  3    -10
7    M  2  2  0      6
8    M 10  0  3    -17
9    M  6  9  3     60
10   M  1  7  2     42
Warning message:
Problem with `mutate()` input `newcol`.
i the condition has length > 1 and only the first element will be used
i Input `newcol` is `f(Q1, Q2, Q3)`. 

然而,

df$newcol <- mapply(f, df$Q1, df$Q2, df$Q3)
df
   Sex Q1 Q2 Q3 newcol
1    F 10  6  3     19
2    F  0  9  9    648
3    F  8  1  2      1
4    F  0  4  7    327
5    F  6  4  1     21
6    M  8  3  3     10
7    M  2  2  0      6
8    M 10  0  3     17
9    M  6  9  3     60
10   M  1  7  2     42

继续工作。 不幸的是,我的函数中有很多 if,并且有 40 个不同的参数要传递给函数,mapply 的输入变得巨大。如何使用预定义的向量将我的问题传递给 mapply,比如

questions <- c("df$Q1", "df$Q2", "df$Q3") 
df$newcol <- mapply(f, questions)

密切相关:我如何定义一个有 40 个参数的函数而不使它跑出页面?

我完全有可能找错了树,如果是这样,我应该如何解决我的问题?

在此先感谢

托马斯·飞利浦

附:这是真正的标准

if(!is.na(df[i, "Q1_Daily_Mean"]) & df[i, "Q1_Daily_Mean"] >= THRESHOLD_MDD_GAD)
  anxiety <- TRUE


if(!is.na(df[i, "Q2_Daily_Mean"]) & df[i, "Q2_Daily_Mean"] >= THRESHOLD_MDD_GAD)
  worry <- TRUE


if(anxiety && worry)
  anxiety_and_worry <- TRUE


if(!is.na(df[i, "Q3_Daily_Mean"]) & df[i, "Q3_Daily_Mean"] >= THRESHOLD_MDD_GAD )
  agitation <- TRUE


if(!is.na(df[i, "Q10_Daily_Mean"]) & df[i, "Q10_Daily_Mean"] >= THRESHOLD_MDD_GAD )
  anger <- TRUE


if(!is.na(df[i, "Q2_Weekly"]) & df[i, "Q2_Weekly"] >= THRESHOLD_MDD_GAD )
  physical_fatigue <- TRUE


if(!is.na(df[i, "Q5_Weekly"]) & df[i, "Q5_Weekly"] >= THRESHOLD_MDD_GAD )
  no_concentration <- TRUE


if(!is.na(df[i, "Q7_Weekly"]) & df[i, "Q7_Weekly"] >= THRESHOLD_MDD_GAD )
  disturbed_sleep <- TRUE


if(!is.na(df[i, "Q13_Weekly"]) & !is.na(df[i, "Q14_Weekly"]) &
   !is.na(df[i, "Q15_Weekly"]) & !is.na(df[i, "Q16_Weekly"]) & 
   !is.na(df[i, "Q17_Weekly"]) & 
   max( df[i, "Q13_Weekly"], df[i, "Q14_Weekly"],
        df[i, "Q15_Weekly"], df[i, "Q16_Weekly"],
        df[i, "Q17_Weekly"] ) >= THRESHOLD_MDD_GAD)
  max_function  <- TRUE


sum_of_symptoms_7 <- anxiety + worry + agitation + anger + 
                     physical_fatigue + no_concentration + disturbed_sleep

if (anxiety_and_worry && (sum_of_symptoms_7 >= CRITERIA_NEEDED_GAD) && max_function)
  # Generalized Anxiety Disorder
  df[i, GAD_DESCRIPTPR_EML] <- TRUE

【问题讨论】:

y &lt;- abs(y) 应该修复它或使用ifelse(y &lt; 0, -y, y) 如果主要关心的是参数的数量(40 确实过多!)考虑tidying 你的数据:有两列,一列用于问题编号,一列用于响应,而不是每个问题一列.或者,您可以将条件作为命名列表传递,列表的名称标识新列名,列表的值给出表达式以评估以填充新列 【参考方案1】:

基本上带有if 语句的函数不是向量化的。你有两个选择。

    使函数矢量化(使用ifelse 或任何其他方式)并像之前一样继续使用mutate
library(dplyr)
library(purrr)

df %>% mutate(newcol = f(Q1, Q2, Q3))
    如果条件太复杂并且您无法对函数进行矢量化,请使用rowwisepmap,它们一次只对一行进行操作。这与您的 mapply 尝试类似。
df %>% mutate(newcol = pmap_dbl(list(Q1, Q2, Q3), ~f(..1, ..2, ..3)))

【讨论】:

【参考方案2】:

您收到“条件长度 > 1 且仅使用第一个元素”警告的原因是 if 与向量结合使用(例如,请参阅 here)。 dpylrmutate 将值的“整个”向量传递给被调用的函数(即不是逐个元素的(行)元素)。这就是 if 声明被混淆的地方。

这解决了你的问题:

df <- data.frame(Sex = c(rep("F", 5), rep("M", 5)),
                 Q1  = sample(0:10, 10, replace=T),
                 Q2  = sample(0:10, 10, replace=T),
                 Q3  = sample(0:10, 10, replace=T)
)

f <- function(q1, q2, q3)
  y <- q1 + (q2^2) - (q3^3)
  y <- ifelse(y<0, -y, y)
  return(y)
 

df %>%
  mutate(newcol = f(Q1, Q2, Q3))

返回:

   Sex Q1 Q2 Q3 newcol
1    F  8  6  3     17
2    F  6  0  0      6
3    F  4  5  7    314
4    F  9  5  7    309
5    F  3  5  9    701
6    M  1 10  5     24
7    M 10  5  4     29
8    M  4  0  3     23
9    M  8  4  7    319
10   M  3  6  3     12

【讨论】:

在这个非常简单的情况下它确实可以 - 谢谢。但一般情况有许多不同的条件以及它们的布尔组合。我一般如何解决这个问题? 您能否使用适合您遇到的问题的 MRE 来扩展您的问题?【参考方案3】:

扩展我上面的评论:

f <- function(data, conditions) 
  columnNames <- names(conditions)
  for (colName in columnNames) 
    qName <- enquo(colName)
    data <- data %>% mutate(!!qName := eval(conditions[[colName]]))
  
  data


df %>% f(list(bigQ1=expression(Q1 > 7), smallQ2=expression(Q2 < 2)))

给出,例如,

   Sex Q1 Q2 Q3 bigQ1 smallQ2
1    F  2  9  9 FALSE   FALSE
2    F  2 10  6 FALSE   FALSE
3    F  9  4  9  TRUE   FALSE
4    F  1  2  8 FALSE   FALSE
5    F  5 10  2 FALSE   FALSE
6    M 10  8  3  TRUE   FALSE
7    M  4  8  0 FALSE   FALSE
8    M  3  8 10 FALSE   FALSE
9    M  5  2  6 FALSE   FALSE
10   M  8  7  4  TRUE   FALSE

将 df 作为函数的第一个参数传递允许管道。

【讨论】:

以上是关于dplyr 创建一个具有其他列的复杂用户定义函数的新列的主要内容,如果未能解决你的问题,请参考以下文章

在具有包含数据帧的列表列的小标题中,如何使用自定义函数包装 mutate(foo = map2(...))?

R中的dplyr mutate - 添加列作为列的连接

具有多个订单列的 dplyr row_number

dplyr 基于具有不同后缀的其他列进行变异

r/dplyr:在 UDF 中使用动态命名的变量

使用dplyr汇总多个列的不同操作