首页 文章

如何将多个列传递给dplyr :: summarize中的函数

提问于
浏览
4

我试图将符合条件的data.frame中的所有列传递给dplyr的汇总函数中的函数,如下所示:

df %>% group_by(Version, Type) %>%
  summarize(mcll(TrueClass, starts_with("pred")))

Error: argument is of length zero

有没有办法做到这一点?一个工作示例如下:

构建样本预测的模拟数据框架 . 这些被解释为分类算法的输出 .

library(dplyr)
nrow <- 40
ncol <- 4
set.seed(567879)

getProbs <- function(i) {
  p <- runif(i)
  return(p / sum(p))
}
df <- data.frame(matrix(NA, nrow, ncol))
for (i in seq(nrow)) df[i, ] <- getProbs(ncol)
names(df) <- paste0("pred.", seq(ncol))

添加一个指示真实类的列

df$TrueClass <- factor(ceiling(runif(nrow, min = 0, max = ncol)))

为子设置添加分类列

df$Type <- c(rep("a", nrow / 2), rep("b", nrow / 2))
df$Version <-  rep(1:4, times = nrow / 4)

现在我想使用以下函数计算这些预测的多类LogLoss:

mcll <- function (act, pred) 
{
  if (class(act) != "factor") {
    stop("act must be a factor")
  }
  pred[pred == 0] <- 1e-15
  pred[pred == 1] <- 1 - 1e-15
  dummies <- model.matrix(~act - 1)
  if (nrow(dummies) != nrow(pred)) {
    return(0)
  }
  return(-1 * (sum(dummies * log(pred)))/length(act))
}

这可以通过整个数据集轻松完成

act <- df$TrueClass
pred <- df %>% select(starts_with("pred"))
mcll(act, pred)

但我想使用dplyr group_by来计算每个数据子集的mcll

df %>% group_by(Version, Type) %>%
  summarize(mcll(TrueClass, starts_with("pred")))

理想情况下,我可以在不更改 mcll() 函数的情况下执行此操作,但如果它简化了其他代码,我愿意这样做 .

谢谢!

编辑:请注意,mcll的输入是真值的向量和概率矩阵,每个“pred”列有一列 . 对于每个数据子集,mcll应返回一个标量 . 我可以通过下面的代码得到我想要的东西,但我希望在dplyr的上下文中有所作为 .

mcll_df <- data.frame(matrix(ncol = 3, nrow = 8))
names(mcll_df) <- c("Type", "Version", "mcll")
count = 1
for (ver in unique(df$Version)) {
  for (type in unique(df$Type)) {
    subdat <- df %>% filter(Type == type & Version == ver)
    val <- mcll(subdat$TrueClass, subdat %>% select(starts_with("pred")))
    mcll_df[count, ] <- c(Type = type, Version = ver, mcll = val)
    count = count + 1
  }
}
head(mcll_df)
  Type Version             mcll
1    a       1 1.42972507510096
2    b       1 1.97189000832723
3    a       2 1.97988830406062
4    b       2 1.21387875938737
5    a       3 1.30629638026735
6    b       3 1.48799237895462

2 回答

  • 0

    这很容易使用 data.table

    library(data.table)
    
    setDT(df)[, mcll(TrueClass, .SD), by = .(Version, Type), .SDcols = grep("^pred", names(df))] 
    #   Version Type       V1
    #1:       1    a 1.429725
    #2:       2    a 1.979888
    #3:       3    a 1.306296
    #4:       4    a 1.668330
    #5:       1    b 1.971890
    #6:       2    b 1.213879
    #7:       3    b 1.487992
    #8:       4    b 1.171286
    
  • 2

    我不得不稍微更改 mcll 功能,但之后就可以了 . 第二个 if 语句出现问题 . 您告诉函数获取 nrow(pred) ,但如果您要汇总多个列,则实际上每次只提供一个向量(因为每个列都会单独分析) . 另外,我将输入的参数的顺序切换到函数中 .

    mcll <- function (pred, act) 
    {
      if (class(act) != "factor") {
        stop("act must be a factor")
      }
       pred[pred == 0] <- 1e-15
       pred[pred == 1] <- 1 - 1e-15
    
      dummies <- model.matrix(~act - 1)
      if (nrow(dummies) != length(pred)) { # the main change is here
        return(0)
      }
      return(-1 * (sum(dummies * log(pred)))/length(act))
    }
    

    从那里我们可以使用 summarise_each 函数 .

    df %>% group_by(Version,Type) %>% summarise_each(funs(mcll(., TrueClass)), matches("pred"))
    
      Version  Type   pred.1   pred.2   pred.3   pred.4
        (int) (chr)    (dbl)    (dbl)    (dbl)    (dbl)
    1       1     a 1.475232 1.972779 1.743491 1.161984
    2       1     b 2.030829 1.331629 1.397577 1.484865
    3       2     a 1.589256 1.740858 1.898906 2.005511
    

    我对数据的一个子集进行了检查,看起来它有效 .

    mcll(df$pred.1[which(df$Type=="a" & df$Version==1)],
     df$TrueClass[which(df$Type=="a" & df$Version==1)])
    
    [1] 1.475232 #pred.1 mcll when Version equals 1 and Type equals a.
    

相关问题