Настройка и старт R-сессии

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

Томас Лин Пендерсен нарисовал исчерпывающую схему процесса старта R-сессии:

Переменные окружения (.Renviron)

Первое, что происходит при старте сессии, — это поиск и чтение переменных окружения. В этих переменных обычно хранятся различные пути (к пакетам, к интерпретатору R, к временным файлам), настройки локали (кодировки, форматы даты, времени, валюты и прочее), информация о системе и прочее. Нередко в переменных окружения рекомендуется хранить также и различные токены авторизации: таким образом они будут использоваться в R-сессии, но не будут присутствовать в явном виде в скриптах.

Полный список переменных окружения, которые могут повлиять на R-сессию, можно увидеть в документации по пакету base по запросу ?'environment variables'. Список уже используемых в сессии переменных и их значений можно увидеть с помощью команды Sys.getenv(), в том числе и значение конкретной переменной:

# первые пять переменных окружения из списка
Sys.getenv()[1:5]
## _R_CHECK_COMPILATION_FLAGS_KNOWN_
##                         -Wformat -Werror=format-security -Wdate-time
## CLICOLOR_FORCE          1
## DBUS_SESSION_BUS_ADDRESS
##                         unix:path=/run/user/1000/bus
## DEFAULTS_PATH           /usr/share/gconf/plasma.default.path
## DERBY_HOME              /usr/lib/jvm/java-17-oracle/db
# смотрим путь к интерпретатору R и пакетам
Sys.getenv(c("R_HOME", "R_LIBS_USER"))
##                                R_HOME                           R_LIBS_USER 
##                          "/usr/lib/R" "~/R/x86_64-pc-linux-gnu-library/4.1"

В том числе можно адресно вызвать значения локали с помощью Sys.getlocale(). В рамках сессии локаль можно изменить с функцией Sys.setlocale() (стоит учитывать, что названия локалей платформозависимы).

# смотрим используемые в сессии параметры локали
Sys.getlocale()
## [1] "LC_CTYPE=en_US.UTF-8;LC_NUMERIC=C;LC_TIME=ru_RU.UTF-8;LC_COLLATE=en_US.UTF-8;LC_MONETARY=ru_RU.UTF-8;LC_MESSAGES=en_US.UTF-8;LC_PAPER=ru_RU.UTF-8;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=ru_RU.UTF-8;LC_IDENTIFICATION=C"

Поиск переменных происходит по цепочке (на примере структуры папок Linux, для Windows будут другие адреса):

  • файл, указанный в R_ENVIRON или R_HOME/etc/Renviron.site, — переменные окружения, которые используются для всех пользователей рабочей станции;

  • файл, указанный в R_ENVIRON_USER или .Renviron, находящийся в папке проекта, — набор переменных окружения и их возможных значений, которые используются в этом проекте (например, токены авторизации);

  • файл /home/.Renviron, находящийся в папке пользователя, — набор переменных окружения и их возможных значений, которые используются во всех проектах этого пользователя.

Если файлы .Renviron отсутствуют и в R_ENVIRON/R_ENVIRON_USER ничего не указано, то используются переменные, указанные в R_HOME/etc/Renviron.site. При необходимости можно создать свой файл .Renviron (стоит учитывать, что если есть .Renviron проекта, то .Renviron пользователя будет проигнорирован, так как он ниже в иерархии процесса загрузки). Формат файла простой: пара VARIABLE=value, одна пара на строку. Например, R_DEFAULT_PACKAGES='utils,grDevices,graphics,stats'. Фактически это обычный текстовый файл со строгой структурой.

Скрипты конфигурации .Rprofile

После загрузки переменных окружения при старте R-сессии загружается файл конфигурации сессии, Rprofile.site / .Rprofile. Процесс схож с загрузкой переменных окружения: сначала поиск файла, задающего параметры сессии для всех пользователей рабочей станции в R_PROFILE или R_HOME/etc/Rprofile.site. Если файла нет, то поиск файла .Rprofile продолжается в R_PROFILE_USER и папке проекта, и только потом в домашней папке пользователя. Эти файлы действуют на все сессии проекта или на все сессии пользователя соответственно.

Чаще всего используют .Rprofile-скрипты, так как хорошей практикой считается максимально сужать область действия файлов конфигураций сессии. Временами можно даже найти на тематических форумах треды (в частности, на Stackoverflow), в которых пользователи делятся своими конфигурациями. В отличие от .Renviron, скрипты .Rprofile содержат код на R и могут включать в себя установку определенных опций, объявление или вызов функций. В .Rprofile также можно объявить функции, которые будут выполняться при старте или при завершении работы (функции .Start() и .Last() соответственно).

Вот так выглядит пример Rprofile.site из документации, где устанавливается репозиторий по умолчанию:

local({
    r <- getOption("repos")
    r["CRAN"] <- "https://cloud.r-project.org"
    options(repos = r)
})

Также пример из документации для .Rprofile. Здесь задаётся зерно для генерации псевдослучайных чисел, опции отображения (количество колонок при выводе на печать, количество значимых знаков и нужно ли отображать маркеры значимости различий). Выражение .First <- function() cat("\n Welcome to R!\n\n") задаёт печать в консоли сообщения Welcome to R! при загрузке сессии. Выражение .Last <- function() cat("\n Goodbye!\n\n") напечатает Goodbye! при завершении сессии:

options(width=65, digits=5)
options(show.signif.stars=FALSE)
setHook(packageEvent("grDevices", "onLoad"),
        function(...) grDevices::ps.options(horizontal=FALSE))
set.seed(1234)
.First <- function() cat("\n   Welcome to R!\n\n")
.Last <- function()  cat("\n   Goodbye!\n\n")

Несмотря на очевидную полезность .Rprofile (да и .Renviron тоже), их следует осмотрительно использовать. В частности, могут возникнуть проблемы с совместным использованием скриптов: если какой-то скрипт написан с учётом того, что в .Rprofile объявляются пользовательские функции, то в любой другой сессии, где нет такого же файла конфигурации сессии, скрипт работать не будет. Например, нередко в .Rprofile пишут объявление функций типа %nin% <- function(x, y) !(x %in% y) или выставляют опцию options(stringsAsFactors = FALSE) (эта опция была актуальна для версий ниже 4.0, в которой её сделали по умолчанию).

Обычно при создании скриптов конфигурации меняют те элементы, которые не влияют напрямую на работу скрипта, но могут сделать жизнь проще. Типовые решения:

  • указывают ближайшее зеркало CRAN;

  • меняют символ начала строки в консоли (как правило, c > на >> или #);

  • устанавливают опции отображения (количество значимых знаков для print(), запрет на отображение чисел в scientific-формате и т. д.);

  • задают определённые действия при старте и завершении сессии, например, при завершении сессии задают запись sessionInfo() в отдельный файл;

  • в редких случаях указывают загрузку часто используемых пакетов и перегружают базовые функции, в частности, мне как-то приходилось переопределять и внедрять в source() элементы трассировки ошибок с помощью traceback(), а также настраивать цвет сообщений об ошибках.

Нередко рекомендуется все объявляемые .Rprofile функции сопровождать проверкой на то, каким образом будет выполняться скрипт, интерактивно в консоли или выполнением R-скрипта через source() или eval(). Например:

# опция для всех процессов сессии, как интерактивных, так и неинтерактивных
options(repos = c(CRAN = "https://cran.rstudio.org"))

# только если работа идёт в интерактивном режиме
if (interactive()) {
  options(scipen = 999)
}

Настройки сессии (options)

После выполнения скрипта конфигурации происходит ещё ряд скрытых от пользователя процедур: подгрузка объектов рабочего пространства из прошлой сессии, если она была (.RData); выполнение объявленной в файле конфигурации функции .First(); подгрузка базовых пакетов (функция base::.First.sys()), загрузка истории выполненных выражений в предыдущей сессии (R_HISTFILE или .Rhistory). Также при выполнении скрипта конфигурации сессии устанавливают опции (options) сессии.

Как правило, с помощью опций задают правила вычисления и отображения результатов. К наиболее наглядным примерам можно отнести опцию импорта строк как факторов, запрет на отображение чисел в scientific (экспоненциальном) формате или определённый формат десятичного разделителя. Значения по умолчанию выставляются при старте сессии после импорта переменных окружения и скриптов конфигураций при подключении базовых пакетов. При этом можно самостоятельно задать новые значения опций в файле конфигурации до старта сессии или же в скрипте сразу во время работы. Допустимо использовать и консоль, но потом можно легко забыть, что были заданы новые значения, и в дальнейшем долго удивляться результатам.

Полный список опций можно получить с помощью функции options(), которая отдаёт список названий опций и их значений (значениями могут быть как строковые или числовые параметры, так и объявление функций). Описание опций можно также получить в справке help(options). Выведем список пакетов, которые подключаются при старте сессии, помимо base:

options()$defaultPackages
## [1] "datasets"  "utils"     "grDevices" "graphics"  "stats"     "methods"

Многие пакеты в R также имеют свой список опций. При подключении пакетов эти опции становятся видны в общем списке, как правило, с названием пакета в префиксе. Вот, например, какие опции появляются при подключении пакета data.table:

library(data.table)
options_names <- names(options())
options_names[grep('datatable', options_names)]
##  [1] "datatable.alloccol"         "datatable.allow.cartesian" 
##  [3] "datatable.auto.index"       "datatable.dfdispatchwarn"  
##  [5] "datatable.optimize"         "datatable.print.class"     
##  [7] "datatable.print.colnames"   "datatable.print.keys"      
##  [9] "datatable.print.nrows"      "datatable.print.rownames"  
## [11] "datatable.print.topn"       "datatable.print.trunc.cols"
## [13] "datatable.use.index"        "datatable.verbose"         
## [15] "datatable.warnredundantby"

Значение опции можно получить и с помощью функции getOption(). Обычно именно этот метод используется, когда надо обратиться к опциям, например, в аргументах функции:

getOption('defaultPackages')
## [1] "datasets"  "utils"     "grDevices" "graphics"  "stats"     "methods"

Опции меняются с помощью все той же функции options(), где аргументом выступает название опции. Изменения в опциях действуют в рамках сессии, поэтому если в работе постоянно требуются какие-то определенные настройки, то есть смысл выносить их в скрипт конфигурации .Rprofile.

# по умолчанию используется экспоненциальное представление чисел
getOption('scipen')
## [1] 0
print(12345679101112)
## [1] 1.234568e+13
# меняем scientific-формат на обычный разрядный
options(scipen = 999)
getOption('scipen')
## [1] 999
print(12345679101112)
## [1] 12345679101112

Информация об R-сессии

Очень часто приходится сохранять информацию, на какой машине были произведены те или иные расчёты, какой набор пакетов и их версий использовался. В частности, это очень помогает при расследовании, что пошло не так при выполнении задачи. Получить информацию о сессии можно с помощью функции sessionInfo(). Функция возвращает объект класса sessionInfo. При печати объекта выводится сводная информация о сессии: версия ОС и R, используемые библиотеки для линейной алгебры, параметры локали, подключённые пакеты. Формулировка loaded via a namespace (and not attached) означает, что пакеты подгружены, но пользователь не имеет возможности обращаться к этих пакетам напрямую, в отличие от функций других пакетов.

## R version 4.1.2 (2021-11-01)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.3 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=ru_RU.UTF-8        LC_COLLATE=en_US.UTF-8    
##  [5] LC_MONETARY=ru_RU.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=ru_RU.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=ru_RU.UTF-8 LC_IDENTIFICATION=C       
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] profvis_0.3.7     data.table_1.14.2
## 
## loaded via a namespace (and not attached):
##  [1] knitr_1.36        xml2_1.3.2        magrittr_2.0.1    downlit_0.2.1    
##  [5] R6_2.5.1          rlang_0.4.12      fastmap_1.1.0     fansi_0.5.0      
##  [9] stringr_1.4.0     tools_4.1.2       xfun_0.26         utf8_1.2.2       
## [13] jquerylib_0.1.4   htmltools_0.5.2   ellipsis_0.3.2    yaml_2.2.1       
## [17] digest_0.6.29     tibble_3.1.6      lifecycle_1.0.1   crayon_1.4.2     
## [21] bookdown_0.22.17  htmlwidgets_1.5.4 sass_0.4.0        vctrs_0.3.8      
## [25] fs_1.5.0          evaluate_0.14     rmarkdown_2.11    stringi_1.7.6    
## [29] compiler_4.1.2    bslib_0.3.1       pillar_1.6.4      jsonlite_1.7.2   
## [33] pkgconfig_2.0.3

Объект sessionInfo содержит более подробную информацию о сессии, в частности, о пакетах: название, имя автора пакета, зависимости, лицензия, версия билда, дата публикации и прочая детальная информация.

# посмотрим информацию о подгруженном пакете Rcpp
si <- sessionInfo()
str(si$loadedOnly$Rcpp)
##  NULL