Как программно создать двоичные столбцы на основе категориальной переменной в data.table?

У меня большой (12 миллионов строк)data.table который выглядит так:

library(data.table)
set.seed(123)
dt <- data.table(id=rep(1:3, each=5),y=sample(letters[1:5],15,replace = T))
> dt
    id y
 1:  1 b
 2:  1 d
 3:  1 c
 4:  1 e
 5:  1 e
 6:  2 a
 7:  2 c
 8:  2 e
 9:  2 c
10:  2 c
11:  3 e
12:  3 c
13:  3 d
14:  3 c
15:  3 a

Я хочу создать новыйdata.table содержащий мою переменнуюid (который будет уникальным ключом этого новогоdata.table) и 5 ​​других двоичных переменных, каждая из которых соответствует каждой категорииy которые ценят1 если идентификатор имеет это значение дляy, 0 иначе.
Выходdata.table должен выглядеть так:

   id a b c d e
1:  1 0 1 1 1 1
2:  2 1 0 1 0 1
3:  3 1 0 1 1 1

Я попытался сделать это в цикле, но это довольно медленно, и я также не знаю, как передать двоичные имена переменных программно, так как они зависят от переменной, которую я пытаюсь «разделить».

РЕДАКТИРОВАТЬ: как отметил @mtoto, на подобный вопрос уже задавали и отвечалиВот, но решение используетreshape2 пакет.
Мне было интересно, есть ли другой (более быстрый) способ сделать это, возможно, используя:= оператор в data.table, так как у меня огромный массив данных, и я довольно много работаю с этим пакетом.

EDIT2: бенчмарк функций в посте @ Аруна по моим данным (~ 12 миллионов строк, ~ 3,5 миллиона различных идентификаторов и 490 различных меток дляy переменная (в результате получается 490 фиктивных переменных)):

system.time(ans1 <- AnsFunction())   # 194s
system.time(ans2 <- dcastFunction()) # 55s
system.time(ans3 <- TableFunction()) # Takes forever and blocked my PC
 kpie10 июн. 2016 г., 09:25
Я заметил, что есть похожие строки, такие как четыре и пять, можете ли вы объяснить эти данные немного лучше? Как я понимаюdata[1][e]=1 if(2>0) else 0 но это просто кажется немного странным
 alexis_laz10 июн. 2016 г., 15:49
Вы также можете подумать о том, чтобы иметь 1/0 в «матрице», возможно, немного, чтобы иметь шанс сэкономить память -uy = unique(dt$y); m = matrix(0L, max(dt$id), length(uy), dimnames = list(NULL, uy)); m[cbind(dt$id, match(dt$y, uy))] = 1L
 hellter10 июн. 2016 г., 16:09
@alexis_laz Я попробую ваш подход, чтобы увидеть, как он складывается с другими, как только я получу доступ к той же машине, которую использовал для других тестов.
 mtoto10 июн. 2016 г., 09:56
Если вы хотите использоватьdata.tableВы могли бы пойти сdcast(): dcast(dt, id ~ y,fun.aggregate = function(x) (length(x) > 0)+0)
 hellter10 июн. 2016 г., 09:34
@kpie я редактировал второйdata.tableтеперь должно быть понятнее:id п.1 имеет различные значенияb,c,d,e заy, но нетa, Это объясняет, почему его ряд на второмdata.table имеет1 везде, кромеa колонка. @mtoto спасибо за ваш ответ, это решило бы мою проблему, но с такими массивными данными мне было интересно, есть ли другой способ сделать то же самое, но внутриdata.tableможет быть с:= оператор.

Ответы на вопрос(3)

фективной, но для больших наборов данных dcast представляется наиболее эффективным и удобным вариантом.

TableFunction <- function(){
    df <- as.data.frame.matrix(table(dt$id, dt$y))
    df[df > 1] <- 1
    df <- cbind(id = as.numeric(row.names(df)), df)
    setDT(df)
}


AnsFunction <- function(){
    ans = data.table(id = unique(dt$id))[, unique(dt$y) := 0L][]
    dt[, {set(ans, i=id, j=unique(y), value=1L); NULL}, by=id]
}

dcastFunction <- function(){
    df <-dcast.data.table(dt, id ~ y, fun.aggregate = function(x) 1L, fill=0L,value.var = "y")

}

library(data.table)
library(microbenchmark)
set.seed(123)
N = 10000
dt <- data.table(id=rep(1:N, each=5),y=sample(letters[1 : 5], N*5, replace = T)) 


microbenchmark(
    "dcast" = dcastFunction(),
    "Table" = TableFunction(),
    "Ans"   = AnsFunction()
    )


 Unit: milliseconds
  expr       min        lq      mean    median        uq       max neval cld
 dcast  42.48367  45.39793  47.56898  46.83755  49.33388  60.72327   100  b 
 Table  28.32704  28.74579  29.14043  29.00010  29.23320  35.16723   100 a  
   Ans 120.80609 123.95895 127.35880 126.85018 130.12491 156.53289   100   c
> all(test1 == test2)
[1] TRUE
> all(test1 == test3)
[1] TRUE
y = apply(matrix(sample(letters, 10L*20L, TRUE), ncol=20L), 1L, paste, collapse="")
dt = data.table(id=sample(1e5,1e7,TRUE), y=sample(y,1e7,TRUE))

microbenchmark(
    "dcast" = dcastFunction(),
    "Table" = TableFunction(),
    "Ans"   = AnsFunction()
)
Unit: seconds
  expr      min       lq     mean   median       uq      max neval cld
 dcast 1.985969 2.064964 2.189764 2.216138 2.266959 2.643231   100 a  
 Table 5.022388 5.403263 5.605012 5.580228 5.830414 6.318729   100   c
   Ans 2.234636 2.414224 2.586727 2.599156 2.645717 2.982311   100  b 
 alexis_laz10 июн. 2016 г., 15:51
Вместоtable + [<-.data.frameАль альтернативаuy = unique(dt$y); m = matrix(0L, max(dt$id), length(uy), dimnames = list(NULL, uy)); m[cbind(dt$id, match(dt$y, uy))] = 1L
 Arun10 июн. 2016 г., 11:47
Я добавил тест на большие данные в свой пост. Я не уверен, что вы используете data.table dcast или reshape2, так как вы используетеsetDT(), который не будет необходим, если вы используете data.table's. И Reshape2 :: Dcast являетсямедленный.
Решение Вопроса

Таблица данных имеет свойdcast реализация с использованием внутренних данных data.table и должна быть быстрой. Попробуйте это:

#    id a b c d e
# 1:  1 0 1 1 1 1
# 2:  2 1 0 1 0 1
# 3:  3 1 0 1 1 1

Просто подумал о другом способе обработки этого путем предварительного распределения и обновления по ссылке (возможно, логика dcast должна быть сделана таким образом, чтобы избежать промежуточных звеньев).

ans = data.table(id = unique(dt$id))[, unique(dt$y) := 0L][]

Осталось только заполнить существующие комбинации1L.

dt[, {set(ans, i=.GRP, j=unique(y), value=1L); NULL}, by=id]
ans
#    id b d c e a
# 1:  1 1 1 1 1 0
# 2:  2 0 0 1 1 1
# 3:  3 0 1 1 1 1

Хорошо, я продолжил тестирование параметров данных OP с ~ 10 миллионами строк и 10 столбцами.

require(data.table)
set.seed(45L)
y = apply(matrix(sample(letters, 10L*20L, TRUE), ncol=20L), 1L, paste, collapse="")
dt = data.table(id=sample(1e5,1e7,TRUE), y=sample(y,1e7,TRUE))

system.time(ans1 <- AnsFunction())   # 2.3s
system.time(ans2 <- dcastFunction()) # 2.2s
system.time(ans3 <- TableFunction()) # 6.2s

setcolorder(ans1, names(ans2))
setcolorder(ans3, names(ans2))
setorder(ans1, id)
setkey(ans2, NULL)
setorder(ans3, id)

identical(ans1, ans2) # TRUE
identical(ans1, ans3) # TRUE

где,

AnsFunction <- function() {
    ans = data.table(id = unique(dt$id))[, unique(dt$y) := 0L][]
    dt[, {set(ans, i=.GRP, j=unique(y), value=1L); NULL}, by=id]
    ans
    # reorder columns outside
}

dcastFunction <- function() {
    # no need to load reshape2. data.table has its own dcast as well
    # no need for setDT
    df <- dcast(dt, id ~ y, fun.aggregate = function(x) 1L, fill=0L,value.var = "y")
}

TableFunction <- function() {
    # need to return integer results for identical results
    # fixed 1 -> 1L; as.numeric -> as.integer
    df <- as.data.frame.matrix(table(dt$id, dt$y))
    df[df > 1L] <- 1L
    df <- cbind(id = as.integer(row.names(df)), df)
    setDT(df)
}
 hellter10 июн. 2016 г., 10:57
@Tobias Dekker только что дал оценку в своем ответе
 Arun10 июн. 2016 г., 10:02
смотреть наans..
 hellter10 июн. 2016 г., 12:34
Это совсем не проблема, я просто не мог сделать это раньше, и я подумал, что тест @Tobias достаточно. Я просто добавил тест в вопросе.
 Tobias Dekker10 июн. 2016 г., 13:08
Ммм, ладно, я должен признать, что dcast - лучший вариант для больших наборов данных. По некоторым причинам он лучше масштабируется для больших наборов данных. Я добавил в свой пост тест по большему набору. Не уверен, что можно улучшить табличную функцию.
 Arun10 июн. 2016 г., 10:05
@helter, не могли бы вы отредактировать свой Q, чтобы показать эталон времени выполнения между двумя методами, опубликованными выше, в ваших исходных данных?
 Arun10 июн. 2016 г., 12:44
Круто, спасибо. Я планирую работать над улучшениемdcast для следующего выпуска. Определенно помогает в знании, как не идти об улучшенииdcast().
 hellter10 июн. 2016 г., 12:52
Я думаю, что самая медленная часть вTableFunction являетсяtable(dt$id, dt$y), На самом деле, работая над этим набором данных, я заметил, что, в общем,table() являетсяочень медленно, может потому что у меня так многоids. По этой причине в целом я склонен использоватьdata.table«s.N оператор вj аргумент при подмножествеby=id, Может быть, изменить этот бит внутриTableFunction улучшит производительность (?), но я не вижу, как получить такой же вывод из первой строкиTableFunction безtable()
 hellter10 июн. 2016 г., 10:02
Ваш подход выглядит именно так, как я искал. Я понимаю, но когда я запускаю код вашего второго подхода наdt это не работает, и я получаюEmpty data.table (0 rows) of 1 col: id
 Arun10 июн. 2016 г., 11:48
@hellter, меня особенно интересовали данныеваш размеры. Это так же просто, как обернуть обе функции, которые я предоставилsystem.time(), Не уверен, почему это проблема. Во всяком случае, я пошел дальше и добавил эталонный тест данных, близких к вашим измерениям.

что в вашем примере не более 3 строк) и знаете столбцы, вы можете начать с массива нулей и использовать функцию apply для обновления значений в этом вторичном Таблица.

Мой R немного ржавчина, но я думаю, что это должно сработать. Кроме того, функция, которую вы передаете методу apply, может содержать условия для добавления необходимых строк и столбцов по мере необходимости.

Мой R немного ржавеет, поэтому я сейчас немного постараюсь написать это, но я думаю, что это способ сделать это.

Если вы ищете что-то немного больше подключи и играй, я нашел эту маленькую болтовню:

There are two sets of methods that are explained below:

gather() and spread() from the tidyr package. This is a newer interface to the reshape2 package.

melt() and dcast() from the reshape2 package.

There are a number of other methods which aren’t covered here, since they are not as easy to use:

The reshape() function, which is confusingly not part of the reshape2 package; it is part of the base install of R.

stack() and unstack()

отсюда ::http://www.cookbook-r.com/Manipulating_data/Converting_data_between_wide_and_long_format/

Если бы я лучше разбирался в R, я бы рассказал вам, как эти различные методы обрабатывают коллизии, переходя от длинных списков к широким. Я гуглил «Составь таблицу из плоских данных в R», чтобы придумать это ...

Также проверитьэтот Это тот же сайт, что и выше, с моей личной упаковкой комментариев: p

Ваш ответ на вопрос