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.