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

이 data.table에서 첫 번째 행을 삭제하고 싶다고 가정 해보십시오. 나는 이것을 할 수 있다는 것을 안다.

DT <- DT[-1, ]

하지만 종종 우리는 객체를 복사하고 있기 때문에 우리는이를 방지 할 수 있습니다 (N은 경우 즉, 3 * N 메모리에 대한 요구 object.size(DT), 여기에서 지적했듯이 지금은 발견했다. set(DT, i, j, value)내가 어떻게 설정 특정 값으로 (여기처럼 알고있다. 모든 설정 1 행과 2 행, 2 열과 3 열의 값은 0)

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 기술 을 사용 한다고 생각하지 않습니다 data.table. 나에게 "기술"에 대한 언급 data.table은 AFAIK가 아닌 어딘가에 SQL 데이터베이스 위에 있다는 것을 암시합니다 .
체이스

1
감사합니다. 예, SQL 유추가 거친 추측이라고 생각합니다.
Florian Oswald

1
종종 행, 같은 유지하기위한 플래그를 정의하기에 충분해야 DT[ , keep := .I > 1]나중에 작업 집합을, : DT[(keep), ...], 아마도 setindex(DT, keep)이 부분 집합의 속도. 만병 통치약은 아니지만 워크 플로우에서 디자인 선택으로 고려해야 할 가치가 있습니다. 메모리에서 모든 행 을 실제로 삭제 하시겠습니까 , 아니면 제외 하시겠습니까? 답변은 사용 사례에 따라 다릅니다.
MichaelChirico

답변:


125

좋은 질문. data.table아직 참조로 행을 삭제할 수 없습니다.

data.table알고 있듯이 열 포인터의 벡터를 과도하게 할당하기 때문에 참조로 을 추가하고 삭제할 수 있습니다 . 계획은 행과 비슷한 작업을 수행하고 빠른 insert및을 허용하는 것 delete입니다. memmoveC에서 행 삭제를 사용 하면 삭제 된 행 다음에 항목 (각 열마다)을 버립니다. 테이블 중간에서 행을 삭제하면 SQL과 같은 행 저장소 데이터베이스와 비교할 때 여전히 비효율적입니다.이 행은 테이블에서 행을 빠르게 삽입하고 삭제하는 데 더 적합합니다. 그러나 여전히 행을 삭제하지 않고 새로운 큰 개체를 복사하는 것보다 훨씬 빠릅니다.

반면에 열 벡터가 과도하게 할당 되므로 끝에 행이 즉시 삽입 (삭제) 될 수 있습니다 . 예를 들어, 증가하는 시계열.


문제로 제기되었습니다 : 참조로 행 삭제 .


1
@Matthew Dowle 이것에 대한 뉴스가 있습니까?
statquant

15
@statquant 37 개의 버그를 수정하고 fread먼저 마무리해야한다고 생각합니다 . 그 후 그것은 꽤 높습니다.
매트 Dowle

15
@MatthewDowle, 당신이하는 모든 일에 다시 한번 감사드립니다.
statquant

1
@rbatt 맞습니다. DT[b<8 & a>3]새로운 data.table을 반환합니다. delete(DT, b>=8 | a<=3)및 을 추가 하고 싶습니다 DT[b>=8 | a<=8, .ROW:=NULL]. 후자의 장점은 []행 번호 입력 i, 결합 i및 최적화의 roll이점 과 같은 다른 기능과 결합하는 것 [i,j,by]입니다.
Matt Dowle

2
@charliealpha 업데이트가 없습니다. 기부를 환영합니다. 기꺼이 안내하겠습니다. C 기술이 필요합니다. 다시 안내하겠습니다.
Matt Dowle

29

메모리 사용을 내부 삭제와 유사하게하기 위해 취한 접근법은 한 번에 열을 하위 집합으로 만들고 삭제하는 것입니다. 적절한 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
}

5
+1 멋진 메모리 효율적인 접근 방식. 따라서 이상적으로는 참조로 행 집합을 삭제해야합니다. 실제로 우리는 그렇게 생각하지 않았습니다. memmove격차를 없애기 위해서는 일련의 작업이 필요 하지만 괜찮습니다.
Matt Dowle

이것은 함수로 작동합니까, 아니면 함수에서 사용하여 메모리 복사를 강제로 수행합니까?
russellpierce

1
data.tables는 항상 참조이기 때문에 함수에서 작동합니다.
vc273

1
고마워요 약간의 속도를 높이려면 (특히 열이 많은 경우) 다음을 변경하십시오 DT[, col:= NULL, with = F].set(DT, NULL, col, NULL)
Michele

2
관용구 변경 및 경고 "with = FALSE : =와 함께 2014 년 10 월 릴리스 된 v1.9.4에서는 더 이상 사용되지 않습니다."를 괄호로 묶습니다 (예 : DT [, (myVar) : = sum (b) , by = a] 변수 myVar에 보유 된 열 이름에 지정합니다. 다른 예는? ': ='을 참조하십시오. 2014 년에 경고 된 것처럼 이제 경고입니다. "
Frank

6

다음은 @ 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
> 

추신. SO를 처음 사용했기 때문에 @ vc273의 스레드에 주석을 추가 할 수 없습니다 :-(


(col) : =에 대해 변경된 구문을 설명하는 vc의 답변 아래에 댓글을 달았습니다. "delete"라는 이름의 함수가 있지만 유지해야 할 항목과 관련된 인수가 이상합니다. Btw, 일반적으로 자신의 데이터에 어둡게 표시하는 대신 재현 가능한 예제를 사용하는 것이 좋습니다. 예를 들어 질문에서 DT를 재사용 할 수 있습니다.
Frank

나는 왜 당신이 그것을 참조로하는지 이해하지 못하지만 나중에 할당 dat <
skan

1
@skan, 해당 할당은 "dat"가 원래 data.table을 서브 세트하여 생성 된 수정 된 data.table을 가리 키도록 지정합니다. <-assingment는 리턴 데이터를 복사하지 않고 새로운 이름을 지정합니다. 링크
Jarno P.

@ Frank, 나는 당신이 지적한 이상에 대한 기능을 업데이트했습니다.
Jarno P.

알았어 고마워. 나는 여전히 재현 가능한 예제 대신 콘솔 출력을 보여주는 것이 권장되지 않는다는 점에 주목할 가치가 있다고 생각하기 때문에 의견을 남기고 있습니다. 또한 단일 벤치 마크는 그다지 유익하지 않습니다. 하위 설정에 걸리는 시간도 측정했다면 더 유익 할 것입니다 (대부분의 사람들이 소요 시간을 직관적으로 알지 못하기 때문에 대부분의 시간은 컴포지션에 소요되는 시간이 훨씬 적습니다). 어쨌든, 나는 이것이 나쁜 대답이라고 제안하는 것은 아닙니다. 나는 upvoters 중 하나입니다.
Frank

4

대신 NULL로 설정하거나 NA로 설정해보십시오 (첫 번째 열의 NA 유형과 일치)

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

3
그래, 그게 내가 생각하는 작동합니다. 내 문제는 많은 데이터가 있고 NA를 사용하여 해당 행을 제거하기 위해 DT를 복사하지 않고도 NA를 사용하여 정확하게 해당 행을 제거하고 싶다는 것입니다. 어쨌든 귀하의 의견에 감사드립니다!
Florian Oswald

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)말하면 참조로 삭제하지는 않지만 어떤 의미에서는 효율적 일 수 있습니다.
Frank

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를 최소로 가져 와서 모든 종 중 최상위 3 개 (Seal 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에서 이름을 바꾸고 삭제시 클래스 속성을 복원 할 수 있습니다. 현재 전치 된 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),]

이것은 실제로 질문에 대한 답이 아니며 (참조로 제거에 관한) tdata.frame에서 사용 하는 것은 일반적으로 좋은 생각이 아닙니다. str(m_iris)모든 데이터가 문자열 / 문자가 되었는지 확인 하십시오. Btw, d_iris[duplicated(Key), which = TRUE]카운터 열을 만들지 않고을 사용하여 행 번호를 얻을 수도 있습니다 .
Frank

1
네, 맞아요. 나는 특별히 그 질문에 대답하지 않습니다. 그러나 참조로 행을 제거하는 것은 아직 공식적인 기능이나 문서가 없으며 많은 사람들 이이 기능을 수행하기 위해 일반적인 기능을 찾고 있습니다. 행을 제거하는 방법에 대한 질문에 답하기 위해 게시물을 만들 수 있습니다. 스택 오버플로는 매우 유용하며 질문에 대한 정확한 답변을 유지해야 할 필요성을 이해합니다. 때때로, 나는 SO가 이와 관련하여 약간의 파시스트 일 수 있다고 생각하지만 ... 그럴만 한 이유가있을 수 있습니다.
rferrisx

알겠습니다. 설명해 주셔서 감사합니다. 나는 지금 여기서 우리의 논의가이 경우에 혼란스러워하는 사람을위한 푯말로 충분하다고 생각합니다.
Frank
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.