循环遍历data.table并根据某些条件创建新列

时间:2016-05-20 06:03:06

标签: r data.table

我有一个包含相当多列的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到total的每一列,我需要创建4个新列,即2 sigma异常值计算的mean,sd,uplimit,lowlimit。我是这样做的 -

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 <- DT[,(lapply(.SD,log)),by = .(town,tc),.SDcols=3:10]

  3. 期待一些专家建议。

3 个答案:

答案 0 :(得分:4)

我们可以使用:=执行此操作。我们对不是分组变量的列名称进行了子集化(&#39; nm&#39;)。使用vector(&#39; nm1&#39;)创建要为新列指定的outer个名称。然后,我们使用OP的代码,unlist输出并将其分配(:=)到&#39; nm1&#39;创建新列。

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)]

问题的第二部分涉及在列之间进行逻辑比较。一种选择是对初始列进行子集化,即“低限制”和“低限制”。和&#39; uplimit&#39;列分别进行比较(因为它们具有相同的尺寸)以获得可以用+强制转换为二进制的逻辑输出。然后将其分配给原始数据集以创建异常值列。

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="_")]]))
 }

&#39; log&#39;也可以使用:=

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

答案 1 :(得分:3)

您的数据应该采用长格式:

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]

答案 2 :(得分:0)

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

mutate_each