Empirical Discovery of Differential Equations from Time Series Data
Overview
A comprehensive toolkit for discovering differential and difference equations from empirical time series data using symbolic regression. The package implements a complete workflow from data preprocessing (including Total Variation Regularized differentiation for noisy economic data), visual exploration of dynamical structure, and symbolic equation discovery via genetic algorithms. It leverages a high-performance 'Julia' backend ('SymbolicRegression.jl') to provide industrial-grade robustness, physics-informed constraints, and rigorous out-of-sample validation. Designed for economists, physicists, and researchers studying dynamical systems from observational data.
The lm path of analyze_fixed_points() ignored the fitted coefficients
For an lm (or glm) equation, the function deparsed the bare right-hand
side of the formula and injected the coefficients into the evaluation
environment under their names. But lm coefficient names are *term labels*
-- I(Z), I(Z^2), (Intercept) -- which are not symbols of the deparsed
text, so the bindings were never consulted and the bare terms were evaluated
with their coefficients effectively set to 1. Fitted to dZ = 2*Z - Z^2,
with coefficients recovered exactly as 2 and -1, it returned the fixed
points of Z + Z^2 -- without a word. Written without I() the failure
changed shape instead of going away: a coefficient named Z collided with
the search variable itself. The example on the function's own manual page
took this path.
Show all changes in 0.1.9Hide the rest of the changes in 0.1.9
lm and glm equations are now evaluated through their own predict()
machinery (on the response scale for glm), so the coefficients enter
exactly as fitted -- I() terms, factors and link functions included, and
no name collision is possible. The nls and symbolic_equation paths,
whose coefficient names are genuine symbols of their expressions, were
correct and are unchanged.
A name that binds nothing is now reported instead of ignored
Every name the equation consults must now be accounted for -- the search
variable, a fitted coefficient, or a declared exogenous value. Previously,
each of the following was silent: an unnamed exogenous_values entry (bound
nothing -- for (nm in names(x)) over a NULL runs zero times); declaring
the search variable (overwritten at every grid point); declaring a name the
equation never uses (no effect); declaring a name that collides with a
fitted coefficient (silently *overwrote the fit*); and a free symbol with no
declared value (evaluation failed point by point into NAs, or worse,
picked up whatever the name meant in the enclosing environment). The first
two and the last two are errors now; the never-consulted name is a warning.
A new coefficient_values argument takes the deliberate case the collision
error forbids: overriding a fitted coefficient by name, validated against
the coefficients that exist.
A root the grid landed on exactly was reported twice
Fixed points were detected as diff(sign(f)) != 0 over the grid. A grid
point falling exactly on a root has sign 0, which that test counts as two
changes -- one into the zero and one out of it -- so uniroot() refined the
same root from both adjacent brackets and the point appeared twice in the
output. An exact zero is now taken as a root in its own right, a crossing
requires a strict sign flip, and two detections closer than half a grid step
-- the instrument's own resolution -- are merged.
analyze_bifurcations() swept parameters that were not there
The parameter was injected as a binding under its name and handed down as an
exogenous value; if that name appeared nowhere in the equation, the binding
was never consulted and the sweep returned n_param identical copies of the
same fixed points, each labelled with the absent parameter's name. The
parameter is now validated before the sweep: a fitted coefficient is varied
as a coefficient (which also makes coefficients of lm equations, like
I(Z), valid bifurcation parameters -- previously impossible), a variable
of the equation is varied as an exogenous value, anything else is an error,
as is parameter == variable.
Two more silences in the same loop: a parameter value whose analysis found
no fixed points was dropped from the output entirely, so the requested grid
could not be reconstructed from the result; and any error was caught and
converted into the same empty frame, so a failure was indistinguishable from
an absence. The returned object now carries $status, one row per requested
parameter value -- ok, no_fixed_points, or error with the error's
message -- plus $mode, and a print() method states the accounting.
From NEWS.md in the release
Install
Health
- OK13 OK2026-08-05
- NOTE12 OK, 1 NOTE2026-08-01
- OK14 OK2026-03-22
- ERROR6 OK, 8 ERROR2026-03-10
Documentation
- Help pages
- 108 (28 internal)
- Examples that run
- 45%9 run in a plain check · 11 only when donttest examples are enabled (R CMD check --as-cran)
- Exported functions with examples
- 32%
- Documented parameters
- 100%
- Return-value docs
- 100%
- Help pages citing references
- 0%
Downloads
Repository
Releases over time
Commits over time
Commit identities over time
2 development-tooling and community-health practices detected across 2 families in the upstream repository
Checks run against github.com/isadorenabi/empiricaldynamics on 2026-10-18.
Dependencies
Installing EmpiricalDynamics adds 40 packages (28 with compiled code, 34 MB of source tarballs). Heaviest: ggplot2 6.0 MB, clarabel 5.7 MB, signal 1.9 MB.
Code & Tests
Datasets
People & History
Pick two releases to compare their code metrics.
- 0.1.9Latest
- 0.1.52026-06-18 · diff ↗
- RR 4.6.0 released · 2026-04-24
- 0.1.3
- 0.1.22026-01-16
- RR 4.5.0 released · 2025-04-11
Package metadata
- First published
- 2026-01-16
- Total releases
- 4 / 1 yrs
- License
- MIT + file LICENSE OSI
- Minimum R
- ≥ 4.0.0
- Download size
- 218 KB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("EmpiricalDynamics")This package has no citation file, so citation() returns this entry built from DESCRIPTION.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-20, which the citation names so these numbers can be found later. More on citing and the projects behind them.