{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Overview"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Autocnet uses numpy, pandas, and matplotlib to manage data and show it in a userfriendly way.  Numpy is powerful processing software, great for large datasets and performing complex calculations.  Pandas organizes the data into a dataframe so it can be viewed and manipulated as needed.  Matplotlib shows the data using various plots. This tutorial will go over basic operations in jupyter notebook using numpy, pandas, and matplotlib.  You will use these software packages extensively working with Autocnet."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Numpy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What is Numpy? Numpy is the core library for computing in Python.  It provides a multidimensional array object, and tools to work with the array.  A numpy array is a grid of values, all of the same type, and indexed by a list of nonnegative integers. The number of dimensions is the rank of the array, the shape of an array is a list of intergers giving the size of the array along each dimension. The real power of numpy is massive vectorization at a processor level.  The difference between python and numpy processing is huge."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import numpy as np        # First thing to do is to add numpy to the notebook you are using. In order to access python modules (or functions from a python module) they must be explicitly loaded into the notebook.\n",
    "\n",
    "a = np.array([1, 2, 3])   # Create a rank 1 array, vector array\n",
    "\n",
    "print(type(a))            # print() will print a value to a stream. This prints the type of array.\n",
    "\n",
    "print(a.shape)            # prints the shape of the array\n",
    "\n",
    "print(a[0], a[1], a[2])   # prints the values of the array\n",
    "\n",
    "print(a.dtype)            # prints the data type (this is a 64-bit intereger)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use the ? symbol to query python about an object.  Below is an example for ?np.array.  A window should pop up that talks about   what this object does."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#### Exercise: Use the ? symbol to find more information about the following objects: print, type, np.dot, np.dtype)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Numpy array's can be manipulated in many ways.  The following sections shows a few ways to perfom different math functions on aspects of a numpy array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "a[0] = 5                  # Change an element of the array. Python has a zero based index, indexing starts at zero instead of one, so the first element is 0.  \n",
    "\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: In the next cell, change the second element in the \"a\" array to 10. Print your results to check."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "a[1]+15                   # Adding to one element in the array."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: Add 5 to the third element in the \"a\" array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "a+1                       # Add one to the entire array,this is called broadcasting, it broadcasts to each element of the array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "a*2                       # Times the array by 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "np.sin(a)        # Sin of the array. This is a vectorized function, this applies the function to each element of the array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "np.std(a)         #Standard deviation of the array."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: Take the standard deviation of the cosine of the \"a\" array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "print(np.append([a], [15, 1, 6])) #Appends elements to an array. Different axis of the array can be specified.\n",
    "print(np.append([a], [[15, 1, 6]], axis=0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "b = np.array([[1,2,3],[4,5,6]])    # Create a rank 2 array, matrix array\n",
    "print(b.shape)                     # Prints \"(2, 3)\"\n",
    "print(b[0, 0], b[0, 1], b[1, 0])   # Prints \"1 2 4\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: Print the second element of the first rank of the \"b\" array."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Numpy arrays can be combined with other arrays. This section goes over working with multiple arrays."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "np.add(a,b)            # Adds two arrays together"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "print(a)\n",
    "print(b)\n",
    "a + b                  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "c = np.zeros((2,2))   # Create an array of all zeros\n",
    "print(c)             "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "d = np.ones(3)    # Create an array of all ones\n",
    "print(d)                       "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "e = np.full((2,2), 7)  # Create a constant array\n",
    "print(e)              "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "f = np.eye(2)         # Create a 2x2 identity matrix\n",
    "print(f)                        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "e = np.random.random((2,2))  # Create an array filled with random values, uses: adding random error to things, pick random elements, pull a random sample from data\n",
    "print(e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "s=a[0]                  # Contents of an array can be accessed and modified by slicing. Slicing takes an elements from one index and moves them to another.\n",
    "print(s)                # This should take the first element of the 'a' array and move it to the s array.\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "print(b[0:1])   # You can also slice items between indexes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: multiple the \"a\" and \"d\" arrays together. Hint: ?np.dot."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "      "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pandas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What is Pandas? Pandas is a open source data analysis and manipulation tool, built on top of the Python programming language. It is a software library for Python and is used to perform data analysis in Python. Under the hood, pandas uses numpy to perform most of its functions. The most common type of pandas dataframe is a 2-dimensional table, with rows and columns.  This is very similar to an excel spreadsheet or SQL table."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import pandas as pd  # Pandas package import\n",
    "import requests      # Http library for Python. Communicates between browser and web server that is storing data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "url = 'https://api.covid19api.com/summary' # Practice data location."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "r = requests.get(url, verify = False) #A warning will pop up because of the DOI firewall.  This will ingest the data to look at."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We use json to extract and structure the data initially but this is just to get it ready for pandas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "json = r.json() # Extracts json structured data from the request."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "json # Variable that will show the data.  It is similar to pvl."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "json.keys() # Contains the keys of the dictionary as a list. We can use these keys to explore the json similar to selecting a column."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "json['Countries']          # Sorts the data by country."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Looking at the type of keys can tell us what has interesting data or not.\n",
    "\n",
    "type(json['Global'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: Look at what type of keys the Countries, Message, Date, and ID columns are."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# JSON to a Dataframe.  # Remove the slug column by specifying columns.\n",
    "df = pd.DataFrame(json['Countries'])\n",
    "df.set_index('Country', inplace=True)\n",
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df.shape            # Returns the shape of the dataframe, 190 rows and 11 columns."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df.columns          # Returns the names of the columns in the dataframe"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# Preview of the Data\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: View the tail of the dataframe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "pd.set_option('display.max_rows',None)              # By default, pandas, does not show the entire dataframe.  Change these two options to change that.\n",
    "pd.set_option('display.max_columns',None)\n",
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df.loc['Colombia']         #Allows you to look up columns and rows based on values. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: Look up the United States, China, Brazil, and France."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df.loc[df['TotalDeaths'] > 100000]   # You can combine .loc with other parameters to sort the dataframe."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Excercise:  Look up the TotalConfirmed > 10000000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df['NewRecovered']             # This will show all the data for the NewRecovered column."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df[[\"TotalConfirmed\",\"TotalDeaths\"]].describe()  # How to compute descriptive statistics .describe() "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: Look up descriptive statistics for TotalConfirmed and NewConfirmed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df['TotalConfirmed'].quantile(.95) # percentile function.  Allows you to compute percentiles on a series. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: Look up the 25th, 50th, and 75th quantile for TotalDeaths using one function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df['NewDeaths']+df['TotalDeaths']    # You can perform simple math functions on the different columns."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: Calculate the death rate by country."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df.sort_values(by='TotalDeaths', ascending=False)     # The .sort_values function allows you to sort data by either the x or y axis.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise:  Sort the dataframe with fewest TotalConfirmed on top."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Matplotlib"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What is Matplotlib? Matplotlib is a comprehensive library for creating static, animated, and interactive visualizations in Python. In this section, we are going to take the dataframe and plot the data using boxplots, histograms, scatterplots, and lineplots.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "attachments": {
    "image.png": {
     "image/png": "[encoded data, 128520 characters]"
    }
   },
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Titles and axes properties\n",
    "![image.png](attachment:image.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Box plots\n",
    "\n",
    "# Creating Data \n",
    "np.random.seed(10) \n",
    "data = np.random.normal(100, 20, 200) \n",
    "  \n",
    "fig = plt.figure(figsize =(15, 9)) \n",
    "  \n",
    "# Plot \n",
    "plt.boxplot(data) \n",
    "\n",
    "plt.show() "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df.boxplot(column=['TotalRecovered','TotalDeaths'])               # You can plot multiple columns on one plot.\n",
    "plt.yscale(\"log\")                       # You can change the x and y axis to log scale.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: Make a boxplot of TotalConfirmed and TotalDeaths cases. Plot using a log scale on the y axis."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Histograms\n",
    "\n",
    "# Data\n",
    "x = [21,22,23,4,5,6,77,8,9,10,31,32,33,34,35,36,37,18,49,50,90,100]\n",
    "num_bins = 10\n",
    "\n",
    "# Plot\n",
    "n, bins, patches = plt.hist(x, num_bins, facecolor='green', alpha=0.5)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "df.hist(grid=False, color='red')       # You can use matplotlib to plot all the columns in a dataframe at once."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###### Exercise: Create a green histogram of the TotalRecovered that has 50% opacity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Scatterplots\n",
    "\n",
    "# Data\n",
    "N = 1000\n",
    "x = np.random.rand(N)\n",
    "y = np.random.rand(N)\n",
    "area = np.pi*3\n",
    "\n",
    "# Plot\n",
    "plt.scatter(x, y, s=area, facecolor='blue', alpha=0.5)\n",
    "\n",
    "plt.title('Scatter plot')\n",
    "\n",
    "plt.xlabel('X')\n",
    "\n",
    "plt.ylabel('Y')\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Choosing what color to use is important when making a plot.  https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html has a wide array of colormaps to choose from.\n",
    "from collections import OrderedDict\n",
    "df.plot.scatter(x='NewConfirmed', y='NewRecovered', colormap='viridis', title='Scatter plot')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise: Create a scatterplot of TotalDeaths vs. TotalConfirmed with a log scale on the TotalConfirmed axis, a title, and a coloarscale.  Is there trend to the data?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# line plots\n",
    "\n",
    "# Data\n",
    "t = np.arange(0.0, 4.0, 0.001)\n",
    "s = 1 + np.sin(2 * np.pi * t)\n",
    "\n",
    "# Plot\n",
    "fig, ax = plt.subplots()\n",
    "ax.plot(t, s)\n",
    "\n",
    "ax.set(xlabel='Wavelength', ylabel='Waveheight',\n",
    "       title='Line Plot')\n",
    "ax.grid()\n",
    "\n",
    "fig.savefig(\"test.png\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Addtional resources"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# A few other things you can modify in Matplotplib\n",
    "\n",
    "x = np.linspace(0, 2, 100)\n",
    "\n",
    "fig, ax = plt.subplots()  # Create a figure and an axes.\n",
    "\n",
    "ax.plot(x, x, label='linear')  # Plot some data on the axes.\n",
    "\n",
    "ax.plot(x, x**2, label='quadratic')  # Plot more data on the axes.\n",
    "ax.plot(x, x**3, label='cubic')\n",
    "\n",
    "ax.set_xlabel('x label')  # Add an x-label to the axes.\n",
    "\n",
    "ax.set_ylabel('y label')  # Add a y-label to the axes.\n",
    "\n",
    "ax.set_title(\"Simple Plot\")  # Add a title to the axes.\n",
    "\n",
    "ax.legend()  # Add a legend."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "AutoCNet PLIO (workshop)",
   "language": "python",
   "name": "autocnet_workshop"
  },
  "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.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
