Martín

R · Estadística · Miscelánea

Principios de creación de paquetes en R

Martín Macías / 2018-04-02


La motivación de crear paquetes en R nace a partir de la necesidad de reunir en un solo lugar, funciones que sean útiles para tareas específicas. Dos ejemplos claros son los paquetes broman y Hmisc de Karl Broman y Frank Harrell, respectivamente. Estos dos paquetes contienen funciones que esos dos estadísticos han utilizado en el desarrollo de sus proyectos.

Como lo dije en un anterior post, inicialmente voy a basarme en el blog Not so standard deviations Writing an R package from scratch de Hilary Parker. Luego, usaré información del blog de Matthew James Denny R Package Pictorial.

Existen dos caminos para crear un paquete en R. El primero es “a pedal”, es decir, utilizando las funciones de las librerías devtools y roxygen2 y el segundo es con la ayuda de RStudio.

Primer camino

Para este camino, se supone que usted no maneja cómodamente RStudio y, por ende, no sabe crear proyectos, ni mucho menos paquetes.

Paso 0: Paquetes necesarios

Los paquetes necesarios para crear un paquete en R son devtools y roxygen2. Aquí se recomienda que descargue la versión en desarrollo de roxygen2.

install.packages("devtools")
library("devtools")
devtools::install_github("klutometis/roxygen")
library(roxygen2)

Paso 1: Crear un directorio para su paquete

Cree un directorio donde almacenará la información de su paquete. Ese directorio no debe estar dentro de un proyecto de RStudio.

setwd("ruta_del_directorio_donde_guarda_sus_funciones")
create("paqueteR")

cuya salida debería ser similar a:

Creating package 'paqueteR' in '/Users/martin/funciones'
No DESCRIPTION found. Creating with values:


Package: paqueteR
Title: What the Package Does (one line, title case)
Version: 0.0.0.9000
Authors@R: person("First", "Last", email = "first.last@example.com", role = c("aut", "cre"))
Description: What the package does (one paragraph).
Depends: R (>= 3.4.3)
License: What license is it under?
Encoding: UTF-8
LazyData: true
* Creating `martinR.Rproj` from template.
* Adding `.Rproj.user`, `.Rhistory`, `.RData` to ./.gitignore

Si busca en su directorio principal, ahora tendrá una carpeta llamada paqueteR, y dentro de ella tendrá una carpeta llamada R, un archivo llamado DESCRIPTION, otro llamado NAMESPACE y un proyecto llamado paqueteR.Rproj. Puede editar el archivo DESCRIPTION para incluir toda su información de contacto, etc.

Paso 2: Agregue funciones

Si está leyendo esto, probablemente tenga funciones para las que quiso crear un paquete. Copie esas funciones en su carpeta R.

Paso 3: Agregue documentación

El paquete roxygen2 hace que todo sea increíble, simple y sencillo. La forma en que funciona es que agrega comentarios especiales al comienzo de cada función, que luego se compilarán en el formato correcto para la documentación del paquete. Los detalles se pueden encontrar en la documentación de roxygen2; solo proporcionaré un ejemplo para nuestra función.

Los comentarios que debe agregar al principio de la función son, por ejemplo, los siguientes:

#' Una función que limpia una cadena de caracteres, removiendo puntuación y números y tokenizándola
#' 
#' @param str Una cadena de caracteres de entrada tal como "This is a cool function!" 
#' @return Un vector que contiene todos los tokens válidos de la cadena de caracteres de entrada original
#' @export

Quizás sea conveniente crear un nuevo archivo para cada función, pero si prefiere simplemente puede crear nuevas funciones secuencialmente en un archivo, solo asegúrese de agregar los comentarios de la documentación antes de cada función.

Paso 4: Procese su documentación

Ahora necesita crear la documentación de sus anotaciones antes. Ya ha realizado el trabajo “difícil” en el Paso 3. El paso 4 es tan fácil al hacer esto:

setwd("./paqueteR")
document()

cuya salida debería ser similar a esto:

Updating paqueteR documentation
Loading paqueteR
Updating roxygen version in /Users/martin/funciones/paqueteR/DESCRIPTION
Writing NAMESPACE

Esto agrega automáticamente los archivos .Rd al directorio `man, y actualiza el archivo NAMESPACE en el directorio principal. Puede leer más sobre esto, pero en términos de pasos a seguir, realmente no tiene que hacer nada más.

Paso 5: Instalación

¡Ahora es tan simple como instalar el paquete! Debe ejecutar esto desde el directorio de trabajo principal que contiene la carpeta paqueteR.

setwd("..")
install("paqueteR")

cuya salida sería más o menos así:

Installing paqueteR
'/Library/Frameworks/R.framework/Resources/bin/R' --no-site-file  \
  --no-environ --no-save --no-restore --quiet CMD INSTALL  \
  '/Users/martin/funciones/paqueteR'  \
  --library='/Library/Frameworks/R.framework/Versions/3.4/Resources/library'  \
  --install-tests 

* installing *source* package ‘paqueteR’ ...
** R
** preparing package for lazy loading
** help
No man pages found in package  ‘paqueteR’ 
*** installing help indices
** building package indices
** testing if installed package can be loaded
* DONE (paqueteR)
Reloading installed paqueteR

Ahora tiene un paquete en R real, en vivo y funcionando. Por ejemplo, intente escribir ?Clean_String. Debería ver aparecer la página de ayuda estándar.

Paso 6: Haga que el paquete sea un repositorio de GitHub

El beneficio de poner el paquete en Github es que puede usar la función install_github() para instalar su nuevo paquete directamente desde la página de Github.

Segundo camino

Paso 0: Cree un repositorio en Github

Conviene crear un repositorio en Github con el mismo nombre del paquete que se creará en RStudio. Cuando se cree, es mejor que no inicialice el repositorio con README.

Paso 1: Cree un paquete en RStudio

Para crear paquetes en RStudio, es conveniente revisar este tutorial..

Seleccione New Project..., luego New Directory, después R Package. Una vez esté allí, necesitará escribir el nombre de su paquete y seleccionar un directorio para alojar su proyecto. Póngale el mismo nombre del repositorio de Github que acabó de crear, ¡No se complique! Podría, también, manualmente escoger algunos archivos en R que contengan las funciones que desee incluir dentro de su paquete. Resalte el cuadro de diálogo para crear un repositorio de Git.

Podría agregar las funciones que desee que tenga su paquete o simplemente hacer Commit a toda esta información, agregar un mensaje al Commit y hacer Push para que el paquete quede alojado en su repositorio de Github y, más bien, después le hace las modificaciones e inserciones a su paquete.

Paso 2: Conecte su paquete con GitHub

Si bien usted ya le hace control de versiones a su paquete, esto sólo es localmente. La idea es que aproveche las ventajas que brinda Github, así que el siguiente paso es conectar su paquete con Github.

Haga click en los dos cuadritos morados y un rombo blanco en el panel de Git. Verá que se activa un cuadro de diálogo llamado “New branch”, haga click en la casilla “Add Remote”, digite en “Remote Name” origin y en “Remote URL”, pegue la dirección del repositorio de Github. Haga click en “Add” (puede que se demore un poco en estar disponible).

Ahora está de vuelta en el cuadro de diálogo “New Branch”, en el campo “Branch” digite master y luego haga click en “Create”. Le aparecerá un cuadro de diálogo, escoja “Overwrite”.

Paso 3: Documente su paquete

Si no incluyó archivos con sus funciones en formato .R, este es el momento de agregarlos manualmente a la carpeta R.

Ahora puede editar la información del archivo DESCRIPTION y diligenciar los campos con texto informativo sobre su paquete. Piense en algo similar a esto:

Package: cleanStringR
Type: Package
Title: Un paquete de ejemplo que hace tal cosa
Version: 0.0.1
Date: 2018-04-06
Author: Daniel Rodríguez <peluche_rosadito@gmail.com>
Maintainer: Martín Macías <martinandresmacias@gmail.com>
Description: Provee funciones para limpiar cadenas
License: GPL2
LazyData: TRUE

Luego, querrá asegurarse de documentar adecuadamente todas las funciones que desee que sean visibles para los usuarios del paquete (se describe en detalle a continuación). Luego podrá usar la función devtools::document() para generar automáticamente archivos de ayuda para cada una de estas funciones y la función devtools::use_package() para crear una dependencia en DESCRIPTION entre su paquete y otro del cual se tenga referencia.

devtools::use_package("stringr")
devtools::document()

Fíjese en el archivo DESCRIPTION y verá que se añadió la línea RoxygenNote: 6.0.1 que confirma la documentación de su paquete.

Paso 4: Documente sus funciones

Por lo pronto copie, pegue y edite inmediatamente encima de su función en el archivo en R donde la tenga. En la sección Object Document del libro de Wickham está detalladamente explicado.

#' Una función que limpia una cadena de caracteres, removiendo puntuación y números y tokenizándola
#' 
#' @param str Una cadena de caracteres de entrada tal como "This is a cool function!" 
#' @return Un vector que contiene todos los tokens válidos de la cadena de caracteres de entrada original
#' @export

Una vez haya terminado de editar su función, digite en la consola de R, el comando que a continuación se da y que permite documentar apropiadamente la función:

devtools::document()

Paso 5: Construya e instale el paquete

Ahora puede avanzar y hacer clic en la pestaña Build en RStudio y luego en el botón Install and Restart en esta pestaña y R creará su paquete y lo cargará en su sistema.

Note que se han agregado más archivos al directorio del paquete (los archivos de ayuda para sus funciones).

Ahora debería poder acceder a su paquete en su equipo local utilizando el comando estándar library(mypackage). El siguiente paso es subir su paquete actualizado a Github. Si todo salió bien, otras personas podrán descargar e instalar su paquete instalando primero devtools y luego ejecutando el siguiente comando:

devtools::install_github("suUsernameEnGithub/NombredelPaquete")

Dependiencias

Si las funciones que queremos utilizar requieren acceso a otros paquetes, podemos asegurarnos de que se agreguen como dependencias (que se descargarán automáticamente con el paquete) ejecutando la siguiente línea de código para cada paquete que deseemos requerir (con el nombre del paquete apropiado insertado):

devtools::use_package("stringr")

Tenga en cuenta que la mejor manera de acceder a la funcionalidad que proporcionan estos paquetes es hacer referencia explícita a sus subfunciones cuando las necesitemos en lugar de cargar todo el paquete como se muestra en el siguiente ejemplo:

stringr::str_replace_all(temp,"[^a-zA-Z\s]", " ")

Esto puede parecer tedioso al principio, pero terminará reduciendo conflictos y haciendo su vida más fácil a largo plazo. Es posible que también desee asegurarse de que sus usuarios tengan instalada una versión más nueva de R. Puede hacer esto agregando la siguiente línea a su archivo DESCRIPTION

Depends: R (>= 3.0.1)

Aquí hay un ejemplo de las importaciones y dependencias en el archivo DESCRIPTION para uno de los paquetes en los que uno puede estar trabajando:

Imports:
        Rcpp,
        RcppArmadillo,
        BH,
        ggplot2,
        methods,
LinkingTo:
        BH,
        Rcpp,
        RcppArmadillo
Depends:
        R (>= 3.0.1)

Documentación

Podemos utilizar el impresionante paquete roxygen2 para hacer toda la documentación de la función si nos atenemos al siguiente formato de comentario para las definiciones de funciones en nuestros archivos fuente R:

#' Una función que sirve para limpiar cademas de entrada, removiendo la puntuación y los números.
#' 
#' @param str Una simple cadena tal como "¡Esta es una función chévere!" 
#' @return Un vector que contiene todos los tokens válidos en cadena de entrada original
#' @export
Clean_String <- function(str){
    ...

La primera línea debe incluir una breve descripción general de lo que hace la función. Luego, para cada parámetro, podemos incluir en la función que queremos incluir una declaración @param, seguida de un espacio, seguido del nombre del parámetro exactamente como aparece en nuestra función, seguido de un espacio, seguido de una descripción de lo que ese parámetro puede ser. Es posible que también deseemos incluir una declaración @return que le indique al usuario qué devuelve la función. Puntualmente, si queremos que el usuario pueda acceder a la función en R, incluiremos un @export en la línea directamente antes de que comience la definición de la función. Una vez que hemos documentado cada función a la que deseamos que el usuario tenga acceso de esta manera (las funciones internas no necesitan ser documentadas), podemos ejecutar el siguiente comando para generar automáticamente archivos de ayuda para nuestras funciones:

devtools::document()

Siguiendo la sugerencia del libro de R Packages de Hadley Wickham, también querrá incluir un archivo en R (posiblemente con el mismo nombre que su paquete; prefiero llamar al mío Package_Documentation.R - Realmente no importa cómo lo llame) en la carpeta R en su directorio de paquetes. En este archivo puede documentar todo el paquete en sí y no solo las funciones individuales. También puede incluir otras declaraciones útiles que se encargarán automáticamente de la configuración para usar el código C++ (por ejemplo), lo que significa que no tiene que acordarse de hacer estas cosas a mano. Aquí hay un ejemplo muy básico de cómo podría verse esto:

#' MyPackage: ¡Un paquete para estimar algo chévere!
#'
#' @section MyPackage functions:
#' Dar un resumen de sus funciones aquí
#'
#' @docType package
#' @name MyPackage
NULL
#> NULL

#' @import methods
NULL

#' @useDynLib GERGM
#' @importFrom Rcpp sourceCpp
NULL

En realidad, debería consultar el capítulo del libro de Hadley Wickham para saber cómo hacerlo, ya que es mucho más minucioso, pero este código al menos puede ayudarte a empezar. Tenga en cuenta que las últimas tres líneas son necesarias si desea incluir código C++ en su paquete, y la declaración de los métodos @import es necesaria si desea crear su propia clase de objeto para su paquete.

Datos de ejemplo

A menudo será útil agregar datos de ejemplo a su paquete, ya que esto permitirá a los usuarios asegurarse de que entienden el formato que desean sus funciones y cuál debería ser su resultado. Afortunadamente, esto es bastante fácil, ya que el paquete devtools proporciona una función para agregar datos a su paquete y garantizar que esté en el formato correcto. Lo primero que debe hacer es cargar todos los objetos de datos que desea incluir con el paquete en su sesión R. Cada objeto debe tener el nombre que desea que tenga cuando el usuario lo vea, y cada objeto se almacenará como un archivo .rda con el mismo nombre en el directorio ./data. A continuación, puede agregar los datos mediante el siguiente comando:

devtools::use_data(mis_datos_1,mis_datos_2, mis_datos_3)

Puede incluir tantos objetos como desee, pero probablemente sea una buena idea no incluir grandes conjuntos de datos, ya que esto hará que la instalación de su paquete sea realmente lenta. Una vez que haya ejecutado las líneas de código anteriores, puede ir al directorio y verificar que todo esté en el lugar correcto.