Objetivo del manual

  • Familiarizarse con los bloques básicos usados en la programación en R

  • Conocer las fuentes principales de documentación estandarizada en R


 

¿Qué es R?

  • Un Lenguaje de Dominio Específico (DSL) libre para la estadística y el análisis de datos
  • Una colección de más de 18695 paquetes (al sep-21-2022)
  • Una comunidad grande y activa en la industria y el mundo académico
  • Una forma de hablar “directamente” con su ordenador

Históricamente:

  • Basado en el lenguaje de programación S
  • Alrededor de 20 años de antigüedad (el linaje data de 1975 - hace casi 40 años)

R

 

Rstudio

Entorno de desarrollo integrado (IDE) para R. Incluye:

  • Una consola
  • Editor para resaltar la sintaxis que soporta la ejecución directa del código
  • Herramientas para el trazado, el historial, la depuración y la gestión del espacio de trabajo

R

 


Elementos del lenguaje R

  • Vectores
  • Listas
  • Matrices
  • Marcos de datos (data frames)
  • Funciones (incluyendo operadores)
  • Tablas (tables)
  • Atributos
  • Arreglos (arrays)
  • Ambientes

 

Estructura básica de la representación de datos

La estructura de datos básica en R es el vector. Hay dos tipos básicos de vectores: vectores atómicos y listas.

Tienen tres propiedades comunes:

  • Tipo, typeof() (clase/modo ~)
  • Longitud, length() (número de elementos)
  • Attributes, attributes() (metadatos)

Se diferencian en los tipos de sus elementos: todos los elementos de un vector atómico deben ser del mismo tipo, mientras que los elementos de una lista pueden tener diferentes tipos

Homogéneo Heterogéneo
1d Vector atómico Lista
2d Matriz Cuadro de datos (data frame)
nd arreglo (Array)

 

Los números o cadenas individuales son en realidad vectores de longitud uno.

 

Vectores atomicos

Tipos de vectores atómicos:

  • Lógico (booleano)
  • Entero
  • Numérico (doble)
  • Caracteres

Los vectores se construyen con la función c():


x <- 1
x1 <- c(1)

all.equal(x, x1)
## [1] TRUE

class(x)
## [1] "numeric"

y <- "something"

class(y)
## [1] "character"

w <- 1L

class(w)
## [1] "integer"

z <- TRUE

class(z)
## [1] "logical"

q <- factor(1)

class(q)
## [1] "factor"

 

Los vectores sólo pueden contener elementos del mismo tipo. Los tipos diferentes de elementos serán forzados al tipo más flexible:


v <- c(10, 11, 12, 13)

class(v)
## [1] "numeric"

typeof(v)
## [1] "double"

is.integer(v)
## [1] FALSE

y <- c("Amazona", "Ara", "Eupsittula", "Myiopsitta")

class(y)
## [1] "character"

is.integer(y)
## [1] FALSE

x <- c(1, 2, 3, "Myiopsitta")

x
## [1] "1"          "2"          "3"          "Myiopsitta"

class(x)
## [1] "character"

 

Los valores que faltan se especifican con NA, que es un vector lógico de longitud 1. NA siempre será interpretado al tipo correcto si se utiliza dentro de c():


v <- c(10, 11, 12, 13, NA)

class(v)
## [1] "numeric"

v <- c(letters[1:3], NA)

class(v)
## [1] "character"

 

Listas

Puede contener objetos de diferentes clases y tamaños. Las listas se construyen con list():

l <- list(ID = c("a", "b", "c", "d", "e"), size = c(1, 2, 3, 4, 5),
    observed = c(FALSE, TRUE, FALSE, FALSE, FALSE))

l
## $ID
## [1] "a" "b" "c" "d" "e"
## 
## $size
## [1] 1 2 3 4 5
## 
## $observed
## [1] FALSE  TRUE FALSE FALSE FALSE
class(l)
## [1] "list"
str(l)
## List of 3
##  $ ID      : chr [1:5] "a" "b" "c" "d" ...
##  $ size    : num [1:5] 1 2 3 4 5
##  $ observed: logi [1:5] FALSE TRUE FALSE FALSE FALSE

… y dimensiones:

l <- list(ID = c("a", "b", "c", "d", "e"), size = c(1, 2, 3, 4, 5,
    6), observed = c(FALSE, TRUE, FALSE, FALSE, FALSE), l)

str(l)
## List of 4
##  $ ID      : chr [1:5] "a" "b" "c" "d" ...
##  $ size    : num [1:6] 1 2 3 4 5 6
##  $ observed: logi [1:5] FALSE TRUE FALSE FALSE FALSE
##  $         :List of 3
##   ..$ ID      : chr [1:5] "a" "b" "c" "d" ...
##   ..$ size    : num [1:5] 1 2 3 4 5
##   ..$ observed: logi [1:5] FALSE TRUE FALSE FALSE FALSE
l2 <- list(l, l)

str(l2)
## List of 2
##  $ :List of 4
##   ..$ ID      : chr [1:5] "a" "b" "c" "d" ...
##   ..$ size    : num [1:6] 1 2 3 4 5 6
##   ..$ observed: logi [1:5] FALSE TRUE FALSE FALSE FALSE
##   ..$         :List of 3
##   .. ..$ ID      : chr [1:5] "a" "b" "c" "d" ...
##   .. ..$ size    : num [1:5] 1 2 3 4 5
##   .. ..$ observed: logi [1:5] FALSE TRUE FALSE FALSE FALSE
##  $ :List of 4
##   ..$ ID      : chr [1:5] "a" "b" "c" "d" ...
##   ..$ size    : num [1:6] 1 2 3 4 5 6
##   ..$ observed: logi [1:5] FALSE TRUE FALSE FALSE FALSE
##   ..$         :List of 3
##   .. ..$ ID      : chr [1:5] "a" "b" "c" "d" ...
##   .. ..$ size    : num [1:5] 1 2 3 4 5
##   .. ..$ observed: logi [1:5] FALSE TRUE FALSE FALSE FALSE

 

Nombrar elementos

Los vectores pueden ser nombrados de tres maneras:

  • Al crearlo: x <- c(a = 1, b = 2, c = 3).
  • Al modificar un vector existente en su lugar: x <- 1:3; names(x) <- c("a", "b", "c") O bien: x <- 1:3; names(x)[[1]] <- c("a")
  • Creando una copia modificada de un vector: x <- setNames(1:3, c("a", "b", "c"))
y <- c(a = 1, 2, 3)

names(y)
## [1] "a" ""  ""
v <- c(1, 2, 3)

names(v) <- c("a")

names(v)
## [1] "a" NA  NA
z <- setNames(1:3, c("a", "b", "c"))

names(z)
## [1] "a" "b" "c"

 

Factores

Los atributos se utilizan para definir los factores. Un factor es un vector que sólo puede contener valores predefinidos y se utiliza para almacenar datos categóricos.

Los factores se construyen sobre vectores enteros utilizando dos atributos:

  • clase “factor”: hace que se comporten de forma diferente a los vectores enteros normales
  • niveles: define el conjunto de valores permitidos
x <- factor(c("a", "b", "b", "a"))
x
## [1] a b b a
## Levels: a b
levels(x)
## [1] "a" "b"
str(x)
##  Factor w/ 2 levels "a","b": 1 2 2 1

 

Los factores parecen vectores de caracteres, pero en realidad son números enteros:

x <- factor(c("a", "b", "b", "a"))

c(x)
## [1] a b b a
## Levels: a b

 

Matrices

Todas los elementos son del mismo tipo:

m <- matrix(c(1, 2, 3, 11, 12, 13), nrow = 2)

dim(m)
## [1] 2 3
m
##      [,1] [,2] [,3]
## [1,]    1    3   12
## [2,]    2   11   13
class(m)
## [1] "matrix" "array"
m <- matrix(c(1, 2, 3, 11, 12, "13"), nrow = 2)
m
##      [,1] [,2] [,3]
## [1,] "1"  "3"  "12"
## [2,] "2"  "11" "13"

 

Se puede crear modificando el atributo de dimensión:

c <- 1:6

is.matrix(c)
## [1] FALSE
attributes(c)
## NULL
dim(c) <- c(3, 2)

c
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6
is.matrix(c)
## [1] TRUE
attributes(c)
## $dim
## [1] 3 2

 

Cuadros de datos (Data frames)

Caso especial de las listas. Puede contener elementos de diferentes tipos:

m <- data.frame(ID = c("a", "b", "c", "d", "e"), size = c(1, 2, 3,
    4, 5), observed = c(FALSE, TRUE, FALSE, FALSE, FALSE))

dim(m)
## [1] 5 3
m
ID size observed
a 1 FALSE
b 2 TRUE
c 3 FALSE
d 4 FALSE
e 5 FALSE
class(m)
## [1] "data.frame"
is.data.frame(m)
## [1] TRUE
is.list(m)
## [1] TRUE
str(m)
## 'data.frame':    5 obs. of  3 variables:
##  $ ID      : chr  "a" "b" "c" "d" ...
##  $ size    : num  1 2 3 4 5
##  $ observed: logi  FALSE TRUE FALSE FALSE FALSE

 

Pero los vectores deben tener la misma longitud:

m <- data.frame(ID = c("a", "b", "c", "d", "e"), size = c(1, 2, 3,
    4, 5, 6), observed = c(FALSE, TRUE, FALSE, FALSE, FALSE))
## Error in data.frame(ID = c("a", "b", "c", "d", "e"), size = c(1, 2, 3, : arguments imply differing number of rows: 5, 6

 


Extraer subconjuntos usando indexación (indexing)

Los elementos dentro de los objetos pueden ser llamados por medio de la indexación. Para sub-conjuntar un vector simplemente llame a la posición del objeto usando corchetes:

x <- c(1, 3, 4, 10, 15, 20, 50, 1, 6)

x[1]
## [1] 1
x[2]
## [1] 3
x[2:3]
## [1] 3 4
x[c(1, 3)]
## [1] 1 4

 

Los elementos se pueden eliminar de la misma manera:

x[-1]
## [1]  3  4 10 15 20 50  1  6
x[-c(1, 3)]
## [1]  3 10 15 20 50  1  6

 

Las matrices y los marcos de datos requieren 2 índices [fila, columna]:

m <- matrix(c(1, 2, 3, 11, 12, 13), nrow = 2)

m[1, ]
## [1]  1  3 12
m[, 1]
## [1] 1 2
m[1, 1]
## [1] 1
m[-1, ]
## [1]  2 11 13
m[, -1]
##      [,1] [,2]
## [1,]    3   12
## [2,]   11   13
m[-1, -1]
## [1] 11 13
df <- data.frame(family = c("Psittacidae", "Trochilidae", "Psittacidae"),
    genus = c("Amazona", "Phaethornis", "Ara"), species = c("aestiva",
        "philippii", "ararauna"))

df
family genus species
Psittacidae Amazona aestiva
Trochilidae Phaethornis philippii
Psittacidae Ara ararauna
df[1, ]
family genus species
Psittacidae Amazona aestiva
df[, 1]
## [1] "Psittacidae" "Trochilidae" "Psittacidae"
df[1, 1]
## [1] "Psittacidae"
df[-1, ]
family genus species
Trochilidae Phaethornis philippii
Psittacidae Ara ararauna
df[, -1]
genus species
Amazona aestiva
Phaethornis philippii
Ara ararauna
df[-1, -1]
genus species
Phaethornis philippii
Ara ararauna
df[, "family"]
## [1] "Psittacidae" "Trochilidae" "Psittacidae"
df[, c("family", "genus")]
family genus
Psittacidae Amazona
Trochilidae Phaethornis
Psittacidae Ara

 

Las listas requieren 1 índice entre dobles corchetes [[índice]]:

l <- list(ID = c("a", "b", "c", "d", "e"), size = c(1, 2, 3, 4, 5),
    observed = c(FALSE, TRUE, FALSE, FALSE, FALSE))

l[[1]]
## [1] "a" "b" "c" "d" "e"
l[[3]]
## [1] FALSE  TRUE FALSE FALSE FALSE

 

Los elementos dentro de las listas también pueden ser subconjuntos en la misma cadena de código:

l[[1]][1:2]
## [1] "a" "b"
l[[3]][2]
## [1] TRUE

 

Explorar objectos

str(df)
## 'data.frame':    3 obs. of  3 variables:
##  $ family : chr  "Psittacidae" "Trochilidae" "Psittacidae"
##  $ genus  : chr  "Amazona" "Phaethornis" "Ara"
##  $ species: chr  "aestiva" "philippii" "ararauna"
names(df)
## [1] "family"  "genus"   "species"
dim(df)
## [1] 3 3
nrow(df)
## [1] 3
ncol(df)
## [1] 3
head(df)
family genus species
Psittacidae Amazona aestiva
Trochilidae Phaethornis philippii
Psittacidae Ara ararauna
tail(df)
family genus species
Psittacidae Amazona aestiva
Trochilidae Phaethornis philippii
Psittacidae Ara ararauna
table(df$genus)
## 
##     Amazona         Ara Phaethornis 
##           1           1           1
typeof(df)
## [1] "list"
View(df)

 

Ejercicio 1

 

  • Utilice los datos de ejemplo iris para crear un subconjunto de datos con sólo las observaciones de la especie setosa

  • Ahora cree un subconjunto de datos que contenga las observaciones tanto de “setosa” como de “versicolor”

  • También con iris cree un subconjunto de datos con las observaciones para las que iris$Sepal.length es mayor que 6

  • ¿Cuántas observaciones tienen una longitud de sépalo superior a 6?

 


 

Funciones

Todas las funciones se crean con la función function() y siguen la misma estructura:

R

* Modified from Grolemund 2014  

R viene con muchas funciones que puedes usar para hacer tareas sofisticadas:

# built in functions
bi <- builtins()

length(bi)
## [1] 1371
sample(bi, 10)
##  [1] "print.libraryIQR"      ".Deprecated"          
##  [3] ".getRequiredPackages2" "as.character.default" 
##  [5] "closeAllConnections"   "assign"               
##  [7] "max"                   "bindtextdomain"       
##  [9] "rowMeans"              ".First"

 

Los operadores son funciones:

1 + 1
## [1] 2
1 + 1
## [1] 2
2 * 3
## [1] 6
2 * 3
## [1] 6

 

Operadores mas utilizados

Operadores aritméticos:

Operador Descrición
+ suma
- resta
* multiplicación
/ división
^ or ** exponente

 

1 - 2
## [1] -1
1 + 2
## [1] 3
2^2
## [1] 4
2^2
## [1] 4
2:3 %in% 2:4
## [1] TRUE TRUE

 

Operadores lógicos:

Operador Descrición
< menor que
<= menor o igual que
> mayo que
>= mayor o igual que
== exactamente igual que
!= diferente que
!x No es x
x | y x O y
x & y x Y y
x %in% y correspondencia

 

1 < 2
## [1] TRUE
1 > 2
## [1] FALSE
1 <= 2
## [1] TRUE
1 == 2
## [1] FALSE
1 != 2
## [1] TRUE
1 > 2
## [1] FALSE
5 %in% 1:6
## [1] TRUE
5 %in% 1:4
## [1] FALSE

 

La mayoría de las funciones están vectorizadas:

1:6 * 1:6

R

* Modified from Grolemund & Wickham 2017

 

## [1]  1  4  9 16 25 36
1:6 - 1:6
## [1] 0 0 0 0 0 0

R recicla vectores de longitud desigual:

1:6 * 1:5

R

* Modified from Grolemund & Wickham 2017

 

## Warning in 1:6 * 1:5: longitud de objeto mayor no es
## múltiplo de la longitud de uno menor
## [1]  1  4  9 16 25  6
1:6 + 1:5
## Warning in 1:6 + 1:5: longitud de objeto mayor no es
## múltiplo de la longitud de uno menor
## [1]  2  4  6  8 10  7

 


El estilo importa

Based on google’s R Style Guide

Nombres de archivos

Los nombres de los archivos deben terminar en .R y, por supuesto, ser auto-explicatorios:

  • Bien: graficar_probabilidad_posterior.R
  • Mal: graf.R

Nombres de objetos

Variables y funciones:

  • Minúsculas
  • Utilice un guión bajo
  • En general, nombres para las variables y verbos para las funciones
  • Procure que los nombres sean concisos y significativos (no siempre es fácil)
  • Avoid using names of existing functions of variables
  - Bien: dia_uno: dia_1, peso_promedio(),
  
  - Mal: diauno, dia1, primer.dia_delmes, mean <- function(x) sum(x), c <- 10

 

Syntaxis

Espacios

  • Utilice espacios alrededor de los operadores y para los argumentos dentro de una función
  • Ponga siempre un espacio después de una coma, y nunca antes (como en el inglés normal)
  • Coloque un espacio antes del paréntesis izquierdo, excepto en una llamada a una función
  - Bien: 
          a <- rnorm(n = 10, sd = 10, mean = 1)
          tab.prior <- table(df[df$dias < 0, "campaign.id"])
          total <- sum(x[, 1])
          total <- sum(x[1, ])
          if (debug)
          mean(1:10)
          
  - Mal: 
         a<-rnorm(n=10,sd=10,mean=1)
         tab.prior <- table(df[df$days.from.opt<0, "campaign.id"])  # necesita espacio alrededor de '<'
         tab.prior <- table(df[df$days.from.opt < 0,"campaign.id"])  # Necesita espacio despues de la coma
         tab.prior<- table(df[df$days.from.opt < 0, "campaign.id"])  # Necesita espacio antes de <-
         tab.prior<-table(df[df$days.from.opt < 0, "campaign.id"])  # Necesita espacio alrededor de <-
         total <- sum(x[,1])  # Necesita espacio antes de la coma  
         if(debug) # Necesita espacio antes del parentesis
         mean (1:10) # Espacio extra luego del nombre de la funcion

 

Corchetes

  • La llave de apertura nunca debe ir en su propia línea
  • La llave de cierre debe ir siempre en su propia línea
  • Puede omitir las llaves cuando un bloque consiste en una sola declaración
-Bien:if (is.null(ylim)) {
    ylim <- c(0, 0.06)
}

if (is.null(ylim)) ylim <- c(0, 0.06)

-Mal: -Mal:
if (is.null(ylim)) ylim <- c(0, 0.06)

if (is.null(ylim)) {
    ylim <- c(0, 0.06)
}

if (is.null(ylim)) {
    ylim <- c(0, 0.06)
}

 

Crear objetos

  • Use <-, no =
-GOOD:x <- 5

-BAD:x = 5

 

Sugerencias para añadir comentarios

  • Comente su código
  • Las líneas enteras comentadas deben comenzar con # y un espacio
  • Los comentarios cortos pueden colocarse después del código precedido por dos espacios, #, y luego un espacio
# Create histogram of frequency of campaigns by pct budget spent.
hist(df$pct.spent,
     breaks = "scott",  # method for choosing number of buckets
     main   = "Histograma: individuos por unidad de tiempo",
     xlab   = "Número de individuos",
     ylab   = "Frecuencia")

 

Disposición general y ordenación (estilo de google)

  • Comentario de la declaración de derechos de autor (?)
  • Comentario del autor
  • Comentario de la descripción del archivo, incluyendo el propósito del programa, las entradas y las salidas
  • declaraciones source() y library()
  • Definiciones de funciones
  • Sentencias ejecutadas, si procede (por ejemplo, print, plot)

 

Documentación de R

La mayoría de los recursos de R están muy bien documentados. Así que la primera fuente de ayuda a la que debe acudir cuando escriba código R es la propia documentación de R. Todos los paquetes están documentados de la misma manera estándar. Familiarizarse con el formato puede simplificar mucho las cosas.

Package documentation

R

 

Reference manuals

Los manuales de referencia son colecciones de la documentación de todas las funciones de un paquete (sólo 1 por paquete):

 

Documentación de las funciones

Todas las funciones (por defecto o de paquetes cargados) deben tener una documentación que siga un formato estándar:

`?`(mean)

help("mean")

R  

Esta documentación también puede mostrarse en Rstudio pulsando F1 cuando el cursor está en el nombre de la función

 

Si no recuerda el nombre de la función pruebe con apropos():

apropos("mean")
##  [1] ".colMeans"      ".rowMeans"      "colMeans"      
##  [4] "kmeans"         "mean"           "mean_cl_boot"  
##  [7] "mean_cl_normal" "mean_sdl"       "mean_se"       
## [10] "mean.Date"      "mean.default"   "mean.difftime" 
## [13] "mean.POSIXct"   "mean.POSIXlt"   "rowMeans"      
## [16] "weighted.mean"

 

Viñetas (vignettes)

Las viñetas son documentos ilustrativos o casos de estudio que detallan el uso de un paquete (opcional, pueden ser varios por paquete).

Las viñetas se pueden llamar directamente desde R:

vgn <- browseVignettes()
vignette()

También deberían aparecer en la página del paquete en CRAN.

 

Demonstraciones

Los paquetes también pueden incluir demostraciones de código extendidas (“demos”). Para listar las demos de un paquete ejecute demo("nombre del paquete"):

demo(package = "stats")

# call demo directly
demo("nlm")

 

Ejercicio 2

 

  • ¿Qué hace la función cut()?

  • ¿Para qué se utiliza el argumento breaks en cut()?

  • Ejecuta las 4 primeras líneas de código de los ejemplos proporcionados en la documentación de cut().

  • ¿Cuántas viñetas tiene el paquete warbleR?

 


Referencias


Información de la sesión

## R version 4.1.1 (2021-08-10)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.2 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=es_ES.UTF-8      
##  [2] LC_NUMERIC=C              
##  [3] LC_TIME=es_CR.UTF-8       
##  [4] LC_COLLATE=es_ES.UTF-8    
##  [5] LC_MONETARY=es_CR.UTF-8   
##  [6] LC_MESSAGES=es_ES.UTF-8   
##  [7] LC_PAPER=es_CR.UTF-8      
##  [8] LC_NAME=C                 
##  [9] LC_ADDRESS=C              
## [10] LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=es_CR.UTF-8
## [12] LC_IDENTIFICATION=C       
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets 
## [6] methods   base     
## 
## other attached packages:
## [1] emo_0.0.0.9000      viridis_0.6.2      
## [3] viridisLite_0.4.0   xaringanExtra_0.7.0
## [5] ggplot2_3.3.6       RColorBrewer_1.1-3 
## [7] kableExtra_1.3.4    knitr_1.39         
## 
## loaded via a namespace (and not attached):
##  [1] httr_1.4.3        sass_0.4.1       
##  [3] pkgload_1.2.4     jsonlite_1.8.0   
##  [5] bslib_0.3.1       brio_1.1.3       
##  [7] assertthat_0.2.1  highr_0.9        
##  [9] yaml_2.3.5        remotes_2.4.2    
## [11] sessioninfo_1.2.2 pillar_1.8.0     
## [13] glue_1.6.2        uuid_1.1-0       
## [15] digest_0.6.29     rvest_1.0.2      
## [17] colorspace_2.0-3  htmltools_0.5.3  
## [19] pkgconfig_2.0.3   devtools_2.4.3   
## [21] purrr_0.3.4       scales_1.2.0     
## [23] webshot_0.5.3     processx_3.6.1   
## [25] svglite_2.1.0     tibble_3.1.8     
## [27] farver_2.1.1      generics_0.1.2   
## [29] usethis_2.1.6     ellipsis_0.3.2   
## [31] cachem_1.0.6      withr_2.5.0      
## [33] cli_3.3.0         magrittr_2.0.3   
## [35] crayon_1.5.1      memoise_2.0.1    
## [37] evaluate_0.15     ps_1.7.1         
## [39] fs_1.5.2          fansi_1.0.3      
## [41] MASS_7.3-54       xml2_1.3.3       
## [43] pkgbuild_1.3.1    rsconnect_0.8.26 
## [45] tools_4.1.1       prettyunits_1.1.1
## [47] formatR_1.12      lifecycle_1.0.1  
## [49] stringr_1.4.0     munsell_0.5.0    
## [51] callr_3.7.0       compiler_4.1.1   
## [53] jquerylib_0.1.4   systemfonts_1.0.4
## [55] rlang_1.0.4       grid_4.1.1       
## [57] rstudioapi_0.13   htmlwidgets_1.5.4
## [59] crosstalk_1.2.0   labeling_0.4.2   
## [61] rmarkdown_2.14    testthat_3.1.4   
## [63] gtable_0.3.0      DBI_1.1.3        
## [65] R6_2.5.1          lubridate_1.8.0  
## [67] gridExtra_2.3     dplyr_1.0.9      
## [69] fastmap_1.1.0     utf8_1.2.2       
## [71] rprojroot_2.0.3   desc_1.4.1       
## [73] stringi_1.7.8     vctrs_0.4.1      
## [75] leaflet_2.1.1     tidyselect_1.1.2 
## [77] xfun_0.31