Оптимизация кода R: для цикла и записи в базу данных

Я пытаюсь оптимизировать написанный мною простой код R по двум аспектам:

1) Для циклов

2) Запись данных в мою базу данных PostgreSQL

Для 1) я знаю, что циклов for следует избегать любой ценой, и рекомендуется использовать lapply, но я не понимаю, как перевести приведенный ниже код с помощью lapply.

Для 2) то, что я делаю ниже, работает, но я не уверен, что это самый эффективный способ (например, делать это вместо rbinding всех данных в фрейм данных R, а затем загрузить весь фрейм данных в мою базу данных PostgreSQL.)

РЕДАКТИРОВАТЬ: я обновил свой код воспроизводимым примером ниже.

for (i in 1:100){

   search <- paste0("https://github.com/search?o=desc&p=", i, &q=R&type=Repositories)

   download.file(search, destfile ='scrape.html',quiet = TRUE)

   url <- read_html('scrape.html')

   github_title <- url%>%html_nodes(xpath="//div[@class=mt-n1]")%>%html_text()

   github_link <- url%>%html_nodes(xpath="//div[@class=mt-n1]//@href")%>%html_text()

   df <- data.frame(github_title, github_link )

   colnames(df) <- c("title", "link")

   dbWriteTable(con, "my_database", df, append = TRUE, row.names = FALSE)

   cat(i)
}

Большое спасибо за все ваши материалы!


person ML_Enthousiast    schedule 05.11.2019    source источник
comment
Ах! Миф продолжается: я знаю, что циклов for следует избегать любой ценой, и рекомендуется использовать циклы lapply.   -  person Parfait    schedule 05.11.2019
comment
до сих пор цикл for был визуально наиболее полным для меня. Дело в том, что мне нужно сделать регулярное выражение для каждого из элементов (a, b, c, d), поэтому я не знаю, как выполнять эти операции, например, в lapply.   -  person ML_Enthousiast    schedule 05.11.2019
comment
Спасибо за ваш отзыв, я отредактировал код, я пытаюсь продвинуться в R, поэтому пытаюсь просмотреть множество репозиториев github.   -  person ML_Enthousiast    schedule 06.11.2019


Ответы (1)


Прежде всего, следует полностью развеять миф о том, что lapply в любом случае быстрее, чем эквивалентный код, использующий for loop. В течение многих лет это было исправлено, и for loops в любом случае должен быть быстрее, чем эквивалентный lapply.

Я буду визуализировать использование for loop, так как вам кажется, что это более интуитивно понятно. Однако обратите внимание, что я работаю в основном в T-sql, и может потребоваться некоторое преобразование.

n <- 1e5
outputDat <- vector('list', n)
for (i in 1:10000){
  id <- element_a[i]
  location <- element_b[i]
  language <- element_c[i]
  date_creation <- element_d[i]
  df <- data.frame(id, location, language, date_creation)
  colnames(df) <- c("id", "location", "language", "date_creation")
  outputDat[[i]] <- df
}
## Combine data.frames
outputDat <- do.call('rbind', outputDat)
#Write the combined data.frame into the database.
##dbBegin(con)   #<= might speed up might not.
dbWriteTable(con, "my_database", df, append = TRUE, row.names = FALSE)
##dbCommit(con)  #<= might speed up might not.

Используя Transact-SQL, вы также можете объединить всю строку в один оператор insert into. Здесь я отклонюсь и буду использовать apply для перебора строк, так как в этом случае это гораздо более читабельно. Цикл for снова работает так же быстро, если все сделано правильно.

#Create the statements. here 
statement <- paste0("('", apply(outputDat, 1, paste0, collapse = "','"), "')", collapse = ",\n") #\n can be removed, but makes printing nicer.
##Optional: Print a bit of the statement
# cat(substr(statement, 1, 2000))

##dbBegin(con)   #<= might speed up might not.
dbExecute(con, statement <- paste0(
'
/*
  SET NOCOCUNT ON seems to be necessary in the DBI API.
  It seems to react to 'n rows affected' messages. 
  Note only affects this method, not the one using dbWriteTable
*/
--SET NOCOUNT ON 
INSERT INTO [my table] values ', statement))
##dbCommit(con)   #<= might speed up might not.

Обратите внимание, как я комментирую, это может просто привести к неправильной загрузке таблицы, поскольку пакет DBI, кажется, иногда не может выполнить транзакцию такого типа, если это приводит к одному или нескольким сообщениям о n rows affected.

И последнее, но не менее важное: после того, как операторы сделаны, их можно скопировать и вставить из R в любой графический интерфейс, который напрямую обращается к базе данных, используя, например, writeLines(statement, 'clipboard') или записывая в текстовый файл (файл более стабилен, если ваши данные содержат много данных). ряды). В редких исключительных случаях это последнее средство может быть быстрее, если по какой-либо причине DBI или альтернативные R пакеты работают слишком медленно без причины. Поскольку это похоже на личный проект, этого может быть достаточно для вашего использования.

person Oliver    schedule 05.11.2019
comment
Большое спасибо, Оливер! Итак, из вашего примера я понимаю, что не рекомендуется добавлять новые строки в базу данных в каждом цикле? В этом случае, если мы сохраним outputDat в среде R и если этот список станет очень тяжелым (например, много гигабайт), у меня могут возникнуть проблемы, нет? - person ML_Enthousiast; 06.11.2019
comment
1) Действительно, вы правы. Причина в том, что R копирует data.frame каждый раз, когда вы используете rbind, поэтому, пока работает rbind, в памяти будет 3 data.frames. outputDat копию outputDat и data.frame, которые вы расширяете до outputDat (и это лучший случай). - person Oliver; 06.11.2019
comment
2) Вы можете столкнуться с проблемами памяти, если ваш data.frame больше, чем может обработать ваш компьютер. Один из способов обойти это — отправлять и очищать список data.frames с интервалами (скажем, 500? 1000? Зависит от размера). Скорее всего, вы можете обойти это, используя data.table вместо data.frame, что в большинстве случаев позволяет избежать копирования объектов. Я бы посоветовал сначала попробовать отметить, есть ли проблемы с памятью, а затем найти решение. - person Oliver; 06.11.2019
comment
Спасибо, это очень ясно. Просто чтобы убедиться, что я вижу все аспекты, в чем основной недостаток выполнения dbWriteTableнепосредственно в цикле for? - person ML_Enthousiast; 06.11.2019
comment
Конечно, рад, что смог помочь. Не углубляясь в исходный код, было бы 2 очевидных недостатка. Главный недостаток — накладные расходы. Каждый раз, когда вы вызываете dbWriteTable, ему придется вызывать внешние процессы для доступа к соединению с базой данных. Эти накладные расходы двойственны: сначала будут накладные расходы, поскольку любая функция, содержащаяся в dbWriteTable, должна обрабатываться непосредственно в R. Во-вторых, он, вероятно, отправляет заявление, подобное моему последнему примеру. В большинстве баз данных SQL механизм оптимизирован для выполнения этой задачи, но при каждом выполнении будут присутствовать небольшие накладные расходы. - person Oliver; 06.11.2019
comment
Скорее всего, накладные расходы, связанные с обработкой данных для правильного размещения в базе данных SQL, являются самыми большими накладными расходами, и это, вероятно, относится к отделу R (или C). - person Oliver; 06.11.2019
comment
Удивительно. Благодаря тонну ! - person ML_Enthousiast; 06.11.2019
comment
@ML_Enthousiast... не забывайте способ StackOverflow сказать спасибо! - person Parfait; 06.11.2019
comment
мой плохой ... только что сделал это! - person ML_Enthousiast; 03.02.2020