diff --git a/examples/conference.ipynb b/examples/conference.ipynb new file mode 100644 index 00000000..213b80df --- /dev/null +++ b/examples/conference.ipynb @@ -0,0 +1,287 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "collapsed_sections": [], + "name": "Conference example", + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "bW1gifIe0pUt" + }, + "source": [ + "\n", + " \n", + " \n", + "
\n", + " Run in Google Colab\n", + " \n", + " View source on GitHub\n", + "
" + ] + }, + { + "cell_type": "markdown", + "source": [ + "This is a simple example that shows how to calculate anonymized statistics using PipelineDP. The input data is a simulated dataset of an imaginary conference participants including their origin coutries. We use PipelineDP to calculate anonymized count of participants aggregated by country." + ], + "metadata": { + "id": "ddrCVxp53UjV" + } + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zxcPpZGuAPq8" + }, + "source": [ + "# Install dependencies and download data\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "E8yzpKYNbHTF" + }, + "outputs": [], + "source": [ + "#@markdown Install dependencies and download data\n", + "\n", + "!pip install pipeline-dp apache_beam\n", + "\n", + "from IPython.display import clear_output\n", + "clear_output()\n", + "import pipeline_dp\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "oi-D38dUApM1" + }, + "source": [ + "# Construct and inspect the input data\n", + "\n", + "Below we construct the input dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "Mimkjqt9h9gr", + "cellView": "form" + }, + "outputs": [], + "source": [ + "#@markdown Construct the input data\n", + "# The format of the input is: (participant_id, country).\n", + "# Participants u_0...u_49 come from Germany, participants u_50...u_149 come from\n", + "# Switzerland, etc.\n", + "input = [(f\"{u}\", \"Germany\") for u in range(50)]\n", + "input += [(f\"{u + 50}\", \"Switzerland\") for u in range(75)]\n", + "input += [(f\"{u + 125}\", \"France\") for u in range(30)]\n", + "input += [(f\"{u + 155}\", \"Italy\") for u in range(40)]\n", + "input += [(f\"{u + 195}\", \"UK\") for u in range(100)]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e2SOjo8qiNnw" + }, + "source": [ + "The goal of this Colab is to demonstrate how to compute the count of participants aggregated by country in a DP manner.\n", + "\n", + "The plot below demonstrates the non-private result." + ] + }, + { + "cell_type": "code", + "source": [ + "#@title Non-private statistics\n", + "countries = [\"Germany\", \"Switzerland\", \"France\", \"Italy\", \"UK\"]\n", + "non_dp_count = [0] * len(countries)\n", + "for participant_info in input:\n", + " country = participant_info[1]\n", + " index = countries.index(country)\n", + " non_dp_count[index] = non_dp_count[index] + 1\n", + "\n", + "plt.bar(countries, non_dp_count)\n", + "plt.suptitle('Count of participants')\n", + "plt.show()\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 294 + }, + "id": "qR1dBaCiqNAa", + "outputId": "9ccdf49d-a9f9-4fce-d7c7-6fc3d66f2fda", + "cellView": "form" + }, + "execution_count": 3, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + } + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Run DP pipeline\n", + "Below we compute the same statistics using differential privacy and PipelineDP." + ], + "metadata": { + "id": "IIjQrB3eFmvp" + } + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "eN9fu0NkSA6u", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "15bf4d5c-6a2e-48a0-8830-81c1a5ecbab4" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[('Germany', MetricsTuple(privacy_id_count=49.10272994384104)), ('Switzerland', MetricsTuple(privacy_id_count=75.26271629976691)), ('France', MetricsTuple(privacy_id_count=32.206397102141636)), ('Italy', MetricsTuple(privacy_id_count=37.134226348807715)), ('UK', MetricsTuple(privacy_id_count=97.95130274764233))]\n" + ] + } + ], + "source": [ + "#@title DP statistics\n", + "\n", + "# Choose the backend: local, Beam or Spark\n", + "backend = pipeline_dp.LocalBackend()\n", + "\n", + "# Define the total privacy loss that can be introduced by this pipeline\n", + "budget_accountant = pipeline_dp.NaiveBudgetAccountant(total_epsilon=1, total_delta=1e-6)\n", + "\n", + "# Create DPEngine\n", + "dp_engine = pipeline_dp.DPEngine(budget_accountant, backend)\n", + "\n", + "# Configure functions to extract partition key, privacy ID and aggregated value\n", + "# from the input data\n", + "data_extractors = pipeline_dp.DataExtractors(\n", + " partition_extractor=lambda row: row[1],\n", + " privacy_id_extractor=lambda row: row[0],\n", + " value_extractor=lambda row: 1)\n", + "\n", + "# Configure the aggregation parameters\n", + "params = pipeline_dp.AggregateParams(\n", + " noise_kind=pipeline_dp.NoiseKind.LAPLACE,\n", + " metrics=[pipeline_dp.Metrics.PRIVACY_ID_COUNT],\n", + " max_partitions_contributed=1,\n", + " max_contributions_per_partition=1,\n", + " min_value=0,\n", + " max_value=1)\n", + "\n", + "# Create a computational graph for the aggregation.\n", + "# All computations are lazy. dp_result is iterable, but iterating it would\n", + "# fail until budget is computed (below).\n", + "# It’s possible to call DPEngine.aggregate multiple times with different\n", + "# metrics to compute.\n", + "dp_result = dp_engine.aggregate(input, params, data_extractors)\n", + "\n", + "# Compute budget per each DP operation. \n", + "budget_accountant.compute_budgets()\n", + "\n", + "# Here's where the lazy iterator initiates computations and gets transformed\n", + "# into actual results\n", + "dp_result = list(dp_result)\n", + "print(dp_result)" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Inspect the result" + ], + "metadata": { + "id": "QsguG0DeF_8L" + } + }, + { + "cell_type": "code", + "source": [ + "#@markdown ##Inspect the result\n", + "#@markdown Below you can see the DP and non-DP results.\n", + "\n", + "dp_count = [0] * len(countries)\n", + "for i, dp_count_per_country in enumerate(dp_result):\n", + " dp_count[i] = dp_count_per_country[1][0]\n", + "\n", + "\n", + "x = np.arange(len(countries))\n", + "\n", + "width = 0.35\n", + "fig, ax = plt.subplots()\n", + "rects1 = ax.bar(x - width/2, non_dp_count, width, label='non-DP')\n", + "rects2 = ax.bar(x + width/2, dp_count, width, label='DP')\n", + "ax.set_title('Count participants per country')\n", + "ax.set_xticks(x)\n", + "ax.set_xticklabels(countries)\n", + "ax.legend()\n", + "fig.tight_layout()\n", + "plt.savefig(\"chart.png\")\n", + "plt.show()\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 297 + }, + "id": "sTkYZ0wSbo3h", + "outputId": "82d59080-d00b-4c00-ff90-dde4838541d6" + }, + "execution_count": 5, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + } + } + ] + } + ] +}