contingency Module#

2x2 contingency table calculations for epidemiology.

This module provides the Table2x2 class for performing epidemiological calculations on 2x2 contingency tables, including risk ratios, odds ratios, risk differences, and various confidence intervals.

Classes#

class episia.stats.contingency.Table2x2(a, b, c, d)[source]#

Bases: object

2x2 contingency table for epidemiological calculations.

Represents a standard 2x2 table layout: +—————-+———–+———–+ | | Exposed | Unexposed | +================+===========+===========+ | Cases | a | b | +—————-+———–+———–+ | Non-cases | c | d | +—————-+———–+———–+

Parameters:
a#

Exposed cases

Type:

int

b#

Unexposed cases

Type:

int

c#

Exposed non-cases

Type:

int

d#

Unexposed non-cases

Type:

int

__init__(a, b, c, d)[source]#

Initialize a 2x2 contingency table.

Parameters:
  • a (int) – Exposed cases (cell a)

  • b (int) – Unexposed cases (cell b)

  • c (int) – Exposed non-cases (cell c)

  • d (int) – Unexposed non-cases (cell d)

Raises:

ValueError – If any cell value is negative

__repr__()[source]#

Return repr(self).

Return type:

str

a#
attributable_fraction_exposed()[source]#

Calculate attributable fraction among the exposed.

AF_exposed = (RR - 1) / RR Represents proportion of cases among exposed attributable to exposure.

Returns:

-∞ to 1)

Return type:

Attributable fraction (range

attributable_fraction_population()[source]#

Attributable fraction in the population (PAF).

PAF = Pe * (RR - 1) / (Pe * (RR - 1) + 1)

where Pe = proportion of cases that are exposed = a / (a + b).

Returns:

-∞ to 1)

Return type:

Population attributable fraction (range

b#
c#
chi_square(correction=True)[source]#

Calculate chi-square test for association.

Parameters:

correction (bool) – Apply Yates’ continuity correction if True

Returns:

Dictionary with ‘chi2’, ‘p_value’, and ‘df’ (degrees of freedom)

Return type:

Dict[str, float]

d#
fisher_exact()[source]#

Perform Fisher’s exact test (especially for small samples).

Returns:

Dictionary with ‘odds_ratio’, ‘p_value’ (two-sided)

Return type:

Dict[str, float]

property odds_exposed: float#

a / c.

Type:

Odds in exposed group

odds_ratio(method=ConfidenceMethod.WALD, confidence=0.95)[source]#

Calculate odds ratio with confidence interval.

Odds Ratio = (a/c) / (b/d) = (a*d) / (b*c)

Parameters:
  • method (ConfidenceMethod) – Method for confidence interval calculation

  • confidence (float) – Confidence level (default: 0.95 for 95% CI)

Returns:

OddsRatioResult object containing estimate and confidence interval

Return type:

OddsRatioResult

Example

>>> table = Table2x2(10, 20, 30, 40)
>>> result = table.odds_ratio()
>>> print(result.estimate)
0.6667
property odds_unexposed: float#

b / d.

Type:

Odds in unexposed group

risk_difference(confidence=0.95)[source]#

Calculate risk difference (attributable risk) with confidence interval.

Risk Difference = Risk_exposed - Risk_unexposed

Parameters:

confidence (float) – Confidence level (default: 0.95)

Returns:

Dictionary with ‘estimate’, ‘ci_lower’, and ‘ci_upper’

Return type:

Dict[str, float]

property risk_exposed: float#

a / (a + c).

Type:

Risk (incidence proportion) in exposed group

risk_ratio(method=ConfidenceMethod.WALD, confidence=0.95)[source]#

Calculate risk ratio (relative risk) with confidence interval.

Risk Ratio = (a/(a+c)) / (b/(b+d))

Parameters:
  • method (ConfidenceMethod) – Method for confidence interval calculation

  • confidence (float) – Confidence level (default: 0.95 for 95% CI)

Returns:

RiskRatioResult object containing estimate and confidence interval

Return type:

RiskRatioResult

Example

>>> table = Table2x2(10, 20, 30, 40)
>>> result = table.risk_ratio()
>>> print(result.estimate)
0.6667
property risk_unexposed: float#

b / (b + d).

Type:

Risk (incidence proportion) in unexposed group

summary()[source]#

Generate comprehensive summary of all calculations.

Returns:

Dictionary with all epidemiological measures

Return type:

Dict[str, float | int | Dict]

to_dict()[source]#

Convert table to dictionary representation.

Return type:

Dict[str, int]

property total: int#

Total number of individuals (a + b + c + d).

property total_cases: int#

Total number of cases (a + b).

property total_exposed: int#

Total number of exposed individuals (a + c).

property total_non_cases: int#

Total number of non-cases (c + d).

property total_unexposed: int#

Total number of unexposed individuals (b + d).

class episia.stats.contingency.RiskRatioResult(estimate, ci_lower, ci_upper, method, table, null_value=1.0)[source]#

Bases: object

Result object for Risk Ratio calculations.

Parameters:
__repr__()[source]#

Return repr(self).

Return type:

str

ci_lower: float#
ci_upper: float#
estimate: float#
method: str#
null_value: float = 1.0#
property p_value: float | None#

Approximate two-sided p-value from the CI (Wald method).

plot(backend='plotly', **kwargs)[source]#

Forest-style plot for this risk ratio.

Parameters:

backend (str)

property significant: bool#

True if the 95% CI does not contain the null value (1.0).

table: Table2x2#
to_dict()[source]#

Return a JSON-serializable dictionary.

Return type:

Dict

class episia.stats.contingency.OddsRatioResult(estimate, ci_lower, ci_upper, method, table, null_value=1.0)[source]#

Bases: object

Result object for Odds Ratio calculations.

Parameters:
__repr__()[source]#

Return repr(self).

Return type:

str

ci_lower: float#
ci_upper: float#
estimate: float#
method: str#
null_value: float = 1.0#
property p_value: float | None#

Approximate two-sided p-value from the CI.

plot(backend='plotly', **kwargs)[source]#

Forest-style plot for this odds ratio.

Parameters:

backend (str)

property significant: bool#

True if the 95% CI does not contain the null value (1.0).

table: Table2x2#
to_dict()[source]#

Return a JSON-serializable dictionary.

Return type:

Dict

class episia.stats.contingency.ConfidenceMethod(value)[source]#

Bases: Enum

Methods for calculating confidence intervals.

DELTA = 'delta'#
EXACT = 'exact'#
SCORE = 'score'#
WALD = 'wald'#

Functions#

episia.stats.contingency.risk_ratio(a, b, c, d, **kwargs)[source]#

Convenience function to calculate risk ratio from raw counts.

Parameters:
  • a (int) – 2x2 table cell counts

  • b (int) – 2x2 table cell counts

  • c (int) – 2x2 table cell counts

  • d (int) – 2x2 table cell counts

  • **kwargs – Passed to Table2x2.risk_ratio()

Returns:

RiskRatioResult object

Return type:

RiskRatioResult

Example

>>> result = risk_ratio(10, 20, 30, 40)
>>> print(result.estimate)
episia.stats.contingency.odds_ratio(a, b, c, d, **kwargs)[source]#

Convenience function to calculate odds ratio from raw counts.

Parameters:
  • a (int) – 2x2 table cell counts

  • b (int) – 2x2 table cell counts

  • c (int) – 2x2 table cell counts

  • d (int) – 2x2 table cell counts

  • **kwargs – Passed to Table2x2.odds_ratio()

Returns:

OddsRatioResult object

Return type:

OddsRatioResult

episia.stats.contingency.from_dataframe(df, exposed_col, outcome_col)[source]#

Create Table2x2 from pandas DataFrame.

Parameters:
  • df – pandas DataFrame

  • exposed_col (str) – Column name for exposure status (True/False or 1/0)

  • outcome_col (str) – Column name for outcome status (True/False or 1/0)

Returns:

Table2x2 object

Return type:

Table2x2

Examples#

Creating a 2x2 table:

from episia.stats.contingency import Table2x2, ConfidenceMethod

# Table: Exposed vs Unexposed, Cases vs Non-cases
table = Table2x2(a=40, b=10, c=20, d=30)

print(f"Risk in exposed: {table.risk_exposed:.3f}")
print(f"Risk in unexposed: {table.risk_unexposed:.3f}")

Risk ratio calculation:

# Risk ratio with Wald confidence interval
rr = table.risk_ratio(method=ConfidenceMethod.WALD, confidence=0.95)
print(rr)  # Risk Ratio: 2.667 (1.514-4.696)
print(f"Significant: {rr.significant}")

Odds ratio calculation:

# Odds ratio with exact confidence interval
or_result = table.odds_ratio(method=ConfidenceMethod.EXACT)
print(or_result)  # Odds Ratio: 6.000 (2.241-16.788)

Comprehensive summary:

summary = table.summary()
print(f"Chi-square: {summary['chi_square']['chi2']:.3f}")
print(f"Fisher exact p-value: {summary['fisher_exact']['p_value']:.4f}")

From DataFrame:

import pandas as pd
from episia.stats.contingency import from_dataframe

df = pd.DataFrame({
    'exposed': [1, 1, 0, 0, 1, 0],
    'case': [1, 0, 1, 0, 1, 0]
})
table = from_dataframe(df, exposed_col='exposed', outcome_col='case')