## Data-driven Design and Analyses of Structures and Materials (3dasm)

## Lecture 1

### Miguel A. Bessa | M.A.Bessa@tudelft.nl  | Associate Professor

## Introduction

**What:** A lecture of the "3dasm" course

**Where:** This notebook comes from this [repository](https://github.com/bessagroup/3dasm_course)

**Reference for entire course:** Murphy, Kevin P. *Probabilistic machine learning: an introduction*. MIT press, 2022. Available online [here](https://probml.github.io/pml-book/book1.html)

**How:** We try to follow Murphy's book closely, but the sequence of Chapters and Sections is different. The intention is to use notebooks as an introduction to the topic and Murphy's book as a resource.
* If working offline: Go through this notebook and read the book.
* If attending class in person: listen to me (!) but also go through the notebook in your laptop at the same time. Read the book.
* If attending lectures remotely: listen to me (!) via Zoom and (ideally) use two screens where you have the notebook open in 1 screen and you see the lectures on the other. Read the book.

**Optional reference (the "bible" by the "bishop"... pun intended 😆) :** Bishop, Christopher M. *Pattern recognition and machine learning*. Springer Verlag, 2006.

**References/resources to create this notebook:**
* [Figure (Car stopping distance)](https://korkortonline.se/en/theory/reaction-braking-stopping/)
* Snippets of code from this awesome [repo](https://github.com/gerdm/prml) by Gerardo Duran-Martin that replicates many figures in Bishop's book

Apologies in advance if I missed some reference used in this notebook. Please contact me if that is the case, and I will gladly include it here.

## **OPTION 1**. Run this notebook **locally in your computer**:
1. Install miniconda3 [here](https://docs.conda.io/en/latest/miniconda.html)
2. Open a command window and create a virtual environment called "3dasm":
```
conda create -n 3dasm python=3 numpy scipy jupyter nb_conda matplotlib pandas scikit-learn rise tensorflow -c conda-forge
```
3. Install [git](https://github.com/git-guides/install-git), open command window & clone the repository to your computer:
```
git clone https://github.com/bessagroup/3dasm_course
```
4. Load jupyter notebook by typing in (anaconda) command window (it will open in your internet browser):
```
conda activate 3dasm
jupyter notebook
```
5. Open notebook (3dasm_course/Lectures/Lecture1/3dasm_Lecture1.ipynb)

**Short note:** My personal environment also has other packages that help me while teaching.

> conda install -n 3dasm -c conda-forge jupyter_contrib_nbextensions hide_code

Then in the 3dasm conda environment:

> jupyter nbextension install --py hide_code --sys-prefix
>
> jupyter nbextension enable --py hide_code
>
> jupyter serverextension enable --py hide_code
>
> jupyter nbextension enable splitcell/splitcell

## **OPTION 2**. Use **Google's Colab** (no installation required, but times out if idle):

1. go to https://colab.research.google.com
2. login
3. File > Open notebook
4. click on Github (no need to login or authorize anything)
5. paste the git link: https://github.com/bessagroup/3dasm_course
6. click search and then click on the notebook (*3dasm_course/Lectures/Lecture1/3dasm_Lecture1.ipynb*)

```python
# Basic plotting tools needed in Python.

import matplotlib.pyplot as plt # import plotting tools to create figures
import numpy as np # import numpy to handle a lot of things!

%config InlineBackend.figure_format = "retina" # render higher resolution images in the notebook
plt.style.use("seaborn") # style for plotting that comes from seaborn
plt.rcParams["figure.figsize"] = (8,4) # rescale figure size appropriately for slides
```

## Outline for today

* Introduction
    - Taking a probabilistic perspective on machine learning
* Basics of univariate statistics
    - Continuous random variables
    - Probabilities vs probability densities
    - Moments of a probability distribution
* The mindblowing Bayes' rule
    - The rule that spawns almost every ML model (even when we don't realize it)

**Reading material**: This notebook + Chapter 2 until Section 2.3

## Get hyped about Artificial Intelligence...

```python
from IPython.display import display, YouTubeVideo, HTML
YouTubeVideo('RNnZwvklwa8', width=512, height=288) # show that slides are interactive:
                                                   # rescale video to 768x432 and back to 512x288
```

**Well...** This class *might* not make you break the world (yet!). Let's focus on the fundamentals:

* Probabilistic perspective on machine learning
* Supervised learning (especially regression)

## Machine learning (ML)

* **ML definition**: A computer program that learns from experience $E$ wrt tasks $T$ such that the performance $P$ at those tasks improves with experience $E$.

* We'll treat ML from a **probabilistic perspective**:
    - Treat all unknown quantities as **random variables**

* What are random variables?
    - Variables endowed with probability distributions!

## The car stopping distance problem

Car stopping distance ${\color{red}y}$ as a function of its velocity ${\color{green}x}$ before it starts braking:

${\color{red}y} = {\color{blue}z} x + \frac{1}{2\mu g} {\color{green}x}^2 = {\color{blue}z} x + 0.1 {\color{green}x}^2$

- ${\color{blue}z}$ is the driver's reaction time (in seconds)
- $\mu$ is the road/tires coefficient of friction (assume $\mu=0.5$)
- $g$ is the acceleration of gravity (assume $g=10$ m/s$^2$).

## The car stopping distance problem

### How to obtain this formula?

$y = d_r + d_{b}$

where $d_r$ is the reaction distance, and $d_b$ is the braking distance.

### Reaction distance $d_r$

$d_r = z x$

with $z$ being the driver's reaction time, and $x$ being the velocity of the car at the start of braking.

## The car stopping distance problem

### Braking distance $d_b$

Kinetic energy of moving car:

$E = \frac{1}{2}m x^2$       where $m$ is the car mass.

Work done by braking:

$W = \mu m g d_b$       where $\mu$ is the coefficient of friction between the road and the tire, $g$ is the acceleration of gravity, and $d_b$ is the car braking distance.

The braking distance follows from $E=W$:

$d_b = \frac{1}{2\mu g}x^2$

Therefore, if we add the reacting distance $d_r$ to the braking distance $d_b$ we get the stopping distance $y$:

$$y = d_r + d_b = z x + \frac{1}{2\mu g} x^2$$

## The car stopping distance problem

$y = {\color{blue}z} x + 0.1 x^2$

The driver's reaction time ${\color{blue}z}$ is a **random variable (rv)**

* Every driver has its own reaction time $z$

* Assume the distribution associated to $z$ is Gaussian with **mean** $\mu_z=1.5$ seconds and **variance** $\sigma_z^2=0.5$ seconds$^2$

$$
z \sim \mathcal{N}(\mu_z=1.5,\sigma_z^2=0.5^2)
$$

where $\sim$ means "sampled from", and $\mathcal{N}$ indicates a Gaussian **probability density function (pdf)**

## Univariate Gaussian pdf

The gaussian pdf is defined as:

$$
    \mathcal{N}(z | \mu_z, \sigma_z^2) = \frac{1}{\sqrt{2\pi\sigma_z^2}}e^{-\frac{1}{2\sigma_z^2}(z - \mu_z)^2}
$$

Alternatively, we can write it using the **precision** term $\lambda_z := 1 / \sigma_z^2$ instead of using $\sigma_z^2$:

$$
    \mathcal{N}(z | \mu_z, \lambda_z^{-1}) = \frac{\lambda_z^{1/2}}{\sqrt{2\pi}}e^{-\frac{\lambda_z}{2}(z - \mu_z)^2}
$$

Anyway, recall how this pdf looks like...

```python
def norm_pdf(z, mu_z, sigma_z2): return 1 / np.sqrt(2 * np.pi * sigma_z2) * np.exp(-(z - mu_z)**2 / (2 * sigma_z2))
zrange = np.linspace(-8, 4, 200) # create a list of 200 z points between z=-8 and z=4
fig, ax = plt.subplots() # create a plot
ax.plot(zrange, norm_pdf(zrange, 0, 1), label=r"$\mu_z=0; \ \sigma_z^2=1$") # plot norm_pdf(z|0,1)
ax.plot(zrange, norm_pdf(zrange, 1.5, 0.5**2), label=r"$\mu_z=1.5; \ \sigma_z^2=0.5^2$") # plot norm_pdf(z|1.5,0.5^2)
ax.plot(zrange, norm_pdf(zrange, -1, 2**2), label=r"$\mu_z=-1; \ \sigma_z^2=2^2$") # plot norm_pdf(z|-1,2^2)
ax.set_xlabel("z", fontsize=20) # create x-axis label with font size 20
ax.set_ylabel("probability density", fontsize=20) # create y-axis label with font size 20
ax.legend(fontsize=15) # create legend with font size 15
ax.set_title("Three different Gaussian pdfs", fontsize=20); # create title with font size 20
```

The green curve shows the Gaussian pdf of the rv $z$ **conditioned** on the mean $\mu_z=1.5$ and variance $\sigma_z^2=0.5^2$ for the car stopping distance problem.

## Univariate Gaussian pdf

$$
    p(z) = \mathcal{N}(z | \mu_z, \sigma_z^2) = \frac{1}{\sqrt{2\pi\sigma_z^2}}e^{-\frac{1}{2\sigma_z^2}(z - \mu_z)^2}
$$

The output of this expression is the **PROBABILITY DENSITY** of $z$ **given** (or conditioned to) a particular $\mu_z$ and $\sigma_z^2$.

* **Important**: Probability Density $\neq$ Probability

So, what is a probability?

## Probability

The probability of an event $A$ is denoted by $\text{Pr}(A)$.

* $\text{Pr}(A)$ means the probability with which we believe event A is true

* An event $A$ is a binary variable saying whether or not some state of the world holds.

Probability is defined such that: $0 \leq \text{Pr}(A) \leq 1$

where $\text{Pr}(A)=1$ if the event will definitely happen and $\text{Pr}(A)=0$ if it definitely will not happen.

## Joint probability

**Joint probability** of two events: $\text{Pr}(A \wedge B)= \text{Pr}(A, B)$

If $A$ and $B$ are **independent**: $\text{Pr}(A, B)= \text{Pr}(A) \text{Pr}(B)$

For example, suppose $z_1$ and $z_2$ are chosen uniformly at random from the set $\mathcal{Z} = \{1, 2, 3, 4\}$.

Let $A$ be the event that $z_1 \in \{1, 2\}$ and $B$ be the event that **another** rv denoted as $z_2 \in \{3\}$.

Then we have: $\text{Pr}(A, B) = \text{Pr}(A) \text{Pr}(B) = \frac{1}{2} \cdot \frac{1}{4}$.

## Probability of a union of two events

Probability of event $A$ or $B$ happening is: $\text{Pr}(A \vee B)= \text{Pr}(A) + \text{Pr}(B) - \text{Pr}(A \wedge B)$

If these events are mutually exclusive (they can't happen at the same time):

$$
\text{Pr}(A \vee B)= \text{Pr}(A) + \text{Pr}(B)
$$

For example, suppose an rv denoted as $z_1$ is chosen uniformly at random from the set $\mathcal{Z} = \{1, 2, 3, 4\}$.

Let $A$ be the event that $z_1 \in \{1, 2\}$ and $B$ be the event that the **same** rv $z_1 \in \{3\}$.

Then we have $\text{Pr}(A \vee B) = \frac{2}{4} + \frac{1}{4}$.

## Conditional probability of one event given another

We define the **conditional probability** of event $B$ happening given that $A$ has occurred as follows:

$$
\text{Pr}(B | A)= \frac{\text{Pr}(A,B)}{\text{Pr}(A)}
$$

This is not defined if $\text{Pr}(A) = 0$, since we cannot condition on an impossible event.

## Conditional independence of one event given another

We say that event $A$ is conditionally independent of event $B$ if we have $\text{Pr}(A | B)= \text{Pr}(A)$

This implies $\text{Pr}(B|A) = \text{Pr}(B)$. Hence, the joint probability becomes $\text{Pr}(A, B) = \text{Pr}(A) \text{Pr}(B)$

The book uses the notation $A \perp B$ to denote this property.

## Coming back to our car stopping distance problem

$y = {\color{blue}z} x + 0.1 x^2$

where $z$ is a **continuous** rv such that $z \sim \mathcal{N}(\mu_z=1.5,\sigma_z^2=0.5^2)$.

* What is the probability of an event $Z$ defined by a reaction time $z \leq 0.52$ seconds?

$$
\text{Pr}(Z)=\text{Pr}(z \leq 0.52)= P(z=0.52)
$$

where $P(z)$ denotes the **cumulative distribution function (cdf)**. Note that cdf is denoted with a capital $P$.

Likewise, we can compute the probability of being in any interval as follows:

$\text{Pr}(a \leq z \leq b)= P(z=b)-P(z=a)$

* But how do we compute the cdf at a particular value $b$, e.g. $P(z=b)$?

## Cdf's result from pdf's

A pdf $p(z)$ is defined as the derivative of the cdf $P(z)$:

$$
p(z)=\frac{d}{d z}P(z)
$$

So, given a pdf $p(z)$, we can compute the following probabilities:

$$\text{Pr}(z \leq b)=\int_{-\infty}^b p(z) dz = P(b)$$
$$\text{Pr}(z \geq a)=\int_a^{\infty} p(z) dz = 1 - P(a)$$
$$\text{Pr}(a \leq z \leq b)=\int_a^b p(z) dz = P(b) - P(a)$$

**IMPORTANT**: $\int_{-\infty}^{\infty} p(z) dz = 1$

### Some notes about pdf's

The integration to unity is important!

$$\int_{-\infty}^{\infty} p(z) dz = 1$$

**Remember:** the integral of a pdf leads to a probability, and probabilities cannot be larger than 1.

For example, from this property we can derive the following:

$$
\int_{-\infty}^{\infty} p(z) dz = \int_{-\infty}^{a} p(z) dz + \int_{a}^{\infty} p(z) dz
$$

$$
\Rightarrow \text{Pr}(z \geq a)= 1 - \text{Pr}(z \leq a) = 1 - \text{P}(a) = 1 - \int_{-\infty}^a p(z) dz
$$

In some cases we will work with probability distributions that are **unnormalized**, so this comment is important!

* Being unnormalized means that the probability density of the distribution does not integrate to 1.
* In this case, we cannot call such function a pdf, even though its output is a probability density.

## Cdf's result from pdf's

Key point?

* Given a pdf $p(z)$, we can compute the probability of a continuous rv $z$ being in a finite interval as follows:

$$
\text{Pr}(a \leq z \leq b)=\int_a^b p(z) dz = P(b) - P(a)
$$

As the size of the interval gets smaller, we can write

$$
\text{Pr}\left(z - \frac{dz}{2} \leq z \leq z + \frac{dz}{2}\right) \approx p(z) dz
$$

Intuitively, this says the probability of $z$ being in a small interval around $z$ is the density at $z$ times
the width of the interval.

```python
from scipy.stats import norm # import from scipy.stats the normal distribution

zrange = np.linspace(-3, 3, 100) # 100 values for plot
fig_std_norm, (ax1, ax2) = plt.subplots(1, 2) # create a plot with 2 subplots side-by-side
ax1.plot(zrange, norm.cdf(zrange, 0, 1), label=r"$\mu_z=0; \ \sigma_z=1$") # plot cdf of standard normal
ax1.set_xlabel("z", fontsize=20)
ax1.set_ylabel("probability", fontsize=20)
ax1.legend(fontsize=15)
ax1.set_title("Standard Gaussian cdf", fontsize=20)

ax2.plot(zrange, norm.pdf(zrange, 0, 1), label=r"$\mu_z=0; \ \sigma_z=1$") # plot pdf of standard normal
ax2.set_xlabel("z", fontsize=20)
ax2.set_ylabel("probability density", fontsize=20)
ax2.legend(fontsize=15)
ax2.set_title("Standard Gaussian pdf", fontsize=20)
fig_std_norm.set_size_inches(25, 5) # scale figure to be wider (since there are 2 subplots)
```

## Note about scipy.stats

[scipy](https://docs.scipy.org/doc/scipy/index.html) is an open-source software for mathematics, science, and engineering. It's brilliant and widely used for many things!

**In particular**, [scipy.stats](https://docs.scipy.org/doc/scipy/reference/stats.html) is a simple module within scipy that has statistical functions and operations that are very useful. This way, we don't need to code all the functions ourselves. That's why we are using it to plot the cdf and pdf of the Gaussian distribution from now on, and we will use it for other things later.

* In case you are interested, scipy.stats has a nice [tutorial](https://docs.scipy.org/doc/scipy/tutorial/stats.html)

## Coming back to our car stopping distance problem

$y = {\color{blue}z} x + 0.1 x^2$

where $z$ is a continuous rv such that $p(z)= \mathcal{N}(z | \mu_z=1.5,\sigma_z^2=0.5^2)$.

* What is the probability of an event $Z$ defined by a reaction time $z \leq 0.52$ seconds?

$$
\text{Pr}(Z) = \text{Pr}(z \leq 0.52) = P(z=0.52) = \int_{-\infty}^{0.52} p(z) dz
$$

```python
Pr_Z = norm.cdf(0.52, 1.5, 0.5) # using scipy norm.cdf(z=0.52 | mu_z=1.5, sigma_z=0.5)

print("The probability of event Z is: Pr(Z) = ",round(Pr_Z,3))
```

    The probability of event Z is: Pr(Z) =  0.025

```python
z_value = 0.52 # z = 0.52 seconds
zrange = np.linspace(0, 3, 200) # 200 values for plot
fig_car_norm, (ax1, ax2) = plt.subplots(1, 2) # create subplot (two figures in 1)
ax1.plot(zrange, norm.cdf(zrange, 1.5, 0.5), label=r"$\mu_z=1.5; \ \sigma_z=0.5$") # Figure 1 is cdf
ax1.plot(z_value, norm.cdf(z_value, 1.5, 0.5), 'r*',markersize=15, linewidth=2,
         label=u'$P(z=0.52~|~\mu_z=1.5, \sigma_z^2=0.5^2)$')
ax1.set_xlabel("z", fontsize=20)
ax1.set_ylabel("probability", fontsize=20)
ax1.legend(fontsize=15)
ax1.set_title("Gaussian cdf of $z$ for car problem", fontsize=20)
ax2.plot(zrange, norm.pdf(zrange, 1.5, 0.5), label=r"$\mu_z=1.5; \ \sigma_z=0.5$") # figure 2 is pdf
ax2.plot(z_value, norm.pdf(z_value, 1.5, 0.5), 'r*', markersize=15, linewidth=2,
         label=u'$p(z=0.52~|~\mu_z=1.5, \sigma_z^2=0.5^2)$')
ax2.set_xlabel("z", fontsize=20)
ax2.set_ylabel("probability density", fontsize=20)
ax2.legend(fontsize=15)
ax2.set_title("Gaussian pdf of $z$ for car problem", fontsize=20)
fig_car_norm.set_size_inches(25, 5) # scale figure to be wider (since there are 2 subplots)
```

### Why is the Gaussian distribution so widely used?

Several reasons:

1. It has two parameters which are easy to interpret, and which capture some of the most basic properties of a distribution, namely its mean and variance.
2. The central limit theorem (Sec. 2.8.6 of the book) tells us that sums of independent random variables have an approximately Gaussian distribution, making it a good choice for modeling residual errors or “noise”.
3. The Gaussian distribution makes the least number of assumptions (has maximum entropy), subject to the constraint of having a specified mean and variance (Sec. 3.4.4 of the book); this makes it a good default choice in many cases.
4. It has a simple mathematical form, which results in easy to implement, but often highly effective, methods.

## Car stopping distance problem

$y = {\color{blue}z} x + 0.1 x^2$

where $z$ is a continuous rv such that $z \sim \mathcal{N}(\mu_z=1.5,\sigma_z^2=0.5^2)$.

* What is the **expected** value for the reaction time $z$?

This is not a trick question! It's the mean $\mu_z$, of course!

* But how do we compute the expected value for any distribution?

## Moments of a distribution

### First moment: Expected value or mean

The expected value (mean) of a distribution is the **first moment** of the distribution:

$$
\mathbb{E}[z]= \int_{\mathcal{Z}}z p(z) dz
$$

where $\mathcal{Z}$ indicates the support of the distribution (the $z$ domain).

* Often, $\mathcal{Z}$ is omitted as it is usually between $-\infty$ to $\infty$
* The expected value $\mathbb{E}[z]$ is often denoted by $\mu_z$

As you might expect (pun intended 😆), the expected value is a linear operator:

$$
\mathbb{E}[az+b]= a\mathbb{E}[z] + b
$$

where $a$ and $b$ are fixed variables (NOT rv's).

Additionally, for a set of $n$ rv's, one can show that the expectation of their sum is as follows:

$\mathbb{E}\left[\sum_{i=1}^n z_i\right]= \sum_{i=1}^n \mathbb{E}[z_i]$

If they are **independent**, the expectation of their product is given by

$\mathbb{E}\left[\prod_{i=1}^n z_i\right]= \prod_{i=1}^n \mathbb{E}[z_i]$

## Moments of a distribution

### Second moment (and relation to Variance)

The 2nd moment of a distribution $p(z)$ is:

$$
\mathbb{E}[z^2]= \int_{\mathcal{Z}}z^2 p(z) dz
$$

#### Variance can be obtained from the 1st and 2nd moments

The variance is a measure of the “spread” of the distribution:

$$
\mathbb{V}[z] = \mathbb{E}[(z-\mu_z)^2] = \int (z-\mu_z)^2 p(z) dz = \mathbb{E}[z^2] - \mu_z^2
$$

* It is often denoted by the square of the standard deviation, i.e. $\sigma_z^2 = \mathbb{V}[z] = \mathbb{E}[(z-\mu_z)^2]$

#### Elaboration of the variance as a result of the first two moments of a distribution

$$
\begin{align}
\mathbb{V}[z] & = \mathbb{E}[(z-\mu_z)^2] \\
& = \int (z-\mu_z)^2 p(z) dz \\
& = \int z^2 p(z) dz + \mu_z^2 \int p(z) dz - 2\mu_z \int zp(z) dz \\
& = \mathbb{E}[z^2] - \mu_z^2
\end{align}
$$

where $\mu_z = \mathbb{E}[z]$ is the first moment, and $\mathbb{E}[z^2]$ is the second moment.

Therefore, we can also write the second moment of a distribution as

$$\mathbb{E}[z^2] = \sigma_z^2 + \mu_z^2$$

#### Variance and standard deviation properties

The standard deviation is defined as

$ \sigma_z = \text{std}[z] = \sqrt{\mathbb{V}[z]}$

The variance of a shifted and scaled version of a random variable is given by

$\mathbb{V}[a z + b] = a^2\mathbb{V}[z]$

where $a$ and $b$ are fixed variables (NOT rv's).

If we have a set of $n$ independent rv's, the variance of their sum is given by the sum of their variances

$$
\mathbb{V}\left[\sum_{i=1}^n z_i\right] = \sum_{i=1}^n \mathbb{V}[z_i]
$$

The variance of their product can also be derived, as follows:

$$
\begin{align}
\mathbb{V}\left[\prod_{i=1}^n z_i\right] & = \mathbb{E}\left[ \left(\prod_i z_i\right)^2 \right] - \left( \mathbb{E}\left[\prod_i z_i \right]\right)^2\\
 & = \mathbb{E}\left[ \prod_i z_i^2 \right] - \left( \prod_i\mathbb{E}\left[ z_i \right]\right)^2\\
 & = \prod_i \mathbb{E}\left[  z_i^2 \right] - \prod_i\left( \mathbb{E}\left[ z_i \right]\right)^2\\
 & = \prod_i \left( \mathbb{V}\left[  z_i \right] +\left( \mathbb{E}\left[ z_i \right]\right)^2 \right)- \prod_i\left( \mathbb{E}\left[ z_i \right]\right)^2\\
 & = \prod_i \left( \sigma_{z,\,i}^2 + \mu_{z,\,i}^2 \right)- \prod_i\mu_{z,\,i}^2 \\
\end{align}
$$

## Note about higher-order moments

* The $k$-th moment of a distribution $p(z)$ is defined as the expected value of the $k$-th power of $z$, i.e. $z^k$:

$$
\mathbb{E}[z^k]= \int_{\mathcal{Z}}z^k p(z) dz
$$

## Mode of a distribution

The mode of an rv $z$ is the value of $z$ for which $p(z)$ is maximum.

Formally, this is written as,

$$ \mathbf{z}^* = \underset{z}{\mathrm{argmax}}~p(z)$$

If the distribution is multimodal, this may not be unique:
* That's why $\mathbf{z}^*$ is in **bold**, to denote that in general it is a vector that is retrieved!
* However, if the distribution is unimodal (one maximum), like the univariate Gaussian distribution, then it retrieves a scalar $z^*$

Note that even if there is a unique mode, this point may not be a good summary of the distribution.

## Mean vs mode for a non-symmetric distribution

```python
# 1. Create a gamma pdf with parameter a = 2.0

from scipy.stats import gamma # import from scipy.stats the Gamma distribution

a = 2.0 # this is the only input parameter needed for this distribution

# Define the support of the distribution (its domain) by using the
# inverse of the cdf (called ppf) to get the lowest z of the plot that
# corresponds to Pr = 0.01 and the highest z of the plot that corresponds
# to Pr = 0.99:
zrange = np.linspace(gamma.ppf(0.01, a), gamma.ppf(0.99, a), 200)

mu_z, var_z = gamma.stats(2.0, moments='mv') # This computes the mean and variance of the pdf

fig_gamma_pdf, ax = plt.subplots() # a trick to save the figure for later use
ax.plot(zrange, gamma.pdf(zrange, a), label=r"$\Gamma(z|a=2.0)$")
ax.set_xlabel("z", fontsize=20)
ax.set_ylabel("probability density", fontsize=20)
ax.legend(fontsize=15)
ax.set_title("Gamma pdf for $a=2.0$", fontsize=20)
plt.close(fig_gamma_pdf) # do not plot the figure now. We will show it in a later cell
```

```python
# 2. Plot the expected value (mean) for this pdf
ax.plot(mu_z, gamma.pdf(mu_z, a), 'r*', markersize=15, linewidth=2, label=u'$\mu_z = \mathbb{E}[z]$')
```

    [<matplotlib.lines.Line2D at 0x7f80bcb1c4f0>]

```python
# 3. Calculate the mode and plot it
from scipy.optimize import minimize # import minimizer

# Finding the maximum of the gamma pdf can be done by minimizing
# the negative gamma pdf. So, we create a function that outputs
# the negative of the gamma pdf given the parameter a=2.0:
def neg_gamma_given_a(z): return -gamma.pdf(z,a)

# Use the default optimizer of scipy (L-BFGS) to find the
# maximum (by minimizing the negative gamma pdf). Note
# that we need to give an initial guess for the value of z,
# so we can use, for example, z=mu_z:
mode_z = minimize(neg_gamma_given_a,mu_z).x

ax.plot(mode_z, np.max(gamma.pdf(mode_z, a)),'g^', markersize=15,
        linewidth=2,label=u'mode $\mathbf{z}^*=\mathrm{argmax}~p(z)$')
ax.legend() # show legend
```

    <matplotlib.legend.Legend at 0x7f80bc769f40>

```python
# Code to generate this Gamma distribution hidden during presentation (it's shown as notes)

print('The mean is ',mu_z) # print the mean calculated for this gamma pdf
print('The mode is approximately ',mode_z) # print the mode
fig_gamma_pdf # show figure of this gamma pdf
```

## The amazing Bayes' rule
Bayesian inference definition:
* Inference means “the act of passing from sample data to generalizations, usually with calculated degrees of certainty”.
* Bayesian is used to refer to inference methods that represent “degrees of certainty” using probability theory, and which leverage Bayes’ rule to update the degree of certainty given data.

**Bayes’ rule** is a formula for computing the probability distribution over possible values of an unknown (or hidden) quantity $z$ given some observed data $y$:

$$
p(z|y) = \frac{p(y|z) p(z)}{p(y)}
$$

Bayes' rule follows automatically from the identity: $p(z|y) p(y) = p(y|z) p(z) = p(y,z) = p(z,y)$

## The amazing Bayes' rule

* I know... You don't find it very amazing (yet!).
* Wait until you realize that almost all ML methods can be derived from this simple formula

$$
p(z|y) = \frac{p(y|z) p(z)}{p(y)}
$$

### See you next class

Have fun!


```python
## This code cell will not be shown in the HTML version of this notebook
#### some helpful imports ####
# import autograd functionality
import autograd.numpy as np

# import testing libraries
import sys
sys.path.append('../')
from mlrefined_libraries import time_series_lib as timelib
from mlrefined_libraries import pid_lib as pidlib

# import dataset path
datapath = '../datasets/'

# import various other libraries e.g., for plotting, deep copying
import copy
import matplotlib.pyplot as plt

# this is needed to compensate for %matplotl+ib notebook's tendancy to blow up images when plotted inline
from matplotlib import rcParams
rcParams['figure.autolayout'] = True
%matplotlib notebook

# autoreload function - so if anything behind the scenes is changeed those changes
# are reflected in the notebook without having to restart the kernel
%load_ext autoreload
%autoreload 2
```

# Principles of PID control

Toggle code on and off in this presentation by clicking the 'Toggle code' button below.

```python
from IPython.display import display
from IPython.display import HTML
import IPython.core.display as di # Example: di.display_html('<h3>%s:</h3>' % str, raw=True)

# This line will hide code by default when the notebook is eåxported as HTML
di.display_html('', raw=True)

# This line will add a button to toggle visibility of code blocks, for use with the HTML export version
di.display_html('''<button onclick="jQuery('.input_area').toggle(); jQuery('.prompt').toggle();">Toggle code</button>''', raw=True)
```

<button onclick="jQuery('.input_area').toggle(); jQuery('.prompt').toggle();">Toggle code</button>

# The standard PID Control Model

- with a trained Imitator or System Model in hand, we can now look at automatically controlling this Imitator to perform desired tasks

- the simplest kind of behavior we make a system obey (like e.g., temperature control and cruise control): is to make the system match a series of **training** *set points* $x_1,\,x_2,\,...,x_T$ as closely as possible

- examples of set points:
    - for cruise control / autonomous driving: speed levels to have the car drive
    - for temperature control: different temperature levels throughout the day
    - for an industrial process like water level: keep a certain level in a tank that can change throughout the day

- this involves putting our Imitator model under the authority of a Controller (or - you can say - we pass our Imitator model through a Control model that selects optimal actions for it)

- once trained the Control Model should be able to choose actions automatically so that the Imitator matches desired set points

- that is, our Controller will choose actions $a_t$ for optimally for our Imitator so that its output $s_{t+1} = f_{\text{imitator}}\left(s_t,a_t\right)$ matches the desired set points as closely as possible (as the system allows), or that

$$s_t \approx x_t \,\,\,\,\, \text{for} \,\,\,\,\, t=2,...,T$$

(often the initial state $s_1$ is determined by the problem, or set to a reasonable reference value)

- once tuned properly a Control Model is often referred to as a *Control Law* or *Optimal Policy*

- how does the Controller choose optimal actions to meet a series of set points?

- *one way* is to train a Controller to choose actions optimally **looking backwards** to decide on the best choice of action in the present

- this is based on the desire to always be correcting for previous mistakes / *historical errors*, or how well $s_t \approx x_t$ previously

- this most popular control approach is called *Proportional Integral Derivative or PID* ontrol

- this is a simple *parameterized dynamic system with unlimited memory* that captures historical *error* between our sequence of training set points and the corresponding states of our system

- as a graphical model it looks like this

- here we have used the *signed error* $e_t = x_t - s_t$ as historical feedback to the Controller Model: $f_{\text{controller}}\left(e_t\right)$

- using the signed error is a convention, you could use another (e.g., absolute value of the error)

- we uses a linear combination of this (and its history and/or its derivatives) to learn how to choose actions optimally

- the simplest parameterized controller (a *Proportional controller*) uses a linear combination of this (signed) error to determine the next action to take

\begin{equation}
f_{\text{controller}}\left(e_t;\Theta_{\text{controller}}\right) = a_t =  w_0 + w_1e_t
\end{equation}

- this is a function with weights we need to tune properly in order for the controller to properly choose actions

- it is called a *Proportional controller* because the action $a_t$ is literally being made proportional to the signed error of the system at the prior state

- a common extension of this idea: use a summary statistic of the history of the error as well as well

- this is usually chosen to be the *integral of the error*

\begin{equation}
h^e_t = h^e_t + \frac{1}{D}e_t
\end{equation}

where $\frac{1}{D}$ is the gap between steps, but in principle any dynamic system with unlimited memory will work

- adding the integral (or history) of the error gives the controller *context*, as the integral summarizes how the error has changed in the past

- this is what we mean when we say that a PID Controller 'looks backward' to decide on the best choice of action in the present

- adding the history $h^e_t$ term to our parameterized action function / control model gives the parameterized update

\begin{equation}
a_t = w_0 + w_1e_t + w_2h^e_t
\end{equation}

- this is a so-called *Proportional Integral* controller - one of the most common automatic controller used today in practice (for set-point matching automatic control problems)

- notice: this history of the error is now an explicit input to our Controller

\begin{equation}
f_{\text{control}}\left(e_t,h^e_t ; \Theta_{\text{controller}} \right) = a_t = w_0 + w_1e_t + w_2h^e_t
\end{equation}

- one final common addition: the derivative of the error: $\frac{e_t - e_{t-1}}{D}$

- proportional information derivative or *local difference* of the error can be added as well, tacking on another term as to the action update

\begin{equation}
a_t = w_0 + w_1e_t + w_2h_t + w_3\frac{e_t - e_{t-1}}{D}
\end{equation}

- using this update in a Control Model we have the so-called *Proportional Inegral Derivative* (PID) controller.

- our Control Model now takes in two prior error terms

# Tuning the weights of a PID Control Model

- how do we tune these weights properly - so that our controller learns how to produce the best actions to lead our system model to match our training set points?

- traditional (non machine-learning) approaches PID controller tuning involve "voodoo" and a lot of human trial and error

- see e.g., the recommendations for tuning on stackoverflow and youtube (note here: $w_1 = K_p$, $w_2 = K_i$, and $w_3 = K_d$)

https://robotics.stackexchange.com/questions/167/what-are-good-strategies-for-tuning-pid-loops

https://www.youtube.com/watch?v=VVOi2dbtxC0&t=1050s

- note: 'traditional' does not mean that 'old' - this is how many people tune their controllers today

- why so much 'voo-doo' and human trial and error for PID tuning?

- because the traditional way of doing PID control already involves one understand an enormous amount of specialized information

    - for Imitator / System modeling: the traditional way is to use 'first principles' differential equations modeling

    - this can lead to system models that are - by their very nature - highly unstable

    - this involves building up not only a steep mathematical knowledge stack, but expert knowledge in a particular domain (e.g., physics, chemistry, robotics, etc.,)

- things often not included in traditional automatic control pedagogy

    - programming / basic CS
    - machine learning / deep learning basics (as an alternative to differential equations modeling)
    - mathematical optimization (although there is some emphasis on this for advanced students of automatic control, the approaches taken are very limiting = only special structures like QP are studied)

# The ML perspective on PID tuning

- but 'automatic parameter tuning' is the 'bread and butter' for machine learning folks - so what do we need to do to auto-tune our PID parameters?

- Like everything else: we need to form a cost function (whose minimum recovers correctly tuned parameters)!

- to design a proper cost, lets look at our entire controller pipeline- including our system model

- to keep things simple we will perform these derivations using the simplest Proportional (P) controller, but everything that follows is the same for PI and PID controllers as well

- our basic Proportional controller takes in the current (signed) error $e_t = x_t - s_t$ and returns the action $a_t$

\begin{equation}
f_{\text{control}}\left(e_t\right) = a_t
\end{equation}

- we then feed this action into our Imitator model to get our next state $s_{t+1}$

\begin{equation}
f_{\text{imitator}}\left(s_t, a_t \right) = f_{\text{imitator}}\left(s_t,\, f_{\text{control}}\left(e_t\right) \right) = s_{t+1}
\end{equation}

- now ideally - we want these actions made *so that this next state matches the input set point*, that is

\begin{equation}
s_{t+1} \approx x_{t+1}
\end{equation}

- in other words, we want the *error* $e_{t+1} = x_{t+1} - s_{t+1}$ to be small in magnitude

- so why not tune $\Theta_{\text{controller}}$ to minimize the average e.g., squared error over the entire sequence of *training set points* $x_1,...,x_T$

\begin{equation}
\frac{1}{T-1}\sum_{t=1}^{T-1}\left(e_{t+1}\right)^2 = \frac{1}{T-1}\sum_{t=1}^T\left(s_{t+1} - x_{t+1}\right)^2
\end{equation}

- we ignore the error on our initial state $s_1$ since we cannot adjust it (its given by the problem at hand!  e.g., with cruise control the car starts with 0 velocity)

- if unwravel the definition of $s_{t+1}$ and express it in terms of our system and control model this is equivalently

\begin{equation}
\frac{1}{T-1}\sum_{t=1}^{T-1}\left(f_{\text{imitator}}\left(s_t,\, f_{\text{control}}\left(e_t;\Theta_{\text{controller}}\right) \right) - x_{t+1}\right)^2
\end{equation}

- note here: we are minimizing over the *Controller parameters* $\Theta_{\text{controller}}$, **not** the parameters of our Imitator model

- the weights of our imitator have already been tuned as necessary to real action/state data (another way to think about it: they are regularized so that the system matches a real set of input/output data)

-  our goal in optimizing our Controller parameters is to solidify our *Optimal Control Law* so that we learn an entire sequence of optimal actions $a_1,\,a_2,\,...,a_{T-1}$

- thus we are getting at our optimal set of actions indirectly (via a parameterized function)

- Lets look at a simple implementation of a PID controller

```python
# a simple implementation of a PID controller
def PID_controller(e_t,h_t,d_t,w):
    # note here in terms of inputs
    # e_t = current error
    # h_t = integral of error
    # d_t = derivative of error
    return w[0] + w[1]*e_t + w[2]*h_t + w[3]*d_t
```

```python
# loop for evaluating control model over all input/output action/state pairs
# Our inputs here:
# s_1 - the initial condition state
# x - sequence of training set points
# w - the control model parameters
def control_loop(x,w):
    # initialize key variables and containers
    s_t = copy.deepcopy(s_1)
    h_t = 0
    d_t = 0
    frac = 1/float(np.size(x))
    action_history = []
    state_history = [s_t]
    error_history = []

    # loop over training set points and run through controller, then
    # system models
    for t in range(np.size(x) - 1):
        # get current set point
        x_t = x[:,t]

        # update error
        e_t = x_t - s_t
        error_history.append(e_t)

        # update integral of error
        h_t = h_t + frac*e_t

        # update derivative of error
        if t > 0:
            d_t = frac*(error_history[-1] - error_history[-2])

        # send error, integral, and derivative to PID controller
        a_t = PID_controller(e_t,h_t,d_t,w)

        # clip a_t to match system specifications?

        # send action to system model
        s_t = tuned_system_model(s_t,a_t)

        # store state output, and actions (for plotting)
        state_history.append(s_t)
        action_history.append(a_t)

    # transition to arrays
    state_history = np.array(state_history)[np.newaxis,:]
    action_history = np.array(action_history)[np.newaxis,:]

    # return velocities and control history
    return state_history,action_history
```

```python
# an implementation of the least squares cost for PID controller tuning
# note here: s is an (1 x T) array and a an (1 x T-1) array
def least_squares(w,x):
    # system_loop - runs over all action-state pairs and produces entire
    # state prediction set
    state_history,action_history = control_loop(x,w)

    # compute least squares error between real and predicted states
    cost = np.sum((state_history[:,1:] - x[:,1:])**2)
    return cost/float(x.shape[1]-1)
```

#### Example: 1  Cruise control

- a `Python` implementation of our `control_model` for the *cruise control* problem.  Notice at each update step the action is clipped to lie in the range $[-50,100]$ - which is the angle of the pedal against the floor of the car.

- because here we are using a 'true model' of the automobile we need to use a zero order optimization method - since we cannot compute the gradient of `system_model` with respect to our PID weights.

```python
# create tuned system model for the car
ind = np.argmin(mylib1.train_cost_histories[0])
w_best = mylib1.weight_histories[0][ind]
# a_norm = mylib1.x_norm
# s_norm = mylib1.y_norm
# s_invnorm = mylib1.y_invnorm
# a_invnorm = mylib1.x_invnorm
# tuned_system_model = lambda state,action: s_invnorm(system_model(s_norm(state),a_norm(action),w_best))

tuned_system_model = lambda state,action: system_model(state,action,w_best)
s_1 = 0.0
```

```python
# loop for evaluating control model over all input/output action/state pairs
# Our inputs here:
# s_1 - the initial condition state
# x - sequence of training set points
# w - the control model parameters
def control_loop(x,w):
    # initialize key variables and containers
    s_t = copy.deepcopy(s_1)
    h_t = 0
    d_t = 0
    frac = 1/float(np.size(x))
    action_history = []
    state_history = [s_t]
    error_history = []

    # loop over training set points and run through controller, then
    # system models
    for t in range(np.size(x) - 1):
        # get current set point
        x_t = x[:,t]

        # update error
        e_t = x_t - s_t
        error_history.append(e_t)

        # update integral of error
        h_t = h_t + frac*e_t

        # update derivative of error
        if t > 0:
            d_t = frac*(error_history[-1] - error_history[-2])

        # send error, integral, and derivative to PID controller
        a_t = PID_controller(e_t,h_t,d_t,w)

        # clip action range to realistic machine standard?
        # clip inputs to -50% to 100% for car
        if a_t >= 100.0:
            a_t = 100.0
        if a_t <= -50.0:
            a_t = -50.0

        # send action to system model
        s_t = tuned_system_model(s_t,a_t)

        # store state output, and actions (for plotting)
        state_history.append(s_t)
        action_history.append(a_t)

    # transition to arrays
    state_history = np.array(state_history)[np.newaxis,:]
    action_history = np.array(action_history)[np.newaxis,:]

    # return velocities and control history
    return state_history,action_history
```

```python
# an implementation of the least squares cost for PID controller tuning
# note here: s is an (1 x T) array and a an (1 x T-1) array
def least_absolute(w,x):
    # system_loop - runs over all action-state pairs and produces entire
    # state prediction set
    state_history,action_history = control_loop(x,w)

    # compute least squares error between real and predicted states
    cost = np.sum(np.abs(state_history[:,1:] - x[:,1:]))
    return cost/float(x.shape[1]-1)
```

```python
# create an instance of the car simulator
demo = pidlib.car_simulator.MyCar()

# create a training sequence of *set points* for trying out the true simulator, and for learning a controller
x_car = demo.create_set_points()
```

```python
# This code cell will not be shown in the HTML version of this notebook
# initialize with input/output data
mylib5 = pidlib.rnn_pid_lib.super_setup.Setup(x_car)

# normalize?
mylib5.preprocessing_steps(normalizer_name = 'none')

# split into training and validation sets
mylib5.make_train_val_split(train_portion = 1)

# choose cost
mylib5.choose_cost(control_loop,cost = least_absolute)

# fit an optimization
w = 0.1*np.random.randn(4,1)
# mylib5.fit(max_its = 59,alpha_choice = 10**(0),optimizer = 'gradient_descent',w_init = w,verbose = False)

mylib5.fit(max_its = 50,alpha_choice = 10**(0),optimizer = 'zero_order',w_init = w,verbose = False)

# show cost function history
mylib5.show_histories(start = 1)
```

    <IPython.core.display.Javascript object>

```python
# This code cell will not be shown in the HTML version of this notebook
# Plot the standard normalized series and its training fit
pidlib.variable_order_plotters.plot_setpoint_train_val_sequences(mylib5)
```

    <IPython.core.display.Javascript object>

- these actions are crazy when the desired speed ramps up and down - we can fix this by *regularizing*

#### Example: 2  PID controller for two tank example

```python
# This code cell will not be shown in the HTML version of this notebook
def two_tank_control_model(x,w):
    #### simulate vehicle response to set points ####
    s_t = [0.0,0.0]
    action_history = []
    state_history = [s_t]
    h = 0.0
    for t in range(np.size(x) - 1):
        # get current set point
        x_t = x[:,t]

        # update error
        e_t = x_t - s_t[1]

        # update integral of error
        h = h + e_t*0.1

        # set action based on PI linear combination
        a_t = w[0] + w[1]*e_t + w[2]*h

        if t > 0:
            a_t += w[3]*(s_t[1] - state_history[-2][1])

        # clip inputs to -50% to 100%
        if a_t >= 100.0:
            a_t = 100.0
        if a_t <= 0.0:
            a_t = 0.0

        # cap off condition
        #if s_t[1] > x_t:
        #    a_t = 0

        # run pid controller
        s_t = demo_3.tank_model(a_t,s_t)

        # store results
        action_history.append(a_t)
        state_history.append(s_t)

    # transition to arrays
    state_history = np.array(state_history).T
    action_history = np.array(action_history)[np.newaxis,:]

    # return velocities and control history
    return state_history,action_history
```

```python
# This code cell will not be shown in the HTML version of this notebook
# an implementation of the least squares cost function for linear regression
def least_squares(w,x):
    states,actions = control_model(x,w)
    # compute cost over batch
    cost = np.sum((states[1,1:] - x[:,1:])**2)
    return cost/float(x.shape[1]-1)

# a compact least absolute deviations cost function
def least_absolute_deviations(w,x):
    states,actions = control_model(x,w)
    # compute cost over batch
    cost = np.sum(np.abs(states[1,1:] - x[:,1:]))
    return cost/float(x.shape[1]-1)
```

```python
# This code cell will not be shown in the HTML version of this notebook
# initialize with input/output data
mylib6 = pidlib.rnn_pid_lib.super_setup.Setup(x_tank2)

# split into training and validation sets
mylib6.make_train_val_split(train_portion = 1)

# choose cost
control_model = two_tank_control_model
mylib6.choose_cost(model = control_model,cost = least_absolute_deviations)

# fit an optimization
w = 0.1*np.random.randn(4,1)
mylib6.fit(max_its = 10,alpha_choice = 'diminishing',optimizer = 'zero_order',w_init = w,verbose = False)

# show cost function history
mylib6.show_histories(start = 5)
```

    <IPython.core.display.Javascript object>

```python
# This code cell will not be shown in the HTML version of this notebook
# plot results
pidlib.two_tank_plotter.plot_results(mylib6)
```

    <IPython.core.display.Javascript object>


Table of Contents

1  $\LaTeX$

1.1  Getting Started

1.1.1  Spacing

1.1.2  Justification

1.1.2.1  Centering

1.1.2.2  Multiline formatting for math

1.2  The Greeks

1.3  Mathematical Symbols

1.3.1  Superscript and Subscript

1.3.2  Grouping

1.3.3  Sets & Probability

1.3.4  Mathematical comparators

1.3.5  Mathematical operators

1.3.6  Fractions

1.4  A few examples of using $\LaTeX$

1.4.0.1  Einstein's mass-energy equivalence

1.4.0.2  Equations of motion

1.4.0.3  Quadratic Equation

1.4.0.4  Some calculus for good measure

1.5  Using in visualizations

# $\LaTeX$

>_LaTeX, which is pronounced «Lah-tech» or «Lay-tech» (to rhyme with «blech» or «Bertolt Brecht»), is a document preparation system for high-quality typesetting. It is most often used for medium-to-large technical or scientific documents but it can be used for almost any form of publishing.\
    $~~~~$ - https://www.latex-project.org/about/_

This git is designed to be a primer in using $\LaTeX$ in Jupyter notebooks with the intention of improving presentation.

$\LaTeX$ is a powerful tool in making your markdowns cells look more professional and stand out. One can introduce $\LaTeX$ into the Jupyter Notebooks markdown cells using imported libraries, however, Jupyter Notebook natively leverages JavaScripts library MathJax to allow rendering of special characters and formatting. Its intended to allow for displaying mathematical functions and text in a more legible format and is widely used in academia.

This notebook aims to act like a cheat sheet for commonly used formatting examples. As we get progress along in the notebook, explanations will be limited to expounding on usage idiosyncrasies of the function in question. In other words, sections later in the book rely on knowledge of previous sections.

## Getting Started
Lets start with the basics. To insert $\LaTeX$ formatted text in Jupyter notebook is enclose the text in '$' to get started. Eg: here is the letter 'A' before and after

A : A\
\\$A\\$ : $A$

Easy enough. However, if all we wanted to bold and italicize our text, we can do that with builtin markdown shortcuts. The real power of $\LaTeX$ comes with introducing special characters. This is done using the escape character '\'. In this example, to get the alpha symbol we prefix it with the backspace character.

\\$alpha\\$ : $alpha$\
\\$\\alpha\\$ : $\alpha$

### Spacing

$\LaTeX$ doesn't care for white space and displays text with standardized spacing regardless of how many whitespace characters are there. To allow for better separation and control one can use '~'

$A          B$ (10 spaces separating the characters)\
$A~~B$ (2 '~' between characters)

### Justification
#### Centering
By default, Markdowns are left justified and so are $\LaTeX$ entries. Enclose text in '\$$' instead of a single '\$' centers the $\LaTeX$ text. eg:

$$A = B$$

#### Multiline formatting for math
If you are typing a multi line mathematical process or derivation, it can be convenient to wrap the entire block in a \\begin and \\end. This allows us to forgo the '$' at the beginning and end of each line. A few things to keep in mind,

- Every line should end with a '\\\\'.
- By default the block right aligned, however one can provide an ***&*** at every line to specify point of alignment

$$
\begin{align}
x &= 10 + 5 +2 \\
x &= 10 + 7\\
x &= 17\\
x - 17 &= 0\\
\end{align}
$$

## The Greeks
The greek alphabets are all represented. Note that some letters have a capitalized version. Letters exempt are the ones which resemble their alphabet equivalent, such as a capital $\alpha$ is ***A***. To access the capital variations, capitalize the first letter

$$
\begin{align}
Alpha&: \alpha\\
Beta&: \beta\\
Gamma&: \gamma~\Gamma\\
Delta&: \delta~\Delta\\
Epsilon&: \epsilon\\
Zeta&: \zeta\\
Eta&: \eta\\
Theta&: \theta~\Theta\\
Iota&: \iota\\
Kappa&: \kappa\\
Lambda&: \lambda~\Lambda\\
Mu&: \mu\\
Nu&: \nu\\
Xi&: \xi~\Xi\\
Omicron&: \omicron\\
Pi&: \pi~\Pi\\
Sigma&: \sigma~\Sigma\\
Tau&: \tau\\
Upsilon&: \upsilon~\Upsilon\\
Phi&: \phi~\Phi\\
Chi&: \chi\\
Psi&: \psi~\Psi\\
Omega&: \omega~\Omega
\end{align}
$$

## Mathematical Symbols
There is a plethora of mathematical symbols available for use as well, however, due to their verbose nature, we won't be going over every single one. Here are a few divided by subsections

### Superscript and Subscript
Superscipts and Subscripts are easily accessible using the '^' and '\_' symbols. This symbol needs to be prefixed to the exponent or the subscript.

$$ a_b $$
$$ x^2 $$

### Grouping
Grouping allows us to group a set of symbols together so they are always presented together. Lets use a superscript to illustrate this example. By default the superscript symbol only captures the first character to superscript. So if I wanted to write anything a little more complicated I'd have to group characters together using the _{ }_ symbols. In the following example we get two very different outcomes depending upon whether we grouped y+z

$$
\begin{align}
No~grouping &:x ^ y + z\\
Grouping &:x ^ {y + z}
\end{align}
$$

### Sets & Probability

Operators for showing set relationship

$$
\begin{align}
Union &: \cup\\
Intersection &: \cap\\
Subset &: \subset\\
Superset &: \supset\\
\end{align}
$$

### Mathematical comparators
These symbols and their usage are fairly self explanatory. Some symbols which have a dedicated spot on the keyboard don't need any special effort. For eg:
$$ = ~ < ~ >$$

However, for others that do not have a dedicated keyboard spot they can be accessed with the escape character,

$$
\begin{align}
\approx ~ \leq~  \geq \\
\equiv ~  \ll~   \gg \\
\neq ~    \leq~  \geq \\
\end{align}
$$

### Mathematical operators
$$\pm ~\times~\cdot$$

### Fractions
Fractions can be presented using the _'\\dfrac'_ or _'\\tfrac'_ command. Depending upon need either can be utilized.

Dfrac: $\dfrac x y$

Tfrac: $\tfrac x y$

This can be combined with grouping to display complex math in a legible form

$$ \dfrac {x^{exp}} {y_{sub}^{exp}} $$

## A few examples of using $\LaTeX$
Lets use what we've learned so far and write some famous and some more obscure equations

#### Einstein's mass-energy equivalence

$$ E = mc^2$$

#### Equations of motion

$$
\begin{align}
v &= u + at \\
s &= ut + \dfrac {a t^2}{2} \\
s &= \dfrac {(u+v)t}{2} \\
v^2 &= u^2 + 2as
\end{align}
$$

#### Quadratic Equation
For,

$$
\begin{align}
a x^2 + b x + c &= 0 \\
x &= \dfrac {-b \pm \sqrt{b^2 - 4 ac}}{2a}
\end{align}
$$

#### Some calculus for good measure

##### Derivation

$$
\begin{align}
    \dfrac{d}{dx} x^n &= n x^{n-1} \\
    \dfrac{d}{dx} (f(x)\cdot g(x)) &= \dfrac{d}{dx} (f(x))\cdot g(x) + \dfrac{d}{dx} (g(x))\cdot f(x)\\
\end{align}
$$

##### Integration

$$
\begin{align}
    \int x^n \cdot dx &= \dfrac{x^{n+1}}{n+1} + C \\
    \int f(x)\cdot g'(x)\cdot dx &= f(x)\cdot g(x) - \int g(x)\cdot f'(x)\cdot dx
\end{align}
$$

## Using in visualizations

These rules can also be applied to your visualizations if needed. All labels and titles can utilize $\LaTeX$ formatting as long as they are inserted as _rstrings_. This allows the use of the backslash escape character. As an example, here we plot a sine function and its differentiation cosine. Take note of the title and legend and the x ticks

```python
import numpy as np
import matplotlib.pyplot as plt

# Generate x values
x = np.arange(0, 4*np.pi, 0.1);

# Get y values for sine wave and its differentiation cosine
y = np.sin(x)
dy = np.cos(x)

# Plot waves
plt.figure(figsize = (10,8))
plt.plot(x, y)
plt.plot(x, dy)

# Give a title for the sine wave plot
plt.title(r'Plot of $sin(x)$ & $\dfrac {d}{dx} sin(x)$')

# Give x axis label for the plot
plt.xlabel('x')

# Give y axis label for the plot
plt.ylabel('f (x)')

plt.xticks(np.arange(0, 9*(np.pi/2),(np.pi)),
          [r'0$\pi$', r'$1\pi$', r'2$\pi$',r'3$\pi$', r'4$\pi$'])

plt.grid(True, alpha =0.2)
plt.axhline(y=0, color='k')

plt.legend([r'$sin(x)$',r'$\dfrac {d}{dx} sin(x) = cos(x)$'],loc = 1)
plt.show();
```

As you can see the possibilities are limitless and learning and leveraging $\LaTeX$ is a must to improve on your presentations


Giulio Tesei

QDETAILSS WS3

2019-10-24, Lund University

### LAYOUT

- What is a Jupyter notebook?

- How can it improve my workflow?

- How to get started

- How to share my notebook to help other scientists to reproduce my analysis

### What is it?
Interactive document that integrates:
- code:
    - A long list of available programming languages:
        - Python, Java, R, Julia, Matlab, Octave, Scala, Spark, PHP, C#, C++, etc.

- command-line tools:
    - copying / deleting / moving files with `cp` / `rm` / `mv`
    - navigate in the directory tree with `cd`
    - create a new folder with `mkdir`

- narrative text:
    - equations
    - tables
    - links

- visualizations

### Code

Documentation accessible within the notebook.
- How can I call this function?
- Which arguments does it have?
- What attributes does this object have?

```python
names = ['marie_curie','amedeo_avogadro','rosalind_franklin']
print(type(names), names[0], type(names[0]))
```

    <class 'list'> marie_curie <class 'str'>

```python
for name in names:
    first_last = name.split('_')
    print('first_last is ',first_last)
    first = first_last[0]
    last = first_last[1]
    print(first.capitalize()+' '+last.swapcase())
```

    first_last is  ['marie', 'curie']
    Marie CURIE
    first_last is  ['amedeo', 'avogadro']
    Amedeo AVOGADRO
    first_last is  ['rosalind', 'franklin']
    Rosalind FRANKLIN

### Command-Line Tools:
No need to use the terminal or file managers.
- copy / delete / move files or folders with `cp` / `rm` / `mv`
- navigate in the directory tree with `cd`
- create a new folder with `mkdir`
- check the pth of the current directory with `pwd`

```python
%pwd
```

    '/Users/giulio/jc/qdetailss'

```python
%mkdir data
%ls
```

    aux/                data/               jupyter_slides.pdf
    custom.css          jupyter.ipynb       reveal.js/

```python
%rm -r data
%ls
```

    aux/                jupyter.ipynb       reveal.js/
    custom.css          jupyter_slides.pdf

### Narrative Text
[Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) markup language:
- equations
- tables
- links

```
###### Free Induction Decay

The oscillating voltage, $V(t)$, has an initial amplitude $V(0)$ which freely decays in time, $t$. The dampened signal can be modeled as a sine function of frequency $\nu$, decaying exponentially with decay constant $T_2$:

\begin{equation}
V(t)=V(0)\,\exp{(-t/T_2)}\,\sin{(2 \pi \nu t)}.
\end{equation}

| Variable      | Description    | Unit  |
|---------------| ---------------|-------|
| $t$      | time | ms |
| $V$      | voltage | V |
| $\nu$    | frequency | Hz |
| $T_2$ | decay constant | ms |

###### References
1. [Wikipedia](http://tiny.cc/r9r0ez)
2. [Merriam-Webster](http://tiny.cc/uas0ez)
```

##### Free Induction Decay

The oscillating voltage, $V(t)$, has an initial amplitude $V(0)$ which freely decays in time, $t$. The dampened signal can be modeled as a sine function of frequency $\nu$, decaying exponentially with decay constant $T_2$:

\begin{equation}
V(t)=V(0)\,\exp{(-t/T_2)}\,\sin{(2 \pi \nu t)}.
\end{equation}

| Variable      | Description    | Unit  |
|---------------| ---------------|-------|
| $t$      | time | ms |
| $V$      | voltage | V |
| $\nu$    | frequency | Hz |
| $T_2$ | decay constant | ms |

###### References
1. [Wikipedia](http://tiny.cc/r9r0ez)
2. [Merriam-Webster](http://tiny.cc/uas0ez)

### How can it improve my workflow?
All the steps of your data analysis and visualization in a single document.
- Interactive data exploration and analysis

- Immediate access to documentation: learn coding, readily use new libraries!

- Facilitates iteration:
    - once the notebook is set up, the analysis can be repeated effortlessly with new variables / data sets

- A large set of freely available tools:
    - Python libraries for linear algebra, fitting data, plotting, handling tabular data, image analysis, bioinformatics, spectroscopy, molecular visualization

- [Examples](https://github.com/jupyter/jupyter/wiki/A-gallery-of-interesting-Jupyter-Notebooks)

```

```

```python
import numpy as np
x = np.linspace(0,np.pi/2.,10)
x
```

    array([0.        , 0.17453293, 0.34906585, 0.52359878, 0.6981317 ,
           0.87266463, 1.04719755, 1.22173048, 1.3962634 , 1.57079633])

```python
np.mean(x) # np.std() to compute the standard deviation
```

    0.7853981633974483

```python
y = np.cos(x) # np.sin(), np.tan(), np.log(), np.exp() etc.
y
```

    array([1.00000000e+00, 9.84807753e-01, 9.39692621e-01, 8.66025404e-01,
           7.66044443e-01, 6.42787610e-01, 5.00000000e-01, 3.42020143e-01,
           1.73648178e-01, 6.12323400e-17])

```python
!head -n 1 aux/pmf.dat
```

    1.525000000000000000e+01 2.086592750614395442e+01 1.224484592940475874e-01

```python
# Load data file
x,y,z = np.loadtxt('aux/pmf.dat',unpack=True)
```

```python
# Integrate along the given axis using the composite trapezoidal rule.
np.trapz(y,x)
```

    940.9132634027817

```python
# Return the derivative of an array.
dy = np.gradient(y,x)
# Save the gradient to a text file
np.savetxt('aux/gradient.dat',y)
```

```python
import matplotlib.pyplot as plt
plt.rcParams.update({'figure.dpi': 70})
```

```python
import matplotlib.pyplot as plt
x = np.linspace(0,10*np.pi,200)
y = np.cos(x)
plt.plot(x,y)
plt.ylabel('Cosine')
plt.xlabel('Angle  /  rad')
plt.show()
```

$$
P(x) = \frac{1}{\sqrt{2 \pi \sigma}} \exp{\left ( \frac{-(x-\mu)^2}{2\sigma^2} \right)}
$$

```python
x = np.linspace(0,9,1000)
for u in range(1,8):
    y = np.exp(-(u-x)**2/(2*0.2**2)) / np.sqrt(2*np.pi*0.2**2)
    plt.plot(x,y,label=str(u))
plt.legend(frameon=False, title='$\mu$ / nm')
plt.xlim(0,9)
plt.xlabel('Distance, $x$ / nm'); plt.ylabel('Probabilty, $P(x)$')
plt.savefig('aux/normal.pdf') # png, jpg, eps
plt.show()
```

```python
plt.rcParams.update({'figure.dpi': 300})
```

```python
import matplotlib.image as mpimg
img = mpimg.imread('aux/protein.png')
print(img.shape)
fig = plt.figure(figsize=(1.2, 1.2))
plt.imshow(img, interpolation='bilinear')
plt.axis('off')
plt.show()
```

```python
plt.rcParams.update({'figure.dpi': 75})
```

```python
from matplotlib.collections import LineCollection
x = np.linspace(0,10,1000)
y = np.exp(-(2.6-x)**2) / np.sqrt(4*np.pi) + np.exp(-(7-x)**2/2) / np.sqrt(8*np.pi)
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
norm = plt.Normalize(x.min(), x.max())
lc = LineCollection(segments, cmap='plasma', norm=norm)
lc.set_array(x); lc.set_linewidth(4); plt.gca().add_collection(lc)
plt.ylabel(r'Probability, $P(x)$'); plt.xlabel(r'Distance, $x$  /  nm')
plt.xlim(0,10); plt.ylim(0,.3)
plt.show()
```

Q: How can I plot a gradient-colored line?

A: Google ["matplotlib gradient color line"](https://matplotlib.org/3.1.1/gallery/lines_bars_and_markers/multicolored_line.html)

You can google very specific questions and quickly find excellent answers, generally on matplolib.org or stackoverflow.com

### Multiple Subplots

```python
fig, axes = plt.subplots(nrows=3,ncols=2,figsize=(7, 6))
x = np.arange(10)
axes[0,0].plot(x, x**2, 'bo')
axes[2,1].plot(x, -x**2, 'r^')
axes[2,1].yaxis.set_ticks_position('right') # yticks on the right side
```

```python
def plotQCM():
    fig, ((ax1,ax2),(ax3,ax4)) = plt.subplots(nrows=2,ncols=2)

    colors = plt.rcParams['axes.prop_cycle'].by_key()['color']

    ax1 = plt.subplot2grid((11, 5), (5, 0), rowspan=6, colspan=3)
    ax3 = plt.subplot2grid((11, 5), (0, 0), rowspan=5, colspan=3)
    ax2 = plt.subplot2grid((11, 5), (5, 3), rowspan=6, colspan=2)
    ax4 = plt.subplot2grid((11, 5), (0, 3), rowspan=5, colspan=2)

    a = np.linspace(0,np.pi,100)

    for ax in [ax2,ax4]:
        for i,c in zip(range(1,13,2),colors):
            ax.plot(np.cos(a*i)+i*2,a,lw=2,color=c)
            ax.plot(-np.cos(a*i)+i*2,a,lw=2,ls=':',color=c)

        ax.set_xticks(np.arange(1,13,2)*2)
        ax.set_xticklabels(np.arange(1,13,2))
        ax.tick_params(axis='both',which='both',bottom=False,right=False,labelbottom=True,labelright=False,
                        left=False,labelleft=False,pad=-.5)
        ax.set_frame_on(False)

    ax2.set_ylim(0-0.06/1.3*np.pi,np.pi+0.06/1.3*np.pi)
    ax4.set_ylim(0-0.06*np.pi,np.pi+0.06*np.pi)

    x0 = 2; y0 = 0; x1 = 1.7;

    c=7

    ax1.fill([x1,x1+2,x1+2.6,x1+.6], [y0,y0,y0+1,y0+1], colors[c], alpha=0.3,
             edgecolor=colors[c],ls='--',lw=0)
    ax1.fill([x1+.6,x1+2.6,x1+2.6,x1+.6], [y0+1,y0+1,y0+1.3,y0+1.3], colors[c], alpha=0.6,
             edgecolor=colors[c],ls='--',lw=0)

    ax1.fill([x0,x0+2,x0+2,x0], [y0+1,y0+1,y0+1.3,y0+1.3], colors[9], alpha=0.6, edgecolor=colors[9],ls='-',lw=0)
    ax1.fill([x0,x0+2,x0+2,x0], [y0,y0,y0+1,y0+1], colors[9], alpha=0.3, edgecolor=colors[9],ls='-',lw=0)

    ax3.fill([x1,x1+2,x1+2.6,x1+.6], [y0,y0,y0+1,y0+1], colors[c], alpha=0.3,
             edgecolor=colors[c],ls='--',lw=0)
    ax3.fill([x0,x0+2,x0+2,x0], [y0,y0,y0+1,y0+1], colors[9], alpha=0.3, edgecolor=colors[9],ls='-',lw=0)

    for ax in [ax1,ax3]:
        ax.axis('off')
        ax.plot([2.3,2.3],[0,1],marker='o',lw=0,color='k')
        ax.hlines(y=[0,1],xmin=[.9925,.9925],xmax=[2.3,2.3],lw=1,color='k')
        ax.annotate(r'$\bigcirc$',xy=(1,0.5),fontsize=24,color='k',horizontalalignment='center',
                    verticalalignment='center')
        ax.annotate(u'\u223F',xy=(1,0.5),fontsize=16,color='k',horizontalalignment='center',
                    verticalalignment='center')
        ax.vlines(x=[1,1],ymin=[0,0.61],ymax=[.4,1],lw=1,color='k')

    ax1.set_xlim(.7,4.5)
    ax3.set_xlim(.7,4.5)
    ax3.set_ylim(-.05,1.05)
    ax1.set_ylim(-.05,1.35)

    ax3.annotate('QCR', xy=(3,0.5),fontsize=14,color='k',horizontalalignment='center', verticalalignment='center')
    ax1.annotate('QCR', xy=(3,0.5),fontsize=14,color='k',horizontalalignment='center', verticalalignment='center')
    ax1.annotate('Film', xy=(3,1.15),fontsize=14,color='k',horizontalalignment='center', verticalalignment='center')

    plt.gcf().text(.6, 0.58, '$n$  = ', fontsize=12)
    plt.gcf().text(.6, 0.058, '$n$  = ', fontsize=12)

    plt.tight_layout(w_pad=2.5,h_pad=1)
    plt.show()
```

```python
def plotFourier():
    fig, (ax1,ax2) = plt.subplots(nrows=1,ncols=2,figsize=(7, 3.5))

    colors = plt.rcParams['axes.prop_cycle'].by_key()['color']

    gamma = 2e-1
    fr = 5
    t = np.arange(0,10,.001)
    cos = np.cos(2*np.pi*fr*t)
    exp = np.exp(-t*2*np.pi*gamma)
    func = exp*cos
    ax1.plot(t,func,color=colors[0])
    ax1.set_xlim(0,4)

    fs = np.linspace(0,10,1000)
    curr = []
    for f in fs:
        curr.append( np.trapz( func*np.cos(2*np.pi*f*t),t) )
    curr = np.array(curr)
    ax2.plot(fs,curr,lw=2,color=colors[0])
    ax2.yaxis.set_label_position('right'); ax2.yaxis.set_ticks_position('right')
    ax2.set_xlim(0,7)
    ax2.set_ylim(-.05,.55)

    ax1.hlines(y=-np.exp(-1),xmin=0,xmax=1/(2*np.pi*gamma))
    ax1.hlines(y=.7,xmin=4/5,xmax=1)
    ax1.hlines(y=.7,xmin=1,xmax=1.5,lw=1,linestyle=':')
    ax1.vlines(x=4/5,ymin=np.exp(-4/5*2*np.pi*gamma),ymax=.7,lw=1,linestyle=':')
    ax1.vlines(x=1,ymin=np.exp(-2*np.pi*gamma),ymax=.7,lw=1,linestyle=':')
    ax1.vlines(x=1/(2*np.pi*gamma)-.01,ymin=-np.exp(-1),ymax=-.6,lw=1,linestyle=':')
    ax1.annotate('1 / ( 2 $\pi$ $\Gamma_r$ )',xy=(.67,-.8),fontsize=16)
    ax2.hlines(y=curr.max()/2.,xmin=5,xmax=5+gamma)
    ax2.hlines(y=curr.max()/2.,xmin=5,xmax=5+gamma+.7,lw=1,linestyle=':')
    ax2.vlines(x=fr,ymin=curr.max()/2.,ymax=curr.max()+.05,lw=1,linestyle=':')
    ax2.annotate('$\Gamma_r$',xy=(6,.185),fontsize=16)
    ax2.annotate("$f_r$",xy=(fr-.2,curr.max()+.07),fontsize=16)
    ax1.annotate('1 / $f_r$',xy=(1.6,.65),fontsize=16)
    ax1.text(x=4.16,y=.1,s='FT',fontsize=16)
    ax1.text(x=4.13,y=-.05,s='⟶',fontsize=16)

    gamma = 4e-1
    fr = 2
    t = np.arange(0,10,.001)
    cos = np.cos(2*np.pi*fr*t)
    exp = np.exp(-t*2*np.pi*gamma)
    func = exp*cos
    ax1.plot(t,func,color=colors[3],lw=1)

    fs = np.linspace(0,10,1000)
    curr = []
    for f in fs:
        curr.append( np.trapz( func*np.cos(2*np.pi*f*t),t) )
    curr = np.array(curr)
    ax2.plot(fs,curr,lw=1,color=colors[3])
    ax2.yaxis.set_label_position('right'); ax2.yaxis.set_ticks_position('right')

    ax2.hlines(y=curr.max()/2.,xmin=fr,xmax=fr+gamma)
    ax2.hlines(y=curr.max()/2.,xmin=fr,xmax=fr+gamma+.7,lw=1,linestyle=':')
    ax2.vlines(x=fr,ymin=curr.max()/2.,ymax=curr.max()+.05,lw=1,linestyle=':')
    ax2.annotate("$\Gamma$",xy=(3.2,.08),fontsize=16)
    ax2.annotate("$f$",xy=(fr-.2,curr.max()+.07),fontsize=16)
    ax1.tick_params(axis='both',which='both',left=False,bottom=False,labelbottom=False,labelleft=False)
    ax2.tick_params(axis='both',which='both',bottom=False,right=False,labelbottom=False,labelright=False)
    ax1.set_xlabel('Time',labelpad=6)
    ax2.set_xlabel('Frequency',labelpad=6)
    ax1.set_ylabel('Current,  $I(t)$',labelpad=6)
    ax2.set_ylabel('Current,  $I(t)$',labelpad=8)

    fig.tight_layout(w_pad=3)
    plt.show()
```

```python
plt.rcParams.update({'figure.dpi': 60})
```

```python
plotQCM()
plotFourier()
```

### [Jupyter Widgets](https://ipywidgets.readthedocs.io/en/latest/)
Gain control and visualize changes in the data!

```python
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
from ipywidgets import interactive

def plot_cos_decay_FT(freq=1,gamma=.2):

    def cos_decay(time,freq,gamma):
        cos = np.cos(2*np.pi*freq*time)
        exp = np.exp(-time*2*np.pi*gamma)
        return exp*cos

    def FT(time,freq,gamma):
        fourier = []
        for f in np.linspace(0,freq*2,1000):
            cos = np.cos(2*np.pi*f*time)
            fourier.append( np.trapz( cos_decay(time,freq,gamma)*cos,time) )
        return np.array(fourier)

    time = np.arange(0,10,.001)

    fig = plt.figure(figsize=(3.5,4))
    ax = plt.axes()
    ax.plot(time,cos_decay(time,freq,gamma),color=plt.get_cmap('tab10')(3), lw=1)

    axins = inset_axes(ax, width='60%', height='30%', loc='upper right', borderpad=1.1)
    axins.plot(np.linspace(0,freq*2,1000),FT(time,freq,gamma),color=plt.get_cmap('tab10')(0), lw=1)

    axins.set_xlabel(r'Frequency, $f$',color=plt.get_cmap('tab10')(0),fontsize=10,labelpad=1)
    axins.set_ylabel(r'$I(f)$',fontsize=10,labelpad=1)

    ax.set_ylabel('$I(t)$',fontsize=12)
    ax.set_xlabel(r'Time, $t$',color=plt.get_cmap('tab10')(3),fontsize=12)
    ax.set_xlim(0,10)
```

```python
interactive_plot = interactive(plot_cos_decay_FT, freq=(1, 5, .1), gamma=(.08,.14,.01) )
interactive_plot.children[0].description=r'$f$' # slide bar
interactive_plot.children[1].description=r'$\Gamma$' # slide bar
interactive_plot
```

    interactive(children=(FloatSlider(value=1.0, description='$f$', max=5.0, min=1.0), FloatSlider(value=0.14, des…

```python
import pandas as pd
from IPython.display import display
```

Library to handle tabular data: a convenient alternative to Excel!

### Size-Exclusion Chromatography
Data from the purification of $\alpha$-synuclein monomers kindly provided by **Veronica Lattanzi**

```bash
%%bash
head -n 22 aux/191923_d_alphasyn.txt
```

    Run Name,20191023 dasyn NIST
    Run Date,12:10:53 PM   10-23-19
    Method Name,Increase_pumpA
    Export Format Version, 1.00
    Method ID, 2059
    Points/Second, 5.00
    Number of Records,  11780
    Offset from Run Start Time,00:00:00
    Run End Time,00:39:17
    Time,Second
    UV,AU,
    Conductivity,mS/cm
    Gradient Pump,<Not Selected>
    Trace 3,<Not Available>
    Trace 4,<Not Available>
    Trace 5,<Not Available>
    Trace 6,<Not Available>
    GP Pressure,<Not Selected>
    Volume,ml
    Fraction,<Not Available>
    Time,UV,Conductivity,Volume
         0.0,-0.000730,   1.040,     0.0

```python
df = pd.read_csv('aux/191923_d_alphasyn.txt',header=20,sep=',',index_col=0)
display(df.head(2))
```

      UV
      Conductivity
      Volume

      Time

      0.0
      -0.000730
      1.04
      0.0

      0.2
      -0.000724
      1.04
      0.0

```python
fig = plt.figure(figsize=(6, 2.5))
plt.plot(df.index/60, df['UV'])
plt.ylabel('Absorbance at 276 nm'); plt.xlabel('Time  /  min')
```

```python
fig = plt.figure(); ax1 = plt.axes()
ax1.plot(df.index/60, df['UV'])
ax2 = ax1.twinx() # creates a new subplot identical to x1, with invisible x-axis and y-axis on the r.h.s
ax2.plot(df.index/60, df['Conductivity'],color=plt.cm.tab10(3))
ax1.tick_params(axis='y',colors=plt.cm.tab10(0))
ax1.set_xlabel('Time  /  min')
ax1.set_ylabel('Absorbance at 276 nm',color=plt.cm.tab10(0))
ax2.set_ylabel('Conductivity  /  mS cm$^{-1}$',color=plt.cm.tab10(3))
ax2.tick_params(axis='y',colors=plt.cm.tab10(3))
plt.savefig('aux/chromatogram.png')
```

```python
fig = plt.figure(figsize=(6, 2.5))

plt.plot(df.index/60, df['UV'])
plt.xlim(17,27)
t1 = 19.8; t2 = 23.5
plt.vlines([t1,t2],ymin=0,ymax=.25,linestyle=':')

plt.ylabel('Absorbance at 276 nm'); plt.xlabel('Time  /  min'); plt.show()

t1 = 19.8*60; t2 = 23.5*60 # convertion to seconds
abs_avg = np.mean(df.loc[t1:t2]['UV']); epsilon = 5960 ;path_length = 0.5
print('Monomer concentration:','{:.3f} μM'.format(abs_avg/epsilon/path_length*1e6))
```

### Data Scraping: Importing an HTML Table from [Sigma Aldrich](https://www.sigmaaldrich.com/life-science/metabolomics/learning-center/amino-acid-reference-chart.html)

```python
url = "https://www.sigmaaldrich.com/life-science/metabolomics/learning-center/amino-acid-reference-chart.html"
df = pd.read_html(url, header=0, index_col=0, na_values='–')[0]
df = df['Alanine':'Valine'] # select rows we are interested in
df = df.apply(pd.to_numeric,errors='ignore') # convert numbers from strings to numeric values
display( df.iloc[::3] ) # show every third amino acid
```

      Abbr.
      Abbr..1
      Molecular  Weight
      Molecular  Formula
      Residue  Formula
      Residue Weight  (-H2O)
      pKa1
      pKb2
      pKx3
      pl4

      Name

      Alanine
      Ala
      A
      89.10
      C3H7NO2
      C3H5NO
      71.08
      2.34
      9.69
      NaN
      6.00

      Aspartic acid
      Asp
      D
      133.11
      C4H7NO4
      C4H5NO3
      115.09
      1.88
      9.60
      3.65
      2.77

      Glutamine
      Gln
      Q
      146.15
      C5H10N2O3
      C5H8N2O2
      128.13
      2.17
      9.13
      NaN
      5.65

      Hydroxyproline
      Hyp
      O
      131.13
      C5H9NO3
      C5H7NO2
      113.11
      1.82
      9.65
      NaN
      NaN

      Lysine
      Lys
      K
      146.19
      C6H14N2O2
      C6H12N2O
      128.18
      2.18
      8.95
      10.53
      9.74

      Proline
      Pro
      P
      115.13
      C5H9NO2
      C5H7NO
      97.12
      1.99
      10.60
      NaN
      6.30

      Threonine
      Thr
      T
      119.12
      C4H9NO3
      C4H7NO2
      101.11
      2.09
      9.10
      NaN
      5.60

      Valine
      Val
      V
      117.15
      C5H11NO2
      C5H9NO
      99.13
      2.32
      9.62
      NaN
      5.96

```python
display( df['Arginine':'Glutamic acid'][['pKa1','pKb2','pKx3']] )
```

      pKa1
      pKb2
      pKx3

      Name

      Arginine
      2.17
      9.04
      12.48

      Asparagine
      2.02
      8.80
      NaN

      Aspartic acid
      1.88
      9.60
      3.65

      Cysteine
      1.96
      10.28
      8.18

      Glutamic acid
      2.19
      9.67
      4.25

```python
df['Arginine':'Glutamine']['Molecular  Weight'].values
```

    array([174.2 , 132.12, 133.11, 121.16, 147.13, 146.15])

```python
df['Arginine':'Glutamine']['Molecular  Weight'].values.mean()
```

    142.31166666666667

```python
display(df[df['pl4']>7])
```

      Abbr.
      Abbr..1
      Molecular  Weight
      Molecular  Formula
      Residue  Formula
      Residue Weight  (-H2O)
      pKa1
      pKb2
      pKx3
      pl4

      Name

      Arginine
      Arg
      R
      174.20
      C6H14N4O2
      C6H12N4O
      156.19
      2.17
      9.04
      12.48
      10.76

      Histidine
      His
      H
      155.16
      C6H9N3O2
      C6H7N3O
      137.14
      1.82
      9.17
      6.00
      7.59

      Lysine
      Lys
      K
      146.19
      C6H14N2O2
      C6H12N2O
      128.18
      2.18
      8.95
      10.53
      9.74

```python
df[df['pl4']>7]['Molecular  Weight'].values.mean()
```

    158.51666666666668

```python
np.mean(df['Molecular  Weight']-df['Residue Weight  (-H2O)'])
```

    18.014545454545452

[](https://jakevdp.github.io/PythonDataScienceHandbook/)
https://jakevdp.github.io/PythonDataScienceHandbook/

### [Jupyter Course in Lund](https://github.com/mlund/jupyter-course)

#### Reproducible and Interactive Data Analysis and Modelling using Jupyter Notebooks (4 ECTS)

- course developed by me, Caterina Doglioni, Mikael Lund and Benjamin Ragan-Kelley
- [COMPUTE](http://cbbp.thep.lu.se/compute/Previous_courses.php) research school (Natural Science)
- video lectures ([Intro & Widgets](https://api.kaltura.nordu.net/p/310/sp/31000/embedIframeJs/uiconf_id/23450585/partner_id/310/widget_id/0_vujap4by?iframeembed=true&playerId=kaltura_player_5bfdb69292c20&flashvars[playlistAPI.kpl0Id]=0_nc717bpa&flashvars[playlistAPI.autoContinue]=true&flashvars[playlistAPI.autoInsert]=true&flashvars[ks]=&flashvars[localizationCode]=en&flashvars[imageDefaultDuration]=30&flashvars[leadWithHTML5]=true&flashvars[forceMobileHTML5]=true&flashvars[nextPrevBtn.plugin]=true&flashvars[sideBarContainer.plugin]=true&flashvars[sideBarContainer.position]=left&flashvars[sideBarContainer.clickToClose]=true&flashvars[chapters.plugin]=true&flashvars[chapters.layout]=vertical&flashvars[chapters.thumbnailRotator]=false&flashvars[streamSelector.plugin]=true&flashvars[EmbedPlayer.SpinnerTarget]=videoHolder&flashvars[dualScreen.plugin]=true), [Libraries](https://www.youtube.com/playlist?list=PLto3nNV9nKZlXSWOAqmmn4J7csD4I6a2d), [ATLAS Dijet](https://api.kaltura.nordu.net/p/310/sp/31000/embedIframeJs/uiconf_id/23450585/partner_id/310/widget_id/0_hr5l2zj6?iframeembed=true&playerId=kaltura_player_5bfdb5d709908&flashvars[playlistAPI.kpl0Id]=0_pspvclw2&flashvars[playlistAPI.autoContinue]=true&flashvars[playlistAPI.autoInsert]=true&flashvars[ks]=&flashvars[localizationCode]=en&flashvars[imageDefaultDuration]=30&flashvars[leadWithHTML5]=true&flashvars[forceMobileHTML5]=true&flashvars[nextPrevBtn.plugin]=true&flashvars[sideBarContainer.plugin]=true&flashvars[sideBarContainer.position]=left&flashvars[sideBarContainer.clickToClose]=true&flashvars[chapters.plugin]=true&flashvars[chapters.layout]=vertical&flashvars[chapters.thumbnailRotator]=false&flashvars[streamSelector.plugin]=true&flashvars[EmbedPlayer.SpinnerTarget]=videoHolder&flashvars[dualScreen.plugin]=true)), hands-on sessions, and peer-reviewed project work
- next event: December–January
- contact:
    - Ross Church: ross@astro.lu.se
    - Caterina Doglioni: caterina.doglioni@hep.lu.se
    - Mikael Lund: mikael.lund@teokem.lu.se

[](http://www.rdkit.org/)

```python
from rdkit import Chem
from rdkit.Chem.Draw import IPythonConsole
m1 = Chem.MolFromSmiles('n1c2C(=O)NC(N)=Nc2ncc1CNc3ccc(cc3)C(=O)N[C@H](C(O)=O)CCC(O)=O')
m1
```

```python
from rdkit.Chem import Draw
Draw.MolToFile(m1,'aux/folate.svg')
```

```python
m1.GetNumAtoms()
```

    32

```python
Chem.MolToSmiles(m1)
```

    'Nc1nc2ncc(CNc3ccc(C(=O)N[C@@H](CCC(=O)O)C(=O)O)cc3)nc2c(=O)[nH]1'

```python
m2 = Chem.AddHs(m1) # add hydrogens
m2
```

```python
from rdkit.Chem import AllChem
Chem.AllChem.EmbedMolecule(m2) # make it 3D using ETKDG method
m2
```

```python
import nglview as nv
view = nv.show_rdkit(m2)
view
```

    NGLWidget()

```python
print(Chem.MolToMolBlock(m2),file=open('aux/folate.mol','w+'))
```

```python

view = nv.show_file('aux/folate.mol')
view
```

    NGLWidget()

```python
import mdtraj as md
s = md.load('aux/4mqj.pdb')
print('Number of atoms:', s.n_atoms)
print('Number of residues:', s.n_residues)
chains = [chain for chain in s.top.chains]
n_chains = len(chains)
print('Number of chains:', n_chains)
s14 = s.atom_slice(s.top.select('all and chainid < 4'))
print('Radius of gyration:',md.compute_rg(s14)[0],'nm')
```

    Number of atoms: 10369
    Number of residues: 2386
    Number of chains: 24
    Radius of gyration: 2.3107479678330973 nm

```python
import nglview as nv
view = nv.show_pdbid('4mqj')
view
```

    NGLWidget()

```python
import matplotlib as mpl
view = nv.show_mdtraj(s)
view.clear_representations(component=0)
for i in range(4):
    chain = [a.index for a in s.top.chain(i).atoms]
    view.add_representation('spacefill', selection=chain, color=mpl.colors.to_hex(plt.cm.tab20(i)))
view
```

    NGLWidget()

```python
def viewColorScheme(molecule,dataframe):
    dataframe = dataframe.copy()
    dataframe['Abbr.'] = dataframe['Abbr.'].str.upper()
    dataframe.set_index('Abbr.', drop=True, inplace=True)
    dd = dataframe.dropna()
    dataframe = dataframe.fillna(0)
    preg = (dd['pKx3']-dd['pKx3'].min()) / (dd['pKx3'].max() - dd['pKx3'].min())
    colorscheme = pd.Series([mpl.colors.to_hex(c) for c in plt.cm.rainbow_r(preg)],index=preg.index)
    view = nv.show_mdtraj(molecule)
    view.clear_representations(component=0)
    for res in [res for chain in chains[:4] for res in chain.residues ]:
        atoms = [a.index for a in res.atoms]
        if dataframe.loc[res.name]['pKx3'] == 0:
            view.add_spacefill(selection=atoms, color='#ffffff')
        else:
            view.add_spacefill(selection=atoms, color=colorscheme[res.name])
    view.camera = 'orthographic'
    return view
```

```python
viewColorScheme(s,df)
```

    NGLWidget()

### How to get started

The installation is simple and quick!

- Install [miniconda](https://docs.conda.io/en/latest/miniconda.html)
- miniconda is the light version of anaconda, a package manager that runs on Windows, Mac and Linux

#### On Mac or Linux

- Download the installation script for your operating system
    - using the terminal: `curl -O https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh`
- Install by running the script:
    - type and enter `bash Miniconda3-latest-MacOSX-x86_64.sh`
- Create a `conda` environment with Python 3.7 (the latest version):
    - type and enter `conda create -n myenv python`, myenv is the name of the environment (any name works)
- Activate the environment:
    - `source activate myenv`
- Install notebook, numpy, pandas, matplotlib, scipy:
    - `conda install notebook numpy pandas matplotlib scipy`
- Install RDkit, mdtraj, nglview, ipywidgets
    - we need to specify the channel: `conda install -c conda-forge rdkit mdtraj nglview ipywidgets`
- launch Jupyter notebook: `jupyter-notebook`

#### On Windows

- Download the installation executable for your operating system
- Install by running the `.exe` file
- Create a new `conda` environment with Python 3.7 (the latest version):
    - open the anaconda prompt from the start menu and navigate to the folder where the course material has been unzipped (e.g. using cd to change directory and dir to list files in a folder)
    - type: `conda create -n myenv python`, myenv is the name of the environment (any name works)
- Activate the environment:
    - `activate myenv`
- Install notebook, numpy, pandas, matplotlib, scipy:
    - `conda install notebook numpy pandas matplotlib scipy`
- Install RDkit, mdtraj, nglview
    - we need to specify the channel: `conda install -c conda-forge rdkit mdtraj nglview ipywidgets`
- launch Jupyter notebook: `jupyter-notebook`

```python
from IPython.display import IFrame
IFrame(src='https://www.youtube.com/embed/HW29067qVWk', width=640, height=400)
```

### How to share my notebooks to help other scientists to reproduce my analyses

- [Ten simple rules for writing and sharing computational analyses in Jupyter Notebooks](https://doi.org/10.1371/journal.pcbi.1007007)

- Saved as an HTML file and provided as Supporting Information

- It is important to provide the list of packages needed to run the notebook:
    - create a conda environment for every project
    - export the conda environment to a yml file: `conda env export > environment.yml`
    - other scientists can quickly reproduce your environment: `conda env create -f environment.yml`

- `notebook.ipynb` + data + `environment.yml` in a zip file as Supporting Information

- Example: [refnx: neutron and X-ray reflectometry analysis in Python](http://scripts.iucr.org/cgi-bin/paper?rg5158)

### How to share my notebooks to help other scientists to reproduce my analyses

- [Create a GitHub repository](https://help.github.com/en/github/getting-started-with-github/create-a-repo)

- Upload your notebook and `environment.yml`

- [myBinder](https://mybinder.readthedocs.io/en/latest/introduction.html) allows you to run the notebook in the repository on a server: no need to download and install

- Example: [refnx: neutron and X-ray reflectometry analysis in Python](https://github.com/refnx/refnx)

[](https://reproducible-science-curriculum.github.io/sharing-RR-Jupyter/01-sharing-github/)

To convert a notebook into slides in pdf format:
1. `jupyter nbconvert --to slides jupyter.ipynb --post serve`
2. `conda install nodejs`
3. `npm install -g decktape`
4. copy `reveal.js/` and `custom.css` in the notebook directory (Developer Tools in Chrome)
5. modify `reveal.js/js/reveal.js` so that: width: "90%", height: "90%", margin: 0, minScale: 1 and maxScale: 1
6. convert html to pdf: `decktape jupyter.slides.html jupyter.pdf`