Working with Sessions#

This topic guide covers how to work with one of the core abstractions of Tumult Analytics: Session. In particular, we will demonstrate the different ways that a Session can be initialized and examined. For a simple end-to-end usage example of a Session, a better place to start is the privacy budget tutorial.

At a high level, a Session allows you to evaluate queries on private data in a way that satisfies differential privacy. When creating a Session, private data must first be loaded into it, along with a privacy budget. You can then use pieces of the total privacy budget to evaluate queries and return differentially private results. Tumult Analytics’ privacy promise and its caveats are described in detail in the privacy promise topic guide.

Constructing a Session#

There are two ways to construct a Session:

  • directly by initializing it from a data source

  • or using a Session Builder.

Both options are described below – for even more details, consult the Session API Reference.

Initializing from a data source#

Sessions are constructed from Spark DataFrames. For example, with a dataframe named spark_df you can construct a Session using from_dataframe() as follows:

session_from_dataframe = Session.from_dataframe(
    privacy_budget=PureDPBudget(2),
    source_id="my_private_data",
    dataframe=spark_df,
)

When you load a Spark DataFrame into a Session, you don’t need to specify the schema of the source; it is automatically inferred from the DataFrame’s schema. Recall from the first tutorial that source_id is simply a unique identifier for the private data that is used when constructing queries.

Using a Session Builder#

For analysis use cases involving only one private data source, from_dataframe() is a convenient way of initializing a Session. However, when you have multiple sources of data, a Session Builder may be used instead. First, create your Builder:

session_builder = Session.Builder()

Next, add a private source to it:

session_builder = session_builder.with_private_dataframe(
    source_id="my_private_data",
    dataframe=spark_df,
)

You may add additional private sources to the Session, although this is a more advanced and uncommon use case. Suppose you had additional private data stored in a CSV file:

name, salary
alice, 52000
bob, 75000
carol, 96000
...

First load the data into a Spark dataframe, then add it to the Session:

salary_df = spark.read.csv(private_csv_path, header=True, inferSchema=True)
session_builder = session_builder.with_private_dataframe(
    source_id="my_other_private_data",
    dataframe=salary_df,
)

Any data file format supported by Spark can be used with Tumult Analytics this way. See the Spark data sources documentation for more details on what formats are supported and the available options for them.

A more common use case is to register public data with your Session (e.g., for use in join operations with the private source).

session_builder = session_builder.with_public_dataframe(
    source_id="my_public_data",
    dataframe=public_df,
)

Public sources can also be added retroactively after a Session is created using the add_public_dataframe() method.

When using a Session Builder, you must specify the overall privacy budget separately:

session_builder = session_builder.with_privacy_budget(PureDPBudget(1))

Once your Session is configured, the final step is to build it:

session = session_builder.build()

Examining a Session’s state#

After creation, a Session exposes several pieces of information. You can list the string identifiers of available private or public data sources using private_sources or public_sources, respectively.

print(session.private_sources)
print(session.public_sources)
['my_private_data', 'my_other_private_data']
['my_public_data']

These IDs will typically be used when constructing queries, to specify which data source a query refers to. They can also be used to access schema information about individual data sources, through get_schema().

print(session.get_schema('my_private_data'))
Schema({'name': ColumnDescriptor(column_type=ColumnType.VARCHAR, allow_null=True, allow_nan=False, allow_inf=False),
  'age': ColumnDescriptor(column_type=ColumnType.INTEGER, allow_null=True, allow_nan=False, allow_inf=False),
  'grade': ColumnDescriptor(column_type=ColumnType.DECIMAL, allow_null=True, allow_nan=True, allow_inf=True)})

As you can see, Schemas contain information about what columns are in the data, what their types are, and whether each column can contain null, NaN, or infinite values.

You can access the underlying DataFrames of public sources directly using public_source_dataframes. Note that there is no corresponding accessor for private source DataFrames; after creating a Session, the private data should not be inspected or modified.

The last key piece of information a Session exposes is how much privacy budget the Session has left. As you evaluate queries, the Session’s remaining budget will decrease. The currently-available privacy budget can be accessed through remaining_privacy_budget. For example, we can inspect the budget of our Session created from the Builder above:

print(session.remaining_privacy_budget)
PureDPBudget(epsilon=1)

We have not evaluated any queries yet using this Session, so the remaining budget is the same as the total budget that we initialized the Session with earlier.