Конструктивные элементы надежного enterprise R приложения

в 5:50, , рубрики: data mining, data science, R, Промышленное программирование

Тем, кто работает с R, хорошо известно, что изначально язык разрабатывался как инструмент для интерактивной работы. Естественно, что методы удобные для консольного пошагового применения человеком, который глубоко в теме, оказываются малопригодными для создания приложения для конечного пользователя. Возможность получить развернутую диагностику сразу по факту ошибки, проглядеть все переменные и трейсы, выполнить вручную элементы кода (возможно, частично изменив переменные) — все это будет недоступно при автономной работе R приложения в enterprise среде. (говорим R, подразумеваем, в основном, Shiny web приложения).

Однако, не все так плохо. Среда R (пакеты и подходы) настолько сильно эволюционировали, что ряд весьма нехитрых трюков позволяет элегантно решать задачу обеспечения стабильности и надежности работы пользовательских приложений. Ряд из них будет описан ниже.

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

В чем сложность задачи?

Основной спектр задач для которых часто применяется R — разнообразная обработка данных. И даже полностью отлаженный алгоритм, обложеные со всех сторон тестами и полностью задокументированный может легко сломаться и выдать ерунду, если ему на вход подсунут кривые данные.

Данные могут поступать на вход как от других информационных систем, так и от пользователей. И, если в первом случае можно требовать соблюдения API и накладывать весьма жесткие ограничения на стабильность информационного потока, то во втором случае от сюрпризов никуда не деться. Человек может ошибиться и подсунуть не тот файл, написать в него не то. 99% пользователей используют в своей работе Excel и предпочитают подсовывать системе именно его, много страничный, с хитрым форматированием. В этом случае задача еще больше усложняется. Даже визуально валидный документ может выглядеть с точки зрения машины полной ерундой. Даты разъезжаются (весьма известная история «Excel’s designer thought 1900 was a leap year, but it was not»). Числовые значения хранятся как текст и наборот. Невидимые ячейки и скрытые формулы… И многое другое. Предусмотреть все возможные грабли в принципе не получится — фантазии не хватит. Чего стоит только задвоение записей в различных join-ах с кривыми источниками.

В качестве дополнительных соображенией примем следующие:

  1. Прекрасный документ «An introduction to data cleaning with R», описывающий процесс предварительной подготовки данных. Для дальнейших шагов из него мы выделим наличие двух фаз валидации: техническая и логическая.

    • Техническая валидация заключается в проверке корректности источника данных. Структура, типы, количественные показатели.
    • Логическая валидация может быть многоэтапной, осуществляемой по ходу проведения расчетов, и заключается в проверке соответствия тех или иных элементов данных или их комбинаций различным логическим требованиям.

  2. Одно из базовых правил при разработке пользовательских интерфейсов — формирование максимально полной диагностики в случае ошибок пользователей. Т.е., если уж пользователь загрузил файл, то надо его максимально проверить на корректность и выдать полную сводку со всеми ошибками (желательно еще и объяснить, что где не так), а не падать при первой же проблеме с сообщением вида «Incorrect input value @ line 528493, pos 17» и требовать загрузки нового файла с исправленной этой ошибкой. Такой подход позволяет многократно сократить количество итераций по формированию правильного источника и повысить качество конечного результата.

Технологии и методы валидации

Пойдем с конца. Для логической валидации существует ряд пакетов. В нашей практике мы остановились на следующих подходах.

  1. Уже классический dplyr. В простых случаях бывает удобно просто нарисовать pipe c проведением ряда проверок и анализом конечного результата.
  2. Пакет validate для проверки технически корректных объектов на соответствие заданным правилам.

Для технической валидации остановились на следующих подходах:

  1. Пакет checkmate с широким спектром быстрых функций для проведения разнобразных технических проверок.
  2. Явная работа с исключениями «Advanced R. Debugging, condition handling, and defensive programming», «Advanced R. Beyond Exception Handling: Conditions and Restarts» как для проведения полного объема валидации за один шаг, так и для обеспечения стабильности работы приложения.
  3. Использование purr обертки для исключений. Весьма полезно при применении внутри pipe.

В коде, разбитом на функции, важным элементом «defensive programming» является проверка входных и выходных параметров функций. В случае языков с динамической типизацией проверку типов приходится делать самостоятельно. Для базовых типов идеально подходит пакет checkmate, особенно его функции qtestqassert. Для проверки data.frame остановились на примерно следующей конструкции (проверка имен и типов). Трюк со слиянием имени и типа позволяет сократить количество строк в проверке.

ff <- function(dataframe1, dataframe2){
  # достали имя текущей функции для задач логирования
  calledFun <- deparse(as.list(sys.call())[[1]])
  tic("Calculating XYZ")

  # проверяем содержимое всех входных дата фреймов (class, а не typeof, чтобы Date отловить)
  list(dataframe1=c("name :: character", "val :: numeric", "ship_date :: Date"),
       dataframe2=c("out :: character", "label :: character")) %>%
    purrr::iwalk(~{
      flog.info(glue::glue("Function {calledFun}: checking '{.y}' parameter with expected structure '{collapse(.x, sep=', ')}'"))
      rlang::eval_bare(rlang::sym(.y)) %>%
        assertDataFrame(min.rows=1, min.cols=length(.x)) %>%
        {assertSetEqual(.x, stri_join(names(.), map_chr(., class), sep=" :: "), .var.name=.y)}
      # {assertSubset(.x, stri_join(names(.), map_chr(., typeof), sep=" :: "))}
    })

  …
}

В части функции проверки типов можно выбирать метод по вкусу, сообразуясь с ожидаемыми данными. class был выбран, поскольку именно он дает дату как Date, а не как число (внутреннее представление). Очень подробно вопрос определения типов данных разбирается в диалоге «A comprehensive survey of the types of things in R. 'mode' and 'class' and 'typeof' are insufficient».

assertSetEqual или assertSubset выбираются из соображений четкого совпадения колонок или же минимально достаточного.

Для практических задач такой небольшой набор вполне покрывает большую часть потребностей.

Предыдущая публикация — R как спасательный круг для системного администратора.

Автор: Илья Шутов

Источник

* - обязательные к заполнению поля