7  Datos

A menudo resulta útil incluir datos en un paquete. Si el objetivo principal de un paquete es distribuir funciones útiles, los conjuntos de datos de ejemplo facilitan la redacción de documentación excelente. Estos conjuntos de datos se pueden crear a mano para proporcionar casos de uso atractivos para las funciones del paquete. A continuación se muestran algunos ejemplos de este tipo de datos de paquete:

En el otro extremo, algunos paquetes existen únicamente con el fin de distribuir datos, junto con su documentación. A veces se les llama “paquetes de datos”. Un paquete de datos puede ser una buena forma de compartir datos de ejemplo entre varios paquetes. También es una técnica útil para obtener archivos estáticos relativamente grandes de un paquete más orientado a funciones, que podría requerir actualizaciones más frecuentes. A continuación se muestran algunos ejemplos de paquetes de datos:

Por último, muchos paquetes se benefician de tener datos internos que se utilizan para fines internos, pero que no están expuestos directamente a los usuarios del paquete.

En este capítulo describimos mecanismos útiles para incluir datos en su paquete. Los detalles prácticos difieren según quién necesita acceso a los datos, con qué frecuencia cambian y qué harán con ellos:

7.1 Datos exportados

La ubicación más común para los datos del paquete es (¡sorpresa!) data/. Recomendamos que cada archivo en este directorio sea un archivo .rda creado por save() que contenga un único objeto R, con el mismo nombre que el archivo. La forma más sencilla de lograrlo es utilizar usethis::use_data().

my_pkg_data <- sample(1000)
usethis::use_data(my_pkg_data)

Imaginemos que estamos trabajando en un paquete llamado “pkg”. El fragmento anterior crea data/my_pkg_data.rda dentro del código fuente del paquete pkg y agrega LazyData: true en su DESCRIPCIÓN. Esto hace que el objeto R my_pkg_data esté disponible para los usuarios de pkg a través de pkg::my_pkg_data o, después de adjuntar pkg con library(pkg), como my_pkg_data.

El fragmento anterior es algo que el mantenedor ejecuta una vez (o cada vez que necesita actualizar my_pkg_data). Este es el código de flujo de trabajo y no debería aparecer en el directorio R/ del paquete fuente. (Hablaremos sobre un lugar adecuado para guardar este código a continuación). Para conjuntos de datos más grandes, es posible que desees experimentar con la configuración de compresión, que está bajo el control del argumento “comprimir”. El valor predeterminado es “bzip2”, pero a veces “gzip” o “xz” pueden crear archivos más pequeños.

Es posible utilizar otros tipos de archivos debajo de data/, pero no lo recomendamos porque los archivos .rda ya son rápidos, pequeños y explícitos. Las otras posibilidades se describen en la documentación de utils::data() y en Datos en paquetes de Escritura de extensiones R. En términos de consejos para los autores de paquetes, el tema de ayuda para data() parece hacer implícitamente las mismas recomendaciones que hacemos anteriormente:

  • Almacene un objeto R en cada archivo data/*.rda
  • Utilice el mismo nombre para ese objeto y su archivo .rda
  • Utilice carga diferida, de forma predeterminada

Si la DESCRIPTION contiene LazyData: true, los conjuntos de datos se cargarán de forma diferida. Esto significa que no ocuparán memoria hasta que los uses. El siguiente ejemplo muestra el uso de la memoria antes y después de cargar el paquete nycflights13. Puede ver que el uso de la memoria no cambia significativamente hasta que inspecciona el conjunto de datos de flights almacenados dentro del paquete.

lobstr::mem_used()
#> 74.57 MB
library(nycflights13)
lobstr::mem_used()
#> 76.30 MB

invisible(flights)
lobstr::mem_used()
#> 117.00 MB

Le recomendamos que incluya LazyData: true en su DESCRIPTION si envía archivos .rda debajo de data/. Si utiliza use_data() para crear dichos conjuntos de datos, automáticamente realizará esta modificación en DESCRIPTION por usted.

Advertencia

Es importante tener en cuenta que los conjuntos de datos cargados de forma diferida no necesitan estar precargados con utils::data() y, de hecho, normalmente es mejor evitar hacerlo. Arriba, una vez que hicimos library(nycflights13), pudimos acceder inmediatamente a flights. No hay llamada a datos(flights), porque no es necesario.

Hay desventajas específicas de las llamadas data(some_pkg_data) que admiten una política de usar data() solo cuando es realmente necesario, es decir, para conjuntos de datos que de otro modo no estarían disponibles:

  • De forma predeterminada, data(some_pkg_data) crea uno o más objetos en el espacio de trabajo global del usuario. Existe la posibilidad de sobrescribir silenciosamente objetos preexistentes con nuevos valores.
  • Tampoco hay garantía de que data(foo) cree exactamente un objeto llamado “foo”. Podría crear más de un objeto y/u objetos con nombres totalmente diferentes.

Un argumento a favor de llamadas como data(some_pkg_data, package = "pkg") que no son estrictamente necesarias es que aclara qué paquete proporciona some_pkg_data. Preferimos alternativas que no modifiquen el espacio de trabajo global, como un comentario de código o el acceso a través de pkg::some_pkg_data.

Este extracto de la documentación de data() transmite que es en gran medida de importancia histórica:

data() Originalmente estaba destinado a permitir a los usuarios cargar conjuntos de datos desde paquetes para usarlos en sus ejemplos y, como tal, cargaba los conjuntos de datos en el espacio de trabajo. .GlobalEnv. Esto evitó tener grandes conjuntos de datos en la memoria cuando no estaban en uso: esa necesidad ha sido reemplazada casi por completo por la carga diferida de conjuntos de datos.

7.1.1 Preservar la historia del origen de los datos del paquete

A menudo, los datos que incluye en data/ son una versión limpia de datos sin procesar que ha recopilado de otro lugar. Recomendamos encarecidamente tomarse el tiempo para incluir el código utilizado para hacer esto en la versión fuente de su paquete. Esto le facilita actualizar o reproducir su versión de los datos. Este script de creación de datos también es un lugar natural para dejar comentarios sobre propiedades importantes de los datos, es decir, qué características son importantes para el uso posterior en la documentación del paquete.

Le sugerimos que mantenga este código en uno o más archivos .R debajo de data-raw/. No lo desea en la versión incluida de su paquete, por lo que esta carpeta debe aparecer en .Rbuildignore. usethis tiene una función conveniente que se puede llamar cuando adopta por primera vez la práctica data-raw/ o cuando agrega un archivo .R adicional a la carpeta:

usethis::use_data_raw()

usethis::use_data_raw("my_pkg_data")

use_data_raw() crea la carpeta data-raw/ y la enumera en .Rbuildignore. Un script típico en data-raw/ incluye código para preparar un conjunto de datos y termina con una llamada a use_data().

Todos estos paquetes de datos utilizan el enfoque recomendado aquí para data-raw/:

ggplot2: Un cuento con moraleja

Tenemos una confesión que hacer: los orígenes de muchos de los conjuntos de datos de ejemplo de ggplot2 se han perdido en las arenas del tiempo. En el gran esquema de las cosas, esto no es un gran problema, pero el mantenimiento es ciertamente más placentero cuando los activos de un paquete pueden reconstruirse de novo y actualizarse fácilmente según sea necesario.

Envío a CRAN

Generalmente, los datos del paquete deben ser más pequeños que un megabyte; si son más grandes, deberá solicitar una exención. Esto suele ser más fácil de hacer si los datos están en su propio paquete y no se actualizarán con frecuencia, es decir, si lo aborda como un “paquete de datos” dedicado. Como referencia, los paquetes babynames y nycflights se han lanzado una vez cada uno o dos años, desde que aparecieron por primera vez en CRAN.

Si tiene problemas de tamaño, debe ser intencional con respecto al método de compresión de datos. El valor predeterminado para usethis::use_data(compress =) es “bzip2”, mientras que el valor predeterminado para save(compress =) es (efectivamente) “gzip”, y “xz” es otra opción válida.

Tendrás que experimentar con diferentes métodos de compresión y tomar esta decisión de forma empírica. tools::resaveRdaFiles("data/") automatiza este proceso, pero no le informa qué método de compresión se eligió. Puede aprender esto después del hecho con tools::checkRdaFiles(). Suponiendo que está realizando un seguimiento del código para generar sus datos, sería prudente actualizar la llamada use_data(compress =) correspondiente debajo de data-raw/ y volver a generar el .rda limpiamente.

7.1.2 Documentar conjuntos de datos

Los objetos en data/ siempre se exportan efectivamente (utilizan un mecanismo ligeramente diferente al de NAMESPACE pero los detalles no son importantes). Esto significa que deben estar documentados. Documentar datos es como documentar una función con algunas diferencias menores. En lugar de documentar los datos directamente, documenta el nombre del conjunto de datos y lo guarda en R/. Por ejemplo, el bloque roxygen2 utilizado para documentar los datos quién(who) en tidyr se guarda en R/data.R y se parece a esto:

#' Datos sobre tuberculosis de la Organización Mundial de la Salud
#'
#' Un subconjunto de datos de un Informe de la Tuberculosis Global de la 
#' Organización Mundial de la Salud ...
#'
#' @format ## `who`
#' Un marco de datos con 7240 filas y 60 columnas:
#' \describe{
#'   \item{country}{Country name}
#'   \item{iso2, iso3}{2 & 3 letter ISO country codes}
#'   \item{year}{Year}
#'   ...
#' }
#' @source <https://www.who.int/teams/global-tuberculosis-programme/data>
"who"

Hay dos etiquetas roxygen que son especialmente importantes para documentar conjuntos de datos:

  • @format ofrece una visión general del conjunto de datos. Para los marcos de datos, debe incluir una lista de definiciones que describa cada variable. Generalmente es una buena idea describir aquí las unidades de las variables.

  • @source proporciona detalles de dónde obtuvo los datos, a menudo una URL.

Nunca @export un conjunto de datos.

7.1.3 Caracteres no ASCII en datos

Los objetos R que almacena en data/*.rda a menudo contienen cadenas, siendo el ejemplo más común las columnas de caracteres en un marco de datos. Si puede restringir estas cadenas para que utilicen únicamente caracteres ASCII, ciertamente simplificará las cosas. Pero, por supuesto, existen muchas razones legítimas por las que los datos del paquete pueden incluir caracteres que no son ASCII.

En ese caso, le recomendamos que adopte el manifiesto UTF-8 Everywhere y utilice la codificación UTF-8. El archivo DESCRIPCIÓN colocado por usethis::create_package() siempre incluye Encoding: UTF-8, por lo que, de forma predeterminada, un paquete producido por devtools ya anuncia que usará UTF-8.

Asegurarse de que las cadenas incrustadas en los datos de su paquete tengan la codificación deseada es algo que debe lograr en su código de preparación de datos, es decir, en los scripts de R debajo de data-raw/. Puede usar Encoding() para conocer la codificación actual de los elementos en un vector de caracteres y funciones como enc2utf8() o iconv() para convertir entre codificaciones.

Envío a CRAN

Si tiene cadenas codificadas en UTF-8 en los datos de su paquete, puede ver esto desde R CMD check:

-   checking data for non-ASCII characters ... NOTE
    Note: found 352 marked UTF-8 strings

Esta NOTA es verdaderamente informativa. No requiere ninguna acción por su parte. Siempre que realmente desee tener cadenas UTF-8 en los datos de su paquete, todo está bien.

Irónicamente, esta NOTA en realidad es suprimida por R CMD check --as-cran, a pesar de que esta nota aparece en los resultados de la verificación una vez que un paquete está en CRAN (lo que implica que CRAN no necesariamente verifica con --as-cran). De forma predeterminada, devtools::check() establece el indicador --as-cran y por lo tanto no transmite esta NOTA. Pero puedes sacarlo a la superficie con check(cran = FALSE, env_vars = c("_R_CHECK_PACKAGE_DATASETS_SUPPRESS_NOTES_" = "false")).

7.2 Datos internos

A veces, las funciones de su paquete necesitan acceso a datos precalculados. Si coloca estos objetos en data/, también estarán disponibles para los usuarios del paquete, lo cual no es apropiado. A veces, los objetos que necesitas son lo suficientemente pequeños y simples como para poder definirlos con c() o data.frame() en el código debajo de R/, tal vez en R/data.R. Los objetos más grandes o más complicados deben almacenarse en los datos internos de su paquete en R/sysdata.rda, para que se carguen de forma diferida según demanda.

A continuación se muestran algunos ejemplos de datos de paquetes internos:

  • Dos paquetes relacionados con el color, munsell y dichromat, utilice R/sysdata.rda para almacenar grandes tablas de datos de color.
  • googledrive y googlesheets4 envuelva las API de Google Drive y Google Sheets, respectivamente. Ambos usan R/sysdata.rda para almacenar datos derivados del llamado Documento de descubrimiento que “describe la superficie de la API, cómo acceder a la API y cómo se estructuran las solicitudes y respuestas de la API”.

La forma más sencilla de crear R/sysdata.rda es utilizar usethis::use_data(internal = TRUE):

internal_this <- ...
internal_that <- ...

usethis::use_data(internal_this, internal_that, internal = TRUE)

A diferencia de data/, donde se utiliza un archivo .rda por objeto de datos exportado, se almacenan todos los objetos de datos internos juntos en un único archivo. R/sysdata.rda.

Imaginemos que estamos trabajando en un paquete llamado “pkg”. El fragmento anterior crea R/sysdata.rda dentro del código fuente del paquete pkg. Esto hace que los objetos internal_this e internal_that estén disponibles para su uso dentro de las funciones definidas debajo de R/ y en las pruebas. Durante el desarrollo interactivo, internal_this e internal_that están disponibles después de una llamada a devtools::load_all(), como una función interna.

Gran parte de los consejos dados para los datos externos también se aplican a los datos internos:

  • Es una buena idea almacenar el código que genera sus objetos de datos internos individuales, así como la llamada use_data() que los escribe todos en R/sysdata.rda. Este es el código de flujo de trabajo que pertenece debajo de data-raw/, no debajo de R/.
  • usethis::use_data_raw() se puede utilizar para iniciar el uso de data-raw/ o para iniciar un nuevo script .R allí.
  • Si su paquete es demasiado grande, experimente con diferentes valores de compress en use_data(internal = TRUE).

También existen distinciones clave en las que difiere el manejo de datos internos y externos:

  • Los objetos en R/sysdata.rda no se exportan (no deberían exportarse), por lo que no es necesario documentarlos.
  • El campo LazyData en el paquete DESCRIPTION no tiene ningún impacto en R/sysdata.rda pero se refiere estrictamente a los datos exportados debajo de data/. Los datos internos siempre se cargan de forma diferida.

7.3 Archivo de datos sin procesar

Si desea mostrar ejemplos de carga/análisis de datos sin procesar, coloque los archivos originales en inst/extdata/. Cuando se instala el paquete, todos los archivos (y carpetas) en inst/ se mueven hacia arriba un nivel hasta el directorio de nivel superior, por lo que no pueden tener nombres que entren en conflicto con las partes estándar de un paquete R, como R/ o DESCRIPTION . Los archivos debajo de inst/extdata/ en el paquete fuente se ubicarán debajo de extdata/ en el paquete instalado correspondiente. Es posible que desee volver a visitar Figura 3.1 para revisar la estructura de archivos para los diferentes estados del paquete.

La razón principal para incluir dichos archivos es cuando una parte clave de la funcionalidad de un paquete es actuar sobre un archivo externo. Ejemplos de dichos paquetes incluyen:

  • readr, que lee datos rectangulares de archivos delimitados
  • readxl, que lee datos rectangulares de hojas de cálculo de Excel
  • xml2, que puede leer XML y HTML desde un archivo
  • archive, que puede leer archivos comprimidos, como tar o ZIP

Todos estos paquetes tienen uno o más archivos de ejemplo debajo de inst/extdata/, que son útiles para escribir documentación y pruebas.

También es común que los paquetes de datos proporcionen, por ejemplo, una versión csv de los datos del paquete que también se proporciona como un objeto R. Ejemplos de dichos paquetes incluyen:

  • palmerpenguins: penguins y penguins_raw también se representan como extdata/penguins.csv y extdata/penguins_raw.csv
  • gapminder: gapminder, continent_colors, y country_colors también se representan como extdata/gapminder.tsv, extdata/continent-colors.tsv, y extdata/country-colors.tsv

Esto tiene dos beneficios: en primer lugar, les da a los profesores y otros expositores más con qué trabajar una vez que deciden utilizar un conjunto de datos específico. Si has empezado a enseñar R con palmerpenguins::penguins o gapminder::gapminder y desea introducir la importación de datos, puede ser útil para los estudiantes si utilizan por primera vez un comando nuevo, como readr::read_csv() o read.csv(), se aplica a un conjunto de datos familiar. Tienen una intuición preexistente sobre el resultado esperado. Finalmente, si los datos del paquete evolucionan con el tiempo, tener un csv u otra representación de texto sin formato en el paquete fuente puede hacer que sea más fácil ver qué ha cambiado.

7.3.1 Rutas de archivos

La ruta a un archivo de paquete que se encuentra debajo de extdata/ depende claramente del entorno local, es decir, depende de dónde se encuentran los paquetes instalados en esa máquina. La función base system.file() puede informar la ruta completa a los archivos distribuidos con un paquete R. También puede resultar útil enumerar los archivos distribuidos con un paquete R.

system.file("extdata", package = "readxl") |> list.files()
#>  [1] "clippy.xls"    "clippy.xlsx"   "datasets.xls"  "datasets.xlsx"
#>  [5] "deaths.xls"    "deaths.xlsx"   "geometry.xls"  "geometry.xlsx"
#>  [9] "type-me.xls"   "type-me.xlsx"

system.file("extdata", "clippy.xlsx", package = "readxl")
#> [1] "/home/runner/work/r-pkgses/r-pkgses/renv/library/linux-ubuntu-jammy/R-4.4/x86_64-pc-linux-gnu/readxl/extdata/clippy.xlsx"

Estas rutas de archivos presentan otro dilema en el flujo de trabajo: cuando estás desarrollando tu paquete, interactúas con él en su formato fuente, pero tus usuarios interactúan con él como un paquete instalado. Afortunadamente, devtools proporciona una corrección para base::system.file() que se activa mediante load_all(). Esto realiza llamadas interactivas a system.file() desde el entorno global y las llamadas desde dentro del espacio de nombres del paquete “simplemente funcionan”.

Tenga en cuenta que, de forma predeterminada, system.file() devuelve la cadena vacía, no un error, para un archivo que no existe.

system.file("extdata", "I_do_not_exist.csv", package = "readr")
#> [1] ""

Si desea forzar un error en este caso, especifique mustWork = TRUE:

system.file("extdata", "I_do_not_exist.csv", package = "readr", mustWork = TRUE)
#> Error in system.file("extdata", "I_do_not_exist.csv", package = "readr", : no file found

El paquete fs ofrece fs::path_package(). Esto es esencialmente base::system.file() con algunas características adicionales que consideramos ventajosas, siempre que sea razonable depender de fs:

  • Se produce un error si la ruta del archivo no existe.
  • Genera errores distintos cuando el paquete no existe versus cuando el archivo no existe dentro del paquete.
  • Durante el desarrollo, funciona para llamadas interactivas, llamadas desde dentro del espacio de nombres del paquete cargado e incluso para llamadas que se originan en dependencias.
fs::path_package("extdata", package = "idonotexist")
#> Error: Can't find package `idonotexist` in library locations:
#>   - '/home/runner/work/r-pkgses/r-pkgses/renv/library/linux-ubuntu-jammy/R-4.4/x86_64-pc-linux-gnu'
#>   - '/home/runner/.cache/R/renv/sandbox/linux-ubuntu-jammy/R-4.4/x86_64-pc-linux-gnu/3df92652'

fs::path_package("extdata", "I_do_not_exist.csv", package = "readr")
#> Error: File(s) '/home/runner/work/r-pkgses/r-pkgses/renv/library/linux-ubuntu-jammy/R-4.4/x86_64-pc-linux-gnu/readr/extdata/I_do_not_exist.csv' do not exist

fs::path_package("extdata", "chickens.csv", package = "readr")
#> /home/runner/work/r-pkgses/r-pkgses/renv/library/linux-ubuntu-jammy/R-4.4/x86_64-pc-linux-gnu/readr/extdata/chickens.csv

7.3.2 pkg_example() ayudantes de camino

Nos gusta ofrecer funciones convenientes que faciliten el acceso a los archivos de ejemplo. Estos son simplemente envoltorios fáciles de usar alrededor de system.file() o fs::path_package(), pero pueden tener características adicionales, como la capacidad de enumerar los archivos de ejemplo. Aquí está la definición y algunos usos de readxl::readxl_example():

readxl_example <- function(path = NULL) {
  if (is.null(path)) {
    dir(system.file("extdata", package = "readxl"))
  } else {
    system.file("extdata", path, package = "readxl", mustWork = TRUE)
  }
}
readxl::readxl_example()
#>  [1] "clippy.xls"    "clippy.xlsx"   "datasets.xls"  "datasets.xlsx"
#>  [5] "deaths.xls"    "deaths.xlsx"   "geometry.xls"  "geometry.xlsx"
#>  [9] "type-me.xls"   "type-me.xlsx"

readxl::readxl_example("clippy.xlsx")
#> [1] "/home/runner/work/_temp/renv/cache/v5/linux-ubuntu-jammy/R-4.4/x86_64-pc-linux-gnu/readxl/1.4.3/8cf9c239b96df1bbb133b74aef77ad0a/readxl/extdata/clippy.xlsx"

7.4 Estado interno

A veces hay información a la que varias funciones de su paquete necesitan acceder:

  • Debe determinarse en el momento de la carga (o incluso más tarde), no en el momento de la compilación. Incluso podría ser dinámico.
  • No tiene sentido pasarlo mediante un argumento de función. A menudo se trata de algún detalle oscuro que el usuario ni siquiera debería conocer.

Una excelente manera de administrar dichos datos es utilizar un entorno.1 Este entorno debe crearse en el momento de la compilación, pero puede completarlo con valores después de que se haya cargado el paquete y actualizar esos valores en el transcurso de una sesión de R. Esto funciona porque los entornos tienen una semántica de referencia (mientras que los objetos R más comunes, como vectores atómicos, listas o marcos de datos, tienen una semántica de valores).

Considere un paquete que pueda almacenar las letras o números favoritos del usuario. Podrías comenzar con un código como este en el archivo siguiente R/:

favorite_letters <- letters[1:3]

#' Reportar mis letras favoritas
#' @export
mfl <- function() {
  favorite_letters
}

#' Cambiar mis letras favoritas
#' @export
set_mfl <- function(l = letters[24:26]) {
  old <- favorite_letters
  favorite_letters <<- l
  invisible(old)
}

favorite_letters se inicializa en (“a”, “b”, “c”) cuando se crea el paquete. Luego, el usuario puede inspeccionar favorite_letters con mfl(), momento en el cual probablemente querrá registrar sus letras favoritas con set_mfl(). Tenga en cuenta que hemos utilizado el operador de superasignación <<- en set_mfl() con la esperanza de que llegue al entorno del paquete y modifique el objeto de datos interno favorite_letters. Pero una llamada a set_mfl() falla así:2

mfl()
#> [1] "a" "b" "c"

set_mfl(c("j", "f", "b"))
#> Error in set_mfl() : 
#>   cannot change value of locked binding for 'favorite_letters'

Debido a que favorite_letters es un vector de caracteres normal, la modificación requiere hacer una copia y volver a vincular el nombre favorite_letters a este nuevo valor. Y eso es lo que no está permitido: no se puede cambiar el enlace de los objetos en el espacio de nombres del paquete (bueno, al menos no sin esforzarse más). Definir favorite_letters de esta manera solo funciona si nunca necesitarás modificarlo.

Sin embargo, si mantenemos el estado dentro de un entorno de paquete interno, podemos modificar los objetos contenidos en el entorno (e incluso agregar objetos completamente nuevos). Aquí hay una implementación alternativa que utiliza un entorno interno llamado “el”.

the <- new.env(parent = emptyenv())
the$favorite_letters <- letters[1:3]

#' Reportar mis letras favoritas
#' @export
mfl2 <- function() {
  the$favorite_letters
}

#' Cambiar mis letras favoritas
#' @export
set_mfl2 <- function(l = letters[24:26]) {
  old <- the$favorite_letters
  the$favorite_letters <- l
  invisible(old)
}

Ahora un usuario puede registrar sus letras favoritas:

mfl2()
#> [1] "a" "b" "c"

set_mfl2(c("j", "f", "b"))

mfl2()
#> [1] "j" "f" "b"

Tenga en cuenta que este nuevo valor para the$favorite_letters persiste solo durante el resto de la sesión actual de R (o hasta que el usuario llame a set_mfl2() nuevamente). Más precisamente, el estado alterado persiste sólo hasta la próxima vez que se carga el paquete (incluso a través de load_all()). En el momento de la carga, el entorno the se restablece a un entorno que contiene exactamente un objeto, llamado favorite_letters, con valor (“a”, “b”, “c”). Es como la película El día de la marmota. (Discutiremos datos más persistentes específicos de paquetes y usuarios en la siguiente sección).

Jim Hester presentó a nuestro grupo la ingeniosa idea de utilizar “el” como nombre de un entorno de paquete interno. Esto le permite referirse a los objetos internos de una manera muy natural, como the$token, que significa “the token”. También es importante especificar parent = vacíoenv() al definir un entorno interno, ya que generalmente no desea que el entorno herede de ningún otro entorno (no vacío).

Como se ve en el ejemplo anterior, la definición del entorno debe realizarse como una asignación de nivel superior en un archivo debajo de R/. (En particular, esta es una razón legítima para definir una no función en el nivel superior de un paquete; consulte la sección Sección 6.4 para saber por qué esto debería ser poco común). En cuanto a dónde colocar esta definición, hay dos consideraciones:

  • Defínelo antes de usarlo. Si otras llamadas de nivel superior se refieren al entorno, la definición debe aparecer primero cuando el código del paquete se ejecuta en el momento de la compilación. Es por eso que R/aaa.R es una opción común y segura.

  • Facilite su búsqueda más adelante cuando esté trabajando en funciones relacionadas. Si un entorno solo es utilizado por una familia de funciones, defínalo allí. Si el uso del entorno se distribuye alrededor del paquete, defínalo en un archivo con connotaciones para todo el paquete.

A continuación se muestran algunos ejemplos de cómo los paquetes utilizan un entorno interno:

  • googledrive: Varias funciones necesitan conocer el ID del archivo del directorio de inicio del usuario actual en Google Drive. Esto requiere una llamada API (una operación relativamente costosa y propensa a errores) que produce una cadena deslumbrante de ~40 caracteres aparentemente aleatorios que solo una computadora puede amar. Sería inhumano esperar que un usuario sepa esto o lo pase a cada función. También sería ineficiente redescubrir la identificación repetidamente. En cambio, Googledrive determina el ID cuando lo necesita por primera vez y luego lo almacena en caché para su uso posterior.
  • usethis: La mayoría de las funciones necesitan conocer el proyecto activo, es decir, a qué directorio apuntar para modificar el archivo. Este suele ser el directorio de trabajo actual, pero no es un uso invariable en el que pueda confiar. Un diseño potencial es hacer posible especificar el proyecto de destino como argumento de cada función en uso. Pero esto crearía un desorden significativo en la interfaz de usuario, así como inquietud interna. En cambio, determinamos el proyecto activo en la primera necesidad, lo almacenamos en caché y proporcionamos métodos para (re)configurarlo.

La publicación del blog Variables de todo el paquete/Caché en paquetes R ofrece un desarrollo más detallado de esta técnica.

7.5 Datos de usuario persistentes

A veces hay datos que obtiene su paquete, en nombre de sí mismo o del usuario, que deberían persistir incluso entre sesiones de R. Esta es nuestra última forma y probablemente la menos común de almacenar datos de paquetes. Para que los datos persistan de esta manera, deben almacenarse en el disco y la gran pregunta es dónde escribir dicho archivo.

Este problema no es exclusivo de R. Muchas aplicaciones necesitan dejar notas para sí mismas. Es mejor cumplir con convenciones externas, que en este caso significa la Especificación del directorio base XDG. Debe utilizar las ubicaciones oficiales para el almacenamiento persistente de archivos, porque es algo responsable y cortés y también para cumplir con las políticas de CRAN.

Envío a CRAN

No se pueden simplemente escribir datos persistentes en el directorio de inicio del usuario. Aquí hay un extracto relevante de la política de CRAN al momento de escribir este artículo:

Los paquetes no deben escribirse en el espacio de archivos de inicio del usuario (incluidos los portapapeles) ni en ningún otro lugar del sistema de archivos aparte del directorio temporal de la sesión de R. …

Para R versión 4.0 o posterior (por lo tanto, se requiere una dependencia de versión o solo es posible un uso condicional), los paquetes pueden almacenar datos específicos del usuario, archivos de configuración y caché en sus respectivos directorios de usuario obtenidos de tools::R_user_dir(), siempre que que por [sic] los tamaños predeterminados se mantengan lo más pequeños posible y los contenidos se administren activamente (incluida la eliminación de material obsoleto).

La función principal que debe utilizar para derivar ubicaciones aceptables para los datos del usuario es tools::R_user_dir()3. Aquí hay algunos ejemplos de las rutas de archivos generadas.:

tools::R_user_dir("pkg", which = "data")
#> [1] "/home/runner/.local/share/R/pkg"
tools::R_user_dir("pkg", which = "config")
#> [1] "/home/runner/.config/R/pkg"
tools::R_user_dir("pkg", which = "cache")
#> [1] "/home/runner/.cache/R/pkg"

Una última cosa que debe considerar con respecto a los datos persistentes es: ¿estos datos realmente necesitan persistir? ¿Realmente necesitas ser el responsable de almacenarlo?

Si los datos son potencialmente confidenciales, como las credenciales del usuario, se recomienda obtener el consentimiento del usuario para almacenarlos, es decir, requerir consentimiento interactivo al iniciar el caché. Considere también que el sistema operativo del usuario o las herramientas de línea de comandos podrían proporcionar un medio de almacenamiento seguro superior a cualquier solución de bricolaje que pueda implementar. Los paquetes keyring, gitcreds, y credentials son ejemplos de paquetes que aprovechan herramientas proporcionadas externamente. Antes de embarcarse en cualquier solución creativa para almacenar secretos, considere que probablemente sea mejor invertir su esfuerzo en integrarlo con una herramienta establecida.


  1. Si no sabe mucho sobre los entornos R y lo que los hace especiales, un gran recurso es el capítulo Entornos de R Avanzado.↩︎

  2. Este ejemplo se ejecutará sin errores si define favorite_letters, mfl() y set_mfl() en el espacio de trabajo global y llama a set_mfl() en la consola. Pero este código fallará una vez que favorite_letters, mfl() y set_mfl() estén definidos dentro de un paquete.↩︎

  3. Tenga en cuenta que tools::R_user_dir() apareció por primera vez en R 4.0. Si necesita admitir versiones anteriores de R, debe usar el [paquete rapppdirs] (https://rappdirs.r-lib.org), que es una adaptación del módulo appdirs de Python y que sigue la política tidyverse con respecto al soporte de la versión R, lo que significa que la versión R mínima admitida está avanzando y eventualmente pasará de R 4.0. rappdirs produce rutas de archivo diferentes a las de tools::R_user_dir(). Sin embargo, ambas herramientas implementan algo que es consistente con la especificación XDG, solo que con diferentes opiniones sobre cómo crear rutas de archivos más allá de lo que dicta la especificación.↩︎