Somebody has to write a number into the manifest, there is no precedent to copy, and every source that discusses the choice explains what epsilon means without saying what to set it to.
Part of Privacy-Preserving Generation Frameworks: where that page covers the mechanisms and their guarantees, this one is the practical question of picking the parameter the first time, and of recording the choice so it can be defended and revised.
The reason no source tells you what to set is that there is no answer independent of the data and the release. Epsilon bounds a ratio of probabilities; it says nothing on its own about how far a point moves, how many people are protected, or whether anything useful survives.
Three things convert it into something decidable, and all three are properties of your release rather than of the definition:
The sensitivity. How much one individual can change the output. For a coordinate this is a distance, and it is the parameter that turns epsilon into metres. Nothing about a chosen epsilon means anything until the sensitivity is fixed.
The utility floor. The worst distortion a consumer will accept, expressed in their units — a positional error, a cell-count error, a metric tolerance. This gives an upper bound on the noise and therefore a lower bound on epsilon.
The release schedule. How many times this population will be published against. Because the budget composes, a per-release epsilon that is fine once is not fine fifty-two times, and the schedule fixes the relationship between the two.
Three inputs convert epsilon from a number with no scale into a bounded interval — and two of the three come from outside the privacy team.
import math
defcoordinate_sensitivity(contract:dict)->float:"""How far one individual can move the output, in metres.
For a released point set this is bounded by the clamp: an individual can move
their own coordinate anywhere within the declared envelope, so the sensitivity
is the envelope diagonal unless the pipeline bounds it more tightly.
"""
w, h = contract["envelope_size_m"]return math.hypot(w, h)defbounded_sensitivity(contract:dict)->float:"""The tighter bound most pipelines can actually claim.
Clamping each contribution to a radius R around its own aggregation cell caps the
sensitivity at 2R regardless of the envelope, which is usually orders of magnitude
smaller — and it is the single most effective thing you can do before choosing ε.
"""return2* contract["contribution_clamp_radius_m"]
The gap between those two functions is the first thing worth measuring, and it is usually enormous: an envelope diagonal of tens of kilometres against a contribution clamp of a few hundred metres. Since the noise scales with sensitivity, tightening it buys utility at no cost to the guarantee — which makes it strictly better than raising epsilon, and it should be exhausted first.
defepsilon_floor(sensitivity_m:float, max_acceptable_error_m:float,
percentile:float=0.95)->float:"""The smallest ε whose error stays under the consumer's tolerance at a given percentile.
For the Laplace mechanism the radial error at percentile p is approximately
-(Δ/ε)·ln(1-p), so inverting gives the ε that keeps the p-th percentile within
the tolerance.
"""return-sensitivity_m * math.log(1- percentile)/ max_acceptable_error_m
The percentile matters more than the formula. Choosing the median gives an epsilon under which half the consumer’s data is worse than they asked for; choosing the ninety-fifth gives one under which one point in twenty is. Ask the consumer which they meant, because they will have a view and it is usually the second.
defepsilon_ceiling(annual_budget:float, releases_per_year:int,
composition:str="basic")->float:"""The largest per-release ε that keeps the composed total under the annual budget."""if composition =="basic":return annual_budget / releases_per_year
delta_prime =1e-6
lo, hi =1e-4, annual_budget
for _ inrange(60):# invert the advanced-composition bound
mid =(lo + hi)/2
k = releases_per_year
composed =(math.sqrt(2* k * math.log(1/ delta_prime))* mid
+ k * mid *(math.exp(mid)-1))
lo, hi =(mid, hi)if composed < annual_budget else(lo, mid)return lo
Two things are worth noticing. The ceiling depends on the composition rule, so the accounting decision has to be made before the parameter can be chosen. And the ceiling for a weekly schedule is far tighter than most first-time choosers expect — an annual budget of 3 over 52 releases is a per-release epsilon under 0.06 under plain addition.
defchoose(sensitivity_m, max_error_m, annual_budget, releases_per_year)->dict:
lo = epsilon_floor(sensitivity_m, max_error_m)
hi = epsilon_ceiling(annual_budget, releases_per_year)return{"sensitivity_m": sensitivity_m,"epsilon_floor": lo,"epsilon_ceiling": hi,"feasible": lo <= hi,"chosen":min(max(lo, lo), hi)if lo <= hi elseNone,}
An empty interval is the most useful output this produces, because it means the release as specified is impossible and it says which of four things has to give: tighten the sensitivity, relax the utility floor, publish less often, or raise the annual budget. Those are four different conversations with four different people, and knowing which one to have is most of the work.
The feasible interval computed across a range of release cadences: the ceiling falls with frequency while the floor does not move, and the interval closes at a cadence most teams choose without checking.
A manifest carrying only the epsilon is a manifest whose number cannot be revisited: a year later nobody knows what it was derived from, so nobody can tell whether a changed schedule or a changed tolerance invalidates it. Recording the derivation makes the number auditable and, more usefully, makes it recomputable when one of its inputs moves.
Nobody will give you a utility floor. Common, and the way through it is to invert the question: generate at three epsilons, show the consumer the measured displacement distribution for each, and ask which is unacceptable. People who cannot state a tolerance in the abstract answer immediately when shown the data.
Measured on the same release: tightening the clamp beats doubling ε twice, and it spends nothing.
The sensitivity is unbounded. If an individual can contribute an unlimited number of records, no epsilon bounds anything. Cap contributions per individual before anything else — it is a precondition for the guarantee rather than a tuning decision.
The first release is a one-off that later becomes a series. Extremely common, and the reason to compute the ceiling even when releases_per_year is one. Record the assumption explicitly, so that when somebody proposes a second release the constraint surfaces rather than being discovered after publication.
Choosing the floor exactly. The interval’s lower end is the smallest epsilon meeting the utility requirement, which means the most private feasible choice — and it is the right default. Choosing higher inside the interval buys utility nobody asked for at a cost in guarantee that somebody will eventually have to defend.
Choosing epsilon is not the end of the task, and three things follow immediately that are easy to
leave undone.
Publish the measured consequence, not the parameter. A release note saying “ε = 0.12” tells a
consumer nothing they can act on. One saying “ε = 0.12; ninety-fifth-percentile per-cell error 24
counts; measured over the actual release” tells them exactly what they are getting. The epsilon is
the guarantee and the measurement is the utility, and consumers need both.
Set a review trigger, not a review date. The number depends on three inputs, and it should be
recomputed when any of them moves rather than on a calendar. A change to the contribution clamp, a
change to the consumer’s tolerance, or a change to the release cadence should each fail a check
that compares the recorded derivation against the current inputs — which is exactly what the
derivation record makes possible.
Run the adversarial check at the chosen value. The guarantee is a worst-case bound; what your
generator, your data and your schedule actually produce is an empirical question, and it is
answered by running a membership-inference attack
at the chosen epsilon. A measured AUC near chance is evidence a reviewer can check; an epsilon on
a manifest is a promise they have to take on trust.