Puedes encontrar esta librería en CRAN y descargarla directamente desde R y RStudio.
ESCUCHA ESTE POST COMO PODCAST
¿Qué ecuación está escondida en los datos? Una introducción a EmpiricalDynamics
Un paquete de R para descubrir y evaluar ecuaciones diferenciales directamente a partir de series temporales, combinando diferenciación numérica robusta, regresión simbólica, modelización estocástica, validación temporal y un backend de alto rendimiento en Julia.
Estado del proyecto — agosto de 2026. La rama principal de GitHub se encuentra en la versión 0.1.13 y declara licencia GPL (≥ 3). La versión publicada actualmente en CRAN es 0.1.9, anterior a ese cambio de licencia.
Tenemos una serie temporal: el PIB de un país, una población biológica, una temperatura, una tasa de interés, la concentración de una sustancia o la lectura de un sensor. Sospechamos que detrás de su movimiento existe alguna ley dinámica. El problema es que no sabemos cuál.
Una estrategia consiste en elegir previamente una ecuación y estimar sus parámetros. Es lo habitual: suponemos una dinámica lineal, logística, exponencial o de alguna otra familia conocida y preguntamos qué valores de los parámetros ajustan mejor los datos.
EmpiricalDynamics permite plantear también la pregunta inversa: ¿qué forma funcional es capaz de descubrir el propio algoritmo a partir de la dinámica observada?
En lugar de restringirse desde el principio a \(\dot Z=\alpha+\beta Z\), una búsqueda simbólica puede explorar combinaciones de variables y operaciones matemáticas, generando distintas ecuaciones candidatas y comparando su calidad de ajuste con su complejidad.
El objetivo no es predecir simplemente el próximo dato mediante una caja negra. Es intentar recuperar una expresión matemática interpretable que describa la dinámica observada y después someter esa expresión a diagnósticos, simulaciones y pruebas de comportamiento.
De estimar parámetros a descubrir ecuaciones
Supongamos que conocemos de antemano la forma:
Entonces el problema consiste esencialmente en estimar \(\alpha\) y \(\beta\).
Equation discovery plantea un problema más amplio:
donde conocemos las observaciones de \(Z\) y posiblemente de variables exógenas \(\mathbf X\), pero la propia función \(f(\cdot)\) también es desconocida.
El algoritmo debe buscar simultáneamente una estructura funcional y sus constantes.
Cuando el sistema es estocástico
Muchos sistemas reales no evolucionan mediante una ley determinista perfecta. Incluso después de descubrir una estructura sistemática puede quedar una componente aleatoria cuya intensidad dependa del propio estado del sistema.
Entonces la descripción natural pasa de una ODE a una ecuación diferencial estocástica:
Aquí \(f\) es el drift: la dinámica sistemática. \(g\) es la difusión: la intensidad del componente estocástico. \(W_t\) representa un proceso de Wiener.
Descubrir una SDE implica, por tanto, resolver dos problemas: recuperar la ley del drift y recuperar la estructura de la difusión.
La arquitectura: seis etapas que se pueden auditar
Preprocesamiento. Estimar derivadas numéricas a partir de observaciones potencialmente ruidosas.
Exploración. Examinar gráficamente relaciones, retratos de fase, superficies y posibles no linealidades.
Descubrimiento simbólico. Buscar ecuaciones candidatas y construir una frontera entre ajuste y complejidad.
Análisis de residuos. Preguntar qué estructura queda sin explicar y, cuando corresponde, construir la difusión de una SDE.
Validación. Utilizar cross-validation temporal, simulación de trayectorias y análisis cualitativo de la dinámica.
Salida. Generar ecuaciones LaTeX, tablas, gráficos y reportes destinados a documentación o publicación.
El primer cuello de botella: calcular una derivada sin amplificar el ruido
Para descubrir \[ \dot Z=f(Z,X) \] necesitamos primero alguna estimación de \(\dot Z\).
Pero diferenciar numéricamente datos ruidosos es peligroso: la derivación amplifica precisamente las fluctuaciones de alta frecuencia que muchas veces querríamos tratar como ruido de medición.
EmpiricalDynamics ofrece varias alternativas.
| Método | Idea | Uso natural |
|---|---|---|
| TVR | Regulariza la variación total de la derivada. | Datos ruidosos, tendencias y posibles discontinuidades. |
| Savitzky–Golay | Ajustes polinomiales locales. | Señales relativamente suaves donde interesa preservar picos. |
| Smoothing spline | Suavizado continuo antes de derivar. | Procesos suaves con ruido. |
| Diferencias finitas | Aproximaciones locales directas. | Datos limpios y suficientemente densos. |
| Espectral | Diferenciación en el dominio de frecuencias. | Señales periódicas; requiere cuidado con el fenómeno de Gibbs. |
TVR: suavizar la derivada sin borrar toda la estructura
El método recomendado por la documentación para muchas aplicaciones empíricas es Total Variation Regularization.
De manera esquemática, busca:
El primer término obliga a que la derivada reconstruya adecuadamente la serie. El segundo penaliza una derivada excesivamente irregular.
La implementación actual reescala internamente el problema para mejorar su acondicionamiento numérico y utiliza una cadena de solvers:
Es el solver preferido actualmente para TVR y la primera opción de la cadena.
Proporciona una familia algorítmica alternativa si la primera solución no alcanza el estado deseado.
Actúa como una tercera ruta de optimización cuando las anteriores no proporcionan una solución satisfactoria.
select_lambda_cv_tvr() puede evaluar automáticamente una
grilla de candidatos y reportar el estado de convergencia de los
solvers.
La paradoja interna de una SDE: un mejor drift puede destruir la evidencia de difusión
Ésta es quizá la idea estadística más interesante del diseño.
TVR mejora la estimación del drift precisamente mediante suavizado. Pero la difusión de una SDE se manifiesta en gran medida a través de fluctuaciones de alta frecuencia.
Si utilizamos después los residuos de una derivada fuertemente regularizada para recuperar \(g\), podemos haber eliminado previamente buena parte de la información que queríamos medir.
TVR quiere limpiar la alta frecuencia para estimar el drift. La difusión vive precisamente en alta frecuencia. Un método que mejora la primera tarea puede empeorar la segunda.
La solución: estimar difusión desde la variación cuadrática
La implementación actual recomienda, cuando se utiliza TVR, no recuperar la difusión desde sus residuos sino volver a los incrementos observados.
Para una SDE:
estimate_diffusion_qv() utiliza esta idea de
variación cuadrática.
Así, la estimación de \(g\) no depende de los residuos ya suavizados por TVR.
Los valores individuales de \((\Delta Z)^2/\Delta t\) son naturalmente muy ruidosos, por lo que el procedimiento aplica internamente un suavizado mediante mediana móvil antes de ajustar la relación funcional.
La evidencia del problema aparece en los propios recovery tests
| Configuración histórica | Drift \(R^2\) | Diffusion \(R^2\) |
|---|---|---|
| Solver previo + difusión por residuos | 0.864 | 0.591 |
| OSQP reescalado + residuos | 0.887 | 0.070 |
| CLARABEL reescalado + residuos | 0.841 | 0.005 |
| CLARABEL + variación cuadrática | 0.841 | 0.985 |
El punto no es que un solver “peor” fuese mejor. Es exactamente lo contrario: conforme el tratamiento de la derivada eliminaba mejor la fluctuación de alta frecuencia, una estimación de difusión basada en esos residuos perdía la señal que necesitaba.
Volver a los incrementos crudos permite desacoplar ambos problemas.
El corazón del paquete: regresión simbólica
Una vez estimada la derivada, el algoritmo puede buscar expresiones capaces de explicar su comportamiento.
En lugar de optimizar únicamente constantes, la búsqueda modifica también la estructura de las expresiones:
El resultado no debería interpretarse simplemente como “la ecuación con menor error”.
EmpiricalDynamics construye una frontera de Pareto donde aparecen ecuaciones con distintos compromisos entre ajuste y complejidad.
Entre los criterios disponibles para elegir entre candidatos se encuentran AIC, BIC y MDL, además de mecanismos de selección sobre la propia frontera.
search_result <- symbolic_search(
data = data,
response = "dZ",
predictors = c("Z", "X"),
backend = "r_genetic",
max_complexity = 15,
n_generations = 50,
population_size = 100,
n_runs = 3
)
plot_pareto_front(search_result)
best_eq <- select_equation(
search_result,
criterion = "bic"
)
Si ya existe una teoría, no hay premio por ignorarla
El paquete no presenta la búsqueda ciega como superior en toda situación.
Si una teoría proporciona una forma funcional concreta, la documentación
recomienda utilizarla directamente mediante
fit_specified_equation().
equation <- fit_specified_equation(
"alpha + beta * Z + gamma * Z^2 + delta * X",
data = data,
derivative_col = "dZ",
method = "levenberg-marquardt",
start = list(
alpha = 0,
beta = 1,
gamma = -0.01,
delta = 0.5
)
)
Ésta es una distinción metodológica importante: descubrimiento cuando desconocemos la estructura; estimación directa cuando poseemos una hipótesis estructural que queremos poner a prueba.
Explorar antes de buscar
explore_dynamics() permite examinar visualmente las relaciones
antes de lanzar una búsqueda simbólica.
Actualmente compara formas lineales, cuadráticas y cúbicas para los predictores, conserva el ajuste ganador, sus coeficientes, el rango donde fue estimado y los AIC de los modelos enfrentados.
Esto importa porque una etiqueta como “cuadrática” no nos dice si la curva realmente cambia de dirección dentro del rango observado.
La propia Wiki reporta que, en simulaciones internas, esta comparación AIC clasifica una relación verdaderamente lineal como “linear” alrededor del 78 % de las veces. Por eso la etiqueta debe leerse como selección de modelo, no como medición infalible de la forma verdadera.
Julia hace el trabajo evolutivo pesado
El backend de alto rendimiento utiliza SymbolicRegression.jl.
La arquitectura mantiene en R el flujo estadístico, los diagnósticos y la interfaz, mientras Julia puede encargarse de búsquedas evolutivas más costosas y paralelizables.
El archivo
inst/julia/symbolic_backend.jl
define una configuración científica que controla, entre otras cosas:
- tamaño de poblaciones;
- número de iteraciones;
- complejidad máxima;
- penalización por falta de parsimonia;
- operadores permitidos;
- checkpoints;
- y detección de determinadas constantes físicas.
El backend actual reconoce como candidatos \(\pi\), \(e\), \(\varphi\), \(g\), \(c\), \(h\) y \(k_B\), incluyendo además algunas transformaciones simples de estas constantes.
Esto permite, por ejemplo, reconocer que un coeficiente numérico descubierto está cerca de \(\pi\), en lugar de presentar únicamente una expansión decimal sin interpretación.
GLS iterativo: la versión actual conserva toda la historia del ajuste
Cuando la varianza condicional cambia con el estado, el paquete puede refinar el drift mediante un procedimiento GLS iterativo.
Esquemáticamente:
y esos pesos modifican la siguiente estimación del drift.
Versiones recientes corrigieron un detalle fundamental: el loop anterior no podía reconocer correctamente su propia convergencia cuando las constantes de una ecuación simbólica aparecían como literales.
La implementación actual exige simultáneamente:
La deviance ponderada debe haber dejado de cambiar de forma material.
Las predicciones de dos iteraciones sucesivas también deben haberse aproximado suficientemente.
Además, el resultado conserva
converged,
stop_reason,
history,
selected_iteration,
las puntuaciones de selección y los candidatos excluidos.
La selección por defecto entre las iteraciones utiliza blocked cross-validation con bloques contiguos y reestimación de las constantes.
Los residuos son una pregunta, no un basurero
Después de ajustar una ecuación, lo que queda sin explicar puede contener información sobre una especificación incompleta.
residual_diagnostics() reúne varias pruebas:
| Prueba | Qué examina |
|---|---|
| Ljung–Box | Dependencia serial restante. |
| ARCH-LM | Heterocedasticidad condicional. |
| Breusch–Pagan | Varianza relacionada con predictores. |
| Jarque–Bera | Desviaciones respecto de normalidad. |
| Runs test | Patrones no aleatorios remanentes. |
Validar una ecuación temporal sin dejar que mire el futuro
Una serie temporal no debería ser validada como si sus filas fueran intercambiables.
La implementación actual admite validación por bloques y esquemas rolling o sliding. En el modo rolling, las observaciones utilizadas para entrenamiento se encuentran antes de la ventana de prueba.
cv <- cross_validate(
equation,
data = data,
response = "dZ",
k = 5,
method = "rolling",
horizon = 4,
window = "expanding"
)
Esto parece un detalle obvio, pero no lo era en versiones anteriores. La 0.1.12 corrigió un defecto por el que la implementación rolling podía utilizar como entrenamiento observaciones posteriores a la ventana que pretendía validar.
También se corrigieron otros problemas:
- los bloques ahora cubren todas las filas en lugar de abandonar observaciones finales;
- \(R^2\) se evalúa contra la media del conjunto de entrenamiento, no contra una media calculada después de conocer los datos de prueba;
- los folds cuyo reajuste falla ya no desaparecen silenciosamente del promedio;
- los pesos de observación se conservan al reestimar cada fold;
- un GLM se vuelve a ajustar como GLM y no accidentalmente como una regresión gaussiana ordinaria.
Una limitación importante de esa cross-validation
Hay todavía una sutileza que la documentación actual hace explícita.
Cuando la variable objetivo es una derivada calculada numéricamente, el propio \(\dot Z_t\) puede haber sido construido utilizando observaciones vecinas.
Con TVR, incluso puede intervenir información de toda la serie.
Por tanto, aunque el modelo de cada fold no se entrene con el futuro bajo el esquema rolling corregido, la derivada que se le entregó pudo haber sido calculada previamente usando esa información.
La propia documentación advierte que estas cifras pueden utilizarse para
comparar ecuaciones candidatas bajo el mismo tratamiento,
pero no deben confundirse con el error que esperaríamos al pronosticar
una serie futura completamente nueva. La opción destinada a recalcular
la derivada dentro de cada fold,
refit_derivative, todavía no está implementada.
Una ecuación no sólo debe ajustar puntos: también debe comportarse correctamente
EmpiricalDynamics incorpora herramientas para estudiar propiedades cualitativas de la ecuación descubierta:
- puntos fijos;
- estabilidad;
- bifurcaciones;
- acotamiento;
- y simulación completa de trayectorias.
Ésta es una diferencia importante entre encontrar una regresión flexible y recuperar una dinámica plausible.
Dos expresiones pueden presentar errores similares sobre la muestra y, sin embargo, producir retratos dinámicos completamente diferentes cuando se integran.
Bifurcaciones bayesianas: una mejora reciente
La versión 0.1.11 endureció de forma importante
analyze_bifurcations().
Para modelos que contienen draws posteriores, la función ya no reduce automáticamente toda la incertidumbre a un único vector de coeficientes. Puede barrer la distribución posterior y devolver distribuciones de puntos fijos.
Además, comprueba que cambiar el parámetro de bifurcación cambie realmente las predicciones del objeto.
Esto evita producir una tabla perfectamente formada pero científicamente vacía donde todos los valores del parámetro generan exactamente el mismo resultado porque la sustitución nunca llegó al mecanismo de predicción.
En la 0.1.13 se añadió además
ed_derivative_step(), que expone oficialmente el paso
\(10^{-6}\) utilizado por la diferencia central en la clasificación de
puntos fijos.
Recovery tests: darle al algoritmo un mundo cuya ley ya conocemos
La validación más directa de un algoritmo de equation discovery consiste en construir un mundo sintético donde conozcamos la ley verdadera, ocultársela al algoritmo y preguntarle si puede recuperarla.
Esto no prueba causalidad en datos observacionales reales.
Sí responde una pregunta previa indispensable: si la ley verdadera está presente en los datos bajo condiciones controladas, ¿el pipeline es capaz de encontrarla?
El atractor de Lorenz
Uno de los benchmarks utiliza el sistema caótico clásico:
| Ecuación | \(R^2\) frente a la dinámica verdadera |
|---|---|
| \(dx/dt\) | 0.937 |
| \(dy/dt\) | 0.960 |
| \(dz/dt\) | 0.914 |
| Promedio | 0.937 |
La Wiki aclara que estas cifras combinan el error de diferenciación TVR y la regresión simbólica, y que la búsqueda evolutiva es estocástica: distintos runs pueden producir resultados diferentes.
El benchmark estocástico
El segundo recovery test utiliza una SDE deliberadamente difícil:
Con 5.000 observaciones, \(\Delta t=0.005\) y un SNR documentado de aproximadamente \(0.29\), los resultados reportados son:
| Componente | \(R^2\) | RMSE | Procedimiento |
|---|---|---|---|
| Drift | 0.841 | 0.547 | TVR + GLS iterativo |
| Diffusion | 0.985 | 0.063 | Variación cuadrática |
Son recovery tests sobre sistemas sintéticos con verdad conocida. Demuestran que el procedimiento puede recuperar esas estructuras bajo las condiciones ensayadas. No garantizan que una ecuación encontrada en datos observacionales sea la verdadera ley causal del sistema.
Descubrir una ecuación no equivale a descubrir causalidad
Ésta es una frontera importante.
Una expresión simbólica puede reproducir extraordinariamente bien una relación observada y seguir reflejando variables omitidas, confundimiento, simultaneidad, errores de medición o una estructura que sólo funciona bajo determinado régimen.
La regresión simbólica responde principalmente: ¿qué estructura matemática es consistente con la dinámica observada?
Convertir esa estructura en una afirmación causal requiere información y razonamiento adicionales.
Instalación: CRAN o versión de desarrollo
La versión publicada en CRAN puede instalarse directamente:
install.packages("EmpiricalDynamics")
Para utilizar el estado más reciente del repositorio:
remotes::install_github(
"IsadoreNabi/EmpiricalDynamics"
)
El backend Julia se configura después desde R:
library(EmpiricalDynamics)
setup_julia_backend()
El metadata actual del paquete declara R 4.0.0 o posterior y Julia 1.6 o posterior como requisito del sistema para el backend. El README recomienda actualmente Julia 1.9 o posterior; utilizar una versión reciente de Julia satisface ambas indicaciones.
La cuestión metodológica de fondo
La tentación en equation discovery es imaginar una máquina que recibe una tabla y devuelve “la ley de la naturaleza”.
EmpiricalDynamics es más interesante precisamente cuando se lo entiende de otra manera.
La búsqueda simbólica constituye solamente una parte de un procedimiento mucho más largo.
Primero hay que construir una derivada defendible. Después explorar la geometría de la relación. Luego buscar o especificar ecuaciones. Después mirar lo que quedó en los residuos. Si existe estructura estocástica, separar drift y difusión. Luego comprobar comportamiento cualitativo y simular trayectorias. Finalmente evaluar qué tanto de la conclusión sobrevive fuera del ajuste inmediato.
Y cada uno de esos pasos puede fallar de una manera distinta.
EmpiricalDynamics es desarrollado por José Mauricio Gómez Julián. La rama de desarrollo actual declara licencia GPL (≥ 3). El código fuente y el historial de cambios están disponibles en GitHub, y la documentación matemática, los recovery tests y las guías de uso se encuentran en la Wiki de EmpiricalDynamics.










