Lambda School Data Science

*Unit 1, Sprint 3, Module 2*

---

# Lambda School Data Science Module 132
## Sampling, Confidence Intervals, and Hypothesis Testing

## Prepare - examine other available hypothesis tests

If you had to pick a single hypothesis test in your toolbox, t-test would probably be the best choice - but the good news is you don't have to pick just one! Here's some of the others to be aware of:

```
# import numpy for numbers work.
import numpy as np
# import chisquare from scipy.stats.
from scipy.stats import chisquare  # One-way chi square test

# chi square can take any crosstab/table and test the independence of rows/cols
# the null hypothesis is that the rows/cols are independent -> low chi square
# the alternative is that there is a dependence -> high chi square
# be aware! Chi square does *not* tell you direction/causation

# create a transverse array.
ind_obs = np.array([[1, 1], [2, 2]]).T
print(ind_obs)
print(chisquare(ind_obs, axis=None))
# crete a a transverse arrray.
dep_obs = np.array([[16, 18, 16, 14, 12, 12], [32, 24, 16, 28, 20, 24]]).T
print(dep_obs)
print(chisquare(dep_obs, axis=None))
```

    [[1 2]
     [1 2]]
    Power_divergenceResult(statistic=0.6666666666666666, pvalue=0.8810148425137847)
    [[16 32]
     [18 24]
     [16 16]
     [14 28]
     [12 20]
     [12 24]]
    Power_divergenceResult(statistic=23.31034482758621, pvalue=0.015975692534127565)

```
# distribution tests:
# we often assume that something is normal, but it can be important to *check*

# for example, later on with predictive modeling, a typical assumption is that
# residuals (prediction errors) are normal - checking is a good diagnostic

# import normaltest from scipy.stats.
from scipy.stats import normaltest
# poisson models arrival times and is related to the binomial (coinflip).
sample = np.random.poisson(5, 1000)
print(normaltest(sample))  # pretty clearly not normal.
```

    NormaltestResult(statistic=27.383778401536198, pvalue=1.1315873224743336e-06)

```
# kruskal-wallis h-test - compare the median rank between 2+ groups.
# can be applied to ranking decisions/outcomes/recommendations.
# The underlying math comes from chi-square distribution, and is best for n>5.
from scipy.stats import kruskal

x1 = [1, 3, 5, 7, 9]
y1 = [2, 4, 6, 8, 10]
print(kruskal(x1, y1))  # x1 is a little better, but not "significantly" so

x2 = [1, 1, 1]
y2 = [2, 2, 2]
z = [2, 2]  # Hey, a third group, and of different size!
print(kruskal(x2, y2, z))  # x clearly dominates
```

    KruskalResult(statistic=0.2727272727272734, pvalue=0.6015081344405895)
    KruskalResult(statistic=7.0, pvalue=0.0301973834223185)

And there's many more! `scipy.stats` is fairly comprehensive, though there are even more available if you delve into the extended world of statistics packages. As tests get increasingly obscure and specialized, the importance of knowing them by heart becomes small - but being able to look them up and figure them out when they *are* relevant is still important.

## T-test Assumptions

```
from scipy.stats import ttest_ind

?ttest_ind
```

<https://statistics.laerd.com/statistical-guides/independent-t-test-statistical-guide.php>

- Independence of means

Are the means of our voting data independent (do not affect the outcome of one another)?

The best way to increase thel likelihood of our means being independent is to randomly sample (which we did not do).

- "Homogeneity" of Variance?

Is the magnitude of the variance between the two roughly the same?

I think we're OK on this one for the voting data, although it probably could be better, one party was larger than the other.

If we suspect this to be a problem then we can use Welch's T-test

- "Dependent Variable" (sample means) are Distributed Normally

<https://stats.stackexchange.com/questions/9573/t-test-for-non-normal-when-n50>

Lots of statistical tests depend on normal distributions. We can test for normality using Scipy as was shown above.

This assumption is often assumed even if the assumption is a weak one. If you strongly suspect that things are not normally distributed, you can transform your data to get it looking more normal and then run your test. This problem typically goes away for large sample sizes (yay Central Limit Theorem) and is often why you don't hear it brought up. People declare the assumption to be satisfied either way.

## Central Limit Theorem

```
# create a random sample of 30 1 or 2 even probability.
np.random.binomial(n=1, p=.5, size=30)
```

    array([0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0,
           0, 1, 0, 0, 0, 1, 1, 0])

```
# import numpy for numbers.
import numpy as np
# import matplotlib.pyplot for graphs.
import matplotlib.pyplot as plt
# import pandas library.
import pandas as pd

# create a function to return the sample means of the sample.
N = 1000
sample_means = []
for x in range(0,N):
  coinflips = np.random.binomial(n=1, p=.5, size=30)
  one_sample = coinflips
  sample_means.append(coinflips.mean())

print(len(sample_means))
print(sample_means)
```

    1000
    [0.5, 0.4666666666666667, 0.43333333333333335, 0.6, 0.5, 0.5666666666666667, 0.5666666666666667, 0.43333333333333335, 0.5333333333333333, 0.7, 0.5333333333333333, 0.7333333333333333, 0.5666666666666667, 0.4666666666666667, 0.43333333333333335, 0.4, 0.4666666666666667, 0.36666666666666664, 0.4666666666666667, 0.7, 0.5666666666666667, 0.5, 0.5, 0.5, 0.4, 0.5333333333333333, 0.5666666666666667, 0.5, 0.5, 0.5, 0.4, 0.36666666666666664, 0.5, 0.4, 0.3333333333333333, 0.5, 0.6, 0.4666666666666667, 0.36666666666666664, 0.5, 0.5666666666666667, 0.3, 0.4666666666666667, 0.5, 0.5666666666666667, 0.5333333333333333, 0.43333333333333335, 0.43333333333333335, 0.5666666666666667, 0.6666666666666666, 0.7, 0.4, 0.43333333333333335, 0.5, 0.6666666666666666, 0.6, 0.3, 0.7, 0.43333333333333335, 0.5666666666666667, 0.4666666666666667, 0.5666666666666667, 0.43333333333333335, 0.5, 0.5333333333333333, 0.4666666666666667, 0.4, 0.6, 0.7, 0.36666666666666664, 0.4666666666666667, 0.5333333333333333, 0.7, 0.4, 0.6, 0.5, 0.3333333333333333, 0.5, 0.5, 0.43333333333333335, 0.4, 0.6333333333333333, 0.43333333333333335, 0.6, 0.4666666666666667, 0.4, 0.5666666666666667, 0.3333333333333333, 0.4, 0.3333333333333333, 0.4, 0.4, 0.43333333333333335, 0.5333333333333333, 0.5333333333333333, 0.36666666666666664, 0.6, 0.5, 0.43333333333333335, 0.5, 0.5666666666666667, 0.43333333333333335, 0.5, 0.43333333333333335, 0.43333333333333335, 0.5666666666666667, 0.4, 0.5666666666666667, 0.4, 0.43333333333333335, 0.36666666666666664, 0.4666666666666667, 0.5, 0.5, 0.5, 0.36666666666666664, 0.5, 0.6333333333333333, 0.26666666666666666, 0.6333333333333333, 0.36666666666666664, 0.6333333333333333, 0.5666666666666667, 0.4, 0.5, 0.7, 0.5333333333333333, 0.5333333333333333, 0.6, 0.4666666666666667, 0.4, 0.6333333333333333, 0.6, 0.5, 0.4666666666666667, 0.4666666666666667, 0.4666666666666667, 0.6333333333333333, 0.5666666666666667, 0.6333333333333333, 0.6, 0.36666666666666664, 0.5, 0.4666666666666667, 0.5333333333333333, 0.4666666666666667, 0.6, 0.6, 0.5333333333333333, 0.5, 0.5333333333333333, 0.5, 0.5333333333333333, 0.5666666666666667, 0.6666666666666666, 0.6, 0.5333333333333333, 0.5666666666666667, 0.36666666666666664, 0.4, 0.4, 0.5666666666666667, 0.5666666666666667, 0.6666666666666666, 0.4, 0.5666666666666667, 0.4, 0.43333333333333335, 0.36666666666666664, 0.4, 0.5333333333333333, 0.6, 0.5666666666666667, 0.43333333333333335, 0.4666666666666667, 0.5666666666666667, 0.6333333333333333, 0.5, 0.6666666666666666, 0.43333333333333335, 0.4, 0.6333333333333333, 0.36666666666666664, 0.5, 0.5, 0.6, 0.4666666666666667, 0.6, 0.4666666666666667, 0.36666666666666664, 0.5333333333333333, 0.5666666666666667, 0.5333333333333333, 0.43333333333333335, 0.36666666666666664, 0.43333333333333335, 0.3333333333333333, 0.6666666666666666, 0.5333333333333333, 0.36666666666666664, 0.43333333333333335, 0.43333333333333335, 0.4666666666666667, 0.3333333333333333, 0.5333333333333333, 0.7, 0.43333333333333335, 0.5666666666666667, 0.6333333333333333, 0.5666666666666667, 0.43333333333333335, 0.4, 0.4, 0.4, 0.5333333333333333, 0.4666666666666667, 0.5666666666666667, 0.4666666666666667, 0.4666666666666667, 0.6333333333333333, 0.5, 0.5, 0.6, 0.6666666666666666, 0.43333333333333335, 0.5, 0.43333333333333335, 0.5666666666666667, 0.4, 0.4, 0.4666666666666667, 0.4666666666666667, 0.4666666666666667, 0.4666666666666667, 0.43333333333333335, 0.6666666666666666, 0.4666666666666667, 0.4, 0.4, 0.4666666666666667, 0.5666666666666667, 0.6333333333333333, 0.6666666666666666, 0.4666666666666667, 0.4, 0.43333333333333335, 0.5, 0.5333333333333333, 0.5, 0.3333333333333333, 0.5, 0.6, 0.36666666666666664, 0.3, 0.5, 0.4, 0.6, 0.5666666666666667, 0.4666666666666667, 0.43333333333333335, 0.4, 0.6, 0.43333333333333335, 0.36666666666666664, 0.6, 0.3333333333333333, 0.4666666666666667, 0.43333333333333335, 0.5, 0.43333333333333335, 0.6, 0.5333333333333333, 0.36666666666666664, 0.5333333333333333, 0.4666666666666667, 0.5, 0.43333333333333335, 0.5666666666666667, 0.5, 0.5333333333333333, 0.4, 0.6, 0.5333333333333333, 0.6, 0.4666666666666667, 0.6, 0.4666666666666667, 0.5333333333333333, 0.4, 0.4, 0.5666666666666667, 0.43333333333333335, 0.6, 0.36666666666666664, 0.6333333333333333, 0.4666666666666667, 0.5666666666666667, 0.5333333333333333, 0.5333333333333333, 0.5666666666666667, 0.6666666666666666, 0.6, 0.5666666666666667, 0.5666666666666667, 0.5, 0.36666666666666664, 0.5666666666666667, 0.6666666666666666, 0.36666666666666664, 0.6333333333333333, 0.36666666666666664, 0.5, 0.6, 0.5666666666666667, 0.43333333333333335, 0.5666666666666667, 0.4666666666666667, 0.4666666666666667, 0.36666666666666664, 0.5, 0.5333333333333333, 0.4, 0.5666666666666667, 0.43333333333333335, 0.7, 0.5, 0.4, 0.5333333333333333, 0.4, 0.4666666666666667, 0.4666666666666667, 0.43333333333333335, 0.5, 0.43333333333333335, 0.6666666666666666, 0.5333333333333333, 0.5, 0.5, 0.5333333333333333, 0.43333333333333335, 0.4666666666666667, 0.43333333333333335, 0.6, 0.5333333333333333, 0.4, 0.5333333333333333, 0.5, 0.4666666666666667, 0.5, 0.43333333333333335, 0.43333333333333335, 0.43333333333333335, 0.5, 0.4666666666666667, 0.5, 0.5333333333333333, 0.4666666666666667, 0.5666666666666667, 0.5333333333333333, 0.5, 0.36666666666666664, 0.3333333333333333, 0.5333333333333333, 0.36666666666666664, 0.5, 0.5, 0.5, 0.5666666666666667, 0.4666666666666667, 0.6333333333333333, 0.6, 0.5333333333333333, 0.5, 0.43333333333333335, 0.4, 0.4, 0.5, 0.3, 0.5666666666666667, 0.5333333333333333, 0.5, 0.5333333333333333, 0.36666666666666664, 0.5, 0.5, 0.5, 0.43333333333333335, 0.36666666666666664, 0.43333333333333335, 0.43333333333333335, 0.5, 0.36666666666666664, 0.36666666666666664, 0.26666666666666666, 0.5333333333333333, 0.5, 0.5666666666666667, 0.5666666666666667, 0.4, 0.36666666666666664, 0.43333333333333335, 0.4666666666666667, 0.36666666666666664, 0.5666666666666667, 0.4, 0.5333333333333333, 0.3333333333333333, 0.36666666666666664, 0.5, 0.5333333333333333, 0.4666666666666667, 0.43333333333333335, 0.3333333333333333, 0.3, 0.6, 0.4666666666666667, 0.4, 0.5666666666666667, 0.4666666666666667, 0.36666666666666664, 0.5, 0.36666666666666664, 0.4666666666666667, 0.43333333333333335, 0.6, 0.36666666666666664, 0.36666666666666664, 0.5, 0.5, 0.43333333333333335, 0.5666666666666667, 0.5666666666666667, 0.6, 0.5333333333333333, 0.43333333333333335, 0.7666666666666667, 0.5333333333333333, 0.36666666666666664, 0.3, 0.5, 0.43333333333333335, 0.5333333333333333, 0.5666666666666667, 0.4, 0.3333333333333333, 0.4666666666666667, 0.5333333333333333, 0.5666666666666667, 0.5, 0.5333333333333333, 0.5, 0.5333333333333333, 0.4666666666666667, 0.36666666666666664, 0.43333333333333335, 0.6333333333333333, 0.4666666666666667, 0.5333333333333333, 0.5, 0.4666666666666667, 0.43333333333333335, 0.4666666666666667, 0.5, 0.5333333333333333, 0.5333333333333333, 0.36666666666666664, 0.5, 0.5666666666666667, 0.6, 0.5333333333333333, 0.43333333333333335, 0.4, 0.5333333333333333, 0.4666666666666667, 0.5, 0.7, 0.6333333333333333, 0.5333333333333333, 0.3333333333333333, 0.6, 0.5333333333333333, 0.6666666666666666, 0.6, 0.5666666666666667, 0.5333333333333333, 0.4, 0.5, 0.3, 0.6, 0.5, 0.5666666666666667, 0.4666666666666667, 0.4666666666666667, 0.4666666666666667, 0.5333333333333333, 0.3, 0.5333333333333333, 0.5666666666666667, 0.6, 0.43333333333333335, 0.43333333333333335, 0.5333333333333333, 0.3333333333333333, 0.5, 0.5666666666666667, 0.5333333333333333, 0.5, 0.6, 0.4, 0.3333333333333333, 0.5666666666666667, 0.5, 0.4666666666666667, 0.4666666666666667, 0.5, 0.6333333333333333, 0.4666666666666667, 0.36666666666666664, 0.7, 0.43333333333333335, 0.5666666666666667, 0.5, 0.43333333333333335, 0.5, 0.4666666666666667, 0.43333333333333335, 0.6666666666666666, 0.4, 0.5, 0.6, 0.43333333333333335, 0.6, 0.4, 0.36666666666666664, 0.6333333333333333, 0.4, 0.36666666666666664, 0.4666666666666667, 0.6333333333333333, 0.4666666666666667, 0.4666666666666667, 0.5333333333333333, 0.4666666666666667, 0.4666666666666667, 0.6333333333333333, 0.5666666666666667, 0.4666666666666667, 0.36666666666666664, 0.6, 0.5, 0.4666666666666667, 0.5, 0.5333333333333333, 0.43333333333333335, 0.5666666666666667, 0.5, 0.5, 0.4, 0.7, 0.7, 0.6, 0.5333333333333333, 0.6666666666666666, 0.4, 0.3333333333333333, 0.5666666666666667, 0.6333333333333333, 0.4666666666666667, 0.43333333333333335, 0.5666666666666667, 0.5666666666666667, 0.5333333333333333, 0.5333333333333333, 0.6333333333333333, 0.5333333333333333, 0.5333333333333333, 0.36666666666666664, 0.5333333333333333, 0.6333333333333333, 0.5333333333333333, 0.5333333333333333, 0.4, 0.36666666666666664, 0.5333333333333333, 0.43333333333333335, 0.36666666666666664, 0.43333333333333335, 0.16666666666666666, 0.5, 0.5666666666666667, 0.6666666666666666, 0.43333333333333335, 0.5333333333333333, 0.4, 0.6, 0.6333333333333333, 0.5, 0.6333333333333333, 0.36666666666666664, 0.4666666666666667, 0.5, 0.5333333333333333, 0.36666666666666664, 0.6333333333333333, 0.4, 0.6333333333333333, 0.6333333333333333, 0.5666666666666667, 0.43333333333333335, 0.5666666666666667, 0.5, 0.5, 0.5666666666666667, 0.6, 0.5666666666666667, 0.5666666666666667, 0.4666666666666667, 0.5666666666666667, 0.6, 0.6, 0.5333333333333333, 0.5, 0.6333333333333333, 0.5666666666666667, 0.6666666666666666, 0.5666666666666667, 0.5333333333333333, 0.6666666666666666, 0.43333333333333335, 0.6, 0.5, 0.5333333333333333, 0.5, 0.43333333333333335, 0.4666666666666667, 0.43333333333333335, 0.5, 0.5333333333333333, 0.43333333333333335, 0.6, 0.6666666666666666, 0.5, 0.36666666666666664, 0.6333333333333333, 0.5666666666666667, 0.5, 0.5666666666666667, 0.5666666666666667, 0.4666666666666667, 0.5333333333333333, 0.43333333333333335, 0.4666666666666667, 0.4666666666666667, 0.5333333333333333, 0.43333333333333335, 0.4666666666666667, 0.4666666666666667, 0.5333333333333333, 0.43333333333333335, 0.5666666666666667, 0.5, 0.6666666666666666, 0.5, 0.5, 0.5666666666666667, 0.3, 0.4, 0.3, 0.6, 0.6333333333333333, 0.4, 0.5666666666666667, 0.5, 0.5666666666666667, 0.4666666666666667, 0.6, 0.43333333333333335, 0.5666666666666667, 0.5, 0.6333333333333333, 0.4, 0.5666666666666667, 0.6333333333333333, 0.4666666666666667, 0.4, 0.5, 0.5, 0.5333333333333333, 0.4, 0.43333333333333335, 0.4666666666666667, 0.5333333333333333, 0.4, 0.4666666666666667, 0.4, 0.5666666666666667, 0.36666666666666664, 0.6333333333333333, 0.4, 0.4666666666666667, 0.36666666666666664, 0.5666666666666667, 0.5333333333333333, 0.4666666666666667, 0.4666666666666667, 0.6333333333333333, 0.5333333333333333, 0.5666666666666667, 0.5333333333333333, 0.4, 0.5333333333333333, 0.43333333333333335, 0.43333333333333335, 0.4, 0.6, 0.4666666666666667, 0.5333333333333333, 0.4, 0.5, 0.5333333333333333, 0.4, 0.26666666666666666, 0.6, 0.4666666666666667, 0.36666666666666664, 0.36666666666666664, 0.4, 0.36666666666666664, 0.5333333333333333, 0.6, 0.43333333333333335, 0.3333333333333333, 0.6666666666666666, 0.43333333333333335, 0.4666666666666667, 0.5, 0.3333333333333333, 0.5666666666666667, 0.5, 0.4, 0.43333333333333335, 0.6, 0.6, 0.3333333333333333, 0.4, 0.5666666666666667, 0.4666666666666667, 0.5333333333333333, 0.4666666666666667, 0.4666666666666667, 0.4, 0.5666666666666667, 0.5, 0.5, 0.5666666666666667, 0.5, 0.4666666666666667, 0.4666666666666667, 0.43333333333333335, 0.6333333333333333, 0.43333333333333335, 0.4, 0.5, 0.5333333333333333, 0.5333333333333333, 0.36666666666666664, 0.5, 0.5, 0.6, 0.3, 0.6666666666666666, 0.5333333333333333, 0.4, 0.36666666666666664, 0.4, 0.6, 0.4666666666666667, 0.5, 0.4, 0.36666666666666664, 0.4666666666666667, 0.4666666666666667, 0.5, 0.4, 0.5, 0.4666666666666667, 0.5333333333333333, 0.5, 0.4, 0.5333333333333333, 0.6333333333333333, 0.6666666666666666, 0.5666666666666667, 0.5666666666666667, 0.5, 0.5333333333333333, 0.4666666666666667, 0.3333333333333333, 0.43333333333333335, 0.5333333333333333, 0.5, 0.7, 0.5333333333333333, 0.5333333333333333, 0.4666666666666667, 0.4666666666666667, 0.5333333333333333, 0.4, 0.4666666666666667, 0.5, 0.4, 0.6333333333333333, 0.5, 0.6, 0.5, 0.5666666666666667, 0.6, 0.6, 0.4666666666666667, 0.36666666666666664, 0.4666666666666667, 0.6, 0.5, 0.43333333333333335, 0.5333333333333333, 0.6666666666666666, 0.5, 0.5, 0.6333333333333333, 0.5666666666666667, 0.4666666666666667, 0.5, 0.5333333333333333, 0.4666666666666667, 0.5666666666666667, 0.5666666666666667, 0.4, 0.5333333333333333, 0.5, 0.4666666666666667, 0.5, 0.5, 0.5333333333333333, 0.43333333333333335, 0.4666666666666667, 0.6333333333333333, 0.5333333333333333, 0.43333333333333335, 0.6333333333333333, 0.4666666666666667, 0.4, 0.5, 0.3333333333333333, 0.5, 0.36666666666666664, 0.6333333333333333, 0.3, 0.4, 0.5333333333333333, 0.43333333333333335, 0.5333333333333333, 0.4, 0.4666666666666667, 0.5, 0.5, 0.5666666666666667, 0.5666666666666667, 0.43333333333333335, 0.6666666666666666, 0.5666666666666667, 0.6, 0.4666666666666667, 0.4666666666666667, 0.43333333333333335, 0.4666666666666667, 0.4666666666666667, 0.4666666666666667, 0.43333333333333335, 0.5666666666666667, 0.4, 0.4, 0.5333333333333333, 0.5333333333333333, 0.6333333333333333, 0.43333333333333335, 0.5333333333333333, 0.4666666666666667, 0.5666666666666667, 0.5, 0.5666666666666667, 0.4666666666666667, 0.43333333333333335, 0.3333333333333333, 0.4666666666666667, 0.5333333333333333, 0.5666666666666667, 0.4666666666666667, 0.6, 0.5666666666666667, 0.3333333333333333, 0.5, 0.5666666666666667, 0.4, 0.5333333333333333, 0.3, 0.4666666666666667, 0.5666666666666667, 0.36666666666666664, 0.26666666666666666, 0.5, 0.5333333333333333, 0.4, 0.43333333333333335, 0.4, 0.4666666666666667, 0.6, 0.7, 0.6666666666666666, 0.5, 0.5, 0.5, 0.5, 0.43333333333333335, 0.5333333333333333, 0.4666666666666667, 0.43333333333333335, 0.6333333333333333, 0.5666666666666667, 0.4, 0.5333333333333333, 0.43333333333333335, 0.5666666666666667, 0.5, 0.43333333333333335, 0.23333333333333334, 0.5666666666666667, 0.5333333333333333, 0.4666666666666667, 0.4666666666666667, 0.43333333333333335, 0.43333333333333335, 0.3, 0.3, 0.5333333333333333, 0.5, 0.4666666666666667, 0.4666666666666667, 0.4666666666666667, 0.43333333333333335, 0.4, 0.4666666666666667, 0.4666666666666667, 0.5333333333333333, 0.5333333333333333, 0.5, 0.6, 0.5, 0.6, 0.5666666666666667, 0.6333333333333333, 0.4, 0.4666666666666667, 0.5, 0.4, 0.4666666666666667, 0.5, 0.5333333333333333, 0.5666666666666667, 0.5333333333333333, 0.43333333333333335, 0.36666666666666664, 0.6, 0.43333333333333335, 0.36666666666666664, 0.5666666666666667, 0.2, 0.5333333333333333, 0.5333333333333333, 0.5333333333333333, 0.36666666666666664, 0.4, 0.4, 0.6333333333333333, 0.7, 0.43333333333333335, 0.6666666666666666, 0.6333333333333333, 0.43333333333333335, 0.5666666666666667, 0.5666666666666667, 0.4666666666666667, 0.4, 0.43333333333333335, 0.5666666666666667, 0.5333333333333333, 0.4, 0.6333333333333333, 0.4666666666666667, 0.43333333333333335, 0.36666666666666664]

```
# create dataframe with single coin flip.
df = pd.DataFrame({'one-samp': one_sample})
df.head()
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>one-samp</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>0</td>
    </tr>
    <tr>
      <th>1</th>
      <td>0</td>
    </tr>
    <tr>
      <th>2</th>
      <td>0</td>
    </tr>
    <tr>
      <th>3</th>
      <td>1</td>
    </tr>
    <tr>
      <th>4</th>
      <td>1</td>
    </tr>
  </tbody>
</table>
</div>

```
# plot histogram to look at distribution of a single coin flip.
df.hist();
```

```
# plot histogram to look at distribution of all coin flips.
ax = plt.hist(sample_means)
plt.title(f'Distribution of {N} sample means \n (of 100 coinflips each)');
```

What does the Central Limit Theorem State? That no matter the initial distribution of the population, the distribution of sample means taken will approximate a normal distribution as $n \rightarrow \infty$.

This has very important implications for hypothesis testing and is precisely the reason why the t-distribution begins to approximate the normal distribution as our sample size increases.

## Standard Error of the Mean

What does it mean to "estimate"? the Population mean?

```
# import numpy for numbers.
import numpy as np
# import pandas library.
import pandas as pd

# average height.
mu = 70
sigma = 3

# create a random sample based on 'average hieght'.
lambda_heights = np.random.normal(mu, sigma, 2000)
print(len(lambda_heights))
lambda_heights
```

    2000

    array([72.28416149, 69.21044578, 76.64673268, ..., 71.29636393,
           71.80389578, 69.07190962])

```
# import seaborn for graphs.
import seaborn as sns
# use distplot to graph the distribution of heights.
sns.distplot(lambda_heights)
plt.title('Distribution of Heights (in inches)');
```

```
# print the mean and standard deviation for lambda heights.
print("Population Mean:", lambda_heights.mean())
print("Population Standard Deviation:", lambda_heights.std())
```

    Population Mean: 69.93528155501507
    Population Standard Deviation: 2.986302867851206

```
# look at all the lambda heights from the sample in its own data frame.
population = pd.DataFrame({'heights': lambda_heights})
print(population.shape)
population.head()
```

    (2000, 1)

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>heights</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>72.284161</td>
    </tr>
    <tr>
      <th>1</th>
      <td>69.210446</td>
    </tr>
    <tr>
      <th>2</th>
      <td>76.646733</td>
    </tr>
    <tr>
      <th>3</th>
      <td>75.675690</td>
    </tr>
    <tr>
      <th>4</th>
      <td>71.889314</td>
    </tr>
  </tbody>
</table>
</div>

```
# take a random sample and print sample mean.
sample1 = population.sample(100)
print(sample1.shape)
sample1.head()
```

    (100, 1)

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>heights</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>890</th>
      <td>69.556818</td>
    </tr>
    <tr>
      <th>9</th>
      <td>70.249208</td>
    </tr>
    <tr>
      <th>1053</th>
      <td>75.782613</td>
    </tr>
    <tr>
      <th>1842</th>
      <td>68.679563</td>
    </tr>
    <tr>
      <th>264</th>
      <td>71.232889</td>
    </tr>
  </tbody>
</table>
</div>

```
print('Sample Mean #1:', sample1['heights'].mean())
```

    Sample Mean #1: 69.93393663065153

```
# take a different random sample and print sample mean
sample2 = population.sample(100)
print(sample2.shape)
sample2.head()
```

    (100, 1)

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>heights</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>333</th>
      <td>67.331469</td>
    </tr>
    <tr>
      <th>977</th>
      <td>68.544879</td>
    </tr>
    <tr>
      <th>189</th>
      <td>73.075877</td>
    </tr>
    <tr>
      <th>984</th>
      <td>73.004353</td>
    </tr>
    <tr>
      <th>1631</th>
      <td>68.502011</td>
    </tr>
  </tbody>
</table>
</div>

```
# show the 'mean' for 'hieghts' of sample2.
print('Sample Mean #2:', sample2['heights'].mean())
```

    Sample Mean #2: 69.90602316881959

## Build and Interpret a Confidence Interval

```
# create a randmom sample of coin flips, 50/50 probability.
coinflips_10000 = np.random.binomial(n=1, p=0.5, size=10000)
# create th sample standard deviation.
sample_std = np.std(coinflips_10000)
print('Sample St Dev:', sample_std)
# create the sample size.
sample_size = len(coinflips_10000)
print('Sample Size:', sample_size)
```

    Sample St Dev: 0.49998774984993377
    Sample Size: 10000

```
# create the standard error for sample.
standard_error = sample_std/np.sqrt(sample_size)
print(standard_error)
```

    0.004999877498499338

### What confidence level do we want our confidence interval to represent?

95% confidence Interval? 99% confidence interval?

```
# import stats from scipy.stats.
import scipy.stats as stats
help(stats.t.ppf)
```

    Help on method ppf in module scipy.stats._distn_infrastructure:

    ppf(q, *args, **kwds) method of scipy.stats._continuous_distns.t_gen instance
        Percent point function (inverse of `cdf`) at q of the given RV.

        Parameters
        ----------
        q : array_like
            lower tail probability
        arg1, arg2, arg3,... : array_like
            The shape parameter(s) for the distribution (see docstring of the
            instance object for more information)
        loc : array_like, optional
            location parameter (default=0)
        scale : array_like, optional
            scale parameter (default=1)

        Returns
        -------
        x : array_like
            quantile corresponding to the lower tail probability q.

```
# set a %97.5 confidence interval.
t = stats.t.ppf(0.975, sample_size-1)
t
```

    1.9602012636213575

```
# find the mean for the coinflips.
sample_mean = coinflips_10000.mean()
# find the confidence interval.
confidence_interval = (sample_mean - t*standard_error, sample_mean + t*standard_error)
#find the margin of error.
margin_of_error = t*standard_error

print("Sample Mean: ", sample_mean)
print("Margin of Error: ", margin_of_error)
print("Confidence Interval: ", confidence_interval)
```

    Sample Mean:  0.4965
    Margin of Error:  0.009800766190510395
    Confidence Interval:  (0.4866992338094896, 0.5063007661905103)

## Graphically Represent a Confidence Interval

```
# import seaborn for graphs.
import seaborn as sns
# use kdeplot to show the coinflips data.
sns.kdeplot(coinflips_10000)
# plot the 'sample_mean' as blue 'k'.
plt.axvline(x=sample_mean, color='k')
# plot the low '0' confidence interval as red 'r'.
plt.axvline(x=confidence_interval[0], color='r')
# plot the high '1' confidence interval as red 'r'.
plt.axvline(x=confidence_interval[1], color='r')
```

## Relationship between Confidence Intervals and T-tests

Confidence Interval == Bounds of statistical significance for our t-test

A sample mean that falls inside of our confidence interval will "FAIL TO REJECT" our null hypothesis

A sample mean that falls outside of our confidence interval will "REJECT" our null hypothesis

```
# import ttest_1samp for 1 tail t-test.
from scipy.stats import t, ttest_1samp
```

```
# import numpy for numbers work.
import numpy as np

# create a list of all the coinflips in the loop.
coinflip_means = []
# create a list from 0 to 100.
for x in range(0,100):
# create a sample of size of 30, probability at .5, between 0 and 1.
  coinflips = np.random.binomial(n=1, p=.5, size=30)
  coinflip_means.append(coinflips.mean())
# print the list of 30 coinflips.
print(coinflip_means)
```

    [0.4, 0.6, 0.5333333333333333, 0.4, 0.5, 0.6666666666666666, 0.5666666666666667, 0.5, 0.5, 0.4, 0.6, 0.7, 0.5, 0.5, 0.6, 0.4, 0.6333333333333333, 0.5, 0.4666666666666667, 0.5666666666666667, 0.6, 0.4, 0.43333333333333335, 0.4666666666666667, 0.5, 0.5, 0.36666666666666664, 0.6666666666666666, 0.5333333333333333, 0.3333333333333333, 0.6666666666666666, 0.5, 0.5666666666666667, 0.5333333333333333, 0.6, 0.4666666666666667, 0.36666666666666664, 0.6333333333333333, 0.43333333333333335, 0.5, 0.4666666666666667, 0.6666666666666666, 0.43333333333333335, 0.5333333333333333, 0.4, 0.6666666666666666, 0.4666666666666667, 0.5, 0.5333333333333333, 0.5, 0.5333333333333333, 0.5, 0.36666666666666664, 0.4666666666666667, 0.6333333333333333, 0.5, 0.5333333333333333, 0.5333333333333333, 0.6666666666666666, 0.5333333333333333, 0.6333333333333333, 0.3, 0.6, 0.5666666666666667, 0.4666666666666667, 0.5, 0.4666666666666667, 0.43333333333333335, 0.6333333333333333, 0.4666666666666667, 0.5333333333333333, 0.5, 0.4666666666666667, 0.36666666666666664, 0.5333333333333333, 0.4666666666666667, 0.43333333333333335, 0.5333333333333333, 0.6333333333333333, 0.4666666666666667, 0.43333333333333335, 0.6333333333333333, 0.3333333333333333, 0.5666666666666667, 0.5333333333333333, 0.4666666666666667, 0.4666666666666667, 0.43333333333333335, 0.5, 0.5, 0.43333333333333335, 0.4, 0.5666666666666667, 0.5666666666666667, 0.43333333333333335, 0.4666666666666667, 0.5, 0.4, 0.6, 0.6]

```
# show the man of the coinflips it total.
np.mean(coinflip_means)
```

    0.5086666666666667

```
import scipy.stats as stats
help(stats.t.interval)
```

    Help on method interval in module scipy.stats._distn_infrastructure:

    interval(alpha, *args, **kwds) method of scipy.stats._continuous_distns.t_gen instance
        Confidence interval with equal areas around the median.

        Parameters
        ----------
        alpha : array_like of float
            Probability that an rv will be drawn from the returned range.
            Each value should be in the range [0, 1].
        arg1, arg2, ... : array_like
            The shape parameter(s) for the distribution (see docstring of the
            instance object for more information).
        loc : array_like, optional
            location parameter, Default is 0.
        scale : array_like, optional
            scale parameter, Default is 1.

        Returns
        -------
        a, b : ndarray of float
            end-points of range that contain ``100 * alpha %`` of the rv's
            possible values.

```
# 95% confidence interval.
# # create the t statistic at 95% confidence.
t_stat = stats.t.ppf(0.95, 99)
print('T Statistic: ', t_stat)
# create the standard deviation for the coinflips mean.
std_sample = np.std(coinflip_means)
# create the standard error for the coinflips mean.
std_err = std_sample/np.sqrt(len(coinflip_means))
# create the high and low confidence interval at 95%.
CI = stats.t.interval(0.95, 99, loc=np.mean(coinflip_means), scale=std_err)
print('95% confidence interval:', CI)
```

    T Statistic:  1.6603911559963895
    95% confidence interval: (0.49145435499878204, 0.5258789783345514)

A null hypothesis that's just inside of our confidence interval == fail to reject

```
# t-test 1 sample.
ttest_1samp(coinflip_means, 0.48186)
```

    Ttest_1sampResult(statistic=3.07475393190666, pvalue=0.002722969613017145)

A null hypothesis that's just outside of our confidence interval == reject

```
# t-test 1 sample.
ttest_1samp(coinflip_means, 0.51813)
```

    Ttest_1sampResult(statistic=-1.0854546645962369, pvalue=0.28035641267084765)

```
def confidence_interval(data, confidence=0.95):
  """
  Calculate a confidence interval around a sample mean for given data.
  Using t-distribution and two-tailed test, default 95% confidence.

  Arguments:
    data - iterable (list or numpy array) of sample observations
    confidence - level of confidence for the interval

  Returns:
    tuple of (mean, lower bound, upper bound)
  """
  data = np.array(data)
  mean = np.mean(data)
  n = len(data)
  stderr = stats.sem(data)
  interval = stderr * stats.t.ppf((1 + confidence) / 2.0, n - 1)
  return (mean, mean - interval, mean + interval)

```

## Run a $\chi^{2}$ Test "by hand" (Using Numpy)

```
import pandas as pd
```

```
# read in the data set.
df = pd.read_csv('https://raw.githubusercontent.com/ryanleeallred/datasets/master/adult.csv', na_values=" ?")
# show the data frame shape.
print(df.shape)
# show the data frame with headers.
df.head()
```

    (32561, 15)

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>age</th>
      <th>workclass</th>
      <th>fnlwgt</th>
      <th>education</th>
      <th>education-num</th>
      <th>marital-status</th>
      <th>occupation</th>
      <th>relationship</th>
      <th>race</th>
      <th>sex</th>
      <th>capital-gain</th>
      <th>capital-loss</th>
      <th>hours-per-week</th>
      <th>country</th>
      <th>salary</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>39</td>
      <td>State-gov</td>
      <td>77516</td>
      <td>Bachelors</td>
      <td>13</td>
      <td>Never-married</td>
      <td>Adm-clerical</td>
      <td>Not-in-family</td>
      <td>White</td>
      <td>Male</td>
      <td>2174</td>
      <td>0</td>
      <td>40</td>
      <td>United-States</td>
      <td><=50K</td>
    </tr>
    <tr>
      <th>1</th>
      <td>50</td>
      <td>Self-emp-not-inc</td>
      <td>83311</td>
      <td>Bachelors</td>
      <td>13</td>
      <td>Married-civ-spouse</td>
      <td>Exec-managerial</td>
      <td>Husband</td>
      <td>White</td>
      <td>Male</td>
      <td>0</td>
      <td>0</td>
      <td>13</td>
      <td>United-States</td>
      <td><=50K</td>
    </tr>
    <tr>
      <th>2</th>
      <td>38</td>
      <td>Private</td>
      <td>215646</td>
      <td>HS-grad</td>
      <td>9</td>
      <td>Divorced</td>
      <td>Handlers-cleaners</td>
      <td>Not-in-family</td>
      <td>White</td>
      <td>Male</td>
      <td>0</td>
      <td>0</td>
      <td>40</td>
      <td>United-States</td>
      <td><=50K</td>
    </tr>
    <tr>
      <th>3</th>
      <td>53</td>
      <td>Private</td>
      <td>234721</td>
      <td>11th</td>
      <td>7</td>
      <td>Married-civ-spouse</td>
      <td>Handlers-cleaners</td>
      <td>Husband</td>
      <td>Black</td>
      <td>Male</td>
      <td>0</td>
      <td>0</td>
      <td>40</td>
      <td>United-States</td>
      <td><=50K</td>
    </tr>
    <tr>
      <th>4</th>
      <td>28</td>
      <td>Private</td>
      <td>338409</td>
      <td>Bachelors</td>
      <td>13</td>
      <td>Married-civ-spouse</td>
      <td>Prof-specialty</td>
      <td>Wife</td>
      <td>Black</td>
      <td>Female</td>
      <td>0</td>
      <td>0</td>
      <td>40</td>
      <td>Cuba</td>
      <td><=50K</td>
    </tr>
  </tbody>
</table>
</div>

```
# plot hist with 'hours-per-week' column, in 20 bins.
df['hours-per-week'].hist(bins=20);
```

```
# look at the statistics info of the data, exclude will show non #s.
df.describe(exclude='number')
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>workclass</th>
      <th>education</th>
      <th>marital-status</th>
      <th>occupation</th>
      <th>relationship</th>
      <th>race</th>
      <th>sex</th>
      <th>country</th>
      <th>salary</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>count</th>
      <td>30725</td>
      <td>32561</td>
      <td>32561</td>
      <td>30718</td>
      <td>32561</td>
      <td>32561</td>
      <td>32561</td>
      <td>31978</td>
      <td>32561</td>
    </tr>
    <tr>
      <th>unique</th>
      <td>8</td>
      <td>16</td>
      <td>7</td>
      <td>14</td>
      <td>6</td>
      <td>5</td>
      <td>2</td>
      <td>41</td>
      <td>2</td>
    </tr>
    <tr>
      <th>top</th>
      <td>Private</td>
      <td>HS-grad</td>
      <td>Married-civ-spouse</td>
      <td>Prof-specialty</td>
      <td>Husband</td>
      <td>White</td>
      <td>Male</td>
      <td>United-States</td>
      <td><=50K</td>
    </tr>
    <tr>
      <th>freq</th>
      <td>22696</td>
      <td>10501</td>
      <td>14976</td>
      <td>4140</td>
      <td>13193</td>
      <td>27816</td>
      <td>21790</td>
      <td>29170</td>
      <td>24720</td>
    </tr>
  </tbody>
</table>
</div>

```
# create cut points to create groups for the 'hours-per-week-column'.
cut_points = [0, 9, 19, 29, 39, 49, 500]
# create label names for the cut points.
label_names = ['0-9','10-19','20-29','30-39','40-49','50+']
# create a new column for the new 'hours_per_week_groups', use pd.cut().
df['hours_per_week_groups'] = pd.cut(df['hours-per-week'], cut_points, labels=label_names)
# show the values for the new groups/column we created.
df.hours_per_week_groups.value_counts()
```

    40-49    18336
    50+       6462
    30-39     3667
    20-29     2392
    10-19     1246
    0-9        458
    Name: hours_per_week_groups, dtype: int64

```
# look at the value counts for 'sex' column.
df.sex.value_counts()
```

     Male      21790
     Female    10771
    Name: sex, dtype: int64

```
# label and sor the 'hours_per_week_group' column.
df = df.sort_values(by='hours_per_week_groups')
# create a contingency table with cross tab for 'sex' and 'hours_per_week_group'
contingency_table = pd.crosstab(df['sex'], df['hours_per_week_groups'], margins=True)
# show the new contingency table.
contingency_table
```

<div>
<style scoped>
    .dataframe tbody tr th:only-of-type {
        vertical-align: middle;
    }

    .dataframe tbody tr th {
        vertical-align: top;
    }

    .dataframe thead th {
        text-align: right;
    }
</style>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th>hours_per_week_groups</th>
      <th>0-9</th>
      <th>10-19</th>
      <th>20-29</th>
      <th>30-39</th>
      <th>40-49</th>
      <th>50+</th>
      <th>All</th>
    </tr>
    <tr>
      <th>sex</th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>Female</th>
      <td>235</td>
      <td>671</td>
      <td>1287</td>
      <td>1914</td>
      <td>5636</td>
      <td>1028</td>
      <td>10771</td>
    </tr>
    <tr>
      <th>Male</th>
      <td>223</td>
      <td>575</td>
      <td>1105</td>
      <td>1753</td>
      <td>12700</td>
      <td>5434</td>
      <td>21790</td>
    </tr>
    <tr>
      <th>All</th>
      <td>458</td>
      <td>1246</td>
      <td>2392</td>
      <td>3667</td>
      <td>18336</td>
      <td>6462</td>
      <td>32561</td>
    </tr>
  </tbody>
</table>
</div>

## Expected Value Calculation
\begin{align}
expected_{i,j} =\frac{(row_{i} \text{total})(column_{j} \text{total}) }{(\text{total observations})}
\end{align}

```
# create the sums for all the rows and columns.
row_sums = contingency_table.iloc[0:2, 6].values
col_sums = contingency_table.iloc[2, 0:6].values

print(row_sums)
print(col_sums)
```

    [10771 21790]
    [  458  1246  2392  3667 18336  6462]

```
# create the total for 'All' row and 'All' column.
total = contingency_table.loc['All','All']
total
```

    32561

```
expected = []
for i in range(len(row_sums)):
  expected_row = []
  for column in col_sums:
    expected_val = column*row_sums[i]/total
    expected_row.append(expected_val)
  expected.append(expected_row)

expected = np.array(expected)
print(expected.shape)
print(expected)
```

    (2, 6)
    [[  151.50388502   412.16995793   791.26046497  1213.02346365
       6065.44811277  2137.59411566]
     [  306.49611498   833.83004207  1600.73953503  2453.97653635
      12270.55188723  4324.40588434]]

## Chi-Squared Statistic with Numpy

\begin{align}
\chi^2 = \sum \frac{(observed_{i}-expected_{i})^2}{(expected_{i})}
\end{align}

For the $observed$ values we will just use a version of our contingency table without the margins as a numpy array. In this way, if our observed values array and our expected values array are the same shape, then we can subtract them and divide them directly which makes the calculations a lot cleaner. No for loops!

```
observed = pd.crosstab(df['sex'], df['hours_per_week_groups']).values
print(observed.shape)
observed
```

    (2, 6)

    array([[  235,   671,  1287,  1914,  5636,  1028],
           [  223,   575,  1105,  1753, 12700,  5434]])

```
chi_square = ((observed - expected)**2/(expected)).sum()
chi_square
```

    2287.190943926107

## Run a $\chi^{2}$ Test using Scipy

```
help(stats.chi2_contingency)
```

    Help on function chi2_contingency in module scipy.stats.contingency:

    chi2_contingency(observed, correction=True, lambda_=None)
        Chi-square test of independence of variables in a contingency table.

        This function computes the chi-square statistic and p-value for the
        hypothesis test of independence of the observed frequencies in the
        contingency table [1]_ `observed`.  The expected frequencies are computed
        based on the marginal sums under the assumption of independence; see
        `scipy.stats.contingency.expected_freq`.  The number of degrees of
        freedom is (expressed using numpy functions and attributes)::

            dof = observed.size - sum(observed.shape) + observed.ndim - 1

        Parameters
        ----------
        observed : array_like
            The contingency table. The table contains the observed frequencies
            (i.e. number of occurrences) in each category.  In the two-dimensional
            case, the table is often described as an "R x C table".
        correction : bool, optional
            If True, *and* the degrees of freedom is 1, apply Yates' correction
            for continuity.  The effect of the correction is to adjust each
            observed value by 0.5 towards the corresponding expected value.
        lambda_ : float or str, optional.
            By default, the statistic computed in this test is Pearson's
            chi-squared statistic [2]_.  `lambda_` allows a statistic from the
            Cressie-Read power divergence family [3]_ to be used instead.  See
            `power_divergence` for details.

        Returns
        -------
        chi2 : float
            The test statistic.
        p : float
            The p-value of the test
        dof : int
            Degrees of freedom
        expected : ndarray, same shape as `observed`
            The expected frequencies, based on the marginal sums of the table.

        See Also
        --------
        contingency.expected_freq
        fisher_exact
        chisquare
        power_divergence

        Notes
        -----
        An often quoted guideline for the validity of this calculation is that
        the test should be used only if the observed and expected frequencies
        in each cell are at least 5.

        This is a test for the independence of different categories of a
        population. The test is only meaningful when the dimension of
        `observed` is two or more.  Applying the test to a one-dimensional
        table will always result in `expected` equal to `observed` and a
        chi-square statistic equal to 0.

        This function does not handle masked arrays, because the calculation
        does not make sense with missing values.

        Like stats.chisquare, this function computes a chi-square statistic;
        the convenience this function provides is to figure out the expected
        frequencies and degrees of freedom from the given contingency table.
        If these were already known, and if the Yates' correction was not
        required, one could use stats.chisquare.  That is, if one calls::

            chi2, p, dof, ex = chi2_contingency(obs, correction=False)

        then the following is true::

            (chi2, p) == stats.chisquare(obs.ravel(), f_exp=ex.ravel(),
                                         ddof=obs.size - 1 - dof)

        The `lambda_` argument was added in version 0.13.0 of scipy.

        References
        ----------
        .. [1] "Contingency table",
               https://en.wikipedia.org/wiki/Contingency_table
        .. [2] "Pearson's chi-squared test",
               https://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test
        .. [3] Cressie, N. and Read, T. R. C., "Multinomial Goodness-of-Fit
               Tests", J. Royal Stat. Soc. Series B, Vol. 46, No. 3 (1984),
               pp. 440-464.

        Examples
        --------
        A two-way example (2 x 3):

        >>> from scipy.stats import chi2_contingency
        >>> obs = np.array([[10, 10, 20], [20, 20, 20]])
        >>> chi2_contingency(obs)
        (2.7777777777777777,
         0.24935220877729619,
         2,
         array([[ 12.,  12.,  16.],
                [ 18.,  18.,  24.]]))

        Perform the test using the log-likelihood ratio (i.e. the "G-test")
        instead of Pearson's chi-squared statistic.

        >>> g, p, dof, expctd = chi2_contingency(obs, lambda_="log-likelihood")
        >>> g, p
        (2.7688587616781319, 0.25046668010954165)

        A four-way example (2 x 2 x 2 x 2):

        >>> obs = np.array(
        ...     [[[[12, 17],
        ...        [11, 16]],
        ...       [[11, 12],
        ...        [15, 16]]],
        ...      [[[23, 15],
        ...        [30, 22]],
        ...       [[14, 17],
        ...        [15, 16]]]])
        >>> chi2_contingency(obs)
        (8.7584514426741897,
         0.64417725029295503,
         11,
         array([[[[ 14.15462386,  14.15462386],
                  [ 16.49423111,  16.49423111]],
                 [[ 11.2461395 ,  11.2461395 ],
                  [ 13.10500554,  13.10500554]]],
                [[[ 19.5591166 ,  19.5591166 ],
                  [ 22.79202844,  22.79202844]],
                 [[ 15.54012004,  15.54012004],
                  [ 18.10873492,  18.10873492]]]]))

```
chi_squared, p_value, dof, expected = stats.chi2_contingency(observed)
print(chi_squared, p_value, dof, expected)
```

    2287.190943926107 0.0 5 [[  151.50388502   412.16995793   791.26046497  1213.02346365
       6065.44811277  2137.59411566]
     [  306.49611498   833.83004207  1600.73953503  2453.97653635
      12270.55188723  4324.40588434]]

Null Hypothesis: Hours worked per week bins is **independent** of sex.

Due to a p-value of 0, we REJECT the null hypothesis that hours worked per week and sex are independent, and conclude that there is an association between hours worked per week and sex.


# Supply Network Design 1

## Objective and Prerequisites

Try this Jupyter Notebook Modeling Example to learn how to solve a classic supply network design problem that involves finding the minimum cost flow through a network. We’ll show you how – given a set of factories, depots, and customers – you can use mathematical optimization to determine the best way to satisfy customer demand while minimizing shipping costs.

This model is example 19 from the fifth edition of Model Building in Mathematical Programming, by H. Paul Williams on pages 273-275 and 330-332.

This example is of beginning difficulty; we assume that you know Python and have some knowledge of the Gurobi Python API and building mathematical optimization models.

**Download the Repository**

You can download the repository containing this and other examples by clicking [here](https://github.com/Gurobi/modeling-examples/archive/master.zip).

**Gurobi License**

In order to run this Jupyter Notebook properly, you must have a Gurobi license. If you do not have one, you can request an [evaluation license](https://www.gurobi.com/downloads/request-an-evaluation-license/?utm_source=3PW&utm_medium=OT&utm_campaign=WW-MU-LOG-OR-O_LEA-PR_NO-Q3_FY20_WW_JPME_supply-network-design_1_COM_EVAL_GITHUB_&utm_term=supply-network-design&utm_content=C_JPM) as a *commercial user*, or download a [free license](https://www.gurobi.com/academia/academic-program-and-licenses/?utm_source=3PW&utm_medium=OT&utm_campaign=WW-MU-LOG-OR-O_LEA-PR_NO-Q3_FY20_WW_JPME_supply-network-design_1_ACADEMIC_EVAL_GITHUB_&utm_term=supply-network-design&utm_content=C_JPM) as an *academic user*.

---
## Problem Description

In this problem, we have six end customers, each with a known demand for a product.  Customer demand can be satisfied from a set of four depots, or directly from a set of two factories.  Each depot can support a maximum volume of product moving through it, and each factory can produce a maximum amount of product.  There are known costs associated with transporting the product, from a factory to a depot, from a depot to a customer, or from a factory directly to a customer.

Our supply network has two factories, in Liverpool and Brighton, that produce a product.  Each has a maximum production capacity:

| Factory | Supply (tons) |
| --- | --- |
| Liverpool | 150,000 |
| Brighton |  200,000 |

The product can be shipped from a factory to a set of four depots.  Each depot has a maximum throughput.  Depots don't produce or consume the product; they simply pass the product on to customers.

| Depot | Throughput (tons) |
| --- | --- |
| Newcastle | 70,000 |
| Birmingham | 50,000 |
| London | 100,000 |
| Exeter | 40,000 |

Our network has six customers, each with a given demand.

| Customer | Demand (tons) |
| --- | --- |
| C1 | 50,000 |
| C2 | 10,000 |
| C3 | 40,000 |
| C4 | 35,000 |
| C5 | 60,000 |
| C6 | 20,000 |

Shipping costs are given in the following table (in dollars per ton).  Columns are source cities and rows are destination cities.  Thus, for example, it costs $1 per ton to ship the product from Liverpool to London.  A '-' in the table indicates that that combination is not possible, so for example it is not possible to ship from the factory in Brighton to the depot in Newcastle.

| To | Liverpool | Brighton | Newcastle | Birmingham | London | Exeter |
| --- | --- | --- | --- | --- | --- | --- |
| Depots |
| Newcastle  | 0.5 |   - |
| Birmingham | 0.5 | 0.3 |
| London     | 1.0 | 0.5 |
| Exeter     | 0.2 | 0.2 |
| Customers |
| C1 | 1.0 | 2.0 |   - | 1.0 |   - |   - |
| C2 |   - |   - | 1.5 | 0.5 | 1.5 |   - |
| C3 | 1.5 |   - | 0.5 | 0.5 | 2.0 | 0.2 |
| C4 | 2.0 |   - | 1.5 | 1.0 |   - | 1.5 |
| C5 |   - |   - |   - | 0.5 | 0.5 | 0.5 |
| C6 | 1.0 |   - | 1.0 |   - | 1.5 | 1.5 |

The question to be answered is how to satisfy the demands of the end customers while minimizing shipping costs.

---
## Model Formulation

### Sets and Indices

$f \in \text{Factories}=\{\text{Liverpool}, \text{Brighton}\}$

$d \in \text{Depots}=\{\text{Newcastle}, \text{Birmingham}, \text{London}, \text{Exeter}\}$

$c \in \text{Customers}=\{\text{C1}, \text{C2}, \text{C3}, \text{C4}, \text{C5}, \text{C6}\}$

$\text{Cities} = \text{Factories} \cup \text{Depots} \cup \text{Customers}$

### Parameters

$\text{cost}_{s,t} \in \mathbb{R}^+$: Cost of shipping one ton from source $s$ to destination $t$.

$\text{supply}_f \in \mathbb{R}^+$: Maximum possible supply from factory $f$ (in tons).

$\text{through}_d \in \mathbb{R}^+$: Maximum possible flow through depot $d$ (in tons).

$\text{demand}_c \in \mathbb{R}^+$: Demand for goods at customer $c$ (in tons).

### Decision Variables

$\text{flow}_{s,t} \in \mathbb{N}^+$: Quantity of goods (in tons) that is shipped from source $s$ to destionation $t$.

### Objective Function

- **Cost**: Minimize total shipping costs.

\begin{equation}
\text{Minimize} \quad Z = \sum_{(s,t) \in \text{Cities} \times \text{Cities}}{\text{cost}_{s,t}*\text{flow}_{s,t}}
\end{equation}

### Constraints

- **Factory output**: Flow of goods from a factory must respect maximum capacity.

\begin{equation}
\sum_{t \in \text{Cities}}{\text{flow}_{f,t}} \leq \text{supply}_{f} \quad \forall f \in \text{Factories}
\end{equation}

- **Customer demand**: Flow of goods must meet customer demand.

\begin{equation}
\sum_{s \in \text{Cities}}{\text{flow}_{s,c}} = \text{demand}_{c} \quad \forall c \in \text{Customers}
\end{equation}

- **Depot flow**: Flow into a depot equals flow out of the depot.

\begin{equation}
\sum_{s \in \text{Cities}}{\text{flow}_{s,d}} =
\sum_{t \in \text{Cities}}{\text{flow}_{d,t}}
\quad \forall d \in \text{Depots}
\end{equation}

- **Depot capacity**: Flow into a depot must respect depot capacity.

\begin{equation}
\sum_{s \in \text{Cities}}{\text{flow}_{s,d}} \leq \text{through}_{d}
\quad \forall d \in \text{Depots}
\end{equation}

---
## Python Implementation

We import the Gurobi Python Module and other Python libraries.

```python
import pandas as pd

import gurobipy as gp
from gurobipy import GRB

# tested with Python 3.7.0 & Gurobi 9.0
```

## Input Data
We define all the input data for the model.

```python
# Create dictionaries to capture factory supply limits, depot throughput limits, and customer demand.

supply = dict({'Liverpool': 150000,
               'Brighton': 200000})

through = dict({'Newcastle': 70000,
                'Birmingham': 50000,
                'London': 100000,
                'Exeter': 40000})

demand = dict({'C1': 50000,
               'C2': 10000,
               'C3': 40000,
               'C4': 35000,
               'C5': 60000,
               'C6': 20000})

# Create a dictionary to capture shipping costs.

arcs, cost = gp.multidict({
    ('Liverpool', 'Newcastle'): 0.5,
    ('Liverpool', 'Birmingham'): 0.5,
    ('Liverpool', 'London'): 1.0,
    ('Liverpool', 'Exeter'): 0.2,
    ('Liverpool', 'C1'): 1.0,
    ('Liverpool', 'C3'): 1.5,
    ('Liverpool', 'C4'): 2.0,
    ('Liverpool', 'C6'): 1.0,
    ('Brighton', 'Birmingham'): 0.3,
    ('Brighton', 'London'): 0.5,
    ('Brighton', 'Exeter'): 0.2,
    ('Brighton', 'C1'): 2.0,
    ('Newcastle', 'C2'): 1.5,
    ('Newcastle', 'C3'): 0.5,
    ('Newcastle', 'C5'): 1.5,
    ('Newcastle', 'C6'): 1.0,
    ('Birmingham', 'C1'): 1.0,
    ('Birmingham', 'C2'): 0.5,
    ('Birmingham', 'C3'): 0.5,
    ('Birmingham', 'C4'): 1.0,
    ('Birmingham', 'C5'): 0.5,
    ('London', 'C2'): 1.5,
    ('London', 'C3'): 2.0,
    ('London', 'C5'): 0.5,
    ('London', 'C6'): 1.5,
    ('Exeter', 'C3'): 0.2,
    ('Exeter', 'C4'): 1.5,
    ('Exeter', 'C5'): 0.5,
    ('Exeter', 'C6'): 1.5
})
```

## Model Deployment

We create a model and the variables. The variables simply capture the amount of product that flows along each allowed path between a source and destination.  Objective coefficients are provided here (in $\text{cost}$) , so we don't need to provide an optimization objective later.

```python
model = gp.Model('SupplyNetworkDesign')
flow = model.addVars(arcs, obj=cost, name="flow")
```

    Using license file c:\gurobi\gurobi.lic

Our first constraints require the total flow along arcs leaving a factory to be at most as large as the supply capacity of that factory.

```python
# Production capacity limits

factories = supply.keys()
factory_flow = model.addConstrs((gp.quicksum(flow.select(factory, '*')) <= supply[factory]
                                 for factory in factories), name="factory")
```

Our next constraints require the total flow along arcs entering a customer to be equal to the demand from that customer.

```python
# Customer demand

customers = demand.keys()
customer_flow = model.addConstrs((gp.quicksum(flow.select('*', customer)) == demand[customer]
                                  for customer in customers), name="customer")
```

Our final constraints relate to depots.  The first constraints require that the total amount of product entering the depot must equal the total amount leaving.

```python
# Depot flow conservation

depots = through.keys()
depot_flow = model.addConstrs((gp.quicksum(flow.select(depot, '*')) == gp.quicksum(flow.select('*', depot))
                               for depot in depots), name="depot")
```

The second set limits the product passing through the depot to be at most equal the throughput of that deport.

```python
# Depot throughput

depot_capacity = model.addConstrs((gp.quicksum(flow.select('*', depot)) <= through[depot]
                                   for depot in depots), name="depot_capacity")
```

We now optimize the model

```python
model.optimize()
```

    Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (win64)
    Thread count: 4 physical cores, 8 logical processors, using up to 8 threads
    Optimize a model with 16 rows, 29 columns and 65 nonzeros
    Model fingerprint: 0x3607c855
    Coefficient statistics:
      Matrix range     [1e+00, 1e+00]
      Objective range  [2e-01, 2e+00]
      Bounds range     [0e+00, 0e+00]
      RHS range        [1e+04, 2e+05]
    Presolve removed 1 rows and 0 columns
    Presolve time: 0.03s
    Presolved: 15 rows, 29 columns, 64 nonzeros

    Iteration    Objective       Primal Inf.    Dual Inf.      Time
           0    1.4800000e+05   1.812500e+04   0.000000e+00      0s
           7    1.9850000e+05   0.000000e+00   0.000000e+00      0s

    Solved in 7 iterations and 0.03 seconds
    Optimal objective  1.985000000e+05

---
## Analysis

Product demand from all of our customers can be satisfied for a total cost of $\$198,500$. The optimal plan is as follows.

```python
product_flow = pd.DataFrame(columns=["From", "To", "Flow"])
for arc in arcs:
    if flow[arc].x > 1e-6:
        product_flow = product_flow.append({"From": arc[0], "To": arc[1], "Flow": flow[arc].x}, ignore_index=True)
product_flow.index=[''] * len(product_flow)
product_flow
```

      From
      To
      Flow

      Liverpool
      C1
      50000.0

      Liverpool
      C6
      20000.0

      Brighton
      Birmingham
      50000.0

      Brighton
      London
      55000.0

      Brighton
      Exeter
      40000.0

      Birmingham
      C2
      10000.0

      Birmingham
      C4
      35000.0

      Birmingham
      C5
      5000.0

      London
      C5
      55000.0

      Exeter
      C3
      40000.0

---
## References

H. Paul Williams, Model Building in Mathematical Programming, fifth edition.

Copyright © 2020 Gurobi Optimization, LLC

```python

```


```python
import ipywidgets as widgets
from IPython.display import display, Markdown
import pandas as pd
import numpy as np
import copy
import sympy as sp
from sympy import sympify
import ipysheet
from ipysheet import sheet, cell
from ipysheet import column, row
from IPython.display import clear_output
from IPython.display import Javascript
import simplex_algorithm  as sa
import functools
M = sp.symbols('M')

#display(Markdown(("# Simplex Tableau"))

def on_restr_change(change, anz_var, anz_schlupf_var, anz_kuenstl_var, button, display_variable_input):
    with display_variable_input:
        if change['new'] != '0':
            anz_var.options = list(map(str, range(1, int(change['new'])+1,)))
            anz_schlupf_var.options = list(map(str, range(0, int(change['new'])+1)))
            anz_kuenstl_var.options = list(map(str, range(0, int(change['new'])+1)))
            anz_var.layout.visibility = "visible"
            anz_schlupf_var.layout.visibility = "visible"
            anz_kuenstl_var.layout.visibility = "visible"
            button.layout.visibility = "visible"
        else:
            anz_var.layout.visibility = "hidden"
            anz_schlupf_var.layout.visibility = "hidden"
            anz_kuenstl_var.layout.visibility = "hidden"

def create_tableau(change, anz_restriktionen,  anz_var, anz_schlupf_var, anz_kuenstl_var, button):
    clear_output(True)
    display(anz_restriktionen)
    display(anz_var)
    display(anz_schlupf_var)
    display(anz_kuenstl_var)
    display(button)

def adjust_cj(change, cell_table):
    for row in range(2, len(cell_table)):
        if change['new'] == cell_table[row][1].value:
            if "s" in cell_table[row][1].value:
                cell_table[row][0].value = cell(row,0, 0, read_only=True, background_color = "white")
            elif "x" in cell_table[row][1].value:
                cell_table[row][0] = cell(row, 0, "...", background_color = 'yellow')

def update_table(table, input_table, simplex_start):
    input_table = table['new']
    simplex_start.disabled = True

def correct_input(button, simplex_start, cell_table):
    wrong_input_counter = 0
    for row in range(0,len(cell_table)):
        for column in range(0,len(cell_table[0])):
            if cell_table[row][column].style == {'backgroundColor': 'yellow'} or cell_table[row][column].style == {'backgroundColor': 'red'} and column != 1:
                try:
                    test = float(cell_table[row][column].value)
                    cell_table[row][column].style = {'backgroundColor': 'yellow'}
                except ValueError:
                    cell_table[row][column].style = {'backgroundColor': 'red'}
                    wrong_input_counter += 1
    if wrong_input_counter == 0:
        simplex_start.disabled = False
    else:
        simplex_start.disabled = True

def start_simplex(button, input_table, sum_var, display_output):

    display_output.clear_output()
    copy_tableau = ipysheet.to_dataframe(input_table)
    copy_tableau = copy_tableau.apply(pd.to_numeric, errors='ignore', downcast='float')
    copy_tableau.columns = range(0, sum_var+3)
    cj = []
    cj_zj = []
    for column in range(0,len(copy_tableau.columns)):
        if column == 0:
            cj.append(np.nan)
            cj_zj.append(np.nan)
        elif column == 1:
            cj.append("cj")
            cj_zj.append("cj-zj")
        elif column == 2:
            cj.append(0)
            cj_zj.append(np.nan)
        else:
            cj.append(0)
            cj_zj.append(0)

    copy_tableau.loc[len(copy_tableau.index)] = cj
    copy_tableau.loc[len(copy_tableau.index)] = cj_zj
    copy_tableau.replace('-M', -M,  inplace = True)

    for row in copy_tableau.index:
        for column in copy_tableau.columns:
            try:
                copy_tableau.loc[row][column] = int(copy_tableau.loc[row][column])
            except Exception:
                pass

    sa.get_cj_zj(copy_tableau)
    #global tableau
    tableau = copy_tableau
    #Simplex Algorithmus

    list_tableaus, Meldungen, list_pivot_elements = sa.simplex_algorithm(tableau, 10,M)

    pd.set_option("precision", 3)

    with display_output:
        display(Markdown("## Ergebnis"))

    #Erzeuge Tableaus (untereinander damit man es ausdrucken kann)
    for table in range(0,len(list_tableaus)):
        with display_output:
            display(Markdown("### " + str(table) + ".Tableau"))
            display(list_tableaus[table].style\
                        .apply(lambda x: ['background: lightblue' if x.name == list_pivot_elements[table][1] else '' for i in x])\
                        .apply(lambda x: ['background: lightblue' if x.name == list_pivot_elements[table][0] else '' for i in x], axis=1)\
                        .hide_index()\
                        .hide_columns())
        for message in range(len(Meldungen[table])):
            with display_output:
                display(widgets.Label(value=Meldungen[table][message]))

def create_input_table(button, anz_restriktionen, anz_var ,anz_schlupf_var, anz_kuenstl_var ,display_table_input, display_output):

    display_table_input.clear_output()
    list_var = []
    simplex_start = widgets.Button(description="Starte Simplex", disabled=True)
    check_input = widgets.Button(description="Überprüfe Eingabe")
    spalte = 3
    reihe_basis_var = 2
    sum_var=int(anz_var.value) + int(anz_schlupf_var.value) + int(anz_kuenstl_var.value)
    input_table = ipysheet.sheet(rows=2+float(anz_restriktionen.value), columns=sum_var+3,  row_headers=False, column_headers=False)
    M = sp.symbols('M')

    #zwei Dimensionales Array, welches Sheet repräsentiert
    cell_table = [[0]*input_table.columns for i in range(input_table.rows)]

    for row in range(0,input_table.rows):
        for column in range(0,input_table.columns):
            if column != 0 and column != 1:
                cell_table[row][column] = cell(row, column, "...", background_color = 'yellow')
                cell_table[row][column].observe(functools.partial(update_table,
                                                                  input_table = input_table,
                                                                  simplex_start =simplex_start
                                                                 )
                                               )

    cell_table[0][0] = cell(0,0, "", read_only=True, background_color='grey')
    cell_table[0][1] = cell(0,1, "", read_only=True, background_color='grey')
    cell_table[0][2] = cell(0,2, "", read_only=True, background_color='grey')

    #Befülle Reihe mit Beschreibung

    cell_table[1][0] = cell(1, 0, "cj", read_only=True, font_weight = 'bold', background_color = "white")
    cell_table[1][1] = cell(1, 1, "Basisvariable", read_only=True, font_weight = 'bold', background_color = "white")
    cell_table[1][2] = cell(1, 2, "Quantity", read_only=True, font_weight = 'bold', background_color = "white")

    for anz in range(1, int(anz_var.value)+1):
        var_name = "x"+ str(anz)
        cell_table[1][spalte] = cell(1,spalte, var_name, read_only=True, font_weight = 'bold', background_color = "white")
        list_var.append(cell_table[1][spalte])
        spalte += 1

    for anz in range(1, int(anz_schlupf_var.value)+1):
        var_name = "s"+ str(anz)
        cell_table[0][spalte] = cell(0,spalte, 0, read_only=True, background_color = "white")
        cell_table[1][spalte] = cell(1,spalte, var_name, read_only=True, font_weight = 'bold', background_color = "white")
        list_var.append(cell_table[1][spalte])
        spalte += 1

    for anz in range(1, int(anz_kuenstl_var.value)+1):
        var_name =  "a"+ str(anz)
        cell_table[0][spalte] = cell(0,spalte, "-M", read_only=True, background_color = "white")
        cell_table[1][spalte] = cell(1,spalte, var_name, read_only=True, font_weight = 'bold', background_color = "white")
        list_var.append(cell_table[1][spalte])
        spalte += 1

    start_basisvar = list_var[(len(list_var)-int(anz_restriktionen.value)):]
    start_basisvar.sort(key=lambda x: x.value)
    basis_selection = []

    for var in list_var:
        if "a" in var.value:
            break
        basis_selection.append(var.value)

    for row in range(2,input_table.rows):
        if "a" in start_basisvar[row-2].value:
            cell_table[row][0] = cell(row,0, '-M', read_only=True, background_color = "white")
            cell_table[row][1] = cell(row,1, start_basisvar[row-2].value, read_only=True, font_weight = 'bold', background_color = "white")
        if "s" in start_basisvar[row-2].value:
            cell_table[row][0] = cell(row,0, 0, read_only=True, background_color = "white")
            cell_table[row][1] = cell(row,1, start_basisvar[row-2].value, read_only=False, font_weight = 'bold', choice = basis_selection, background_color = "yellow")
            cell_table[row][1].observe(functools.partial(adjust_cj, cell_table = cell_table))
        if "x" in start_basisvar[row-2].value:
            cell_table[row][1] = cell(row,1, start_basisvar[row-2].value, read_only=False, font_weight = 'bold', choice = basis_selection, background_color = "yellow")
            cell_table[row][0] = cell(row, 0, "...", background_color = 'yellow')
            cell_table[row][1].observe(functools.partial(adjust_cj, cell_table = cell_table))

    with display_table_input:
        display(Markdown("## Erzeuge das Standardtableau"))
        display(input_table)
        display(check_input)
        display(simplex_start)

    check_input.on_click(functools.partial(correct_input,
                                           simplex_start = simplex_start,
                                           cell_table = cell_table
                                          )
                        )

    simplex_start.on_click(functools.partial(start_simplex, input_table = input_table, sum_var = sum_var, display_output = display_output))

    # example
    #0                                      300         200   0   0  -M  -M
    #1   cj  Basisvariable  Quantity         x1          x2  s1  s2  a1  a2
    #2   -M             a1        60          2           2   0   0   1   0
    #3   -M             a2        80          2           8  -1   0   0   1
    #4    0             s2        40          1           0   0   1   0   0
    #5  NaN             cj    -140*M       -4*M       -10*M   M   0  -M  -M
    #6  NaN          cj-zj       NaN  4*M + 300  10*M + 200  -M   0   0   0

display(Markdown("# Simplex Tableau"))
display(Markdown("## Definiere Art/Anzahl der Variablen"))
display_variable_input = widgets.Output()
display_table_input = widgets.Output()
display_output = widgets.Output()

anz_restriktionen = widgets.Dropdown(
                        options=list(map(str, range(0, 6))),
                        value= '0',
                        description='Restriktionen:',
                        disabled=False,
                    )

anz_var = widgets.Dropdown(
            options=list(map(str, range(1, 6))),
            value='1',
            description='echte Variablen:',
            disabled=False,
         )

anz_schlupf_var = widgets.Dropdown(
                    options=list(map(str, range(0, 6))),
                    value='0',
                    description='Schlupfvariablen:',
                    disabled=False,
                )

anz_kuenstl_var = widgets.Dropdown(
                    options=list(map(str, range(0, 6))),
                    value='0',
                    description='künstliche Variablen:',
                    disabled=False,
                )
#sum_var = int(anz_var.value) + int(anz_schlupf_var.value) + int(anz_kuenstl_var.value)
button = widgets.Button(description="Erzeuge Eingabetableau!")

anz_restriktionen.observe(functools.partial(on_restr_change,
                                            anz_var = anz_var,
                                            anz_schlupf_var = anz_schlupf_var,
                                            anz_kuenstl_var = anz_kuenstl_var,
                                            button = button,
                                            display_variable_input = display_variable_input),
                          names = "value"
                         )

button.on_click(functools.partial(create_tableau,
                                  anz_restriktionen = anz_restriktionen,
                                  anz_var = anz_var,
                                  anz_schlupf_var = anz_schlupf_var,
                                  anz_kuenstl_var = anz_kuenstl_var,
                                  button = button)
               )
display(display_variable_input)
display(display_table_input)
display(display_output)

anz_restriktionen.layout.visibility = "visible"
anz_var.layout.visibility = "hidden"
anz_schlupf_var.layout.visibility = "hidden"
anz_kuenstl_var.layout.visibility = "hidden"
button.layout.visibility = "hidden"

with display_variable_input:
    display(anz_restriktionen)
    display(anz_var)
    display(anz_schlupf_var)
    display(anz_kuenstl_var)
    display(button)

button.on_click(functools.partial(create_input_table,
                                  anz_restriktionen = anz_restriktionen,
                                  anz_var = anz_var,
                                  anz_schlupf_var = anz_schlupf_var,
                                  anz_kuenstl_var = anz_kuenstl_var,
                                  display_table_input = display_table_input,
                                  display_output = display_output
                                 )
               )
```

# Simplex Tableau

## Definiere Art/Anzahl der Variablen

    Output()

    Output()

    Output()


```python
# This cell is for the Google Colaboratory
# https://stackoverflow.com/a/63519730
if 'google.colab' in str(get_ipython()):
  # https://colab.research.google.com/notebooks/io.ipynb
  import google.colab.drive as gcdrive
  # may need to visit a link for the Google Colab authorization code
  gcdrive.mount("/content/drive/")
  import sys
  sys.path.insert(0,"/content/drive/My Drive/Colab Notebooks/nmisp/60_linear_algebra_2")

```

```python
# 그래프, 수학 기능 추가
# Add graph and math features
import pylab as py
import numpy as np
import numpy.linalg as nl
# 기호 연산 기능 추가
# Add symbolic operation capability
import sympy as sy

```

참고문헌 : Pytel 외 저, 이주성 외 역, 재료역학, 2판, 한티미디어, 2013.
Ref: Pytel, Kiusalaas, Sharma, Mechanics of Materials, 2nd Ed., Cengage Learning, 2013.

다음과 같은 보의 반력을 구해 보자.

Let's try to find the reaction forces of the following beam.

길이 $L=7 m$
Length $L=7 m$

```python
L_m = 7

```

양단 단순 지지
Simple supports at both ends

왼쪽 끝에서 $x_1 = 2 m$ 지점에 $F_1 = 14 kN$ 하중

$F_1 = 14 kN$ load at $x_1 = 2 m$ from the left end

```python
x_1_m = 2
F_1_N = 14e3

```

왼쪽 끝에서 $x_2 = 5 m$ 지점에 $F_2 = 28 kN$ 하중

$F_2 = 28 kN$ load at $x_2 = 5 m$ from the left end

```python
x_2_m = 5
F_2_N = 28e3

```

$y$ 방향 힘의 평형
Force equilibrium in $y$ direction

$$
R_1 + R_2 = F_1 + F_2
$$

$x_1=2m$ 지점에서의 모멘트 평형

$$
-x_1R_1 + \left(L-x_1 \right) R_2 = \left(x_2 - x_1\right) F_2
$$

행렬 형태로는 다음과 같다.
In matrix form:

$$
\begin{bmatrix}
1 & 1 \\
-x_1 & L-x_1 \\
\end{bmatrix}
\begin{pmatrix}
R_1 \\
R_2 \\
\end{pmatrix}
=
\begin{pmatrix}
F_1 + F_2 \\
\left(x_2 - x_1\right)F_2 \\
\end{pmatrix}
$$

따라서 계수 행렬과 상수항 열 벡터는 각각 다음과 같다.

Thus coefficient matrix and the column vector of constant terms are as follows, respectively. (https://en.wikipedia.org/wiki/Coefficient_matrix)

```python
A = np.array([[1, 1],
              [-x_1_m, L_m - x_1_m]])

```

```python
b = np.array([[F_1_N + F_2_N],
              [(x_2_m - x_1_m)*F_2_N]])

```

위 방정식의 해는 다음과 같이 구할 수 있다.

We can find the solution of the equation above as follows.

```python
nl.solve(A, b)

```

첫 행의 값은 $R_1$, 두번째 행의 값은 $R_2$이다.

The values of the first and second row are $R_1$ and $R_2$ respectively.

## Final Bell
마지막 종

```python
# stackoverfow.com/a/24634221
import os
os.system("printf '\a'");

```

```python

```


```python
# 그래프, 수학 기능 추가
# Add graph and math features
import pylab as py
import numpy as np
import numpy.linalg as nl
# 기호 연산 기능 추가
# Add symbolic operation capability
import sympy as sy

```

```python
sy.init_printing()

```

# 전진 오일러법: 고차 상미분 방정식
Forward Euler Method: Higher Order ODE

## 1계 미분방정식의 예
An example of a first order ODE

$$
\left\{
    \begin{align}
        2 \frac{d}{dt}x(t)+ x(t)&=0 \\
        x(0)&=x_0 \\
    \end{align}
\right.
$$

위 1계 미분방정식의 경우 다음과 같이 근사해를 구할 수 있었다.
We could find an approximate solution for a first order ordinary differential equation as follows.

```python
a_0, a_1 = 2.0, 1.0

def dx_dt(x, t):
    return - a_1 * x / a_0

```

```python
def forward_euler(f, t_array, x_0):
    time_list = [t_array[0]]
    result_list = [x_0]

    x_i = x_0

    for k, t_i in enumerate(t_array[:-1]):
        # time step
        delta_t = t_array[k+1] - t_array[k]

        # slope
        s_i = f(x_i, t_i)

        # x[i + 1]
        x_i_plus_1 = x_i + s_i * delta_t

        time_list.append(t_array[k+1])
        result_list.append(x_i_plus_1)

        x_i = x_i_plus_1

    return time_list, result_list

```

```python
# Time step interval
delta_t = 0.1

# Time array
t_sec_array = np.arange(0, 6 + delta_t*0.5, delta_t)

# Initial state
x_0 = 4.5

# *** ODE solver ***
t_01, x_01 = forward_euler(dx_dt, t_sec_array, x_0)

```

```python
def exact(t):
    return x_0 * py.exp((-a_1 / a_0) * t)

```

```python
py.plot(t_01, x_01, '.-', label=f'$\\Delta t={delta_t}$')

# Indicate the exact solution
exact_x_array = exact(t_sec_array)
py.plot(t_sec_array, exact_x_array, label='exact')

py.axis('equal')

py.xlabel('t(sec)')
py.ylabel('x(m)')

py.legend(loc=0)
py.grid(True)

```

그러나 앞으로 만나게 될 미분 방정식은 더 높은 차수의 미분을 포함할 수 있다.
However, in the future, we may meet differential equations including higher order differentiation.

## 2계 (비선형) 상미분 방정식
Second order (Nonlinear) Ordinary Differential Equation

예를 들어 단진자가 어떻게 움직이는지 다음과 같이 묘사할 수 있다.

For example, we can describe how a simple pendulum moves as follows.

Ref : Wikipedia contributors, 'Pendulum (mathematics)', Wikipedia, The Free Encyclopedia, 2 June 2018, 13:28 UTC, <https://en.wikipedia.org/w/index.php?title=Pendulum_(mathematics)&oldid=844080803> [accessed 5 August 2018]

$$
\frac{d^2\theta}{dt^2} + \frac{g}{l}sin\theta = 0
$$

위 상미분 방정식은 $\theta$의 2계 미분과 $sin\theta$ 를 포함하고 있다.
The ordinary differential equation above includes a second order derivative of $\theta$ and $sin\theta$.

여기서 $sin\theta$ 는 비선형으로, 위 해당 식은 2계 비선형 상미분 방정식이다.
Here $sin\theta$ is nonlinear; thus the equation above is a second order nonlinear ordinary differential equation.

해당 방정식을 풀기 위해 다음과 같이 $x_0$, $x_1$ 와 같은 상태변수를 도입하자.
To solve the equation, let's introduce state variables $x_0$ and $x_1$ as follows.

$$
\begin{cases}
    x_0 = \theta\\
    x_1 = \frac{d\theta}{dt}  = \frac{d}{dt}x_0\\
\end{cases}
$$

방정식의 각 항을 상태변수로 다시 써 보자.
Let's rewrite each term of the equation using the state variables.

$$
\begin{cases}
    sin \theta = sin x_0\\
    \frac{d^2\theta}{dt^2} = \frac{d}{dt} \frac{d\theta}{dt}= \frac{d}{dt} x_1\\
\end{cases}
$$

다시 방정식에 대입해 보자.
Let's substitute back to the equation.

$$
\frac{dx_1}{dt} + \frac{g}{l}sinx_0 = 0 \\
\frac{dx_1}{dt} =- \frac{g}{l}sinx_0
$$

$x_0$와 $x_1$의 미분을 살펴 보자.
Let's take a look at the derivatives of $x_0$ and $x_1$.

$$
\frac{d}{dt}
\begin{pmatrix}
    x_0\\
    x_1
\end{pmatrix}
=
\begin{pmatrix}
    \frac{d}{dt}x_0\\
    \frac{d}{dt}x_1
\end{pmatrix}
=
\frac{d}{dt}
\begin{pmatrix}
    \theta\\
    \frac{d}{dt}\theta
\end{pmatrix}
=
\begin{pmatrix}
    x_1\\
    -\frac{g}{l}sinx_0
\end{pmatrix}
$$

이를 python 함수로 구현해 보자.
Let's implement this in a python function.

```python
g_mpsps = 9.8
l_m = 0.3

legends = ('$\\theta(deg)$', '$\\frac{d}{dt}\\theta(deg/s)$')
ylabel = ''

# Initial state
x_0 = np.array([np.deg2rad(90), 0])

def pendulum_NL(x, t):
    """
    Parameters
    ==========
    x: array of theta and d(theta)/dt
    t: time value

    Return Value
    ============
    One dimensional array of dx/dt
    """

    return np.array([x[1], (-g_mpsps/l_m)*np.sin(x[0])])

```

전진 오일러법을 적용해 보자.
Let's apply the Forward Euler Method.

```python
# Time step interval
delta_t = 0.1

# Time array
t_sec_array = np.arange(0, 6 + delta_t*0.5, delta_t)

# *** ODE solver ***
t_01, x_01 = forward_euler(pendulum_NL, t_sec_array, x_0)

```

```python
x_result_array = np.array(x_01).T
for label, x_result in zip(legends, x_result_array):
    py.plot(t_01, np.rad2deg(x_result), label=label)
# https://stackoverflow.com/questions/11481644/how-do-i-assign-multiple-labels-at-once-in-matplotlib

py.xlabel('t(sec)')
py.ylabel(ylabel)

py.legend(loc=0)
py.grid(True)

```

위 근사해는 불안정해 보인다. $\Delta t$를 줄여 보자.
The approximate solution looks unstable. Let's make $\Delta t$ smaller.

```python
# Time step interval
delta_t = 0.001

# Time array
t_sec_array = np.arange(0, 6 + delta_t*0.5, delta_t)

# *** ODE solver ***
t_01, x_01 = forward_euler(pendulum_NL, t_sec_array, x_0)

```

```python
x_result_array = np.array(x_01).T
for label, x_result in zip(legends, x_result_array):
    py.plot(t_01, np.rad2deg(x_result), label=label)
# https://stackoverflow.com/questions/11481644/how-do-i-assign-multiple-labels-at-once-in-matplotlib

py.xlabel('t(sec)')
py.ylabel(ylabel)

py.legend(loc=0)
py.grid(True)

```

30cm 길이의 단진자로 시험해 보자.
Let's test using a 30cm simple pendulum.

## 높은 차수의 선형 상미분 방정식
Linear Ordinary Differential Equation of Higher Order

위 예에서는 어떻게 2차 (비선형) 상미분 방정식에 전진 오일러법을 적용하는지 살펴보았다.

In the previous example, we observed how to apply forward euler method to a 2nd order (nonlinear) ordinary differential equation.

일반적으로, 우변이 0인 (모든 가능한) $n$차 선형 상미분 방정식은 다음과 같이 쓸 수 있다.

In general, we can write (all possible) $n$th order linear ordinary differential equation with right side zero as follows.

$$
    a_0 \frac{d^nx}{dt^n} + a_1 \frac{d^{n-1}x}{dt^{n-1}} + a_2 \frac{d^{n-2}x}{dt^{n-2}} + \ldots + a_i  \frac{d^{n-i}x}{dt^{n-i}} + \ldots + a_{n-2} \frac{d^2x}{dt^2} + a_{n-1} \frac{dx}{dt} + a_n x = 0
$$

가장 높은 차수의 항만 남기고 모두 우변으로 옮겨 보자.

Let's move everyone to the right side except the highest order term.

$$
\begin{align}
    a_0 \frac{d^nx}{dt^n} &=
    - a_1 \frac{d^{n-1}x}{dt^{n-1}}
    - a_2 \frac{d^{n-2}x}{dt^{n-2}}
    - \ldots
    - a_i  \frac{d^{n-i}x}{dt^{n-i}}
    - \ldots
    - a_{n-2} \frac{d^2x}{dt^2}
    - a_{n-1} \frac{dx}{dt}
    - a_n x \\
    \frac{d^nx}{dt^n} &=
    - \frac{a_1}{a_0} \frac{d^{n-1}x}{dt^{n-1}}
    - \frac{a_2}{a_0} \frac{d^{n-2}x}{dt^{n-2}}
    - \ldots
    - \frac{a_i}{a_0}  \frac{d^{n-i}x}{dt^{n-i}}
    - \ldots
    - \frac{a_{n-2}}{a_0} \frac{d^2x}{dt^2}
    - \frac{a_{n-1}}{a_0} \frac{dx}{dt}
    - \frac{a_n}{a_0} x
\end{align}
$$

앞과 마찬가지로, 최고 차수인 $n$ 개의 상태변수를 도입해 보자.
As before, let's introduce $n$, the highest order, state variables.

$$
\mathbf{q}=
\begin{pmatrix}
    q_0 \\ q_1 \\ q_2 \\ \vdots \\ q_i \\ \vdots \\ q_{n-2} \\ q_{n-1}
\end{pmatrix}
=
\begin{pmatrix}
    x \\ \frac{d}{dt}x \\ \frac{d^2}{dt^2}x \\ \vdots \\ \frac{d^i}{dt^i}x \\ \vdots \\ \frac{d^{n-2}}{dt^{n-2}}x \\ \frac{d^{n-1}}{dt^{n-1}}x
\end{pmatrix}
$$

가장 높은 차수의 미분을 상태변수로 표시해 보자.
Let's indicate the highest order differentiation in the state variable form.

$$
    \frac{d^nx}{dt^n} =
    - \frac{a_1}{a_0} q_{n-1}
    - \frac{a_2}{a_0} q_{n-2}
    - \ldots
    - \frac{a_i}{a_0}  q_{n-i}
    - \ldots
    - \frac{a_{n-2}}{a_0} q_{2}
    - \frac{a_{n-1}}{a_0} q_{1}
    - \frac{a_n}{a_0} q_{0}
$$

시간에 대해 $\mathbf{q}$ 를 미분해 보자.
Let's differentiate $\mathbf{q}$ with respect to time.

$$
\frac{d}{dt}\mathbf{q}=
\begin{pmatrix}
    \frac{d}{dt}q_0 \\ \frac{d}{dt}q_1 \\ \frac{d}{dt}q_2 \\ \vdots \\ \frac{d}{dt}q_i \\ \vdots \\ \frac{d}{dt}q_{n-2} \\ \frac{d}{dt}q_{n-1}
\end{pmatrix}
=
\begin{pmatrix}
    \frac{d}{dt}x \\ \frac{d^2}{dt^2}x  \\ \frac{d^3}{dt^3}x \\ \vdots \\ \frac{d^i}{dt^i}x \\ \vdots \\ \frac{d^{n-1}}{dt^{n-1}}x \\ \frac{d^{n}}{dt^{n}}x
\end{pmatrix}
=
\begin{pmatrix}
q_1 \\ q_2 \\ q_3 \\ \vdots \\ q_{i+1} \\ \vdots \\ q_{n-1} \\
- \frac{a_1}{a_0} q_{n-1}
    - \frac{a_2}{a_0} q_{n-2}
    - \ldots
    - \frac{a_i}{a_0}  q_{n-i}
    - \ldots
    - \frac{a_{n-2}}{a_0} q_{2}
    - \frac{a_{n-1}}{a_0} q_{1}
    - \frac{a_n}{a_0} q_{0}
\end{pmatrix}
$$

행렬 형태로 다시 쓸 수도 있다.
We can rewrite in a matrix form.

$n=4$인 경우를 생각해 보자.
Let's think about a case of $n=4$.

$$
\frac{d\mathbf{q}}{dt}
=
\begin{pmatrix}
\frac{d}{dt} q_0 \\ \frac{d}{dt} q_1 \\ \frac{d}{dt} q_2 \\ \frac{d}{dt} q_3
\end{pmatrix}
=
\begin{pmatrix}
q_1 \\ q_2 \\ q_3 \\
- \frac{a_4}{a_0}q_0 - \frac{a_3}{a_0}q_1 - \frac{a_2}{a_0}q_2 - \frac{a_1}{a_0}q_3
\end{pmatrix}
=
\begin{bmatrix}
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
- \frac{a_4}{a_0} & - \frac{a_3}{a_0} & - \frac{a_2}{a_0} & - \frac{a_1}{a_0}
\end{bmatrix}
\begin{pmatrix}
q_0 \\ q_1 \\ q_2 \\ q_3
\end{pmatrix}
=
\mathbf{Aq}
$$

위 식의 의미는, $n$차 선형 상미분 방정식의 경우, $n$개의 1차 선형 상미분 방정식으로 바꾸어 풀 수 있다는 것이다.

The equation above means that we can solve a $n$th order linear ordinary differential equation by converting it to a system of $n$ first order linear differential equations.

다음 예를 살펴 보자.
Let's think about following example.

$$
         \frac{d^4x}{dt^4}
         + 12 \frac{d^3x}{dt^3}
         + 54 \frac{d^2x}{dt^2}
         + 108 \frac{dx}{dt}
         + 80 x = 0
$$

연립 1차 선형 상미분 방정식으로는 다음과 같이 다시 쓸 수 있다.
We can rewrite in a system of linear ODE's as follows.

$$
\frac{d\mathbf{q}}{dt}
=
\begin{bmatrix}
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
- 80 & - 108 & - 54 & -12
\end{bmatrix}
\begin{pmatrix}
q_0 \\ q_1 \\ q_2 \\ q_3
\end{pmatrix}
=
\mathbf{Aq}
$$

여기서 상태변수는 다음과 같다.
Here, state variables are as follows.

$$
\mathbf{q} = \begin{pmatrix}q_0 & q_1 & q_2 & q_3 \end{pmatrix}^T = \begin{pmatrix}x & \frac{dx}{dt} & \frac{d^2x}{dt^2} & \frac{d^3x}{dt^3} \end{pmatrix}^T
$$

python 함수 형태로도 써 보자.
Let's write a python function, too.

```python
matrix_A = np.matrix([
        [0, 1, 0, 0],
        [0, 0, 1, 0],
        [0, 0, 0, 1],
        [-80, -108, -54, -12],]
)

legends = (f'$q_{k}$' for k in range(matrix_A.shape[0]))

ylabel = '$\mathbf{q}$'

def fourth_order(q, t):
    """
    Parameters
    ==========
    q: array of q_0, q_1, q_2, and q_3
    t: time value

    Return Value
    ============
    One dimensional array of dq/dt
    """

    q_column = np.matrix(q).T
    qdot_column = matrix_A * q_column

    qdot_array = np.array(qdot_column.T).flatten()

    return qdot_array

```

전진 오일러법을 적용해 보자.
Let's apply the Forward Euler Method.

```python
# Time step interval
delta_t = 0.01

# Time array
t_sec_array = np.arange(0, 6 + delta_t*0.5, delta_t)

# Initial state
x_0 = np.array([1, 0, 0, 0])

# *** ODE solver ***
t, q_list = forward_euler(fourth_order, t_sec_array, x_0)

```

```python
q_result_array = np.array(q_list).T
for label, q_result in zip(legends, q_result_array):
    py.plot(t, q_result, label=label)
# https://stackoverflow.com/questions/11481644/how-do-i-assign-multiple-labels-at-once-in-matplotlib

py.xlabel('t(sec)')
py.ylabel(ylabel)

py.legend(loc=0)
py.grid(True)

```

## 부록: 단진자의 기울기 장
Appendix: Slope Field of the Simple Pendulum

$$
\frac{d}{dt}
\begin{pmatrix}
    x_0\\
    x_1
\end{pmatrix}
=
\begin{pmatrix}
    \frac{d}{dt}x_0\\
    \frac{d}{dt}x_1
\end{pmatrix}
=
\frac{d}{dt}
\begin{pmatrix}
    \theta\\
    \frac{d}{dt}\theta
\end{pmatrix}
=
\begin{pmatrix}
    x_1\\
    -\frac{g}{l}sinx_0
\end{pmatrix}
$$

단진자 사례의 상태변수는 $\left(\theta, \frac{d}{dt}\theta\right)$이다.
State variables of the simple pendulum example are $\left(\theta, \frac{d}{dt}\theta\right)$.

가로축을 $\theta$, 세로축을 $\frac{d}{dt}\theta$로 하는 2차원 좌표 평면을 생각해 보자.
Let's think about a 2D plane with $\theta$ as the horizontal axis and $\frac{d}{dt}\theta$ as the vertical axis.

각 상태에서 상태변수의 변화의 방향 $\left(\frac{d}{dt}\theta, \frac{d^2}{dt^2}\theta \right)$ 을 표시해 보자.
At each state, let's present the direction of state variable change $\left(\frac{d}{dt}\theta, \frac{d^2}{dt^2}\theta \right)$.

```python
def ode_slopes_2states_cartesian(func, theta_rad_list, theta_dot_rad_list, time_list):
    """
    Plot field of arrows indicating derivatives of the state
    :param func:
    :param theta_rad_list:
    :param theta_dot_rad_list:
    :param time_list:
    :return:
    """

    # cartesian coordinate
    y_rad = np.meshgrid(theta_rad_list, theta_dot_rad_list)

    # derivatives of state at each point
    y_rad_dot = func(y_rad, time_list)

    # color
    color_mesh = np.sqrt(y_rad_dot[0] * y_rad_dot[0] + y_rad_dot[1] * y_rad_dot[1])

    py.figure(figsize=(18, 18))
    py.axis('equal')
    py.quiver(py.rad2deg(y_rad[0]), py.rad2deg(y_rad[1]), py.rad2deg(y_rad_dot[0]), py.rad2deg(y_rad_dot[1]), color_mesh, angles='xy')
    l, r, b, t = py.axis()
    x_span, y2_mesh = r - l, t - b
    py.axis([l - 0.05 * x_span, r + 0.05 * x_span, b - 0.05 * y2_mesh, t + 0.05 * y2_mesh])
    py.grid()

```

```python
time_list = []

# list of theta
theta_deg_array = np.arange(-540, 540+1, 30)
theta_rad_list = np.deg2rad(theta_deg_array)

# list of theta_dot
theta_dot_deg_array = np.arange(-540, 540+1, 45)
theta_dot_rad_list = np.deg2rad(theta_dot_deg_array)

# plot direction field
ode_slopes_2states_cartesian(pendulum_NL, theta_rad_list, theta_dot_rad_list, time_list)

py.plot(py.rad2deg(x_result_array[0]), py.rad2deg(x_result_array[1]), 'r-')

ax = py.gca()

xlims = py.xlim(left=theta_deg_array[0], right=theta_deg_array[-1])
# http://matplotlib.1069221.n5.nabble.com/How-do-I-set-grid-spacing-td9968.html
ax.set_xticks(np.hstack([np.arange(0, xlims[1]+1, 90), np.arange(-90, xlims[0]-1, -90)]))

ylims = py.ylim(bottom=theta_dot_deg_array[0], top=theta_dot_deg_array[-1],)
# http://matplotlib.1069221.n5.nabble.com/How-do-I-set-grid-spacing-td9968.html
ax.set_yticks(np.hstack([np.arange(0, ylims[1]+1, 90), np.arange(-90, ylims[0]-1, -90)]))

py.xlabel('$\\theta(deg)$')
py.ylabel('$\\frac{d}{dt}\\theta(deg/sec)$')
py.title('Simple pendulum')

```

## 도전 과제
Try This

다음 2계 선형 상미분 방정식의 수치해를 전진 오일러법으로 구하시오:

Find the numerical solutions of the following second order linear ordinary equation using Forward Euler Method:

$$
\begin{align}
\frac{d^2}{dt^2}x(t) + 2\frac{d}{dt}x(t) + 2x(t) &= 0 \\
x(0) &= 0 \\
\frac{d}{dt}x(0) &= 1
\end{align}
$$

다음 2계 선형 상미분 방정식의 수치해를 전진 오일러법으로 구하시오:

Find the numerical solutions of the following second order linear ordinary equation using Forward Euler Method:

$$
\begin{align}
\frac{d^2}{dt^2}x(t) + 4\frac{d}{dt}x(t) + 3x(t) &= 0 \\
x(0) &= 0 \\
\frac{d}{dt}x(0) &= 1
\end{align}
$$

## Final Bell
마지막 종

```python
# stackoverfow.com/a/24634221
import os
os.system("printf '\a'");

```

```python

```