Воспроизводимые вычисления в R. Как разделить код и данные?

от автора

Достаточно часто возникает потребность проведения периодических вычислений и подготовки консолидированного отчета по самодостаточным данным. Т.е. по данным, которые хранятся в виде файлов. Это могут быть данные, набранные из открытых источников, различные документы и excel таблицы, выгрузки из корпоративных систем. Данные в сыром виде могут занимать как несколько мегабайт, так и несколько гигабайт. Данные могут быть обезличенными, либо содержать конфиденциальную информацию. В том случае, когда код вычислений помещается в репозиторий, а работа ведется более чем одним человеком более чем на одном компьютере, возникает проблема сохранения консистентности кода и данных. При этом необходимо еще обеспечить соблюдение разных прав доступа к коду и данным.

Является продолжением предыдущих публикаций.

RStudio сейчас активно разрабатывают пакет pins для решения этой проблемы. К сожалению, применяемые бэкенд решения несколько непопулярны и дороговаты для применения на просторах нашей страны. AWS, Azure, Google cloud… за каждый чих надо платить, и за хранение и за трафик. Аутентификацию AWS4 pins пока не поддерживает, так что Yandex cloud пока тоже в стороне, хотя и он не бесплатен.

С другой стороны, команды аналитиков, работающих над конкретными задачами, как правило, невелики (не более 5-10 человек). Многие используют Google drive, One drive и пр., в платном или бесплатном формате. Почему бы не воспользоваться уже приобретенными ресурсами? Ниже предлагается один из возможных workflow.

Общий план

  1. Вычисления должны проводиться локально на машине, а значит на машине должна быть актуальная реплика всех необходимых для проведения вычислений данных.
  2. Код должен быть под системой контроля версий. Данные туда не должны никоим образом попадать (потенциальные объем и конфиденциальность). Будем хранить реплику данных либо в отдельной папке в проекте (включив ее в .gitignore), либо во внешней относительно проекта директории.
  3. Хранилищем мастер данных будет выступать google drive. Права на доступ к директориям развешиваем в нем же.

Осталось дело за малым. Необходимо реализовать функционал синхронизации локальной реплики данных с облаком. Авторизация и аутентификация обеспечивается google.

Ниже код.

library(googledrive) library(memoise) # синхронизация кэша с google disk drive_user() updateGdCache(here::here("data/"), cloud_folder = "XXX___jZnIW3jdkbdxK0iazx7t63Dc")

Функция для синхронизации кэша

updateGdCache <- function(local_folder, cloud_folder){   # обновляем весь кэш одним махом   cache_fname <- "gdrive_sig.Rds"   # 0. Делаем memoise на загрузку данных из гугла   getGdriveFolder <- memoise(function(gdrive_folder){     drive_ls(as_id(gdrive_folder), recursive = FALSE)   })    # 1. Проверяем наличие и загружаем облачные идентификаторы указанной папки   cloud_gdrive_sig <- purrr::possibly(getGdriveFolder, NULL)(cloud_folder)   # Если в облаке ничего нет или связи с ним нет, то и делать дальше нечего   if(is.null(cloud_gdrive_sig)) {     message("Some Google Drive issues happened. Can't update cache")     return()   }   # 2. Выцепляем директорию и имя файла из пути   fdir <- if(fs::is_dir(local_folder)) local_folder else fs::path_dir(local_folder)    # 3. Загружаем список локальных файлов на файловой системе   local_files <- fs::dir_ls(fdir, recurse = FALSE) %>%     fs::path_file()    # 4. Проверяем наличие и загружаем локальный кэш облачных идентификаторов   local_gdrive_sig <- purrr::possibly(readRDS, NULL, quiet = TRUE)(fs::path(fdir, cache_fname))   if(is.null(local_gdrive_sig)){     # Если локального кэша нет, то сверять нечего, просто загружаем все из облака     # сохраняем структуру, удаляем все данные     local_gdrive_sig <- cloud_gdrive_sig %>%       dplyr::filter(row_number() == -1)   }   # актуализируем сигнатуры локальных файлов с учетом реально существующих файлов   local_gdrive_sig <- local_gdrive_sig %>%     dplyr::filter(name %in% local_files)    # 5. Сверяем идентификаторы и времена последнего изменения, оставляем файл на обновление, если есть отличия   # Облако первично, по нему формируем список файлов для загрузки   reconcile_tbl <- cloud_gdrive_sig %>%     dplyr::rename(drive_resource_cloud = drive_resource) %>%     dplyr::left_join(local_gdrive_sig, by = c("name", "id")) %>%     tidyr::hoist(drive_resource_cloud, cloud_modified_time = "modifiedTime") %>%     tidyr::hoist(drive_resource, local_modified_time = "modifiedTime") %>%     # TODO: надо сверять время, а тут времена изменения выступают как строки     # для отсутствующих локальных файлов время модификации = NA     dplyr::mutate(not_in_sync = is.na(local_modified_time) | cloud_modified_time != local_modified_time)    # 6. Выгружаем файлы на диск   syncFile <- function(fpath, id){     res <- purrr::possibly(drive_download, otherwise = NULL)(as_id(id), path = fpath, overwrite = TRUE, verbose = TRUE)     ifelse(is.null(res), FALSE, TRUE)   }   # пакетная загрузка, для сброса сигнатур в кэш оставляем только те, что успешно загрузились   sync_gdrive_sig <- reconcile_tbl %>%     dplyr::filter(not_in_sync == TRUE) %>%     dplyr::mutate(fpath = fs::path(fdir, name)) %>%     dplyr::mutate(sync_status = purrr::map2_lgl(fpath, id, syncFile)) %>%     dplyr::select(name, id, sync_status)    # 7. Сбрасываем в локальный кэш только файлы, находящиеся в синхронизации   # Собираем все воедино   cloud_gdrive_sig %>%     # исключаем ошибочные файлы     dplyr::anti_join(dplyr::filter(sync_gdrive_sig, sync_status == FALSE), by = c("name", "id")) %>%     saveRDS(fs::path(fdir, cache_fname)) }

В качестве пути указываем идентификатор папки в google drive, можно взять его из адресной строки браузера. Идентификатор будет неизменным, даже если папка будет перемещаться в драйве.

Просто, компактно, удобно и бесплатно.

Пара замечаний

  1. Есть проблемы с кодировкой у gargle 0.4.0 версии. Надо грузить dev версию. Подробнее здесь.
  2. Есть проблемы с авторизацией на RStudio Server «Unable to authorize from RStudio Server #79 {Closed}», но идеи по обходному пути можно поглядеть здесь.

Предыдущая публикация — «Программирование и новогодняя елка, можно ли их совместить?».

ссылка на оригинал статьи https://habr.com/ru/post/487940/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *