Як видалити рядок за посиланням у data.table?


150

Моє запитання пов'язане із призначенням посиланням на копіювання в 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

Скажіть, я хочу видалити перший рядок із цього таблиця даних. Я знаю, що можу це зробити:

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

у data.table?


17
Я не думаю, що так data.table()використовується SQL-технологія, що можна провести паралель між різними операціями в SQL та різними аргументами до a data.table. Для мене посилання на "технологію" дещо випливає з того data.table, що десь сидить зверху в базі даних SQL, що AFAIK не є випадком.
Чейз

1
дякую погоню. так, я думаю, що аналогія sql була дикою здогадкою.
Флоріан Освальд

1
Часто має бути достатньо визначити прапор для збереження рядків, наприклад DT[ , keep := .I > 1], потім підмножина для наступних операцій: DT[(keep), ...]можливо, навіть setindex(DT, keep)швидкість цього підмножини. Це не панацея, але варто розглядати як вибір дизайну у вашому робочому процесі - чи дійсно ви хочете видалити всі ці рядки з пам’яті , чи бажаєте ви їх виключити? Відповідь відрізняється від випадку використання.
MichaelChirico

Відповіді:


125

Гарне питання. data.tableще не може видалити рядки за посиланням.

data.tableВи можете додавати та видаляти стовпці за посиланням, оскільки він перерозподіляє вектор покажчиків стовпців, як відомо. План полягає в тому, щоб зробити щось подібне для рядків і дозволити швидко insertі delete. Видалення рядків використовує memmoveC, щоб зміщувати елементи (у кожному стовпці) після видалених рядків. Видалення рядка в середині таблиці все ще буде досить неефективним порівняно з базою даних сховища рядків, такою як SQL, яка більше підходить для швидкого вставки та видалення рядків, де б ці рядки не знаходилися в таблиці. Але все-таки це було б набагато швидше, ніж копіювання нового великого об’єкта без видалених рядків.

З іншого боку, оскільки вектори стовпців будуть перерозподілені, рядки можна буде вставити (і видалити) в кінці , миттєво; наприклад, зростаючий часовий ряд.


Це подано як випуск: Видаліть рядки за посиланням .


1
@Matthew Dowle Чи є новини про це?
статичний

15
@statquant Я думаю, що я повинен виправити 37 помилок і закінчити freadспочатку. Після цього він досить високий.
Метт Даул

15
@MatthewDowle впевнений, ще раз дякую за все, що ти робиш.
статичний

1
@rbatt Правильно. DT[b<8 & a>3]повертає новий таблицю даних. Ми хотіли б додати delete(DT, b>=8 | a<=3)і DT[b>=8 | a<=8, .ROW:=NULL]. Перевагою останнього було б поєднання з іншими особливостями, []такими як номери рядків у i, приєднатисяi і rollвигоди від [i,j,by]оптимізації.
Метт Даул

2
@charliealpha Не оновлюється. Внески Я готовий направляти. Тут потрібні навички C - знову ж таки, я готовий керуватися.
Метт Даул

29

підхід, який я застосував для того, щоб використання пам'яті було подібним до видалення на місці, - це підмножити стовпець за один раз та видалити. не так швидко, як правильне рішення пам'яті C, але використання пам'яті - це все, що мені тут все одно. щось на зразок цього:

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
}

5
+1 Приємний ефективний підхід до пам'яті. Тож в ідеалі нам потрібно видалити набір рядків за посиланням насправді, чи не так, я про це не думав. Потрібно мати серію memmoves, щоб зрушити прогалини, але це нормально.
Метт Даул

Чи буде ця функція функцією, чи використання у функції та повернення примушує її робити копії пам'яті?
russellpierce

1
він би працював у функції, оскільки data.tables - це завжди посилання.
vc273

1
спасибі, приємний. Для прискорення трохи (особливо з великою кількістю стовпців) змінити DT[, col:= NULL, with = F]вset(DT, NULL, col, NULL)
Мікеле

2
Оновлення у світлі зміни ідіоми та попередження "with = FALSE разом із: = було застаріло в v1.9.4, випущеному жовтня 2014 року. Будь ласка, оберніть LHS: = з дужками; наприклад, DT [, (myVar): = sum (b) , by = a] призначити імена стовпців, що містяться в змінній myVar. Дивіться? ': =' для інших прикладів. Як застережено в 2014 році, це зараз є попередженням. "
Франк

6

Ось робоча функція, заснована на відповіді @ vc273 та відгуках @ Франка.

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" - це таблиця даних. Видалення 14-ти рядків з рядків 1,4М займає 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 :-(


Я прокоментував відповідь vc, пояснюючи змінений синтаксис для (col): =. Вигляд непарним є функція з назвою "delete", але аргумент, пов'язаний з тим, що потрібно зберегти. Btw, як правило, краще використовувати відтворюваний приклад, а не показувати тьмяні для власних даних. Наприклад, ви можете повторно використовувати DT з питання.
Франк

Я не розумію, чому ви це робите за посиланням, але пізніше використовуєте завдання dat <-
skan

1
@skan, це призначення призначає "dat", щоб вказати на модифікований таблицю даних, який сам був створений за допомогою підстановки вихідного таблиця даних. <- assingment не копіює повернених даних, а лише присвоює їм нове ім'я. посилання
Ярно П.

@Frank, я оновив функцію для дивацтва, яке ви вказали.
Ярно П.

Добре, дякую. Я залишаю коментар, оскільки все ще думаю, що варто відзначити, що показ консольного виводу замість відтворюваного прикладу тут не рекомендується. Крім того, єдиний орієнтир не настільки інформативний. Якщо ви також виміряли час, необхідний для підмножини, це було б більш інформативно (оскільки більшість з нас не інтуїтивно знає, скільки часу займає, тим більше, скільки часу займає ваш комп). У всякому разі, я не хочу припускати, що це погана відповідь; Я один з її прихильників.
Френк

4

Замість цього або намагайтеся встановити NULL, спробуйте встановити NA (відповідність типу NA для першого стовпця)

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

3
так, я думаю, це працює. Моя проблема полягає в тому, що у мене дуже багато даних, і я хочу позбутися саме тих рядків з NA, можливо, без копіювання DT, щоб позбутися цих рядків. дякую за ваш коментар у будь-якому випадку!
Флоріан Освальд

4

Тема все ще цікавить багатьох людей (включаючи мене).

Що на рахунок того? Я використовував assignдля заміни glovalenvта описаного раніше коду. Було б краще зафіксувати оригінальне середовище, але, принаймні, globalenvвоно є ефективним для пам'яті та діє як зміна за посиланням.

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)

Щоб було зрозуміло, це не видаляється за посиланням (на основі address(DT); delete(DT, 3); address(DT)), хоча в деякому сенсі це може бути ефективно.
Френк

1
Ні, це не є. Це імітує поведінку та ефективно працює з пам'яттю. Ось чому я сказав: це діє так . Але строго кажучи, ти маєш рацію, адресу змінили.
JRR

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 або наступних таблицях даних. Дивіться https://stackoverflow.com/a/47406952/305675 . Тут я замовив іриси по Sepal Length, візьміть вказаний Sepal.Length як мінімум, виберіть трійку кращих (за довжиною Sepal) усіх видів і поверніть всі супутні дані:

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

Підходи, перш за все, упорядковують таблицю даних послідовно під час видалення рядків. Ви можете перенести таблицю даних і видалити або замінити старі рядки, які тепер перенесені стовпці. При використанні ': = 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" до перенесеного тепер таблиці даних створює всі класи символів.

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

Можна також додати додатковий лічильник з '.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),]

Це насправді не відповідає на питання (про видалення за допомогою посилання) та використання tв data.frame зазвичай не є хорошою ідеєю; перевірте, str(m_iris)чи всі дані стали рядком / символом. До речі, ви також можете отримати номери рядків, використовуючи, d_iris[duplicated(Key), which = TRUE]не роблячи стовпчика лічильника.
Франк

1
Так, ти маєш рацію. Я не відповідаю конкретно на питання. Але видалення рядка за посиланням поки не має офіційної функціональності чи документації, і багато людей збираються прийти на цю посаду, шукаючи загальну функціональність, щоб зробити саме це. Ми можемо створити публікацію, щоб просто відповісти на питання про те, як видалити рядок. Переповнення стека дуже корисно, і я дуже розумію необхідність точного відповіді на запитання. Хоча іноді, я думаю, ТАК може бути трохи фашистським у цьому плані ... але, можливо, для цього є вагомі причини.
rferrisx

Добре, дякую за пояснення. Я думаю, що наразі наша дискусія тут є достатньою табличкою для тих, хто заплутається у цій справі.
Френк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.