如何在 data.table 中通过引用删除行?

我的问题与 data.table中的参考作业和复制作业有关。我想知道是否可以通过引用删除行,类似于

DT[ , someCol := NULL]

我想知道

DT[someRow := NULL, ]

我想有一个很好的理由来解释为什么这个函数不存在,所以也许你可以指出一个很好的替代方法来代替通常的复制方法,如下所示。特别是我最喜欢的例子(data.table) ,

DT = data.table(x = rep(c("a", "b", "c"), each = 3), y = c(1, 3, 6), v = 1:9)
#      x y v
# [1,] a 1 1
# [2,] a 3 2
# [3,] a 6 3
# [4,] b 1 4
# [5,] b 3 5
# [6,] b 6 6
# [7,] c 1 7
# [8,] c 3 8
# [9,] c 6 9

假设我想从 data.table 中删除第一行,我知道我可以这样做:

DT <- DT[-1, ]

但是通常我们可能想要避免这种情况,因为我们正在复制对象(这需要大约3 * N 内存,如果 N object.size(DT)正如这里指出的。 现在我找到了 set(DT, i, j, value)。我知道如何设置特定的值(如下所示: 将第1行和第2列中的所有值以及第2列和第3列中的值设置为零)

set(DT, 1:2, 2:3, 0)
DT
#      x y v
# [1,] a 0 0
# [2,] a 0 0
# [3,] a 6 3
# [4,] b 1 4
# [5,] b 3 5
# [6,] b 6 6
# [7,] c 1 7
# [8,] c 3 8
# [9,] c 6 9

但是我怎样才能删除前两行,比如说? 做

set(DT, 1:2, 1:3, NULL)

将整个 DT 设置为 NULL。

我的 SQL 知识是非常有限的,所以你们告诉我: 既然 data.table 使用 SQL 技术,是否有一个相当于 SQL 命令

DELETE FROM table_name
WHERE some_column=some_value

数据表?

57706 次浏览

相反,或者尝试将其设置为 NULL,尝试将其设置为 NA (与第一列的 NA 类型相匹配)

set(DT,1:2, 1:3 ,NA_character_)

问得好。 data.table还不能通过引用删除行。

data.table可以通过引用添加和删除 柱子,因为它过度分配了列指针的向量。计划是对行执行类似的操作,并允许快速 insertdelete。行删除操作将使用 C 语言中的 memmove在被删除的行之后增加项目(在每一列中)。与行存储数据库(如 SQL)相比,在表中间删除行的效率仍然很低,后者更适合快速插入和删除表中的行。但是,这仍然比复制一个没有被删除行的新大对象要快得多。

另一方面,由于列向量将被过度分配,行可以立即插入(并删除) 最后; 例如,一个不断增长的时间序列。


作为问题提交: 通过引用删除行

为了使内存的使用类似于就地删除,我采取的方法是一次删除一个列。没有正确的 C memmove 解决方案快,但内存使用是我在这里关心的全部。像这样:

DT = data.table(col1 = 1:1e6)
cols = paste0('col', 2:100)
for (col in cols){ DT[, (col) := 1:1e6] }
keep.idxs = sample(1e6, 9e5, FALSE) # keep 90% of entries
DT.subset = data.table(col1 = DT[['col1']][keep.idxs]) # this is the subsetted table
for (col in cols){
DT.subset[, (col) := DT[[col]][keep.idxs]]
DT[, (col) := NULL] #delete
}

下面是一个基于@vc273的回答和@Frank 的反馈的工作函数。

delete <- function(DT, del.idxs) {           # pls note 'del.idxs' vs. 'keep.idxs'
keep.idxs <- setdiff(DT[, .I], del.idxs);  # select row indexes to keep
cols = names(DT);
DT.subset <- data.table(DT[[1]][keep.idxs]); # this is the subsetted table
setnames(DT.subset, cols[1]);
for (col in cols[2:length(cols)]) {
DT.subset[, (col) := DT[[col]][keep.idxs]];
DT[, (col) := NULL];  # delete
}
return(DT.subset);
}

它的用法如下:

dat <- delete(dat,del.idxs)   ## Pls note 'del.idxs' instead of 'keep.idxs'

其中“ dat”是一个 data.table,在我的笔记本电脑上从1.4 M 行中删除14k 行需要0.25秒。

> dim(dat)
[1] 1419393      25
> system.time(dat <- delete(dat,del.idxs))
user  system elapsed
0.23    0.02    0.25
> dim(dat)
[1] 1404715      25
>

另外,由于我是新来的,所以我不能在@vc273的帖子中添加评论:-(

许多人(包括我)仍然对这个话题感兴趣。

怎么样?我使用 assign来替换 glovalenv和前面描述的代码。捕获原始环境会更好,但是至少在 globalenv中它是内存高效的,并且像通过 ref 进行更改一样。

delete <- function(DT, del.idxs)
{
varname = deparse(substitute(DT))


keep.idxs <- setdiff(DT[, .I], del.idxs)
cols = names(DT);
DT.subset <- data.table(DT[[1]][keep.idxs])
setnames(DT.subset, cols[1])


for (col in cols[2:length(cols)])
{
DT.subset[, (col) := DT[[col]][keep.idxs]]
DT[, (col) := NULL];  # delete
}


assign(varname, DT.subset, envir = globalenv())
return(invisible())
}


DT = data.table(x = rep(c("a", "b", "c"), each = 3), y = c(1, 3, 6), v = 1:9)
delete(DT, 3)

以下是我使用过的一些策略。我相信。ROW 函数可能会出现。以下这些方法都不快。这些是一些略微超出子集或过滤的策略。我试着像 dba 那样思考,只是试图清理数据。如上所述,您可以选择或删除 data.table 中的行:

data(iris)
iris <- data.table(iris)


iris[3] # Select row three


iris[-3] # Remove row three


You can also use .SD to select or remove rows:


iris[,.SD[3]] # Select row three


iris[,.SD[3:6],by=,.(Species)] # Select row 3 - 6 for each Species


iris[,.SD[-3]] # Remove row three


iris[,.SD[-3:-6],by=,.(Species)] # Remove row 3 - 6 for each Species

注:。SD 创建原始数据的一个子集,并允许您在 j 或后续的 data.table 中执行相当多的工作。参见 https://stackoverflow.com/a/47406952/305675。在这里,我订购了我的虹膜的萼片长度,采取指定的萼片。以最小长度为准,选择所有物种中的前三个(以萼片长度为准) ,并返回所有相关数据:

iris[order(-Sepal.Length)][Sepal.Length > 3,.SD[1:3],by=,.(Species)]

上述方法在删除行时按顺序重新排序 data.table。您可以转换 data.table 并删除或替换现在转换为列的旧行。当使用’: = NULL’删除换位的行时,后面的列名也会被删除:

m_iris <- data.table(t(iris))[,V3:=NULL] # V3 column removed


d_iris <- data.table(t(iris))[,V3:=V2] # V3 column replaced with V2

当您将 data.frame 转换回 data.table 时,您可能希望从原始 data.table 重命名,并在删除的情况下恢复类属性。将“ : = NULL”应用到现在转换的 data.table 将创建所有字符类。

m_iris <- data.table(t(d_iris));
setnames(d_iris,names(iris))


d_iris <- data.table(t(m_iris));
setnames(m_iris,names(iris))

您可能只是想删除重复的行,您可以做或不键:

d_iris[,Key:=paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)]


d_iris[!duplicated(Key),]


d_iris[!duplicated(paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)),]

也可以使用’添加增量计数器。我。然后,可以搜索重复的键或字段,并通过计数器删除记录来删除它们。这在计算上是昂贵的,但是有一些优点,因为您可以打印要删除的行。

d_iris[,I:=.I,] # add a counter field


d_iris[,Key:=paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)]


for(i in d_iris[duplicated(Key),I]) {print(i)} # See lines with duplicated Key or Field


for(i in d_iris[duplicated(Key),I]) {d_iris <- d_iris[!I == i,]} # Remove lines with duplicated Key or any particular field.

你也可以用0或者 NA 填充一行,然后使用 i 查询来删除它们:

 X
x v foo
1: c 8   4
2: b 7   2


X[1] <- c(0)


X
x v foo
1: 0 0   0
2: b 7   2


X[2] <- c(NA)
X
x  v foo
1:  0  0   0
2: NA NA  NA


X <- X[x != 0,]
X <- X[!is.na(x),]