Данные бина внутри группы с использованием перерывов из другого DF - PullRequest
3 голосов
/ 14 июня 2019

Как избежать использования цикла for в следующем коде для ускорения вычислений (реальные данные примерно в 1e6 раз больше)

id = rep(1:5, 20)
v = 1:100
df = data.frame(groupid = id, value = v)
df = dplyr::arrange(df, groupid)

bkt = rep(seq(0, 100, length.out = 4), 5)
id = rep(1:5, each = 4)
bktpts = data.frame(groupid = id, value = bkt)

for (i in 1:5) {
  df[df$groupid == i, "bin"] = cut(df[df$groupid == i, "value"],
                                   bktpts[bktpts$groupid == i, "value"],
                                   include.lowest = TRUE, labels = F)
}

Ответы [ 3 ]

2 голосов
/ 14 июня 2019

Другой способ:

library(data.table)
setDT(df); setDT(bktpts)

bktpts[, b := rowid(groupid) - 1L]
df[, b := bktpts[copy(.SD), on=.(groupid, value), roll = -Inf, x.b]]

# check result
df[, any(b != bin)]
# [1] FALSE

См. ?data.table, как работают подвижные соединения.

2 голосов
/ 14 июня 2019

Я не уверен, почему yout bktpts отформатирован так, как есть?

Но вот решение для data.table, которое должно быть (хотя бы немного) быстрее, чем ваш цикл for.

library( data.table )

setDT(df)[ setDT(bktpts)[, `:=`( id = seq_len(.N),
                                 value_next = shift( value, type = "lead", fill = 99999999 ) ),
                         by = .(groupid) ],
           bin := i.id,
           on = .( groupid, value >= value, value < value_next ) ][]
1 голос
/ 14 июня 2019

Я вышел с другим data.table ответом:

library(data.table) # load package

# set to data.table
setDT(df) 
setDT(bktpts)

# Make a join
df[bktpts[, list(.(value)), by = groupid], bks := V1, on = "groupid"]

# define the bins:
df[, bin := cut(value, bks[[1]], include.lowest = TRUE, labels = FALSE), by = groupid]

# remove the unneeded bks column
df[, bks := NULL]

Объясняя код:

bktpts[, list(.(value)), by = groupid] - это новая таблица с в списке со значениями value для каждого groupid. Если вы запустите его в одиночку, вы поймете, куда мы идем.

bks := V1 присваивает переменной bks в df все, что существует в V1, что является именем столбца списка в предыдущей таблице. Конечно, on = "groupid" - это переменная, по которой мы производим соединение.

Код, определяющий ячейки, не требует особых пояснений, кроме бита bks[[1]]. Это должно быть [[, чтобы получить доступ к значениям списка и предоставить вектор, как требуется функцией cut.

ИЗМЕНИТЬ В ДОБАВИТЬ:

Все команды data.table могут быть объединены в один неразборчивый вызов:

df[bktpts[, list(.(value)), by = groupid], 
   bks := V1, 
   on = "groupid"][, 
                bin := cut(value, 
                           bks[[1]], 
                           include.lowest = TRUE, 
                           labels = FALSE), 
                by = groupid][, 
                             bks := NULL]
...