```python
import matplotlib.pyplot as plt
import plotly as py
import plotly.graph_objs as go
import numpy as np
import math
import ipywidgets as widgets
from IPython.display import display, Math, Latex, HTML
from astropy.table import Table, Column
from ipywidgets import interact, interactive

py.offline.init_notebook_mode(connected=True)

%matplotlib inline

font = {'family' : 'sans-serif',
        'weight' : 'normal',
        'size'   : 14}

plt.rc('font', **font)

#code for User_Graph

class User_Graph:

    def __init__(self,num_points,x_dim,y_dim):

        #how many points do you want the user to input?
        self.num_points = num_points

        #dimensions of the graph
        self.x_dim = x_dim
        self.y_dim = y_dim

        #create lists for x and y
        self.x_list = []
        self.y_list = []

    def user_input(self):
        #ask users to input data points
        try:
            #get x values
            x_list = input("Enter the x-values as a list of the form [a,b,c,d,...]: " )
            x_list = x_list.strip(']')
            x_list = x_list.strip('[')
            x_list = x_list.split(',')
            for i in x_list:
                x_val = float(i)
                self.x_list.append(x_val)

            #get y values
            y_list = input("Enter the y-values as a list of the form [a,b,c,d,...]: " )
            y_list = y_list.strip(']')
            y_list = y_list.strip('[')
            y_list = y_list.split(',')
            for i in y_list:
                y_val = float(i)
                self.y_list.append(y_val)

        except:
            print("Entries must be real numbers!")
            self.user_input()

        if len(self.x_list) != len(self.y_list):
            print("Must have an equal number of x and y values! Try Again.")
            self.user_input()

    def make_graph(self, title):
        #make a graph out of the available data points

        layout = go.Layout(
            title = title,
            yaxis = dict(
                title="<i>Y</i> Values",
                range = [self.y_dim[0], self.y_dim[1]]
            ),
            xaxis = dict(
                title="<i>X</i> Values",
                range = [self.x_dim[0], self.x_dim[1]]
            ),
        )

        trace1 = go.Scatter(
            x = self.x_list,
            y = self.y_list,
            mode = "lines+markers",
            name = "extend plot",
            line=dict(
                color = ('rgb(22, 96, 167)'),
                shape = "spline",
                dash = "dot"
            )

        )

        fig = go.Figure(data = [trace1], layout = layout)
        py.offline.iplot(fig)

    def run_it(self):
        # provide a title
        title = input("Give the graph a title: ")
        self.user_input()
        self.make_graph(title)

#code for Double_Graph

class Double_Graph:

    def __init__(self,num_points):

        #how many points do you want the user to input?
        self.num_points = num_points

        #create lists for x and y
        self.x1_list = []
        self.y1_list = []
        self.x2_list = []
        self.y2_list = []

    def function_1(self):
        #ask users to input data points
        print("Enter data points for Quentin.")
        try:
            #get x values
            x1_list = input("Enter the x-values as a list of the form [a,b,c,d,...]: " )
            x1_list = x1_list.strip(']')
            x1_list = x1_list.strip('[')
            x1_list = x1_list.split(',')
            for i in x1_list:
                x1_val = float(i)
                self.x1_list.append(x1_val)

            #get y values
            y1_list = input("Enter the y-values as a list of the form [a,b,c,d,...]: " )
            y1_list = y1_list.strip(']')
            y1_list = y1_list.strip('[')
            y1_list = y1_list.split(',')
            for i in y1_list:
                y1_val = float(i)
                self.y1_list.append(y1_val)

        except:
            print("Entries must be real numbers!")
            self.function_1(data_num)

    def function_2(self):
        #ask users to input data points
        print("Now, enter data points for Isabelle")
        try:
            x2_list = input("Enter the x-values as a list of the form [a,b,c,d,...]: " )
            x2_list = x2_list.strip(']')
            x2_list = x2_list.strip('[')
            x2_list = x2_list.split(',')
            for i in x2_list:
                x2_val = float(i)
                self.x2_list.append(x2_val)

            #get y values
            y2_list = input("Enter the y-values as a list of the form [a,b,c,d,...]: " )
            y2_list = y2_list.strip(']')
            y2_list = y2_list.strip('[')
            y2_list = y2_list.split(',')
            for i in y2_list:
                y2_val = float(i)
                self.y2_list.append(y2_val)

        except:
            print("Entries must be real numbers!")
            self.function_2(data_num)

    def make_graph(self, title):
        #make a graph out of the available data points

        layout = go.Layout(
            title = title,
            yaxis = dict(
                title="<i>Y</i> Values",
                range = [0,15]
            ),
            xaxis = dict(
                title="<i>X</i> Values",
                range = [0,8]
            ),
        )

        trace1 = go.Scatter(
            x = self.x1_list,
            y = self.y1_list,
            mode = "lines+markers",
            name = "Quentin",
            line=dict(
                color = ('rgb(22, 96, 167)'),
                dash = "dot"
            )

        )

        trace2 = go.Scatter(
            x = self.x2_list,
            y = self.y2_list,
            mode = "lines+markers",
            name = "Isabelle",
            line=dict(
                color = ('rgb(205, 12, 24)'),
                dash = "dot"
            )

        )

        fig = go.Figure(data = [trace1,trace2], layout = layout)
        py.offline.iplot(fig)

    def run_it(self):
        title = input("Give the graph a title: ")
        self.function_1()
        self.function_2()
        self.make_graph(title)

'''Above, we are importing all the necessary modules in order to run the notebook.
Numpy allows us to define arrays of values for our variables to plot them
matplotlib is what we use to create the figures
the display and widgets are to make the notebook look neat
'''

HTML('''
<form action="javascript:code_toggle()"><input type="submit" id="toggleButton" value="Show Code"></form>''')

```

<form action="javascript:code_toggle()"><input type="submit" id="toggleButton" value="Show Code"></form>

  Understanding the Relationship Between Two Variables Using Graphs

***

*GIF taken from https://giphy.com/gifs/reaction-BmmfETghGOPrW, June 11th, 2018.*

***

> ## **Introduction**

Humans are constantly interacting with other people, objects, and things. The outcomes of these interactions depend on the many **variables** that may exist in the given situation. A variable can be described as something that is free to change, and the way in which it changes can influence the outcome of a certain event. Consider the following example:

>### **Example**
Say you have a job that pays you an hourly wage of \$10 per hour. Your wage then determines how much money you will make if you were to work for 5 hours ($5$ hours $\times$ $\$10$ per hour = $\$50$). If your wage were to change to \$12 per hour, the amount of money you would make in 5 hours would change as well ($5$ hours $\times$ $\$12$ per hour = $\$60$). Your wage is therefore a *variable* that determines how much money you are going to make.

Many of the choices we make every day require us to understand how certain variables will affect the results of our actions. The better our understanding, the better we are at reaching our desired outcome! A useful tool to help increase our understanding of how variables affect results is called a **graph**, which is simply a visual representation of the relationship between variables.

This lesson will focus on how we can use graphs to improve our understanding of how two variables interact with each other. While there are many different types of graphs, we will focus on line graphs in this lesson to demonstrate the relationship between variables within **linear functions**. We will go through:
- how to create a graph of a linear function
- how to analyze the important characteristics of a graph, including slope, $x$ and $y$ intercepts

Take a look at the graph shown below. This is a line graph that shows the relationship between a persons wage and how much money they will earn if they were to work for 5 hours. You can use the toolbar at the top of the graph to manipulate it.

```python
wages = np.linspace(0,50,11)
money = []
for item in wages:
    y = item*5
    money.append(y)

layout = go.Layout(
    title = "Money Earned After Working 5 Hours",
    yaxis = dict(
        title="Money Earned (Dollars)"
    ),
    xaxis = dict(
        title="Wage (Dollars per hour)",
    ),
)

trace1 = go.Scatter(
    x = wages,
    y = money,
    mode = "lines+markers",
    name = "Plot 1",
    line=dict(
        color = ('rgb(205, 12, 24)'),
        shape = "spline",
        dash = "dot"
    )

)

fig = go.Figure(data = [trace1], layout = layout)
py.offline.iplot(fig)
```

 Figure 1: This graph shows the relationship between a persons wage, and the money they will make after working for 5 hours.

***

> ## **Background**

### **Algebraic Equations**

When dealing with mathematics, the relationship between certain variables can be written as an **algebraic equation**. In an algebraic equation, letters are used to represent variables that may have any value. For example, the relationship between wage and the amount of money made after working for 5 hours, as described above, can be shown as follows:

\begin{equation}
\label{1}
\rm MONEY\: MADE = 5\: HOURS \times WAGE
\end{equation}

here is the same equation, only now it is expressed algebraicly:

\begin{equation}
\label{2}
M = 5W
\end{equation}

In the algebraic equation, each letter represents a variable. The $W$ corresponds to wage, and the $M$ corresponds to the resulting amount of money made.

When creating an algebraic expression, the letter that you assign to a variable really doesn't matter. However, in mathematics it is most common to select the letters $x$ and $y$ to represent equations with two different variables.
In most cases that you will see in class, an equation will be given to you, and there may not be any meaning behind what each variable represents. Take a look at some of the examples below to get a better grasp on how an algebraic equation may look.
***
>#### **Examples**

\begin{equation}
y = 3x + 9
\end{equation}

\begin{equation}
y = \frac{x}{4} + 3
\end{equation}

\begin{equation}
y = \frac{x + 7}{2}
\end{equation}
***

### **Linear Functions**

Algebraic equations are sometimes called **functions**, particularly when they are used to create a graph. A **linear function** has the form:

\begin{equation}
y = mx + b
\end{equation}

where both the $x$ and $y$ variables in the equation are of degree 1 or 0, and $m$ and $b$ are *constants*. A *constant* is a number in an equation that does not change, or in other words, is not a variable. A constant can be positive, negative, a fraction or a whole number.

Take another look at the equations shown in the section above, these are all linear functions! When a linear function is used to create a graph, the graph will look like a straight line (hence the name *linear* function).

In this lesson we will only be looking at linear functions and their corresponding graphs.

Use the section below to better your understanding of the difference between a *linear* function, and a *non-linear* function.

```python
def lin_or_non(val):
    if val == "Linear":
        display(Latex("Correct!"))
        display(Latex("This equation has no exponent on either variable, and is therefore linear."))
    elif val == "Non-Linear":
        display(Latex("Try Again!"))

display(Latex("Is this function linear or non-linear?"))
display(Math('y = 12x -3'))

interact(lin_or_non, val = widgets.Dropdown(options=[' ', 'Linear', 'Non-Linear'],value = ' ',description = 'Choose One:',disabled = False));

```

```python
def lin_or_non(val):
    if val == "Linear":
        display(Latex("Try Again!"))
    elif val == "Non-Linear":
        display(Latex("Right on!"))
        display(Latex("Because $x$ is raised to the power of 2, this function is not linear."))

display(Latex("Is this function linear or non-linear?"))
display(Math('y = 2x^2 + 17'))

interact(lin_or_non, val = widgets.Dropdown(options=[' ', 'Linear', 'Non-Linear'],value = ' ',description = 'Choose One:',disabled = False));

```

```python
def lin_or_non(val):
    if val == "Linear":
        display(Latex("Try Again!"))
    elif val == "Non-Linear":
        display(Latex("Way to go!"))
        display(Latex("This graph does not resemble a straight line, therefore, it is not linear."))
        display(Latex("The equation for this function is:"))
        display(Math("y = x^2"))

display(Latex("Is this function linear or non-linear?"))

x = np.linspace(-10,10,21)
y = []
for num in x:
    y_val = num**2
    y.append(y_val)

layout = go.Layout(
    title = "<i>Y</i> as a Function of <i>X</i>",
    yaxis = dict(
        title="<i>Y</i> Values"
    ),
    xaxis = dict(
        title="<i>X</i> Values"
    ),
)

trace1 = go.Scatter(
    x = x,
    y = y,
    mode = "lines+markers",
    name = "Plot 1",
    line=dict(
        color = ('rgb(22, 96, 167)'),
        shape = "spline",
        dash = "dot"
    )

)

fig = go.Figure(data = [trace1], layout = layout)
py.offline.iplot(fig)

interact(lin_or_non, val = widgets.Dropdown(options=[' ', 'Linear', 'Non-Linear'],value = ' ',description = 'Choose One:',disabled = False));

```

```python
def lin_or_non(val):
    if val == "Linear":
        display(Latex("Good job!"))
        display(Latex("This graph is a straight line, and is therefore linear."))
        display(Latex("The equation for this function is:"))
        display(Math("y = -x - 6"))
    elif val == "Non-Linear":
        display(Latex("Try Again!"))

display(Latex("Is this function linear or non-linear?"))

x = np.linspace(-10,4,10)
y = []
for num in x:
    y_val = -num - 6
    y.append(y_val)

layout = go.Layout(
    title = "<i>Y</i> as a Function of <i>X</i>",
    yaxis = dict(
        title="<i>Y</i> Values"
    ),
    xaxis = dict(
        title="<i>X</i> Values"
    ),
)

trace1 = go.Scatter(
    x = x,
    y = y,
    mode = "lines+markers",
    name = "Plot 1",
    line=dict(
        color = ('rgb(100, 100, 100)'),
        shape = "spline",
        dash = "dot"
    )

)

fig = go.Figure(data = [trace1], layout = layout)
py.offline.iplot(fig)

interact(lin_or_non, val = widgets.Dropdown(options=[' ', 'Linear', 'Non-Linear'],value = ' ',description = 'Choose One:',disabled = False));

```

Now that we know how linear functions can look, lets talk about how we can represent them on a graph!

## **Creating a Graph**

### **Independent and Dependent Variables**

When you want to express a linear function with two variables on a graph, it is important to first establish the **independent variable** and the **dependent variable**. The *independent* variable can be thought of as the variable that is subject to change in order to produce different results, whereas the *dependent* variable can be thought of as the variable whose value *depends* on the independent variable. The difference between the two types of variables may be difficult to understand at first, so here is an example that may help.
***
>#### **Example**
Let's say you are conducting an experiment to measure how many push-ups you can do in a certain amount of time. There are two variables at play here: the amount of time you are given to do push-ups, and the amount of push-ups you actually do in that time. In this scenario, you are able to freely change how much time you have to do push-ups. You could give yourself 30 seconds, an hour, or a day! Therefore, *time is the independent variable*.
On the other hand, the amount of push-ups you are actually able to do *depends* on the amount of time you have to do them. Therefore, the *dependent variable is the number of push-ups you do*.
***
When you are given an equation with two variables ($x$ and $y$), it is common to establish $x$ as the independent variable, and $y$ as the dependent variable.
***

```python
def wage(val):
    if val == "W (Wage)":
        display(Latex("Correct!"))
        display(Latex("The amount of money you make depends on your wage, and your wage is free to change. It is therefore the independent variable."))
    elif val == "M (Money Made)":
        display(Latex("Try Again!"))

display(Latex("From the wage example shown in the section above, which variable do you think is the independent variable?"))
display(Math('M = 5W'))

interact(wage, val = widgets.Dropdown(options=[' ', 'W (Wage)', 'M (Money Made)'],value = ' ',description = 'Choose One:',disabled = False));

```

***
### **The Cartesian Plane**

Once we have established the independent and dependent variables in our linear function, we are ready to set up a graph of their relationship. When graphing two variables, we use what is called the **Cartesian Plane**. You have probably seen a graph on the Cartesian Plane before, but here is what it looks like:

```python
layout = go.Layout(
    title = "The Cartesian Plane",
    yaxis = dict(
        title="<i>Y</i> Axis",
        range = [-10,10],
        dtick = 1.00
    ),
    xaxis = dict(
        title="<i>X</i> Axis",
        range = [-10,10],
        dtick = 1.00
    ),
)

trace1 = go.Scatter(
    x = [5,-5,-5,5],
    y = [5,5,-5,-5],
    mode = "marker+text",
    name = "Plot 1",
    text=['Quadrant 1', 'Quadrant 2', 'Quadrant 3', 'Quadrant 4'],
    textposition='bottom'
)

fig = go.Figure(data = [trace1], layout = layout)
py.offline.iplot(fig)
```

 Figure 2: This is the standard layout for the Cartesian Plane, where you will plot all linear functions. The point where $x = 0$ and $y = 0$ is called the origin.

On the Cartesian Plane, there are two **axes**: the $x$ axis (horizontal), and the $y$ axis (vertical). When we want to graph a certain relationship, we label the $y$ axis with the dependent variable, and the $x$ axis with the independent variable. When you aren't given a specific name for what the independent and dependent variable represent, it is okay to just label the $x$ axis "$X$" and the $y$ axis "$Y$".

When giving the graph a title, it is customary to describe what is being shown on the graph. Your title should provide the observer with a concise understanding of what is being shown. In most cases, you will describe the dependent variable as a function of the independent variable, or "$Y$ as a Function of $X$".

The Cartesian Plane consists of four **quadrants**, or sections. The top right section, where both $x$ and $y$ are positive (+,+), is quadrant 1. Then going counter-clockwise, quadrant 2 is the section where $x$ is negative and $y$ is positive (-,+), in quadrant 3 both $x$ and $y$ are negative (-,-), and in quadrant 4, $x$ is positive and $y$ is negative (+,-).

Now that we have established how a graph should look, lets talk about how we generate the values that we want to plot on the graph!

### **Finding the Data Points**

The data points that are shown on a graph don't just appear out of nowhere; they can come from scientific observations, measurements, or mathematical relationships. When graphing on the Cartesian Plane, each data point has an $x$ value and a $y$ value (sometimes called an $x$ coordinate and a $y$ coordinate), and these values are what determine where the point will appear on the graph.

The best way to describe how we find the $x$ value and $y$ value for each data point is through an example.

***
> #### **Example**

Suppose you are given a linear equation such as $y = 3x + 2$, and you are asked to plot a graph with ten data points, starting from $x = 1$ up to $x = 5$.

To begin generating data points, the first thing you need to do is pick ten unique $x$ values between $1$ and $5$.

For simplicity, let's pick 1, 2, 3, 4, and 5.

\begin{array}{| c | c |}
\hline
  X\: Value & Y\: Value  \\
  \hline
  1 & ? \\\hline
  2 & ? \\\hline
  3 & ? \\\hline
  4 & ? \\\hline
  5 & ? \\\hline
\end{array}

Now we need to find the value of $y$ at each one of the chosen values for $x$. We can do this by plugging our chosen $x$ values into the equation that we have been given: $y = 3x + 2$.

Lets take our first $x$ value, 1, and plug it into the equation

\begin{equation}
y = 3x + 2
\end{equation}
\begin{equation}
x = 1
\end{equation}

So, plugging in this value:

\begin{equation}
y = 3(1) + 2
\end{equation}
\begin{equation}
y = 3 + 2
\end{equation}
\begin{equation}
y = 5
\end{equation}

We now have the value of our first data point! When $x = 1$, $y = 5$. We would write this data point down at (1,5). Lets fill in the table so we can keep track of the data points we find:

\begin{array}{| c | c |}
\hline
  X\: Value  & Y\: Value  \\
  \hline
  1 & 5 \\\hline
  2 & ? \\\hline
  3 & ? \\\hline
  4 & ? \\\hline
  5 & ? \\\hline
\end{array}

In the section below, use the above method to find the remaining $y$ values.

```python

###### Create input boxes to find remaining values

def find_y(xval):
    try:

        y = 3*(xval) + 2

        ans = input("When x = " + str(xval) + ", y = ")
        answer = int(ans)

        if answer == y:
            print("Correct!")

            #show updated table
            x_col = [1,2,3,4,5]
            y_col = []
            for i in range(1,xval + 1):
                yval = 3*(i) + 2
                y_col.append(yval)

            #fill in empty spots with question marks
            if len(y_col) < 5:
                extra = 5 - len(y_col)
                for j in range(1, extra + 1):
                    y_col.append("?")

            #show the table
            t = Table([x_col,y_col], names = ("X Value","Y Value"))
            print(t)

        else:
            print("Incorrect, try again!")
            print("Remember, the equation is: y = 3x + 2")
            find_y(xval)

    except:
        print("Answer must be an integer! Try again.")
        find_y(xval)

for i in range(2,6):
    find_y(i)

```

Good job! Now that we have all of the data points, we are ready to plot them on the graph.

### **Plotting the Data Points on the Graph**

To plot our data points on a graph, we will need our Cartesian Plane, with the $x$ axis and $y$ axis labelled as follows:

```python
layout = go.Layout(
    title = "<i>Y</i> as a Function of <i>X</i>",
    yaxis = dict(
        title="<i>Y</i> Values",
        range = [-1,20],
        dtick = 1.00
    ),
    xaxis = dict(
        title="<i>X</i> Values",
        range = [-1,10],
        dtick = 1.00
    )
)

trace1 = go.Scatter(
    x = [],
    y = [],
    mode = "lines+markers",
    name = "Plot 1",
    line=dict(
        color = ('rgb(205, 12, 24)'),
        shape = "spline",
        dash = "dot"
    )

)

fig = go.Figure(data = [trace1], layout = layout)
py.offline.iplot(fig)

```

 Figure 3: The layout of our graph. As all of our data points have positive $x$ and $y$ values, we only need to focus on the first quadrant (top right corner).

Now, using our chart of data points, lets plot our first point. The $x$ value of our first point is 1, and the $y$ value is 5. So lets find the point on the graph where $x=1$ and $y=5$ and mark this point

```python
layout = go.Layout(
    title = "<i>Y</i> as a Function of <i>X</i>",
    yaxis = dict(
        title="<i>Y</i> Values",
        range = [-1,20],
        dtick = 1.00
    ),
    xaxis = dict(
        title="<i>X</i> Values",
        range = [-1,10],
        dtick = 1.00
    )
)

trace1 = go.Scatter(
    x = [1],
    y = [5],
    mode = "lines+markers",
    name = "Plot 1",
    line=dict(
        color = ('rgb(205, 12, 24)'),
        shape = "spline",
        dash = "dot"
    )

)

fig = go.Figure(data = [trace1], layout = layout)
py.offline.iplot(fig)
```

 Figure 4: Here is the location of our first data point.

That is your first data point!

In the section below, use the above methods to create a graph and plot all of the data points.

\begin{array}{| c | c |}
\hline
  X\: Value & Y\: Value \\
  \hline
  1 & 5 \\\hline
  2 & 8 \\\hline
  3 & 11 \\\hline
  4 & 14 \\\hline
  5 & 17 \\\hline
\end{array}

```python
g = User_Graph(5,[-1,10],[-1,20])
g.run_it()
```

    Give the graph a title: Y as a Function of X
    Enter the x-values as a list of the form [a,b,c,d,...]: [1,2,3,4,5]
    Enter the y-values as a list of the form [a,b,c,d,...]: [5,8,11,14,17]

If done correctly, your graph should look something like this:

```python
layout = go.Layout(
    title = "<i>Y</i> as a Function of <i>X</i>",
    yaxis = dict(
        title="<i>Y</i> Values",
        range = [-1,20]
    ),
    xaxis = dict(
        title="<i>X</i> Values",
        range = [-1,10]
    )
)

trace1 = go.Scatter(
    x = [1,2,3,4,5],
    y = [5,8,11,14,17],
    mode = "lines+markers",
    name = "Plot 1",
    line=dict(
        color = ('rgb(205, 12, 24)'),
        shape = "spline",
        dash = "dot"
    )

)

fig = go.Figure(data = [trace1], layout = layout)
py.offline.iplot(fig)
```

If the graph you made looks significantly different, try running the code segment again to re-try!

Now that you know how to create a graph, let's talk about some of the important things that a graph can tell us.

***

**Features of a Linear Graph: Intercepts**

A graph can tell us some interesting details about how two variables relate to one another in a given scenario. Two such details are called the $x$ intercept and the $y$ intercept. The $x$ intercept is the point on the graph where the function crosses the $x$ axis, and similarily, the $y$ intercept is the point where the function crosses the $y$ axis.
- At the $x$ intercept, $y = 0$
- At the $y$ intercept, $x = 0$.

This may seem like trivial information, but depending on what the graph is representing, the position of the $x$ and $y$ intercepts can prove to be very informative.

***
> ### **Example**

Let's say you have a job as a taxi driver and your wage is approximately \$15 per hour. However, before you start your shift you need to fill your car with \$30 worth of gas. Knowing this, how many hours do you have to work in order to start making money?

The equation for this scenario can be represented as:

\begin{equation}
y = 15x - 30
\end{equation}

where $x$ represents how many hours you have worked, and $y$ represents the amount of money you have made.

Take a look at the graph below that shows the equation.

```python
hours = np.linspace(0,20,11)
money = []

for item in hours:
    y = item*15 - 30
    money.append(y)

layout = go.Layout(
    title = "Amount of Money Earned as a Function of Hours Worked",
    yaxis = dict(
        title="Money Earned (Dollars)"
    ),
    xaxis = dict(
        title="Hours Worked"
    ),
)

trace1 = go.Scatter(
    x = hours,
    y = money,
    mode = "lines+markers",
    name = "Plot 1",
    line=dict(
        color = ('rgb(22, 96, 167)'),
        shape = "spline",
        dash = "dot"
    )

)

fig = go.Figure(data = [trace1], layout = layout)
py.offline.iplot(fig)

```

 Figure 5: The amount of money a taxi driver will earn after having to pay for $30 worth of gas.

To make money, your value for *Money Earned* needs to be greater than zero, and the point where this begins is the $x$ intercept! By identifying the $x$ intercept (the data point on the graph at (2,0)), you now know the minimum amount of time you need to work (2 Hours) in order for you to start making money after spending \$30 to fill up with gas.

While this particular example shows the significance of the $x$ intercept, there are many other scenarios where it may be useful to identify the $y$ intercept on a graph as well.

***

To find the value of the $x$ intercept without using a graph, we can use the linear equation:

\begin{equation}
y = 15x - 30
\end{equation}

and set the value of $y$ to zero, then solve for $x$. It would look something like this:

\begin{equation}
(0) = 15x - 30
\end{equation}

\begin{equation}
30 = 15x
\end{equation}

\begin{equation}
\frac{30}{15} = x
\end{equation}

\begin{equation}
2 = x
\end{equation}

As before, the x$$ intercept is found to be at (2,0). The same process can be used to find the value of the $y$ intercept, only instead of setting $y$ to zero, we set $x$ so zero, as follows:

\begin{equation}
y = 15(0) - 30
\end{equation}

\begin{equation}
y = -30
\end{equation}

So now we see that the $y$ intecept for this particular equation is (0,-30).

You may have noticed that the $y$ coordinate of the $y$ intercept is actually just the $b$ value in an equation of the form $y = mx + b$!

> ## **Note**
If a graph is a perfectly straight horizontal line (slope = 0), the function may not have an $x$ intercept. If the function is a perfectly vertical line (slope is undefined), the graph may not have a $y$ intercept. In all other cases, a graph will have both an $x$ and $y$ intercept. However, the range in which you have plotted may not show them.

***

**Features of a Linear Graph: Slope**

Another important characteristic of a linear function is **slope**. The *slope* of a function can be thought of as the steepness or the angle of the function as it appears on a graph. What it really represents is the rate at which the dependent variable changes with respect to the independent variable.

That may sound complicated at first, but here is an example that might make it easier to understand what slope is.

***

> ### **Example**

Say you have been given a set of data points describing how far a car has travelled in a given amount of time:

\begin{array}{| c | c |}
\hline
  Time(seconds) & Distance Travelled(meters) \\
  \hline
  0 & 0 \\\hline
  20 & 400 \\\hline
  40 & 800 \\\hline
  60 & 1200 \\\hline
  80 & 1600 \\\hline
  100 & 2000 \\\hline
  120 & 2400 \\\hline
  140 & 2800 \\\hline
  160 & 3200 \\\hline
  180 & 3600 \\\hline
  200 & 4000 \\\hline
\end{array}

You are then asked to find out how fast the car was travelling. Let's set up a graph of this data.

```python
time = np.linspace(0,200,11)
distance = np.linspace(0,4000,11)

layout = go.Layout(
    title = "Distance Travelled as a Function of Time",
    yaxis = dict(
        title="Distance Travelled (Meters)"
    ),
    xaxis = dict(
        title="Time (Seconds)"
    ),
)

trace1 = go.Scatter(
    x = time,
    y = distance,
    mode = "lines+markers",
    name = "Plot 1",
    line=dict(
        color = ('rgb(22, 96, 167)'),
        shape = "spline",
        dash = "dot"
    )

)

fig = go.Figure(data = [trace1], layout = layout)
py.offline.iplot(fig)
```

 Figure 6: This graph shows the distance a car travels after a given amount of time.

We want to find how fast the car is travelling. Knowing that speed is a measurement of distance over time, let's use the total distance travelled and the total amount of time to find the average speed of the car.
From the graph, we can see that the car initially started at position 0 meters, and travelled a total of 4000 meters. We also know that the car started moving at time 0 seconds, and stopped moving after 200 seconds. Using this information, we can set up the following equations to find the speed of the car:

\begin{equation}
Speed = \frac{Total\: Distance}{Total\: Time}
\end{equation}

and we know that:

\begin{equation}
Total\: Distance = Final\: Distance - Initial\: Distance
\end{equation}
\begin{equation}
Total\: Time = Final\: Time - Initial\: Time
\end{equation}

so:

\begin{equation}
Speed = \frac{Final\: Distance - Initial\: Distance}{Final\: Time - Initial\: Time}
\end{equation}

\begin{equation}
Speed = \frac{4000m - 0m}{200s - 0s}
\end{equation}

\begin{equation}
Speed = \frac{4000m}{200s}
\end{equation}

\begin{equation}
 Speed = 20 m/s
\end{equation}

From the equations above, we can see that the car travels 20 meters every second. While it may not be obvious, we just found the slope of the graph! By finding the average speed of the car, we identified the rate at which the dependent variable (Distance Travelled) changes with respect to the independent variable (Time).

***

When looking at a graph, the general equation used to find the slope of a linear function from point ($x_i$,$y_i$) to ($x_f$,$y_f$) is given as follows:

\begin{equation}
Slope = \frac{y_f - y_i}{x_f - x_i}
\end{equation}

where the subscript $f$ stands for *final*, and the $i$ stands for *initial*.

When looking at a linear function that has the form:
\begin{equation}
y = mx + b
\end{equation}
the value of the slope is the same as the value of the coefficient $m$.

Use the section below to see the effect that slope can have on a function.

```python
def slider(slope):

    x_list = [0,1,2,3,4,5,6,7,8,9,10]
    y_list = []
    for i in x_list:
        y_val = slope*(i)
        y_list.append(y_val)

    layout = go.Layout(
        showlegend = True,
        title ='y = ' + str(slope) + 'x',
        yaxis = dict(
            title="<i>X</i> Values",
            range = [-1,20]
        ),
        xaxis = dict(
            title="<i>Y</i> Values",
            range = [-1,20]
        ),
    )

    trace1 = go.Scatter(
        x = x_list,
        y = y_list,
        mode = "lines+markers",
        name = str('$y = ' + str(slope) + 'x$'),
        line=dict(
            color = ('rgb(22, 96, 167)'),
            shape = "spline",
            dash = "dot"
        )

    )

    fig = go.Figure(data = [trace1], layout = layout)
    py.offline.iplot(fig, filename = 'show-legend')

interactive(slider, slope=(0,5,0.5))

```

Failed to display Jupyter Widget of type interactive.

  If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean
  that the widgets JavaScript is still loading. If this message persists, it
  likely means that the widgets JavaScript library is either not installed or
  not enabled. See the Jupyter
  Widgets Documentation for setup instructions.

  If you're reading this message in another frontend (for example, a static
  rendering on GitHub or NBViewer),
  it may mean that your frontend doesn't currently support widgets.

 Figure 7: This graph shows the way in which slope can affect the shape of a function. The larger the slope, the "steeper" the graph is.

When dealing with a single linear function, the slope will be constant across the entire graph. However, if a graph consists of more than one linear function, the slope may change at some point.
Take a look at the following graph to see what a graph with more than one linear function may look like:

```python
x1 = [0,1,2,3,4,5]
y1 = []

for num in x1:
    y1_val = num + 1
    y1.append(y1_val)

x2 = [6,7,8,9,10]
y2 = []

for num in x2:
    y2_val = 3*(num) - 9
    y2.append(y2_val)

x_list = x1 + x2
y_list = y1 + y2

layout = go.Layout(
    title = "<i>Y</i> as a Function of <i>X</i>",
    yaxis = dict(
        title="<i>Y</i> Values"
    ),
    xaxis = dict(
        title="<i>X</i> Values"
    ),
)

trace1 = go.Scatter(
    x = x_list,
    y = y_list,
    mode = "lines+markers",
    name = "Plot 1",
    line=dict(
        color = ('rgb(22, 96, 167)'),
        dash = "dot"
    )

)

fig = go.Figure(data = [trace1], layout = layout)
py.offline.iplot(fig)

```

 Figure 8: On this plot, notice the distinct change in steepness at $x = 5$. The slope increases at this point.

As you can see above, the graph has two distinct sections: one from $x = 0$ to $x = 5$, and the other from $x = 5$ to $x = 10$. As we have seen from the equations of linear functions, we know that the slope of one function has a constant value. Because these sections each have a different slope, they must represent two distinct linear functions.

You may be wondering how one graph can show two distinct functions at the same time, but consider our example of the car shown above. What if halfway through the trip, the car began to move at a faster speed? If this were the case, the slope of the graph would increase at the halfway point, much like the graph shown above.

## **Questions**

The following section is meant to test your understanding and improve your familiarity with the concepts we have gone over in this lesson.

### **Question 1.**

Use the following table of data points describing the motion of a car driving to a destination and complete the following tasks:
- Plot a graph (be sure to label the axes, and to give the graph a title!)
- Find the slope of the graph
- Find the $x$ and $y$ intercept
- Derive the equation for the linear function

\begin{array}{| c | c |}
\hline
  Time\: (seconds) & Distance\: Away\: from\: Destination\: (meters) \\
  \hline
  0 & 10 \\\hline
  1 & 8 \\\hline
  2 & 6 \\\hline
  3 & 4 \\\hline
  4 & 2 \\\hline
  5 & 0 \\\hline
  6 & -2 \\\hline
  7 & -4 \\\hline
  8 & -6 \\\hline
  9 & -8 \\\hline
\end{array}

Use the section below to create the graph:

```python
q1 = User_Graph(10, [-1,10], [-10,12])
q1.run_it()

```

    Label the X-Axis: Time (seconds)
    Label the Y-Axis: Distance From Destination (meters)
    Give the graph a title: Driving Somewhere
    Enter the x-values as a list of the form [a,b,c,d,...]: [0,1,2,3,4,5,6,7,8,9]
    Enter the y-values as a list of the form [a,b,c,d,...]: [10,8,6,4,2,0,-2,-4,-6,-8]

Use the following sections to answer some questions about the graph you just made:

```python
def ask_slope():
    slope = input("What is the value of the slope of the above graph? (Enter your answer as an integer) ")

    if slope in ["-2"]:
        display(Latex("Good stuff!"))
    else:
        display(Latex("Try Again!"))
        display(Latex("Remember, the equation for the slope of a line is:"))
        display(Math("Slope = (y_f - y_i)/(x_f - x_i)"))
        ask_slope()

ask_slope()
```

```python
def ask_y_int():
    y_int = input("What are the coordinates for the y intercept in the above graph? (Enter your answer as (x,y)) ")

    if y_int in ["(0,10)"]:
        display(Latex("Correct!"))
    else:
        display(Latex("Try Again!"))
        display(Latex("Hint: Look at where the graph crosses the $y$ axis."))
        ask_y_int()

ask_y_int()
```

    What are the coordinates for the y intercept in the above graph? (Enter your answer as (x,y)) (0,10)

Good Job!

```python
def ask_x_int():
    x_int = input("What are the coordinates for the x intercept in the above graph? (Enter your answer as (x,y)) ")

    if x_int in ["(5,0)"]:
        display(Latex("Well done!"))
    else:
        display(Latex("Try Again!"))
        display(Latex("Hint: Look at where the graph crosses the $x$ axis."))
        ask_x_int()

ask_x_int()
```

    What are the coordinates for the x intercept in the above graph? (Enter your answer as (x,y)) (5,0)

Good Job!

```python
def ask_equation():
    eq = input("What is the equation of the linear function shown on the graph? (Enter your answer as: y = mx + b)")

    if eq in ["y = -2x + 10", "y = -(2)x + 10", "y = (-2)x + 10", "y=-2x+10"]:
        display(Latex("Good Job!"))
    else:
        display(Latex("Try Again!"))
        display(Latex("Hint: Remeber that slope = $m$, and the $y$ intercept = $b$"))
        ask_equation()

ask_equation()
```

    What is the equation of the linear function shown on the graph? (Enter your answer as: y = mx + b)y=-2x+10

Good Job!

```python
def ask_q():
    q = input("At what point did the person walk past their destination? (Enter your answer as (x,y)) ")

    if q in ["(5,0)"]:
        display(Latex("Way to go!"))
    else:
        display(Latex("Try Again!"))
        display(Latex("Hint: At what point is the person 0 meters away from their destination, but then keeps going?"))
        ask_q()

ask_q()
```

    At what point did the person walk past their destination? (Enter your answer as (x,y)) (5,0)

Good Job!

###### Question 2.

Quentin and Isabelle both earn their allowance by doing exercise. Quentins parents pay him \$8.00 each time he goes for a run,  plus an additional \$0.50 for every kilometer he runs.

Isabelle gets paid \$5.00 every time she goes for a run, and an additional \$1.00 for every kilometer she runs.

At what distance do Quentin and Isabelle make the same amount of money?
Who makes the most money after running for 10 kilometers?

To help answer these questions, here are the linear equations that describe Quentin and Isabelle's allowance:

Quentin:
\begin{equation}
y = \frac{1}{2}x + 8
\end{equation}

Isabelle:
\begin{equation}
y = x + 5
\end{equation}

Create a graph that shows both of these functions at the points:

\begin{array}{| c | c |}
\hline
  Distance\: (km) & Money\: Made\: (Dollars)\\
  \hline
  0 & ? \\\hline
  1 & ? \\\hline
  2 & ? \\\hline
  3 & ? \\\hline
  4 & ? \\\hline
  5 & ? \\\hline
  6 & ? \\\hline
  7 & ? \\\hline
\end{array}

*Hint*: Create one table for each of the equations and find the corresponding y values, then use the section below to enter the points.

```python
D = Double_Graph(5)
D.run_it()
```

    Label the X-Axis: Distance (km)
    Label the Y-Axis: Money Made (dollars)
    Give the graph a title: Quentin and Isabelle
    Enter data points for Quentin.
    Enter the x-values as a list of the form [a,b,c,d,...]: [0,1,2,3,4,5,6,7]
    Enter the y-values as a list of the form [a,b,c,d,...]: [8,8.5,9,9.5,10,10.5,11,11.5]
    Now, enter data points for Isabelle
    Enter the x-values as a list of the form [a,b,c,d,...]: [0,1,2,3,4,5,6,7]
    Enter the y-values as a list of the form [a,b,c,d,...]: [5,6,7,8,9,10,11,12]

```python
def ask_q2():
    q = input("At what distance do Quentin and Isabelle make the same amount of money? (Enter your answer as a whole number) ")

    if q in ["6", "6km", "6 km", "6 kilometers"]:
        display(Latex("You got it!"))
    else:
        display(Latex("Try Again!"))
        display(Latex("Hint: What is the $x$ coordinate where the two functions cross?"))
        ask_q2()

ask_q2()
```

    At what distance do Quentin and Isabelle make the same amount of money? (Enter your answer as a whole number) 6

Good Job!

```python
def ask_q3():
    q = input("Who makes the most money after running 10 kilometers? (Enter: Quentin, or Isabelle) ")

    if q in ["Isabelle"]:
        display(Latex("Nice work!"))
    else:
        display(Latex("Try Again!"))
        display(Latex("Hint: Who has the highest $y$ value when $x = 10$?"))
        ask_q3()

ask_q3()
```

    Who makes the most money after running 10 kilometers? (Enter: Quentin, or Isabelle) Q

Try Again!

Hint: Who has the highest $y$ value when $x = 10$?

    Who makes the most money after running 10 kilometers? (Enter: Quentin, or Isabelle) I

Try Again!

Hint: Who has the highest $y$ value when $x = 10$?

    Who makes the most money after running 10 kilometers? (Enter: Quentin, or Isabelle) Isabelle

Good Job!

## **Conclusion**

In this lesson, we have highlighted some of the many ways that a graph can help us understand the relationship between two variables in a linear function. Some of the important methods that you should remember include:

- Defining a Linear Function
- Identifying the Independent and Dependent Variables
- Setting up a Graph on the Cartesian Plane
- Creating and Plotting Data Points
- Features of a Graph ($x$ intercept, $y$ intercept, and slope)

Throughout your life, you will be presented with all sorts of statistics, data, charts and graphs. It is important that you have the skills required to analyze what a graph is trying to tell you, not just so you can pass your math class, but to enable you to make informed decisions when you need to consider how variables can interact and affect results.


# 微積分の計算について　　　N0.3　不定積分の内容－１

###  学籍番号[＿＿＿＿＿＿＿＿＿]クラス[＿＿＿＿＿]　クラス番号[＿＿＿＿＿] 名前[＿＿＿＿＿＿＿＿＿＿＿＿＿＿＿]

積分の式

    （１）変形、整理できるか
     (２）部分分数に変換できるか
    （３）三角関数などは公式を使って変形できるか
    （４）分数の分母の有理化できるか
    （５）分母を平方完成形にできるか

積分のルール

$$  \int cf(x) dx = c \int f(x) dx $$

$$  \int \{ f(x)\pm g(x)\} dx = \int f(x) dx \pm \int g(x) dx $$

積分の手法

    （１）置換積分法
    （２）部分積分法

積分の公式の基礎から

```python
from sympy import *
x, n , y, a = symbols('x n y a')
init_printing()
m ='3//1'
i =0
```

```python
expr = 6*x**2
itg = Integral(expr,x)
i=i+1
print( 'No.',m,'-',i)
itg
```

```python
itg.doit()
```

```python
expr = (x-1)*(x+1)
itg = Integral(expr,x)
i=i+1
print( 'No.',m,'-',i)
itg
```

```python
itg.doit()
```

```python
expr = 2/x
itg = Integral(expr,x)
i=i+1
print( 'No.',m,'-',i)
itg
```

```python
itg.doit()
```

```python
expr = cbrt(x**2)
itg = Integral(expr,x)
i=i+1
print( 'No.',m,'-',i)
itg
```

```python
itg.doit()
```

```python
expr = 2*x + sin(x)
itg = Integral(expr,x)
i=i+1
print( 'No.',m,'-',i)
itg
```

```python
itg.doit()
```

```python
expr = sec(x)**2 +1/x
itg = Integral(expr,x)
i=i+1
print( 'No.',m,'-',i)
itg
```

```python
simplify(itg.doit())
```

```python
expr = 1-exp(x)
itg = Integral(expr,x)
i=i+1
print( 'No.',m,'-',i)
itg
```

```python
simplify(itg.doit())
```

```python

```

```python

```


# Tutorial: optimal binning with continuous target

## Basic

To get us started, let's load a well-known dataset from the UCI repository and transform the data into a ``pandas.DataFrame``.

```python
import pandas as pd
from sklearn.datasets import load_boston
```

```python
data = load_boston()
df = pd.DataFrame(data.data, columns=data.feature_names)
```

We choose a variable to discretize and the continuous target.

```python
variable = "LSTAT"
x = df[variable].values
y = data.target
```

Import and instantiate an ``ContinuousOptimalBinning`` object class. We pass the variable name and its data type.

```python
from optbinning import ContinuousOptimalBinning
```

```python
optb = ContinuousOptimalBinning(name=variable, dtype="numerical")
```

We fit the optimal binning object with arrays ``x`` and ``y``.

```python
optb.fit(x, y)
```

    ContinuousOptimalBinning(cat_cutoff=None, dtype='numerical', max_bin_size=None,
                             max_n_bins=None, max_n_prebins=20, max_pvalue=None,
                             max_pvalue_policy='consecutive', min_bin_size=None,
                             min_mean_diff=0, min_n_bins=None, min_prebin_size=0.05,
                             monotonic_trend='auto', name='LSTAT',
                             outlier_detector=None, outlier_params=None,
                             prebinning_method='cart', special_codes=None,
                             split_digits=None, time_limit=100, user_splits=None,
                             user_splits_fixed=None, verbose=False)

You can check if an optimal solution has been found via the ``status`` attribute:

```python
optb.status
```

    'OPTIMAL'

You can also retrieve the optimal split points via the ``splits`` attribute:

```python
optb.splits
```

    array([ 4.6500001 ,  5.49499989,  6.86500001,  9.7249999 , 11.67499971,
           13.0999999 , 16.08500004, 19.89999962, 23.31500053])

#### The binning table

The optimal binning algorithms return a binning table; a binning table displays the binned data and several metrics for each bin. Class ``ContinuousOptimalBinning`` returns an object ``ContinuousBinningTable`` via the ``binning_table`` attribute.

```python
binning_table = optb.binning_table
```

```python
type(binning_table)
```

    optbinning.binning.binning_statistics.ContinuousBinningTable

The `binning_table` is instantiated, but not built. Therefore, the first step is to call the method `build`, which returns a ``pandas.DataFrame``.

```python
binning_table.build()
```

      Bin
      Count
      Count (%)
      Sum
      Std
      Mean
      Min
      Max
      Zeros count
      WoE
      IV

      0
      [-inf, 4.65)
      50
      0.098814
      1985.9
      8.19865
      39.718000
      22.8
      50.0
      0
      17.1852
      1.698142

      1
      [4.65, 5.49)
      28
      0.055336
      853.2
      6.12354
      30.471429
      21.9
      50.0
      0
      7.93862
      0.439291

      2
      [5.49, 6.87)
      45
      0.088933
      1188.6
      5.13626
      26.413333
      20.6
      48.8
      0
      3.88053
      0.345106

      3
      [6.87, 9.72)
      89
      0.175889
      2274.9
      6.84525
      25.560674
      11.9
      50.0
      0
      3.02787
      0.532570

      4
      [9.72, 11.67)
      49
      0.096838
      1057.9
      2.99484
      21.589796
      15.0
      31.0
      0
      -0.94301
      0.091319

      5
      [11.67, 13.10)
      35
      0.069170
      697.5
      2.59214
      19.928571
      14.5
      27.9
      0
      -2.60423
      0.180135

      6
      [13.10, 16.09)
      66
      0.130435
      1289.9
      3.54171
      19.543939
      10.2
      30.7
      0
      -2.98887
      0.389852

      7
      [16.09, 19.90)
      69
      0.136364
      1129.3
      3.60795
      16.366667
      8.3
      27.5
      0
      -6.16614
      0.840837

      8
      [19.90, 23.32)
      28
      0.055336
      368.4
      3.91284
      13.157143
      5.0
      21.7
      0
      -9.37566
      0.518811

      9
      [23.32, inf)
      47
      0.092885
      556.0
      4.00659
      11.829787
      5.0
      23.7
      0
      -10.703
      0.994154

      10
      Special
      0
      0.000000
      0.0
      NaN
      0.000000
      NaN
      NaN
      0
      -22.5328
      0.000000

      11
      Missing
      0
      0.000000
      0.0
      NaN
      0.000000
      NaN
      NaN
      0
      -22.5328
      0.000000

      Totals

      506
      1.000000
      11401.6

      22.532806
      5.0
      50.0
      0

      6.030218

Let's describe the columns of this binning table:

- Bin: the intervals delimited by the optimal split points.
- Count: the number of records for each bin.
- Count (%): the percentage of records for each bin.
- Sum: the target sum for each bin.
- Std: the target std for each bin.
- Mean: the target mean for each bin.
- Min: the target min value for each bin.
- Max: the target max value for each bin.
- Zeros count: the number of zeros for each bin.
- WoE: Surrogate Weight-of-Evidence for each bin.
- IV: Surrogate IV for each bin.

The WoE IV for a continuous target is computed as follows:

\begin{equation}
IV = \sum_{i=1}^n \text{WoE}_i \frac{r_i}{r_T}, \quad \text{WoE}_i = |U_i - \mu|,
\end{equation}

where $U_i$ is the target mean value for each bin, $\mu$ is the total target mean, $r_i$ is the number of records for each bin, and $r_T$ is the total number of records.

The last row shows the total number of records, sum and mean.

You can use the method ``plot`` to visualize the histogram and mean curve. Note that the Bin ID corresponds to the binning table index.

```python
binning_table.plot()
```

Optionally, you can show the binning plot with the actual bin widths.

```python
binning_table.plot(style="actual", add_special=False, add_missing=False)
```

##### Mean transformation

Now that we have checked the binned data, we can transform our original data into mean values. You can check the correctness of the transformation using pandas ``value_counts`` method, for instance.

```python
x_transform_mean = optb.transform(x, metric="bins")
```

```python
pd.Series(x_transform_mean).value_counts()
```

    [6.87, 9.72)      89
    [16.09, 19.90)    69
    [13.10, 16.09)    66
    [-inf, 4.65)      50
    [9.72, 11.67)     49
    [23.32, inf)      47
    [5.49, 6.87)      45
    [11.67, 13.10)    35
    [4.65, 5.49)      28
    [19.90, 23.32)    28
    dtype: int64

## Advanced

Many of the advanced options have been covered in the previous tutorials with a binary target. **Check it out!** In this section, we focus on the mean monotonicity trend and the mean difference between bins.

#### Binning table statistical analysis

The ``analysis`` method performs a statistical analysis of the binning table, computing the Information Value (IV) and Herfindahl-Hirschman Index (HHI). Additionally, several statistical significance tests between consecutive bins of the contingency table are performed using the Student's t-test.

```python
binning_table.analysis()
```

    -------------------------------------------------
    OptimalBinning: Continuous Binning Table Analysis
    -------------------------------------------------

      General metrics

        IV                       6.03021763
        HHI                      0.11313253
        HHI (normalized)         0.03250821
        Quality score            0.21464367

      Monotonic trend            descending

      Significance tests

        Bin A  Bin B  t-statistic       p-value
            0      1     5.644492  3.313748e-07
            1      2     2.924528  5.175586e-03
            2      3     0.808313  4.206096e-01
            3      4     4.714124  6.127108e-06
            4      5     2.712699  8.189719e-03
            5      6     0.622294  5.353397e-01
            6      7     5.162973  8.663417e-07
            7      8     3.742511  4.988278e-04
            8      9     1.408305  1.643801e-01

#### Mean monotonicity

The monotonic_trend option permits forcing a monotonic trend to the mean curve. The default setting “auto” should be the preferred option, however, some business constraints might require to impose different trends. The default setting “auto” chooses the monotonic trend most likely to minimize the L1-norm from the options “ascending”, “descending”, “peak” and “valley” using a machine-learning-based classifier.

```python
variable = "INDUS"
x = df[variable].values
y = data.target
```

```python
optb = ContinuousOptimalBinning(name=variable, dtype="numerical",
                                monotonic_trend="auto")
optb.fit(x, y)
```

    ContinuousOptimalBinning(cat_cutoff=None, dtype='numerical', max_bin_size=None,
                             max_n_bins=None, max_n_prebins=20, max_pvalue=None,
                             max_pvalue_policy='consecutive', min_bin_size=None,
                             min_mean_diff=0, min_n_bins=None, min_prebin_size=0.05,
                             monotonic_trend='auto', name='INDUS',
                             outlier_detector=None, outlier_params=None,
                             prebinning_method='cart', special_codes=None,
                             split_digits=None, time_limit=100, user_splits=None,
                             user_splits_fixed=None, verbose=False)

```python
binning_table = optb.binning_table
binning_table.build()
```

      Bin
      Count
      Count (%)
      Sum
      Std
      Mean
      Min
      Max
      Zeros count
      WoE
      IV

      0
      [-inf, 3.35)
      63
      0.124506
      1994.0
      8.56984
      31.650794
      16.5
      50.0
      0
      9.11799
      1.135243

      1
      [3.35, 5.04)
      57
      0.112648
      1615.2
      8.07271
      28.336842
      17.2
      50.0
      0
      5.80404
      0.653814

      2
      [5.04, 6.66)
      66
      0.130435
      1723.7
      7.87908
      26.116667
      16.0
      50.0
      0
      3.58386
      0.467460

      3
      [6.66, 8.01)
      31
      0.061265
      692.0
      4.60489
      22.322581
      14.4
      35.2
      0
      -0.210226
      0.012879

      4
      [8.01, 16.57)
      100
      0.197628
      2045.5
      3.54735
      20.455000
      11.9
      28.7
      0
      -2.07781
      0.410634

      5
      [16.57, 18.84)
      132
      0.260870
      2165.3
      8.50734
      16.403788
      5.0
      50.0
      0
      -6.12902
      1.598874

      6
      [18.84, inf)
      57
      0.112648
      1165.9
      9.51909
      20.454386
      7.0
      50.0
      0
      -2.07842
      0.234130

      7
      Special
      0
      0.000000
      0.0
      NaN
      0.000000
      NaN
      NaN
      0
      -22.5328
      0.000000

      8
      Missing
      0
      0.000000
      0.0
      NaN
      0.000000
      NaN
      NaN
      0
      -22.5328
      0.000000

      Totals

      506
      1.000000
      11401.6

      22.532806
      5.0
      50.0
      0

      4.513036

```python
binning_table.plot()
```

```python
binning_table.analysis()
```

    -------------------------------------------------
    OptimalBinning: Continuous Binning Table Analysis
    -------------------------------------------------

      General metrics

        IV                       4.51303567
        HHI                      0.16875752
        HHI (normalized)         0.06485221
        Quality score            0.74831364

      Monotonic trend                valley

      Significance tests

        Bin A  Bin B  t-statistic   p-value
            0      1     2.180865  0.031181
            1      2     1.537968  0.126745
            2      3     2.976660  0.003739
            3      4     2.075258  0.044180
            4      5     4.934158  0.000002
            5      6    -2.770230  0.006721

A smoother curve, keeping the valley monotonicity, can be achieved by using ``monotonic_trend="convex"``.

```python
optb = ContinuousOptimalBinning(name=variable, dtype="numerical",
                                monotonic_trend="convex")
optb.fit(x, y)
```

    ContinuousOptimalBinning(cat_cutoff=None, dtype='numerical', max_bin_size=None,
                             max_n_bins=None, max_n_prebins=20, max_pvalue=None,
                             max_pvalue_policy='consecutive', min_bin_size=None,
                             min_mean_diff=0, min_n_bins=None, min_prebin_size=0.05,
                             monotonic_trend='convex', name='INDUS',
                             outlier_detector=None, outlier_params=None,
                             prebinning_method='cart', special_codes=None,
                             split_digits=None, time_limit=100, user_splits=None,
                             user_splits_fixed=None, verbose=False)

```python
binning_table = optb.binning_table
binning_table.build()
```

      Bin
      Count
      Count (%)
      Sum
      Std
      Mean
      Min
      Max
      Zeros count
      WoE
      IV

      0
      [-inf, 3.99)
      92
      0.181818
      2932.6
      8.6887
      31.876087
      16.5
      50.0
      0
      9.34328
      1.698778

      1
      [3.99, 5.04)
      28
      0.055336
      676.6
      4.0002
      24.164286
      17.2
      37.3
      0
      1.63148
      0.090279

      2
      [5.04, 6.08)
      40
      0.079051
      878.6
      4.40503
      21.965000
      16.0
      42.8
      0
      -0.567806
      0.044886

      3
      [6.08, 18.84)
      289
      0.571146
      5747.9
      8.10754
      19.888927
      5.0
      50.0
      0
      -2.64388
      1.510042

      4
      [18.84, inf)
      57
      0.112648
      1165.9
      9.51909
      20.454386
      7.0
      50.0
      0
      -2.07842
      0.234130

      5
      Special
      0
      0.000000
      0.0
      NaN
      0.000000
      NaN
      NaN
      0
      -22.5328
      0.000000

      6
      Missing
      0
      0.000000
      0.0
      NaN
      0.000000
      NaN
      NaN
      0
      -22.5328
      0.000000

      Totals

      506
      1.000000
      11401.6

      22.532806
      5.0
      50.0
      0

      3.578116

```python
binning_table.plot()
```

```python
binning_table.analysis()
```

    -------------------------------------------------
    OptimalBinning: Continuous Binning Table Analysis
    -------------------------------------------------

      General metrics

        IV                       3.57811558
        HHI                      0.38126670
        HHI (normalized)         0.27814448
        Quality score            0.22169448

      Monotonic trend       valley (convex)

      Significance tests

        Bin A  Bin B  t-statistic       p-value
            0      1     6.536206  2.732420e-09
            1      2     2.139577  3.636038e-02
            2      3     2.459425  1.602640e-02
            3      4    -0.419474  6.761033e-01

For example, we can force the variable INDUS (proportion of non-retail business acres per town) to be monotonically descending with respect to the house-price.

```python
optb = ContinuousOptimalBinning(name=variable, dtype="numerical",
                                monotonic_trend="descending")
optb.fit(x, y)
```

    ContinuousOptimalBinning(cat_cutoff=None, dtype='numerical', max_bin_size=None,
                             max_n_bins=None, max_n_prebins=20, max_pvalue=None,
                             max_pvalue_policy='consecutive', min_bin_size=None,
                             min_mean_diff=0, min_n_bins=None, min_prebin_size=0.05,
                             monotonic_trend='descending', name='INDUS',
                             outlier_detector=None, outlier_params=None,
                             prebinning_method='cart', special_codes=None,
                             split_digits=None, time_limit=100, user_splits=None,
                             user_splits_fixed=None, verbose=False)

```python
binning_table = optb.binning_table
binning_table.build()
```

      Bin
      Count
      Count (%)
      Sum
      Std
      Mean
      Min
      Max
      Zeros count
      WoE
      IV

      0
      [-inf, 3.35)
      63
      0.124506
      1994.0
      8.56984
      31.650794
      16.5
      50.0
      0
      9.11799
      1.135243

      1
      [3.35, 5.04)
      57
      0.112648
      1615.2
      8.07271
      28.336842
      17.2
      50.0
      0
      5.80404
      0.653814

      2
      [5.04, 6.66)
      66
      0.130435
      1723.7
      7.87908
      26.116667
      16.0
      50.0
      0
      3.58386
      0.467460

      3
      [6.66, 8.01)
      31
      0.061265
      692.0
      4.60489
      22.322581
      14.4
      35.2
      0
      -0.210226
      0.012879

      4
      [8.01, 16.57)
      100
      0.197628
      2045.5
      3.54735
      20.455000
      11.9
      28.7
      0
      -2.07781
      0.410634

      5
      [16.57, 20.73)
      162
      0.320158
      2874.8
      9.61845
      17.745679
      5.0
      50.0
      0
      -4.78713
      1.532638

      6
      [20.73, inf)
      27
      0.053360
      456.4
      3.69088
      16.903704
      7.0
      23.0
      0
      -5.6291
      0.300367

      7
      Special
      0
      0.000000
      0.0
      NaN
      0.000000
      NaN
      NaN
      0
      -22.5328
      0.000000

      8
      Missing
      0
      0.000000
      0.0
      NaN
      0.000000
      NaN
      NaN
      0
      -22.5328
      0.000000

      Totals

      506
      1.000000
      11401.6

      22.532806
      5.0
      50.0
      0

      4.513036

```python
binning_table.plot()
```

```python
binning_table.analysis()
```

    -------------------------------------------------
    OptimalBinning: Continuous Binning Table Analysis
    -------------------------------------------------

      General metrics

        IV                       4.51303567
        HHI                      0.19336343
        HHI (normalized)         0.09253386
        Quality score            0.42428843

      Monotonic trend            descending

      Significance tests

        Bin A  Bin B  t-statistic   p-value
            0      1     2.180865  0.031181
            1      2     1.537968  0.126745
            2      3     2.976660  0.003739
            3      4     2.075258  0.044180
            4      5     3.245419  0.001354
            5      6     0.811840  0.418853

#### Mininum mean difference between consecutive bins

Now, we note that the mean difference between consecutive bins is not significant enough. Therefore, we decide to set ``min_mean_diff=2.0``:

```python
optb = ContinuousOptimalBinning(name=variable, dtype="numerical",
                                monotonic_trend="descending", min_mean_diff=2.0)
optb.fit(x, y)
```

    ContinuousOptimalBinning(cat_cutoff=None, dtype='numerical', max_bin_size=None,
                             max_n_bins=None, max_n_prebins=20, max_pvalue=None,
                             max_pvalue_policy='consecutive', min_bin_size=None,
                             min_mean_diff=2.0, min_n_bins=None,
                             min_prebin_size=0.05, monotonic_trend='descending',
                             name='INDUS', outlier_detector=None,
                             outlier_params=None, prebinning_method='cart',
                             special_codes=None, split_digits=None, time_limit=100,
                             user_splits=None, user_splits_fixed=None,
                             verbose=False)

```python
binning_table = optb.binning_table
binning_table.build()
```

      Bin
      Count
      Count (%)
      Sum
      Std
      Mean
      Min
      Max
      Zeros count
      WoE
      IV

      0
      [-inf, 3.35)
      63
      0.124506
      1994.0
      8.56984
      31.650794
      16.5
      50.0
      0
      9.11799
      1.135243

      1
      [3.35, 5.04)
      57
      0.112648
      1615.2
      8.07271
      28.336842
      17.2
      50.0
      0
      5.80404
      0.653814

      2
      [5.04, 6.66)
      66
      0.130435
      1723.7
      7.87908
      26.116667
      16.0
      50.0
      0
      3.58386
      0.467460

      3
      [6.66, 16.57)
      131
      0.258893
      2737.5
      3.90562
      20.896947
      11.9
      35.2
      0
      -1.63586
      0.423513

      4
      [16.57, inf)
      189
      0.373518
      3331.2
      9.01838
      17.625397
      5.0
      50.0
      0
      -4.90741
      1.833005

      5
      Special
      0
      0.000000
      0.0
      NaN
      0.000000
      NaN
      NaN
      0
      -22.5328
      0.000000

      6
      Missing
      0
      0.000000
      0.0
      NaN
      0.000000
      NaN
      NaN
      0
      -22.5328
      0.000000

      Totals

      506
      1.000000
      11401.6

      22.532806
      5.0
      50.0
      0

      4.513036

```python
binning_table.plot()
```

```python
binning_table.analysis()
```

    -------------------------------------------------
    OptimalBinning: Continuous Binning Table Analysis
    -------------------------------------------------

      General metrics

        IV                       4.51303567
        HHI                      0.25174585
        HHI (normalized)         0.12703682
        Quality score            0.73853806

      Monotonic trend            descending

      Significance tests

        Bin A  Bin B  t-statistic   p-value
            0      1     2.180865  0.031181
            1      2     1.537968  0.126745
            2      3     5.076919  0.000002
            3      4     4.424387  0.000014


# Indefinite Integrals

As we noted in the previous sections on the Fundamental Theorem of Calculus and Antiderivatives, indefinite integrals are also called antiderivatives and are the same process. Indefinite integrals are expressed without upper and lower limits on the integrand, the notation $\int f(x)$ is used to denote the function as an antiderivative of $F$. Therefore, $\int f(x) \space dx = F^\prime(x)$.

For example, the integral $\int x^3 \space dx = \frac{1}{4}x^4 + C$, just as we saw in the same example in the context of antiderivatives. The constant of integration is added to the result as before.

Below is a table of some of the more frequently occurring indefinite integrals.

$$ \int cf(x) \space dx = c\int f(x) \space dx $$

$$ \int \bigg[f(x) + g(x)\bigg] dx = \int f(x) dx + \int g(x) dx $$

$$ \int k \space dx = kx + C $$

$$ \int x^n \space dx = \frac{x^{n+1}}{n+1} + C \qquad (n \neq -1) $$

$$ \int \frac{1}{x} \space dx = \ln{|x|} + C $$

$$ \int e^x \space dx = e^x + C \qquad \int a^x \space dx = \frac{a^x}{\ln{a}} + C $$

$$ \int \sin{x} \space dx = -\cos{x} + C \qquad \int \cos{x} \space dx = \sin{x} + C $$

$$ \int \sec^2{x} \space dx = \tan{x} + C \qquad \int \csc^2{x} \space dx = -\cot{x} + C $$

$$ \int \frac{1}{x^2 + 1} \space dx = \tan^{-1}{x} + C \qquad \int \frac{1}{\sqrt{1-x^2}} \space dx = \sin^{-1}{x} + C $$

$$ \int \sinh{x} \space dx = \cosh{x} + C \qquad \int \cosh{x} \space dx =  \sinh{x} + C $$

## Examples

```python
from sympy import symbols, limit, diff, sin, cos, log, tan, sqrt, init_printing, plot, integrate, sinh
from mpmath import ln, e, pi

init_printing()
x = symbols('x')
y = symbols('y')
```

### Example 1: Determine the general integral $\int x^2 + x^{-2} \space dx$

We perform antidifferentiation of each term in the integral to compute the result:

$$ \int x^2 + x^{-2} \space dx = \frac{1}{3} x^3 - \frac{1}{x} + C $$

Confirm our result using SymPy's `integrate()` function.

```python
integrate(x ** 2 + x ** -2, x)
```

### Example 2: Find the general integral $\int x^4 - \frac{1}{2} x^3 + \frac{1}{4} x - 2 \space dx$

As in the previous example, we use antidifferentiation on each term:

$$ \int x^4 - \frac{1}{2} x^3 + \frac{1}{4} x - 2 \space dx = \frac{1}{5}x^5 - \frac{1}{8}x^4 + \frac{1}{8}x^2 - 2x $$

```python
integrate(x ** 4 - (1/2) * x ** 3 + (1/4) * x - 2, x)
```

### Example 3: Find the general integral $\int (1-x)(2+x^2) \space dx$

Start by expanding the terms, then find the antiderivative of each term:

$$ \int 2 + x^2 - 2x - x^3 \space dx = 2x + \frac{1}{3}x^3 - x^2 - \frac{1}{4}x^4 $$

```python
integrate((1 - x) * (2 + x ** 2), x)
```

### Example 4: Determine the general integral $\int \sin{x} + \sinh{x} \space dx$

Take the antiderivative of each term:

$$ \int \sin{x} + \sinh{x} \space dx = -\cos{x} + \cosh{x} $$

```python
integrate(sin(x) + sinh(x), x)
```

### Example 5: Find the general integral $\int_{-1}^0 2x - e^x \space dx$

The last example involves a definite integral, which we previously encountered in the Fundamental Theorem of Calculus section. Find the antiderivative of each term:

$$ \int_{-1}^0 2x - e^x \space dx = x^2 - e^x \Bigg\rvert_{-1}^0 $$

$$ (0)^2 - e^{(0)} - (-1)^2 - e^{(-1)} = -1 - 1 - e^{-1} = -2 + e^{-1} $$

Calculate $-2 - e^{-1}$ and verify the result with SymPy.

```python
-2 + e ** -1
```

    mpf('-1.6321205588285577')

```python
integrate(2 * x - e ** x, (x, -1, 0))
```

## References

Stewart, J. (2007). Essential calculus: Early transcendentals. Belmont, CA: Thomson Higher Education.

Weisstein, Eric W. "Indefinite Integral." From MathWorld--A Wolfram Web Resource. http://mathworld.wolfram.com/IndefiniteIntegral.html


```python
%pylab inline
pylab.rcParams['figure.figsize'] = (16.0, 8.0)
```

    Populating the interactive namespace from numpy and matplotlib

# Drawing from multivariate distributions

## Draws from the multivariate normal distribution

Draws from a multivariate normal distribution
$$ N(\mathbf{\mu}, \mathbf{\Sigma}) $$
can be generated by

1) Calculate the Cholesky decomposition $\mathbf{\Sigma} = \mathbf{R}^T\mathbf{R}$
``` python
    from numpy.linalg import cholesky
    R = cholesky(Sigma)
```

2) Generate standard normally distributed values $\mathbf{Z}$

3) Evaluate $$ \mathbf{X} = \mathbf{\mu 1} + \mathbf{RZ}$$

### Exercise 2.1

Draw 1000 samples from the bivariate distribution
$$ N\left(\left( \begin{array}{c}
0.2 \\ -1.0
\end{array}\right),
\left(\begin{array}{cc}
0.01 & -0.014 \\ -0.014 & 0.04
\end{array}\right)
\right)
$$

```python
draws = 1000

```

Draws from the multivariate normal distribution can more easily generated using the built-in **scipy** functions

```python
    from scipy.stats import multivariate_normal
    dist = multivariate_normal(mu, Sigma)
    X = dist.rvs(size)
```
**Note**
Scipy *rvs* functions return arrays of shape (number of draws, size of mean)

### Exercise 2.2

Repeat Exercise 2.1 with the built-in scipy function and compare the results.

```python
from scipy.stats import multivariate_normal

mu = array([0.2, -1.0])
Sigma = array([[0.01, -0.014],
              [-0.014, 0.04]])

dist = multivariate_normal(mu, Sigma)
X = dist.rvs(10000)

scatter(X[:,0], X[:,1]);
```

## Draws using a copula function

In many practical cases knowledge about the input quantities is available in terms of their individual distributions and a correlation coefficient. This is insufficient to assign a unique multivariate distribution. Therefore, a copula function can be defined
$$
C(\mu_1,\ldots,\mu_N) = \mathbb{P} \left[ X_1\leq G_{X_1}^{-1}(\mu_1)\ldots,X_N\leq G_{X_N}^{-1}(\mu_N) \right]
$$

### Example copula functions

* all input quantities are mutually independent
$$ C(\mu_1,\ldots,\mu_N) = \prod_{k=1}^N \mu_k $$

* the input quantities are correlated with $\rho\equiv 1$
$$ C(\mu_1,\ldots,\mu_N) = \min_{k} \mu_k $$

* two input quantities are correlated with $\rho$
$$ C(\mu_1,\mu_2) = F_2(G_{X_1}^{-1}(\mu_1),G_{X_2}^{-1}(\mu_2),\rho) $$

The copula can be used to incorporate the correlation coefficient and the individual distributions $g_{X_i}$ to formally define a multivariate distribution.

#### Example
Input quantities $X_1,X_2$ with correlation coefficient $\rho$ and
\begin{align}
X_1 \sim & N(\mu, \sigma) \\
X_2 \sim & U(a, b)
\end{align}

Use bivariate normal copula function:

1) Draw from bivariate standard normal distribution
$$ z \sim N\left(\mathbf{0}, \left(\begin{array}{cc}
1.0 & \rho \\ \rho & 1.0
\end{array}\right) \right)
$$

2) Evaluate cumulative distribution function of the copula
\begin{align}
\zeta_1 =& G_N(z_1) \\
\zeta_2 =& G_N(z_2)
\end{align}

3) Evaluate inverse cumulative distribution functions
\begin{align}
x_1 =& G_{X_1}^{-1}(\zeta_1) \\
x_2 =& G_{X_2}^{-1}(\zeta_2)
\end{align}

### Exercise 2.3

Consider the input quantities $X_1,X_2$ with
* $X_1$ has best estimate 0.2 with uncertainty of 50%
* $X_2$ has best estimate -1.0 with uncertainty of 20%
* correlation between $X_1$ and $X_2$ is $\rho=-0.7$

Generate 1000 random draws using a bivariate normal copula function.

```python
from scipy.stats import norm, multivariate_normal
rho = -0.7

mu1 = 0.2
umu1 = 0.1

mu2 = -1.0
umu2 = 0.2

rho = -0.7

# Ziehung aus der bivariaten Normalverteilung
dist_z = multivariate_normal(array([0,0]), array([[1, rho], [rho, 1]]))
z = dist_z.rvs(1000) # shape(z) = (1000,2)

# Einsetzen in die kumulutative Verteilung der Normalverteilung
zeta_1 = norm.cdf(z[:,0])
zeta_2 = norm.cdf(z[:,1])

# Einsetzen in die inverse kumulative Verteilung von X1, X2
x1 = norm.ppf(zeta_1, loc=mu1, scale=umu1)
x2 = norm.ppf(zeta_2, loc=mu2, scale=umu2)

figure(1)
scatter(x1,x2)

figure(2)
subplot(211)
hist(x1)
subplot(212)
hist(x2);

```

### Exercise 2.4

Consider the input quantities $X_1, X_2$ with

* $X_1$ has best estimate $x_1=2.4$ with expanded uncertainty $U=0.4 (k=2)$ under normality assumption

* $X_2$ is located in $[-1.5, 1.5]$

* $X_1, X_2$ are correlated with $\rho = 0.4$

Draw 1000 samples from their joint probability distribution using a normal distribution copula function.

```python
from scipy.stats import norm, uniform, multivariate_normal
rho = 0.7

```

.ipynb_checkpoints/03 Drawing from multivariate distributions-checkpoint.ipynb


# Multivariate Kalman filter

# Purpose
* implementation of multivariate kalman filter inspired by: [https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/06-Multivariate-Kalman-Filters.ipynb](https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/06-Multivariate-Kalman-Filters.ipynb)

# Methodology
* Implement a kalman filter to chase a ship at unsteady velocity

# Setup

```python
# %load imports.py

%matplotlib inline
%load_ext autoreload
%autoreload 2

import pandas as pd
pd.options.display.max_rows = 999
pd.options.display.max_columns = 999
pd.set_option("display.max_columns", None)
import numpy as np
import os
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import norm
import filterpy.stats as stats
```

```python
import math
import numpy as np
from numpy.random import randn

def compute_dog_data(z_var, process_var, count=1, dt=1.):
    "returns track, measurements 1D ndarrays"
    x, vel = 0., 1.
    z_std = math.sqrt(z_var)
    p_std = math.sqrt(process_var)
    xs, zs = [], []
    for _ in range(count):
        v = vel + (randn() * p_std)

        x += v*dt
        xs.append([x,v])

        zs.append(x + randn() * z_std)

    return np.array(xs), np.array(zs),
```

```python
from filterpy.common import Q_discrete_white_noise

dt = 1.
R_var = 10
Q_var = 0.01

count = 50
track, zs = compute_dog_data(R_var, Q_var, count)

```

```python
from scipy.linalg import inv

def filter(zs,x,P,F,H,R,Q):

    xs, cov = [], []
    for z in zs:
        # predict
        x = F @ x
        P = F @ P @ F.T + Q

        #update
        S = H @ P @ H.T + R
        K = P @ H.T @ inv(S)
        y = z - H @ x
        x += K @ y
        P = P - K @ H @ P

        xs.append(x)
        cov.append(P)

    xs, cov = np.array(xs), np.array(cov)
    return xs, cov
```

```python
x = np.array([[0, 1]]).T  # state mean
P = np.diag([500, 49])         # state covariance
F = np.array([[1, dt],         # transition function
              [0,  1]])
H = np.array([[1., 0.]])       # measurement function
R = np.array([[R_var]])        # measurement covariance
# z : measurement mean
Q = Q_discrete_white_noise(dim=2, dt=dt, var=Q_var)  # process covariance

xs,cov=filter(zs=zs, x=x, P=P, F=F, H=H, R=R, Q=Q)
```

```python
fig,ax=plt.subplots()

ax.plot(track[:,0], label='real')
ax.plot(zs, '.', label='measurement')
ax.plot(xs[:,0], label='filter')
ax.set_ylabel('x [m]')
ax.legend()

fig,ax=plt.subplots()

ax.plot(track[:,1], label='real')
ax.plot(xs[:,1], label='filter')
ax.set_ylabel('v [m/s]')
ax.legend()
```

```python
from scipy.stats import multivariate_normal

ncols = 4
fig,axes=plt.subplots(ncols=ncols)
fig.set_size_inches(15,5)

for n,i in enumerate(range(0, len(xs), int(len(xs)/(ncols-1)))):

    mus = xs[i].flatten()
    rv = multivariate_normal(mean=mus, cov=cov[i])

    x_ = np.linspace(np.min(xs[:,0]), np.max(xs[:,0]),50)
    y_ = np.linspace(np.min(xs[:,1]), np.max(xs[:,1]),50)

    xx_, yy_ = np.meshgrid(x_, y_)
    pos = np.dstack((xx_, yy_))

    ax=axes[n]
    ax.contourf(xx_, yy_, rv.pdf(pos));
```

## Smoother

```python
from numpy import zeros
from numpy.linalg import inv

def rts_smoother(Xs, Ps, F, Q):
    n, dim_x, _ = Xs.shape

    # smoother gain
    K = zeros((n,dim_x, dim_x))
    x, P, Pp = Xs.copy(), Ps.copy(), Ps.copy()

    for k in range(n-2,-1,-1):
        Pp[k] = F @ P[k] @ F.T + Q # predicted covariance

        K[k]  = P[k] @ F.T @inv(Pp[k])
        x[k] += K[k] @ (x[k+1] - (F @ x[k]))
        P[k] += K[k] @ (P[k+1] - Pp[k]) @ K[k].T
    return (x, P, K, Pp)
```

```python
xs,cov=filter(zs=zs, x=x, P=P, F=F, H=H, R=R, Q=Q)
xs = xs.reshape((len(zs),len(x)))

xs_reverse = np.flipud(xs)
P_reverse = np.flipud(cov)
```

```python
def filter3(zs,x,Ps,F,H,R,Q):

    xs, cov = [], []
    for z,P in zip(zs,Ps):
        # predict
        x = F @ x
        P = F @ P @ F.T + Q

        #update
        S = H @ P @ H.T + R
        K = P @ H.T @ inv(S)
        y = z - H @ x
        x += K @ y
        P = P - K @ H @ P

        xs.append(x)
        cov.append(P)

    xs, cov = np.array(xs), np.array(cov)
    return xs, cov
```

```python
xs_,cov_ = filter3(zs=xs_reverse[:,0], x=xs_reverse[0,:], Ps=P_reverse, F=F, H=H, R=R, Q=Q)
xs_ = np.flipud(xs_)
xs_ = xs_.reshape(xs.shape)
cov_ = np.flipud(cov_)

fig,ax=plt.subplots()

ax.plot(track[:,0], label='real')
ax.plot(zs, '.', label='measurement')
ax.plot(xs[:,0], label='filter')
ax.plot(x_[:,0], label='smoother')
ax.set_ylabel('x [m]')

```

```python
xs_reverse.shape
```

```python
fig,ax=plt.subplots()

ax.plot(track[:,0], label='real')
ax.plot(zs, '.', label='measurement')
ax.plot(xs[:,0], label='filter')
ax.plot(x_smooth[:,0], label='smoother')
ax.set_ylabel('x [m]')
ax.legend()

fig,ax=plt.subplots()

ax.plot(track[:,1], label='real')
ax.plot(xs[:,1], label='filter')
ax.plot(x_smooth[:,1], label='smoother')
ax.set_ylabel('v [m/s]')
ax.legend()
```

## PyKalman

```python
from filterpy.kalman import KalmanFilter
kf = KalmanFilter(dim_x=2, dim_z=1)

kf.x = np.array([[0, 1]]).T  # state mean
kf.P = np.diag([500, 49])         # state covariance
kf.F = np.array([[1, dt],         # transition function
              [0,  1]])
kf.H = np.array([[1., 0.]])       # measurement function
kf.R = np.array([[R_var]])        # measurement covariance
# z : measurement mean
kf.Q = Q_discrete_white_noise(dim=2, dt=dt, var=Q_var)  # process covariance
```

```python
(mu, cov, _, _) = kf.batch_filter(zs=zs)

fig,ax=plt.subplots()
ax.plot(track[:,0], label='real')
ax.plot(zs, '.', label='measurement')
ax.plot(mu[:,0], label='filter')
ax.set_ylabel('x [m]')
ax.legend()

fig,ax=plt.subplots()
ax.plot(track[:,1], label='real')
ax.plot(mu[:,1], label='filter')
ax.set_ylabel('v [m/s]')
ax.legend()
```

## Ball drop

### Example
Let's exemplify this with a simple problem, simulated a ball being dropped in air.
The forces acting on this ball will be the drag from the air, which is modelled as: $C_d \cdot \dot{x}^2$ and the gravity, which is modelled as $g \cdot m$:

```python
from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame,
                                      Particle, Point)
import sympy as sp
from IPython.display import display, Math, Latex
from sympy.physics.vector.printing import vpprint, vlatex
from src.substitute_dynamic_symbols import run, lambdify

x = dynamicsymbols('x')
m,F,g = sp.symbols('m F g')

eq_kinetics = sp.Eq(F, -m*g)
Math(vlatex(eq_kinetics))
```

The kinematics connecting the forces to motion can be described with Newtons 2nd law:

```python
eq_kinematics = sp.Eq(F, m*x.diff().diff())
Math(vlatex(eq_kinematics))
```

The acceleration can then be calculated using these two equations:

```python
eqs=[eq_kinetics,eq_kinematics]
solution = sp.solve(eqs, x.diff().diff(), F, dict=True)[0]
eq_acc = sp.Eq(x.diff().diff(),solution[x.diff().diff()])
Math(vlatex(eq_acc))
```

This equation can be used to simulate the motion of the ball, by doing a time step integration of this initial value problem.

```python
from scipy.integrate import solve_ivp

acceleration_lambda = lambdify(eq_acc.rhs)

inputs={
'g' : 9.81,
}

def step(t,states, inputs):

    x1d = states[1]
    x2d = acceleration_lambda(**inputs)

    dstates = [x1d, x2d]
    return dstates

t_ = np.linspace(0,10,100)
y0 = [
    0,0
]
solution = solve_ivp(fun=step, y0=y0, t_span=[t_[0],t_[-1]], t_eval=t_, args=(inputs,))

df_result = pd.DataFrame(solution.y.T, index=solution.t, columns=['x','x1d'])
df_result['x2d'] = acceleration_lambda(**inputs)

fig,axes=plt.subplots(nrows=3)
ax=axes[0]
df_result.plot(y='x', label='$x$ [m]', ax=ax)

ax.set_title('Ball position')

ax=axes[1]
df_result.plot(y='x1d', label='$\dot{x}$ [m/s]', ax=axes[1])
ax.set_title('Ball velocity [m/s]')
ax.set_xlabel('time [s]')

ax=axes[2]
df_result.plot(y='x2d', label='$\ddot{x}$ [m/s^2]', ax=axes[2])
ax.set_title('Ball acceleration [m/s]')
ax.set_xlabel('time [s]')

for ax in axes:
    ax.grid(True)
plt.tight_layout()
```

```python
df_measure = df_result.copy()
df_measure['x1d'] = np.NaN
df_measure['x2d'] = np.NaN

R_var = 100
Q_var = 0.01

df_measure['R'] = np.random.normal(loc=0, scale=np.sqrt(R_var), size=len(df_measure))  # measurement noise
df_measure['Q'] = np.random.normal(loc=0, scale=np.sqrt(Q_var), size=len(df_measure))  # process noise

df_measure['x']+=(df_measure['R']+df_measure['Q'])

```

```python
dt = float(df_measure.index[1]-df_measure.index[0] )
x = np.array([[0, 0]]).T  # state mean
P = np.diag([500, 49])         # state covariance
F = np.array([[1, dt],         # transition matrix
              [0,  1]])
H = np.array([[1., 0.]])       # measurement function
R = np.array([[R_var]])        # measurement covariance
Q = Q_discrete_white_noise(dim=2, dt=dt, var=Q_var)  # process covariance
xs,cov=filter(zs=df_measure['x'], x=x, P=P, F=F, H=H, R=R, Q=Q)
df_pred2 = pd.DataFrame(data=xs.reshape((len(df_result),2)), index=df_result.index, columns=['x','x1d'])

x_smooth, P_smooth, K_smooth, Pp_smoot = rts_smoother(xs, cov, F, Q)
df_pred2_smooth= pd.DataFrame(data=x_smooth.reshape((len(df_measure),2)), columns=['x','x1d'], index=df_measure.index)

```

## Control

```python
from scipy.linalg import inv

def filter2(zs, x,u,P,F,H,R,Q,B):

    xs, cov = [], []
    for z in zs:
        # predict
        x = F @ x + B.dot(u)
        P = F @ P @ F.T + Q

        #update
        S = H @ P @ H.T + R
        K = P @ H.T @ inv(S)
        y = z - H @ x
        x += K @ y
        P = P - K @ H @ P

        xs.append(x)
        cov.append(P)

    xs, cov = np.array(xs), np.array(cov)
    return xs, cov
```

```python
def rts_smoother_control(Xs, Ps, F, Q, B, u):
    n, dim_x, _ = Xs.shape

    # smoother gain
    K = zeros((n,dim_x, dim_x))
    x, P, Pp = Xs.copy(), Ps.copy(), Ps.copy()

    for k in range(n-2,-1,-1):
        Pp[k] = F @ P[k] @ F.T + Q # predicted covariance

        K[k]  = P[k] @ F.T @inv(Pp[k])
        x[k] += K[k] @ (x[k+1] - (F @ x[k]))
        P[k] += K[k] @ (P[k+1] - Pp[k]) @ K[k].T
    return (x, P, K, Pp)
```

```python
dt = float(df_measure.index[1]-df_measure.index[0] )
x = np.array([[0, 0]]).T  # state mean
P = np.diag([500, 49])         # state covariance
F = np.array([[1, dt],         # transition matrix
              [0,  1]])
H = np.array([[1., 0.]])       # measurement function
R = np.array([[R_var]])        # measurement covariance
Q = Q_discrete_white_noise(dim=2, dt=dt, var=Q_var)  # process covariance
B = np.array([[1/2*dt**2],         # control transition matrix
                  [dt]])

a = -9.81

xs,cov=filter2(zs=df_measure['x'], x=x, u=a, P=P, F=F, H=H, R=R, Q=Q, B=B)
df_pred_control = pd.DataFrame(data=xs.reshape((len(df_measure),2)), columns=['x','x1d'], index=df_measure.index)

x_smooth, P_smooth, K_smooth, Pp_smoot = rts_smoother_control(xs, cov, F, Q, B=B, u=a)
df_pred_control_smooth= pd.DataFrame(data=x_smooth.reshape((len(df_measure),2)), columns=['x','x1d'], index=df_measure.index)

```

```python
fig,ax=plt.subplots()
fig.set_size_inches(15,8)

df_result.plot(y='x', ax=ax, label='real')
df_measure.plot(y='x', style='.', ax=ax, label='measure')
df_pred2.plot(y='x', style='--', ax=ax, label='filter')
df_pred2_smooth.plot(y='x', style='--', ax=ax, label='smoother')
df_pred_control.plot(y='x', style='--', ax=ax, label='filter control')
df_pred_control_smooth.plot(y='x', style='--', ax=ax, label='smoother control ')

fig,ax=plt.subplots()
fig.set_size_inches(15,8)
df_result.plot(y='x1d', ax=ax, label='real')
df_pred2.plot(y='x1d', style='--', ax=ax, label='filter')
df_pred2_smooth.plot(y='x1d', style='--', ax=ax, label='smoother')
df_pred_control.plot(y='x1d', style='--', ax=ax, label='filter control')
df_pred_control_smooth.plot(y='x1d', style='--', ax=ax, label='smoother control ')

```

### statsmodels

```python
import numpy as np
import statsmodels.api as sm
import matplotlib.pyplot as plt

# --- True values --- #
init_state = 1.
# covariance matrix
state_noise = 0.02
# measurement noise
measure_noise = 0.8

# --- Generate Data --- #
np.random.seed(1234)
numsteps = 80

# control inputs
controls = np.array([0]*1 + [0]*19 + [3]*40 + [0]*20)
# get true states
true_states = np.zeros(numsteps)
true_states[0] = init_state
true_states += controls
# state noise
true_states += np.random.normal(0, state_noise, numsteps)

# measurements
measurements = np.array([(s + np.random.normal(0, measure_noise)) for s in true_states])

# --- Create the model --- #
mod = sm.tsa.statespace.MLEModel(measurements, k_states=1, k_posdef=1)
mod['design'] = [1.]
mod['obs_cov'] = [measure_noise]
mod['state_intercept'] = np.r_[controls[1:], np.nan][None,:]
mod['selection'] = [1.]
mod['state_cov'] = [state_noise]
mod.initialize_known(1+controls[0:1], [[0.02]])
res = mod.filter(init_state)

# --- Plot the results --- #
fig, axes = plt.subplots(2, 1, figsize=(10,6))
time = range(numsteps)
axes[0].plot(time, measurements, 'b', label='measured')
axes[0].plot(time, true_states, 'r', label='true')
axes[0].plot(time, res.filtered_state[0], 'g', label='estimates')
#axes[0].plot(time, res.forecasts[0], 'g', label='estimates')
axes[0].legend()

axes[1].plot(time, res.filtered_state_cov[0,0,:], 'b', label='covariance')
axes[1].legend();
```

```python

```