Skip to main content

Differentially private publication pipeline for aggregate group statistics with k-anonymity gating and budget accounting

Project description

dp-group-stats

A lightweight Python library for publishing differentially private aggregate statistics over small groups, with k-anonymity gating and privacy budget accounting.

PyPI version Python 3.10+ License: MIT

What this solves

When publishing aggregate statistics (means, sums) for small professional groups on a recurring schedule, you need:

  1. Differential privacy — calibrated noise so individual contributions can't be reverse-engineered
  2. K-anonymity gating — suppress groups that are too small to publish safely
  3. Budget accounting — track cumulative privacy spend per user across groups and time
  4. Graceful degradation — when budget runs low, statistics get noisier instead of disappearing

Existing DP libraries are either massive frameworks (OpenDP, TensorFlow Privacy) or bare mechanism implementations. dp-group-stats is the pipeline layer in between: configuration, noise, publication policy, and budget tracking in one focused package with zero dependencies.

Install

pip install dp-group-stats

Requirements: Python 3.10+ — no dependencies beyond the standard library.

Quick start

from datetime import date
from dp_group_stats import (
    DPGroupStatsConfig,
    InMemoryPrivacyLedger,
    PublicationStatus,
    compute_adaptive_epsilon,
    get_period_bounds,
    get_publication_status,
    laplace_noise,
    laplace_ci_half_width,
)

# 1. Configure the pipeline
config = DPGroupStatsConfig(
    period_type="weekly",
    annual_epsilon_cap=150.0,
)

# 2. Check publication eligibility
status = get_publication_status(
    was_active=False,
    consecutive_eligible=3,
    consecutive_ineligible=0,
    activation_weeks=config.release_policy.activation_weeks,
    deactivation_grace_weeks=config.release_policy.deactivation_grace_weeks,
)
assert status == PublicationStatus.published

# 3. Clip contributions and add noise
raw_actual_hours = [42.0, 55.0, 38.0, 61.0, 200.0, 44.0]
clipped = [config.bounds.clip_actual(h) for h in raw_actual_hours]
true_sum = sum(clipped)

noisy_sum = true_sum + laplace_noise(
    epsilon=config.epsilon_split.actual_sum,
    sensitivity=config.bounds.actual_sensitivity,
)
noisy_mean = noisy_sum / len(clipped)

# 4. Compute confidence intervals
ci_half, n_display = laplace_ci_half_width(
    epsilon=config.epsilon_split.actual_sum,
    sensitivity=config.bounds.actual_sensitivity,
    n_users=len(clipped),
)
print(f"Mean: {noisy_mean:.1f}h [±{ci_half:.1f}h 90% CI] (~{n_display} reporters)")

# 5. Track budget
ledger = InMemoryPrivacyLedger()
for user_id in ["u1", "u2", "u3", "u4", "u5", "u6"]:
    ledger.record(
        user_id=user_id,
        family="state_specialty",
        cell_key="DEU/BE/cardiology",
        period_start=date(2026, 3, 16),
        epsilon=config.epsilon_split.total,
    )

summary = ledger.budget_summary(annual_cap=config.annual_epsilon_cap)
print(f"Worst-case user: {summary['worst_case_spent']:.1f}ε of {summary['annual_cap']}ε cap")

Components

Configuration (dp_group_stats.config)

  • ContributionBounds — clipping bounds that limit sensitivity (default: planned [0, 80h], actual [0, 120h])
  • EpsilonSplit — how per-cell epsilon is divided across quantities (default: 0.2 planned, 0.8 actual)
  • ReleasePolicyConfig — k-anonymity threshold, dominance rule, activation/deactivation streaks
  • DPGroupStatsConfig — top-level config with budget validation at construction time

Mechanisms (dp_group_stats.mechanisms)

  • laplace_noise() — Laplace mechanism with injectable RNG for deterministic testing
  • laplace_ci_half_width() — confidence intervals with rounded group size to avoid leaking exact n

Policy (dp_group_stats.policy)

Publication state machine: suppressed → warming_up → published → cooling_down → suppressed

  • Cells must pass k_min and dominance checks for N consecutive periods before publishing
  • Grace period before suppression (noise continues during cooldown to prevent information leakage)

Accounting (dp_group_stats.accounting)

  • PrivacyLedger (Protocol) — pluggable interface for budget tracking
  • InMemoryPrivacyLedger — reference implementation for testing/simulation
  • EpsilonLedger — lightweight cell-only tracker
  • compute_adaptive_epsilon() — graceful degradation that scales epsilon down as the annual cap approaches

Bring your own storage backend by implementing the PrivacyLedger protocol (e.g., SQL-backed for production).

Periods (dp_group_stats.periods)

  • get_period_bounds() — weekly/biweekly/monthly period boundaries
  • period_before() / compute_period_index() — temporal navigation

Design decisions

This library makes specific choices documented in the design specs:

  • Substitution neighboring relation: DP protects contribution values, not user presence. Counts are public; only sums are noised.
  • Per-user budget accounting: the privacy guarantee lives with users, not cells. A user appearing in multiple release families accumulates epsilon across all of them.
  • Time-based degradation: adaptive epsilon is computed from elapsed time and configured spending, not from actual per-user data (which would itself leak information).
  • K-anonymity as policy, not proof: k_min and dominance rules are publication safeguards layered on top of DP, not substitutes for it.

Developed for

This library was extracted from Open Working Hours, a privacy-first platform for healthcare workers to track and report working hours. The design was validated through 200+ simulation scenarios covering parameter selection for workforce statistics.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dp_group_stats-0.1.0.tar.gz (49.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dp_group_stats-0.1.0-py3-none-any.whl (18.9 kB view details)

Uploaded Python 3

File details

Details for the file dp_group_stats-0.1.0.tar.gz.

File metadata

  • Download URL: dp_group_stats-0.1.0.tar.gz
  • Upload date:
  • Size: 49.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for dp_group_stats-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9cf25804247731f27c69263ca5ad6af3ab2d99287933388e77aef6f5ca18bcea
MD5 41e26eb4a0b4ff150003c3e5240b4e0b
BLAKE2b-256 281f13fd02863a2326c145580a4108449e60fa74488b17abfdba7f3878252206

See more details on using hashes here.

File details

Details for the file dp_group_stats-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: dp_group_stats-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for dp_group_stats-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e7e5a2b2518a4f923df1c0ac2480367d77570ed193c590aeabd91d6ac4db1137
MD5 6f4eb7eebc8fd030a05dd333abcd1aba
BLAKE2b-256 6dc0c669e4ca20373266100303c1371d437f6d458b835385e14987426609d5e5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page