Reproducibilidad: renv y Docker

quarto
reproducibilidad
Lo que distingue un análisis reproducible de uno con buenas intenciones. renv para bloquear versiones de paquetes R, uv para Python, Docker para bloquear el sistema entero, y la decisión de cuánto rigor necesitas en cada caso.

Reproducibilidad: cuatro niveles

No todos los análisis necesitan el mismo rigor. Hay cuatro niveles, cada uno con coste creciente:

Nivel Qué bloquea Cómo
1. Código versionado El qué se hizo Git
2. Versiones de paquetes Las librerías exactas renv (R), uv/requirements.txt (Python)
3. Versión de R/Python El intérprete .Rprofile / pyproject.toml
4. Sistema completo OS, librerías de sistema Docker

Para análisis personal: nivel 1. Para análisis publicado o que pasa de un colaborador a otro: nivel 2 mínimo. Para análisis regulado (clínico, auditable): nivel 4.

renv: lockfile para R

renv es el sistema oficial moderno de Posit para gestionar dependencias de paquetes en R. Reemplaza al viejo packrat.

Inicializar

En la carpeta del proyecto:

install.packages("renv")
renv::init()

Esto:

  1. Crea una librería local del proyecto en renv/library/.
  2. Genera renv.lock con las versiones exactas de los paquetes que usa el código.
  3. Modifica .Rprofile para que al abrir el proyecto, R use la librería local.

A partir de aquí, todos los install.packages() van a la librería del proyecto, no a la global. Los proyectos quedan aislados.

El archivo renv.lock

{
  "R": {
    "Version": "4.4.1",
    "Repositories": [...]
  },
  "Packages": {
    "DESeq2": {
      "Package": "DESeq2",
      "Version": "1.44.0",
      "Source": "Bioconductor",
      "Hash": "abc123..."
    },
    "tidyverse": {
      "Package": "tidyverse",
      "Version": "2.0.0",
      ...
    }
  }
}

Cada paquete con versión + hash + fuente. Versionado en Git. Cualquiera que clone el repo puede restaurar el entorno exacto:

renv::restore()

Workflow típico

# Al instalar un paquete nuevo
install.packages("nuevo-paquete")

# Actualizar el lockfile
renv::snapshot()

snapshot() revisa qué paquetes usas (escaneando el código) y los añade al renv.lock. Es explícito, no graba cualquier paquete instalado, solo los referenciados desde tu código.

Para Quarto

renv se integra automáticamente con Quarto: al renderizar dentro de un proyecto con renv, usa la librería local. No requiere configuración adicional.

Quitar renv de un proyecto

Si decides que no compensa:

renv::deactivate()

Vuelve a la librería global. renv.lock se queda como historial pero no se aplica.

uv para Python

Para Python, uv es el equivalente moderno a renv. Más rápido que pip, más conciso que conda, gestiona todo: versiones de Python, librerías, virtualenvs.

Inicializar

uv init mi-proyecto
cd mi-proyecto

Estructura inicial:

mi-proyecto/
├── pyproject.toml      ← config del proyecto
├── uv.lock             ← lockfile
├── README.md
└── .python-version     ← versión Python pinneada

Añadir dependencias

uv add pandas numpy matplotlib seaborn
uv add jupyter           # para Quarto con Python

uv add descarga, instala en un venv del proyecto, y graba en pyproject.toml + uv.lock.

Restaurar en otra máquina

uv sync

Crea el venv, instala todo desde uv.lock. Reproducible y rápido (segundos).

Para Quarto

Activa el venv antes de renderizar:

uv run quarto render

uv run ejecuta el comando con el entorno del proyecto. Quarto usa el Python del venv automáticamente.

requirements.txt: el clásico

Si tu proyecto Python no usa uv, lo mínimo es requirements.txt:

pandas==2.1.0
numpy==1.26.0
matplotlib==3.8.0
seaborn==0.13.0
jupyter==1.0.0

Restaurar:

pip install -r requirements.txt

requirements.txt es menos riguroso que uv.lock, no bloquea sub-dependencias, no versiona Python. Para reproducibilidad real, uv o un lockfile generado con pip-compile (de pip-tools).

Versión de R y Python

renv graba la versión de R en renv.lock pero no la fuerza. Para bloquearla:

# .Rprofile
if (getRversion() != "4.4.1") {
    stop("Este proyecto requiere R 4.4.1, tienes ",
         getRversion())
}

Para Python con uv:

# pyproject.toml
[project]
requires-python = "==3.11.7"

O en .python-version:

3.11.7

uv honra esto y descarga la versión correcta si hace falta.

Docker: el nivel máximo

renv y uv bloquean paquetes y lenguaje. No bloquean:

  • Versión del sistema operativo.
  • Librerías de sistema (libxml2, openssl, etc.).
  • Versión de LaTeX para PDFs.
  • Versión de Quarto mismo.

Para análisis que tienen que reproducirse dentro de 5 años con bit-perfect output, Docker.

Imagen base

Para proyectos R + Quarto:

FROM rocker/r-ver:4.4.1

RUN apt-get update && apt-get install -y \
    libxml2-dev libcurl4-openssl-dev libssl-dev \
    libfontconfig1-dev \
    && rm -rf /var/lib/apt/lists/*

# Quarto
RUN curl -LO https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.57/quarto-1.5.57-linux-amd64.deb \
    && dpkg -i quarto-1.5.57-linux-amd64.deb \
    && rm quarto-1.5.57-linux-amd64.deb

# renv
RUN R -e 'install.packages("renv", repos = "https://cloud.r-project.org")'

WORKDIR /project
COPY . .

RUN R -e 'renv::restore()'

CMD ["quarto", "render"]

rocker/r-ver:4.4.1 es la imagen oficial de Posit con R fijado. Encima:

  1. Instala librerías de sistema necesarias para paquetes R.
  2. Instala Quarto en versión específica.
  3. Instala renv.
  4. Copia el proyecto y restaura dependencias.

Build y run:

docker build -t mi-analisis .
docker run --rm -v $PWD:/project mi-analisis

El render ocurre dentro del contenedor. Outputs van a tu carpeta local vía el volume mount.

Para Bioconductor

Hay imágenes oficiales de Bioconductor:

FROM bioconductor/bioconductor_docker:RELEASE_3_20

Trae R + Bioconductor + librerías de sistema esperables (rsamtools, samtools, etc.). Imprescindible para pipelines RNA-seq reproducibles.

Imagen multi-lenguaje

Para R + Python juntos:

FROM rocker/r-ver:4.4.1

# Python via uv
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
ENV PATH="/root/.local/bin:${PATH}"

# Quarto + el resto...

uv se instala como binario CLI, no como paquete pip. Compatible con cualquier imagen base.

Cuándo Docker, cuándo no

Situación Solución
Análisis personal Nada / Git
Compartir con un colega renv o uv
Publicar reproducible renv + _freeze/ commitado
Análisis regulado (clínico, auditable) Docker + renv
Pipeline de producción Docker + CI

Docker tiene coste: 30-60 minutos de setup inicial, learning curve, build times. Para un análisis ad-hoc es overhead. Para algo que tiene que correr dentro de 3 años, es lo único que garantiza el output exacto.

Documentar el entorno

Independientemente del nivel, siempre sessionInfo() al final del documento:

`​`​`{r}
#| label: session-info
#| echo: false

sessionInfo()
`​`​`

Para Python:

`​`​`{python}
#| label: session-info
#| echo: false

import platform, sys
print(f"Python: {sys.version}")
print(f"Platform: {platform.platform()}")
print("\nPackages:")
import pip
for line in pip.main(["list", "--format=freeze"]).split("\n"):
    if line:
        print(f"  {line}")
`​`​`

Lo más sencillo. Al final del informe, queda escrito qué versiones se usaron. Para reproducibilidad básica, esto + Git ya es mucho.

El workflow recomendado completo

Para un proyecto Quarto serio:

  1. git init desde el principio.
  2. renv::init() (R) o uv init (Python).
  3. freeze: auto en _quarto.yml y commit de _freeze/.
  4. CI que sirve el sitio sin recomputar (Netlify/GitHub Pages).
  5. sessionInfo() al final de cada documento.
  6. Docker si el análisis es crítico para reproducir en años.

Coste total: medio día de setup. Beneficio: cualquiera puede regenerar tu análisis dentro de 2 años. Vale la pena.

Trampas habituales

  • Olvidar renv::snapshot() después de instalar un paquete. La librería tiene el paquete pero renv.lock no, colaboradores no lo verán.
  • Commit de renv/library/. Está en .gitignore automáticamente, son binarios pesados. Solo commit del renv.lock.
  • uv sin venv: si llamas a quarto render sin uv run, usa el Python del sistema, no el del proyecto.
  • Docker con latest en imagen base. rocker/r-ver:latest cambia con el tiempo. Pin a la versión exacta (4.4.1).
  • Asumir que renv + Git basta para auditoría regulatoria. No, los reviewers piden Docker + lockfile + datos hashed. Conoce el nivel de rigor que se te pide.

Has terminado la ruta

Con esto cierras Quarto y reproducibilidad. Has visto:

  • Quarto vs Rmd, instalación, primer documento.
  • Front matter, secciones, output multi-formato.
  • Chunks con R y Python conviviendo.
  • Figuras y tablas publicables.
  • Cross-references y bibliografía.
  • Parametrización para informes en lote.
  • Includes y child documents para modularidad.
  • Cache y freeze para iterar rápido.
  • Websites y libros como proyectos.
  • Publicación en GitHub Pages, Netlify, QuartoPub.
  • Reproducibilidad real con renv, uv y Docker.

A partir de aquí, las direcciones naturales: extensiones de Quarto (_extensions/), revistas con quarto-journals, dashboards con quarto-dashboards, integración con targets para pipelines analíticos serios.

Cuando publiquemos el libro Quarto profesional, cubriremos plantillas corporativas, multilingüe, casos completos con repositorio reproducible público y un dashboard real desde cero.