首页 文章

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

提问于
浏览
125

我的问题与 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)as pointed out here . 现在我找到 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

在data.table?

6 回答

  • 100

    为了使内存使用类似于就地删除,我采取的方法是一次对列进行子集并删除 . 没有正确的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
    }
    
  • 28

    好问题 . data.table 无法通过引用删除行 .

    data.table 可以通过引用添加和删除列,因为它会过度分配列指针的向量,如您所知 . 计划是为行做类似的事情并允许快速 insertdelete . 行删除将使用C中的 memmove 来删除已删除行之后的项目(在每一列中) . 与诸如SQL之类的行存储数据库相比,删除表中间的行仍然是非常低效的,这更适合于快速插入和删除表中这些行的任何位置 . 但是,它仍然比复制没有删除行的新大对象快得多 .

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


    它是一个问题:Delete rows by reference .

  • 4

    这是一个基于@ 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.4M行中的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
    >
    

    PS . 由于我是SO的新手,我无法在@ vc273的帖子中添加评论:-(

  • 1

    而是尝试设置为NULL,尝试设置为NA(匹配第一列的NA类型)

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

    很多人(包括我在内)这个话题仍然很有趣 .

    那个怎么样?我用 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)
    
  • 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 . 在这里,我按照Sepal Length命令我的虹膜,将指定的Sepal.Length作为最小值,选择所有物种的前三个(通过Sepal Length)并返回所有伴随数据:

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

    您可能只想删除可以使用或不使用Key执行的重复行:

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

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

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

相关问题