Cuaderno de ejercicios
Todos los ejercicios y desafíos del taller en una sola página
Para ver las soluciones hacé clic en Solución. Te recomendamos mirarla solo después de intentarlo. Si te trabás, poné tu nota adhesiva visible, asi te podemos ayudar.
1. ¿Qué es un paquete?
Desafío 1.1: ¿Qué hay en un paquete? 5 minutos, en el documento colaborativo
¿Cuál de estas afirmaciones sobre los paquetes de R es correcta?
- Un paquete solo puede tener funciones.
- Para crear un paquete hay que publicarlo en CRAN.
- Un paquete es una carpeta organizada de una manera específica, que puede tener código, documentación y datos.
- Para crear un paquete hay que saber programar en C.
Escribí tu respuesta (la letra) en el documento colaborativo.
La respuesta correcta es la c.
- Si elegiste la a: los paquetes también pueden tener datos, documentación, tests, plantillas, aplicaciones Shiny…
- Si elegiste la b: CRAN es solo uno de los lugares donde se pueden publicar paquetes. Un paquete puede vivir en tu computadora, en GitHub o en R-universe, y sigue siendo un paquete.
- Si elegiste la d: algunos paquetes usan C o C++ para ser más rápidos, pero la gran mayoría está escrita solo en R.
Desafío 1.2: Tu código repetido 3 minutos
Pensá en algún código que hayas copiado y pegado en más de un proyecto: una función, una limpieza de datos, un gráfico con tus colores favoritos, unos datos que siempre usás.
Escribilo en una línea en el documento colaborativo. ¡Ese puede ser tu primer paquete!
2. Crear el paquete
Desafío 2.1: Crear tu paquete 10 minutos
- Comprobá el nombre con
pak::pkg_name_check("dichosos"). - Creá el paquete con
usethis::create_package(). Guardalo en una carpeta que encuentres fácil, por ejemplo enDocumentoso en tu carpeta de proyectos, pero no adentro de otro proyecto de RStudio. - En la nueva sesión de RStudio, corré
devtools::check(). - Agregá la licencia MIT y volvé a correr
devtools::check()hasta ver 0 errores y 0 warnings.
pak::pkg_name_check("dichosos")
usethis::create_package("~/Documents/dichosos") # o la ruta que elijas
# En la sesión nueva, la del paquete:
devtools::check()
usethis::use_mit_license("Tu Nombre")
devtools::check()Si te aparece una NOTE que dice unable to verify current time, no te preocupes: pasa cuando no hay conexión a internet o la red es lenta.
Desafío 2.2: ¿Quién es quién? 5 minutos, en parejas
Uní cada archivo o carpeta con lo que hace.
| Archivo o carpeta | Qué hace | |
|---|---|---|
1. DESCRIPTION |
a. Lista las funciones que el paquete exporta. No se edita a mano. | |
2. R/ |
b. Lista archivos que R ignora cuando construye el paquete. | |
3. NAMESPACE |
c. Tiene los metadatos: nombre, título, autoría, licencia, dependencias. | |
4. .Rbuildignore |
d. Guarda los scripts con las funciones. | |
5. dichosos.Rproj |
e. Es el archivo del proyecto de RStudio. |
1-c, 2-d, 3-a, 4-b, 5-e.
Desafío 2.3: Presentá tu paquete 5 minutos
Abrí el archivo DESCRIPTION y completá:
Title: una línea, con mayúscula en cada palabra importante y sin punto final.Description: un párrafo que explique qué hace el paquete.Authors@R: tu nombre y tu correo.
Corré devtools::check() otra vez. ¿Cambió algo?
Package: dichosos
Title: Dichos y Expresiones Populares de America Latina
Version: 0.0.0.9000
Authors@R:
person("Yanina", "Bellini Saibene", , "tu.correo@ejemplo.com",
role = c("aut", "cre"))
Description: Un paquete de ejemplo para aprender a desarrollar paquetes
de R. Incluye dichos y expresiones populares de America Latina y
funciones para elegirlos al azar y buscarlos por palabra.
License: MIT + file LICENSE
check() debería seguir dando 0 errores y 0 warnings. Estos campos no se comprueban por su contenido, pero son lo primero que ve cualquier persona que mira tu paquete.
3. Nuestra primera función
Desafío 3.1: Tu primera función en un paquete 8 minutos
- Creá el archivo de la función con
usethis::use_r("dicho_al_azar"). - Escribí la función
dicho_al_azar()(podés copiarla de los snippets). - Agregá al vector un dicho de tu país o de algún otro lado pero que te guste.
- Guardá, cargá el paquete con
devtools::load_all()y llamá a la función varias veces.
usethis::use_r("dicho_al_azar")En R/dicho_al_azar.R:
dicho_al_azar <- function() {
dichos <- c(
"Estar al horno",
"Perdido como perro en cancha de bochas",
"Pasarlo chancho",
"Un dicho tuyo" # <- ¡el tuyo!
)
sample(dichos, size = 1)
}En la consola:
devtools::load_all()
dicho_al_azar()
dicho_al_azar()Desafío 3.2: Poné los pasos en orden 3 minutos
Estos son los pasos para agregar una función nueva a un paquete, pero están desordenados. Ordenalos.
devtools::load_all()- Escribir la función en el archivo.
usethis::use_r("nombre_funcion")- Probar la función en la consola.
- Guardar el archivo.
usethis::use_r("nombre_funcion")- Escribir la función en el archivo.
- Guardar el archivo.
devtools::load_all()- Probar la función en la consola.
Y si algo no anda: volver al paso 2. ¡Es un ciclo!
Desafío 3.3: ¿Qué va a pasar? 4 minutos
Agregá otro dicho al vector y guardá el archivo, pero no corras load_all(). Antes de llamar a dicho_al_azar() muchas veces, pensá: ¿va a aparecer el dicho nuevo?
Probalo. Después corré load_all() y volvé a probar.
Sin load_all(), R sigue usando la versión de la función que cargó la última vez: el dicho nuevo no aparece nunca. Guardar el archivo no alcanza; hay que volver a cargar el paquete. Este es uno de los errores más comunes cuando empezamos a desarrollar paquetes: “¡cambié el código y no pasa nada!”.
4. Documentar funciones
Desafío 4.1: Documentá dicho_al_azar() 8 minutos
- Poné el cursor adentro de la función e insertá el esqueleto de roxygen2 (Code > Insert Roxygen Skeleton).
- Completá el título, la descripción,
@returny@examples. - Corré
devtools::document()y mirá la ayuda con?dicho_al_azar. - Abrí el archivo
NAMESPACE. ¿Qué cambió?
#' Elegir un dicho al azar
#'
#' Devuelve un dicho popular de América Latina elegido al azar.
#'
#' @return Un texto (vector de caracteres) con el dicho.
#' @export
#'
#' @examples
#' dicho_al_azar()
dicho_al_azar <- function() {
# ...
}devtools::document()
?dicho_al_azarNAMESPACE ahora tiene la línea export(dicho_al_azar), gracias a la etiqueta @export.
Desafío 4.2: Encontrá los errores 5 minutos, en parejas
Esta documentación tiene tres errores. ¿Cuáles son?
#' Elegir un dicho al azar
#
#' Devuelve un dicho popular elegido al azar.
#'
#' @params pais El país del que queremos el dicho.
#' @return Un texto con el dicho.
#'
#' @examples
#' dicho_al_azar()
dicho_al_azar <- function(pais = NULL) {
# ...
}- La segunda línea empieza con
#en lugar de#'. Para roxygen2, ahí termina el bloque. - La etiqueta es
@param, en singular, no@params. - Falta
@export. Sin esa etiqueta, la función no se exporta: no se puede usar conlibrary(dichosos)después de instalar el paquete.
Desafío 4.3 (si te sobra tiempo o para hacerlo en casa): La ayuda por dentro 2 minutos
Abrí la carpeta man/ y el archivo dicho_al_azar.Rd. ¿Lo escribiste vos? ¿Lo deberías editar?
Lo escribió roxygen2, a partir de tus comentarios. La primera línea dice % Generated by roxygen2: do not edit by hand. Si lo editás a mano, la próxima vez que corras document() se pierden los cambios. Siempre se edita el comentario #' en el archivo .R.
5. Datos en el paquete
Desafío 5.1: Datos adentro del paquete 10 minutos
- Corré
usethis::use_data_raw("dichos"). - Descargá dichos.csv y guardalo en la carpeta
data-raw/de tu paquete. - Completá
data-raw/dichos.Rpara leer el CSV y guardarlo conusethis::use_data(). Corré el script. - Corré
devtools::load_all()y mirá los datos:head(dichos),table(dichos$pais).
usethis::use_data_raw("dichos")
download.file(
"https://yabellini.github.io/desarrollo_paquetes/datos/dichos.csv",
"data-raw/dichos.csv"
)En data-raw/dichos.R:
dichos <- read.csv("data-raw/dichos.csv", encoding = "UTF-8")
usethis::use_data(dichos, overwrite = TRUE)source("data-raw/dichos.R")
devtools::load_all()
head(dichos)
table(dichos$pais)Desafío 5.2: Completá los espacios 7 minutos
Esta nueva versión de dicho_al_azar() usa los datos y tiene un argumento pais. Completá los ____, guardá, cargá y probá con dicho_al_azar(pais = "Chile").
#' Elegir un dicho al azar
#'
#' Devuelve un dicho elegido al azar del conjunto de datos [dichos].
#'
#' @param ____ Texto con el nombre de un país, por ejemplo `"Argentina"`.
#' Si es `NULL` (el valor por defecto) se elige entre todos los países.
#'
#' @return Un texto (vector de caracteres de largo 1) con el dicho.
#' @export
#'
#' @examples
#' dicho_al_azar()
#' dicho_al_azar(pais = "Argentina")
dicho_al_azar <- function(pais = ____) {
opciones <- dichos
if (!is.null(pais)) {
opciones <- dichos[dichos$____ == pais, ]
}
fila <- sample(nrow(____), size = 1)
opciones$____[fila]
}#' @param pais Texto con el nombre de un país, ...
dicho_al_azar <- function(pais = NULL) {
opciones <- dichos
if (!is.null(pais)) {
opciones <- dichos[dichos$pais == pais, ]
}
fila <- sample(nrow(opciones), size = 1)
opciones$dicho[fila]
}No te olvides de devtools::document(): cambiamos la documentación.
Desafío 5.3: ¿Y si el país no existe? 3 minutos
Probá dicho_al_azar(pais = "Narnia"). ¿Qué devuelve? ¿Es útil para quien usa el paquete? Escribí en el documento colaborativo qué te gustaría que pase.
Devuelve character(0): un vector de texto vacío, sin ningún aviso. La función “falla en silencio”, y quien la usa no sabe qué hizo mal. Lo vamos a arreglar en el próximo episodio con un mensaje de error claro.
Desafío 5.4 (para llevar a casa): Sumá un dicho de tu país 5 minutos
- Agregá una fila a
data-raw/dichos.csvcon un dicho de tu país. - Volvé a correr
data-raw/dichos.R. - Si agregaste un país nuevo, actualizá el número de filas en la documentación de
R/data.R. - Corré
devtools::load_all()y buscá tu dicho.
Compartilo en el documento colaborativo: ¡juntamos todos los dichos del taller!
6. Dependencias y mensajes claros
Desafío 6.1: ¿Imports o Suggests? 3 minutos
Para cada caso, ¿la dependencia va en Imports o en Suggests?
- Tu función usa
cli::cli_abort()para avisar errores. - Usás
testthatpara los tests del paquete. - Usás rmarkdown solo para construir el README.
- Tu función principal filtra datos con
dplyr::filter().
- Imports: sin
cli, la función no puede funcionar. - Suggests: los tests solo los corre quien desarrolla el paquete.
- Suggests (o ni siquiera eso:
README.Rmdestá en.Rbuildignore). - Imports.
Regla práctica: si tu paquete no funciona sin esa dependencia, va en Imports.
Desafío 6.2: Un error que ayude 10 minutos
- Agregá cli como dependencia con
usethis::use_package("cli"). - Modificá
dicho_al_azar()para que, si el país no está en los datos, dé un error concli::cli_abort()que diga qué país pidió la persona y cuáles están disponibles. - Cargá y probá con
dicho_al_azar(pais = "Narnia"). - Corré
devtools::check(). Si aparece un WARNING por caracteres no ASCII, arreglalo.
dicho_al_azar <- function(pais = NULL) {
opciones <- dichos
if (!is.null(pais)) {
if (!pais %in% dichos$pais) {
cli::cli_abort(c(
"No tengo dichos de {.val {pais}}.",
"i" = "Los pa\u00edses disponibles son: {.val {unique(dichos$pais)}}."
))
}
opciones <- dichos[dichos$pais == pais, ]
}
fila <- sample(nrow(opciones), size = 1)
opciones$dicho[fila]
}devtools::load_all()
dicho_al_azar(pais = "Narnia")
#> Error in `dicho_al_azar()`:
#> ! No tengo dichos de "Narnia".
#> ℹ Los países disponibles son: "Argentina", "Brasil", "Chile", ...Si escribiste países con tilde, check() da un WARNING. La tilde se escribe \u00ed.
Desafío 6.3: ¿Qué está mal? 2 minutos
Una persona escribió esto en R/dicho_del_dia.R. ¿Qué le dirías?
library(cli)
dicho_del_dia <- function(fecha = Sys.Date()) {
if (!inherits(fecha, "Date")) {
cli_abort("{.arg fecha} tiene que ser una fecha.")
}
# ...
}Nunca uses library() adentro del código de un paquete. En un paquete, el código de R/ se corre cuando se construye el paquete, no cuando se usa, y library() cambiaría la sesión de quien lo usa. Lo correcto: declarar la dependencia con usethis::use_package("cli") y llamar a la función con cli::cli_abort().
7. Tests
Desafío 7.1: Tus primeros tests 8 minutos
- Corré
usethis::use_testthat(3)yusethis::use_test("dicho_al_azar"). - Escribí un test que compruebe que
dicho_al_azar()devuelve un solo texto. - Escribí un test que compruebe que, para un país que no existe, la función da un error.
- Corré los tests con
devtools::test().
test_that("dicho_al_azar() devuelve un solo texto", {
resultado <- dicho_al_azar()
expect_type(resultado, "character")
expect_length(resultado, 1)
})
test_that("dicho_al_azar() avisa si no conoce el país", {
expect_error(dicho_al_azar(pais = "Narnia"), "Narnia")
})devtools::test()
#> [ FAIL 0 | WARN 0 | SKIP 0 | PASS 3 ]Desafío 7.2: Rompé el paquete a propósito 3 minutos
En R/dicho_al_azar.R, cambiá size = 1 por size = 2. Antes de correr los tests, pensá: ¿qué test va a fallar?
Corré devtools::test(), leé el mensaje y después volvé a dejar size = 1.
Falla expect_length(resultado, 1), porque ahora la función devuelve dos dichos:
── Failure: dicho_al_azar() devuelve un solo texto ──
`resultado` has length 2, not length 1.
Romper el código a propósito es una forma de comprobar que los tests sirven.
Desafío 7.3: Una función nueva con su test para quien termine antes
Escribí la función buscar_dicho(palabra), que devuelve las filas de dichos donde la palabra aparece en el dicho o en el significado. Pista: grepl(palabra, dichos$dicho, ignore.case = TRUE).
Documentala, escribí un test con usethis::use_test("buscar_dicho") y corré devtools::check().
#' Buscar dichos por palabra
#'
#' Busca una palabra en los dichos y en sus significados.
#'
#' @param palabra Texto a buscar. No distingue mayúsculas de minúsculas.
#'
#' @return Un data frame con las filas de [dichos] que contienen la palabra.
#' @export
#'
#' @examples
#' buscar_dicho("dinero")
buscar_dicho <- function(palabra) {
encontrados <- grepl(palabra, dichos$dicho, ignore.case = TRUE) |
grepl(palabra, dichos$significado, ignore.case = TRUE)
dichos[encontrados, ]
}test_that("buscar_dicho() encuentra dichos", {
resultado <- buscar_dicho("dinero")
expect_s3_class(resultado, "data.frame")
expect_gt(nrow(resultado), 0)
})
test_that("buscar_dicho() no distingue mayúsculas", {
expect_equal(buscar_dicho("PERRO"), buscar_dicho("perro"))
})La versión completa, con un mensaje cuando no encuentra nada, está en el paquete terminado.
8. Compartir el paquete
Desafío 8.1: Un README y a instalar 10 minutos
- Creá el README con
usethis::use_readme_rmd(). - Escribí una oración sobre qué hace el paquete y un ejemplo con
dicho_al_azar(). - Construilo con
devtools::build_readme(). - Instalá el paquete con
devtools::install(). - Reiniciá R, o mejor, abrí otro proyecto, y escribí
library(dichosos)ydicho_al_azar().
¡Si te salió un dicho, ya tenés tu primer paquete instalado! 🎉
# dichosos
¡Dichos y expresiones populares de América Latina, directo a tu consola de R!
## Ejemplo
::: {.cell}
```{.r .cell-code}
library(dichosos)
dicho_al_azar(pais = "Chile")
```
:::devtools::build_readme()
devtools::install()En otra sesión:
library(dichosos)
dicho_al_azar()Desafío 8.2 (opcional, si tenés GitHub configurado): Al mundo 5 minutos
usethis::use_git()y aceptá hacer el primer commit.usethis::use_github().- Pegá el enlace a tu repositorio en el documento colaborativo.
- Instalá el paquete de otra persona del taller con
pak::pak("usuario/dichosos"). ¿Qué dichos agregó?
Desafío 8.3: Para cerrar 3 minutos
En el documento colaborativo (o en tus notas adhesivas):
- Para mantener: una cosa que aprendiste hoy y te gustó.
- Para mejorar: una cosa que mejorarías del taller, o que todavía no te quedó clara.
Y una más: ¿qué código tuyo vas a convertir en paquete?
9. La IA en el ciclo de desarrollo
Desafío 9.1: ¿Dónde sí y dónde no? 5 minutos, en parejas
Para cada tarea de dichosos, decidí: ¿le darías el trabajo a una herramienta de IA, la usarías solo para un borrador que después revisás, o la harías vos?
- Elegir el nombre del paquete.
- Escribir el código de
buscar_dicho(). - Escribir los significados de los dichos de tu país.
- Escribir los tests de
dicho_del_dia(). - Arreglar el WARNING de caracteres no ASCII.
- Escribir el README.
- Responder un issue de otra persona que encontró un error en tu paquete.
Anotá tus respuestas en el documento colaborativo. No hay una sola respuesta correcta: lo interesante es el por qué.
- Vos. La IA no sabe si un nombre es una mala palabra en tu país (
pkg_name_check()tampoco), y el nombre es una decisión de diseño. - IA, y después vos. Pedile algo específico, probalo en la consola con
load_all()y leé cada línea. Si no entendés el código, no lo agregues. - Vos. Los datos son tu responsabilidad. Un modelo de lenguaje puede inventar un dicho o un significado con total seguridad, y en
@sourcedice de dónde salieron. - IA, con supervisión. A los agentes les encanta escribir tests: tu trabajo es que sean pocos, claros y que de verdad fallen cuando algo se rompe.
- IA. Es un problema mecánico, con un mensaje de
check()claro. Las herramientas de IA son muy buenas arreglando lo que encuentracheck(). - Borrador. La IA puede armar la estructura, pero el README es comunicación entre personas: revisalo, escribilo con tu voz y contá que usaste IA.
- Vos. Podés usar IA para entender el error o probar un arreglo, pero la respuesta es comunicación personal: la escribís vos. Si la usaste para traducir, está bien, y lo decís. Son pautas que pedimos en rOpenSci.
Desafío 9.2: Revisá lo que escribió la IA 7 minutos
Le pedimos a un asistente de IA: “Agregá a dichosos una función que devuelva los dichos de un tema”. Esto es lo que escribió. Con lo que aprendiste hoy, encontrá al menos cinco problemas.
library(dplyr)
# Esta función recibe un tema y devuelve los dichos de ese tema.
# Primero filtra los datos por tema y después devuelve el resultado.
dichos_por_tema <- function(tema) {
if (!tema %in% dichos$tema) {
stop("No hay dichos de ese tema. Probá con otro.")
}
dichos %>% filter(tema == tema)
}Y en tests/testthat/test-dichos_por_tema.R:
test_that("dichos_por_tema() funciona", {
expect_true(TRUE)
resultado <- dichos_por_tema("dinero")
expect_s3_class(resultado, "data.frame")
expect_equal(nrow(resultado), 6)
expect_equal(ncol(resultado), 4)
expect_equal(names(resultado), c("dicho", "significado", "pais", "tema"))
expect_true(all(resultado$tema == "dinero"))
expect_error(dichos_por_tema("dinosaurios"))
})En la función:
library(dplyr)en el código del paquete. Nunca. Hay que declarar la dependencia conusethis::use_package("dplyr")y usardplyr::filter(). O mejor: ¿hace falta dplyr? Con R base alcanza (dichos[dichos$tema == tema, ]) y el paquete tiene una dependencia menos.filter(tema == tema)no filtra nada. Adentro defilter(), los dostemason la columna, así que la condición siempre es verdadera y devuelve todos los dichos. Es el tipo de error que solo aparece si probás la función en la consola.- No tiene documentación de roxygen2 ni
@export. - “Probá” tiene tilde en el código:
check()da un WARNING. VaProb\u00e1. - El error no ayuda: no dice qué tema se pidió ni cuáles existen. Mejor
cli::cli_abort()con la lista de temas, como hicimos endicho_al_azar(). - Los comentarios explican el qué, no el por qué. Si el código se entiende, sobran.
En el test:
expect_true(TRUE)no comprueba nada.expect_equal(nrow(resultado), 6)se rompe cada vez que alguien suma un dicho de dinero. Es un test frágil.- Es un solo test gigante. Mejor varios
test_that()cortos, cada uno con una descripción de lo que comprueba. - Lo bueno:
all(resultado$tema == "dinero")sí detecta el error del punto 2… pero solo si alguien corre los tests. Revisar lo que escribe la IA incluye correr los tests y probar en la consola.
Desafío 9.3 (opcional, con GitHub Copilot Free u otro asistente): Pedilo bien para casa
Escribí un pedido corto y preciso para que un asistente agregue dichos_por_tema() a tu paquete. Con tu cuenta de GitHub podés usar el chat de Copilot en https://github.com/copilot. Por ejemplo:
Agregá a dichosos la función
dichos_por_tema(tema)enR/dichos_por_tema.R. Usá R base, sin dependencias nuevas. Si el tema no existe, dá un error concli::cli_abort()que liste los temas disponibles, como endicho_al_azar(). Documentala con roxygen2 y exportala. Escribí tests cortos entests/testthat/test-dichos_por_tema.R. Antes de escribir código, mostrame un plan.
Después: leé el plan, pedile el código, copialo a tu paquete, revisá los cambios con Git, probá la función con load_all() y corré devtools::check(). ¿Qué hizo distinto a lo que habrías hecho vos?