我有一个包含相当多列的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)]
有些人可以帮助简化此代码,以便
我不必为异常值编写单独的代码行。在这个例子中,我们只有8个变量,但如果我们有100个变量,我们最终会编写100行代码吗?可以使用for循环完成吗?怎么样?
一般来说,对于data.table,我们如何添加保留原始列的新列。因此,例如下面我将记录第3列到第10列。如果我不创建新的DTlog,它将覆盖DT中的原始列。如何在DT中保留原始列,并在DT中使用新列。
DTlog <- DT[,(lapply(.SD,log)),by = .(town,tc),.SDcols=3:10]
期待一些专家建议。
答案 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
(列内容)+x
与as.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