Разбить строковый столбец на несколько фиктивных переменных

Как относительно неопытный пользователь пакета data.table в R, я пытался обработать один текстовый столбец в большое количество столбцов-индикаторов (фиктивных переменных), где 1 в каждом столбце указывает на то, что определенная подстрока была находится внутри строкового столбца. Например, я хочу обработать это:

ID     String  
1       a$b  
2       b$c  
3       c  

в это:

ID     String     a     b     c  
1       a$b       1     1     0  
2       b$c       0     1     1  
3        c        0     0     1  

Я понял, как выполнять обработку, но она занимает больше времени, чем хотелось бы, и я подозреваю, что мой код неэффективен. Воспроизводимая версия моего кода с фиктивными данными приведена ниже. Обратите внимание, что в реальных данных существует более 2000 подстрок для поиска, каждая подстрока имеет длину около 30 символов и может содержать до нескольких миллионов строк. При необходимости я могу распараллелить и использовать много ресурсов для решения этой проблемы, но я хочу максимально оптимизировать код. Я попытался запустить Rprof, который не предложил никаких очевидных (для меня) улучшений.

set.seed(10)  
elements_list <- c(outer(letters, letters, FUN = paste, sep = ""))  
random_string <- function(min_length, max_length, separator) {  
    selection <- paste(sample(elements_list, ceiling(runif(1, min_length, max_length))), collapse = separator)  
    return(selection)  
}  
dt <- data.table(id = c(1:1000), messy_string = "")  
dt[ , messy_string := random_string(2, 5, "$"), by = id]  
create_indicators <- function(search_list, searched_string) {  
    y <- rep(0, length(search_list))  
    for(j in 1:length(search_list)) {  
        x <- regexpr(search_list[j], searched_string)  
        x <- x[1]  
        y[j] <- ifelse(x > 0, 1, 0)  
    }  
    return(y)  
}  
timer <- proc.time()  
indicators <- matrix(0, nrow = nrow(dt), ncol = length(elements_list))  
for(n in 1:nrow(dt)) {  
    indicators[n, ] <- dt[n, create_indicators(elements_list, messy_string)]  
}  
indicators <- data.table(indicators)  
setnames(indicators, elements_list)  
dt <- cbind(dt, indicators)  
proc.time() - timer  

user  system elapsed 
13.17    0.08   13.29 

РЕДАКТИРОВАТЬ

Спасибо за отличные ответы - все намного лучше моего метода. Ниже приведены результаты некоторых тестов скорости, с небольшими изменениями для каждой функции для использования 0L и 1L в моем собственном коде, для сохранения результатов в отдельных таблицах по методам и для стандартизации порядка. Это истекшее время из односкоростных тестов (а не медиан из многих тестов), но каждый больший прогон занимает много времени.

Number of rows in dt     2K      10K      50K     250K      1M   
OP                       28.6    149.2    717.0   
eddi                     5.1     24.6     144.8   1950.3  
RS                       1.8     6.7      29.7    171.9     702.5  
Original GT              1.4     7.4      57.5    809.4   
Modified GT              0.7     3.9      18.1    115.2     473.9  
GT4                      0.1     0.4      2.26    16.9      86.9

Совершенно очевидно, что модифицированная версия подхода GeekTrader является лучшей. Я все еще немного расплывчат в том, что делает каждый шаг, но я могу пройти через это на досуге. Хотя это немного выходит за рамки первоначального вопроса, но если кто-то захочет объяснить, что методы GeekTrader и Ricardo Saporta делают более эффективно, это будет оценено как мной, так и, вероятно, любым, кто посетит эту страницу в будущем. Мне особенно интересно понять, почему некоторые методы масштабируются лучше, чем другие.

***** РЕДАКТИРОВАТЬ № 2 *****

Я попытался отредактировать ответ GeekTrader с помощью этого комментария, но, похоже, это не сработало. Я сделал две очень незначительные модификации функции GT3, чтобы: a) упорядочить столбцы, что добавляет небольшое количество времени, и b) заменить 0 и 1 на 0L и 1L, что немного ускоряет процесс. Вызовите результирующую функцию GT4. Таблица выше отредактирована для добавления времени для GT4 при разных размерах таблицы. Ясно, что победитель на милю, и у него есть дополнительное преимущество, потому что он интуитивно понятен.

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

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