Skip to content

Instantly share code, notes, and snippets.

@aflaxman
Created February 12, 2021 19:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aflaxman/128630fe47851c475c9e642df64e780b to your computer and use it in GitHub Desktop.
Save aflaxman/128630fe47851c475c9e642df64e780b to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Fri Feb 12 10:35:30 PST 2021\r\n"
]
}
],
"source": [
"import numpy as np, matplotlib.pyplot as plt, pandas as pd\n",
"pd.set_option('display.max_rows', 8)\n",
"!date\n",
"\n",
"%load_ext autoreload\n",
"%autoreload 2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# A sketch of fitting and sampling household structure\n",
"\n",
"Synthesizing a population for testing the Census DAS could be aided by having a model for grouping individuals into household. This notebook sketches an approach that uses MCMC and logistic regression to attempt this, and is inspired by the exponential random graph model (ERGM) approach. Since I want a fixed set of household sizes, I'm going to do the MCMC myself.\n",
"\n",
"## First specify and fit a model for dyads"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"# In a future version of this notebook, this data should come\n",
"# from ACS PUMS, perhaps specific to a small area.\n",
"\n",
"N = 10\n",
"data = pd.DataFrame(index=range(N))\n",
"data['sex'] = np.random.choice([1,2], size=N)\n",
"data['race'] = np.random.choice([1,2,3,4], size=N)\n",
"data['age'] = np.random.uniform(0, 100, size=N)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>sex</th>\n",
" <th>race</th>\n",
" <th>age</th>\n",
" <th>hh_id</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>1</td>\n",
" <td>69.392177</td>\n",
" <td>0</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>4</td>\n",
" <td>5.988308</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>1</td>\n",
" <td>3</td>\n",
" <td>74.787229</td>\n",
" <td>1</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>1</td>\n",
" <td>4</td>\n",
" <td>59.340454</td>\n",
" <td>2</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>6</th>\n",
" <td>1</td>\n",
" <td>3</td>\n",
" <td>3.625385</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>7</th>\n",
" <td>1</td>\n",
" <td>3</td>\n",
" <td>73.313203</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>8</th>\n",
" <td>1</td>\n",
" <td>4</td>\n",
" <td>14.528634</td>\n",
" <td>3</td>\n",
" </tr>\n",
" <tr>\n",
" <th>9</th>\n",
" <td>1</td>\n",
" <td>4</td>\n",
" <td>13.324554</td>\n",
" <td>3</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>10 rows × 4 columns</p>\n",
"</div>"
],
"text/plain": [
" sex race age hh_id\n",
"0 1 1 69.392177 0\n",
"1 2 4 5.988308 1\n",
"2 1 3 74.787229 1\n",
"3 1 4 59.340454 2\n",
".. ... ... ... ...\n",
"6 1 3 3.625385 3\n",
"7 1 3 73.313203 3\n",
"8 1 4 14.528634 3\n",
"9 1 4 13.324554 3\n",
"\n",
"[10 rows x 4 columns]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data['hh_id'] = [0, 1, 1, 2, 2, 2, 3, 3, 3, 3] # the reason to use PUMS is to have these household ids\n",
"data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The ERGM-inspired model includes a term for each pair of rows\n",
"\n",
"And the likelihood for a network is the product of the likelihood for each dyad; to get this we will fit a logistic regression to predict if each dyad is in the same household."
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>1</th>\n",
" <th>2</th>\n",
" <th>3</th>\n",
" <th>4</th>\n",
" <th>5</th>\n",
" <th>6</th>\n",
" <th>7</th>\n",
" <th>8</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1.0</td>\n",
" <td>1.0</td>\n",
" <td>69.392177</td>\n",
" <td>2.0</td>\n",
" <td>4.0</td>\n",
" <td>5.988308</td>\n",
" <td>2.0</td>\n",
" <td>4.0</td>\n",
" <td>415.541701</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>1.0</td>\n",
" <td>1.0</td>\n",
" <td>69.392177</td>\n",
" <td>1.0</td>\n",
" <td>3.0</td>\n",
" <td>74.787229</td>\n",
" <td>1.0</td>\n",
" <td>3.0</td>\n",
" <td>5189.648585</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>1.0</td>\n",
" <td>1.0</td>\n",
" <td>69.392177</td>\n",
" <td>1.0</td>\n",
" <td>4.0</td>\n",
" <td>59.340454</td>\n",
" <td>1.0</td>\n",
" <td>4.0</td>\n",
" <td>4117.763280</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>1.0</td>\n",
" <td>1.0</td>\n",
" <td>69.392177</td>\n",
" <td>2.0</td>\n",
" <td>3.0</td>\n",
" <td>60.984843</td>\n",
" <td>2.0</td>\n",
" <td>3.0</td>\n",
" <td>4231.870975</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>41</th>\n",
" <td>1.0</td>\n",
" <td>3.0</td>\n",
" <td>3.625385</td>\n",
" <td>1.0</td>\n",
" <td>4.0</td>\n",
" <td>13.324554</td>\n",
" <td>1.0</td>\n",
" <td>12.0</td>\n",
" <td>48.306640</td>\n",
" </tr>\n",
" <tr>\n",
" <th>42</th>\n",
" <td>1.0</td>\n",
" <td>3.0</td>\n",
" <td>73.313203</td>\n",
" <td>1.0</td>\n",
" <td>4.0</td>\n",
" <td>14.528634</td>\n",
" <td>1.0</td>\n",
" <td>12.0</td>\n",
" <td>1065.140670</td>\n",
" </tr>\n",
" <tr>\n",
" <th>43</th>\n",
" <td>1.0</td>\n",
" <td>3.0</td>\n",
" <td>73.313203</td>\n",
" <td>1.0</td>\n",
" <td>4.0</td>\n",
" <td>13.324554</td>\n",
" <td>1.0</td>\n",
" <td>12.0</td>\n",
" <td>976.865730</td>\n",
" </tr>\n",
" <tr>\n",
" <th>44</th>\n",
" <td>1.0</td>\n",
" <td>4.0</td>\n",
" <td>14.528634</td>\n",
" <td>1.0</td>\n",
" <td>4.0</td>\n",
" <td>13.324554</td>\n",
" <td>1.0</td>\n",
" <td>16.0</td>\n",
" <td>193.587564</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>45 rows × 9 columns</p>\n",
"</div>"
],
"text/plain": [
" 0 1 2 3 4 5 6 7 8\n",
"0 1.0 1.0 69.392177 2.0 4.0 5.988308 2.0 4.0 415.541701\n",
"1 1.0 1.0 69.392177 1.0 3.0 74.787229 1.0 3.0 5189.648585\n",
"2 1.0 1.0 69.392177 1.0 4.0 59.340454 1.0 4.0 4117.763280\n",
"3 1.0 1.0 69.392177 2.0 3.0 60.984843 2.0 3.0 4231.870975\n",
".. ... ... ... ... ... ... ... ... ...\n",
"41 1.0 3.0 3.625385 1.0 4.0 13.324554 1.0 12.0 48.306640\n",
"42 1.0 3.0 73.313203 1.0 4.0 14.528634 1.0 12.0 1065.140670\n",
"43 1.0 3.0 73.313203 1.0 4.0 13.324554 1.0 12.0 976.865730\n",
"44 1.0 4.0 14.528634 1.0 4.0 13.324554 1.0 16.0 193.587564\n",
"\n",
"[45 rows x 9 columns]"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def data_to_dyads(data):\n",
" y = []\n",
" X = []\n",
"\n",
" for i in range(N):\n",
" for j in range(i+1, N):\n",
" in_same_hh = 1.0 * (data.hh_id[i] == data.hh_id[j])\n",
" y.append(in_same_hh)\n",
"\n",
" # in a future version, do more careful feature engineering for this feature vector\n",
" feature_vector = list(data.iloc[i, :3]) + list(data.iloc[j, :3]) + list(data.iloc[i,:3] * data.iloc[j, :3])\n",
" X.append(feature_vector)\n",
" X, y = np.array(X), np.array(y)\n",
" return X, y\n",
"X, y = data_to_dyads(data)\n",
"pd.DataFrame(X)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [],
"source": [
"import sklearn.linear_model"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,\n",
" intercept_scaling=1, l1_ratio=None, max_iter=100,\n",
" multi_class='warn', n_jobs=None, penalty='l2',\n",
" random_state=None, solver='liblinear', tol=0.0001, verbose=0,\n",
" warm_start=False)"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mod = sklearn.linear_model.LogisticRegression(solver='liblinear')\n",
"mod.fit(X, y)"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>0</th>\n",
" <th>1</th>\n",
" <th>2</th>\n",
" <th>3</th>\n",
" <th>4</th>\n",
" <th>5</th>\n",
" <th>6</th>\n",
" <th>7</th>\n",
" <th>8</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>-0.816132</td>\n",
" <td>0.506608</td>\n",
" <td>-0.004909</td>\n",
" <td>0.295278</td>\n",
" <td>-0.462564</td>\n",
" <td>0.013626</td>\n",
" <td>-0.609106</td>\n",
" <td>0.070913</td>\n",
" <td>-0.000462</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" 0 1 2 3 4 5 6 \\\n",
"0 -0.816132 0.506608 -0.004909 0.295278 -0.462564 0.013626 -0.609106 \n",
"\n",
" 7 8 \n",
"0 0.070913 -0.000462 "
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pd.DataFrame(mod.coef_) # TODO: clearer names for coefficients"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Now use this model to calculate the likelihood for a network"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([[-0.04726385, -3.0755483 ],\n",
" [-0.0272106 , -3.61772299],\n",
" [-0.02448233, -3.72201975],\n",
" [-0.02565874, -3.67567278],\n",
" [-0.12470837, -2.14348355]])"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mod.predict_log_proba(X)[:5]"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([-0.04726385, -3.61772299, -0.02448233])"
]
},
"execution_count": 27,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mod.predict_log_proba(X)[[0,1,2], [0,1,0]]"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([-0.04726385, -0.0272106 , -0.02448233, -0.02565874, -0.12470837,\n",
" -0.09745281, -0.02794942, -0.05505211, -0.05625309, -0.8870669 ,\n",
" -0.40221366, -0.2147284 , -0.34907741, -0.28015528, -0.52423186,\n",
" -0.26570556, -0.26266975, -0.09633442, -0.08769057, -0.37886697,\n",
" -0.34812417, -0.09250935, -0.22968797, -0.23490885, -1.43229862,\n",
" -0.68035686, -0.66599885, -0.30909227, -0.51936483, -0.52612127,\n",
" -2.18365032, -0.10404441, -0.03902672, -0.07035239, -0.07155174,\n",
" -0.2694028 , -0.44818405, -0.2203965 , -0.21834183, -0.50296801,\n",
" -0.99105587, -1.00012868, -1.5700919 , -1.55084269, -0.63114437])"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dyad_log_pr = mod.predict_log_proba(X)\n",
"dyad_log_pr = dyad_log_pr[range(len(y)), y.astype(int)] # select the log pr for the edge being present/absent based on the household structure\n",
"dyad_log_pr"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"-19.14441743034192"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"network_log_pr = np.sum(dyad_log_pr)\n",
"network_log_pr"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"50.3 ms ± 1.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n"
]
}
],
"source": [
"# refactor that into a function\n",
"def network_logp(data, mod):\n",
" X, y = data_to_dyads(data) # TODO: speed up by not recomputing X\n",
"\n",
" dyad_log_pr = mod.predict_log_proba(X)\n",
" dyad_log_pr = dyad_log_pr[range(len(y)), y.astype(int)] # select the log pr for the edge being present/absent based on the household structure\n",
" \n",
" network_log_pr = np.sum(dyad_log_pr)\n",
"\n",
" return network_log_pr\n",
"%timeit network_logp(data, mod)"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"76.5 µs ± 1.61 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n"
]
}
],
"source": [
"# refactor that into a function\n",
"def fast_network_logp(y, mod):\n",
" dyad_log_pr = mod.predict_log_proba(X)\n",
" dyad_log_pr = dyad_log_pr[range(len(y)), y.astype(int)] # select the log pr for the edge being present/absent based on the household structure\n",
" \n",
" network_log_pr = np.sum(dyad_log_pr)\n",
"\n",
" return network_log_pr\n",
"%timeit fast_network_logp(y, mod)"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [],
"source": [
"# could get a large speed up, but this will be fine for now"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Now use MCMC to sample a network\n",
"\n",
"The Metropolis rejection step is simple enough to do by hand here."
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {},
"outputs": [],
"source": [
"old_logp = network_logp(data, mod)\n",
"n_accepted = 0"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {},
"outputs": [],
"source": [
"# pick a random pair to swap\n",
"i, j = np.random.choice(range(N), size=2, replace=False)\n",
"data.loc[[j,i], 'hh_id'] = data.hh_id[i], data.hh_id[j]"
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {},
"outputs": [],
"source": [
"# calculate the log likelihood after swapping\n",
"new_logp = network_logp(data, mod)"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1.0"
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# accept/reject based on likelihood ratio\n",
"alpha = np.exp(new_logp - old_logp)\n",
"alpha"
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {},
"outputs": [],
"source": [
"if np.random.uniform() < alpha:\n",
" # accept\n",
" old_logp = new_logp\n",
" n_accepted += 1\n",
"else: # reject\n",
" data.loc[[j,i], 'hh_id'] = data.loc[[i,j], 'hh_id']"
]
},
{
"cell_type": "code",
"execution_count": 74,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[3, 0, 2, 1, 3, 2, 2, 3, 1, 3]"
]
},
"execution_count": 74,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(data.hh_id)"
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"-32.83564756158489"
]
},
"execution_count": 75,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"new_logp"
]
},
{
"cell_type": "code",
"execution_count": 76,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"-32.83564756158489"
]
},
"execution_count": 76,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"old_logp"
]
},
{
"cell_type": "code",
"execution_count": 77,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 5.07 s, sys: 0 ns, total: 5.07 s\n",
"Wall time: 5.07 s\n"
]
}
],
"source": [
"%%time\n",
"\n",
"# refactor into a loop\n",
"n_samples = 100\n",
"\n",
"old_logp = network_logp(data, mod)\n",
"n_accepted = 0\n",
"\n",
"for i in range(n_samples):\n",
" # pick a random pair to swap\n",
" i, j = np.random.choice(range(N), size=2, replace=False)\n",
" data.loc[[j,i], 'hh_id'] = data.hh_id[i], data.hh_id[j]\n",
" \n",
" # calculate the log likelihood after swapping\n",
" new_logp = network_logp(data, mod)\n",
" \n",
" # accept/reject based on likelihood ratio\n",
" alpha = np.exp(new_logp - old_logp)\n",
"\n",
" if np.random.uniform() < alpha:\n",
" # accept\n",
" old_logp = new_logp\n",
" n_accepted += 1\n",
" else: # reject\n",
" data.loc[[j,i], 'hh_id'] = data.loc[[i,j], 'hh_id']"
]
},
{
"cell_type": "code",
"execution_count": 78,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(100, 35)"
]
},
"execution_count": 78,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"n_samples, n_accepted"
]
},
{
"cell_type": "code",
"execution_count": 79,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[1, 3, 3, 3, 0, 2, 3, 2, 1, 2]"
]
},
"execution_count": 79,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(data.hh_id)"
]
},
{
"cell_type": "code",
"execution_count": 80,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(8, 5)"
]
},
"execution_count": 80,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"i,j"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# In practice, the way I imagine this would work is\n",
"\n",
"1) Use ACS data to fit a model `mod`, perhaps for a state or a PUMA\n",
"2) Use synth pop data and SF1 data to initialize a new data frame with an arbitrary mapping of individuals to households (that respects the number and size of households reported in the corresponding SF1 tables)\n",
"3) Use MCMC to \"tune up\" the arbitrary mapping to a mapping that is more likely, based on the ACS data (as summarized in model `mod`)\n",
"\n",
"The more steps of MCMC (`n_samples`) the longer it will take, but the more like the sample will be."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.8"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment