- PVSM.RU - https://www.pvsm.ru -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. Пакет checkmate [6] с широким спектром быстрых функций для проведения разнобразных технических проверок.
  2. Явная работа с исключениями «Advanced R. Debugging, condition handling, and defensive programming» [7], «Advanced R. Beyond Exception Handling: Conditions and Restarts» [8] как для проведения полного объема валидации за один шаг, так и для обеспечения стабильности работы приложения.
  3. Использование purr [9] обертки для исключений. Весьма полезно при применении внутри 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» [10].

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

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

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

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

Источник [12]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/data-mining/282650

Ссылки в тексте:

[1] предыдущих публикаций: https://habrahabr.ru/users/i_shutov/posts/

[2] «Excel’s designer thought 1900 was a leap year, but it was not»: https://www.joelonsoftware.com/2006/06/16/my-first-billg-review/

[3] «An introduction to data cleaning with R»: https://cran.r-project.org/doc/contrib/de_Jonge+van_der_Loo-Introduction_to_data_cleaning_with_R.pdf

[4] dplyr: https://dplyr.tidyverse.org

[5] validate: https://cran.r-project.org/web/packages/validate/index.html

[6] checkmate: https://cran.r-project.org/web/packages/checkmate/index.html

[7] «Advanced R. Debugging, condition handling, and defensive programming»: http://adv-r.had.co.nz/Exceptions-Debugging.html

[8] «Advanced R. Beyond Exception Handling: Conditions and Restarts»: http://adv-r.had.co.nz/beyond-exception-handling.html

[9] purr: https://purrr.tidyverse.org

[10] «A comprehensive survey of the types of things in R. 'mode' and 'class' and 'typeof' are insufficient»: https://stackoverflow.com/questions/8855589/a-comprehensive-survey-of-the-types-of-things-in-r-mode-and-class-and-type

[11] R как спасательный круг для системного администратора: https://habr.com/post/348128/

[12] Источник: https://habr.com/post/413865/?utm_source=habrahabr&utm_medium=rss&utm_campaign=413865