{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# How to use `indigopy`\n",
    "\n",
    "Example code for how to use the `indigopy` package. The sample data used in this example notebook is derived from the [INDIGO](https://doi.org/10.1007/978-1-4939-8891-4_13), [INDIGO-MTB](https://doi.org/10.1128/mbio.02627-19), and [MAGENTA](https://doi.org/10.1371/journal.pcbi.1006677) publications.  \n",
    "\n",
    "## Set up environment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import dependencies\n",
    "import pandas as pd\n",
    "from scipy.stats import spearmanr\n",
    "from sklearn.metrics import r2_score, classification_report\n",
    "from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Import package functions\n",
    "from indigopy.core import load_sample, featurize, classify"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example: *E. coli*\n",
    "\n",
    "The following analysis and results were originally reported in the [INDIGO](https://doi.org/10.1007/978-1-4939-8891-4_13) paper.  \n",
    "- **Training dataset**: 105 two-way interactions between 15 antibiotics  \n",
    "- **Testing dataset**: 66 two-way interactions between the 15 antibiotics in the training set + 4 new antibiotics  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Defining INDIGO features: 100%|██████████| 105/105 [00:00<00:00, 147.47it/s]\n",
      "Defining INDIGO features: 100%|██████████| 66/66 [00:00<00:00, 209.53it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Regression results:\n",
      "\tSpearman R = 0.5832\n",
      "\tSpearman p = 2.76e-07\n",
      "\tR2 = 0.3711\n",
      "Classification results:\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           A       0.50      0.31      0.38        13\n",
      "           N       0.67      0.93      0.78        42\n",
      "           S       0.00      0.00      0.00        11\n",
      "\n",
      "    accuracy                           0.65        66\n",
      "   macro avg       0.39      0.41      0.39        66\n",
      "weighted avg       0.53      0.65      0.57        66\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\sklearn\\metrics\\_classification.py:1334: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n",
      "  _warn_prf(average, modifier, msg_start, len(result))\n",
      "c:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\sklearn\\metrics\\_classification.py:1334: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n",
      "  _warn_prf(average, modifier, msg_start, len(result))\n",
      "c:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\sklearn\\metrics\\_classification.py:1334: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n",
      "  _warn_prf(average, modifier, msg_start, len(result))\n"
     ]
    }
   ],
   "source": [
    "# Load sample data\n",
    "sample = load_sample('ecoli')\n",
    "\n",
    "# Define input arguments\n",
    "key             = sample['key']\n",
    "profiles        = sample['profiles']\n",
    "feature_names   = sample['feature_names']\n",
    "train_ixns      = sample['train']['interactions']\n",
    "train_scores    = sample['train']['scores']\n",
    "test_ixns       = sample['test']['interactions']\n",
    "test_scores     = sample['test']['scores']\n",
    "\n",
    "# Determine ML features\n",
    "train_data      = featurize(train_ixns, profiles, feature_names=feature_names, key=key, silent=True)\n",
    "test_data       = featurize(test_ixns, profiles, feature_names=feature_names, key=key, silent=True)\n",
    "X_train, X_test = train_data['feature_df'].to_numpy().transpose(), test_data['feature_df'].to_numpy().transpose()\n",
    "\n",
    "# Determine class labels\n",
    "thresh, classes = (-0.5, 2), ('S', 'N', 'A')\n",
    "train_labels    = classify(train_scores, thresholds=thresh, classes=classes)\n",
    "test_labels     = classify(test_scores, thresholds=thresh, classes=classes)\n",
    "\n",
    "# Train and apply a regression-based model\n",
    "reg_model = RandomForestRegressor()\n",
    "reg_model.fit(X_train, train_scores)\n",
    "reg_y = reg_model.predict(X_test)\n",
    "r, p = spearmanr(test_scores, reg_y)\n",
    "r2 = r2_score(test_scores, reg_y)\n",
    "print('Regression results:')\n",
    "print('\\tSpearman R = {}'.format(round(r, 4)))\n",
    "print('\\tSpearman p = {:.3g}'.format(p))\n",
    "print('\\tR2 = {}'.format(round(r2, 4)))\n",
    "\n",
    "# Train and apply a classification-based model\n",
    "class_model = RandomForestClassifier()\n",
    "class_model.fit(X_train, train_labels)\n",
    "class_y = class_model.predict(X_test)\n",
    "print('Classification results:')\n",
    "print(classification_report(test_labels, class_y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example: *M. tuberculosis*\n",
    "\n",
    "The following analysis and results were originally reported in the [INDIGO-MTB](https://doi.org/10.1128/mbio.02627-19) paper.  \n",
    "- **Training dataset**: 196 two- to five-way interactions between 40 antibacterials  \n",
    "- **Testing dataset**: 36 two- to three-way interactions between the 13 antibacterials  \n",
    "- **Clinical dataset**: clinical outcomes for 57 two- to five-way interactions between 7 antibacterials  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Defining INDIGO features: 100%|██████████| 196/196 [00:00<00:00, 230.32it/s]\n",
      "Defining INDIGO features: 100%|██████████| 36/36 [00:00<00:00, 235.30it/s]\n",
      "Defining INDIGO features: 100%|██████████| 57/57 [00:00<00:00, 248.92it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Regression results:\n",
      "\tSpearman R = 0.5453\n",
      "\tSpearman p = 0.000583\n",
      "\tR2 = 0.1152\n",
      "Classification results:\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           A       0.62      0.31      0.42        16\n",
      "           N       0.00      0.00      0.00         1\n",
      "           S       0.68      0.79      0.73        19\n",
      "\n",
      "    accuracy                           0.56        36\n",
      "   macro avg       0.44      0.37      0.38        36\n",
      "weighted avg       0.64      0.56      0.57        36\n",
      "\n",
      "Clinical results:\n",
      "\tSpearman R = 0.562\n",
      "\tSpearman p = 5.39e-06\n"
     ]
    }
   ],
   "source": [
    "# Load sample data\n",
    "sample = load_sample('mtb')\n",
    "\n",
    "# Define input arguments\n",
    "key             = sample['key']\n",
    "profiles        = sample['profiles']\n",
    "feature_names   = sample['feature_names']\n",
    "train_ixns      = sample['train']['interactions']\n",
    "train_scores    = sample['train']['scores']\n",
    "test_ixns       = sample['test']['interactions']\n",
    "test_scores     = sample['test']['scores']\n",
    "clinical_ixns   = sample['clinical']['interactions']\n",
    "clinical_scores = sample['clinical']['scores']\n",
    "\n",
    "# Determine ML features\n",
    "train_data      = featurize(train_ixns, profiles, feature_names=feature_names, key=key, silent=True)\n",
    "test_data       = featurize(test_ixns, profiles, feature_names=feature_names, key=key, silent=True)\n",
    "clinical_data   = featurize(clinical_ixns, profiles, feature_names=feature_names, key=key, silent=True)\n",
    "X_train, X_test = train_data['feature_df'].to_numpy().transpose(), test_data['feature_df'].to_numpy().transpose()\n",
    "X_clinical      = clinical_data['feature_df'].to_numpy().transpose()\n",
    "\n",
    "# Determine class labels\n",
    "thresh, classes = (0.9, 1.1), ('S', 'N', 'A')\n",
    "train_labels    = classify(train_scores, thresholds=thresh, classes=classes)\n",
    "test_labels     = classify(test_scores, thresholds=thresh, classes=classes)\n",
    "\n",
    "# Train and apply a regression-based model\n",
    "reg_model = RandomForestRegressor()\n",
    "reg_model.fit(X_train, train_scores)\n",
    "reg_y = reg_model.predict(X_test)\n",
    "r, p = spearmanr(test_scores, reg_y)\n",
    "r2 = r2_score(test_scores, reg_y)\n",
    "print('Regression results:')\n",
    "print('\\tSpearman R = {}'.format(round(r, 4)))\n",
    "print('\\tSpearman p = {:.3g}'.format(p))\n",
    "print('\\tR2 = {}'.format(round(r2, 4)))\n",
    "\n",
    "# Train and apply a classification-based model\n",
    "class_model = RandomForestClassifier()\n",
    "class_model.fit(X_train, train_labels)\n",
    "class_y = class_model.predict(X_test)\n",
    "print('Classification results:')\n",
    "print(classification_report(test_labels, class_y))\n",
    "\n",
    "# Apply model to clinical data\n",
    "clinical_y = reg_model.predict(X_clinical)\n",
    "r, p = spearmanr(clinical_scores, clinical_y)\n",
    "print('Clinical results:')\n",
    "print('\\tSpearman R = {}'.format(round(-r, 4)))\n",
    "print('\\tSpearman p = {:.3g}'.format(p))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example: *S. aureus*\n",
    "\n",
    "The following analysis and results were originally reported in the [INDIGO](https://doi.org/10.1007/978-1-4939-8891-4_13) paper.  \n",
    "- **Training dataset**: 171 two-way interactions between 19 antibiotics measured in *E. coli*  \n",
    "- **Testing dataset**: 45 two-way interactions between the 10 antibiotics measured in *S. aureus*  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Defining INDIGO features: 100%|██████████| 171/171 [00:00<00:00, 250.37it/s]\n",
      "Defining INDIGO features: 100%|██████████| 45/45 [00:00<00:00, 244.55it/s]\n",
      "Mapping orthologous genes: 100%|██████████| 1/1 [00:04<00:00,  4.40s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Regression results:\n",
      "\tSpearman R = 0.6202\n",
      "\tSpearman p = 5.53e-06\n",
      "\tR2 = -1.17\n",
      "Classification results:\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           A       0.00      0.00      0.00         2\n",
      "           N       0.49      1.00      0.66        22\n",
      "           S       0.00      0.00      0.00        21\n",
      "\n",
      "    accuracy                           0.49        45\n",
      "   macro avg       0.16      0.33      0.22        45\n",
      "weighted avg       0.24      0.49      0.32        45\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\sklearn\\metrics\\_classification.py:1334: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n",
      "  _warn_prf(average, modifier, msg_start, len(result))\n",
      "c:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\sklearn\\metrics\\_classification.py:1334: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n",
      "  _warn_prf(average, modifier, msg_start, len(result))\n",
      "c:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\sklearn\\metrics\\_classification.py:1334: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n",
      "  _warn_prf(average, modifier, msg_start, len(result))\n"
     ]
    }
   ],
   "source": [
    "# Load sample data\n",
    "sample = load_sample('saureus')\n",
    "\n",
    "# Define input arguments\n",
    "key             = sample['key']\n",
    "profiles        = sample['profiles']\n",
    "feature_names   = sample['feature_names']\n",
    "train_ixns      = sample['train']['interactions']\n",
    "train_scores    = sample['train']['scores']\n",
    "test_ixns       = sample['test']['interactions']\n",
    "test_scores     = sample['test']['scores']\n",
    "strains         = sample['orthology']['strains']\n",
    "orthology_map   = sample['orthology']['map']\n",
    "\n",
    "# Determine ML features\n",
    "train_data      = featurize(train_ixns, profiles, feature_names=feature_names, key=key, silent=True)\n",
    "test_data       = featurize(test_ixns, profiles, feature_names=feature_names, key=key, silent=True, \n",
    "                            strains=strains, orthology_map=orthology_map)\n",
    "X_train, X_test = train_data['feature_df'].to_numpy().transpose(), test_data['feature_df'].to_numpy().transpose()\n",
    "\n",
    "# Determine class labels\n",
    "thresh, classes = (-0.5, 2), ('S', 'N', 'A')\n",
    "train_labels    = classify(train_scores, thresholds=thresh, classes=classes)\n",
    "test_labels     = classify(test_scores, thresholds=thresh, classes=classes)\n",
    "\n",
    "# Train and apply a regression-based model\n",
    "reg_model = RandomForestRegressor()\n",
    "reg_model.fit(X_train, train_scores)\n",
    "reg_y = reg_model.predict(X_test)\n",
    "r, p = spearmanr(test_scores, reg_y)\n",
    "r2 = r2_score(test_scores, reg_y)\n",
    "print('Regression results:')\n",
    "print('\\tSpearman R = {}'.format(round(r, 4)))\n",
    "print('\\tSpearman p = {:.3g}'.format(p))\n",
    "print('\\tR2 = {}'.format(round(r2, 4)))\n",
    "\n",
    "# Train and apply a classification-based model\n",
    "class_model = RandomForestClassifier()\n",
    "class_model.fit(X_train, train_labels)\n",
    "class_y = class_model.predict(X_test)\n",
    "print('Classification results:')\n",
    "print(classification_report(test_labels, class_y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example: *A. baumannii*\n",
    "\n",
    "The following analysis and results were originally reported in the [MAGENTA](https://doi.org/10.1371/journal.pcbi.1006677) paper.  \n",
    "- **Training dataset**: 338 two- to three-way interactions between 24 antibiotics measured in *E. coli* cultured in various media conditions  \n",
    "- **Testing dataset**: 45 two-way interactions between the 8 antibiotics measured in *A. baumannii*  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Defining INDIGO features: 100%|██████████| 338/338 [00:01<00:00, 247.62it/s]\n",
      "Defining INDIGO features: 100%|██████████| 45/45 [00:00<00:00, 249.99it/s]\n",
      "Mapping orthologous genes: 100%|██████████| 1/1 [00:06<00:00,  6.00s/it]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Regression results:\n",
      "\tSpearman R = 0.6061\n",
      "\tSpearman p = 1.02e-05\n",
      "\tR2 = -0.5064\n",
      "Classification results:\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           A       0.39      1.00      0.56        17\n",
      "           N       0.00      0.00      0.00        11\n",
      "           S       1.00      0.06      0.11        17\n",
      "\n",
      "    accuracy                           0.40        45\n",
      "   macro avg       0.46      0.35      0.22        45\n",
      "weighted avg       0.52      0.40      0.25        45\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "c:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\sklearn\\metrics\\_classification.py:1334: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n",
      "  _warn_prf(average, modifier, msg_start, len(result))\n",
      "c:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\sklearn\\metrics\\_classification.py:1334: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n",
      "  _warn_prf(average, modifier, msg_start, len(result))\n",
      "c:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python310\\lib\\site-packages\\sklearn\\metrics\\_classification.py:1334: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n",
      "  _warn_prf(average, modifier, msg_start, len(result))\n"
     ]
    },
    {
     "data": {
      "image/png": "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PT0AQJMmTfDw4UNERESUOBS1tAQwNTX47xkJKQNisTb3mskYgPJ9uiw/E8r7fs5P2/qJiYkB9ztdUsUKRScnp09v/q9D5cLalT2c1tXV/ey5vfj4eGRkZEAqlSrsMaalpcHcvOh7xz58+IAJEybg2rVrWLlyJb7//nuF6fwgz/fNN9/g4sWLStXPJ5czZGVll3j5snDz5nVs27YZgwcPg4MDXTj4kkkkYu71u2NJaqxEfTIyPkIkyivQbmysD23t4u1BFisU1flon6OjI+RyOa5fv87twT158gSpqalcKP+bVCrFmDFjcP/+fURERMDFxUVh+sOHD9GvXz+sX79eYdrff/8NKyurUtUrk1WcZ78lEgk2b96EjIx32LJlE6ytben2ki9YRfruqYtMJoe2duk+BwEr6yslKjB58mTcunULixYtgr6+PmbPng1DQ0MurKVSKTIzM1G1alUIhUKEhIRg3bp1WLFiBZydnRXWVbVqVejo6KBv377IycnB3LlzYWpqit9//x27du3C/v37YW1tXaI68/LkSE//WOrtVZUDB35XeDKje3dP9OzZR91lkTLCGINUqp7BySQSCSZO9AUArF69Xm1/fIVCUaGH72ZmBsXeU9SIUMzOzsaiRYtw4sQJAJ9uHp85cyZMTU0BALGxsRg8eDC2bdsGFxcXfPfdd3j69Gmh68qf582bN1ixYgUuXLiArKws2NraYsqUKdy9kCVRkUIxNTUFM2YEQi7/51BCW1sbCxYE0xgtROUkEjF8fYcDANavj4RIVLrzeqr2xYWipqgoocgYw6pVS4vs7YX6BSSq9iWFIl3D/wJRv4CElByF4heIxmghpOQoFL9ANEYLISVHofiFyu8XMD8AqV9AQoqHQvELRv0CEqI8CsUvWH6/gNWqVcegQcPoxm1CiqHUHUKQio36BSREObSnSAghPBSKhBDCQ6FICCE8FIqEEMJDoUgIITwUioQQwkOhSAghPBSKhBDCQ6FICCE8FIqEEMJDoUgIITwUioQQwkOhSAghPBSKhBDCQ6FICCE8FIqEEMJDoUgIITwUioQQwkOhSAghPBSKhBDCQ6FICCE8GhGKEokEc+fOhaurKxwcHDB58mSkp6d/dpn169fDxsamwD++nTt3wsPDA82aNcOAAQNw7969stwMQogG0IhQnDNnDi5evIiQkBBs3boVjx8/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",
      "text/plain": [
       "<Figure size 300x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Load sample data\n",
    "sample = load_sample('abaumannii')\n",
    "\n",
    "# Define input arguments\n",
    "key             = sample['key']\n",
    "profiles        = sample['profiles']\n",
    "feature_names   = sample['feature_names']\n",
    "train_ixns      = sample['train']['interactions']\n",
    "train_scores    = sample['train']['scores']\n",
    "test_ixns       = sample['test']['interactions']\n",
    "test_scores     = sample['test']['scores']\n",
    "strains         = sample['orthology']['strains']\n",
    "orthology_map   = sample['orthology']['map']\n",
    "\n",
    "# Determine ML features\n",
    "train_data      = featurize(train_ixns, profiles, feature_names=feature_names, key=key, silent=True)\n",
    "test_data       = featurize(test_ixns, profiles, feature_names=feature_names, key=key, silent=True, \n",
    "                            strains=strains, orthology_map=orthology_map)\n",
    "X_train, X_test = train_data['feature_df'].to_numpy().transpose(), test_data['feature_df'].to_numpy().transpose()\n",
    "\n",
    "# Determine class labels\n",
    "thresh, classes = (-0.5, 0), ('S', 'N', 'A')\n",
    "train_labels    = classify(train_scores, thresholds=thresh, classes=classes)\n",
    "test_labels     = classify(test_scores, thresholds=thresh, classes=classes)\n",
    "\n",
    "# Train and apply a regression-based model\n",
    "reg_model = RandomForestRegressor()\n",
    "reg_model.fit(X_train, train_scores)\n",
    "reg_y = reg_model.predict(X_test)\n",
    "r, p = spearmanr(test_scores, reg_y)\n",
    "r2 = r2_score(test_scores, reg_y)\n",
    "print('Regression results:')\n",
    "print('\\tSpearman R = {}'.format(round(r, 4)))\n",
    "print('\\tSpearman p = {:.3g}'.format(p))\n",
    "print('\\tR2 = {}'.format(round(r2, 4)))\n",
    "\n",
    "# Train and apply a classification-based model\n",
    "class_model = RandomForestClassifier()\n",
    "class_model.fit(X_train, train_labels)\n",
    "class_y = class_model.predict(X_test)\n",
    "print('Classification results:')\n",
    "print(classification_report(test_labels, class_y))\n",
    "\n",
    "# Visualize results\n",
    "df = pd.DataFrame({'x': test_labels, 'y': reg_y})\n",
    "df.replace({'A': 'Antagonism', 'N': 'Neutral', 'S': 'Synergy'}, inplace=True)\n",
    "sns.set(rc={'figure.figsize':(3, 4)})\n",
    "ax = sns.boxplot(x='x', y='y', data=df, order=['Antagonism', 'Neutral', 'Synergy'], )\n",
    "ax.set(title='A. baumannii', xlabel='True class', ylabel='Predicted score')\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.10.8 64-bit",
   "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.10.8"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "afb734500600fd355917ca529030176ea0ca205570884b88f2f6f7d791fd3fbe"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
