Constructors#
In OpenDP, Measurements and Transformations are created by calling constructor functions.
The majority of the library’s interface consists of these make_* constructors,
like make_clamp
or make_base_laplace
.
Because Measurements and Transformations are themselves like functions (they can be invoked on an input and return an output), you can think of constructors as higher-order functions: You call them to produce another function that you will then feed data.
Constructors are organized into Transformations, Measurements and Combinators:
Let’s demonstrate with a few examples! In this example, we use a constructor to build a clamp transformation:
>>> from opendp.transformations import make_clamp
>>> clamper = make_clamp(bounds=(0, 10))
...
>>> # invoke the function with some data
>>> clamper([-1, 0, 3, 5, 10, 20])
[0, 0, 3, 5, 10, 10]
The input metric and output metric are implicitly SymmetricDistance. We can use the stability map on this transformation to ask a hypothetical question: If neigboring datasets differ by as much as d_in, then how much can neigboring datasets differ after running this transformation (d_out)?
>>> # map an input distance to an output distance
>>> clamper.map(d_in = 1)
1
We know the clamp transformation is 1-stable, so d_out = 1 * d_in. Similarly, we can check if the transformation is (d_in, d_out)-close (that is, map(d_in) <= d_out ) by checking the stability relation:
>>> # check if clamper is (d_in, d_out)-close
>>> clamper.check(d_in = 1, d_out = 1)
True
We can use the >> shorthand to chain multiple transformations (internally uses the make_chain_tt combinator).
>>> from opendp.transformations import make_cast_default
...
>>> # build another transformation that casts, and fills nulls with a default value (0)
>>> caster = make_cast_default(TIA=str, TOA=int)
...
>>> # construct a new transformation such that preprocessor(x) = clamper(caster(x))
>>> preprocessor = caster >> clamper
...
>>> # invoke the chained transformation
>>> preprocessor(["1", "2", "3", "20", "a"])
[1, 2, 3, 10, 0]
>>> # since both are 1-stable transformations, then the map remains trivial
>>> preprocessor.map(d_in=2)
2
In this simplified example with the opendp.measurements.make_base_discrete_laplace()
constructor, we assume the data was properly preprocessed and aggregated such that the sensitivity (by absolute distance) is at most 1.
>>> from opendp.measurements import make_base_discrete_laplace
...
>>> # call the constructor to produce the measurement `base_dl`
>>> base_dl = make_base_discrete_laplace(scale=1.0)
...
>>> # investigate the privacy relation
>>> absolute_distance = 1
>>> base_dl.map(d_in=absolute_distance) # returns epsilon
1.0
>>> # feed some data/invoke the measurement as a function
>>> aggregated = 5
>>> release = base_dl(aggregated)
As you can see, constructor functions are the gateway to building differentially private analyses in OpenDP. The next sections are a tour of the available constructor functions.