Episia Package#
Episia - Epidemiology Toolbox for Python Based on OpenEpi algorithms, extended for the African public health context.
Quick start:
from episia import epi
# SEIR model
model = epi.seir(N=1_000_000, I0=10, E0=50, beta=0.35,
sigma=1/5.2, gamma=1/14)
result = model.run()
result.plot().show()
# Biostatistics
rr = epi.risk_ratio(a=40, b=10, c=20, d=30)
print(rr)
# Report
report = epi.report(result, title="SEIR — Burkina Faso 2024")
report.save_html("report.html")
Advanced usage (direct imports):
# Models
from episia.models import SIRModel, SEIRModel, SEIRDModel
from episia.models import SensitivityAnalysis, ModelCalibrator
# Stats
from episia.stats import risk_ratio, odds_ratio, diagnostic_test_2x2
from episia.stats import roc_analysis, sample_size_risk_ratio
# Reporting
from episia import EpiReport
report = EpiReport(title="My report", author="Dr. Ouedraogo")
report.add_metrics({"R0": 3.2, "Peak": "42,500"})
report.save_html("report.html")
# Visualization
from episia.viz import plot_epicurve, plot_roc, plot_forest, set_theme
set_theme("dark")
# Surveillance data
from episia.data import SurveillanceDataset, AlertEngine
- class episia.AlertEngine(dataset)[source]#
Bases:
objectThreshold-based and statistical alert detection for surveillance data.
Example:
engine = AlertEngine(dataset) alerts = engine.run( threshold=10, zscore_threshold=2.0, use_endemic_channel=True, ) for a in alerts: print(a.period, a.severity, a.message)
- Parameters:
dataset (SurveillanceDataset)
- __init__(dataset)[source]#
- Parameters:
dataset (SurveillanceDataset)
- run(threshold=None, zscore_threshold=2.0, use_endemic_channel=False, historical_years=None, freq='W')[source]#
Run all enabled alert detectors.
- Parameters:
threshold (float | None) – Absolute case count threshold.
zscore_threshold (float) – Z-score threshold for statistical alert.
use_endemic_channel (bool) – Use endemic channel (requires ≥3 historical years).
historical_years (List[int] | None) – Years to use for endemic channel baseline.
freq (str) – Aggregation frequency (‘D’, ‘W’, ‘ME’).
- Returns:
List of Alert objects, sorted by period.
- Return type:
- class episia.EpiReport(title='Epidemiological report', author=None, institution=None, date=None, description=None)[source]#
Bases:
objectStructured epidemiological report builder.
Example:
report = EpiReport(title="Analyse SEIR", author="Dr. Ouedraogo") report.add_text("Introduction...", title="Introduction") report.add_metrics({"R0": 3.2, "Peak": 42500}) report.add_table(df, title="Results") report.save_html("rapport.html") report.save_markdown("rapport.md")
- Parameters:
- __init__(title='Epidemiological report', author=None, institution=None, date=None, description=None)[source]#
- class episia.EpisiaAPI[source]#
Bases:
objectUnified Episia API single entry point for all functionality. Instantiated as the module-level epi singleton.
- static report(result, title=None, **kwargs)[source]#
Build a report from any EpiResult or ModelResult.
- static seir(N, I0, E0, beta, sigma, gamma, t_end=365, **kwargs)[source]#
Convenience factory for SEIRModel.
- static seird(N, I0, E0, beta, sigma, gamma, mu, t_end=365, **kwargs)[source]#
Convenience factory for SEIRDModel.
- class episia.SurveillanceDataset(df, *, date_col='date', cases_col='cases', deaths_col=None, district_col=None, disease_col=None, population_col=None)[source]#
Bases:
objectStructured surveillance case count dataset.
- Wraps a pandas DataFrame with columns:
date / week / period time axis district / site spatial unit (optional) disease disease or syndrome name cases integer case count deaths integer death count (optional) population population at risk (optional)
Built from CSV, DHIS2 exports, or a plain DataFrame.
Example:
from episia.data.surveillance import SurveillanceDataset ds = SurveillanceDataset.from_csv("meningite_2024.csv", date_col="semaine", cases_col="cas") print(ds.summary()) ds.epicurve().plot().show() alerts = ds.alert_engine().run()
- Parameters:
- __init__(df, *, date_col='date', cases_col='cases', deaths_col=None, district_col=None, disease_col=None, population_col=None)[source]#
- aggregate(freq='W', group_by=None)[source]#
Aggregate cases by time frequency and optional grouping columns.
- completeness(freq='auto', period_col=None)[source]#
Compute reporting completeness over the observed date range.
Identifies expected periods (inferred from
freq) versus periods actually present in the dataset, and returns the missing ones.This is especially relevant when loading data from DHIS2 instances in sub-Saharan Africa where silent gaps are common: a district that did not submit its weekly report will simply be absent from the export, distorting endemic-channel baselines without any warning.
- Parameters:
freq (str) –
Expected reporting frequency. One of:
"auto"– inferred from median gap between dates (default)."W"– weekly (ISO weeks)."ME"– monthly (pandas >= 2.2)."QE"– quarterly."D"– daily.
period_col (str | None) – Column that contains DHIS2 period strings such as
"2024W01"or"202401"(optional). When provided, completeness is calculated against these string labels rather than inferred datetime periods, and theperiod_range()helper is used internally to build the expected sequence.
- Returns:
expected_periods(int)reported_periods(int)completeness_rate(float, 0.0 – 1.0)missing_periods(list[str])freq(str, the frequency used)
- Return type:
dict with keys
- Raises:
ValueError – if the dataset has fewer than 2 records.
Example:
ds = client.to_dataset( data_element="FTRrcoaog83", period="LAST_52_WEEKS", org_unit="ImspTQPwCqd", ) result = ds.completeness() # { # "expected_periods": 52, # "reported_periods": 49, # "completeness_rate": 0.942, # "missing_periods": ["2024W15", "2024W16", "2024W31"], # "freq": "W", # }
- property df#
- endemic_channel(historical_years=None, percentiles=(25, 50, 75))[source]#
Compute the endemic channel (historical percentile envelope).
Groups by ISO week number across historical years. Returns the percentile bands used for alert zone classification.
- filter_date(start=None, end=None)[source]#
Filter to a date range (inclusive).
- Parameters:
- Return type:
- filter_disease(disease)[source]#
Return a new dataset filtered to a single disease.
- Parameters:
disease (str)
- Return type:
- filter_district(district)[source]#
Return a new dataset filtered to a single district.
- Parameters:
district (str)
- Return type:
- classmethod from_csv(path, date_col='date', cases_col='cases', deaths_col=None, district_col=None, disease_col=None, population_col=None, **read_kwargs)[source]#
Load from CSV file.
- Parameters:
date_col (str) – Column name for date / week.
cases_col (str) – Column name for case counts.
deaths_col (str | None) – Column name for deaths (optional).
district_col (str | None) – Column name for district / site (optional).
disease_col (str | None) – Column name for disease / syndrome (optional).
population_col (str | None) – Column for population at risk (optional).
**read_kwargs – Passed to pd.read_csv.
- Returns:
SurveillanceDataset.
- Return type:
- classmethod from_dict(data, **kwargs)[source]#
Create from a plain dict of lists.
- Parameters:
- Return type:
- episia.report_from_model(model_result, title=None, author=None, institution=None, sensitivity_result=None, backend='plotly', theme='scientific')[source]#
Full model simulation report (SIR / SEIR / SEIRD).
- episia.report_from_result(result, title=None, author=None, backend='plotly', theme='scientific')[source]#
One-line report from any EpiResult.
Package Overview#
The episia package provides a comprehensive toolbox for epidemiological analysis.
The recommended entry point is the unified epi interface:
from episia import epi
# Unified API
model = epi.seir(N=1_000_000, I0=10, E0=50, beta=0.35, sigma=1/5.2, gamma=1/14)
rr = epi.risk_ratio(a=40, b=10, c=20, d=30)
report = epi.report(result)
For advanced usage, individual modules can be imported directly.
Version Information#
Lazy Loading#
Episia uses lazy loading to improve startup performance. Heavy modules
(scipy, sklearn, plotly, matplotlib) are only imported when
first used. This means:
from episia import epiis fastfrom episia import risk_ratiotriggers the import of required dependencies
The lazy loading is implemented via PEP 562 (__getattr__).
Plotly Renderer Configuration#
Episia automatically configures the Plotly renderer based on the environment:
In Jupyter notebooks: Renderer is left unchanged (inline display works normally)
In scripts/terminals: Renderer is set to
"browser"to open figures in your web browser
This prevents raw JSON from being dumped in the terminal when calling fig.show().
Unified Interface#
- episia.epi = EpisiaAPI()#
Unified Episia API single entry point for all functionality. Instantiated as the module-level epi singleton.
The main entry point for Episia functionality. Provides a unified interface to all modules through a single object.
- class episia.api.unified.EpisiaAPI[source]
Bases:
objectUnified Episia API single entry point for all functionality. Instantiated as the module-level epi singleton.
- static cumulative_incidence(*args, **kwargs)[source]
- static diagnostic(*args, **kwargs)[source]
- static get_available_themes()[source]
- static incidence_rate(*args, **kwargs)[source]
- static mean_ci(*args, **kwargs)[source]
- static odds_ratio(*args, **kwargs)[source]
- static plot_epicurve(*args, **kwargs)[source]
- static plot_forest(*args, **kwargs)[source]
- static plot_roc(*args, **kwargs)[source]
- static prevalence(*args, **kwargs)[source]
- static proportion_ci(*args, **kwargs)[source]
- static read_csv(path, **kwargs)[source]
- static report(result, title=None, **kwargs)[source]
Build a report from any EpiResult or ModelResult.
- static risk_ratio(*args, **kwargs)[source]
- static sample_size(*args, **kwargs)[source]
- static seir(N, I0, E0, beta, sigma, gamma, t_end=365, **kwargs)[source]
Convenience factory for SEIRModel.
- static seird(N, I0, E0, beta, sigma, gamma, mu, t_end=365, **kwargs)[source]
Convenience factory for SEIRDModel.
- static sir(N, I0, beta, gamma, t_end=160, **kwargs)[source]
Convenience factory for SIRModel.
- static surveillance_from_csv(path, **kwargs)[source]
Reporting#
- class episia.api.reporting.EpiReport(title='Epidemiological report', author=None, institution=None, date=None, description=None)[source]
Bases:
objectStructured epidemiological report builder.
Example:
report = EpiReport(title="Analyse SEIR", author="Dr. Ouedraogo") report.add_text("Introduction...", title="Introduction") report.add_metrics({"R0": 3.2, "Peak": 42500}) report.add_table(df, title="Results") report.save_html("rapport.html") report.save_markdown("rapport.md")
- Parameters:
- __init__(title='Epidemiological report', author=None, institution=None, date=None, description=None)[source]
- add_figure(figure, title=None, caption=None, width='100%')[source]
- add_metrics(metrics, title='Key indicators')[source]
- add_result(result, title=None)[source]
- add_table(data, title=None, caption=None, max_rows=100)[source]
- add_text(text, title=None, level=2)[source]
- episia.api.reporting.report_from_result(result, title=None, author=None, backend='plotly', theme='scientific')[source]
One-line report from any EpiResult.
- episia.api.reporting.report_from_model(model_result, title=None, author=None, institution=None, sensitivity_result=None, backend='plotly', theme='scientific')[source]
Full model simulation report (SIR / SEIR / SEIRD).
Surveillance Data#
- class episia.data.surveillance.SurveillanceDataset(df, *, date_col='date', cases_col='cases', deaths_col=None, district_col=None, disease_col=None, population_col=None)[source]
Bases:
objectStructured surveillance case count dataset.
- Wraps a pandas DataFrame with columns:
date / week / period time axis district / site spatial unit (optional) disease disease or syndrome name cases integer case count deaths integer death count (optional) population population at risk (optional)
Built from CSV, DHIS2 exports, or a plain DataFrame.
Example:
from episia.data.surveillance import SurveillanceDataset ds = SurveillanceDataset.from_csv("meningite_2024.csv", date_col="semaine", cases_col="cas") print(ds.summary()) ds.epicurve().plot().show() alerts = ds.alert_engine().run()
- Parameters:
- __init__(df, *, date_col='date', cases_col='cases', deaths_col=None, district_col=None, disease_col=None, population_col=None)[source]
- aggregate(freq='W', group_by=None)[source]
Aggregate cases by time frequency and optional grouping columns.
- attack_rate(population=None, per=100000)[source]
Compute overall attack rate.
- completeness(freq='auto', period_col=None)[source]
Compute reporting completeness over the observed date range.
Identifies expected periods (inferred from
freq) versus periods actually present in the dataset, and returns the missing ones.This is especially relevant when loading data from DHIS2 instances in sub-Saharan Africa where silent gaps are common: a district that did not submit its weekly report will simply be absent from the export, distorting endemic-channel baselines without any warning.
- Parameters:
freq (str) –
Expected reporting frequency. One of:
"auto"– inferred from median gap between dates (default)."W"– weekly (ISO weeks)."ME"– monthly (pandas >= 2.2)."QE"– quarterly."D"– daily.
period_col (str | None) – Column that contains DHIS2 period strings such as
"2024W01"or"202401"(optional). When provided, completeness is calculated against these string labels rather than inferred datetime periods, and theperiod_range()helper is used internally to build the expected sequence.
- Returns:
expected_periods(int)reported_periods(int)completeness_rate(float, 0.0 – 1.0)missing_periods(list[str])freq(str, the frequency used)
- Return type:
dict with keys
- Raises:
ValueError – if the dataset has fewer than 2 records.
Example:
ds = client.to_dataset( data_element="FTRrcoaog83", period="LAST_52_WEEKS", org_unit="ImspTQPwCqd", ) result = ds.completeness() # { # "expected_periods": 52, # "reported_periods": 49, # "completeness_rate": 0.942, # "missing_periods": ["2024W15", "2024W16", "2024W31"], # "freq": "W", # }
- property df
- endemic_channel(historical_years=None, percentiles=(25, 50, 75))[source]
Compute the endemic channel (historical percentile envelope).
Groups by ISO week number across historical years. Returns the percentile bands used for alert zone classification.
- filter_date(start=None, end=None)[source]
Filter to a date range (inclusive).
- Parameters:
- Return type:
- filter_disease(disease)[source]
Return a new dataset filtered to a single disease.
- Parameters:
disease (str)
- Return type:
- filter_district(district)[source]
Return a new dataset filtered to a single district.
- Parameters:
district (str)
- Return type:
- classmethod from_csv(path, date_col='date', cases_col='cases', deaths_col=None, district_col=None, disease_col=None, population_col=None, **read_kwargs)[source]
Load from CSV file.
- Parameters:
date_col (str) – Column name for date / week.
cases_col (str) – Column name for case counts.
deaths_col (str | None) – Column name for deaths (optional).
district_col (str | None) – Column name for district / site (optional).
disease_col (str | None) – Column name for disease / syndrome (optional).
population_col (str | None) – Column for population at risk (optional).
**read_kwargs – Passed to pd.read_csv.
- Returns:
SurveillanceDataset.
- Return type:
- classmethod from_dataframe(df, **kwargs)[source]
Wrap an existing DataFrame.
- Return type:
- classmethod from_dict(data, **kwargs)[source]
Create from a plain dict of lists.
- Parameters:
- Return type:
- property n_records: int
- to_timeseries_result()[source]
Convert to api.results.TimeSeriesResult for viz integration.
- Returns:
TimeSeriesResult ready for plot_epicurve().
- property total_cases: int
- class episia.data.surveillance.AlertEngine(dataset)[source]
Bases:
objectThreshold-based and statistical alert detection for surveillance data.
Example:
engine = AlertEngine(dataset) alerts = engine.run( threshold=10, zscore_threshold=2.0, use_endemic_channel=True, ) for a in alerts: print(a.period, a.severity, a.message)
- Parameters:
dataset (SurveillanceDataset)
- __init__(dataset)[source]
- Parameters:
dataset (SurveillanceDataset)
- alert_summary(alerts)[source]
Summarise a list of alerts.
- run(threshold=None, zscore_threshold=2.0, use_endemic_channel=False, historical_years=None, freq='W')[source]
Run all enabled alert detectors.
- Parameters:
threshold (float | None) – Absolute case count threshold.
zscore_threshold (float) – Z-score threshold for statistical alert.
use_endemic_channel (bool) – Use endemic channel (requires ≥3 historical years).
historical_years (List[int] | None) – Years to use for endemic channel baseline.
freq (str) – Aggregation frequency (‘D’, ‘W’, ‘ME’).
- Returns:
List of Alert objects, sorted by period.
- Return type:
Available Lazy Imports#
The following names are lazily loaded from their respective modules:
Statistics:
- risk_ratio
- odds_ratio
- proportion_ci
- mean_ci
- diagnostic_test_2x2
- roc_analysis
- sample_size_risk_ratio
- sample_size_single_proportion
Visualization:
- set_theme
- get_available_themes
- plot_epicurve
- plot_roc
- plot_forest
Examples#
Basic usage with unified interface:
from episia import epi
# SEIR model
model = epi.seir(
N=1_000_000,
I0=10,
E0=50,
beta=0.35,
sigma=1/5.2,
gamma=1/14
)
result = model.run()
result.plot().show()
# Risk ratio
rr = epi.risk_ratio(a=40, b=10, c=20, d=30)
print(rr) # Risk Ratio: 2.667 (1.514-4.696)
# Generate report
report = epi.report(result, title="SEIR Analysis")
report.save_html("report.html")
Direct imports for advanced usage:
from episia.models import SEIRModel, SensitivityAnalysis
from episia.stats import roc_analysis, sample_size_risk_ratio
from episia.viz import plot_epicurve, set_theme
from episia.data import SurveillanceDataset
# Set theme globally
set_theme("dark")
# Load surveillance data
ds = SurveillanceDataset.from_csv("cases.csv")
# ROC analysis
roc_result = roc_analysis(y_true, y_scores)
Version check:
import episia
print(f"Episia version: {episia.__version__}")