Виберіть / призначте data.table, коли імена змінних зберігаються у векторному символі


91

Як ви посилаєтесь на змінні в a, data.tableякщо імена змінних зберігаються у векторному символі? Наприклад, це працює для data.frame:

df <- data.frame(col1 = 1:3)
colname <- "col1"
df[colname] <- 4:6
df
#   col1
# 1    4
# 2    5
# 3    6

Як я можу виконати цю саму операцію для data.table, з :=позначенням або без нього ? Очевидна річ dt[ , list(colname)]не працює (і я не очікував).

Відповіді:


132

Два способи програмного вибору змінних:

  1. with = FALSE:

    DT = data.table(col1 = 1:3)
    colname = "col1"
    DT[, colname, with = FALSE] 
    #    col1
    # 1:    1
    # 2:    2
    # 3:    3
    
  2. ..префікс 'dot dot' ( ):

    DT[, ..colname]    
    #    col1
    # 1:    1
    # 2:    2
    # 3:    3
    

Для подальшого опису позначення "точка-крапка" ( ..) див. Нові функції в 1.10.2 (наразі це не описано в тексті довідки).

Щоб призначити змінну (и), оберніть LHS :=в дужках:

DT[, (colname) := 4:6]    
#    col1
# 1:    4
# 2:    5
# 3:    6

Останній відомий як plonk стовпця , оскільки ви замінюєте весь вектор стовпця посиланням. Якби підмножина iбула присутня, вона була б призначена за посиланням. Parens навколо (colname)- це скорочення, представлене у версії v1.9.4 від CRAN жовтня 2014 р. Ось новина :

Використання with = FALSEwith :=зараз застаріло у всіх випадках, враховуючи те, що обгортання LHS :=з дужками було кращим протягом певного часу.

colVar = "col1"
DT[, colVar := 1, with = FALSE]                 # deprecated, still works silently
DT[, (colVar) := 1]                             # please change to this
DT[, c("col1", "col2") := 1]                    # no change
DT[, 2:4 := 1]                                  # no change
DT[, c("col1","col2") := list(sum(a), mean(b)]  # no change
DT[, `:=`(...), by = ...]                       # no change

Див. Також розділ Деталі у ?`:=`:

DT[i, (colnamevector) := value]
# [...] The parens are enough to stop the LHS being a symbol

І щоб відповісти на подальше запитання в коментарі, ось один із способів (як зазвичай існує безліч способів):

DT[, colname := cumsum(get(colname)), with = FALSE]
#    col1
# 1:    4
# 2:    9
# 3:   15 

або, ви можете знайти його легше читати, писати і налагоджувати тільки до , подібне побудова динамічного оператора SQL для відправки на сервер:evalpaste

expr = paste0("DT[,",colname,":=cumsum(",colname,")]")
expr
# [1] "DT[,col1:=cumsum(col1)]"

eval(parse(text=expr))
#    col1
# 1:    4
# 2:   13
# 3:   28

Якщо ви робите це багато, ви можете визначити допоміжну функцію EVAL:

EVAL = function(...)eval(parse(text=paste0(...)),envir=parent.frame(2))

EVAL("DT[,",colname,":=cumsum(",colname,")]")
#    col1
# 1:    4
# 2:   17
# 3:   45

Тепер, коли data.table1.8.2 автоматично оптимізує jефективність, можливо, краще використовувати evalметод. Наприклад, get()in jзапобігає деякій оптимізації.

Або є set(). Низька накладна, функціональна форма :=, що тут було б непогано. Див ?set.

set(DT, j = colname, value = cumsum(DT[[colname]]))
DT
#    col1
# 1:    4
# 2:   21
# 3:   66

1
Дякую за відповідь Метью. With = FALSE, безумовно, вирішує частину моєї проблеми. Насправді, я хочу замінити стовпець на закінчення стовпця. Чи можу я якось посилатися на назву стовпця за змінною в правій частині призначення?
frankc

Насправді, я просто закріпив cumum зовні з іншою назвою, яка не існує всередині dt і яка чудово працює.
frankc

1
Але це була б ціла зайва лінія! Не дуже елегантно :) Але добре, іноді це корисно. У цих випадках найкраще починати ім’я змінної .або ..уникати будь-яких можливих маскувань, якщо DTколи-небудь містили цей символ як назву стовпця в майбутньому (і дотримуйтесь домовленості, з якої імена стовпців не починаються .). Є кілька запитів на функції, щоб зробити його більш надійним, щоб охопити такі проблеми, як додавання .()та ..().
Matt Dowle

Я відповів, перш ніж помітив, що ви відредагували свою відповідь. Моя перша думка була eval (синтаксичний розбір ()), але з якихось причин у мене були проблеми з тим, щоб вона працювала, коли мені спало на думку просто робити це зовні. Це чудова відповідь із багатьма речами, про які я не думав. Дякую за data.table загалом, це чудовий пакет.
frankc

2
Зверніть увагу , що ви можете використовувати квазі-Perl типу рядка інтерполяції fn$з gsubfn пакета , щоб поліпшити читаність рішення EVAL: library(gsubfn); fn$EVAL( "DT[,$colname:=cumsum($colname)]" ).
Г. Гротендік

8

* Це насправді не відповідь, але у мене недостатньо вуличних балів, щоб залишати коментарі: /

У будь-якому випадку, для кожного, хто може насправді створити новий стовпець у таблиці даних з іменем, що зберігається у змінній, я маю працювати наступне. Я не маю уявлення про його ефективність. Будь-які пропозиції щодо вдосконалення? Чи безпечно припустити, що безіменному новому стовпцю завжди буде присвоєно ім'я V1?

colname <- as.name("users")
# Google Analytics query is run with chosen metric and resulting data is assigned to DT
DT2 <- DT[, sum(eval(colname, .SD)), by = country]
setnames(DT2, "V1", as.character(colname))

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


+1 за те, що я просто працюю: я згоден, що це не повинно бути "способом" це зробити, але, витративши приблизно 45 хвилин, розливаючи кожну публікацію SO на цю тему, це єдине рішення, до якого я насправді зміг дійти робота - спасибі, що знайшли час на це вказати!
нейропсих

Радий, що міг допомогти! На жаль, я ніколи не знайшов більш елегантного рішення безпосередньо за допомогою data.tables, хоча цей 3 лайнер не страшний. У моєму сценарії я зрозумів, що більш простою альтернативою було б використання tidyr, щоб просто зробити мої дані "довгими", а не "широкими", оскільки на основі вводу користувача я завжди міг фільтрувати по одному стовпцю, а не вибирати з набору колонок.
efh0888

2
Не можна впевнено вважати, що V1це нова назва. Наприклад, якщо ви читаєте csv за допомогою freadі є безіменний стовпець, він матиме V1ім'я (і read.csvдасть X). Отже, можливо, у вашому столі вже є V1. Можливо, просто names(DT)[length(names(DT))]
дістаньте

2

Для кількох стовпців і функції, застосованої до значень стовпців.

Під час оновлення значень функції функція RHS повинна бути об’єктом списку, тому використання циклу на .SDwith lapplyзробить трюк.

У наведеному нижче прикладі цілі стовпці перетворюються на числові

a1 <- data.table(a=1:5, b=6:10, c1=letters[1:5])
sapply(a1, class)  # show classes of columns
#         a           b          c1 
# "integer"   "integer" "character" 

# column name character vector
nm <- c("a", "b")

# Convert columns a and b to numeric type
a1[, j = (nm) := lapply(.SD, as.numeric ), .SDcols = nm ]

sapply(a1, class)
#         a           b          c1 
# "numeric"   "numeric" "character" 

2

Отримати кілька стовпців з data.table через змінну або функцію:

library(data.table)

x <- data.table(this=1:2,that=1:2,whatever=1:2)

# === explicit call
x[, .(that, whatever)]
x[, c('that', 'whatever')]

# === indirect via  variable
# ... direct assignment
mycols <- c('that','whatever')
# ... same as result of a function call
mycols <- grep('a', colnames(x), value=TRUE)

x[, ..mycols]
x[, .SD, .SDcols=mycols]

# === direct 1-liner usage
x[, .SD, .SDcols=c('that','whatever')]
x[, .SD, .SDcols=grep('a', colnames(x), value=TRUE)]

які всі дають

   that whatever
1:    1        1
2:    2        2

Я вважаю .SDcolsшлях найелегантнішим.


1

Ви можете спробувати це

colname <- as.name ("COL_NAME")

DT2 <- DT [, список (COL_SUM = сума (eval (кол-ім'я, .SD))), від = c (група)]


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