Skip to content

R develpment notes

Espacio para seguir la guía de R Packages.

Setup

install.packages(c("devtools", "roxygen2", "testthat", "knitr"))

install.packages("rstudioapi")
rstudioapi::isAvailable("0.99.149") # Should be TRUE when run from RStudio

devtools::install_github("r-lib/devtools")

library(devtools)
has_devel() # Checks if build dependencies are OK

dir.create("Projects/Rdevel")
devtools::create("Projects/Rdevel/cellMagick")

dir()
# [1] "cellMagick.Rproj" "DESCRIPTION"      "NAMESPACE"        "R"

Workflow

The first practical advantage to using a package is that it’s easy to re-load your code. You can either run devtools::load_all(), or in RStudio press Ctrl/Cmd + Shift + L, which also saves all open files, saving you a keystroke.

Crear R/hw.R

helloWorld <- function() print("Hola!")

Apretar Ctrl + Shift + L y ejecutar helloWorld() en la consola.

The R landscape

There are some functions that modify global settings that you should never use because there are better alternatives.

  • Don’t use library() or require()
  • Never use source()
  • If you modify global options() or graphics par(), save the old values and reset when you’re done.
  • Avoid modifying the working directory.

Loading package side-effects

You might need to do some initial setup when the package loads. To do that, you can use (define) two special functions: .onLoad() and .onAttach().

CRAN notes

you must use only ASCII characters in your .R files [or] use the special unicode escape "\u1234" format.

DESCRIPTION

Existe Imports: y Suggests:, y se puede manejar la falta de un paquete sugerido:

# There's a fallback method if the package isn't available
my_fun <- function(a, b) {
  if (requireNamespace("pkg", quietly = TRUE)) {
    pkg::f()
  } else {
    g()
  }
}

There are actually many other rarely, if ever, used fields. A complete list can be found in the “The DESCRIPTION file” section of the R extensions manual.

LICENCE

Me gustó LGPL porque se puede usar en otros proyectos pero no requiere que todos los proyectos donde se usa sean también libres.

Documentación

R provides a standard way of documenting the objects in a package: you write .Rd files in the man/ directory.

Workflow

  1. Add roxygen comments '# to your .R files.
  2. Run devtools::document() or do a full rebuild with Ctrl + Shift + B.
  3. Preview documentation with ?

Rinse and repeat.

roxygen

Ver como usar comentario '#' en http://r-pkgs.had.co.nz/man.html#roxygen-comments

#' Say hi!
#' @param dummy A dummy parameter that does nothing. It's super effective!
#' @examples
#' helloWorld(":)")
#' @export
#' @seealso \link{print}
helloWorld <- function(dummy) print("Hola!")

Funciones con puntos en el nombre (S3 confusion)

Las funciones con "." en sus nombres pueden exportarse como S3methods después de un devtools::document().

Ver: https://stackoverflow.com/a/24607763/11524079

Para prevenirlo, al lado del @export tag hay que poner el nombre de la función en cuestión.

Documenting packages

There’s no object that corresponds to a package, so you need to document NULL.

I usually put this documentation in a file called .R.

#' foo: A package for computating the notorious bar statistic.
#'
#' The foo package provides three categories of important functions:
#' foo, bar and baz.
#' 
#' @section Foo functions:
#' The foo functions ...
#'
#' @docType package
#' @name foo
NULL

NAMESPACE

… in this chapter you’ll learn how to generate the NAMESPACE file with roxygen2

- Add roxygen '# comments to your .R files. You only need to learn one tag, @export.

  1. Run devtools::document() (or press Ctrl + Shift + D in RStudio)

A Shiny Package

https://github.com/mangothecat/shinyAppDemo

Packaging C code

Info sobre C en la wiki:

Patience...

Info sobre makefiles:

Ejemplos

Ejemplos de otros paquetes para revisar:

Incluir libraries del sistema

Como encontrar los paths a las libraries

$ pkg-config --libs glib-2.0 
-lglib-2.0 
$ pkg-config --cflags glib-2.0       
-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include 

Makevars de ejemplo

PKG_CFLAGS se usa para incluir archivos .h.

PKG_LIBS se usa para incluir "C libraries", o sea, archivos .so (librerias dinamicas) o .a (librerias estaticas).

Entonces, para incluir glib, en el ./src/Makevars del paquete se puede escribir:

  PKG_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include
  PKG_LIBS=-lglib-2.0

Pero alcanza con

  PKG_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include

O también

  PKG_CPPFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include

Para incluir libtiff también:

  PKG_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include
  PKG_LIBS=-lglib-2.0 -ltiff

Compilar e incluir código de libraries en el paquete

Si los archivos de una library están "incluidos" en el paquete, se los "incluye" usando los paths relativos correctos.

Además, hay que decirle a R que compile las libraries, agregando una tareas ("targets") al archivo src/Makevars.

Por ejemplo para compilar libtiff, incluirla y limpiar después se puede hacer lo siguiente.

Incluir headers y libraries

PKG_CPPFLAGS, PKG_LIBS y otras flags:

CC=ccache clang -Qunused-arguments
CXX=ccache clang++ -Qunused-arguments
CCACHE_CPP2=yes

# Include de archivos ".h"
PKG_CPPFLAGS= -I./tiff410/include
# Links a archivos ".so" (las libraries)
PKG_LIBS= -L./tiff410/r_build/libtiff -ltiff -ljpeg -llzma -lz

MAKEFLAGS = -j4

Crear "targets" de compilación. Los "targets" son normalmente tareas de compilación que terminan en la creación de un archivo, o simplemente tareas sin output si se las especifica como .PHONY.

Cada target puede tener otros targets como pre-requisito, y esto es necesario para que CellID se compile después de libtiff. En este caso CellID está representado como target en la variable $(SHLIB).

# https://makefiletutorial.com/
# A Makefile consists of a set of rules. A rule generally looks like this:
# 
# targets : prerequisites
#    command
#    command
#    command

# Adding .PHONY to a target will prevent make from confusing the phony target with a file name. 
# For example, if the file “clean” is created somehow elsewhere, "make clean" will still be run.
.PHONY: all tiflibs rbuildcleanup

all: $(SHLIB) rbuildcleanup

$(SHLIB): tiflibs

# Note that each command is run independently, that's why there is a "cd" before each one.
# These lines tell cmake to build inside the r_build directory, keeping everything else tidy, and easy to cleanup.
tiflibs:
    cd tiff410/r_build && cmake --clean-first ../
    cd tiff410/r_build && make all -j4 -B
    cd tiff410/r_build && cmake --target clean ../

# The "cleanup" target must depend on the rest in order to be execured last in a parallel compilation
rbuildcleanup: $(SHLIB)
    rm -rf tiff410/r_build/*

Conditional includes

CC=ccache clang -Qunused-arguments
CXX=ccache clang++ -Qunused-arguments
CCACHE_CPP2=yes

PKG_LIBS = -ltiff

# See "1.2.1 Using Makevars" at https://cran.r-project.org/doc/manuals/r-devel/R-exts.html#Using-Makevars
# Sell also: What is "all"? https://stackoverflow.com/a/22735335
.PHONY: all testing_is_fun
#       May be important to add: ".DEFAULT_GOAL := all"

# If you want to create and then link to a library, say using code in a subdirectory, use something like:
all: $(SHLIB)
$(SHLIB): testing_is_fun

# Test warnings
# $(warning Makevars debug warning: value of SHLIB is "$(SHLIB)")
$(warning checking libtiff)
TIFFSTATUS := $(shell $(LD) -ltiff || echo 1)
ifeq ($(TIFFSTATUS), 1)
$(warning TIFF library not found $(PKG_LIBS))
# OBJECTS := testing.o

testing_is_fun:
    mv testing.c cell.c

else
$(warning TIFF library found)
# OBJECTS := align_image.o cell.o contiguous.o date_and_time.o fft.o fft_stats.o fit.o fl_dist.o flatten.o nums.o oif.o segment.o split_and_overlap.o testing.o tif.o

testing_is_fun:
    rm testing.c

endif

# $(warning the value of "OBJECTS" is: "$(OBJECTS)")
# testing_is_fun:
#   echo Testing is fun!
#   # the value of "SHLIB" is: "$(SHLIB)"
#   # the value of ".DEFAULT_GOAL" is: "$(.DEFAULT_GOAL)"
#   echo the value of "OBJECTS" is: "$(OBJECTS)"
#   # the value of "LD" is: "$(LD)"
#   # TIFFSTATUS is: $(TIFFSTATUS)

References

Referencias y recursos

https://web.archive.org/web/20190831012258/http://www.libtiff.org/index.html

http://gnuwin32.sourceforge.net/packages/tiff.htm

https://stackoverflow.com/a/57677953

https://stackoverflow.com/a/1618618

http://mingw-w64.org/doku.php/download

https://cygwin.com/install.html elegir todo lo que diga tiff con fuente e instalar cygwin

https://medium.com/@meghamohan/all-about-static-libraries-in-c-cea57990c495

https://www.geeksforgeeks.org/static-vs-dynamic-libraries/

https://stackoverflow.com/questions/58156585/build-rocksdb-static-library-inside-r-package

https://stackoverflow.com/questions/53631025/rcpp-install-package-with-static-libraries-for-platform-independent-usage

https://github.com/rwinlib/libtiff

https://stackoverflow.com/questions/53631025/rcpp-install-package-with-static-libraries-for-platform-independent-usage

https://stackoverflow.com/questions/58808393/how-do-i-create-an-r-package-that-depends-on-a-shared-library-with-rcpp

"As will the simpler approach of maybe just putting the source files of libbcd into the package src/ directory -- and R will take care of the rest."

Debugfear Makefile o Makevars

https://www.oreilly.com/openbook/make3/book/ch12.pdf

The warning function is very useful for debugging wayward makefiles.

$(warning A top-level warning)

Descifrar código en C

Vale ayudarse de https://cdecl.org/

.C - Interfaz a C en R

Parece que R espera que la función de C no cambie la estructura de los argumentos que se definieron en el wrapper. Pasé un día entero intentando entender cómo arreglar un problema y llegué a esa conclusión.

Otros links útiles para entender el * y el & de C o C++ y entender qué onda:

Non standard evaluation

arrange

Como convertir nombres de variables en "strings" a "símbolos" para funciones tipo-tidy.

Ver:

  • ?as.symbol
  • ?sym
time_colum <- "t.frame"

arrange(cdata, !!as.symbol(time_colum))

arrange(cdata, !!sym(time_colum))

Ver:

filter

filter_starwars <- function(...) {
  F <- quos(...)
  filter(starwars, !!!F)
}

filter_starwars(species == 'Human', homeworld %in% c('Tatooine', 'Alderaan'), height > 175)

The big-bang operator !!! forces-splice a list of objects.

You can supply several expressions directly, e.g. quos(foo, bar), but more importantly you can also supply dots: quos(...).