2013-06-05 16 views
25

Próbuję wymyślić elegancki sposób użycia przypisania :=, aby zastąpić wiele kolumn jednocześnie w data.table przez zastosowanie funkcji współdzielonej. Typowym zastosowaniem tego może być zastosowanie funkcji łańcuchowej (np. gsub) do wszystkich kolumn znaków w tabeli. Nie jest trudno rozszerzyć ten sposób na data.table, ale szukam metody zgodnej z metodą robienia rzeczy.Eleganckie przypisywanie wielu kolumn w data.table przy pomocy lapply()

Na przykład:

library(data.table) 

m <- matrix(runif(10000), nrow = 100) 
df <- df1 <- df2 <- df3 <- as.data.frame(m) 
dt <- as.data.table(df) 
head(names(df)) 
head(names(dt)) 

## replace V20-V100 with sqrt 

# data.frame approach 
# by column numbers 
df1[20:100] <- lapply(df1[20:100], sqrt) 
# by reference to column numbers 
v <- 20:100 
df2[v] <- lapply(df2[v], sqrt) 
# by reference to column names 
n <- paste0("V", 20:100) 
df3[n] <- lapply(df3[n], sqrt) 

# data.table approach 
# by reference to column names 
n <- paste0("V", 20:100) 
dt[, n] <- lapply(dt[, n, with = FALSE], sqrt) 

Rozumiem, że jest bardziej wydajny do pętli nad wektorem nazw kolumn z wykorzystaniem := przypisać:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE] 

nie podoba mi się to, bo don” tak jak odniesienie do data.table w wyrażeniu . Wiem też, że mogę używać := przypisać z lapply zważywszy, że znam nazwy kolumn: (. Można przedłużyć ten przez budowanie wypowiedzi z nieznanych nazw kolumn)

dt[, c("V20", "V30", "V40", "V50", "V60") := lapply(list(V20, V30, V40, V50, V60), sqrt)] 

Poniżej przedstawiamy pomysły Próbowałem tego, ale nie byłem w stanie ich zmusić do pracy. Czy popełniam błąd, czy istnieje inne podejście, którego mi brakuje?

# possible data.table approaches? 
# by reference to column names; assignment works, but not lapply 
n <- paste0("V", 20:100) 
dt[, n := lapply(n, sqrt), with = FALSE] 
# by (smaller for example) list; lapply works, but not assignment 
dt[, list(list(V20, V30, V40, V50, V60)) := lapply(list(V20, V30, V40, V50, V60), sqrt)] 
# by reference to list; neither assignment nor lapply work 
l <- parse(text = paste("list(", paste(paste0("V", 20:100), collapse = ", "), ")")) 
dt[, eval(l) := lapply(eval(l), sqrt)] 

Odpowiedz

30

Tak, masz rację tu pytanie:

Rozumiem, że lepiej jest zapętlić wektor nazw kolumn przy użyciu :=, aby przypisać:

for (col in paste0("V", 20:100)) dt[, col := sqrt(dt[[col]]), with = FALSE]

marginesie: zauważ, że nowy sposób robienia czyli:

for (col in paste0("V", 20:100)) 
    dt[ , (col) := sqrt(dt[[col]])] 

ponieważ with = FALSE nie był łatwy do odczytania, czy mowa LHS lub RHS z :=. Odsuń się.

Jak wiadomo, jest to wydajne, ponieważ powoduje to, że każda kolumna jest jedna po drugiej, więc pamięć robocza jest potrzebna tylko dla jednej kolumny na raz.To może zrobić różnicę między działaniem a niepowodzeniem z przerażającym brakem pamięci.

Problem z lapply na RHS z := polega na tym, że RHS (lapply) jest oceniany jako pierwszy; tj. tworzony jest wynik dla 80 kolumn. To jest nowa kolumna o wartości 80 kolumn, która musi zostać przydzielona i zapełniona. Potrzebujesz więc 80 wolnej pamięci RAM, aby operacja zakończyła się sukcesem. To użycie pamięci RAM dominuje w porównaniu z kolejną natychmiastową operacją przypisania (plonking) tych 80 nowych kolumn do szczelin wskaźników tablicy danych.

Jak zauważył @Frank, jeśli masz dużo kolumn (powiedzmy 10 000 lub więcej), wtedy mały narzut wysyłania do metody [.data.table zaczyna się sumować). Aby wyeliminować ten narzut, że istnieje data.table::set, który pod ?set jest opisany jako "loopable" :=. Używam pętli for dla tego typu operacji. Jest to najszybszy sposób i dość łatwo jest pisać i czytać.

for (col in paste0("V", 20:100)) 
    set(dt, j = col, value = sqrt(dt[[col]])) 

Mimo że z zaledwie 80 kolumnami jest mało prawdopodobne, aby to miało znaczenie. (Zauważ, że może być bardziej powszechna pętla set w dużej liczbie wierszy niż duża liczba kolumn.) Jednak zapętlona set nie rozwiązuje problemu powtarzającego się odniesienia do nazwy symbolu dt, o której wspomniałeś w pytaniu:

Nie podoba mi się to, ponieważ nie podoba mi się odniesienie do data.table w wyrażeniu aj.

Uzgodnione. Najlepsze, co mogę zrobić, to wrócić do pętli :=, ale zamiast tego użyj get.

for (col in paste0("V", 20:100)) 
    dt[, (col) := sqrt(get(col))] 

Jednak obawiam się, że za pomocą get w j może być nieefektywne. Konieczne jest wykonanie analizy porównawczej, #1380. Być może jest to mylące w używaniu get() na RHS, ale nie na LHS. Aby sprostać że mogliśmy cukier LHS i pozwalają get() jak również, #1381:

for (col in paste0("V", 20:100)) 
    dt[, get(col) := sqrt(get(col))] 

również może value z set mogą być uruchamiane w zakresie DT, #1382.

for (col in paste0("V", 20:100)) 
    set(dt, j = col, value = sqrt(get(col)) 
+1

Dziękuję bardzo za umieszczenie nawiasów wokół 'col'. Dopóki nie przypomniałem sobie tej sztuczki, dostałem kolumnę o nazwie "col". – Farrel

7

Czy tego właśnie szukasz?

dt[ , names(dt)[20:100] :=lapply(.SD, function(x) sqrt(x)) , .SDcols=20:100] 

Słyszałem powiedzieć, że za pomocą .SD nie jest tak skuteczny, ponieważ tworzy kopię tabeli wcześniej, ale jeśli tabela nie jest ogromny (oczywiście to jest względna w zależności od specyfikacji systemowych) Wątpię to wiele zmieni.

+4

Powiedziano mi, że [ 'set' może również przyspieszyć operacje tak] (http://stackoverflow.com/questions/16846380/how-to-apply-same-function-to-every -przypisana-kolumna-w-tabeli-danych/16846530 # 16846530). – Frank

+0

@Frank +1 dla tej odpowiedzi, a ja mam zaznaczone książki do wykorzystania w przyszłości. Nie sądzę, żebym użył tutaj pętli 'for'. Sprytny. –

+0

@Frank, nie wiedziałem o podejściu 'for' loop +' set'. Będę musiał pomyśleć o użyciu tego w przyszłości. –

13

Powinny działać, jeśli chcesz odnieść się do kolumn o nazwie ciąg:

n = paste0("V", 20:100) 
dt[, (n) := lapply(n, function(x) {sqrt(get(x))})] 

lub

dt[, (n) := lapply(n, function(x) {sqrt(dt[[x]])})] 
+2

Dodatkowy) jest potrzebny w ostatnim wierszu: 'dt [, (n): = lapply (n, function (x) {sqrt (dt [[x]])})] ' – HywelMJ

+0

@HywelMJ thanks, naprawiony – eddi

+0

Może dodać również opcje' .SDcols', takie jak 'dt [, (n): = lapply (.SD, sqrt), .SDcols = n] '? Hmm ..po namyśle, że Simon już wcześniej zrobił coś podobnego. –

Powiązane problemy