【问题标题】:Loop through data.table and create new columns basis some condition循环遍历 data.table 并根据某些条件创建新列
【发布时间】:2016-05-20 06:03:05
【问题描述】:

我有一个包含很多列的 data.table。我需要遍历它们并使用某些条件创建新列。目前我正在为每一列编写单独的条件行。让我用一个例子来解释。让我们将样本数据视为 -

set.seed(71)

DT <- data.table(town = rep(c('A','B'), each=10),
                 tc = rep(c('C','D'), 10),
                 one = rnorm(20,1,1),
                 two = rnorm(20,2,1),
                 three = rnorm(20,3,1),
                 four = rnorm(20,4,1),
                 five = rnorm(20,5,2),
                 six = rnorm(20,6,2),
                 seven = rnorm(20,7,2),
                 total = rnorm(20,28,3))

对于从 1 到总计的每一列,我需要创建 4 个新列,即 mean、sd、uplimit、lowlimit 用于 2 sigma 异常值计算。我正在这样做 -

DTnew <- DT[, as.list(unlist(lapply(.SD, function(x) list(mean = mean(x), sd = sd(x), uplimit = mean(x)+1.96*sd(x), lowlimit = mean(x)-1.96*sd(x))))), by = .(town,tc)]

然后我将把这个 DTnew data.table 与我的 DT 合并

DTmerge <- merge(DT, DTnew, by= c('town','tc'))

现在想出异常值,我正在为每个变量编写单独的代码集 -

DTAoutlier <- DTmerge[ ,one.Aoutlier := ifelse (one >= one.lowlimit & one <= one.uplimit,0,1)]
DTAoutlier <- DTmerge[ ,two.Aoutlier := ifelse (two >= two.lowlimit & two <= two.uplimit,0,1)]
DTAoutlier <- DTmerge[ ,three.Aoutlier := ifelse (three >= three.lowlimit & three <= three.uplimit,0,1)]

有人可以帮助简化这段代码,以便

  1. 我不必为异常值编写单独的代码行。在这个例子中,我们只有 8 个变量,但是如果我们有 100 个变量,我们最终会写 100 行代码吗?这可以使用 for 循环来完成吗?怎么样?

  2. 一般而言,对于 data.table,我们如何添加保留原始列的新列。例如,下面我记录第 3 到第 10 列。如果我不创建新的 DTlog,它会覆盖 DT 中的原始列。如何在 DT 中保留原始列并在 DT 中保留新列。

    DTlog &lt;- DT[,(lapply(.SD,log)),by = .(town,tc),.SDcols=3:10]

期待一些专家的建议。

【问题讨论】:

  • 我知道您特别在寻找 data.table 解决方案,但请注意您正在寻找的基本上是 data.table 相当于 dplyr 的 @ 可能会有所帮助987654329@
  • @shreyasgm 将其作为答案发布
  • @Prasad 这样做有什么意义?为什么你在一个组中一遍又一遍地写相同的 4 个数字?

标签: r data.table


【解决方案1】:

我们可以使用:= 做到这一点。我们对不是分组变量('nm')的列名进行子集化。使用outer ('nm1') 创建一个vector 的名称以分配给新列。然后,我们使用 OP 的代码 unlist 输出并将 (:=) 分配给 'nm1' 以创建新列。

nm <- names(DT)[-(1:2)]

nm1 <- c(t(outer(c("Mean", "SD", "uplimit", "lowlimit"), nm, paste, sep="_")))

DT[, (nm1):= unlist(lapply(.SD, function(x) { Mean = mean(x)
                                  SD = sd(x)
                     uplimit = Mean + 1.96*SD
                     lowlimit = Mean - 1.96*SD
             list(Mean, SD, uplimit, lowlimit) }), recursive=FALSE) ,
                    .(town, tc)]

问题的第二部分涉及在列之间进行逻辑比较。一种选择是将初始列、'lowlimit' 和 'uplimit' 列分别子集化并进行比较(因为它们具有相同的维度)以获得可以使用+ 强制转换为二进制的逻辑输出。然后将其分配给原始数据集以创建异常值列。

m1 <- +(DT[, nm, with = FALSE] >= DT[, paste("lowlimit", nm, sep="_"), 
          with = FALSE] & DT[, nm, with = FALSE] <= DT[, 
            paste("uplimit", nm, sep="_"), with = FALSE])
DT[,paste(nm, "Aoutlier", sep=".") := as.data.frame(m1)]

或者除了比较data.tables,我们还可以使用for循环和set(这样会更有效)

nm2 <- paste(nm, "Aoutlier", sep=".")
DT[, (nm2) := NA_integer_]
for(j in nm){
 set(DT, i = NULL, j = paste(j, "Aoutlier", sep="."), 
   value = as.integer(DT[[j]] >= DT[[paste("lowlimit", j, sep="_")]] & 
           DT[[j]] <= DT[[paste("uplimit", j, sep="_")]]))
 }

“日志”列也可以用:=创建

DT[,paste(nm, "log", sep=".") := lapply(.SD,log),by = .(town,tc),.SDcols=nm]

【讨论】:

  • 谢谢阿克伦!!能否请您解释一下代码的 + 部分。 + 实际上在做什么以及为什么使用with = FALSE。然后是for循环,请详细说明set部分。为什么我们说i=NULL?另外,为什么要给出两个方括号DT[[j]]?抱歉,我可能会问一些基本问题,但如果您能澄清一下,那就太好了。您还可以指出一些好的资源来了解更多关于 data.tables 和此类操作的示例吗?
  • @Prasad + 是一种将逻辑矩阵转换为二进制矩阵的巧妙方法。它的作用与as.integeras.numeric 相同,但它也会保留结构。如果您阅读?data.table,就会提到with = FALSE 用于子集列。我们没有为该行设置任何条件,因为该列中的所有行都已更改。如果有一个特定的条件,比如只更改行中具有某些值的行中的值,我们可以在i 中指定它。您可以通过查看小插图了解有关 data.table 的更多信息。
【解决方案2】:

您的数据可能应该是长格式:

m = melt(DT, id=c("town","tc"))

然后只需编写一次测试

m[, 
  is_outlier := +(abs(value-mean(value)) > 1.96*sd(value))
, by=.(town, tc, variable)]

我在这个数据中没有看到异常值(根据给定的异常值定义):

m[, .N, by=is_outlier] # this is a handy alternative to table()

#    is_outlier   N
# 1:          0 160

它是如何工作的

  • melt 保留 id 列并将其余所有列堆叠到
    • variable(列名)
    • value(栏目内容)
  • +xas.integer(x) 做同样的事情,将 TRUE/FALSE 强制为 1/0

如果您真的喜欢宽格式的数据,那么:

vjs = setdiff(names(DT), c("town","tc"))
DT[, 
  paste0(vjs,".out") := lapply(.SD, function(x) +(abs(x-mean(x)) > 1.96*sd(x)))
, by=.(town, tc), .SDcols=vjs]

【讨论】:

  • 嗨弗兰克,对测试代码不是很清楚。我们为什么要写+(abs(value-mean(value))?然后部分是variable
  • @Prasad 抱歉,忘记解释这些,但现在已编辑。如果还有什么不清楚的,请告诉我
【解决方案3】:

为了完整起见,应该注意dplyrmutate_each 提供了解决此类问题的便捷方法:

library(dplyr)

result <- DT %>%
    group_by(town,tc) %>%
    mutate_each(funs(mean,sd,
                     uplimit = (mean(.) + 1.96*sd(.)),
                     lowlimit = (mean(.) - 1.96*sd(.)),
                     Aoutlier = as.integer(. >= mean(.) - 1.96*sd(.) &
                                               . <= mean(.) - 1.96*sd(.))),
                -town,-tc)

【讨论】:

    猜你喜欢
    • 2015-10-03
    • 2021-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-24
    • 1970-01-01
    相关资源
    最近更新 更多