Bir data.table'da belirtilen her sütuna aynı işlev nasıl uygulanır


86

Aynı işlemi belirli sütunlarda gerçekleştirmek istediğim bir data.table var. Bu sütunların isimleri bir karakter vektöründe verilmiştir. Bu özel örnekte, tüm bu sütunları -1 ile çarpmak istiyorum.

Bazı oyuncak verileri ve ilgili sütunları belirten bir vektör:

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

Şu anda bunu şu şekilde yapıyorum, karakter vektörünün üzerinden geçiyorum:

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

Bunu for döngüsü olmadan doğrudan yapmanın bir yolu var mı?

Yanıtlar:


151

Bu işe yarıyor gibi görünüyor:

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

Sonuç

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

Burada birkaç numara var:

  • İçinde parantezler olduğundan (cols) :=, sonuç cols"cols" adlı yeni bir değişken yerine içinde belirtilen sütunlara atanır .
  • .SDcolsçağrıya sadece bu sütunlara baktığımızı ve bu sütunlarla ilişkili ata'nın ubsetini kullanmamıza izin .SDverdiğini Ssöyler D.
  • lapply(.SD, ...).SDbir sütun listesi olan üzerinde çalışır (tüm data.frames ve data.tables gibi). lapplybir liste döndürür, böylece sonunda jşöyle görünür cols := list(...).

DÜZENLEME : İşte @Arun'un bahsettiği gibi muhtemelen daha hızlı olan başka bir yol:

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

22
Başka bir yolu kullanmaktır setbir ile for-loop. Daha hızlı olacağından şüpheleniyorum.
Arun

3
@Arun Bir düzenleme yaptım. Demek istediğin bu muydu? Daha setönce kullanmadım
Frank

8
+1 Harika cevap. Evet, bunun gibi durumlarda da bir fordöngü tercih ederim set.
Matt Dowle

2
Evet, set()veri kümem için kullanmak daha hızlı görünüyor, ~ 4 kat daha hızlı! İnanılmaz.
Konstantinos

2
Teşekkürler @JamesHirschorn. Emin değilim, ancak yine de standart deyim olan .SD'yi kullanmaktan ziyade sütunları bu şekilde alt kümelendirmenin daha fazla ek yükü olduğundan şüpheleniyorum, giriş vinyetinde github.com/Rdatatable/data.table/wiki/Getting-started Sanırım deyimin nedenlerinden biri, tablo adını iki kez yazmaktan kaçınmaktır.
Frank

20

Sütunların adını da değiştirmek istediğinizde bir cevap eklemek istiyorum. Ampirik çalışmalarda sıklıkla görülen çoklu sütunun logaritmasını hesaplamak istiyorsanız, bu oldukça kullanışlıdır.

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
İsimleri bir kurala göre değiştirmenin bir yolu var mı? Örneğin dplyr'de iris%>% mutate_at (değişken (eşleşmeler ("Sepal")), list (times_two = ~. * 2)) yapabilirsiniz ve yeni adlara "_times_two" ekleyecektir.
kennyB

1
Bunun mümkün olduğunu sanmıyorum ama bundan pek emin değilim.
hannes101

bu , yerinde out_colsbırakılırken, adlı sütunları ekleyecektir cols. Dolayısıyla, bunları ya açıkça 1) yalnızca log.a ve log.b'yi isteyerek ortadan kaldırmanız gerekir: a'yı [,.(outcols)]sonuna zincirleyin ve dtyoluyla yeniden depolayın <-. 2) zincirleme ile eski sütunları çıkarın [,c(cols):=NULL]. Zincirleme olmayan bir çözüm 3) ' dt[,c(cols):=...]setnames(dt, cols, newcols)
ün

@mpag, evet bu doğru, ancak deneysel araştırma durumum için çoğu zaman veri setinde her iki diziye de ihtiyacım var.
hannes101

11

GÜNCELLEME: Aşağıdaki, döngü olmadan yapmanın düzgün bir yoludur

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

Kolay kod okunabilirliği için düzgün bir yoldur. Ancak performans açısından, aşağıdaki mikro ölçüt sonucuna göre Frank'in çözümünün gerisinde kalıyor

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

aşağıdaki çizelgede gösterildiği gibi

performance_comparison_chart

Önceki Cevabım: Aşağıdakiler de işe yarar

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

Bu, aslında Frank'in bir buçuk yıl önceki cevabıyla aynı şey.
Dean MacGregor

1
Teşekkürler, Frank'in cevabı set kullanıyordu. Milyonlarca satır içeren büyük data.table'lar ile çalışırken şunu görüyorum: = operatör işlevlerden daha iyi performans gösteriyor
Orhan Celik

2
Eski bir soruya cevap eklememin sebebi şu: Ben de benzer bir sorun yaşadım, google arama ile bu yazı ile karşılaştım. Daha sonra sorunuma bir çözüm buldum ve bunun burada da geçerli olduğunu görüyorum. Aslında benim önerim, soru anında mevcut olmayan, kütüphanenin yeni sürümlerinde mevcut olan yeni bir data.table işlevi kullanıyor. Paylaşmanın iyi bir fikir olduğunu düşündüm, benzer sorunu olan başkalarının burada google arama ile sonuçlanacağını düşündüm.
Orhan Çelik

1
dt3 satırdan oluşan bir karşılaştırma mı yapıyorsunuz ?
Uwe

3
Hannes'ın cevabı farklı bir hesaplama yapıyor ve bu yüzden diğerleriyle karşılaştırılmamalı, değil mi?
Frank

2

Yukarıdaki çözümlerin hiçbiri grup hesaplamasında işe yaramıyor gibi görünüyor. Aldığım en iyisi şu:

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

1

Sütunların dize vektörünü temel alan yeni sütunlar oluşturmak için örnek eklemek. Jfly cevabına göre:

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
Fyi, başlıktaki "belirtilen her sütun", soruyu soranın bunu bir sütun alt kümesine (belki hepsine değil) uygulamakla ilgilendiği anlamına geliyordu.
Frank

1
@Frank tabi! Bu durumda OP, dt [, c ("a", "b")] * (- 1) gerçekleştirebilir.
amonk

1
dt[, cols] <- dt[, cols] * (-1)
Gregor Thomas

yeni gerekli sözdizimi dt [, cols] <- dt [, ..cols] * (-1)
Arthur Yip

0

dplyrişlevler data.tables üzerinde çalışır , bu yüzden burada dplyr"döngüden kaçınan" bir çözüm var :)

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

Ben orhan adlı kodunu (satır ve sütun ekleme) kullanarak benchmarked ve göreceksiniz dplyr::mutateile acrossçoğunlukla daha hızlı başka çözümler çoğundan daha ve daha yavaş lapply kullanarak data.table çözümü daha yürütür.

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

2020-10-16 tarihinde reprex paketi tarafından oluşturuldu (v0.3.0)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.