data.table의 지정된 모든 열에 동일한 함수를 적용하는 방법


85

특정 열에서 동일한 작업을 수행하려는 data.table이 있습니다. 이 열의 이름은 문자형 벡터로 제공됩니다. 이 특정 예에서는이 모든 열에 -1을 곱하고 싶습니다.

관련 열을 지정하는 일부 장난감 데이터 및 벡터 :

library(data.table)
dt <- data.table(a = 1:3, b = 1:3, d = 1:3)
cols <- c("a", "b")

지금은 문자 벡터를 반복하면서 이렇게하고 있습니다.

for (col in 1:length(cols)) {
   dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
}

for 루프없이 직접 수행 할 수있는 방법이 있습니까?

답변:


150

이것은 작동하는 것 같습니다.

dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols]

결과는

    a  b d
1: -1 -1 1
2: -2 -2 2
3: -3 -3 3

여기에 몇 가지 트릭이 있습니다.

  • 에 괄호가 있기 때문에 (cols) :=결과가 cols"cols"라는 새 변수 대신에 지정된 열에 할당됩니다 .
  • .SDcols호출에 해당 열만보고 있음을 알려주고 해당 열과 관련된 ata .SDSubset 을 사용할 수 있도록합니다 D.
  • lapply(.SD, ...).SD모든 data.frames 및 data.tables와 같이 열 목록 인 에서 작동합니다 . lapply그래서 끝에서, 목록을 반환 j같은 외모 cols := list(...).

편집 : @Arun이 언급했듯이 아마도 더 빠른 또 다른 방법이 있습니다.

for (j in cols) set(dt, j = j, value = -dt[[j]])

21
또 다른 방법은 사용하는 것입니다 set로모그래퍼 for-loop. 더 빠를 것 같아요.
Arun

3
@Arun 나는 편집했다. 그게 무슨 뜻입니까? 나는 set전에 사용하지 않았습니다 .
Frank

8
+1 좋은 답변입니다. 예 , 이와 같은 경우에도 for루프를 선호합니다 set.
Matt Dowle

2
예, 사용하는 set()것이 내 데이터 세트에 대해 ~ 4 배 더 빠른 것 같습니다! 놀랄 만한.
Konstantinos

2
감사합니다, @JamesHirschorn. 확실하지 않지만, 인트로 비 네트 github.com/Rdatatable/data.table/wiki/Getting-started에 나타나는 표준 관용구 인 .SD를 사용하는 것보다 열을 부분 화하는 데 더 많은 오버 헤드가 있다고 생각합니다. 관용구의 이유 중 하나는 테이블 이름을 두 번 입력하지 않기 때문이라고 생각합니다.
Frank

20

열 이름도 변경하고 싶을 때 답변을 추가하고 싶습니다. 여러 열의 로그를 계산하려는 경우 매우 유용하며, 이는 종종 경험적 작업의 경우입니다.

cols <- c("a", "b")
out_cols = paste("log", cols, sep = ".")
dt[, c(out_cols) := lapply(.SD, function(x){log(x = x, base = exp(1))}), .SDcols = cols]

1
규칙에 따라 이름을 변경하는 방법이 있습니까? 예를 들어 dplyr에서는 iris %> % mutate_at (vars (matches ( "Sepal")), list (times_two = ~. * 2))를 수행 할 수 있으며 새 이름에 "_times_two"를 추가합니다.
kennyB

1
나는 그것이 가능하다고 생각하지 않지만 실제로는 확실하지 않습니다.
hannes101

이렇게하면 이름이 out_cols인 열이 추가 되지만 여전히 cols제자리에 남아 있습니다. 따라서 명시 적으로 1) log.a 및 log.b 만 요청하여 제거해야합니다. a [,.(outcols)]를 끝까지 연결하고을 dt통해 다시 저장합니다 <-. 2) 체인으로 연결된 이전 열을 제거하십시오 [,c(cols):=NULL]. dt[,c(cols):=...]setnames(dt, cols, newcols)
비체 인

@mpag, 맞습니다. 그러나 경험적 연구의 사용 사례에서는 대부분 데이터 세트에 두 시리즈가 모두 필요합니다.
hannes101

11

업데이트 : 다음은 for 루프없이 수행하는 깔끔한 방법입니다.

dt[,(cols):= - dt[,..cols]]

쉬운 코드 가독성을위한 깔끔한 방법입니다. 그러나 성능에 관해서는 아래 microbenchmark 결과에 따라 Frank의 솔루션 뒤에 머물러 있습니다.

mbm = microbenchmark(
  base = for (col in 1:length(cols)) {
    dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
  },
  franks_solution1 = dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols],
  franks_solution2 =  for (j in cols) set(dt, j = j, value = -dt[[j]]),
  hannes_solution = dt[, c(out_cols) := lapply(.SD, function(x){log(x = x, base = exp(1))}), .SDcols = cols],
  orhans_solution = for (j in cols) dt[,(j):= -1 * dt[,  ..j]],
  orhans_solution2 = dt[,(cols):= - dt[,..cols]],
  times=1000
)
mbm

Unit: microseconds
expr                  min        lq      mean    median       uq       max neval
base_solution    3874.048 4184.4070 5205.8782 4452.5090 5127.586 69641.789  1000  
franks_solution1  313.846  349.1285  448.4770  379.8970  447.384  5654.149  1000    
franks_solution2 1500.306 1667.6910 2041.6134 1774.3580 1961.229  9723.070  1000    
hannes_solution   326.154  405.5385  561.8263  495.1795  576.000 12432.400  1000
orhans_solution  3747.690 4008.8175 5029.8333 4299.4840 4933.739 35025.202  1000  
orhans_solution2  752.000  831.5900 1061.6974  897.6405 1026.872  9913.018  1000

아래 차트와 같이

performance_comparison_chart

내 이전 답변 : 다음도 작동합니다.

for (j in cols)
  dt[,(j):= -1 * dt[,  ..j]]

이것은 본질적으로 1 년 반 전 Frank의 답변과 동일합니다.
Dean MacGregor

1
감사합니다. Frank의 대답은 set을 사용했습니다. 운영자 성능이 뛰어 기능을 = 내가 행 수백만 큰 data.table의 작업을 할 때, 나는 볼
오르한 셀릭

2
이전 질문에 대한 답변을 추가 한 이유는 다음과 같습니다. 유사한 문제가 발생하여 Google 검색으로이 게시물을 발견했습니다. 나중에 내 문제에 대한 해결책을 찾았고 여기에도 적용된다는 것을 알았습니다. 실제로 내 제안은 질문 당시에는 존재하지 않았던 새로운 버전의 라이브러리에서 사용할 수있는 data.table의 새로운 기능을 사용합니다. 나는 공유하는 것이 좋은 생각이라고 생각했는데, 비슷한 문제를 가진 다른 사람들이 여기에 Google 검색으로 끝날 것이라고 생각했습니다.
Orhan Celik 2018

1
dt3 개의 행으로 구성된 벤치마킹 중입니까?
Uwe

3
Hannes의 대답은 다른 계산을 수행하므로 다른 것과 비교해서는 안됩니다.
Frank

2

위의 솔루션 중 어느 것도 그룹 별 계산과 함께 작동하지 않는 것 같습니다. 다음은 내가 얻은 최고입니다.

for(col in cols)
{
    DT[, (col) := scale(.SD[[col]], center = TRUE, scale = TRUE), g]
}

1

열의 string 형 벡터를 기반으로 새 열을 만드는 예제를 추가합니다. Jfly 답변을 기반으로 :

dt <- data.table(a = rnorm(1:100), b = rnorm(1:100), c = rnorm(1:100), g = c(rep(1:10, 10)))

col0 <- c("a", "b", "c")
col1 <- paste0("max.", col0)  

for(i in seq_along(col0)) {
  dt[, (col1[i]) := max(get(col0[i])), g]
}

dt[,.N, c("g", col1)]

0
library(data.table)
(dt <- data.table(a = 1:3, b = 1:3, d = 1:3))

Hence:

   a b d
1: 1 1 1
2: 2 2 2
3: 3 3 3

Whereas (dt*(-1)) yields:

    a  b  d
1: -1 -1 -1
2: -2 -2 -2
3: -3 -3 -3

1
참고로 제목의 "모든 지정 열"은 질문자가 열의 하위 집합에 적용하는 데 관심이 있음을 의미합니다 (모두는 아닐 수도 있음).
Frank

1
@Frank 확실히! 이 경우 OP는 dt [, c ( "a", "b")] * (-1)을 수행 할 수 있습니다.
amonk

1
음, 완전하게 말하기dt[, cols] <- dt[, cols] * (-1)
그레고르 토마스

필요한 새 구문은 dt [, cols] <-dt [, ..cols] * (-1)
Arthur Yip

0

dplyr함수는 data.tables에서 작동 하므로 여기에 dplyr"for 루프를 피하는"솔루션이 있습니다. :)

dt %>% mutate(across(all_of(cols), ~ -1 * .))

나는 오르한의 코드 (행과 열을 추가)을 사용하여 벤치마킹 당신은 볼 dplyr::mutate과 함께 across주로 빨리 다른 솔루션의 대부분에 비해 속도가 느린 lapply 사용하여 data.table 솔루션보다 실행합니다.

library(data.table); library(dplyr)
dt <- data.table(a = 1:100000, b = 1:100000, d = 1:100000) %>% 
  mutate(a2 = a, a3 = a, a4 = a, a5 = a, a6 = a)
cols <- c("a", "b", "a2", "a3", "a4", "a5", "a6")

dt %>% mutate(across(all_of(cols), ~ -1 * .))
#>               a       b      d      a2      a3      a4      a5      a6
#>      1:      -1      -1      1      -1      -1      -1      -1      -1
#>      2:      -2      -2      2      -2      -2      -2      -2      -2
#>      3:      -3      -3      3      -3      -3      -3      -3      -3
#>      4:      -4      -4      4      -4      -4      -4      -4      -4
#>      5:      -5      -5      5      -5      -5      -5      -5      -5
#>     ---                                                               
#>  99996:  -99996  -99996  99996  -99996  -99996  -99996  -99996  -99996
#>  99997:  -99997  -99997  99997  -99997  -99997  -99997  -99997  -99997
#>  99998:  -99998  -99998  99998  -99998  -99998  -99998  -99998  -99998
#>  99999:  -99999  -99999  99999  -99999  -99999  -99999  -99999  -99999
#> 100000: -100000 -100000 100000 -100000 -100000 -100000 -100000 -100000

library(microbenchmark)
mbm = microbenchmark(
  base_with_forloop = for (col in 1:length(cols)) {
    dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
  },
  franks_soln1_w_lapply = dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols],
  franks_soln2_w_forloop =  for (j in cols) set(dt, j = j, value = -dt[[j]]),
  orhans_soln_w_forloop = for (j in cols) dt[,(j):= -1 * dt[,  ..j]],
  orhans_soln2 = dt[,(cols):= - dt[,..cols]],
  dplyr_soln = (dt %>% mutate(across(all_of(cols), ~ -1 * .))),
  times=1000
)

library(ggplot2)
ggplot(mbm) +
  geom_violin(aes(x = expr, y = time)) +
  coord_flip()

reprex 패키지 (v0.3.0)로 2020-10-16에 생성

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.