```python
%%html

<p> Code is hidden for ease of viewing. Click the Show/Hide button to see. </>
<form action="javascript:code_toggle()"><input type="submit" id="toggleButton" value="Show Code"></form>
```

 Code is hidden for ease of viewing. Click the Show/Hide button to see. </>
<form action="javascript:code_toggle()"><input type="submit" id="toggleButton" value="Show Code"></form>

```python
import random
import math
import scipy as sp
import scipy.stats as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from numpy import linspace
from scipy.stats import truncnorm
from ipywidgets import widgets, interact, Layout, Button, Box, interact_manual, fixed
from IPython.display import display, Markdown, Javascript, HTML
```

```python
# Function: obtains z-value to use given confidence level
# Input: confidence level (enter value from 0 to 1.00)
# Output: positive z-value

def get_z_value(confidence_level):

    z_value_to_obtain = 1 - ((1-confidence_level)/2)

    z_value = st.norm.ppf(z_value_to_obtain)

    return round( z_value , 4 )

# Function: calculate confidence inteval using 95% confidence level
# Input: data set (as a list), confidence level, and a string for output 'confidence interval','mean','std','error'
# Output: confidence interval
# Example: calculate_confidence_interval([12,19,32,1,9,10,23,23,12,19],'confidence interval')

def calculate_confidence_interval(data_set,get_output,confidence_level):

    # Associated z-value with 95% confidence level

    z = get_z_value(confidence_level)

    # Convert data set

    data_set = np.array(data_set)

    # Data set parameters

    n = data_set.size
    x = np.mean(data_set)
    s = np.std(data_set)
    moe = z*(s/n**(1/2))

    # Output

    confidence_interval = ( x-z*(s/n**(1/2)) , x+z*(s/n**(1/2)) )

    if(get_output == 'confidence interval'):

        return confidence_interval

    if(get_output == 'mean'):

        return x

    if(get_output == 'std'):

        return s

    if(get_output == 'margin of error'):

        return moe

# Function: modified truncated normal
# Input: mean, standard deviation, lower bound, upper bound
# Output: truncated normal value
# Example: get_truncated_normal(mean=8,sd=2,low=1,upp=10)

def get_truncated_normal(mean=0, sd=1, low=0, upp=10):

    return truncnorm( (low - mean) / sd, (upp - mean) / sd, loc=mean, scale=sd)

# Function: generate data points that are noramlly distributed
# Input: mean, standard deviation, lower bound, upper bound, number of data points
# Output: list of data points where the data follow a normal distribution
# Example: generate_data_points(175,15,150,200,100)

def generate_data_values(mean,st_dev,lower_bd,uper_bd,data_pts):

    x = get_truncated_normal(mean=mean,sd=st_dev,low=lower_bd,upp=uper_bd)

    return x.rvs(data_pts)

# Function: create a list of intervals
# Input: start value, size of interval, number of intervals
# Output: list of intervals with fixed sizes
# Example: create_intervals(150,2.5,20)

def create_intervals(start,interval_size,number_of_intervals):

    list_of_intervals = []

    for i in range(number_of_intervals):

        current_interval = [ start + i*interval_size , start + (i+1)*interval_size ]

        list_of_intervals.append(current_interval)

    return list_of_intervals

# Function: create a dictionary with interval as keys
# Input: list of intervals
# Output: dictionary, with keys as intervals, values intiated at 0
# Example: interval_dictionary( [ [1,2] , [3,4] ])

def interval_dict(interval_list):

    interval_item_count = {}

    for interval in interval_list:

        interval_key = "[" + str(interval[0]) + "," + str(interval[1]) + "]"

        interval_item_count[interval_key] = 0

    return interval_item_count

# Function: counts the number of values that belong in a certain interval
# Input: values of data, list of intervals, dictionary with intervals as key
# Ouput: dictionary with interval as key and value as the number of times a value in the data set lives in that interval

def interval_value_count(data_values,interval_list,interval_dict):

    for value in data_values:

        for interval in interval_list:

            lower_bd = interval[0]
            upper_bd = interval[1]

            interval_key = "[" + str(lower_bd) + "," + str(upper_bd) + "]"

            if(lower_bd <= value and value < upper_bd):

                interval_dict[interval_key] += 1

    return interval_dict

# Function: plots confidence intervals, showing where the real mean lies and showing intervals that "miss" the mean
# Input: data set, lower bound and upper bound of data set, sample set, iteration (number of intervals to generate)
# Output: confidence interval plots

def ci_plot(data, lower_bound, upper_bound, sample, iterations, print_statement,confidence_level):

    fig = plt.figure(figsize=(20, 0.75*iterations))
    ax = fig.add_subplot(111)

    # Obtain data set statistics

    data = np.array(data)
    data_mean = data.mean()

    # We could use the data set's minimum and maximum to set as x_min and x_max, but we've set it to be 150,200
    # in this case, just to make the plot visually appealing.
    # (i.e. the dataset is randomly generated from 150-200, however, we might have 151.3,198.7 as min,max.

    xmin = lower_bound
    xmax = upper_bound

    # Plot confidence intervals

    y = 2*iterations
    counter = 0

    for i in range(iterations):

        sample_set = random.sample(data.tolist(), sample)
        confidence_interval = calculate_confidence_interval(sample_set, 'confidence interval',confidence_level)
        plt.hlines(0.75*i , xmin, confidence_interval[0])
        plt.hlines(0.75*i, confidence_interval[1], xmax)

        if (data_mean < confidence_interval[0] or data_mean > confidence_interval[1]):

            plt.hlines(0.75*i, confidence_interval[0], confidence_interval[1], color = 'r', linewidth = 4)
            plt.text(confidence_interval[0], 0.75*i -0.1, '(', horizontalalignment='center',  fontsize = 25, color = 'r', weight = 'bold')
            plt.text(confidence_interval[1],  0.75*i -0.1, ')', horizontalalignment='center', fontsize = 25, color = 'r', weight = 'bold')
            counter += 1
        else:

            plt.hlines(0.75*i, confidence_interval[0], confidence_interval[1], color = 'g', linewidth = 4)
            plt.text(confidence_interval[0], 0.75*i -0.1, '(', horizontalalignment='center',  fontsize = 25, color = 'g', weight = 'bold')
            plt.text(confidence_interval[1],  0.75*i -0.1, ')', horizontalalignment='center', fontsize = 25, color = 'g', weight = 'bold')

        ci_min = str("{0:.2f}".format(confidence_interval[0]))
        ci_max = str("{0:.2f}".format(confidence_interval[1]))

        plt.text(confidence_interval[0], 0.75*i -0.4, ci_min, horizontalalignment='right',  fontsize = 12)
        plt.text(confidence_interval[1],  0.75*i -0.4, ci_max, horizontalalignment='left', fontsize = 12)

        plt.text(lower_bound - 3, 0.75*i, lower_bound, verticalalignment = 'center', fontsize = 15)
        plt.text(upper_bound + 2, 0.75*i, upper_bound, verticalalignment = 'center', fontsize = 15)

    if (print_statement == True):

        percentage_containing_true = float("{0:.2f}".format((iterations-counter)/iterations))*100
        percentage_not_containing_true = (100 - percentage_containing_true)

        display(Markdown("<center>" + str(round(percentage_containing_true,2)) +"% of the confidence intervals contain the true mean value. That is, "
                         + str(round(percentage_not_containing_true,2)) + "% do not. <center>"))

    plt.text(data_mean, 0.75*iterations+0.25 , str("{0:.2f}".format(data_mean)), horizontalalignment = 'center', fontsize = 15, color = 'b')
    plt.vlines(data_mean, -0.25, iterations, linestyle = '--', color = 'b', linewidth = 3)
    ax.set_xlim(lower_bound-5,upper_bound+10)
    ax.set_ylim(-.25,0.75*iterations)
    plt.axis('off')
    plt.show()

# Function: executes the cell below on click event using a Jupyter button widget

def run_cells(ev):

    display(Javascript('IPython.notebook.execute_cell_range(IPython.notebook.get_selected_index()+1,IPython.notebook.get_selected_index()+2)'))

# Function: executes the cell above on click event using Jupyter button widget
# Note: I tried parametrizing this function by allowing for which range of cells to run, but the JS command
#       does not work for some reason.

def run_cell_above(ev):

    display(Javascript('IPython.notebook.execute_cell_range(IPython.notebook.get_selected_index()-1,IPython.notebook.get_selected_index())'))

# Function: executes the cell previous on click event using a Jupyter button widget

def run_prev(ev):

    display(Javascript('IPython.notebook.execute_cell_range(IPython.notebook.get_selected_index()-2,IPython.notebook.get_selected_index()+0)'))

def run_above_current(ev):

    display(Javascript('IPython.notebook.execute_cell_range(IPython.notebook.get_selected_index()-2,IPython.notebook.get_selected_index()+1)'))

# Function: checks if input is float
# Input: string
# Output: boolean

def isfloat(val):

    try:

        float(val)

        return True

    except ValueError:

        return False

# Function: check if input is positive float
# Input: string
# Output: 1 or 0

def is_positive_float(val):

    if(isfloat(val) == True and float(val) >= 0):

        return 1

    else:

        return 0
```

```python
# Parameters for data generation

data_points = 25000
mean = 175
standard_error = 15
lower_bound = 150
upper_bound = 200

# Functions to call to set up dictionary containing height intervals and population count

data_values = generate_data_values(mean,standard_error,lower_bound,upper_bound,data_points)
interval_list = create_intervals(150,2.5,20)
interval_dictionary = interval_dict(interval_list)
data_dict = interval_value_count(data_values,interval_list,interval_dictionary)

# Note:
# This interval is added with 0 value to get the last ticker value of 200 to show up in the bar plot later
# It's for improving the presentation of the bar plot

data_dict['200.0,202.25'] = 0
```

# Interpreting Statistical Data

## Introduction

Statistical reasoning plays an increasingly large role in everyday's life, from politics (opinion polls) to sports (statistics about sports teams), through many news about health or economy for example. The goal of this notebook is to introduce and illustrate two important notions about interpreting statistical data, the notions of confidence interval and confidence level.

This notebook is split into two parts. The first part is a short lecture about the statistical concepts we want to work with. The second one  provides some interactive tools to experiment with them and illustrates the use of these concepts in some real-world applications.

## Part 1. Statistical Concepts

### 1.A. Mean and Standard Deviation

We first introduce well known concepts, mean and standard deviation.

Definition: mean. Given a data set of $N$ numbers $\{x_1,x_2,\dots,x_N\}$, we denote the **mean** of the data set $\overline{x}$ (pronounced "x bar") to be
$$ $$
$$\overline{x} = \frac{x_1+x_2+\cdots+x_N}{N}.$$

**Example. ** Suppose we have the following set of data: $\{13, 19, 7, 3\}$. Here, we have $4$ numbers, so $N=4$. The mean of this data set is given by:

$$\begin{align}
\overline{x} &= \frac{x_1+x_2+x_3+x_4}{4} \\
&= \frac{13+19+7+3}{4} \\
&= 10.5
\end{align}$$

```python
# Generate random mean for question

expected_answer = round( random.random()*100 , 1)

# Display exercise prompt

display(Markdown("**Exercise. ** Produce a data set with five different positive distinct values with a mean of " + "$" + str(expected_answer) + "$"))
display(Markdown("In the input box below, separate each of your values with a comma."))
display(Markdown("For example: `52.7, 39.2, 11.3, 42.1 , 56.5`"))
```

**Exercise. ** Produce a data set with five different positive distinct values with a mean of $61.2$

In the input box below, separate each of your values with a comma.

For example: `52.7, 39.2, 11.3, 42.1 , 56.5`

```python
# Set up text and button widget parameters

mean_exercise_text = widgets.Text( placeholder='Enter your numbers here.', description='', disabled=False , layout=Layout(width='40%') )
mean_exercise_button = widgets.Button( button_style='info',description="Enter", layout=Layout(width='20%', height='30px') )
generate_new_mean = widgets.Button( button_style='info',description="Generate New Exercise", layout=Layout(width='20%', height='30px') )

# Display widgets

display(mean_exercise_text)
display(mean_exercise_button)
display(generate_new_mean)

# On button click, execute the next cell

mean_exercise_button.on_click( run_cells )
generate_new_mean.on_click( run_prev )
```

    Text(value='', layout=Layout(width='40%'), placeholder='Enter your numbers here.')

    Button(button_style='info', description='Enter', layout=Layout(height='30px', width='20%'), style=ButtonStyle(…

    Button(button_style='info', description='Generate New Exercise', layout=Layout(height='30px', width='20%'), st…

```python
# Obtain user input

user_input = mean_exercise_text.value
user_entries = user_input.split(",")

# Check user input validity

input_valid = 1
user_input_total = 0
user_input_set = set()

# Check for number of entries

if(len(user_entries) > 5):

    display(Markdown("Too much entries. Please enter five distinct decimal values."))
    input_valid = 0

if(user_input != ""):

    # Check entries are of positive decimal values only

    for entry in user_entries:

        if(is_positive_float(entry) == False):

            display(Markdown("`" + entry + "`" + " is not a valid input. Please enter positive decimal values only."))
            input_valid = 0

        else:

            user_input_total += float(entry)
            user_input_set.add(float(entry))

    # Check if five distinct positive decimal entries

    if(input_valid == 1 and len(user_input_set) < 5):

        display(Markdown("Please enter five distinct positive decimal values only."))
        input_valid = 0

    # Prepare string output format

    string_out = ""

    for entry in user_input_set:

        string_out += "$" + str(entry) + "$" + ", "

    string_out = string_out[0:len(string_out) - 2]

    # Check user mean

    user_mean = round( user_input_total/5 , 2 )
    user_mean_str = "$" + str(user_mean) + "$"

    if(input_valid == 1 and user_mean == expected_answer):

        display(Markdown("Your data set " + string_out + " has a mean value of " + user_mean_str + " which is the expected mean value!"))
        mean_exercise_button.close()
        mean_exercise_text.close()

    elif(input_valid == 1 and user_mean != expected_answer):

        display(Markdown("Your data set " + string_out + " has a mean value of " + user_mean_str + ". The expected mean value is " + "$" + str(expected_answer) + "$" + " Please try again."))
```

$$\cdots$$

Definition: standard deviation. Given a data set of $N$ numbers $\{x_1,x_2,\dots,x_N\}$, we define the **standard deviation** of the data set, that we denote by $s$ as follows:
$$\displaystyle s = \sqrt{ \frac{1}{N}\left( (x_1-\overline{x})^2 + (x_2-\overline{x})^2 + \cdots + (x_N-\overline{x})^2 \right) }.$$

**Example. ** Suppose again that we have the following set of data: $\{13, 19, 7, 3\}$. In the previous example, we've calculated $N=4$ and $\overline{x} = 10.5$. The standard deviation of this data set is given to be:

$$\begin{align} s &= \sqrt{ \frac{1}{4}\left( (x_1-\overline{x})^2 + (x_2-\overline{x})^2 + (x_3-\overline{x})^2 + + (x_4-\overline{x})^2 \right) }  \\
&= \sqrt{ \frac{1}{4}\left( (13 - 10.5)^2 + (19-10.5)^2 + (7-10.5)^2 + (3-10.5)^2 \right) } \\
&= \sqrt{ \frac{1}{4}\left( 6.25 + 72.5 + 12.25 + 56.25 \right) } \\
&= \sqrt{ \frac{147.25}{4} } \\
&\approx 6.06733\dots \\
\end{align}$$

**Significance. **
The standard deviation complements the mean by telling us how much we can expect that an element of our data set deviates from the mean. The mean alone does not differentiate between the case where all elements of our data set have the same value (for example $\{2,2,2,2\}$ with mean $2$ and standard deviation $0$) or have value alternating around the mean ($\{1,3,1,3\}$ has also mean $2$ but standard deviation $1$).

The standard deviation of a data set is a measure that quantifies the amount of variation in the data set. What a low standard deviation says is that the data points tend to be closer to the mean of the data set, whereas a high standard deviation says that the data points are more spread apart and take over a wider range of values that are further from the mean.

**Exercise: ** Produce a data set of four values with a mean value of $5$ and standard deviation of $2.24$.

**Hint: ** Use integer values only.

In the input box below, separate each of your values with a comma.

Example: `1, 7, 8, 9`

```python
# Set up text and button widget parameters

std_exercise_text = widgets.Text( placeholder='Enter your numbers here.', description='', disabled=False , layout=Layout(width='40%') )
std_exercise_button = widgets.Button( button_style='info',description="Enter", layout=Layout(width='20%', height='30px') )

# Display widgets

display(std_exercise_text)
display(std_exercise_button)

# On button click, execute the next cell

std_exercise_button.on_click( run_cells )

# Vector holding mean, std values

mean_vector = []
std_vector = []
dataset_vector = []
```

    Text(value='', layout=Layout(width='40%'), placeholder='Enter your numbers here.')

    Button(button_style='info', description='Enter', layout=Layout(height='30px', width='20%'), style=ButtonStyle(…

```python
# Expected input

expected_mean = 5
expected_std = 2.24

# Obtain user input

user_input = std_exercise_text.value
user_entries = user_input.split(",")

# Check user input validity

input_valid = 1
user_input_total = 0
user_input_set = set()

# Check for number of entries

if(len(user_entries) > 4):

    display(Markdown("Too much entries. Please enter four decimal values only."))
    input_valid = 0

if(user_input != ""):

    # Check entries are of positive decimal values only

    for entry in user_entries:

        if(is_positive_float(entry) == False):

            display(Markdown("`" + entry + "`" + " is not a valid input. Please enter positive decimal values only."))
            input_valid = 0

        else:

            user_input_total += float(entry)
            user_input_set.add(float(entry))

    # Check if five distinct positive decimal entries

    if(input_valid == 1 and len(user_input_set) < 4):

        display(Markdown("Please enter four distinct positive decimal values only."))
        input_valid = 0

    if(input_valid == 1):

        # Calculate user mean

        user_mean = 0

        for entry in user_entries:

            user_mean += float(entry)

        user_mean = round( user_mean/len(user_entries) , 2 )

        # Calculate user STD

        total_sum = 0

        for entry in user_entries:

            entry = float(entry)

            total_sum += (entry-user_mean)**2

        user_std = round( (total_sum/len(user_entries))**(1/2) , 2 )

        # Correct input

        if(input_valid == 1 and user_mean == expected_mean and user_std == expected_std):

            display(Markdown("Your dataset has the expected mean of $5$ and standard deviation of $2.58!$"))

            # Close tabs

            std_exercise_button.close()
            std_exercise_text.close()

        # Display values

        elif(input_valid == 1 and user_mean != expected_mean and user_std != expected_std):

            wrong_mean_message = "Your data set has a mean of " + "$" + str(user_mean) + "$" + " which does not equal the expected mean of $5$."

            display(Markdown(wrong_mean_message))
            display(Markdown("Your data set gives the following values: "))
            display(Markdown("Mean: " + "$" + str(user_mean) + "$"))
            display(Markdown("Standard Deviation: " + "$" + str(round(user_std,2)) + "$"))

        elif(input_valid == 1 and user_mean == expected_mean and user_std != expected_std):

            display(Markdown("Your data set gives the following values: "))
            display(Markdown("Mean: " + "$" + str(user_mean) + "$"))
            display(Markdown("Standard Deviation: " + "$" + str(round(user_std,2)) + "$"))

            mean_vector.append(user_mean)
            std_vector.append(user_std)
            dataset_vector.append(user_entries)

            # Check for std the user came up with that is closest to the expected answer

            diff_vector = []

            for entry in std_vector:

                diff = abs(entry-expected_std)
                diff_vector.append( round(diff,2) )

            # Obtain index of minimum difference in the list

            index_of_best = np.argmin(diff_vector)

            # Check if user's current answer is better than their best answer so far

            display(Markdown("**Best input so far: **"))

            current_diff = abs(user_std - expected_std)

            best_dataset = ""

            for entry in dataset_vector[index_of_best]:

                best_dataset += entry + ","

            best_dataset_msg = "Data set with values: " + "$" + str(best_dataset[:-1]) + "$" + " has standard value of " + "$" + str(std_vector[index_of_best]) + "$" + " which is closest to the expected standard value of $2.24.$"

            display(Markdown(best_dataset_msg))
```

### 1.B. Sampling Data

We consider now the following context, inspired by real-world application of statistics. We want to obtain the average (mean) value of a statistic over a very large number of cases. For example, we could be interested in the average height of people in Canada. In order to obtain the *exact answer*, we would need to measure the height of all people living in Canada. This is obviously impossible. So we need to proceed in a different way: we will select a **random sample** of the Canadian population and obtain the height measurements of the people in that sample. And the question that naturally occurs is: how can we deduce anything about the average height of the whole population from this sample? Surprisingly, we will see that we can say something, using the notions of **confidence level**, **confidence interval** and **margin of error**, concepts you might have heard about especially in opinion polls. This is based on non-trivial statistics, and we will not explore the technical aspects, but illustrate these notions with concrete examples.

#### Confidence interval and confidence level
So there exists a quantity $x$ we want to estimate because we can not obtain it exactly (the average height of people living in Canada in our example). We have measured the height of a random sample of $N$ people, let's denote it by  $\{x_1,x_2,\dots,x_N\}$.

The first important point related to sampling data is to estimate the mean of a quantity, that is, we will not compute a single number to approximate $x$, but an **interval** $(x_\text{low},x_\text{high})$. So we decide to lose *precision*, as an interval is less precise than a single number. The key point is that we want to be **confident**  that the value $x$ is in that interval. This notion of confidence is important and we want to quantify it in some way. We would like to be able to say something like "I am $95\%$ confident that the value $x$ is in the interval $(x_\text{low},x_\text{high})$ I computed". This is a sentence we read often with opinion polls, aimed at describing their **accuracy**.

Now, for us, we want to understand how to interpret the expression "$95\%$ confident". In our example about averaging the height of Canadian people, it can be understood as follows: if we were going to repeat *often* (ideally an infinite number of times ... obviously this is impossible) and *independently* (our samples are truly random) the process of
* selecting a random set of $N$ people
* measuring the height of the sampled people, $\{x_1,x_2,\dots,x_N\}$
* using the same method to deduce from $\{x_1,x_2,\dots,x_N\}$ the interval $(x_\text{low},x_\text{high})$

then $95\%$ of the intervals generated as above would contain the exact value $x$ we want to estimate.

So we have **no guarantee** about the precise interval $(x_\text{low},x_\text{high})$ we computed from a specific sample $\{x_1,x_2,\dots,x_N\}$ but we expect it contains $x$ with probability $95\%$.

#### Computing a confidence interval for a given confidence level

The question now is: if we want to have a given confidence level (so we chose it), what is the method to compute $(x_\text{low},x_\text{high})$ from $\{x_1,x_2,\dots,x_N\}$? Let's denote the confidence level by $\beta$, a real number between $0$ and $1$ ($\beta=0.95$ means a confidence level of $95\%$).

The method to compute $(x_\text{low},x_\text{high})$ is as follows:
* Step 1. We calculate the mean $\overline{x}$ of the sampled set $\{x_1,x_2,\dots,x_N\}$, and its standard deviation $s$.
* Step 2. We find the **z-score** $z$ corresponding to the chosen confidence level $\beta$ (we will not discuss in this notebook how these z-scores are obtained, but they can be read in a table, see http://www.statisticshowto.com/tables/z-table/ and we provide a separate notebook to compute it given $\beta$).
* Step 3. Calculate $\displaystyle x_\text{low}=\overline{x}- z\frac{s}{\sqrt{N}}$ and $\displaystyle x_\text{high}= \overline{x}+ z\frac{s}{\sqrt{N}}$.
So our confidence interval is:
$$(x_\text{low},x_\text{high}) = \displaystyle \left(\overline{x}- z\frac{s}{\sqrt{N}} ,\overline{x}+ z\frac{s}{\sqrt{N}} \right).$$

##### Comments.
This formula is actually quite intuitive. We can see that this confidence interval is centred around the mean of the sampled data ($\overline{x}$), with some slack on both sides that depends on the standard deviation, the number of samples and the mysterious z-score. So intuitively, we assume that $\overline{x}$ is more or less our approximation of $x$; this makes sense. But if the standard deviation is large (i.e. we see lots of variation in the sampled measurements) then we widen the interval. Conversely, if we sample many data points (i.e. if $N$ is large) then we shrink the interval (because we divide by $\sqrt{N}$); again this make sense, as the more we sample the more we expect to get a good idea of the quantity we want to approximate. What is nice is that all these ideas combine into a quite simple formula, thanks to the z-score.

##### Example.
Engineers are concerned with designing devices operated by workers to achieve high productivity and comfort. The article "Studies on Ergonomically Designed Alphanumeric Keyboards" (_Human Factors_, 185: 175-187) reports a study of preferred height for a keyboard. A sample of $N=31$ is given and a preferred keyboard height was reported by each of the $31$ sampled typists, with the sample average preferred being $\overline{x} = 80.0$ cm and the standard deviation being $s=2.0$ cm. Using the steps above, how may we obtain a 95% confidence interval for the real mean - the real preferred height for the population of experienced typists.

* Step 1. We know $N=31, \overline{x}=80$, and $s=2$.
* Step 2. Since we want a confidence level of 95%, we obtain an associated z-score of $1.96$.
* Step 3. Calculating the estimates, we have: $\overline{x} \pm 1.96\cdot \frac{s}{\sqrt{n}} = 80 \pm (1.96)\frac{2.0}{\sqrt{31}} = 80.0 \pm 0.7 $. So $x_\text{low} = 79.3$ and $x_\text{high} = 80.7$.

So our confidence interval is $(79.3,80.7)$ and the confidence level of this interval (the probability it contains the mean preferred height for the population of experienced typists) is 95%. we can observe that the interval is quite narrow, thanks to the small standard deviation and the large value of $N$.

#### Margin of error.
Last we introduce the notion of margin of error.

Definition: margin of error. In a confidence interval, we define the value $z\frac{s}{\sqrt{n}}$ to be the **margin of error** of the confidence interval.

So we can see that actually the margin of error is nothing else that the half-size of the interval, based on the standard deviation, the sample size and the z-score associated to the confidence level.

This is exactly how you should understand the notion of margin of error that we see in many opinion polls: they have polled a sample of $N$ people, chosen a confidence level, computed the corresponding confidence interval and report the centre value $\overline{x}$ and the half-size of the interval as margin of error.

## Part 2: Interactive Activities and Real Examples.

## How Does the Sample Size, Confidence Level, and Margin of Error Affect Confidence Intervals?

When calculating confidence interval, margin of error, and determining what confidence level to use, the size of the random sample we use is important.

In the example we develop now, we look at the height distribution of men, among a population size of 25,000 individuals. In an interactive tool below, we will be looking at the impact of the random sample size on the size of the confidence interval and the margin of error.

```python
x_values = data_dict.keys()
y_values = list( data_dict.values() )

# Fix labeling for barchart

x_labels = linspace(150,200, len(y_values) )

# Bar plot

fig = plt.figure( figsize=(20,5) )
ax = fig.add_subplot(111)
bars = plt.bar( x_labels + (1.25) , y_values , 2.5 , hatch = '//' , alpha = 1 , edgecolor = 'black' , color = 'orange' , linewidth=2 )
mean_line = plt.vlines(data_values.mean(), 0, 2000, linestyle = '--', color = 'b', linewidth = 3)
mean_text = plt.text(data_values.mean(), 2150 , 'Mean = ' + str("{0:.2f}".format(data_values.mean())), horizontalalignment = 'center', fontsize = 15, color = 'b')
# Note:
# A dummy bar is intoduced at the right end of the bar plot, this is to have the maximum value of the plot
# on the x-axis show up for presentation purposes.

bars[-1].set_color('white')

# Plot display commands

ax.set_ylim(0,2500)
plt.title("Height Distribution of Men \n (Population Size = 25000)",fontsize = 20)
plt.xlabel('Height (in centimeters)',fontsize = 16)
plt.ylabel('Number of people',fontsize = 16)
plt.xticks(fontsize = 16)
plt.yticks(fontsize = 16)
plt.show()
```

In the bar chart above, each bar represents the number of men from the population that belongs in a certain height range. We have used intervals of size 2.5 to group our countings. For instance, the first bar counts the number of men with height 150-152.5, and we see that they are approximately 500 of the 25,000 population size. The next bar counts the number of men with height 152.5-150, with the count approximately at 600, and so on. The dashed line in blue shows the location of the mean in the bar plot.

Using a 95% confidence level means that if we record the heights of say 1,000 men from the population over and over again and calculate the confidence interval, we expect about 95% of the confidence intervals will obtain the true average height of men in the population.

Try it for yourself!

The interactive tool below generates confidence intervals using 95% confidence levels for a sample size of your choice. The green intervals obtain the true mean whereas red intervals do not. With this tool you can appreciate visually how increasing the number of samples impacts positively the likelihood that the confidence intervals actually contain the mean height.

```python
# Counter to prevent plotting intervals until run interact is clicked

counter_ci = 0
```

```python
# Initialize widgets

style = {'description_width': 'initial'}

sample_size = widgets.IntSlider(value = 2500, min = 20, max = 5000, step = 20, description = 'Sample Size', style=style, readout_format='d')
iterations = widgets.IntSlider(value = 50,min = 5, max = 100, description = 'Number of Intervals', style=style, readout_format='d')
ci_button = widgets.Button(button_style='info', description="Run Interact", layout=Layout(width='15%', height='30px'))

# Display widgets

display(sample_size)
display(iterations)
display(ci_button)

# Plot intervals on click

ci_button.on_click( run_cells )
```

    IntSlider(value=2500, description='Sample Size', max=5000, min=20, step=20, style=SliderStyle(description_widt…

    IntSlider(value=50, description='Number of Intervals', min=5, style=SliderStyle(description_width='initial'))

    Button(button_style='info', description='Run Interact', layout=Layout(height='30px', width='15%'), style=Butto…

```python
# Plot intervals once run interact is clicked

counter_ci += 1

if counter_ci >= 2:
    ci_plot(data_values, lower_bound, upper_bound, sample_size.value , iterations.value, True, 0.95)
```

What happens to the size of the confidence intervals and margin of errors as sample sizes increase? Justify your answer.

```python
# Text widget for this interactives question prompt

samplesize_interp_text = widgets.Textarea( value='', placeholder='', description='', disabled=False , layout=Layout(width='100%', height='100px') )
```

```python
# Counter to question attempts

counter_samplesize_q = 0
```

```python
# Other widgets for this interactive's question prompt

box_layout = Layout(display='flex',
                    flex_flow='row',
                    align_items='stretch',
                    width='100%', justify_content = 'center')

save_button = widgets.Button(button_style='info',description="Save", layout=Layout(width='15%', height='30px'))
save_box = Box(children=[save_button], layout=box_layout)

display(samplesize_interp_text)
display(save_box)

save_button.on_click( run_cells )
```

    Textarea(value='', layout=Layout(height='100px', width='100%'), placeholder='')

    Box(children=(Button(button_style='info', description='Save', layout=Layout(height='30px', width='15%'), style…

```python
def run_edit_cells( b ):
    display(Javascript('IPython.notebook.execute_cell_range(IPython.notebook.get_selected_index()-1,IPython.notebook.get_selected_index())'))
    edit_button.close()

samplesize_interp = samplesize_interp_text.value

edit_button = widgets.Button(button_style='info',description="Edit", layout=Layout(width='15%', height='30px'))

answer_button = widgets.Button(button_style='info',description="Reveal Answer", layout=Layout(width='15%', height='30px'))
options_box = Box(children=[edit_button, answer_button], layout=box_layout)

if ( samplesize_interp != ''):

    samplesize_interp_text.close()
    save_button.close()

    samplesize_interp_text = widgets.Textarea( value= samplesize_interp, placeholder='', description='', disabled=False , layout=Layout(width='100%', height='100px') )

    display(Markdown('<center>' + samplesize_interp + '</center>'))
    display(options_box)

edit_button.on_click( run_edit_cells )
answer_button.on_click(run_cells)
```

```python
counter_samplesize_q += 1

if (counter_samplesize_q >= 2):
    display(Markdown("<b> Expected Answer: </b> "))
    display(Markdown("As sample size increases, confidence intervals and margin of errors decrease."))
    display(Markdown("<b> Explanation: </b> "))
    display(Markdown("By increasing sample size, we are increasing our data set and thus, we can acheive a more precise"
                    + " estimate of the true average height of men in the population. Note that the margin of error is half"
                    + " of the size of the confidence interval. Hence, we see that if margin of error increases, then confidence increases and if margin of error decreases, then confidence intervals decreases."))
```

In the interactive tool below, we will fix our random sample size to be 1,000 individuals and compute 50 confidence intervals. So the only parameter we need to set is the confidence level, anbd we will see how it impacts the margin of error, i.e. the size of the intervals. Indeed the margin of error depends on the sample size (fixed to 1000 here) and the z-value, that gets lower as the confidence level decreases.

Below, we can see that as we choose a higher value for the confidence level, the greater the z-value we need to use, which in turns makes the margin of error term larger, thus making the confidence interval larger. Intuitively, this is what we expect as a larger interval is more likely to contain the exact mean we want to estimate. With a low confidence level, the confidence intervals are very tight, and have a higher probability of not containing the true mean.

```python
# Tried a step size of 0.1, but IntSlider widget does not have this feature

confidence_level_slider = widgets.IntSlider( value = 50, min = 1, max = 99, description = 'Confidence Level', style=style, readout_format='d')
confidence_level_button = widgets.Button(button_style='info', description="Run Interact", layout=Layout(width='15%', height='30px'))

display(confidence_level_slider)
display(confidence_level_button)

confidence_level_button.on_click( run_cells )
```

    IntSlider(value=50, description='Confidence Level', max=99, min=1, style=SliderStyle(description_width='initia…

    Button(button_style='info', description='Run Interact', layout=Layout(height='30px', width='15%'), style=Butto…

```python
# Obtain confidence level and z-values

confidence_level = (confidence_level_slider.value)*(0.01)
z = get_z_value(confidence_level)

# Display

confidence_level_str = str( float(confidence_level)*100)[:-2] + "\%"
message_out = "To obtain a confidence interval with $" + confidence_level_str + "$ confidence level requires a z-value of " + "$" + str(z) + ".$"

display(Markdown(message_out))

# Interval plots

sample_size_value = 1000
number_of_intervals = 50

ci_plot(data_values, lower_bound, upper_bound, sample_size_value , number_of_intervals, True, confidence_level)
```

## Confidence Intervals and Confidence Levels in Media

In 2017, [The Hockey News](https://thehockeynews.com/news/article/front-office-confidence-rankings-part-2-fans-weigh-in-on-how-each-team-is-doing) created a survey asking people to mark each NHL team's front office on a scale of 5 in six different categories: roster building, cap management, draft and develop, trading, free agency, and their overall vision. Over 1300 people contributed in the survey and since not everyone was familiar with every team's management group, each NHL team had roughly 800 to 1000 votes.

The goal was to **rank** the NHL teams in each of the 6 categories. For a given category, this would have been easy if each team had received as many marks as there were people contributing to the survey: it would have been sufficient to rank according to the mean mark. But as noted above, not every participant marked every team, so The Hockey news faced an issue akin to sampling marks, where a participant not marking a team in a category was considered as this (missing) mark being un-sampled. So instead of a mean mark per category for each team, The Hockey News computed a 95% confidence interval. That is, for each of the 30 teams, they  obtained six confidence intervals (one for each ranking category), with 95% certainty that the intervals contains the actual mark value. Now, each of these confidence intervals have their associated margin of error (there are 180 intervals in total) which averaged out to be 0.06, which is quite small.

Take for example the Nashville Predators who ranked first overall out of the 30 teams.

Since the margin of error is about 0.06, the true mark (out of 5) of the team's ability in roster building likely (with probability 95%) lies in the confidence interval:

$$(4.4 - 0.06, 4.4 + 0.06) = (4.34, 4.46).$$

## Confidence Intervals and Margin of Error in Media

```python
# Create nested column labels

inner_columns = ['Men, Sample Size','Men, Mean','Men, Standard Deviation','Women, Sample Size','Women, Mean','Women, Standard Deviation']

# Row names

indices_labels = ['Systolic Blood Pressure','Diastolic Blood Pressure','Total Serum Cholesterol', 'Weight', 'Height', 'Body Mass Index']
dataframe = pd.DataFrame( [ [1623, 128.2, 17.5, 1911,126.5, 20.1] , [1622, 75.6, 9.8, 1910, 72.6, 9.7] , [1544, 192.4, 35.2, 1766, 207.1, 36.7], [1612, 194.0, 33.8, 1984, 157.7, 34.6], [1545, 68.9, 2.7, 1781, 63.4, 2.5], [1545, 28.8, 4.6, 1781, 27.6, 5.9] ] , columns = inner_columns)

# Group the labels as split by " , "

labels = dataframe.columns.str.split(', ', expand=True).values
dataframe.columns = pd.MultiIndex.from_tuples([x for x in labels])
original_index_list = dataframe.index.tolist()

for current_index in range(len(original_index_list)):

    idx = original_index_list.index(current_index)
    original_index_list[current_index] = indices_labels[current_index]

d = dict(selector="th", props=[('text-align', 'left')])
dataframe.index = original_index_list
```

[Boston University of Public Health](http://sphweb.bumc.bu.edu/otlt/MPH-Modules/BS/BS704_Confidence_Intervals/BS704_Confidence_Intervals_print.html) analyzed data of 3,539 participants who attended the 7th Examination of the Offspring Cohort in The Framingham Heart Study. For people participating to the study, several health measures were taken, again with a missing data issue as not all participants submitted their information for each category, an issue similar to the hockey example above. The summarized data set is presented below.

```python
pd.options.display.max_rows = 120

display(dataframe.style.set_properties(**{'width':'12em', 'text-align':'center'})\
    .set_table_styles([d]))
```

        	        Men	        Women

        	        Sample Size	        Mean	        Standard Deviation	        Sample Size	        Mean	        Standard Deviation

                        Systolic Blood Pressure
                        1623
                        128.2
                        17.5
                        1911
                        126.5
                        20.1

                        Diastolic Blood Pressure
                        1622
                        75.6
                        9.8
                        1910
                        72.6
                        9.7

                        Total Serum Cholesterol
                        1544
                        192.4
                        35.2
                        1766
                        207.1
                        36.7

                        Weight
                        1612
                        194
                        33.8
                        1984
                        157.7
                        34.6

                        Height
                        1545
                        68.9
                        2.7
                        1781
                        63.4
                        2.5

                        Body Mass Index
                        1545
                        28.8
                        4.6
                        1781
                        27.6
                        5.9

With 95% confidence, the confidence interval of Men's Systolic Blood Pressure is $\left(127.35, 129.05\right)$ and the margin of error is $0.85$. That is, there is a 95% chance that the actual average of Men's Systolic Blood Pressure in the population belongs in this confidence interval.

```python
def run_edit1_cells( b ):

    display(Javascript('IPython.notebook.execute_cell_range(IPython.notebook.get_selected_index()-1,IPython.notebook.get_selected_index())'))
    edit_button_1.close()

def run_edit2_cells( b ):
    display(Javascript('IPython.notebook.execute_cell_range(IPython.notebook.get_selected_index()-1,IPython.notebook.get_selected_index())'))
    edit_button_2.close()

```

**Exercise 1.**

In the next two exercises, we will be computing confidence intervals with a certain confidence level. In the following input box below, enter the confidence level desired to obtain the appropriate z-value to use.

```python
# Set up text and button widget parameters

get_zv_text = widgets.Text( placeholder='Enter a value between 0 and 100.', description='', disabled=False , layout=Layout(width='25%') )
get_zv_button = widgets.Button(button_style='info',description="Enter", layout=Layout(width='10%', height='30px') )

# Display widgets

display(get_zv_text)
display(get_zv_button)

# On button click, execute the next cell

get_zv_button.on_click( run_cells )
```

    Text(value='', layout=Layout(width='25%'), placeholder='Enter a value between 0 and 100.')

    Button(button_style='info', description='Enter', layout=Layout(height='30px', width='10%'), style=ButtonStyle(…

```python
# Obtain user input

confidence_level = get_zv_text.value

# Check range & display output if value is valid

invalid_input_msg = "Enter a value between 0 and 100."

if(isfloat(confidence_level) == 1):

    confidence_level = float(get_zv_text.value)*(0.01)

    if( (confidence_level > 0) and (confidence_level < 1) ):

        z_value = get_z_value(confidence_level)
        md_print = "The z-value you need to use for a " + "$" + str(get_zv_text.value) + "\%$" + " confidence interval is " + "$" + str(z_value) + ".$"
        display(Markdown( md_print) )

    else:

        display(Markdown(invalid_input_msg))

else:

    display(Markdown(invalid_input_msg))
```

Enter a value between 0 and 100.

**(i)** Calculate the confidence interval of Women's Weight with a $95\%$ confidence level. Round each value to the nearest hundredth.

```python
# Widgets for interval question

box_layout = Layout(display='flex',
                    flex_flow='row',
                    align_items='stretch',
                    width='100%', justify_content = 'center')

# Interval display format

comma_a = widgets.HTMLMath(value="$,$")
open_bracket_a = widgets.HTMLMath(value="$\Big($")
close_bracket_a = widgets.HTMLMath(value="$\Big)$")

# Text input for left and right interval

left_endpoint_text_a = widgets.Textarea( value='', placeholder='', description='', disabled=False , layout=Layout(width='10%', height='32.5px') )
right_endpoint_text_a = widgets.Textarea( value='', placeholder='', description='', disabled=False , layout=Layout(width='10%', height='32.5px') )

# Display widgets

confidence_interval = [open_bracket_a, left_endpoint_text_a, comma_a, right_endpoint_text_a, close_bracket_a]
ci_box = Box(children=confidence_interval, layout=box_layout)
display(ci_box)
submit_button_1 = widgets.Button(button_style='info',description="Submit", layout=Layout(width='15%', height='30px'))
submit_box = Box(children=[submit_button_1], layout=box_layout)
display(submit_box)

# Run next cell on submit click

submit_button_1.on_click( run_cells )

# Question attempt counter

counter_a = 0
```

    Box(children=(HTMLMath(value='$\\Big($'), Textarea(value='', layout=Layout(height='32.5px', width='10%'), plac…

    Box(children=(Button(button_style='info', description='Submit', layout=Layout(height='30px', width='15%'), sty…

```python
# Obtain user's interval values

min_range_a = left_endpoint_text_a.value
max_range_a = right_endpoint_text_a.value

# Fix format

correct_min_range_a = "{0:.2f}".format(157.7-1.96*34.6/math.sqrt(1984))
correct_max_range_a = "{0:.2f}".format(157.7+1.96*34.6/math.sqrt(1984))

if ( min_range_a == correct_min_range_a and max_range_a == correct_max_range_a):

    # Close question prompt widgets

    open_bracket_a.close()
    close_bracket_a.close()
    comma_a.close()
    left_endpoint_text_a.close()
    right_endpoint_text_a.close()
    submit_button_1.close()

    confidence_interval_string = '(' + min_range_a + ',' + max_range_a + ')'

    # Display user answer

    display(Markdown("**You answered: **"))
    display(Markdown("<center>$"+confidence_interval_string+"$ </center>"))
    display(Markdown("This is correct!"))

    # Display expected answer + explanations

    display(Markdown("<b> Expected Answer: </b> "))
    display(Markdown("$(156.18, 159.22)$"))
    display(Markdown("<b> Explanation: </b> "))
    display(Markdown("For Women's weight, the sample size is $N = 1984$, the mean is $\overline x = 157.7$, and the standard deviation is $s = 34.6$."
                     + " Since the z-value for a $95\%$ confidence level is $1.96$, the confidence interval is <center> $\Bigg( 157.7 - 1.96 \cdot \displaystyle "
                     + "{34.6 \over \sqrt{1984} },157.7 + 1.96 \cdot \displaystyle {34.6 \over \sqrt{1984}} \Bigg) = (156.18,159.22)$</center>"))

else:

    # Increment question counter

    counter_a += 1

    if (counter_a != 1):
        incorrect = widgets.HTML(value=" <center>Incorrect! Please try again. </center>")
        display(incorrect)

    # Question attempts maximum limit

if (counter_a > 3):

    # Close question prompt widgets

    open_bracket_a.close()
    close_bracket_a.close()
    comma_a.close()
    left_endpoint_text_a.close()
    right_endpoint_text_a.close()
    submit_button_1.close()
    incorrect.close()

    # Display expected answer + explanation

    display(Markdown("<b> Expected Answer: </b> "))
    display(Markdown("$(156.18, 159.22)$"))
    display(Markdown("<b> Explanation: </b> "))
    display(Markdown("For Women's weight, the sample size is $N = 1984$, the mean is $\overline x = 157.7$, and the standard deviation is $s = 34.6$."
                     + " Since the z-value for a $95\%$ confidence level is $1.96$, the confidence interval is <center> $\Bigg( 157.7 - 1.96 \cdot \displaystyle "
                     + "{34.6 \over \sqrt{1984} },157.7 + 1.96 \cdot \displaystyle {34.6 \over \sqrt{1984}} \Bigg) = (156.18,159.22)$</center>"))
```

**(ii)** Use the terms confidence level, confidence interval and margin of error to interpret the true measurement of women's average weight in Boston.

```python
interpretation_text_a = widgets.Textarea( value='', placeholder='', description='', disabled=False , layout=Layout(width='100%', height='100px') )
```

```python
save_button_1 = widgets.Button(button_style='info',description="Save", layout=Layout(width='15%', height='30px'))
save_box = Box(children=[save_button_1], layout=box_layout)

display(interpretation_text_a)
display(save_box)

save_button_1.on_click( run_cells )
```

    Textarea(value='', layout=Layout(height='100px', width='100%'), placeholder='')

    Box(children=(Button(button_style='info', description='Save', layout=Layout(height='30px', width='15%'), style…

```python
interp_a = interpretation_text_a.value

edit_button_1 = widgets.Button(button_style='info',description="Edit", layout=Layout(width='15%', height='30px'))
edit_box = Box(children=[edit_button_1], layout=box_layout)

if ( interp_a != ''):

    interpretation_text_a.close()
    save_button_1.close()

    interpretation_text_a = widgets.Textarea( value= interp_a, placeholder='', description='', disabled=False , layout=Layout(width='100%', height='100px') )

    display(Markdown('<center>' + interp_a + '</center>'))
    display(edit_box)

edit_button_1.on_click( run_edit1_cells )
```

**Exercise 2.**

**(i)** Calculate the confidence interval of Men's Body Mass Index (BMI) with a $99\%$ confidence level. Round each value to the nearest hundredth.

```python
# Text display for interval

comma_b = widgets.HTMLMath(value="$,$")
open_bracket_b = widgets.HTMLMath(value="$\Big($")
close_bracket_b = widgets.HTMLMath(value="$\Big)$")

# Text input for user's interval

left_endpoint_text_b = widgets.Textarea( value='', placeholder='', description='', disabled=False , layout=Layout(width='10%', height='32.5px') )
right_endpoint_text_b = widgets.Textarea( value='', placeholder='', description='', disabled=False , layout=Layout(width='10%', height='32.5px') )

# Display widgets

confidence_interval = [open_bracket_b, left_endpoint_text_b, comma_b, right_endpoint_text_b, close_bracket_b]
ci_box = Box(children=confidence_interval, layout=box_layout)
display(ci_box)
submit_button_2 = widgets.Button(button_style='info',description="Submit", layout=Layout(width='15%', height='30px'))
submit_box = Box(children=[submit_button_2], layout=box_layout)
display(submit_box)

# Run next cell on submit click

submit_button_2.on_click( run_cells )

# Question counter

counter_b = 0
```

    Box(children=(HTMLMath(value='$\\Big($'), Textarea(value='', layout=Layout(height='32.5px', width='10%'), plac…

    Box(children=(Button(button_style='info', description='Submit', layout=Layout(height='30px', width='15%'), sty…

```python
# Obtain user's values

min_range_b = left_endpoint_text_b.value
max_range_b = right_endpoint_text_b.value

# z-value for this exercise

z_value = 2.5758

# Format range

correct_min_range_b = "{0:.2f}".format(28.8-z_value*4.6/math.sqrt(1545))
correct_max_range_b = "{0:.2f}".format(28.8+z_value*4.6/math.sqrt(1545))

if ( min_range_b == correct_min_range_b and max_range_b == correct_max_range_b):

    # Close question prompt widget

    open_bracket_b.close()
    close_bracket_b.close()
    comma_b.close()
    left_endpoint_text_b.close()
    right_endpoint_text_b.close()
    submit_button_2.close()

    # Display user answer

    confidence_interval_string = '(' + min_range_b + ',' + max_range_b + ')'
    display(Markdown("**Your answer: **"))
    display(Markdown("<center>$"+confidence_interval_string+"$ </center>"))

    # Display expected answe + explanations

    display(Markdown("<b> Expected Answer: </b> "))
    display(Markdown("$(28.50, 29.10)$")) # Change
    display(Markdown("<b> Explanation: </b> "))
    display(Markdown("For Men's Body Mass Index, the sample size is $N = 1545$, the mean is $\overline x = 28.8$, and the standard deviation is $s = 4.6$."
                     + " Since the z-value for a $99\%$ confidence level is $2.5758$, the confidence interval is:"))
    display(Markdown("<center> $\displaystyle \Bigg( 28.8 - 2.5758 \cdot  {4.6 \over \sqrt{1545} },28.8 + 2.5758 \cdot {4.6 \over \sqrt{1545}} \Bigg) = (28.50,29.10)$</center>"))

else:

    # Increment question counter on failed attempts

    counter_b += 1

    if (counter_b != 1):
        incorrect = widgets.HTML(value="<center>Incorrect! Please try again. </center>")
        display(incorrect)

    # Question attempt maximum limit

if (counter_b > 3):

    # Close question prompts

    open_bracket_b.close()
    close_bracket_b.close()
    comma_b.close()
    left_endpoint_text_b.close()
    right_endpoint_text_b.close()
    submit_button_2.close()

    # Display expected answer + explanation

    incorrect.close()
    display(Markdown("<b> Expected Answer: </b> "))
    display(Markdown("$(28.50, 29.10)$")) # Change
    display(Markdown("<b> Explanation: </b> "))
    display(Markdown("For Men's Body Mass Index, the sample size is $N = 1545$, the mean is $\overline x = 28.8$, and the standard deviation is $s = 4.6$."
                     + " Since the z-value for a $99\%$ confidence level is $2.5758$, the confidence interval is:"))
    display(Markdown("<center> $\displaystyle \Bigg( 28.8 - 2.5758 \cdot  {4.6 \over \sqrt{1545} },28.8 + 2.5758 \cdot {4.6 \over \sqrt{1545}} \Bigg) = (28.50,29.10)$</center>"))
```

**(ii)** Use the terms confidence level, confidence interval and margin of error to interpret the true measurement of Men's average BMI in Boston.

```python
interpretation_text_b = widgets.Textarea( value='', placeholder='', description='', disabled=False , layout=Layout(width='100%', height='100px') )
```

```python
save_button_2 = widgets.Button(button_style='info',description="Save", layout=Layout(width='15%', height='30px'))
save_box = Box(children=[save_button_2], layout=box_layout)

display(interpretation_text_b)
display(save_box)

save_button_2.on_click( run_cells )
```

    Textarea(value='', layout=Layout(height='100px', width='100%'), placeholder='')

    Box(children=(Button(button_style='info', description='Save', layout=Layout(height='30px', width='15%'), style…

```python
interp_b = interpretation_text_b.value

edit_button_2 = widgets.Button(button_style='info',description="Edit", layout=Layout(width='15%', height='30px'))
edit_box = Box(children=[edit_button_2], layout=box_layout)

if ( interp_b != ''):

    interpretation_text_b.close()
    save_button_2.close()

    interpretation_text_b = widgets.Textarea( value= interp_b, placeholder='', description='', disabled=False , layout=Layout(width='100%', height='100px') )

    display(Markdown('<center>' + interp_b + '</center>'))
    display(edit_box)

edit_button_2.on_click( run_edit2_cells )
```

## Analyzing Statistical Data Presented in the Media

According to an Opinion Article in the National Post [Ontario Teachers Are Overpaid](https://nationalpost.com/opinion/allan-richarz-ontarios-teachers-are-overpaid), the article states that "Ontario’s Sunshine List of public-sector employees making over `$`100,000 each year. Thousands of elementary and secondary school teachers, admin and staff make the list; with some teachers reaching as high as `$`133,000 annually" and the article argues that "teachers in the province are overpaid; the work is not that onerous nor specialized and the hours not too taxing."

However, looking at the high school teacher salary in Ontario as reported on [PayScale](https://www.payscale.com/research/CA/Job=High_School_Teacher/Salary/004c9fd6/Toronto-ON), shows that these teacher may not necessarily be overpaid.

It matters first as to which group of workers we are looking at in the industry for instance, some employees more engaged with administrative work such as Principals, Vice Principals, and Treasurers make more than the average secondary school teachers and can drag the average salary up.

It also matters to look at how long they have been working in the industry. A count of 61 entry level teachers (working for 0-5 years) reported an income of `$`52,000 a year and a count of 44 mid-career level teachers (working for 5-10 years) reported an income of `$`56,000 a year.

It is not until we get to the count of 33 experienced teachers (working for 10-15 years) reporting an income of `$`75,000 a year and late career teachers (working for 15-20 years) reporting an income of `$`88,000 that we see comparable incomes as reported by the article.

Unfortunately we cannot obtain a confidence interval, as the individual data values in the data set are not provided publicly by PayScale.

[](https://github.com/callysto/curriculum-notebooks/blob/master/LICENSE.md)


# Finding Primes with the Sieve of Eratosthenes
pronunciation: "air-uh-TAWS-thuh-neeze"

Basic idea is that prime numbers a a precious resource in number land (there are only an infinite number of them!). They are sort of the building blocks of all the integers.

```python
N = 20
candidates = {ii : 'unknown' for ii in range(1,N+1)}
candidates
# use 'unknown' to say we don't know yet if an integer is prime
# use True if it is a prime
# use False if it is not a prime
```

    {1: 'unknown',
     2: 'unknown',
     3: 'unknown',
     4: 'unknown',
     5: 'unknown',
     6: 'unknown',
     7: 'unknown',
     8: 'unknown',
     9: 'unknown',
     10: 'unknown',
     11: 'unknown',
     12: 'unknown',
     13: 'unknown',
     14: 'unknown',
     15: 'unknown',
     16: 'unknown',
     17: 'unknown',
     18: 'unknown',
     19: 'unknown',
     20: 'unknown'}

```python
for candidate_number in candidates:
    if candidates[candidate_number] != 'unknown':
        print(f'''We already think {candidate_number} is {"prime" if candidates[candidate_number] else "not-prime"}''')
        continue
    ans = input(f"is {candidate_number} prime? [y/n/unknown]")
    if ans == 'y':
        candidates[candidate_number] = True
        for ii in range(candidate_number+1, N+1):
            if ii % candidate_number == 0 and candidate_number != 1:
                candidates[ii] = False
    elif ans == 'n':
        candidates[candidate_number] = False
    else:
        candidates[candidate_number] = 'unknown'
        print(f"you entered that you did not know if {candidate_number} was prime. Will have to go back to that one.")

```

    is 1 prime? [y/n/unknown]n
    is 2 prime? [y/n/unknown]y
    is 3 prime? [y/n/unknown]y
    We already think 4 is not-prime
    is 5 prime? [y/n/unknown]y
    We already think 6 is not-prime
    is 7 prime? [y/n/unknown]y
    We already think 8 is not-prime
    We already think 9 is not-prime
    We already think 10 is not-prime
    is 11 prime? [y/n/unknown]y
    We already think 12 is not-prime
    is 13 prime? [y/n/unknown]y
    We already think 14 is not-prime
    We already think 15 is not-prime
    We already think 16 is not-prime
    is 17 prime? [y/n/unknown]y
    We already think 18 is not-prime
    is 19 prime? [y/n/unknown]y
    We already think 20 is not-prime

```python
2 % 2
```

    0

```python
print(f"primes up to {N} are:")
# print(candidates)
[ii for ii in candidates if candidates[ii] == True ]
```

    primes up to 20 are:

    [2, 3, 5, 7, 11, 13, 17, 19]

```python
import numba
```

```python
def is_prime(n: int) -> bool:
    """Primality test using 6k+-1 optimization."""
    if n <= 3:
        return n > 1
    if n % 2 == 0 or n % 3 == 0:
        return False
    i = 5
    while i ** 2 <= n:
        if n % i == 0 or n % (i + 2) == 0:
            return False
        i += 6
    return True
```

```python
@numba.jit
def is_prime1(n: int) -> bool:
    """Primality test using 6k+-1 optimization."""
    if n <= 3:
        return n > 1
    if n % 2 == 0 or n % 3 == 0:
        return False
    i = 5
    while i ** 2 <= n:
        if n % i == 0 or n % (i + 2) == 0:
            return False
        i += 6
    return True

# compile
is_prime1(10001)

```

    False

```python
big = 10293912834928234283
```

```python
%timeit  for ii in range(1000):  is_prime(ii)
```

    1000 loops, best of 5: 590 µs per loop

```python
%timeit for ii in range(1000): is_prime1(ii)
```

    1000 loops, best of 5: 238 µs per loop

```python
%timeit is_prime(big)
```

    100000 loops, best of 5: 12.6 µs per loop

```python
%timeit for ii in range(big, big+10): is_prime1(ii)
```

```python
is_prime1.inspect_llvm()
```

    {(int64,): '; ModuleID = \'is_prime1\'\nsource_filename = "<string>"\ntarget datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"\ntarget triple = "x86_64-unknown-linux-gnu"\n\n@"_ZN08NumbaEnv8__main__13is_prime1$241Ex" = common local_unnamed_addr global i8* null\n@.const.is_prime1 = internal constant [10 x i8] c"is_prime1\\00"\n@PyExc_RuntimeError = external global i8\n@".const.missing Environment: _ZN08NumbaEnv8__main__13is_prime1$241Ex" = internal constant [61 x i8] c"missing Environment: _ZN08NumbaEnv8__main__13is_prime1$241Ex\\00"\n@"_ZN08NumbaEnv5numba7cpython7numbers14int_power_impl12$3clocals$3e13int_power$242Exx" = common local_unnamed_addr global i8* null\n\n; Function Attrs: nofree norecurse nounwind writeonly\ndefine i32 @"_ZN8__main__13is_prime1$241Ex"(i8* noalias nocapture %retptr, { i8*, i32, i8* }** noalias nocapture %excinfo, i64 %arg.n) local_unnamed_addr #0 {\nentry:\n  %.13 = icmp slt i64 %arg.n, 4\n  br i1 %.13, label %B10, label %B18.else.if\n\nB10:                                              ; preds = %entry\n  %.39 = icmp sgt i64 %arg.n, 1\n  %.54 = zext i1 %.39 to i8\n  store i8 %.54, i8* %retptr, align 1\n  ret i32 0\n\nB42:                                              ; preds = %B18.else.if\n  store i8 0, i8* %retptr, align 1\n  ret i32 0\n\nB52:                                              ; preds = %B18.else.if, %11\n  %lsr.iv = phi i64 [ %lsr.iv.next, %11 ], [ -7, %B18.else.if ]\n  %i.2.0 = phi i64 [ %13, %11 ], [ 5, %B18.else.if ]\n  %.570.i = mul nsw i64 %i.2.0, %i.2.0\n  %.293 = icmp sgt i64 %.570.i, %arg.n\n  br i1 %.293, label %B106, label %B64.else.if\n\nB76:                                              ; preds = %24\n  %0 = add i64 %i.2.0, 2\n  %1 = or i64 %arg.n, %0\n  %2 = and i64 %1, -4294967296\n  %3 = icmp eq i64 %2, 0\n  br i1 %3, label %4, label %9\n\n4:                                                ; preds = %B76\n  %5 = trunc i64 %0 to i32\n  %6 = trunc i64 %arg.n to i32\n  %7 = urem i32 %6, %5\n  %8 = zext i32 %7 to i64\n  br label %11\n\n9:                                                ; preds = %B76\n  %10 = srem i64 %arg.n, %0\n  br label %11\n\n11:                                               ; preds = %9, %4\n  %12 = phi i64 [ %8, %4 ], [ %10, %9 ]\n  %.425 = icmp slt i64 %12, 0\n  %.414.0 = select i1 %.425, i64 %lsr.iv, i64 0\n  %.454 = icmp eq i64 %12, %.414.0\n  %lsr.iv.next = add i64 %lsr.iv, -6\n  %13 = add i64 %0, 4\n  br i1 %.454, label %B92, label %B52\n\nB92:                                              ; preds = %11, %24\n  store i8 0, i8* %retptr, align 1\n  ret i32 0\n\nB106:                                             ; preds = %B52\n  store i8 1, i8* %retptr, align 1\n  ret i32 0\n\nB18.else.if:                                      ; preds = %entry\n  %.872 = and i64 %arg.n, 1\n  %.118 = icmp eq i64 %.872, 0\n  %.1673 = urem i64 %arg.n, 3\n  %.198 = icmp eq i64 %.1673, 0\n  %or.cond = or i1 %.118, %.198\n  br i1 %or.cond, label %B42, label %B52\n\nB64.else.if:                                      ; preds = %B52\n  %14 = or i64 %arg.n, %i.2.0\n  %15 = and i64 %14, -4294967296\n  %16 = icmp eq i64 %15, 0\n  br i1 %16, label %17, label %22\n\n17:                                               ; preds = %B64.else.if\n  %18 = trunc i64 %i.2.0 to i32\n  %19 = trunc i64 %arg.n to i32\n  %20 = urem i32 %19, %18\n  %21 = zext i32 %20 to i64\n  br label %24\n\n22:                                               ; preds = %B64.else.if\n  %23 = srem i64 %arg.n, %i.2.0\n  br label %24\n\n24:                                               ; preds = %22, %17\n  %25 = phi i64 [ %21, %17 ], [ %23, %22 ]\n  %.339 = icmp slt i64 %25, 0\n  %26 = add i64 %lsr.iv, 2\n  %.328.0 = select i1 %.339, i64 %26, i64 0\n  %.366 = icmp eq i64 %25, %.328.0\n  br i1 %.366, label %B92, label %B76\n}\n\ndefine i8* @"_ZN7cpython8__main__13is_prime1$241Ex"(i8* nocapture readnone %py_closure, i8* %py_args, i8* nocapture readnone %py_kws) local_unnamed_addr {\nentry:\n  %.5 = alloca i8*, align 8\n  %.6 = call i32 (i8*, i8*, i64, i64, ...) @PyArg_UnpackTuple(i8* %py_args, i8* getelementptr inbounds ([10 x i8], [10 x i8]* @.const.is_prime1, i64 0, i64 0), i64 1, i64 1, i8** nonnull %.5)\n  %.7 = icmp eq i32 %.6, 0\n  br i1 %.7, label %entry.if, label %entry.endif, !prof !0\n\nentry.if:                                         ; preds = %entry.endif.endif.endif, %entry\n  ret i8* null\n\nentry.endif:                                      ; preds = %entry\n  %.11 = load i8*, i8** @"_ZN08NumbaEnv8__main__13is_prime1$241Ex", align 8\n  %.16 = icmp eq i8* %.11, null\n  br i1 %.16, label %entry.endif.if, label %entry.endif.endif, !prof !0\n\nentry.endif.if:                                   ; preds = %entry.endif\n  call void @PyErr_SetString(i8* nonnull @PyExc_RuntimeError, i8* getelementptr inbounds ([61 x i8], [61 x i8]* @".const.missing Environment: _ZN08NumbaEnv8__main__13is_prime1$241Ex", i64 0, i64 0))\n  ret i8* null\n\nentry.endif.endif:                                ; preds = %entry.endif\n  %.20 = load i8*, i8** %.5, align 8\n  %.23 = call i8* @PyNumber_Long(i8* %.20)\n  %.24 = icmp eq i8* %.23, null\n  br i1 %.24, label %entry.endif.endif.endif, label %entry.endif.endif.if, !prof !0\n\nentry.endif.endif.if:                             ; preds = %entry.endif.endif\n  %.26 = call i64 @PyLong_AsLongLong(i8* nonnull %.23)\n  call void @Py_DecRef(i8* nonnull %.23)\n  br label %entry.endif.endif.endif\n\nentry.endif.endif.endif:                          ; preds = %entry.endif.endif, %entry.endif.endif.if\n  %.21.0 = phi i64 [ %.26, %entry.endif.endif.if ], [ 0, %entry.endif.endif ]\n  %.31 = call i8* @PyErr_Occurred()\n  %.32 = icmp eq i8* %.31, null\n  br i1 %.32, label %entry.endif.endif.endif.endif, label %entry.if, !prof !1\n\nentry.endif.endif.endif.endif:                    ; preds = %entry.endif.endif.endif\n  %.13.i = icmp slt i64 %.21.0, 4\n  br i1 %.13.i, label %"_ZN8__main__13is_prime1$241Ex.exit", label %B18.else.if.i\n\nB52.i:                                            ; preds = %B18.else.if.i, %11\n  %lsr.iv = phi i64 [ %lsr.iv.next, %11 ], [ -7, %B18.else.if.i ]\n  %i.2.0.i = phi i64 [ %13, %11 ], [ 5, %B18.else.if.i ]\n  %.570.i.i = mul nsw i64 %i.2.0.i, %i.2.0.i\n  %.293.i = icmp sgt i64 %.570.i.i, %.21.0\n  br i1 %.293.i, label %entry.endif.endif.endif.endif.endif.if.endif, label %B64.else.if.i\n\nB76.i:                                            ; preds = %24\n  %0 = add i64 %i.2.0.i, 2\n  %1 = or i64 %.21.0, %0\n  %2 = and i64 %1, -4294967296\n  %3 = icmp eq i64 %2, 0\n  br i1 %3, label %4, label %9\n\n4:                                                ; preds = %B76.i\n  %5 = trunc i64 %0 to i32\n  %6 = trunc i64 %.21.0 to i32\n  %7 = urem i32 %6, %5\n  %8 = zext i32 %7 to i64\n  br label %11\n\n9:                                                ; preds = %B76.i\n  %10 = srem i64 %.21.0, %0\n  br label %11\n\n11:                                               ; preds = %9, %4\n  %12 = phi i64 [ %8, %4 ], [ %10, %9 ]\n  %.425.i = icmp slt i64 %12, 0\n  %.414.0.i = select i1 %.425.i, i64 %lsr.iv, i64 0\n  %.454.i = icmp eq i64 %12, %.414.0.i\n  %lsr.iv.next = add i64 %lsr.iv, -6\n  %13 = add i64 %0, 4\n  br i1 %.454.i, label %"_ZN8__main__13is_prime1$241Ex.exit.thread", label %B52.i\n\nB18.else.if.i:                                    ; preds = %entry.endif.endif.endif.endif\n  %.872.i = and i64 %.21.0, 1\n  %.118.i = icmp eq i64 %.872.i, 0\n  %.1673.i = urem i64 %.21.0, 3\n  %.198.i = icmp eq i64 %.1673.i, 0\n  %or.cond.i = or i1 %.118.i, %.198.i\n  br i1 %or.cond.i, label %"_ZN8__main__13is_prime1$241Ex.exit.thread", label %B52.i\n\nB64.else.if.i:                                    ; preds = %B52.i\n  %14 = or i64 %.21.0, %i.2.0.i\n  %15 = and i64 %14, -4294967296\n  %16 = icmp eq i64 %15, 0\n  br i1 %16, label %17, label %22\n\n17:                                               ; preds = %B64.else.if.i\n  %18 = trunc i64 %i.2.0.i to i32\n  %19 = trunc i64 %.21.0 to i32\n  %20 = urem i32 %19, %18\n  %21 = zext i32 %20 to i64\n  br label %24\n\n22:                                               ; preds = %B64.else.if.i\n  %23 = srem i64 %.21.0, %i.2.0.i\n  br label %24\n\n24:                                               ; preds = %22, %17\n  %25 = phi i64 [ %21, %17 ], [ %23, %22 ]\n  %.339.i = icmp slt i64 %25, 0\n  %26 = add i64 %lsr.iv, 2\n  %.328.0.i = select i1 %.339.i, i64 %26, i64 0\n  %.366.i = icmp eq i64 %25, %.328.0.i\n  br i1 %.366.i, label %"_ZN8__main__13is_prime1$241Ex.exit.thread", label %B76.i\n\n"_ZN8__main__13is_prime1$241Ex.exit":             ; preds = %entry.endif.endif.endif.endif\n  %.39.i = icmp slt i64 %.21.0, 2\n  br i1 %.39.i, label %"_ZN8__main__13is_prime1$241Ex.exit.thread", label %entry.endif.endif.endif.endif.endif.if.endif\n\n"_ZN8__main__13is_prime1$241Ex.exit.thread":      ; preds = %11, %24, %B18.else.if.i, %"_ZN8__main__13is_prime1$241Ex.exit"\n  br label %entry.endif.endif.endif.endif.endif.if.endif\n\nentry.endif.endif.endif.endif.endif.if.endif:     ; preds = %B52.i, %"_ZN8__main__13is_prime1$241Ex.exit.thread", %"_ZN8__main__13is_prime1$241Ex.exit"\n  %27 = phi i64 [ 0, %"_ZN8__main__13is_prime1$241Ex.exit.thread" ], [ 1, %"_ZN8__main__13is_prime1$241Ex.exit" ], [ 1, %B52.i ]\n  %.65 = call i8* @PyBool_FromLong(i64 %27)\n  ret i8* %.65\n}\n\ndeclare i32 @PyArg_UnpackTuple(i8*, i8*, i64, i64, ...) local_unnamed_addr\n\ndeclare void @PyErr_SetString(i8*, i8*) local_unnamed_addr\n\ndeclare i8* @PyNumber_Long(i8*) local_unnamed_addr\n\ndeclare i64 @PyLong_AsLongLong(i8*) local_unnamed_addr\n\ndeclare void @Py_DecRef(i8*) local_unnamed_addr\n\ndeclare i8* @PyErr_Occurred() local_unnamed_addr\n\ndeclare i8* @PyBool_FromLong(i64) local_unnamed_addr\n\ndefine i1 @"cfunc._ZN8__main__13is_prime1$241Ex"(i64 %.1) local_unnamed_addr {\nentry:\n  %.13.i = icmp slt i64 %.1, 4\n  br i1 %.13.i, label %"_ZN8__main__13is_prime1$241Ex.exit", label %B18.else.if.i\n\nB52.i:                                            ; preds = %B18.else.if.i, %11\n  %lsr.iv = phi i64 [ %lsr.iv.next, %11 ], [ -7, %B18.else.if.i ]\n  %i.2.0.i = phi i64 [ %13, %11 ], [ 5, %B18.else.if.i ]\n  %.570.i.i = mul nsw i64 %i.2.0.i, %i.2.0.i\n  %.293.i = icmp sgt i64 %.570.i.i, %.1\n  br i1 %.293.i, label %entry.endif.endif, label %B64.else.if.i\n\nB76.i:                                            ; preds = %24\n  %0 = add i64 %i.2.0.i, 2\n  %1 = or i64 %.1, %0\n  %2 = and i64 %1, -4294967296\n  %3 = icmp eq i64 %2, 0\n  br i1 %3, label %4, label %9\n\n4:                                                ; preds = %B76.i\n  %5 = trunc i64 %0 to i32\n  %6 = trunc i64 %.1 to i32\n  %7 = urem i32 %6, %5\n  %8 = zext i32 %7 to i64\n  br label %11\n\n9:                                                ; preds = %B76.i\n  %10 = srem i64 %.1, %0\n  br label %11\n\n11:                                               ; preds = %9, %4\n  %12 = phi i64 [ %8, %4 ], [ %10, %9 ]\n  %.425.i = icmp slt i64 %12, 0\n  %.414.0.i = select i1 %.425.i, i64 %lsr.iv, i64 0\n  %.454.i = icmp eq i64 %12, %.414.0.i\n  %lsr.iv.next = add i64 %lsr.iv, -6\n  %13 = add i64 %0, 4\n  br i1 %.454.i, label %"_ZN8__main__13is_prime1$241Ex.exit.thread", label %B52.i\n\nB18.else.if.i:                                    ; preds = %entry\n  %.872.i = and i64 %.1, 1\n  %.118.i = icmp eq i64 %.872.i, 0\n  %.1673.i = urem i64 %.1, 3\n  %.198.i = icmp eq i64 %.1673.i, 0\n  %or.cond.i = or i1 %.118.i, %.198.i\n  br i1 %or.cond.i, label %"_ZN8__main__13is_prime1$241Ex.exit.thread", label %B52.i\n\nB64.else.if.i:                                    ; preds = %B52.i\n  %14 = or i64 %.1, %i.2.0.i\n  %15 = and i64 %14, -4294967296\n  %16 = icmp eq i64 %15, 0\n  br i1 %16, label %17, label %22\n\n17:                                               ; preds = %B64.else.if.i\n  %18 = trunc i64 %i.2.0.i to i32\n  %19 = trunc i64 %.1 to i32\n  %20 = urem i32 %19, %18\n  %21 = zext i32 %20 to i64\n  br label %24\n\n22:                                               ; preds = %B64.else.if.i\n  %23 = srem i64 %.1, %i.2.0.i\n  br label %24\n\n24:                                               ; preds = %22, %17\n  %25 = phi i64 [ %21, %17 ], [ %23, %22 ]\n  %.339.i = icmp slt i64 %25, 0\n  %26 = add i64 %lsr.iv, 2\n  %.328.0.i = select i1 %.339.i, i64 %26, i64 0\n  %.366.i = icmp eq i64 %25, %.328.0.i\n  br i1 %.366.i, label %"_ZN8__main__13is_prime1$241Ex.exit.thread", label %B76.i\n\n"_ZN8__main__13is_prime1$241Ex.exit":             ; preds = %entry\n  %.39.i = icmp slt i64 %.1, 2\n  br i1 %.39.i, label %"_ZN8__main__13is_prime1$241Ex.exit.thread", label %entry.endif.endif\n\n"_ZN8__main__13is_prime1$241Ex.exit.thread":      ; preds = %11, %24, %B18.else.if.i, %"_ZN8__main__13is_prime1$241Ex.exit"\n  br label %entry.endif.endif\n\nentry.endif.endif:                                ; preds = %B52.i, %"_ZN8__main__13is_prime1$241Ex.exit.thread", %"_ZN8__main__13is_prime1$241Ex.exit"\n  %27 = phi i1 [ false, %"_ZN8__main__13is_prime1$241Ex.exit.thread" ], [ true, %"_ZN8__main__13is_prime1$241Ex.exit" ], [ true, %B52.i ]\n  ret i1 %27\n}\n\n; Function Attrs: nounwind\ndeclare void @llvm.stackprotector(i8*, i8**) #1\n\nattributes #0 = { nofree norecurse nounwind writeonly }\nattributes #1 = { nounwind }\n\n!0 = !{!"branch_weights", i32 1, i32 99}\n!1 = !{!"branch_weights", i32 99, i32 1}\n'}

```python
import sympy
```

```python
sympy
```

```python
import mpmath
```

```python
from sympy import init_printing, init_session
init_printing()
init_session()
```

    IPython console for SymPy 1.7.1 (Python 3.7.11-64-bit) (ground types: python)

    These commands were executed:
    >>> from __future__ import division
    >>> from sympy import *
    >>> x, y, z, t = symbols('x y z t')
    >>> k, m, n = symbols('k m n', integer=True)
    >>> f, g, h = symbols('f g h', cls=Function)
    >>> init_printing()

    Documentation can be found at https://docs.sympy.org/1.7.1/

```python
from sympy import sieve
```

```python
1000 in sieve
```

    False

```python
sieve._list
```

    array('l', [2, 3, 5, 7, 11, 13])

```python
1001 in sieve
```

    False

```python
sieve._list
```

    array('l', [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997])

```python
print([ii for ii in sieve.primerange(1,21)])
```

    [2, 3, 5, 7, 11, 13, 17, 19]

```python
from sympy import prime as nthprime
```

```python
nthprime(10)
```

```python
nthprime(100)
```

```python
nthprime(1000)
```

```python
nthprime(10000)
```

```python
import altair as alt
from vega_datasets import data

source = data.cars()

# Configure common options
base = alt.Chart(source).transform_aggregate(
    num_cars='count()',
    groupby=['Origin', 'Cylinders']
).encode(
    alt.X('Cylinders:O', scale=alt.Scale(paddingInner=0)),
    alt.Y('Origin:O', scale=alt.Scale(paddingInner=0)),
)

# Configure heatmap
heatmap = base.mark_rect().encode(
    color=alt.Color('num_cars:Q',
        scale=alt.Scale(scheme='viridis'),
        legend=alt.Legend(direction='horizontal')
    )
)

# Configure text
text = base.mark_text(baseline='middle').encode(
    text='num_cars:Q',
    color=alt.condition(
        alt.datum.num_cars > 100,
        alt.value('black'),
        alt.value('white')
    )
)

# Draw the chart
heatmap + text
```

```python
import numpy as np
from bokeh.plotting import figure, show
from bokeh.io import output_notebook

# Call once to configure Bokeh to display plots inline in the notebook.
output_notebook()
```

```python
from bokeh.io import output_file, show
from bokeh.plotting import figure
from bokeh.sampledata.periodic_table import elements
from bokeh.transform import dodge, factor_cmap

output_file("periodic.html")

periods = ["I", "II", "III", "IV", "V", "VI", "VII"]
groups = [str(x) for x in range(1, 19)]

df = elements.copy()
df["atomic mass"] = df["atomic mass"].astype(str)
df["group"] = df["group"].astype(str)
df["period"] = [periods[x-1] for x in df.period]
df = df[df.group != "-"]
df = df[df.symbol != "Lr"]
df = df[df.symbol != "Lu"]

cmap = {
    "alkali metal"         : "#a6cee3",
    "alkaline earth metal" : "#1f78b4",
    "metal"                : "#d93b43",
    "halogen"              : "#999d9a",
    "metalloid"            : "#e08d49",
    "noble gas"            : "#eaeaea",
    "nonmetal"             : "#f1d4Af",
    "transition metal"     : "#599d7A",
}

TOOLTIPS = [
    ("Name", "@name"),
    ("Atomic number", "@{atomic number}"),
    ("Atomic mass", "@{atomic mass}"),
    ("Type", "@metal"),
    ("CPK color", "$color[hex, swatch]:CPK"),
    ("Electronic configuration", "@{electronic configuration}"),
]

p = figure(title="Periodic Table (omitting LA and AC Series)", plot_width=1000, plot_height=450,
           x_range=groups, y_range=list(reversed(periods)),
           tools="hover", toolbar_location=None, tooltips=TOOLTIPS)

r = p.rect("group", "period", 0.95, 0.95, source=df, fill_alpha=0.6, legend_field="metal",
           color=factor_cmap('metal', palette=list(cmap.values()), factors=list(cmap.keys())))

text_props = {"source": df, "text_align": "left", "text_baseline": "middle"}

x = dodge("group", -0.4, range=p.x_range)

p.text(x=x, y="period", text="symbol", text_font_style="bold", **text_props)

p.text(x=x, y=dodge("period", 0.3, range=p.y_range), text="atomic number",
       text_font_size="11px", **text_props)

p.text(x=x, y=dodge("period", -0.35, range=p.y_range), text="name",
       text_font_size="7px", **text_props)

p.text(x=x, y=dodge("period", -0.2, range=p.y_range), text="atomic mass",
       text_font_size="7px", **text_props)

p.text(x=["3", "3"], y=["VI", "VII"], text=["LA", "AC"], text_align="center", text_baseline="middle")

p.outline_line_color = None
p.grid.grid_line_color = None
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_standoff = 0
p.legend.orientation = "horizontal"
p.legend.location ="top_center"
p.hover.renderers = [r] # only hover element boxes

show(p)
```

```python

```


# Lecture 06

## Integration I:

### Introduction and Integration by Substitution

```python
import numpy as np
import sympy as sp
sp.init_printing()
##################################################
##### Matplotlib boilerplate for consistency #####
##################################################
from ipywidgets import interact
from ipywidgets import FloatSlider
from matplotlib import pyplot as plt

%matplotlib inline

from IPython.display import set_matplotlib_formats
set_matplotlib_formats('svg')

global_fig_width = 10
global_fig_height = global_fig_width / 1.61803399
font_size = 12

plt.rcParams['axes.axisbelow'] = True
plt.rcParams['axes.edgecolor'] = '0.8'
plt.rcParams['axes.grid'] = True
plt.rcParams['axes.labelpad'] = 8
plt.rcParams['axes.linewidth'] = 2
plt.rcParams['axes.titlepad'] = 16.0
plt.rcParams['axes.titlesize'] = font_size * 1.4
plt.rcParams['figure.figsize'] = (global_fig_width, global_fig_height)
plt.rcParams['font.sans-serif'] = ['Computer Modern Sans Serif', 'DejaVu Sans', 'sans-serif']
plt.rcParams['font.size'] = font_size
plt.rcParams['grid.color'] = '0.8'
plt.rcParams['grid.linestyle'] = 'dashed'
plt.rcParams['grid.linewidth'] = 2
plt.rcParams['lines.dash_capstyle'] = 'round'
plt.rcParams['lines.dashed_pattern'] = [1, 4]
plt.rcParams['xtick.labelsize'] = font_size
plt.rcParams['xtick.major.pad'] = 4
plt.rcParams['xtick.major.size'] = 0
plt.rcParams['ytick.labelsize'] = font_size
plt.rcParams['ytick.major.pad'] = 4
plt.rcParams['ytick.major.size'] = 0
##################################################
```

## Wake Up Exercise

$\displaystyle \frac{{\rm d}}{{\rm d}x} 3x^3 =$

$\displaystyle \frac{{\rm d}}{{\rm d}x} {4\over x^4} =$

$\displaystyle \frac{{\rm d}}{{\rm d}x} x^2\sin x =$

$\displaystyle \frac{{\rm d}}{{\rm d}t} \ln t =$

$\displaystyle \frac{{\rm d}}{{\rm d}t} e^{4t} =$

# Integration

**Calculating the area under a curve**

If we want to find the area under a curve, we can divide the area into strips, calculate the area of each strip, and sum these areas together.

e.g. find the area of

$$y = f(x) = x^2\qquad\mbox{between}\qquad x=1\text{ and }x=2$$

```python
import matplotlib.patches as patches
def plot_integration1(boxes):
    def f(x):
        return x**2
    x = np.linspace(0.8,2.2,1000)
    y = f(x)
    plt.plot(x,y)
    plt.plot(x,x*0)
    plt.plot([1,1],[0,f(1)],ls='--',lw=2)
    plt.plot([2,2],[0,f(2)],ls='--',lw=2)
    if len(boxes) > 1:
        box_width = boxes[1]-boxes[0]
    else:
        box_width = 0.1
    for box in boxes:
        rect = patches.Rectangle((box,0),box_width,f(box+box_width),facecolor='k')
        plt.gca().add_patch(rect)
def plot_integration_single(f):
    plot_integration1([f])
def plot_integration_multi(h):
    plot_integration1(np.arange(1,2,h))
```

```python
interact(plot_integration_single, f = FloatSlider(value=1.5,min=1,max=1.9,step=0.1))
```

The area of the strip is $\delta A_i \approx y(x_i)~\delta x$

We can sum up all the strips between $x=1$ and $x=2$ to give an estimate for the total area:

$$A \approx \sum \delta A_i = \sum y(x_i)~\delta x.$$

This becomes more accurate as $\delta x$ becomes smaller.
We can use the same trick as when differentiating: let $\delta x$ tend to $0$:

$$A = \lim_{\delta x \to 0}\sum y(x_i)~\delta x
    = \int\limits^{2}_{1} y~{\rm d}x$$

$\int$ is the old English `S` and stands for the phrase "Sum Over".

This process is called **integration**.

```python
interact(plot_integration_multi, h = FloatSlider(value=0.1,min=0.01,max=0.2,step=0.01))
```

# Calculating the integral

Let us 'invent' a function $F(x)$ that gives the area from $0$ to $x$.
Then $A = F(2) - F(1)$.

With this (imagined) function, we can find the area of one of our tiny steps **exactly**, as

$$F(x + \delta x) - F(x)$$

Remember we approximated this as $\displaystyle y\,\delta x = f(x)\,\delta x$, so:

$$ F(x + \delta x) - F(x) \approx f(x)\delta x$$

$$ f(x) \approx {F(x+\delta x)-F(x)\over\delta x}$$

# Calculating the integral
The error in this approximation tends to $0$ as $\delta x\to 0$, so

$$f(x) = \lim_{\delta x\to 0} {F(x+\delta x)-F(x)\over\delta x} = F'(x)$$

(recalling the definition of the derivative).

In other words, for our example,

$$A(x) = \int\limits^{x}_{1} y(t)~{\rm d}t\qquad\mbox{then}\qquad y(x) = A'(x)$$

**Integration reverses the process of differentiation**.

# Calculating integrals

We know that:
$$~~~~~~~~~~~~~~~~~~~~~~~{{\rm d} \left(x^2\right)\over {\rm d}x}=~2 x~~~~~\Rightarrow~~~~~
\biggr[x^2\biggl]_{x_1}^{x_2} = \int_{x_1}^{x_2} 2 x~{\rm d}x $$

Likewise:

$${{\rm d}\left(\frac{x^3}{3}\right)\over {\rm d}x}=~x^2~~~~~~~\Rightarrow~~~~~~
\int_{x=1}^{x=2} x^2~{\rm d}x = \biggr[{1\over
3}x^3\biggl]_{x=1}^{x=2}={8\over 3}-{1\over 3}= {7\over 3}$$

**In general:**

$${{\rm d}\left(a\thinspace x^{n+1}\right)\over {\rm d}x}=(n+1)a\thinspace~x^n~~~~~
\Rightarrow ~~~~\int_{x_1}^{x_2} a\thinspace x^n~{\rm d}x = \biggr[{a\over
(n+1)} \thinspace x^{(n+1)}\biggl]_{x_1}^{x_2}$$

# SymPy examples

$$\int_{x_1}^{x_2} x^6~{\rm d}x =$$

```python
x, x1, x2 = sp.symbols('x x_1 x_2')
sp.integrate(x**6,(x,x1,x2))
```

$$\int_{x_1}^{x_2} x^{3\over 2}~{\rm d}x =$$

```python
sp.integrate(x**(sp.sympify(3)/2),(x,x1,x2))
```

$$\int_{x_1}^{x_2} x^{-{1\over 2}}~{\rm d}x = $$

```python
sp.integrate(x**(-sp.sympify(1)/2),(x,x1,x2))
```

# Indefinite integrals

Consider now the functions:

$$y=x^2+7 ~~~~~~~~~~{\rm (3)}$$

$$y=x^2-100 ~~~~~~~~~{\rm (4)}$$

Differentiating (3):
$${{\rm d}y\over {\rm d}x}=2x$$

Differentiating (4):
$${{\rm d}y\over {\rm d}x}=2x$$

This implies that the integral:

$$y=\int 2x~dx=x^2 + \kappa~~~~~{\rm where~~}\kappa~~{\rm is~an~unknown~constant}$$

An integral without limits is called an **indefinite integral**.

# Indefinite integrals in SymPy:

```python
x = sp.symbols('x')
sp.integrate(2*x,x)
```

# Other integrals

recall that:

$${{\rm d}\over {\rm d}x}\ln x={1\over x}$$

# Other integrals

recall that:

$${{\rm d}\over {\rm d}x}\ln x={1\over x}~~~~~~~~\Rightarrow~~\int{1\over x}~{\rm d}x=\ln x +
\kappa$$

This constant $\kappa$ can be written as:

$$\kappa=\ln B$$
where $B$ is another constant. Thus:

$$\int {1\over x}~dx = \ln x + \kappa=\ln x +\ln B =\ln (B ~x)$$

# Integrating trigonometric functions

Recall that:

$${{\rm d}\over {\rm d}x}\biggr(\sin x\biggl)= \cos x~~~~~~~~~~~~~{\rm and}~~~~~~~~~~~
{{\rm d}\over {\rm d}x}\biggr(\cos x\biggl)= -\sin x$$

Example (i):
$$\int_{0}^{\pi/2} \cos x \thinspace dx =$$

```python
sp.integrate(sp.cos(x),(x,0,sp.pi/2))
```

Example (ii):
$$\int_{0}^{\pi/2} \sin x \thinspace {\rm d}x =$$

```python
sp.integrate(sp.sin(x),(x,0,sp.pi/2))
```

# Integrating trigonometric functions

Recall that:

$${{\rm d}\over {\rm d}x}\biggr(\sin x\biggl)= \cos x~~~~~~~~~~~~~{\rm and}~~~~~~~~~~~
{{\rm d}\over {\rm d}x}\biggr(\cos x\biggl)= -\sin x$$

Example (i):
$$\int_{0}^{\pi/2} \cos x \thinspace {\rm d}x =
  \biggr[\sin x \biggl]_0^{\pi/2}=1-0=1$$

Example (ii):
$$\int_{0}^{\pi/2} \sin x \thinspace {\rm d}x =
  \biggr[-\cos x \biggl]_0^{\pi/2}=0-(-1)=1$$

# Summary of integration formulae:

$\displaystyle \int a\,{\rm d}x=ax+C$

$\displaystyle \int ax\,{\rm d}x={1\over2}ax^2+C$

$\displaystyle \int ax^n\,{\rm d}x={1\over{n+1}}ax^{n+1}+C\qquad\mbox{for all }n\mbox{ except }n=-1$

$\displaystyle \int x^{-1}\,{\rm d}x=\int {1\over x}\,dx = \ln \vert x\vert +C$

# Application:

Recall that $f'(t)$ gives the rate at
which $f(t)$ changes at time $t$.  Integrating the derivative
$f'(t)$, we see:
$$\int_a^b f'(t) \thinspace {\rm d}t = \biggr[f(t)\biggl]_a^b = f(b)-f(a) $$

Therefore, the definite integral from $a$ to $b$ of $f'(t)$ with
respect to $t$ will always give the **net** change that $f(t)$ has
undergone as the parameter $t$ moves from $a$ to $b$.

## Example:
A chemical process produces NaCl at the rate of $3\sqrt{t}$
grams per minute.  We ask three questions:

1. What is the rate of production one minute into the process?
2. What is the quantity of NaCl produced over the next three minutes?
3. What is the mean rate of NaCl production over this interval?

# Solution:

Let $f(t)$ denote the grams of NaCl
produced after $t$ minutes.  Then $f'(t)= 3\sqrt{t}$.

1. The rate of production one minute into the process is $f'(1) = 3\sqrt{1} = 3$ grams per minute.

2. The quantity of NaCl produced over the next three minutes is
$$f(4)-f(1) = \int_1^4\,f'(t)\,{\rm d}t = \int_1^4\,3\sqrt{t}\,{\rm d}t
            = \phantom{\left[2t^{3/2}\right]_1^4 = 2\times(8-1) = 14}$$

```python
t = sp.Symbol('t')
sp.integrate(3*sp.sqrt(t),(t,1,4))
```

(c) The mean rate is the constant rate which would give the same overall effect:
$${1\over b-a}\int_a^b\,f'(t)\,{\rm d}t = {1\over 4-1}\int_1^4\,3\sqrt{t}\,{\rm d}t
                                      = \phantom{{14\over 3}}$$

```python
sp.integrate(3*sp.sqrt(t),(t,1,4))/(4-1)
```

# Substitution Method

Suppose we wish to integrate:

$$I=\int f(g(x))~{\rm d}x~~~~~~~~~~~{\rm~e.g.}~~\int_{a}^{b} (3-4x)^{-5}~{\rm d}x$$

In general, for $g(x)=u$,
we can write:

$$\int_{x_1}^{x_2}\thinspace f(g(x))~{\rm d}x =
\int_{u_1}^{u_2}\thinspace f(u) {{\rm d}x\over {\rm d}u} {\rm d}u $$

Integral version of the chain rule.

# Examples:

Consider:

$$\int_{a}^{b} (2x + 3)^4~{\rm d}x~~~~~~~~~~~~~{\rm Equation ~(7)}$$
Multiplying this out and then integrating it would be very tedious.
Try a substitution instead:

$$u=2x+3~~~\Longleftrightarrow x={1\over 2}(u-3)={u\over 2}-{3\over 2}~~~~
\Longleftrightarrow {{\rm d}x\over {\rm d}u}={1\over 2}$$

Substitute into Equation (7) above:

$$\int_{u(a)}^{u(b)} u^4 \times {1\over 2}~{\rm d}u ={1\over 2}\int_{u(a)}^{u(b)}
u^4~du = {1\over 2}\biggr[{1\over 5}u^5\biggl]_{u(a)}^{u(b)}=
{1\over 2}\biggr[{1\over 5}(2x+3)^5\biggl]_a^b$$

```python
a, b = sp.symbols('a b')
sp.integrate((2*x + 3)**4,(x,a,b))
```

# Examples:

$$\int_{a}^{b} (3-4x)^{-5}~{\rm d}x~~~~~~~~~~~~~{\rm Equation~(8)}$$

Let:

$$u=3-4x~~~\Longleftrightarrow x={1\over 4}(3-u)={3\over 4}-{u\over 4}~~~~
\Longleftrightarrow {{\rm d}x\over {\rm d}u}=-{1\over 4}$$

Substitute into Equation (8) above:

\begin{align*}
\int_{u(a)}^{u(b)} u^{-5} \times {-1\over 4}~{\rm d}u =
-{1\over 4}\int_{u(a)}^{u(b)} u^{-5}~{\rm d}u &= -{1\over 4}\biggr[{1\over
-4}u^{-4}\biggl]_{u(a)}^{u(b)} \\
&= {1\over 16}\biggr[(3-4x)^{-4}\biggl]_a^b
\end{align*}

```python
sp.integrate((3 - 4*x)**(-5),(x,a,b))
```


This example is kindly contributed by [FreddyBaudine](https://github.com/FreddyBaudine) for reproducing [pygae/galgebra#26](https://github.com/pygae/galgebra/issues/26) and [pygae/galgebra#30](https://github.com/pygae/galgebra/issues/30) with modifications by [utensil](https://github.com/utensil).

Please note before Python code, there's an invisible markdown cell with the following code to enable `color` and define some colors from http://latexcolor.com/:

```markdown
$$
\require{color}
\definecolor{airforceblue}{rgb}{0.36, 0.54, 0.66}
\definecolor{applegreen}{rgb}{0.55, 0.71, 0.0}
\definecolor{atomictangerine}{rgb}{1.0, 0.6, 0.4}
$$
```

$$
\require{color}
\definecolor{airforceblue}{rgb}{0.36, 0.54, 0.66}
\definecolor{applegreen}{rgb}{0.55, 0.71, 0.0}
\definecolor{atomictangerine}{rgb}{1.0, 0.6, 0.4}
$$

```python
from __future__ import print_function
import sys
from galgebra.printer import  Format, xpdf
Format()
from sympy import symbols, sin, pi, latex, Array, permutedims
from galgebra.ga import Ga
```

```python
from IPython.display import Math
```

# Base manifold (three dimensional)
## Metric tensor (cartesian coordinates - norm = False)

```python
from sympy import cos, sin, symbols
g3coords = (x,y,z) = symbols('x y z')
g3 = Ga('ex ey ez', g = [1,1,1], coords = g3coords,norm=False) # Create g3
(e_x,e_y,e_z) = g3.mv()
```

```python
Math(r'g =%s' % latex(g3.g))
```

$\displaystyle g =\left[\begin{matrix}1 & 0 & 0\\0 & 1 & 0\\0 & 0 & 1\end{matrix}\right]$

# Two dimensioanal submanifold - Unit sphere

Basis not normalised

```python
sp2coords = (theta, phi) = symbols(r'{\color{airforceblue}\theta} {\color{applegreen}\phi}', real = True)
sp2param = [sin(theta)*cos(phi), sin(theta)*sin(phi), cos(theta)]

sp2 = g3.sm(sp2param, sp2coords, norm = False) # submanifold

(etheta, ephi) = sp2.mv() # sp2 basis vectors
(rtheta, rphi) = sp2.mvr() # sp2 reciprocal basis vectors

sp2grad = sp2.grad

sph_map = [1, theta, phi]  # Coordinate map for sphere of r = 1
```

```python
Math(r'(\theta,\phi)\rightarrow (r,\theta,\phi) = %s' % latex(sph_map))
```

$\displaystyle (\theta,\phi)\rightarrow (r,\theta,\phi) = \left [ 1, \quad {\color{airforceblue}\theta}, \quad {\color{applegreen}\phi}\right ]$

```python
Math(r'e_\theta \cdot e_\theta = %s' % (etheta|etheta))
```

$\displaystyle e_\theta \cdot e_\theta = 1$

```python
Math(r'e_\phi \cdot e_\phi = %s' % (ephi|ephi))
```

$\displaystyle e_\phi \cdot e_\phi = {\sin{\left ({\color{airforceblue}\theta} \right )}}^{2}$

```python
Math('g = %s' % latex(sp2.g))
```

$\displaystyle g = \left[\begin{matrix}1 & 0\\0 & \sin^{2}{\left ({\color{airforceblue}\theta} \right )}\end{matrix}\right]$

```python
Math(r'g^{-1} = %s' % latex(sp2.g_inv))
```

$\displaystyle g^{-1} = \left[\begin{matrix}1 & 0\\0 & \frac{1}{\sin^{2}{\left ({\color{airforceblue}\theta} \right )}}\end{matrix}\right]$

Christoffel symbols of the first kind:

```python
Cf1 = sp2.Christoffel_symbols(mode=1)
Cf1 = permutedims(Array(Cf1), (2, 0, 1))
```

```python
Math(r'\Gamma_{1, \alpha, \beta} = %s \quad \Gamma_{2, \alpha, \beta} = %s ' % (latex(Cf1[0, :, :]), latex(Cf1[1, :, :])))
```

$\displaystyle \Gamma_{1, \alpha, \beta} = \left[\begin{matrix}0 & 0\\0 & - \frac{\sin{\left (2 {\color{airforceblue}\theta} \right )}}{2}\end{matrix}\right] \quad \Gamma_{2, \alpha, \beta} = \left[\begin{matrix}0 & \frac{\sin{\left (2 {\color{airforceblue}\theta} \right )}}{2}\\\frac{\sin{\left (2 {\color{airforceblue}\theta} \right )}}{2} & 0\end{matrix}\right] $

```python
Cf2 = sp2.Christoffel_symbols(mode=2)
Cf2 = permutedims(Array(Cf2), (2, 0, 1))
```

```python
Math(r'\Gamma^{1}_{\phantom{1,}\alpha, \beta} = %s \quad \Gamma^{2}_{\phantom{2,}\alpha, \beta} = %s ' % (latex(Cf2[0, :, :]), latex(Cf2[1, :, :])))
```

$\displaystyle \Gamma^{1}_{\phantom{1,}\alpha, \beta} = \left[\begin{matrix}0 & 0\\0 & - \frac{\sin{\left (2 {\color{airforceblue}\theta} \right )}}{2}\end{matrix}\right] \quad \Gamma^{2}_{\phantom{2,}\alpha, \beta} = \left[\begin{matrix}0 & \frac{1}{\tan{\left ({\color{airforceblue}\theta} \right )}}\\\frac{1}{\tan{\left ({\color{airforceblue}\theta} \right )}} & 0\end{matrix}\right] $

```python
F = sp2.mv('F','vector',f=True) #scalar function
f = sp2.mv('f','scalar',f=True) #vector function
```

```python
Math(r'\nabla = %s' % sp2grad)
```

$\displaystyle \nabla = \boldsymbol{e}_{{\color{airforceblue}\theta}} \frac{\partial}{\partial {\color{airforceblue}\theta}} + \boldsymbol{e}_{{\color{applegreen}\phi}} \frac{1}{{\sin{\left ({\color{airforceblue}\theta} \right )}}^{2}} \frac{\partial}{\partial {\color{applegreen}\phi}}$

```python
Math(r'\nabla f = %s' % (sp2.grad * f))
```

$\displaystyle \nabla f = \partial_{{\color{airforceblue}\theta}} f  \boldsymbol{e}_{{\color{airforceblue}\theta}} + \frac{\partial_{{\color{applegreen}\phi}} f }{{\sin{\left ({\color{airforceblue}\theta} \right )}}^{2}} \boldsymbol{e}_{{\color{applegreen}\phi}}$

```python
Math(r'F = %s' % F)
```

$\displaystyle F = F^{{\color{airforceblue}\theta}}  \boldsymbol{e}_{{\color{airforceblue}\theta}} + F^{{\color{applegreen}\phi}}  \boldsymbol{e}_{{\color{applegreen}\phi}}$

```python
Math(r'\nabla F = %s' % (sp2.grad * F))
```

$\displaystyle \nabla F = \left ( \frac{F^{{\color{airforceblue}\theta}} }{\tan{\left ({\color{airforceblue}\theta} \right )}} + \partial_{{\color{airforceblue}\theta}} F^{{\color{airforceblue}\theta}}  + \partial_{{\color{applegreen}\phi}} F^{{\color{applegreen}\phi}} \right )  + \left ( \frac{2 F^{{\color{applegreen}\phi}} }{\tan{\left ({\color{airforceblue}\theta} \right )}} + \partial_{{\color{airforceblue}\theta}} F^{{\color{applegreen}\phi}}  - \frac{\partial_{{\color{applegreen}\phi}} F^{{\color{airforceblue}\theta}} }{{\sin{\left ({\color{airforceblue}\theta} \right )}}^{2}}\right ) \boldsymbol{e}_{{\color{airforceblue}\theta}}\wedge \boldsymbol{e}_{{\color{applegreen}\phi}}$

# One dimensioanal submanifold

Basis not normalised

```python
cir_th = phi = symbols(r'{\color{atomictangerine}\phi}',real = True)
cir_map = [pi/8, phi]
```

```python
Math(r'(\phi)\rightarrow (\theta,\phi) = %s' % latex(cir_map))
```

$\displaystyle (\phi)\rightarrow (\theta,\phi) = \left [ \frac{\pi}{8}, \quad {\color{atomictangerine}\phi}\right ]$

```python
cir1d = sp2.sm( cir_map , (cir_th,), norm = False) # submanifold

cir1dgrad = cir1d.grad

(ephi) = cir1d.mv()
```

```python
Math(r'e_\phi \cdot e_\phi = %s' % latex(ephi[0] | ephi[0]))
```

$\displaystyle e_\phi \cdot e_\phi = - \frac{\sqrt{2}}{4} + \frac{1}{2}$

```python
Math('g = %s' % latex(cir1d.g))
```

$\displaystyle g = \left[\begin{matrix}- \frac{\sqrt{2}}{4} + \frac{1}{2}\end{matrix}\right]$

```python
h = cir1d.mv('h','scalar',f= True)

H = cir1d.mv('H','vector',f= True)
```

```python
Math(r'\nabla = %s' % cir1dgrad)
```

$\displaystyle \nabla = \boldsymbol{e}_{{\color{atomictangerine}\phi}} \left ( 2 \sqrt{2} + 4\right ) \frac{\partial}{\partial {\color{atomictangerine}\phi}}$

```python
Math(r'\nabla h = %s' %(cir1d.grad * h).simplify())
```

$\displaystyle \nabla h = \left(2 \sqrt{2} + 4\right) \partial_{{\color{atomictangerine}\phi}} h  \boldsymbol{e}_{{\color{atomictangerine}\phi}}$

```python
Math('H = %s' % H)
```

$\displaystyle H = H^{{\color{atomictangerine}\phi}}  \boldsymbol{e}_{{\color{atomictangerine}\phi}}$

```python
Math(r'\nabla H = %s' % (cir1d.grad * H).simplify())
```

$\displaystyle \nabla H = \partial_{{\color{atomictangerine}\phi}} H^{{\color{atomictangerine}\phi}} $

```python

```