Тем, кто работает с 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-ах с кривыми источниками.
В качестве дополнительных соображенией примем следующие:
Прекрасный документ «An introduction to data cleaning with R», описывающий процесс предварительной подготовки данных. Для дальнейших шагов из него мы выделим наличие двух фаз валидации: техническая и логическая.
- Техническая валидация заключается в проверке корректности источника данных. Структура, типы, количественные показатели.
- Логическая валидация может быть многоэтапной, осуществляемой по ходу проведения расчетов, и заключается в проверке соответствия тех или иных элементов данных или их комбинаций различным логическим требованиям.
- Одно из базовых правил при разработке пользовательских интерфейсов — формирование максимально полной диагностики в случае ошибок пользователей. Т.е., если уж пользователь загрузил файл, то надо его максимально проверить на корректность и выдать полную сводку со всеми ошибками (желательно еще и объяснить, что где не так), а не падать при первой же проблеме с сообщением вида «Incorrect input value @ line 528493, pos 17» и требовать загрузки нового файла с исправленной этой ошибкой. Такой подход позволяет многократно сократить количество итераций по формированию правильного источника и повысить качество конечного результата.
Технологии и методы валидации
Пойдем с конца. Для логической валидации существует ряд пакетов. В нашей практике мы остановились на следующих подходах.
- Уже классический
dplyr. В простых случаях бывает удобно просто нарисовать pipe c проведением ряда проверок и ана��изом конечного результата. - Пакет
validateдля проверки технически корректных объектов на соответствие заданным правилам.
Для технической валидации остановились на следующих подходах:
- Пакет
checkmateс широким спектром быстрых функций для проведения разнобразных технических проверок. - Явная работа с исключениями «Advanced R. Debugging, condition handling, and defensive programming», «Advanced R. Beyond Exception Handling: Conditions and Restarts» как для проведения полного объема валидации за один шаг, так и для обеспечения стабильности работы приложения.
- Использование
purrобертки для исключений. Весьма полезно при применении внутри pipe.
В коде, разбитом на функции, важным элементом «defensive programming» является проверка входных и выходных параметров функций. В случае языков с динамической типизацией проверку типов приходится делать самостоятельно. Для базовых типов идеально подходит пакет checkmate, особенно его функции qtest\qassert. Для проверки 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 как спасательный круг для системного администратора.