{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Local Effects"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One of the more important aspects of random forest nodes, and by extension node clusters, is that they describe what we would call \"Local Effects\"\n",
    "\n",
    "While a conventional linear regression might describe a linear relationship between the behavior of a feature and a target that is true across the entire dataset, a node in a random forest may just as easily be a child of another node, and thus only trained on a small part of the dataset. Therefore a relationship that it describes between a feature and a target may be true across the entire dataset, or it may only be true conditionally on the predictions made by the parents of the node."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import scanpy as sc\n",
    "\n",
    "import sys\n",
    "sys.path.append('/Users/bbrener1/battle/rf_5/rusty_axe')\n",
    "sys.path.append('/Users/bbrener1/battle/rf_5/')\n",
    "import tree_reader as tr\n",
    "import lumberjack\n",
    "\n",
    "# sys.path.append('../')\n",
    "# import rusty_axe.tree_reader as tr \n",
    "# import rusty_axe.lumberjack\n",
    "\n",
    "import pickle \n",
    "\n",
    "# data_location = \"../data/aging_brain/\"\n",
    "data_location = \"/Users/bbrener1/battle/rusty_forest_4/data/aging_brain/\"\n",
    "\n",
    "young = pickle.load(open(data_location + \"aging_brain_young.pickle\",mode='rb'))\n",
    "old = pickle.load(open(data_location + \"aging_brain_old.pickle\",mode='rb'))\n",
    "\n",
    "filtered = pickle.load(open(data_location + \"aging_brain_filtered.pickle\",mode='rb'))\n",
    "\n",
    "batch_encoding = np.loadtxt(data_location + 'aging_batch_encoding.tsv')\n",
    "batch_encoding = batch_encoding.astype(dtype=bool)\n",
    "\n",
    "young_mask = np.zeros(37069,dtype=bool)\n",
    "old_mask = np.zeros(37069,dtype=bool)\n",
    "\n",
    "young_mask[:young.shape[0]] = True\n",
    "old_mask[young.shape[0]:] = True\n",
    "\n",
    "forest = tr.Forest.load(data_location + 'cv_forest_trimmed_extra')\n",
    "forest.arguments"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# forest = lumberjack.fit(\n",
    "#     young.X,\n",
    "#     header=young.var_names,\n",
    "#     trees=100,\n",
    "#     ifs=500,\n",
    "#     ofs=500,\n",
    "#     ss=500,\n",
    "#     depth=8,\n",
    "#     leaves=10,\n",
    "#     sfr=1,\n",
    "#     norm='l1',\n",
    "#     dispersion_mode='var',\n",
    "#     standardize='true',\n",
    "#     reduction=10,\n",
    "#     reduce_input='true',\n",
    "#     reduce_output='true'\n",
    "# )\n",
    "# forest.set_cache(True)\n",
    "# forest.trim(.01)\n",
    "# forest.backup(data_location+\"cv_forest_13\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "len(forest.nodes())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "filtered_feature_mask = np.zeros(2000,dtype=bool)\n",
    "\n",
    "for feature in forest.output_features:\n",
    "    f_i = list(young.var_names).index(feature)\n",
    "    filtered_feature_mask[f_i] = True\n",
    "    \n",
    "young_filtered = young[:,filtered_feature_mask]\n",
    "young_filtered.shape\n",
    "\n",
    "old_filtered = old[:,filtered_feature_mask]\n",
    "old_filtered.shape\n",
    "\n",
    "np.savetxt(\"filtered_feature_mask.txt\",filtered_feature_mask)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forest.reset_split_clusters()\n",
    "\n",
    "forest.interpret_splits(\n",
    "    depth=10,\n",
    "    mode='partial_absolute',\n",
    "    metric='cosine',\n",
    "    pca=100,\n",
    "    relatives=False,\n",
    "    k=20,\n",
    "    resolution=1,\n",
    ")\n",
    "\n",
    "print(len(forest.split_clusters))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forest.maximum_spanning_tree(mode='samples')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "\n",
    "# forest.tsne_coordinates = young.obsm['X_umap']\n",
    "# forest.html_tree_summary(n=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# forest.backup(data_location + \"full_clustering\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# We now would like to see if there are any local associations that are dramatically different\n",
    "# from global ones, to the degree that it is impossible to recapture them using PCA-based analysis. \n",
    "\n",
    "# We will need to perform a PCA analysis first. \n",
    "\n",
    "# from sklearn.decomposition import PCA\n",
    "\n",
    "# model = PCA(n_components=40).fit(young_filtered.X)\n",
    "# transformed = model.transform(young_filtered.X)\n",
    "# recovered = model.inverse_transform(transformed)\n",
    "\n",
    "# centered = young_filtered.X - np.mean(young_filtered.X,axis=0)\n",
    "# null_squared_residual = np.power(centered,2)\n",
    "\n",
    "# recovered_residual = young_filtered.X - recovered\n",
    "# recovered_squared_residual = np.power(recovered_residual,2)\n",
    "\n",
    "# pca_recovered_per_sample = np.sum(recovered_squared_residual,axis=1)\n",
    "# pca_recovered_fraction_per_sample = np.sum(recovered_squared_residual,axis=1) / np.sum(null_squared_residual,axis=1)\n",
    "# print(np.sum(null_squared_residual))\n",
    "# print(np.sum(recovered_squared_residual))\n",
    "\n",
    "# print(f\"Remaining variance:{(np.sum(recovered_squared_residual) / np.sum(null_squared_residual))}\")\n",
    "\n",
    "\n",
    "from sklearn.decomposition import PCA\n",
    "\n",
    "model = PCA(n_components=25).fit(young.X)\n",
    "transformed = model.transform(young.X)\n",
    "recovered = model.inverse_transform(transformed)\n",
    "\n",
    "centered = young.X - np.mean(young.X,axis=0)\n",
    "null_squared_residual = np.power(centered,2)\n",
    "\n",
    "recovered_residual = young.X - recovered\n",
    "recovered_squared_residual = np.power(recovered_residual,2)\n",
    "\n",
    "pca_recovered_per_sample = np.sum(recovered_squared_residual,axis=1)\n",
    "pca_recovered_fraction_per_sample = np.sum(recovered_squared_residual,axis=1) / np.sum(null_squared_residual,axis=1)\n",
    "print(np.sum(null_squared_residual))\n",
    "print(np.sum(recovered_squared_residual))\n",
    "\n",
    "print(f\"Remaining variance:{(np.sum(recovered_squared_residual) / np.sum(null_squared_residual))}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# Here we specify two interesting features and see what the weights for them are in each PC\n",
    "\n",
    "f1 = \"Syt1\"\n",
    "f2 = \"Cd74\"\n",
    "\n",
    "f1_index = list(filtered.var_names).index(f1)\n",
    "f2_index = list(filtered.var_names).index(f2)\n",
    "# f1_index = forest.truth_dictionary.feature_dictionary[f1]\n",
    "# f2_index = forest.truth_dictionary.feature_dictionary[f2]\n",
    "\n",
    "f1_weights = model.components_[:,f1_index]\n",
    "f2_weights = model.components_[:,f2_index]\n",
    "\n",
    "# plt.figure()\n",
    "# plt.scatter(forest.output[:,f1_index],forest.output[:,f2_index],s=1)\n",
    "# plt.show()\n",
    "\n",
    "plt.figure()\n",
    "plt.title(f\"PC Weights of {f1} vs {f2}\")\n",
    "plt.plot([.2,-.2],np.array([-.2,.2])*.55,color='red',label=\"Slope of -.55\")\n",
    "plt.plot([.2,-.2],np.array([0,0]),'--',color='lightgray')\n",
    "plt.plot([0,0],[.2,-.2],'--',color='lightgray')\n",
    "plt.scatter(f1_weights,f2_weights,s=2)\n",
    "plt.xlabel(f\"{f1} weight\")\n",
    "plt.ylabel(f\"{f2} weight\")\n",
    "for i,(x,y) in enumerate(zip(f1_weights,f2_weights)):\n",
    "    plt.text(x+.005,y-.01,str(i),fontsize=5)\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "for i,pc in enumerate(model.components_):\n",
    "    print(f\"PC:{i}, {f1}:{pc[f1_index]}, {f2}:{pc[f2_index]}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "f1 = \"Tmem119\"\n",
    "f2 = \"Cd74\"\n",
    "\n",
    "f1_index = forest.truth_dictionary.feature_dictionary[f1]\n",
    "f2_index = forest.truth_dictionary.feature_dictionary[f2]\n",
    "\n",
    "for factor in forest.split_clusters:\n",
    "    print(\"=====================================\")\n",
    "    print(factor.name())\n",
    "    print(\"=====================================\")\n",
    "    local = factor.local_correlations()\n",
    "    print(local[f1_index,f2_index])\n",
    "#     print(f_names)\n",
    "#     print(discrepancy)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Here we visualize the loadings of each PC to get a sense for where the PC is making meaningful predictions. \n",
    "# (It may give us a hint as to whether or not it specifies a particular cell type)\n",
    "\n",
    "# plt.figure(figsize=(6,5))\n",
    "# plt.scatter(*forest.tsne_coordinates.T,c=np.ones(len(forest.samples)),cmap='binary',vmin=0,vmax=1,s=2)\n",
    "# plt.xlabel(\"UMAP Embedding, (AU)\")\n",
    "# plt.ylabel(\"UMAP Embedding, (AU)\")\n",
    "# plt.colorbar()\n",
    "# plt.show()\n",
    "\n",
    "for i,pc in enumerate(transformed.T):\n",
    "    plt.figure(figsize=(6,5))\n",
    "    plt.title(f\"PC {i} Loadings\")\n",
    "    ab_max = np.max(np.abs(pc))\n",
    "    plt.scatter(*forest.tsne_coordinates.T,c=pc,s=3,alpha=.4,cmap='bwr',vmin=-ab_max,vmax=ab_max)\n",
    "    plt.xlabel(\"UMAP Embedding, (AU)\")\n",
    "    plt.ylabel(\"UMAP Embedding, (AU)\")\n",
    "    plt.colorbar()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Local Discrepancy Analysis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Now we will look for features that have an especially large discrepancy in the local \n",
    "# correlation compared to the global correlation for each factor. \n",
    "\n",
    "global_correlations = forest.global_correlations()\n",
    "\n",
    "for factor in forest.split_clusters[1:]:\n",
    "    fi_pairs = factor.most_local_correlations(n=20)\n",
    "    features = forest.output_features\n",
    "    f_names = [(features[i],features[j]) for (i,j) in fi_pairs]\n",
    "    local_correlations = factor.local_correlations()\n",
    "    print(\"=====================================\")\n",
    "    print(factor.name())\n",
    "    print(\"=====================================\")\n",
    "    print(\"F1\\tF2\\tGlobal\\tLocal\")\n",
    "    print(\"-------------------------------------\")\n",
    "    for f1,f2 in fi_pairs:\n",
    "        print(f\"{features[f1]}\\t{features[f2]}\\t{np.around(global_correlations[f1,f2],3)}\\t{np.around(local_correlations[f1,f2],3)}\")\n",
    "    print(\"=====================================\")\n",
    "    \n",
    "# Interesting result from 7: Rtn1,Bcan\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# We want to find interesting gene pairs and plot their correlations across several factors to find inconsistencies.\n",
    "\n",
    "# First we find interesting pairs\n",
    "\n",
    "global_correlations = forest.global_correlations()\n",
    "\n",
    "interesting_pairs = []\n",
    "\n",
    "for factor in forest.split_clusters:\n",
    "    for ip in factor.most_local_correlations(n=3):\n",
    "        if ip not in interesting_pairs and (ip[1],ip[0]) not in interesting_pairs:\n",
    "            interesting_pairs.append(ip)\n",
    "    \n",
    "# uniques = list(set([y for x in interesting_pairs for y in x]))\n",
    "    \n",
    "interesting_pair_names = [f\"{forest.output_features[f1]}, {forest.output_features[f2]}\" for (f1,f2) in interesting_pairs]\n",
    "    \n",
    "factor_correlation_table = np.zeros((len(interesting_pairs),len(forest.split_clusters)))\n",
    "\n",
    "# Now we plot the local correlations of each interesting pair in each factor\n",
    "\n",
    "for i,factor in enumerate(forest.split_clusters):\n",
    "    local_correlations = factor.local_correlations()\n",
    "    for j,(f1,f2) in enumerate(interesting_pairs):\n",
    "        factor_correlation_table[j,i] = local_correlations[f1,f2] - global_correlations[f1,f2]\n",
    "#         factor_correlation_table[j,i] = local_correlations[f1,f2]\n",
    "\n",
    "#     local_correlations = factor.local_correlations(indices=uniques)\n",
    "#     for j,(f1,f2) in enumerate(interesting_pairs):\n",
    "#         f1_u = uniques.index(f1)\n",
    "#         f2_u = uniques.index(f2)\n",
    "#         factor_correlation_table[j,i] = local_correlations[f1_u,f2_u]\n",
    "\n",
    "# Now we have a table of pairs and how they correlate across a lot of factors. We can plot it, but it's best to\n",
    "# agglomerate first\n",
    "\n",
    "# plt.figure()\n",
    "# plt.imshow(factor_correlation_table,interpolation='none',aspect='auto',cmap='bwr',vmin=-1,vmax=1)\n",
    "# plt.colorbar()\n",
    "# plt.show()\n",
    "\n",
    "from scipy.cluster.hierarchy import linkage,dendrogram\n",
    "\n",
    "pair_agglomeration = dendrogram(linkage(factor_correlation_table, metric='cosine', method='average'), no_plot=True)['leaves']\n",
    "factor_agglomeration = dendrogram(linkage(factor_correlation_table.T, metric='cosine', method='average'), no_plot=True)['leaves']\n",
    "\n",
    "print(len(pair_agglomeration))\n",
    "print(len(factor_agglomeration))\n",
    "\n",
    "agg_indices = np.array(interesting_pairs)[pair_agglomeration]\n",
    "agg_names = np.array(interesting_pair_names)[pair_agglomeration]\n",
    "\n",
    "plt.figure(figsize=(7,20))\n",
    "plt.title(\"Local Correlations of Selected Feature Pairs\")\n",
    "plt.imshow(factor_correlation_table[pair_agglomeration].T[factor_agglomeration].T,interpolation='none',aspect='auto',cmap='bwr',vmin=-1,vmax=1)\n",
    "plt.yticks(np.arange(len(agg_names)),labels=agg_names)\n",
    "plt.colorbar(label=\"Local Correlation\")\n",
    "plt.xlabel(\"Factors\")\n",
    "plt.xticks(np.arange(len(factor_agglomeration)),labels=factor_agglomeration,rotation=90)\n",
    "plt.show()\n",
    "\n",
    "\n",
    "print([(x,y) for x,y in enumerate(agg_names)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forest.split_clusters[0].local_correlations()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forest.global_correlations()\n",
    "# forest.output_features[1639]\n",
    "\n",
    "# print(forest.split_clusters[23].local_correlations(indices=[717,1639]))\n",
    "# print(forest.split_clusters[20].local_correlations(indices=[717,1639]))\n",
    "\n",
    "# cluster 23, Rrares2 (717), Meg3 (1639)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Here we check the naive linear fit between two features (eg a simple correlation among all cells)\n",
    "\n",
    "from scipy.stats import linregress\n",
    "\n",
    "f1 = \"Tmem119\"\n",
    "f2 = \"Cd74\"\n",
    "\n",
    "f1_index = forest.truth_dictionary.feature_dictionary[f1]\n",
    "f2_index = forest.truth_dictionary.feature_dictionary[f2]\n",
    "\n",
    "f1_values = forest.output[:,f1_index]\n",
    "f2_values = forest.output[:,f2_index]\n",
    "\n",
    "slope,intercept,r_fit,_,_ = linregress(f1_values,f2_values)\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"Linar Fit, {f1}, {f2}, Naive\")\n",
    "plt.scatter(f1_values,f2_values,s=3)\n",
    "plt.plot(np.arange(7), intercept + (np.arange(7) * slope),c='red',label=f\"Slope:{np.around(slope,3)},R2:{np.around(r_fit,3)}\")\n",
    "plt.legend()\n",
    "plt.xlabel(f\"{f1}\")\n",
    "plt.ylabel(f\"{f2}\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Here we filter only for cells that have a high or low sister score for a particular factor\n",
    "# and linearly regress two genes to check for a \"local\" association. \n",
    "\n",
    "\n",
    "from scipy.stats import linregress\n",
    "\n",
    "factor = forest.split_clusters[18]\n",
    "factor_threshold = .1\n",
    "factor_mask = np.abs(factor.sister_scores()) > factor_threshold\n",
    "\n",
    "plt.figure()\n",
    "plt.title(f\"Sister scores, {factor.name()}\")\n",
    "plt.hist(factor.sister_scores(),bins=50)\n",
    "plt.plot([factor_threshold,factor_threshold],[-100,100],color='red')\n",
    "plt.plot([-factor_threshold,-factor_threshold],[-100,100],color='red',label=\"Sister score threshold\")\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "f1 = \"Tmem119\"\n",
    "f2 = \"Cd74\"\n",
    "\n",
    "f1_index = forest.truth_dictionary.feature_dictionary[f1]\n",
    "f2_index = forest.truth_dictionary.feature_dictionary[f2]\n",
    "\n",
    "f1_values = forest.output[:,f1_index][factor_mask]\n",
    "f2_values = forest.output[:,f2_index][factor_mask]\n",
    "\n",
    "slope,intercept,r_fit,_,_ = linregress(f1_values,f2_values)\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"Linar Fit, {f1}, {f2}, \\n Factor {factor.name()}, Filtered\")\n",
    "plt.scatter(f1_values,f2_values,s=3)\n",
    "plt.plot(np.arange(7), intercept + (np.arange(7) * slope),c='red',label=f\"Slope:{np.around(slope,3)},R2:{np.around(r_fit,3)}\")\n",
    "plt.xlabel(f\"{f1}\")\n",
    "plt.ylabel(f\"{f2}\")\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "plt.figure()\n",
    "plt.scatter(*forest.tsne_coordinates.T,c=factor_mask,s=1)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Here we filter only for cells that have a high or low sister score for a particular factor\n",
    "# and linearly regress two genes to check for a \"local\" association. \n",
    "\n",
    "\n",
    "from scipy.stats import linregress\n",
    "\n",
    "factor = forest.split_clusters[18]\n",
    "factor_threshold = .05\n",
    "factor_mask = np.abs(factor.sister_scores()) > factor_threshold\n",
    "\n",
    "plt.figure()\n",
    "plt.title(f\"Sister scores, {factor.name()}\")\n",
    "plt.hist(factor.sister_scores(),bins=50)\n",
    "plt.plot([factor_threshold,factor_threshold],[-100,100],color='red')\n",
    "plt.plot([-factor_threshold,-factor_threshold],[-100,100],color='red',label=\"Sister score threshold\")\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "f1 = \"Tmem119\"\n",
    "f2 = \"Cd74\"\n",
    "\n",
    "f1_index = forest.truth_dictionary.feature_dictionary[f1]\n",
    "f2_index = forest.truth_dictionary.feature_dictionary[f2]\n",
    "\n",
    "f1_values = forest.output[:,f1_index][factor_mask]\n",
    "f2_values = forest.output[:,f2_index][factor_mask]\n",
    "\n",
    "slope,intercept,r_fit,_,_ = linregress(f1_values,f2_values)\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"Linar Fit, {f1}, {f2}, \\n Factor {factor.name()}, Filtered\")\n",
    "plt.scatter(f1_values,f2_values,s=3)\n",
    "plt.plot(np.arange(7), intercept + (np.arange(7) * slope),c='red',label=f\"Slope:{np.around(slope,3)},R2:{np.around(r_fit,3)}\")\n",
    "plt.xlabel(f\"{f1}\")\n",
    "plt.ylabel(f\"{f2}\")\n",
    "plt.legend()\n",
    "plt.show()\n",
    "\n",
    "plt.figure()\n",
    "plt.scatter(*forest.tsne_coordinates.T,c=factor_mask,s=1)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Here we find highly weighted genes for a particular PC, as well as the rankings of particular features of interest\n",
    "# Our objective is to see if the two featurs represent an important part of the variance captured by the PC\n",
    "pc = 8\n",
    "\n",
    "f1 = \"Tmem119\"\n",
    "f2 = \"Cd74\"\n",
    "\n",
    "f1_index = forest.truth_dictionary.feature_dictionary[f1]\n",
    "f2_index = forest.truth_dictionary.feature_dictionary[f2]\n",
    "\n",
    "weights = model.components_[pc]\n",
    "\n",
    "weight_sort = np.argsort(np.abs(weights))\n",
    "\n",
    "print(list(forest.output_features[weight_sort[:-20:-1]]))\n",
    "print(list(weights[weight_sort[:-20:-1]]))\n",
    "\n",
    "print(f\"{f1}: {len(weights) - list(weight_sort).index(f1_index)}\")\n",
    "print(f\"{f2}: {len(weights) - list(weight_sort).index(f2_index)}\")\n",
    "\n",
    "print(weights[f1_index])\n",
    "print(weights[f2_index])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "for s_c in forest.split_clusters:\n",
    "    scores = s_c.sister_scores()\n",
    "    log_scores = s_c.log_sister_scores(prior=10)\n",
    "\n",
    "    abmax=np.max(np.abs(scores))\n",
    "\n",
    "    plt.figure()\n",
    "    plt.title(\"Regular\")\n",
    "    plt.scatter(*forest.tsne_coordinates.T,c=scores,cmap='bwr',s=1,vmin=-abmax,vmax=abmax)\n",
    "    plt.colorbar()\n",
    "    plt.show()\n",
    "\n",
    "    abmax=np.max(np.abs(log_scores))\n",
    "\n",
    "    plt.figure()\n",
    "    plt.title(\"Log\")\n",
    "    plt.scatter(*forest.tsne_coordinates.T,c=log_scores,cmap='bwr',s=1,vmin=-abmax,vmax=abmax)\n",
    "    plt.colorbar()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "factor = forest.split_clusters[34]\n",
    "\n",
    "samples = factor.sample_scores()\n",
    "sisters = factor.sister_scores()\n",
    "log_sisters = factor.log_sister_scores()\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"Distribution of Sample Scores In {factor.name()}\")\n",
    "plt.hist(samples,bins=50)\n",
    "plt.ylabel(\"Frequency\")\n",
    "plt.xlabel(\"Sample Scores\")\n",
    "plt.show()\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"Distribution of Sister Scores In {factor.name()}\")\n",
    "plt.hist(sisters,bins=50)\n",
    "plt.ylabel(\"Frequency\")\n",
    "plt.xlabel(\"Sister Scores\")\n",
    "plt.show()\n",
    "\n",
    "plt.figure()\n",
    "plt.hist(log_sisters,bins=50)\n",
    "plt.show()\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Here we test whether or not a particular factor over-expresses a gene of interest\n",
    "# (Used as a statistical test for cell type identity, eg \"is factor 34 immune cells?\")\n",
    "from scipy.stats import ttest_ind\n",
    "\n",
    "factor = forest.split_clusters[3]\n",
    "factor_threshold = .05\n",
    "mask = factor.sister_scores() > factor_threshold\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"Distribution of sister scores in Cluster{factor.name()}\")\n",
    "plt.hist(factor.sister_scores(),bins=50,log=True)\n",
    "# plt.plot([factor_threshold,factor_threshold],[-100,100],color='red')\n",
    "# plt.plot([-factor_threshold,-factor_threshold],[-100,100],color='red',label=\"Sister score threshold\")\n",
    "plt.xlabel(\"Sister scores\")\n",
    "plt.ylabel(\"Frequency\")\n",
    "plt.show()\n",
    "\n",
    "feature = \"Cldn5\"\n",
    "\n",
    "f_index = list(young.var_names).index(feature)\n",
    "\n",
    "test = ttest_ind(young.X[mask][:,f_index],young.X[~mask][:,f_index],equal_var=False)\n",
    "\n",
    "print(f\"{feature} in {factor.name()} vs all other: {test}\")\n",
    "\n",
    "from scipy.stats import sem\n",
    "\n",
    "m1 = np.around(np.mean(young.X[mask][:,f_index]),3)\n",
    "se1 = np.around(sem(young.X[mask][:,f_index]),3)\n",
    "m2 = np.around(np.mean(young.X[~mask][:,f_index]),3)\n",
    "se2 = np.around(sem(young.X[~mask][:,f_index]),3)\n",
    "\n",
    "print(f\"Mean expression: {str(m1)} +/- {str(se1)} vs {str(m2)} +/- {str(se2)}\")\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"{feature} Mean Expression\")\n",
    "plt.bar([0,1],[m1,m2],yerr=[se1,se2],width=.5,tick_label=[f\"NC {factor.name()}\",\"Rest\",])\n",
    "plt.ylabel(\"Mean Expression (Log TPM)\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Here we test whether or not a particular factor over-expresses a gene of interest\n",
    "# (Used as a statistical test for cell type identity, eg \"is factor 34 immune cells?\")\n",
    "from scipy.stats import ttest_ind\n",
    "\n",
    "factor = forest.split_clusters[13]\n",
    "factor_threshold = .05\n",
    "mask = factor.sister_scores() > factor_threshold\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"Distribution of sister scores in Cluster{factor.name()}\")\n",
    "plt.hist(factor.sister_scores(),bins=50)\n",
    "plt.plot([factor_threshold,factor_threshold],[-100,100],color='red')\n",
    "plt.plot([-factor_threshold,-factor_threshold],[-100,100],color='red',label=\"Sister score threshold\")\n",
    "plt.xlabel(\"Sister scores\")\n",
    "plt.ylabel(\"Frequency\")\n",
    "plt.show()\n",
    "\n",
    "feature = \"Cldn5\"\n",
    "\n",
    "f_index = list(young.var_names).index(feature)\n",
    "\n",
    "test = ttest_ind(young.X[mask][:,f_index],young.X[~mask][:,f_index],equal_var=False)\n",
    "\n",
    "print(f\"{feature} in {factor.name()} vs all other: {test}\")\n",
    "\n",
    "from scipy.stats import sem\n",
    "\n",
    "m1 = np.around(np.mean(young.X[mask][:,f_index]),3)\n",
    "se1 = np.around(sem(young.X[mask][:,f_index]),3)\n",
    "m2 = np.around(np.mean(young.X[~mask][:,f_index]),3)\n",
    "se2 = np.around(sem(young.X[~mask][:,f_index]),3)\n",
    "\n",
    "print(f\"Mean expression: {str(m1)} +/- {str(se1)} vs {str(m2)} +/- {str(se2)}\")\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"{feature} Mean Expression\")\n",
    "plt.bar([0,1],[m1,m2],yerr=[se1,se2],width=.5,tick_label=[f\"NC {factor.name()}\",\"Rest\",])\n",
    "plt.ylabel(\"Mean Expression (Log TPM)\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Here we test whether or not a particular factor over-expresses a gene of interest\n",
    "# (Used as a statistical test for cell type identity, eg \"is factor 34 immune cells?\")\n",
    "from scipy.stats import ttest_ind\n",
    "\n",
    "factor = forest.split_clusters[13]\n",
    "factor_threshold = .05\n",
    "mask = factor.sister_scores() > factor_threshold\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"Distribution of sister scores in Cluster{factor.name()}\")\n",
    "plt.hist(factor.sister_scores(),bins=50)\n",
    "plt.plot([factor_threshold,factor_threshold],[-100,100],color='red')\n",
    "plt.plot([-factor_threshold,-factor_threshold],[-100,100],color='red',label=\"Sister score threshold\")\n",
    "plt.xlabel(\"Sister scores\")\n",
    "plt.ylabel(\"Frequency\")\n",
    "plt.show()\n",
    "\n",
    "factor_2 = forest.split_clusters[21]\n",
    "factor_2_threshold = .05\n",
    "mask_2 = factor_2.sister_scores() > factor_2_threshold\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"Distribution of sister scores in Cluster{factor_2.name()}\")\n",
    "plt.hist(factor_2.sister_scores(),bins=50)\n",
    "plt.plot([factor_2_threshold,factor_2_threshold],[-100,100],color='red')\n",
    "plt.plot([-factor_2_threshold,-factor_2_threshold],[-100,100],color='red',label=\"Sister score threshold\")\n",
    "plt.xlabel(\"Sister scores\")\n",
    "plt.ylabel(\"Frequency\")\n",
    "plt.show()\n",
    "\n",
    "\n",
    "feature = \"Cldn5\"\n",
    "\n",
    "f_index = list(young.var_names).index(feature)\n",
    "\n",
    "test = ttest_ind(young.X[mask][:,f_index],young.X[mask_2][:,f_index],equal_var=False)\n",
    "\n",
    "print(f\"{feature} in {factor.name()} vs {factor_2.name()}:\")\n",
    "\n",
    "from scipy.stats import sem\n",
    "\n",
    "m1 = np.around(np.mean(young.X[mask][:,f_index]),3)\n",
    "se1 = np.around(sem(young.X[mask][:,f_index]),3)\n",
    "m2 = np.around(np.mean(young.X[mask_2][:,f_index]),3)\n",
    "se2 = np.around(sem(young.X[mask_2][:,f_index]),3)\n",
    "\n",
    "print(f\"Mean expression: {str(m1)} +/- {str(se1)} vs {str(m2)} +/- {str(se2)}\")\n",
    "print(test)\n",
    "\n",
    "plt.figure(figsize=(3,2.5))\n",
    "plt.title(f\"{feature} Mean Expression\")\n",
    "plt.bar([0,1],[m1,m2],yerr=[se1,se2],width=.5,tick_label=[f\"NC {factor.name()}\",f\"{factor_2.name()}\",])\n",
    "plt.ylabel(\"Mean Expression (Log TPM)\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for feature in young.var_names:\n",
    "    if \"Rad\" in feature:\n",
    "        print(feature)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "feature = \"Cdkn1a\"\n",
    "\n",
    "f_index = list(young.var_names).index(feature)\n",
    "\n",
    "plt.figure()\n",
    "plt.title(f\"Distribution of {feature} in Cells\")\n",
    "plt.scatter(*filtered.obsm[\"X_umap\"][young_mask].T,c=young.X[:,f_index],s=young.X[:,f_index]+1)\n",
    "plt.colorbar(label=\"Log TPM\")\n",
    "plt.show()\n",
    "\n",
    "plt.figure()\n",
    "plt.title(f\"Distribution of {feature} in Cells\")\n",
    "plt.scatter(*filtered.obsm[\"X_umap\"][old_mask].T,c=old.X[:,f_index],s=old.X[:,f_index]+1)\n",
    "plt.colorbar(label=\"Log TPM\")\n",
    "plt.show()\n",
    "\n",
    "# Working cell cycle markers:\n",
    "# Ccn*\n",
    "# Gem (shitty)\n",
    "# Plk2?(Unconfirmed, Plk1 is conventioanl)\n",
    "# Top2a (very specific)\n",
    "# \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "feature = \"Fth1\"\n",
    "\n",
    "f_index = list(young.var_names).index(feature)\n",
    "\n",
    "plt.figure(figsize=(10,10))\n",
    "plt.scatter(*filtered.obsm['X_umap'][young_mask].T,c=filtered.X[young_mask,f_index],s=4)\n",
    "plt.colorbar()\n",
    "plt.show()\n",
    "\n",
    "plt.figure(figsize=(10,10))\n",
    "plt.scatter(*filtered.obsm['X_umap'][old_mask].T,c=filtered.X[old_mask,f_index],s=4)\n",
    "plt.colorbar()\n",
    "plt.show()\n",
    "\n",
    "# f_index = list(forest.output_features).index(feature)\n",
    "\n",
    "# plt.figure(figsize=(10,10))\n",
    "# plt.scatter(*filtered.obsm['X_umap'][young_mask].T,c=ns_residuals[:,f_index],s=4)\n",
    "# plt.show()\n",
    "\n",
    "# plt.figure()\n",
    "# plt.scatter(ns[:,0],forest.output[:,f_index],alpha=.1)\n",
    "# plt.show()\n",
    "# plt.figure()\n",
    "# plt.scatter(ns[:,0],ns_residuals[:,f_index],alpha=.1)\n",
    "# plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Examining Partial Effects"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "factor = forest.split_clusters[21]\n",
    "\n",
    "\n",
    "additive = np.mean(forest.node_representation(factor.nodes,mode='additive_mean'),axis=0)\n",
    "additive_sort = np.argsort(additive)\n",
    "\n",
    "print(forest.output_features[additive_sort[-10:]])\n",
    "print(additive[additive_sort[-10:]])\n",
    "\n",
    "rtn_1_index = list(forest.output_features).index('Rtn1')\n",
    "klk_6_index = list(forest.output_features).index('Klk6')\n",
    "print(additive[klk_6_index])\n",
    "\n",
    "partials = forest.node_representation(factor.nodes,mode='partial_absolute') \n",
    "partial_means = np.mean(partials,axis=0)\n",
    "partial_sort = np.argsort(np.abs(partial_means))\n",
    "\n",
    "print(forest.output_features[partial_sort[:10]])\n",
    "print(partial_means[partial_sort[:10]])\n",
    "\n",
    "print(forest.output_features[partial_sort[-10:]])\n",
    "print(partial_means[partial_sort[-10:]])\n",
    "\n",
    "print(additive[klk_6_index])\n",
    "print(partial_means[klk_6_index])\n",
    "\n",
    "plt.figure()\n",
    "plt.hist(partials[:,klk_6_index],bins=50)\n",
    "plt.show()\n",
    "\n",
    "# ratio = np.abs(additive/partial)\n",
    "# ratio_sort = np.argsort(ratio)\n",
    "# print(ratio[ratio_sort])\n",
    "# print(forest.output_features[ratio_sort[:10]])\n",
    "# print(forest.output_features[ratio_sort[-10:]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "partials.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "factor = forest.split_clusters[23]\n",
    "\n",
    "f_index = list(forest.output_features).index('Klk6')\n",
    "\n",
    "additive = np.mean(forest.node_representation(factor.nodes,mode='additive_mean'),axis=0)\n",
    "additive_sort = np.argsort(additive)\n",
    "\n",
    "print(forest.output_features[additive_sort[-10:]])\n",
    "print(additive[additive_sort[-10:]])\n",
    "\n",
    "print(additive[f_index])\n",
    "\n",
    "partials = forest.node_representation(factor.nodes,mode='partial')\n",
    "partial_means = np.mean(partials,axis=0)\n",
    "# partial_sort = np.argsort(np.abs(partial_means))\n",
    "partial_sort = np.argsort(partial_means)\n",
    "\n",
    "print(forest.output_features[partial_sort[-10:]])\n",
    "print(partial_means[partial_sort[-10:]])\n",
    "\n",
    "print(additive[f_index])\n",
    "print(partial_means[f_index])\n",
    "\n",
    "plt.figure()\n",
    "plt.hist(partials[:,klk_6_index],bins=50)\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(np.mean(young_filtered.X,axis=0)[klk_6_index])\n",
    "print(np.var(young_filtered.X,axis=0)[klk_6_index])\n",
    "\n",
    "mask_19p = forest.split_clusters[19].sister_scores() > .05\n",
    "mask_19m = forest.split_clusters[19].sister_scores() < .05\n",
    "\n",
    "print(np.mean(young_filtered.X[mask_19p],axis=0)[klk_6_index])\n",
    "print(np.var(young_filtered.X[mask_19p],axis=0)[klk_6_index])\n",
    "print(np.mean(young_filtered.X[mask_19m],axis=0)[klk_6_index])\n",
    "print(np.var(young_filtered.X[mask_19m],axis=0)[klk_6_index])\n",
    "\n",
    "mask_23p = forest.split_clusters[23].sister_scores() > .05\n",
    "mask_23m = forest.split_clusters[23].sister_scores() < -.05\n",
    "\n",
    "print(np.mean(young_filtered.X[mask_23p],axis=0)[klk_6_index])\n",
    "print(np.var(young_filtered.X[mask_23p],axis=0)[klk_6_index])\n",
    "\n",
    "print(np.mean(young_filtered.X[mask_23m],axis=0)[klk_6_index])\n",
    "print(np.var(young_filtered.X[mask_23m],axis=0)[klk_6_index])\n",
    "\n",
    "print(np.sum(mask_19p))\n",
    "print(np.sum(mask_19m))\n",
    "print(np.sum(mask_23p))\n",
    "print(np.sum(mask_23m))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "young_filtered.shape\n",
    "\n",
    "# for feature in young.var_names:\n",
    "#     if \"Ccn\" in feature:\n",
    "#         print(feature)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parent_class = [p.split_cluster for p in forest.split_clusters[27].parents()]\n",
    "\n",
    "np.unique(parent_class,return_counts=True) \n",
    "np.unique(parent_class,return_counts=True)[1] / len(parent_class)\n",
    "\n",
    "\n",
    "# plt.figure()\n",
    "# plt.hist(parent_class)\n",
    "# plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "import matplotlib.patheffects as PathEffects\n",
    "\n",
    "factor = forest.split_clusters[34]\n",
    "\n",
    "n=5\n",
    "m=10\n",
    "\n",
    "lt,hd = factor.top_local_table(n)\n",
    "\n",
    "\n",
    "plt.figure()\n",
    "plt.title(f\"Local Correlations in NC{factor.name()}\")\n",
    "plt.imshow(lt,cmap='bwr',vmin=-1,vmax=1)\n",
    "for i in range(m):\n",
    "        for j in range(m):\n",
    "            text = plt.text(j, i, np.around(lt[i, j], 1),\n",
    "                           ha=\"center\", va=\"center\", c='w', fontsize=7)\n",
    "            text.set_path_effects(\n",
    "                [PathEffects.withStroke(linewidth=.8, foreground='black')])\n",
    "plt.xticks(ticks=np.arange(10),labels=hd,rotation=45,fontsize=10)\n",
    "plt.yticks(ticks=np.arange(10),labels=hd,rotation=45,fontsize=10)\n",
    "plt.colorbar(label=\"Weighted Correlation\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.patheffects as PathEffects\n",
    "\n",
    "factor = forest.split_clusters[34]\n",
    "\n",
    "n=5\n",
    "m=10\n",
    "\n",
    "gt,hd = factor.top_global_table(n)\n",
    "\n",
    "\n",
    "plt.figure()\n",
    "plt.title(\"Global Correlations\")\n",
    "plt.imshow(gt,cmap='bwr',vmin=-1,vmax=1)\n",
    "for i in range(m):\n",
    "        for j in range(m):\n",
    "            text = plt.text(j, i, np.around(gt[i, j], 1),\n",
    "                           ha=\"center\", va=\"center\", c='w', fontsize=7)\n",
    "            text.set_path_effects(\n",
    "                [PathEffects.withStroke(linewidth=.8, foreground='black')])\n",
    "plt.xticks(ticks=np.arange(10),labels=hd,rotation=45,fontsize=10)\n",
    "plt.yticks(ticks=np.arange(10),labels=hd,rotation=45,fontsize=10)\n",
    "plt.colorbar(label=\"Weighted Correlation\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from glob import glob\n",
    "\n",
    "glob(\"./*.ipynb\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pwd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from \n",
    "\n",
    "from nbformat import read as nb_read\n",
    "from nbformat import write as nb_write\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "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.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}

