diff --git a/notebooks/Untitled.ipynb b/notebooks/Untitled.ipynb
new file mode 100644
index 0000000..1e154e2
--- /dev/null
+++ b/notebooks/Untitled.ipynb
@@ -0,0 +1,238 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import itertools \n",
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "# from pandas_risk import *\n",
+ "from time import time\n",
+ "import os\n",
+ "\n",
+ "attr = ['gender','race','zip','year_of_birth']\n",
+ "comb_attr = [\n",
+ " ['zip' ,'gender', 'birth_datetime', 'race'], \n",
+ " ['zip', 'gender', 'year_of_birth', 'race'], \n",
+ " ['gender','race','zip'],\n",
+ " ['race','year_of_birth','zip']\n",
+ "]\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "SQL_CONTROLLED=\"SELECT * FROM deid_risk.basic_risk60k\"\n",
+ "dfc = pd.read_gbq(SQL_CONTROLLED,private_key='/home/steve/dev/google-cloud-sdk/accounts/curation-test.json')\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def risk(**args):\n",
+ " Yi = args['data']\n",
+ " Yi = Yi.fillna(' ')\n",
+ " sizes = args['prop'] if 'prop' in args else np.arange(5,100,5)\n",
+ " FLAG = args['flag'] if 'flag' in args else 'UNFLAGGED'\n",
+ " N = args['num_runs']\n",
+ " if 'cols' in args :\n",
+ " columns = args['cols']\n",
+ " else:\n",
+ " columns = list(set(Yi.columns.tolist()) - set(['person_id']))\n",
+ " p = pd.DataFrame()\n",
+ " y_i= pd.DataFrame({\"group_size\":Yi.groupby(columns,as_index=False).size()}).reset_index()\n",
+ " for index in sizes :\n",
+ " for n in np.repeat(index,N):\n",
+ " \n",
+ " # we will randomly sample n% rows from the dataset\n",
+ " i = np.random.choice(Yi.shape[0],((Yi.shape[0] * n)/100),replace=False)\n",
+ " x_i= pd.DataFrame(Yi).loc[i] \n",
+ " risk = x_i.deid.risk(id='person_id',quasi_id = columns)\n",
+ " x_i = pd.DataFrame({\"group_size\":x_i.groupby(columns,as_index=False).size()}).reset_index()\n",
+ "\n",
+ "\n",
+ " r = pd.merge(x_i,y_i,on=columns,how='inner')\n",
+ " if r.shape[0] == 0 :\n",
+ " continue\n",
+ " r['marketer'] = r.apply(lambda row: (row.group_size_x / np.float64(row.group_size_y)) /np.sum(x_i.group_size) ,axis=1)\n",
+ " r['sample %'] = np.repeat(n,r.shape[0])\n",
+ " r['tier'] = np.repeat(FLAG,r.shape[0])\n",
+ " r['sample marketer'] = np.repeat(risk['marketer'].values[0],r.shape[0])\n",
+ " # r['patient_count'] = np.repeat(r.shape[0],r.shape[0])\n",
+ " r = r.groupby(['sample %','tier','sample marketer'],as_index=False).sum()[['sample %','marketer','sample marketer','tier']]\n",
+ " p = p.append(r)\n",
+ " p.index = np.arange(p.shape[0]).astype(np.int64)\n",
+ " return p\n",
+ " \n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pandas_risk import *\n",
+ "o = pd.DataFrame()\n",
+ "PATH=\"out/experiment-phase-2.xlsx\"\n",
+ "writer = pd.ExcelWriter(PATH,engine='xlsxwriter')\n",
+ "comb_attr = [\n",
+ " ['zip' ,'gender', 'birth_datetime', 'race'], \n",
+ " ['zip', 'gender', 'year_of_birth', 'race'], \n",
+ " ['gender','race','zip'],\n",
+ " ['race','year_of_birth','zip']\n",
+ "]\n",
+ "\n",
+ "for cols in comb_attr :\n",
+ " o = risk(data=dfc,cols=cols,flag='CONTROLLED',num_runs=5)\n",
+ " #\n",
+ " # adding the policy\n",
+ " x = [1* dfc.columns.isin(cols) for i in range(o.shape[0])]\n",
+ " o = o.join(pd.DataFrame(x,columns = dfc.columns))\n",
+ " #\n",
+ " # Write this to excel notebook\n",
+ " o.to_excel(writer,\"-\".join(cols))\n",
+ "# break\n",
+ " \n",
+ "\n",
+ "# p = p.rename(columns={'marketer_x':'sample marketer'})\n",
+ "# p.index = np.arange(p.shape[0]).astype(np.int64)\n",
+ "\n",
+ "writer.save()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " person_id | \n",
+ " year_of_birth | \n",
+ " month_of_birth | \n",
+ " day_of_birth | \n",
+ " birth_datetime | \n",
+ " race_concept_id | \n",
+ " ethnicity_concept_id | \n",
+ " location_id | \n",
+ " care_site_id | \n",
+ " person_source_value | \n",
+ " ... | \n",
+ " gender_source_concept_id | \n",
+ " race_source_value | \n",
+ " ethnicity_source_value | \n",
+ " sex_at_birth | \n",
+ " birth_date | \n",
+ " race | \n",
+ " zip | \n",
+ " city | \n",
+ " state | \n",
+ " gender | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
0 rows × 21 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ "Empty DataFrame\n",
+ "Columns: [person_id, year_of_birth, month_of_birth, day_of_birth, birth_datetime, race_concept_id, ethnicity_concept_id, location_id, care_site_id, person_source_value, gender_source_value, gender_source_concept_id, race_source_value, ethnicity_source_value, sex_at_birth, birth_date, race, zip, city, state, gender]\n",
+ "Index: []\n",
+ "\n",
+ "[0 rows x 21 columns]"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x = [1* dfc.columns.isin(cols) for i in range(o.shape[0])]\n",
+ "o.join(pd.DataFrame(x,columns = dfc.columns))\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "NameError",
+ "evalue": "name 'columns' is not defined",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mcolumns\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mNameError\u001b[0m: name 'columns' is not defined"
+ ]
+ }
+ ],
+ "source": [
+ "columns\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 2",
+ "language": "python",
+ "name": "python2"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 2
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython2",
+ "version": "2.7.15rc1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/notebooks/data-analysis.ipynb b/notebooks/data-analysis.ipynb
index d7c44c2..8e4e21b 100644
--- a/notebooks/data-analysis.ipynb
+++ b/notebooks/data-analysis.ipynb
@@ -177,7 +177,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
- "version": "2.7.10"
+ "version": "2.7.15rc1"
},
"varInspector": {
"cols": {
diff --git a/notebooks/data-preparation.ipynb b/notebooks/data-preparation.ipynb
new file mode 100644
index 0000000..adbd66e
--- /dev/null
+++ b/notebooks/data-preparation.ipynb
@@ -0,0 +1,95 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " skiping ...\n",
+ " skiping ...\n",
+ " skiping ...\n",
+ " skiping ...\n",
+ " skiping ...\n",
+ " skiping ...\n",
+ " skiping ...\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "2"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "\"\"\"\n",
+ " This notebook is designed to generate SQL syntax all the quasi-identifiers for the patients in the database\n",
+ " The resulting SQL will be run against bigquery to produce a table with every record mapping to a patient\n",
+ " \n",
+ "\"\"\"\n",
+ "\n",
+ "from risk import *\n",
+ "ihandle = UtilHandler(path='/home/steve/dev/google-cloud-sdk/accounts/curation-prod.json',dataset='combined20180822',key_field='person_id',key_table='person',filter=['person','observation'])\n",
+ "r = ihandle.migrate_tables()\n",
+ "len(r)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "u' SELECT person.person_id , person.year_of_birth , person.month_of_birth , person.day_of_birth , person.birth_datetime , person.race_concept_id , person.ethnicity_concept_id , person.location_id , person.care_site_id , person.person_source_value , person.gender_source_value , person.gender_source_concept_id , person.race_source_value , person.ethnicity_source_value , basic_observation.sex_at_birth AS sex_at_birth1 , basic_observation.birth_date AS birth_date1 , basic_observation.race AS race1 , basic_observation.zip AS zip1 , basic_observation.city AS city1 , basic_observation.state AS state1 , basic_observation.gender AS gender1 FROM (select * from deid_image.person ) as person INNER JOIN (select * from deid_image.basic_observation ) as basic_observation ON basic_observation.person_id = person.person_id '"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "ihandle = UtilHandler(path='/home/steve/dev/google-cloud-sdk/accounts/curation-test.json',dataset='deid_image',key_field='person_id',key_table='person',filter=['person','basic_observation'])\n",
+ "ihandle.create_table().replace('\\n',' ')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 2",
+ "language": "python",
+ "name": "python2"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 2
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython2",
+ "version": "2.7.15rc1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/notebooks/experiments.ipynb b/notebooks/experiments.ipynb
new file mode 100644
index 0000000..3d52a33
--- /dev/null
+++ b/notebooks/experiments.ipynb
@@ -0,0 +1,610 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "\"\"\"\n",
+ " Health Information Privacy Lab\n",
+ " This notebook is intended to run experiments and generate the data to be used by another notebook\n",
+ "\n",
+ " pre-requisites:\n",
+ " - pandas_risk This is a custom framework that will compute risk for a given dataset\n",
+ " - google-cloud-bigquery\n",
+ " - numpy\n",
+ "\"\"\"\n",
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "from pandas_risk import *\n",
+ "from time import time\n",
+ "import os\n",
+ "#\n",
+ "#-- Loading the dataset\n",
+ "class Logger :\n",
+ " cache = []\n",
+ " @staticmethod\n",
+ " def clear():\n",
+ " Logger.cache = []\n",
+ " @staticmethod\n",
+ " def log(**args) :\n",
+ " Logger.cache.append(args)\n",
+ " \n",
+ "SQL_CONTROLLED=\"SELECT person_id,birth_datetime,city,zip,state,race,gender FROM deid_risk.basic_risk60k\"\n",
+ "SQL_REGISTERED = \"SELECT person_id,birth_datetime,city,zip,state,race,gender FROM deid_risk.basic_deid_risk60k\"\n",
+ "dfr = pd.read_gbq(SQL_REGISTERED,private_key='/home/steve/dev/google-cloud-sdk/accounts/curation-test.json')\n",
+ "dfc = pd.read_gbq(SQL_CONTROLLED,private_key='/home/steve/dev/google-cloud-sdk/accounts/curation-test.json')\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 99,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " sample % | \n",
+ " marketer | \n",
+ " sample marketer | \n",
+ " tier | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " 5 | \n",
+ " 0.974945 | \n",
+ " 0.981364 | \n",
+ " controlled | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " 5 | \n",
+ " 0.975513 | \n",
+ " 0.981996 | \n",
+ " controlled | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " 5 | \n",
+ " 0.975798 | \n",
+ " 0.980733 | \n",
+ " controlled | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " 5 | \n",
+ " 0.976364 | \n",
+ " 0.981996 | \n",
+ " controlled | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " 5 | \n",
+ " 0.976364 | \n",
+ " 0.981996 | \n",
+ " controlled | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " sample % marketer sample marketer tier\n",
+ "0 5 0.974945 0.981364 controlled\n",
+ "1 5 0.975513 0.981996 controlled\n",
+ "2 5 0.975798 0.980733 controlled\n",
+ "3 5 0.976364 0.981996 controlled\n",
+ "4 5 0.976364 0.981996 controlled"
+ ]
+ },
+ "execution_count": 99,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "\n",
+ "#\n",
+ "FLAG='REGISTERED-TIER-1'\n",
+ "if FLAG == 'REGISTERED-TIER' :\n",
+ " Yi = pd.DataFrame(dfr)\n",
+ " FOLDER='registered'\n",
+ "else:\n",
+ " Yi = pd.DataFrame(dfc)\n",
+ " FOLDER='controlled'\n",
+ "Yi = Yi.fillna(' ')\n",
+ "N = 5\n",
+ "N_ = str(N)\n",
+ "SUFFIX = FOLDER+'-tier-'+str(N)+'-experiment.xlsx'\n",
+ "PATH = os.sep.join(['out',SUFFIX])\n",
+ "\n",
+ "\n",
+ "columns = list(set(Yi.columns.tolist()) - set(['person_id']))\n",
+ "merged_columns = list(columns)+['field_count']\n",
+ "m = {}\n",
+ "p = pd.DataFrame()\n",
+ "n = 0\n",
+ "y_i= pd.DataFrame({\"group_size\":Yi.groupby(columns,as_index=False).size()}).reset_index()\n",
+ "#.deid.risk(id='person_id',quasi_id=columns)\n",
+ "for index in np.arange(5,105,5):\n",
+ " for n in np.repeat(index,N) :\n",
+ "# np.random.seed( np.random.randint(0,int(time())+np.random.randint(0,1000)+index+n ) \n",
+ " #\n",
+ " # we will randomly sample n% rows from the dataset\n",
+ " i = np.random.choice(Yi.shape[0],((Yi.shape[0] * n)/100),replace=False)\n",
+ " x_i= pd.DataFrame(Yi).loc[i] \n",
+ " risk = x_i.deid.risk(id='person_id',quasi_id = columns)\n",
+ " x_i = pd.DataFrame({\"group_size\":x_i.groupby(columns,as_index=False).size()}).reset_index()\n",
+ " \n",
+ "# y_i= pd.DataFrame(Yi).deid.risk(id='person_id',quasi_id=columns)\n",
+ "\n",
+ "\n",
+ " r = pd.merge(x_i,y_i,on=columns,how='inner')\n",
+ " if r.shape[0] == 0 :\n",
+ " print 'skipping ',n\n",
+ " continue\n",
+ " r['marketer'] = r.apply(lambda row: (row.group_size_x / np.float64(row.group_size_y)) /np.sum(x_i.group_size) ,axis=1)\n",
+ " r['sample %'] = np.repeat(n,r.shape[0])\n",
+ " r['tier'] = np.repeat(FOLDER,r.shape[0])\n",
+ " r['sample marketer'] = np.repeat(risk['marketer'].values[0],r.shape[0])\n",
+ "# r['patient_count'] = np.repeat(r.shape[0],r.shape[0])\n",
+ " r = r.groupby(['sample %','tier','sample marketer'],as_index=False).sum()[['sample %','marketer','sample marketer','tier']]\n",
+ "# r['marketer'] = r.apply(lambda row: (row.group_size_x / row.group_size_y) / row.patient_count_x,axis=1 )\n",
+ "# r = r.groupby(columns+['marketer_x'],as_index=False).sum()[columns+['marketer','marketer_x']]\n",
+ "# r['sample %'] = np.repeat(n,r.shape[0])\n",
+ "# r['tier'] = np.repeat(FOLDER,r.shape[0])\n",
+ " p = p.append(r)\n",
+ "\n",
+ "writer = pd.ExcelWriter(PATH,engine='xlsxwriter')\n",
+ "p = p.rename(columns={'marketer_x':'sample marketer'})\n",
+ "p.index = np.arange(p.shape[0]).astype(np.int64)\n",
+ "p.to_excel(writer,FOLDER)\n",
+ "writer.save()\n",
+ "p.head() "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 100,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 100,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ "