{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "92fb5068-0669-43e3-9799-9dca9ffe6638",
   "metadata": {
    "editable": true,
    "jupyter": {
     "source_hidden": true
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "%%html\n",
    "<style>\n",
    "table {float:left}\n",
    "</style>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11174c4d-dcb5-49d8-96fc-b83ad35a6193",
   "metadata": {},
   "source": [
    "<div>\n",
    "<img src=\"https://cps.unileoben.ac.at/wp/CPS_Logo_Black.png\" width=\"260\"/>\n",
    "</div>\n",
    "Chair of Cyber-Physical-Systems, Austria"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a04ead7b-4fce-4ea2-9501-d60a112aadcb",
   "metadata": {},
   "source": [
    "<style>\n",
    "td, th {\n",
    "   border: none!important;\n",
    "}\n",
    "</style>\n",
    "\n",
    "| Credentials |  |\n",
    "|----|---|\n",
    "|Host | Montanuniversitaet Leoben |\n",
    "|Web | https://cps.unileoben.ac.at |\n",
    "|Mail | cps@unileoben.ac.at |\n",
    "|Authors | Vedant Dave, Fotis Lygerakis, Linus Nwankwo, Melanie Neubauer, Nikolaus Feith and Elmar Rueckert|\n",
    "|Corresponding Authors | melanie.neubauer@unileoben.ac.at, rueckert@unileoben.ac.at |\n",
    "|Last edited |  02.10.2023 |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35a0d7a0",
   "metadata": {},
   "source": [
    "# Database Basics"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b05e025-9843-410e-80cd-f2db24148da8",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-success\">\n",
    "This is a tutorial on the basics of interfacing databases for beginners. \n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b9edce42-35e4-4dcd-886c-8d19a30bfdf0",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "## Content\n",
    "***\n",
    "[1. Motivation for learning Python?](#sec:whypython)   \n",
    "\n",
    "[2. How to run Python on your computer?](#sec:runpython)\n",
    "\n",
    "[3. Overview of Important Libraries](#sec:libraries)\n",
    "\n",
    "[4. Environments](#sec:environments)\n",
    "***\n",
    "[5. Let's Code Basic Commands](#sec:basiccommands)\n",
    "\n",
    "[6. Conditions & Loops](#sec:conditionsloops)\n",
    "\n",
    "[7. Lists & Dictionaries](#sec:lists)\n",
    "***\n",
    "[8. Numpy Arrays](#sec:arrays)\n",
    "\n",
    "[9. Functions and Efficient Implementations](#sec:functions)\n",
    "\n",
    "[10. Classes & Object Oriented Programming](#sec:classes)\n",
    "\n",
    "[11. Plotting & Scientific Figures](#sec:plotting)\n",
    "***\n",
    "[12. Advanced Features & Other Resource](#sec:resources)\n",
    "\n",
    "[13. Using Large Language Models like ChatGPT](#sec:llms)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4e0ceef-663a-46c3-9e52-6cd6c942422f",
   "metadata": {},
   "source": [
    "# <a id=\"sec:whypython\">1. Motivation for Learning Python?</a>\n",
    "- Easy to use.\n",
    "- Lots of Libraries.\n",
    "- Large Community (Stack Overflow).\n",
    "- Easy to debug.\n",
    "- Most popular programming language to date (see the image below taken from https://spectrum.ieee.org/the-top-programming-languages-2023 on Sept. 2023).\n",
    "- Used in almost all research disciplines.\n",
    "    \n",
    "<div>\n",
    "<img src=\"https://cps.unileoben.ac.at/wp/statistics_2023_programming_language.png\" width=\"600\"/>\n",
    "</div>\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ba97999c",
   "metadata": {},
   "source": [
    "# <a id=\"sec:runpython\">2. How to run Python in your Computer?</a> \n",
    "You have many IDE's. Here we are using **Jupyter Notebook**. The main reason is that that it is easy to write these passages and code in one single Notebook, then share it with others.\n",
    "\n",
    "If you are using Python for some personal project in your local PC, I recommend using **PyCharm** or **Visual Studio Code**. They are user friendly and very good for beginners. If you have any dobuts in them, you can contact me anytime."
   ]
  },
  {
   "attachments": {
    "ide1_ol5h4k.webp": {
     "image/webp": "[encoded data, 105128 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "00b11a79",
   "metadata": {},
   "source": [
    "![ide1_ol5h4k.webp](attachment:ide1_ol5h4k.webp)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cff5af33",
   "metadata": {},
   "source": [
    "# <a id=\"sec:libraries\">3. Overview of Important Libraries</a>  \n",
    "### Arrays/ Matrix Compuation\n",
    "- Numpy\n",
    "- Numba\n",
    "- Pandas (Tabular Data)\n",
    "\n",
    "### Plotting\n",
    "- Matplotlib\n",
    "- Plotly\n",
    "- Seaborn (Advanced)\n",
    "- Mayavi (Very Advanced)\n",
    "\n",
    "\n",
    "### Machine Learning\n",
    "- Scipy\n",
    "- Scikit-learn\n",
    "- Catboost\n",
    "\n",
    "### Neural Networks\n",
    "- Pytorch\n",
    "- Tensorflow"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14b350a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# This command is only for Numpy installation\n",
    "%pip install numpy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "482cbfe2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import a Library in your Code\n",
    "import  numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6217301",
   "metadata": {},
   "source": [
    "# <a id=\"sec:environments\">4. Environments</a> \n",
    "Why do you sometimes need local environments?\n",
    "- Conflicts\n",
    "- Dependencies\n",
    "- Extra Libraries"
   ]
  },
  {
   "attachments": {
    "Screenshot%20from%202022-10-03%2017-41-19.png": {
     "image/png": "[encoded data, 63464 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "7bb0ca61",
   "metadata": {},
   "source": [
    "![Screenshot%20from%202022-10-03%2017-41-19.png](attachment:Screenshot%20from%202022-10-03%2017-41-19.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f033898",
   "metadata": {},
   "source": [
    "What options do you have?\n",
    "- [Virtual Environment](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/)\n",
    "- [Conda Environment](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html)\n",
    "\n",
    "[Virtualenv vs Conda](https://www.reddit.com/r/learnpython/comments/xjzhxs/difference_between_virtualenv_venv_and_conda_env/): From what I understand, pip is a package manager for Python. venv is an environment manager for Python. conda is both a package and environment manager and is language agnostic. You can find even more explanation [here](https://towardsdatascience.com/a-guide-to-conda-environments-bc6180fc533#:~:text=venv%20is%20an%20environment%20manager,any%20language%20(in%20theory)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66f200c3",
   "metadata": {},
   "source": [
    "# <a id=\"sec:basiccommands\">5. Let's Code Basic Commands</a> \n",
    "## Print Statement"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "974c33a4",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Hello World!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8381ce9",
   "metadata": {},
   "source": [
    "## Value Assignments to Variables\n",
    "Unlike C or C++, you don't need to predefine variables before assigning them some value. Things are simple in Python. Just assign the variable some value.\n",
    "It must be noted that the standard interpreter in Python is written in C, thus the code is eventually represented as it is written in C. We will not go into th detail. If anyone is interested, they can refer to this [link](https://www.youtube.com/watch?v=54NWGAYhfbc)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9464bc38",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Integer =  20\n",
      "String =  Hello\n"
     ]
    }
   ],
   "source": [
    "a = 20 # Integer\n",
    "s = \"Hello\" # String\n",
    "\n",
    "print(\"Integer = \", a)\n",
    "print(\"String = \", s)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4d96b2f",
   "metadata": {},
   "source": [
    "## Comments"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f304b4f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# This is a Single line Comment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "904aa124",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'\\nThis is a \\nMulti line \\nComment\\n'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"\"\"\n",
    "This is a \n",
    "Multi line \n",
    "Comment\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94215fa0-27d8-489e-8286-3fb11240617a",
   "metadata": {},
   "source": [
    "## Exercises\n",
    "\n",
    "<div class=\"alert alert-block alert-warning\">\n",
    "Create a variable that takes the concatenation of an integer value and of a constant string. \n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "48502b54-997b-427b-83ba-1f4543c179e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Place your code here that concatenates an integer with a constant string.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56a96347",
   "metadata": {},
   "source": [
    "# <a id=\"sec:conditionsloops\">6. Conditions & Loops</a>  \n",
    "Many might be familiar with this thing. This is just a simple conditional statement."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "584af0eb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "This variable is larger than 10\n"
     ]
    }
   ],
   "source": [
    "if a < 10:\n",
    "    print(\"This variable is smaller than 10\")\n",
    "elif a > 10:\n",
    "    print(\"This variable is larger than 10\")\n",
    "else:\n",
    "    pass # Do nothing, just pass the program to next line"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c09128a3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "This variable is larger than 10\n"
     ]
    }
   ],
   "source": [
    "# If-else statement in a single line\n",
    "k = \"This variable is smaller than 10\" if a < 10 else \"This variable is larger than 10\"\n",
    "print(k)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "a8d79470",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n"
     ]
    }
   ],
   "source": [
    "# Check if it is equal to some value\n",
    "k = True if a == 20 else False\n",
    "print(k)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a5495504",
   "metadata": {},
   "source": [
    "## Other Operators\n",
    "- != : Not equal to\n",
    "- <= : Smaller than or equal to\n",
    "- in : If a variable is in list or not\n",
    "- is : Comparison of two variables in memory location terms"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e6a7e562",
   "metadata": {},
   "source": [
    "# Loops"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e490d6c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(5):\n",
    "    print(i)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91a5d09e",
   "metadata": {},
   "source": [
    "Python indexing starts with 0 (not 1) and goes to n-1. Here, we print from 0 to 4 (0,...,4).\n",
    "\n",
    "The **range** function is the mostly used function. The purpose of range is to provide entries from 0 to n-1. For example **range(20)** will give you numbers (integers) from 0 to 19.\n",
    "\n",
    "You can do any computation with loops. The looping becomes powerful when used to extract out details or changing details from sequences."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e09994d",
   "metadata": {},
   "source": [
    "# <a id=\"sec:lists\">7. Lists & Dictionaries</a>  \n",
    "The most basic data structure in Python is the sequence. Each element of a sequence is assigned a number - its position or index. The first index is zero, the second index is one, and so forth.\n",
    "\n",
    "List contains a sequence of any data types. You can mix whatever data types you want.\n",
    "\n",
    "**Fun Fact:** You can also make a List of all the files inside certain folder in your computer. Then you can access them one by one and perform your desired operations (like extracting data from them)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "93934611",
   "metadata": {},
   "outputs": [],
   "source": [
    "list1 = [1, 24, \"My\", \"Name\", 58, 89.256, \"is\"]\n",
    "list2 = [\"Slim\", 45, 0.567, \"Shady\"]\n",
    "list_added = list1 + list2\n",
    "\n",
    "print(\"Added Lists: \", list_added)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9dba34d0",
   "metadata": {},
   "source": [
    "## Append someting to List"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9c143e9d",
   "metadata": {},
   "outputs": [],
   "source": [
    "l = [1 , 2, 0.256]\n",
    "\n",
    "for i in range(len(l)):\n",
    "    list_added.append(l[i])\n",
    "    \n",
    "print(\"New List : \", list_added)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "90503ee1",
   "metadata": {},
   "source": [
    "## Check if something is in your list"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "09d43b68",
   "metadata": {},
   "outputs": [],
   "source": [
    "find_this = \"Slim\"\n",
    "find_also_this = \"Shady\"\n",
    "\n",
    "# Everything in one line\n",
    "finder = True if find_this and find_also_this in list_added else False\n",
    "\n",
    "# Print multiple variables in the print statement with Format\n",
    "print(\"Are {} and {} in the given list?\".format(find_this, find_also_this))\n",
    "print(finder)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd98fa5c",
   "metadata": {},
   "source": [
    "## Other List Operations\n",
    "- Length\n",
    "- Sort\n",
    "- Reverse\n",
    "\n",
    "And many more. You can find them [here](https://docs.python.org/3/tutorial/datastructures.html)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7cf506f7",
   "metadata": {},
   "source": [
    "# Dictionaries\n",
    "Also stores multipe data types into it. Each storage **value** has one **key** through which each value can be accessed.\n",
    "\n",
    "Each key is separated from its value by a colon (:), the items are separated by commas, and the whole thing is enclosed in curly braces. An empty dictionary without any items is written with just two curly braces, like this: {}. Keys are unique within a dictionary while values may not be. The values of a dictionary can be of any type, but the keys must be of an immutable data type such as strings, numbers, or tuples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cfed21c3",
   "metadata": {},
   "outputs": [],
   "source": [
    "dict = {'Sensor': 'Joint1', 'Torque': 1, 'Joint_Config': [0.25, 0.55, 0.10]}\n",
    "\n",
    "print(\"Sensor : \", dict[\"Sensor\"])\n",
    "print(\"Joint Position : \", dict[\"Joint_Config\"][0])\n",
    "print(\"Joint Velocity : \", dict[\"Joint_Config\"][1])\n",
    "print(\"Joint Acceleration : \", dict[\"Joint_Config\"][2])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b75fb1ae",
   "metadata": {},
   "source": [
    "## Get all the Keys and Values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "41ecc0f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"All the Keys are: \", dict.keys(), \"\\n\")\n",
    "\n",
    "print(\"All the Values are: \", dict.values(), \"\\n\")\n",
    "\n",
    "print(\"All the Keys-Values pairs are: \", dict.items())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cc07bad4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# To get them in form of list, just convert them to list\n",
    "keys_in_list_form = list(dict.keys())\n",
    "print(\"Keys in List Format:\", keys_in_list_form)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0683f0a",
   "metadata": {},
   "source": [
    "## Many Other methods for Dictionaries"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fee69abc",
   "metadata": {},
   "source": [
    "- Update values\n",
    "- Add new elements\n",
    "\n",
    "And Many more. You can find them [here](https://www.programiz.com/python-programming/methods/dictionary)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9deea1f",
   "metadata": {},
   "source": [
    "# <a id=\"sec:arrays\">8. Numpy Arrays</a>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0842f910",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-success\">\n",
    "Numpy is a fundamental package for scientific computing in Python. It is a Python library that provides a multidimensional array object, various derived objects (such as masked arrays and matrices), and an assortment of routines for fast operations on arrays, including mathematical, logical, shape manipulation, sorting, selecting, I/O, discrete Fourier transforms, basic linear algebra, basic statistical operations, random simulation and much more.\n",
    "</div>\n",
    "\n",
    "At the core of the NumPy package, is the <strong>ndarray object</strong>. This encapsulates n-dimensional arrays of homogeneous data types, with many operations being performed in compiled code for performance.\n",
    "\n",
    "NumPy arrays are stored at one continuous place in memory unlike lists, so processes can access and manipulate them very efficiently. NumPy is a Python library and is written partially in Python, but most of the parts that require fast computation are written in C or C++."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9bddf001",
   "metadata": {},
   "source": [
    "## Some Examples of Arrays"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8b5f237e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Construct a simple 1D numpy array\n",
    "arr_1D = np.array([1,2,3,4])\n",
    "print(arr_1D)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b3b6aa14",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Construct a 2D numpy array\n",
    "arr_2D = np.array([\n",
    "        [1, 2, 3, 4],\n",
    "        [5, 6, 7, 8],\n",
    "        [9, 10, 11, 12]\n",
    "        ])\n",
    "print(arr_2D)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "797e5acf",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Construct a 3D numpy array\n",
    "arr_3D = np.array([[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]])\n",
    "print(arr_3D)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3e71acfa",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Check the dimensions of your array\n",
    "print(arr_1D.ndim)\n",
    "print(arr_2D.ndim)\n",
    "print(arr_3D.ndim)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e463242",
   "metadata": {},
   "source": [
    "## Shape and Reshape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c90d6e51",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Find the shape of your array\n",
    "print(\"Shape of arr_1D : \", np.shape(arr_1D))\n",
    "print(\"Shape of arr_2D : \", np.shape(arr_2D))\n",
    "print(\"Shape of arr_3D : \", np.shape(arr_3D))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0589f0eb",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Reshape your arrays into any shape\n",
    "arr_2D_into_1D = arr_2D.reshape(12,)\n",
    "print(arr_2D_into_1D)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56a8e21f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# When you don't know the dimensions\n",
    "arr_3D_into_1D = arr_3D.reshape(-1)\n",
    "print(arr_3D_into_1D)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1cc1a078",
   "metadata": {},
   "outputs": [],
   "source": [
    "# You want it to be some type (3,n) but don't know n\n",
    "arr_3D_into_2D = arr_3D.reshape(3,-1)\n",
    "print(arr_3D_into_2D)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8230d2e",
   "metadata": {},
   "source": [
    "**Note:** You cannot reshape the array of shape (3,20) into the shape of (59,1) because the reshaped array must have the same indices as the original one."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7a916c7",
   "metadata": {},
   "source": [
    "## Indexing Arrays"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b507a091",
   "metadata": {},
   "source": [
    "How do you find one element in an array?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eb2fd658",
   "metadata": {},
   "outputs": [],
   "source": [
    "arr = np.array([\n",
    "        [1, 2, 3, 4],\n",
    "        [5, 6, 7, 8],\n",
    "        [9, 10, 11, 12]\n",
    "        ])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8fe5e2d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Access 4th element in the 1st row\n",
    "elem = 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fcb84876",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Access all the elements from the final row\n",
    "elems = arr[-1,:]\n",
    "print(elems)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "787801f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Access all the elements from the final column\n",
    "elems = arr[:,-1]\n",
    "print(elems)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e5502684",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Access two middle elements from the middle row\n",
    "elems = 0"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19bd4fcb",
   "metadata": {},
   "source": [
    "## Generate n samples between two numbers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fe8f8051",
   "metadata": {},
   "outputs": [],
   "source": [
    "n = 20\n",
    "l = np.linspace(0,1,n)\n",
    "print(l)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e515af41",
   "metadata": {},
   "source": [
    "## Generate Random arrays"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "92ef427d",
   "metadata": {},
   "outputs": [],
   "source": [
    "r = np.random.rand(5,5)\n",
    "print(r)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00332ec8",
   "metadata": {},
   "source": [
    "## Concatenate two arrays"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a84961b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "arr1 = np.array([[1, 2, 3]])\n",
    "arr2 = np.array([[4, 5, 6]])\n",
    "print(\"Shape of array 1: \", arr1.shape)\n",
    "print(\"Shape of array 2: \", arr2.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b092f35",
   "metadata": {},
   "outputs": [],
   "source": [
    "arr = np.concatenate((arr1,arr2))\n",
    "print(arr)\n",
    "print(\"Shape of array concatenated along rows: \", arr.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a5ba1882",
   "metadata": {},
   "outputs": [],
   "source": [
    "arr_col = np.concatenate((arr1,arr2), axis=1)\n",
    "print(arr_col)\n",
    "print(\"Shape of array concatenated along columns: \", arr_col.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "78c5aa8d",
   "metadata": {},
   "source": [
    "## Converting Lists to Arrays"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12328b05",
   "metadata": {},
   "outputs": [],
   "source": [
    "# List\n",
    "l = [[1,2,3],[4,5,6]]\n",
    "\n",
    "# Print and Check type\n",
    "print(l)\n",
    "print(type(l))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fed041ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "# List converted to array\n",
    "arr = np.asarray(l)\n",
    "\n",
    "# Print and Check type\n",
    "print(arr)\n",
    "print(type(arr))\n",
    "\n",
    "# Check Shape\n",
    "print(\"Array Shape: \",arr.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f8137d0a",
   "metadata": {},
   "source": [
    "As lists can be of undefined shape, it sometime creates problem when converted to the list. The problem is not faced by the program for conversion, but more while accessing data from indexes. You might have to define it to store it as an object type data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "866f6422",
   "metadata": {},
   "outputs": [],
   "source": [
    "# List\n",
    "l = [[1,2,3],[4]]\n",
    "\n",
    "# Print and Check type\n",
    "print(l)\n",
    "print(type(l))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "969ced1c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# List converted to array\n",
    "arr = np.asarray(l)\n",
    "\n",
    "# Print and Check type\n",
    "print(arr)\n",
    "print(type(arr))\n",
    "\n",
    "# Check Shape\n",
    "print(\"Array Shape: \",arr.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d953a0f3",
   "metadata": {},
   "source": [
    "## Search index of your desired variable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7c043f28",
   "metadata": {},
   "outputs": [],
   "source": [
    "arr = np.array([\n",
    "        [1, 2, 3, 4],\n",
    "        [5, 6, 7, 8],\n",
    "        [9, 10, 11, 12]\n",
    "        ])\n",
    "\n",
    "# Find the index\n",
    "idx= np.where(arr==11)\n",
    "print(idx)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d20535b",
   "metadata": {},
   "source": [
    "# <a id=\"sec:functions\">9. Functions</a>\n",
    "Most important part of any language. It is a code block that is callable and reusable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "638e9c10",
   "metadata": {},
   "outputs": [],
   "source": [
    "def function_for_multiply(m1,m2):\n",
    "    return m1.dot(m2)\n",
    "\n",
    "m1 = np.random.rand(50,30)\n",
    "m2 = np.random.rand(30,500)\n",
    "\n",
    "m = function_for_multiply(m1,m2)\n",
    "print(m.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6774c704",
   "metadata": {},
   "source": [
    "## Improving the Computational Times with Numba"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6e67f2d",
   "metadata": {},
   "source": [
    "Numba is a just-in-time compiler for Python that works best on code that uses NumPy arrays and functions, and loops.\n",
    "\n",
    "It is an additional library that makes your code faster. It can make it very fast. But it depends on your code. \n",
    "\n",
    "Numba is suited for codes that have:\n",
    "- Loops\n",
    "- Numpy functions\n",
    "\n",
    "More information on Numba can be found [here](https://numba.pydata.org/numba-doc/latest/index.html)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22b0f024",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install numba\n",
    "%pip install numba"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ea21f6d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numba as nb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f250a3d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "def std(xs):\n",
    "  # compute the mean\n",
    "  mean = 0\n",
    "  for x in xs: \n",
    "    mean += x\n",
    "  mean /= len(xs)\n",
    "  # compute the variance\n",
    "  ms = 0\n",
    "  for x in xs:\n",
    "     ms += (x-mean)**2\n",
    "  variance = ms / len(xs)\n",
    "  std = math.sqrt(variance)\n",
    "  return std"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a8da7e65",
   "metadata": {},
   "outputs": [],
   "source": [
    "a = np.random.normal(0, 1, 10000000)\n",
    "std(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bb4a64f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "c_std = nb.njit(std,fastmath=True)\n",
    "c_std(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37f4030f",
   "metadata": {},
   "outputs": [],
   "source": [
    "%timeit std(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40ff100a",
   "metadata": {},
   "outputs": [],
   "source": [
    "%timeit c_std(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c27f8c0d",
   "metadata": {},
   "source": [
    "# <a id=\"sec:classes\">10. Classes & Object Oriented Programming</a>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "718e6f20",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-success\">\n",
    "Python is an object oriented programming language. Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by their class) for modifying their state.\n",
    "</div>\n",
    "\n",
    "For more information on Object Oriented Programming, check out this [video](https://www.youtube.com/watch?v=-DP1i2ZU9gk)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6baa3dc1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define a Class\n",
    "class Student:\n",
    "    # Attributes\n",
    "    name = \"Vedant\"\n",
    "    matr = 1997\n",
    "    \n",
    "    # Method\n",
    "    def is_first_letter(self):\n",
    "        if self.name[0] == \"V\":\n",
    "            print(\"The name starts with the letter V\")\n",
    "        else:\n",
    "            print(\"The name does not starts with the letter V\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "71a58bcd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Object Instantiation\n",
    "student_1 = Student()\n",
    "\n",
    "# Access Class Attributes\n",
    "print(\"The name of the Student is :\", student_1.name)\n",
    "\n",
    "# Call the function from the class\n",
    "student_1.is_first_letter()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6fcd77c7",
   "metadata": {},
   "source": [
    "## Self and Initializer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dc2a404",
   "metadata": {},
   "source": [
    "The classes have two very important aspects to consider:\n",
    "\n",
    "### The self\n",
    "\n",
    "This keyword is used to represent the instance of the given class. It allows the user to access the attributes and methods of each object in python. It is defined explicitly eveytime. \n",
    "\n",
    "The example above also uses self when it defines attributes and as the first parameter of the method(function). If we have a method that takes no arguments, we still have one argument. For more details, check [this](https://www.programiz.com/article/python-self-why#:~:text=The%20self%20keyword%20is%20used,information%20for%20both%20these%20objects.) out.\n",
    "\n",
    "### \\_\\_init\\_\\_ method\n",
    "\\_\\_init\\_\\_ is called when a new object is requested. The function \\_\\_init\\_\\_ is called immediately after the object is created and is used to initialize it. It is supposed to use its arguments to assign attributes on the new object, such that the required invariants for normal operation of the object are set up. The object is already a valid pre-existing place to store attributes by the time the code in \\_\\_init\\_\\_ begins running. \n",
    "\n",
    "Is \\_\\_init\\_\\_ is a Constructor or not? You will always find people confused regarding this. You can find a good answer [here](https://stackoverflow.com/questions/6578487/init-as-a-constructor).\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0bc93023",
   "metadata": {},
   "source": [
    "## Class and Instance Variables\n",
    "Class Variables are constant along all the instances.\n",
    "\n",
    "Instance variables are not constant and uniquely defined across all the instances of the class.\n",
    "\n",
    "In the above example, name and matriculation number were Class variables as they remain constant no matter how many instances we define. But what if we want to include names and matriculation numbers for multiple People with different positions?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7043a1e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define a Class\n",
    "class People:\n",
    "    # Class Variables\n",
    "    department = \"Cyber-Physical-Systems\"\n",
    "    \n",
    "    def __init__(self, name, matr, position):\n",
    "        self.name = name\n",
    "        self.matr = matr\n",
    "        self.position = position\n",
    "    \n",
    "    # Method\n",
    "    def is_first_letter(self):\n",
    "        if self.name[0] == \"V\":\n",
    "            print(\"The name starts with the letter V\")\n",
    "        else:\n",
    "            print(\"The name does not starts with the letter V\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f63cb6fb",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Instantiate Object for first instance\n",
    "person_1 = People(\"Vedant\", 1997, \"Student\")\n",
    "\n",
    "# Instantiate Object for second instance\n",
    "person_2 = People(\"Elmar\", \"None\", \"Professor\")\n",
    "\n",
    "# Instantiate Object for third instance\n",
    "person_3 = People(\"Konrad\", \"None\", \"Lab Technician\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "872fbc38",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Access Attributes of both instances\n",
    "# First Person\n",
    "print(\"Name of Person 1: \", person_1.name)\n",
    "print(\"Matriculation Number of Person 1: \", person_1.matr)\n",
    "print(\"Position of Person 1: \", person_1.position)\n",
    "print(\"Department of Person 1: \", person_1.department)\n",
    "person_1.is_first_letter()\n",
    "print(\"\\n\")\n",
    "\n",
    "# Second Person\n",
    "print(\"Name of Person 2: \", person_2.name)\n",
    "print(\"Matriculation Number of Person 2: \", person_2.matr)\n",
    "print(\"Position of Person 2: \", person_2.position)\n",
    "print(\"Department of Person 2: \", person_2.department)\n",
    "person_2.is_first_letter()\n",
    "print(\"\\n\")\n",
    "\n",
    "# Third Person\n",
    "print(\"Name of Person 3: \", person_3.name)\n",
    "print(\"Matriculation Number of Person 3: \", person_3.matr)\n",
    "print(\"Position of Person 3: \", person_3.position)\n",
    "print(\"Department of Person 3: \", person_3.department)\n",
    "person_3.is_first_letter()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5304cc8d",
   "metadata": {},
   "source": [
    "## Inheritance\n",
    "One class can inherit the functionalities of another class. It makes the object utilise multiple functionalities from different classes.\n",
    "\n",
    "The class which inherits the functionalities is called as Child/Sub Class while the other one is called as Parent/Super Class."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8dc070f5",
   "metadata": {},
   "source": [
    "### Single Inheritance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f024880",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define a Class\n",
    "class Vehicle(object):\n",
    "    def __init__(self, weight):\n",
    "        self.weight = weight\n",
    "        \n",
    "    def is_heavy_duty(self):\n",
    "        yes = True if self.weight > 10000 else False\n",
    "        return yes\n",
    "    \n",
    "class Car(Vehicle):\n",
    "    def is_heavy_duty(self):\n",
    "        return False"
   ]
  },
  {
   "attachments": {
    "Screenshot%20from%202022-10-04%2017-30-00.png": {
     "image/png": "[encoded data, 31296 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "d4441343",
   "metadata": {},
   "source": [
    "![Screenshot%20from%202022-10-04%2017-30-00.png](attachment:Screenshot%20from%202022-10-04%2017-30-00.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91f874c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Instantiate Object for Vehicle Class\n",
    "automobile = Vehicle(15000)\n",
    "print(\"Is this a heavy duty vehicle?: \", automobile.is_heavy_duty())\n",
    "\n",
    "# Instantiate Object for Car Class\n",
    "car = Car(1500)\n",
    "print(\"Is this a heavy duty vehicle?: \", car.is_heavy_duty())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2248972",
   "metadata": {},
   "source": [
    "### super() method\n",
    "The method super() has two major uses in Python:\n",
    "- Allows us to avoid using the base class name explicitly\n",
    "- Working with Multiple Inheritance\n",
    "\n",
    "The above example can be constructed using super() method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25acdc1c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define a Class\n",
    "class Vehicle:\n",
    "    def __init__(self, weight):\n",
    "        self.weight = weight\n",
    "        \n",
    "    def is_heavy_duty(self):\n",
    "        yes = True if self.weight > 10000 else False\n",
    "        return yes\n",
    "    \n",
    "class Car(Vehicle):\n",
    "    def __init__(self, weight):\n",
    "        super().__init__(weight)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2aec32ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Instantiate Object for Car Class\n",
    "car = Car(15000)\n",
    "print(\"Is this a heavy duty vehicle?: \", car.is_heavy_duty())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee9aa30e",
   "metadata": {},
   "source": [
    "### Multiple Inheritance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2b05446e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define Parent/Super Classes\n",
    "class Vehicle:      \n",
    "    def is_this_vehicle(self):\n",
    "        print(\"Yes, this is a Vehicle\")\n",
    "        \n",
    "class OnRoad:\n",
    "    def __init__(self,name):\n",
    "        self.name = name\n",
    "        \n",
    "    def is_onroad(self):\n",
    "        yes = \"It is an On-Road Vehicle\" if self.name == \"Car\" else \"It is not an On-Road Vehicle\"\n",
    "        print(yes)\n",
    "        \n",
    "\n",
    "class Public:\n",
    "    def is_public(self):\n",
    "        print(\"No, it is not a Public vehicle\")\n",
    "        \n",
    "        \n",
    "# Define Child/Sub Class\n",
    "class What_is_it(Vehicle, OnRoad, Public):\n",
    "    def __init__(self):\n",
    "        super().__init__('Car')"
   ]
  },
  {
   "attachments": {
    "Screenshot%20from%202022-10-04%2019-07-03.png": {
     "image/png": "[encoded data, 38792 characters]"
    }
   },
   "cell_type": "markdown",
   "id": "acee28b4",
   "metadata": {},
   "source": [
    "![Screenshot%20from%202022-10-04%2019-07-03.png](attachment:Screenshot%20from%202022-10-04%2019-07-03.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13339858",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Instantiate Object\n",
    "thing = What_is_it()\n",
    "thing.is_this_vehicle()\n",
    "thing.is_onroad()\n",
    "thing.is_public()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b34b85b",
   "metadata": {},
   "source": [
    "For more information on inheritance, you can see this [video](https://www.youtube.com/watch?v=FlGjISF3l78)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08705e5c",
   "metadata": {},
   "source": [
    "# <a id=\"sec:plotting\">11. Plotting & Scientific Figures</a>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "828e486b-2c9d-470f-9af3-bbfa562ae37a",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-success\">\n",
    "A major reason for the popularity of Python is the simplicity of creating art work. The following instructions are important to generate figures of sufficient quality for our courses.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76ff568d-822a-4b61-96c6-9221fbd79446",
   "metadata": {},
   "source": [
    "- Make sure that all axis have labels. \n",
    "- Always add a legend (images or tables may be exceptions).\n",
    "- Use different line colors, e.g., via matplotlib.colors import LinearSegmentedColormap, ListedColormap.\n",
    "- Use a minimum line width of $2$ and different line styles. \n",
    "- The font size of text in figures should be equal to the figure caption font size.\n",
    "- The caption of a figure needs to be self-explaining. All major elements in a figure need to be defined.\n",
    "- remove bounding boxes of legends and of the graph if not needed.\n",
    "\n",
    "Below we provide a variaity of simple and best practice examples of figures. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c5f45e1d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install matplotlib\n",
    "%pip install matplotlib"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c301f605-c228-4128-90fb-e7b0cdacf000",
   "metadata": {},
   "source": [
    "## Simple Figures"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3a93c736-0158-4256-a238-021555eb8aa4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install matplotlib\n",
    "%pip install matplotlib"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e39839c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24a69812",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Random points\n",
    "x = np.linspace(0,1,200)\n",
    "y = x**3 - x**2 - 1\n",
    "\n",
    "# Plot points\n",
    "fig = plt.Figure()\n",
    "plt.plot(x,y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "107b9050",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Scatter Plot\n",
    "fig = plt.Figure()\n",
    "plt.scatter(x,y)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ceda083-b334-40d2-9eab-e44899f5e71b",
   "metadata": {},
   "source": [
    "## Best Practice Examples of Figures"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7447a9b2-60ca-45fd-a44e-507041ecf49e",
   "metadata": {},
   "source": [
    "### Timeseries Data Plots"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "271930b0-3976-4a1e-9572-72ffba07975b",
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "!pip3 install scikit-learn\n",
    "\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "# Hide the right and top spines\n",
    "plt.rcParams['axes.spines.right'] = False\n",
    "plt.rcParams['axes.spines.top'] = False\n",
    "\n",
    "X = np.linspace(start=0, stop=10, num=1_000).reshape(-1, 1)\n",
    "y = np.squeeze(X * np.sin(X))\n",
    "rng = np.random.RandomState(1)\n",
    "\n",
    "training_indices = rng.choice(np.arange(y.size), size=6, replace=False)\n",
    "X_train, y_train = X[training_indices], y[training_indices]\n",
    "\n",
    "import sklearn as sk\n",
    "from sklearn.gaussian_process import GaussianProcessRegressor\n",
    "from sklearn.gaussian_process.kernels import RBF\n",
    "\n",
    "kernel = 1 * RBF(length_scale=1.0, length_scale_bounds=(1e-2, 1e2))\n",
    "gaussian_process = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=9)\n",
    "gaussian_process.fit(X_train, y_train)\n",
    "gaussian_process.kernel_\n",
    "mean_prediction, std_prediction = gaussian_process.predict(X, return_std=True)\n",
    "\n",
    "plt.plot(X, y, label=r\"$f(x) = x \\sin(x)$\", linestyle=\"dotted\")\n",
    "plt.scatter(X_train, y_train, label=\"Observations\")\n",
    "plt.plot(X, mean_prediction, label=\"Mean prediction\")\n",
    "plt.fill_between(\n",
    "    X.ravel(),\n",
    "    mean_prediction - 1.96 * std_prediction,\n",
    "    mean_prediction + 1.96 * std_prediction,\n",
    "    alpha=0.5,\n",
    "    label=r\"95% confidence interval\",\n",
    ")\n",
    "plt.legend(frameon=False, loc='lower right')\n",
    "plt.xlabel(\"$x$\")\n",
    "plt.ylabel(\"$f(x)$\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47dec937-83d3-407a-a181-d1c2375524ea",
   "metadata": {},
   "source": [
    "## Bar Plots"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a287d15f-09fa-4286-a343-a717230441d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# creating the dataset\n",
    "data = {'C         ':20, 'C++    ':15, 'Java     ':30,\n",
    "        'Python ':35}\n",
    "data_std = {'C':2, 'C++':2.5, 'Java':2,\n",
    "        'Python':4}\n",
    "courses = list(data.keys())\n",
    "values = list(data.values())\n",
    "stds = list(data_std.values())\n",
    "labels = []\n",
    "for i in range(len(courses)):\n",
    "    label = courses[i] + ' ' + str(values[i]) + '+/-' + str(stds[i])\n",
    "    labels.append(label)\n",
    "\n",
    "fig = plt.figure(figsize = (10, 5))\n",
    "\n",
    "my_cmap = plt.get_cmap(\"Pastel2\")\n",
    "rescale = lambda values: (values - np.min(values)) / (np.max(values) - np.min(values))\n",
    "\n",
    "plt.bar(courses, values, yerr=stds, capsize=3, color=my_cmap(rescale(values)), label=labels)\n",
    "\n",
    " \n",
    "plt.xlabel(\"Courses offered\")\n",
    "plt.ylabel(\"No. of students enrolled\")\n",
    "\n",
    "plt.legend(frameon=False, loc='upper left')\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "85849e6c",
   "metadata": {},
   "source": [
    "# <a id=\"sec:resources\">12. Advanced Features & Other Resources </a>\n",
    "- [Exceptions](https://realpython.com/python-exceptions/)\n",
    "- [Lambda Function](https://towardsdatascience.com/lambda-functions-with-practical-examples-in-python-45934f3653a8)\n",
    "- [Maps](https://realpython.com/python-map-function/)\n",
    "- [Filters](https://www.programiz.com/python-programming/methods/built-in/filter)\n",
    "- [Reduce](https://www.geeksforgeeks.org/reduce-in-python/)\n",
    "- [Decorators](https://www.programiz.com/python-programming/decorator)\n",
    "- [Generators](https://realpython.com/introduction-to-python-generators/)\n",
    "- [Itertools Module](https://www.geeksforgeeks.org/python-itertools/)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dda1742d",
   "metadata": {},
   "source": [
    "## Other Important Libraries to Study"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9190d459",
   "metadata": {},
   "source": [
    "- [Pandas](https://pandas.pydata.org/docs/index.html) : File Handling\n",
    "- [Matplotlib](https://matplotlib.org/stable/index.html): Plotting Graphs\n",
    "- [Scipy](https://scipy.org/) : Scientific Computation\n",
    "- [Scikit-learn](https://scikit-learn.org/stable/) : Machine Learning Algorithms"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f295187",
   "metadata": {},
   "source": [
    "## Related Tutorials:\n",
    "- Reading Tutorials: https://www.tutorialspoint.com/python/index.htm\n",
    "- Video Tutorials: https://www.youtube.com/watch?v=QXeEoD0pB3E&list=PLsyeobzWxl7poL9JTVyndKe62ieoN-MZ3&index=1\n",
    "- Python Documentations: https://docs.python.org/3/tutorial/"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3145f451-18a1-48a0-9189-8a58cf52b204",
   "metadata": {},
   "source": [
    "# <a id=\"sec:llms\">13. Using Large Language Models like ChatGPT </a>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f98786bc-a9d5-4e4a-b7b8-00cd807c4d9b",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-success\">\n",
    "Large Langue Models like ChatGPT are great tools to speedup your learning curve and productivity. Yopu are encuraged to use such tools but be aware that you need to adapt the generated code to fulfill our requirements (Comments and Clean Coding Priniples, Encapsulation into Classes, Figure requirements, and many more). Solely using the code generator results will not improve your skills nor will you pass our courses.   \n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ca729cc-204f-4742-b593-02f6b381212a",
   "metadata": {},
   "source": [
    "- Find here a detailed tutorial on how to use ChatGPT for coding: https://realpython.com/chatgpt-coding-mentor-python/. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42616c52-bc16-45e8-93c3-26a648ff9566",
   "metadata": {},
   "source": [
    "## Example Query for generating Plots\n",
    "\n",
    "### User: \n",
    "Hi, I am learning to program in Python. Can you show me a best practice example of a scientific bar plot with different colors and error bars?\n",
    "\n",
    "### ChatGPT: \n",
    "Certainly! Creating a scientific bar plot with different colors and error bars in Python can be accomplished using libraries like Matplotlib and NumPy. Here's an example that demonstrates best practices for creating such a plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "902fe7f4-7dfd-47be-a883-721893bdbe72",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# Sample data\n",
    "categories = ['Category A', 'Category B', 'Category C', 'Category D']\n",
    "values = [10, 15, 12, 8]\n",
    "errors = [1, 2, 1.5, 1.2]\n",
    "\n",
    "# Define custom colors for the bars\n",
    "colors = ['skyblue', 'salmon', 'lightgreen', 'lightcoral']\n",
    "\n",
    "# Create a figure and axis\n",
    "fig, ax = plt.subplots()\n",
    "\n",
    "# Plot the bars with error bars\n",
    "bars = ax.bar(categories, values, yerr=errors, capsize=5, color=colors)\n",
    "\n",
    "# Add labels and a title\n",
    "ax.set_xlabel('Categories')\n",
    "ax.set_ylabel('Values')\n",
    "ax.set_title('Scientific Bar Plot with Error Bars')\n",
    "\n",
    "# Customize the appearance\n",
    "ax.set_ylim(0, max(values) + max(errors) + 1)\n",
    "ax.grid(axis='y', linestyle='--', alpha=0.7)\n",
    "ax.set_axisbelow(True)\n",
    "\n",
    "# Add data labels above the bars\n",
    "for bar, value, error in zip(bars, values, errors):\n",
    "    ax.text(\n",
    "        bar.get_x() + bar.get_width() / 2,\n",
    "        bar.get_height() + 0.5,\n",
    "        f'{value:.1f} ± {error:.1f}',\n",
    "        ha='center',\n",
    "        va='bottom',\n",
    "        fontsize=10,\n",
    "    )\n",
    "\n",
    "# Show the plot\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23718836-6f0f-4e75-8ec2-65f466c54eda",
   "metadata": {},
   "source": [
    "### Explainations of ChatGPT:\n",
    "In this example:\n",
    "We import the necessary libraries, Matplotlib and NumPy.\n",
    "We define sample data for categories, values, and errors. You should replace this with your own data.\n",
    "\n",
    "Custom colors are specified for each bar in the colors list.\n",
    "We create a Matplotlib figure and axis.\n",
    "\n",
    "The bar function is used to plot the bars, and the yerr parameter is used to specify error bars. The capsize parameter determines the size of the caps at the end of the error bars.\n",
    "\n",
    "Labels and a title are added to the plot.\n",
    "\n",
    "We customize the appearance of the plot, including setting the y-axis limit, adding a grid, and ensuring the grid is below the bars.\n",
    "\n",
    "Data labels are added above each bar to display the values along with their corresponding errors.\n",
    "\n",
    "Finally, we display the plot using plt.show().\n",
    "\n",
    "You can modify the categories, values, errors, and colors lists to customize the plot for your specific data. This example follows best practices for creating a scientific bar plot with error bars in Python."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00826db7-b1c7-4678-b445-76cd4da6258d",
   "metadata": {},
   "source": [
    "### User:\n",
    "How can I add a legend to the bar plot?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5db9d40c-2cb3-4606-b03f-dcb8be0a983f",
   "metadata": {},
   "source": [
    "### ChatGPT:\n",
    "\n",
    "In this modified code:\n",
    "\n",
    "We define a list of labels for each bar, corresponding to the legend entries.\n",
    "\n",
    "Inside the loop that plots the bars, we use the label parameter to assign a label to each bar.\n",
    "\n",
    "After plotting all the bars, we add the legend using ax.legend().\n",
    "\n",
    "Now, when you run the code, you will have a legend that indicates which color corresponds to each experiment or category. You can customize the labels list to match your data and legend entries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58157f0d-8f63-489f-9e4e-e1eafc3cb802",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "# Sample data\n",
    "categories = ['Category A', 'Category B', 'Category C', 'Category D']\n",
    "values = [10, 15, 12, 8]\n",
    "errors = [1, 2, 1.5, 1.2]\n",
    "\n",
    "# Define custom colors for the bars\n",
    "colors = ['skyblue', 'salmon', 'lightgreen', 'lightcoral']\n",
    "\n",
    "# Define labels for the legend\n",
    "labels = ['Experiment 1', 'Experiment 2', 'Experiment 3', 'Experiment 4']\n",
    "\n",
    "# Create a figure and axis\n",
    "fig, ax = plt.subplots()\n",
    "\n",
    "# Plot the bars with error bars and labels for the legend\n",
    "bars = []\n",
    "for i in range(len(categories)):\n",
    "    bar = ax.bar(categories[i], values[i], yerr=errors[i], capsize=5, color=colors[i], label=labels[i])\n",
    "    bars.append(bar)\n",
    "\n",
    "# Add labels and a title\n",
    "ax.set_xlabel('Categories')\n",
    "ax.set_ylabel('Values')\n",
    "ax.set_title('Scientific Bar Plot with Error Bars')\n",
    "\n",
    "# Customize the appearance\n",
    "ax.set_ylim(0, max(values) + max(errors) + 1)\n",
    "ax.grid(axis='y', linestyle='--', alpha=0.7)\n",
    "ax.set_axisbelow(True)\n",
    "\n",
    "# Add data labels above the bars\n",
    "for bar, value, error in zip(bars, values, errors):\n",
    "    ax.text(\n",
    "        bar[0].get_x() + bar[0].get_width() / 2,\n",
    "        bar[0].get_height() + 0.5,\n",
    "        f'{value:.1f} ± {error:.1f}',\n",
    "        ha='center',\n",
    "        va='bottom',\n",
    "        fontsize=10,\n",
    "    )\n",
    "\n",
    "# Add the legend\n",
    "ax.legend()\n",
    "\n",
    "# Show the plot\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bed4db74-7263-495a-954a-3b9794f53940",
   "metadata": {},
   "source": [
    "### Other Queries\n",
    "\n",
    "- 'How can I concatenate list elements like courses[i] with strings like '+/-'?' results in "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dddd39d0-51f3-4d4e-83ca-a9294c2153f0",
   "metadata": {},
   "source": [
    "#### concatenate list elements like courses[i] with strings like '+/-' in Python, you can use string formatting or string concatenation. Here are two common methods to achieve this:\n",
    "\n",
    "* Using f-strings (formatted string literals):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f0bb6322-18f4-4cd6-a85d-3fdd6d44161d",
   "metadata": {},
   "outputs": [],
   "source": [
    "courses = ['Math', 'Physics', 'Chemistry']\n",
    "grades = [90, 85, 88]\n",
    "\n",
    "for i in range(len(courses)):\n",
    "    result = f'{courses[i]} +/- {grades[i]}'\n",
    "    print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cfea6f57-1c73-41db-a69b-374bc99000b8",
   "metadata": {},
   "source": [
    "- Using string concatenation with the + operator:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "388e2fd7-8f5e-4124-aba6-cf00412a2fb1",
   "metadata": {},
   "outputs": [],
   "source": [
    "courses = ['Math', 'Physics', 'Chemistry']\n",
    "grades = [90, 85, 88]\n",
    "\n",
    "for i in range(len(courses)):\n",
    "    result = courses[i] + ' +/- ' + str(grades[i])\n",
    "    print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45bdf290-e5a4-47fd-b678-673be7974d2c",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.11.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
