%matplotlib widget

# To ignore warnings (http://stackoverflow.com/questions/9031783/hide-all-warnings-in-ipython)
import warnings
warnings.filterwarnings('ignore')

import IPython

import math
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import matplotlib.cm as cm

"""
Explanation: Interactive Jupyter notebooks
2021-03-17 - Jérémie Decock
Goal:
Here we will see:
- how to display interactive plots in Python in a notebook (with matplotlib/ipympl)
- how to use widgets to make interactive interfaces in a notebook
Note: using widgets/interactive plots is painful in JupyterLab 2, so this tutorial assumes either Jupyter Notebook or JupyterLab 3 is installed.
To install JupyterLab 3 in a dedicated conda environment: 
conda create -n demo python numpy matplotlib
conda activate demo
pip install jupyterlab==3
juypter lab
Or you can test this tutorial on Binder

Note: This notebook can be seen in Slideshow mode using RISE (but it works on Jupyter Notebook only not Jupyter Lab).
Import directives
End of explanation
"""


x = np.arange(-10, 10, 0.01)
y = np.sin(2. * 2. * np.pi * x) * 1. / np.sqrt(2. * np.pi) * np.exp(-(x**2.)/2.)
plt.plot(x, y);

"""
Explanation: Interactive Matplotlib plots in Jupyter
Interactive plots requires ipympl (c.f. https://github.com/matplotlib/ipympl).
pip install ipympl
Then simply insert the %matplotlib widget directive in a "code" cell at the begining of the notebook.
Example: 2D plots
End of explanation
"""


xx, yy = np.meshgrid(np.arange(-5, 5, 0.25), np.arange(-5, 5, 0.25))
z = np.sin(np.sqrt(xx**2 + yy**2))

fig = plt.figure()
ax = axes3d.Axes3D(fig)
ax.plot_surface(xx, yy, z, cmap=cm.jet, rstride=1, cstride=1, color='b', shade=True)
plt.show()

"""
Explanation: Example: 3D plots
End of explanation
"""


import plotly.express as px
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")
fig.show()

import plotly.express as px
df = px.data.iris()
fig = px.scatter_matrix(df, dimensions=["sepal_width", "sepal_length", "petal_width", "petal_length"], color="species")
fig.show()

import plotly.express as px
df = px.data.gapminder()
fig = px.scatter(df, x="gdpPercap", y="lifeExp", animation_frame="year", animation_group="country",
           size="pop", color="continent", hover_name="country", facet_col="continent",
           log_x=True, size_max=45, range_x=[100,100000], range_y=[25,90])
fig.show()

import plotly.express as px
df = px.data.carshare()
fig = px.scatter_mapbox(df, lat="centroid_lat", lon="centroid_lon", color="peak_hour", size="car_hours",
                  color_continuous_scale=px.colors.cyclical.IceFire, size_max=15, zoom=10,
                  mapbox_style="carto-positron")
fig.show()

# C.f. https://plotly.com/python/time-series/

import pandas as pd
import plotly.express as px
import plotly.io as pio
#pio.renderers.default = "browser"

df = pd.read_csv("pvgis.csv.tar.gz")
df['time'] = pd.date_range("2005-01-01", periods=len(df), freq='1h')

fig = px.line(df,
              x='time',
              y=[
                  'temperature',
              ],
              width=800,
              height=400,
              title='Ratios')

fig.update_xaxes(
    rangeslider_visible=True,
    rangeselector=dict(
        buttons=list([
            dict(count=1, label="1d", step="day", stepmode="backward"),
            dict(count=7, label="1w", step="day", stepmode="backward"),
            dict(count=1, label="1m", step="month", stepmode="backward"),
            dict(count=1, label="1y", step="year", stepmode="backward"),
            dict(step="all")
        ])
    )
)
fig.show()

"""
Explanation: Plotly
"The plotly Python library is an interactive, open-source plotting library that supports over 40 unique chart types covering a wide range of statistical, financial, geographic, scientific, and 3-dimensional use-cases.
Built on top of the Plotly JavaScript library (plotly.js), plotly enables Python users to create beautiful interactive web-based visualizations that can be displayed in Jupyter notebooks, saved to standalone HTML files"
Installation:
Plotly may be installed using pip:
pip install plotly
Documentation: https://plotly.com/python/getting-started/
End of explanation
"""


import ipywidgets
from ipywidgets import interact

"""
Explanation: https://plotly.com/python/time-series/
Bokeh
"Bokeh is a Python library for creating interactive visualizations for modern web browsers. It helps you build beautiful graphics, ranging from simple plots to complex dashboards with streaming datasets. With Bokeh, you can create JavaScript-powered visualizations without writing any JavaScript yourself."
Installation:
pip install bokeh
Documentation: https://bokeh.org/
https://demo.bokeh.org/sliders
https://demo.bokeh.org/selection_histogram
https://demo.bokeh.org/stocks
Widgets in Jupyter with ipywidget
ipywidgets are interactive HTML widgets for Jupyter notebooks.
C.f. https://ipywidgets.readthedocs.io/en/latest/
What are widgets?
Widgets are eventful python objects that have a representation in the browser, often as a control like a button, slider, checkbox, textbox, etc.
What can they be used for?
You can use widgets to build interactive GUIs for your notebooks.
Installation
pip install ipywidgets
Import directives
End of explanation
"""


%matplotlib inline

from ipywidgets import IntSlider
from IPython.display import display

slider = IntSlider(min=1, max=10)  # make the widget
display(slider)                    # display it

"""
Explanation: Make a first widget
End of explanation
"""


import ipywidgets

slider = IntSlider(min=1, max=10, description='x')  # make the widget

def f(x):
    print(x**2)

out = ipywidgets.interactive_output(f, {'x': slider})

ipywidgets.HBox([ipywidgets.VBox([slider]), out])

"""
Explanation: Link the widget to a function
End of explanation
"""


def f(x):
    return x

"""
Explanation: Widget list
A lot of widgets are available...
C.f. https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html
The ipywidgets.interact class
"The interact function (ipywidgets.interact) automatically creates user interface (UI) controls for exploring code and data interactively.
It is the easiest way to get started using IPython’s widgets."
C.f. http://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html
Using interact as a function
"At the most basic level, interact autogenerates UI controls for function arguments, and then calls the function with those arguments when you manipulate the controls interactively. To use interact, you need to define a function that you want to explore. Here is a function that returns its only argument x."
End of explanation
"""


from ipywidgets import interact

interact(f, x=5);

"""
Explanation: When you pass this function as the first argument to interact along with an integer keyword argument (x=5), a slider is generated and bound to the function parameter.
End of explanation
"""


def f(x):
    print("Hello {}".format(x))
    
interact(f, x="IPython Widgets");

"""
Explanation: When you move the slider, the function is called, and its return value is printed.
Text
interact choose the appropriate widget depending on the function argument type.
For instance if x is a string then a text box widget is used.
End of explanation
"""


def square(num):
    print("{} squared is {}".format(num, num*num))

interact(square, num=5);

def square(num):
    print("{} squared is {}".format(num, num*num))

interact(square, num=(0, 100));

def square(num):
    print("{} squared is {}".format(num, num*num))

interact(square, num=(0, 100, 10));

"""
Explanation: Integer (IntSlider)
For integers a slider widget is used.
End of explanation
"""


def plot(t):
    fig = plt.figure()
    ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
    
    x = np.linspace(0, 2, 100)
    y = np.sin(2. * np.pi * (x - 0.01 * t))
    ax.plot(x, y, lw=2)

interact(plot, t=(0, 100, 1));

x = np.random.normal(size=1000)

def plot(num):
    plt.hist(x, bins=num)

interact(plot, num=(10, 100));

x, y = np.random.normal(size=(2, 100000))

def plot(num):
    fig = plt.figure(figsize=(8.0, 8.0))
    ax = fig.add_subplot(111)
    im = ax.hexbin(x, y, gridsize=num)
    fig.colorbar(im, ax=ax)

interact(plot, num=(10, 60));

"""
Explanation: Example with Matplotlib
End of explanation
"""


def square(num):
    print("{} squared is {}".format(num, num*num))

interact(square, num=5.);

def square(num):
    print("{} squared is {}".format(num, num*num))

interact(square, num=(0., 10.));

def square(num):
    print("{} squared is {}".format(num, num*num))

interact(square, num=(0., 10., 0.5));

"""
Explanation: Float (FloatSlider)
The same for floats.
End of explanation
"""


def greeting(upper):
    text = "hello"
    if upper:
        print(text.upper())
    else:
        print(text.lower())

interact(greeting, upper=False);

"""
Explanation: Boolean (Checkbox)
A boolean generates a checkbox widget.
End of explanation
"""


def greeting(name):
    print("Hello {}".format(name))

interact(greeting, name=["John", "Bob", "Alice"]);

"""
Explanation: List (Dropdown)
A list generates a dropdown widget.
End of explanation
"""


def translate(word):
    print(word)

interact(translate, word={"One": "Un", "Two": "Deux", "Three": "Trois"});

x = np.arange(-2 * np.pi, 2 * np.pi, 0.1)

def plot(function):
    y = function(x)
    plt.plot(x, y)

interact(plot, function={"Sin": np.sin, "Cos": np.cos});

"""
Explanation: Dictionnary (Dropdown)
The same for dictionaries.
End of explanation
"""


def greeting(upper, name):
    text = "hello {}".format(name)
    if upper:
        print(text.upper())
    else:
        print(text.lower())

interact(greeting, upper=False, name=["john", "bob", "alice"]);

"""
Explanation: Example of using multiple widgets on one function
A widget is made for each argument of the target function.
End of explanation
"""


@interact(text="IPython Widgets")
def greeting(text):
    print("Hello {}".format(text))

"""
Explanation: Using interact as a decorator with named parameters
As an alternative syntax, Python decorators can be used to bind the widget and the target function.
Text
End of explanation
"""


@interact(num=5)
def square(num):
    print("{} squared is {}".format(num, num*num))

@interact(num=(0, 100))
def square(num):
    print("{} squared is {}".format(num, num*num))

@interact(num=(0, 100, 10))
def square(num):
    print("{} squared is {}".format(num, num*num))

"""
Explanation: Integer (IntSlider)
End of explanation
"""


@interact(num=5.)
def square(num):
    print("{} squared is {}".format(num, num*num))

@interact(num=(0., 10.))
def square(num):
    print("{} squared is {}".format(num, num*num))

@interact(num=(0., 10., 0.5))
def square(num):
    print("{} squared is {}".format(num, num*num))

"""
Explanation: Float (FloatSlider)
End of explanation
"""


@interact(upper=False)
def greeting(upper):
    text = "hello"
    if upper:
        print(text.upper())
    else:
        print(text.lower())

"""
Explanation: Boolean (Checkbox)
End of explanation
"""


@interact(name=["John", "Bob", "Alice"])
def greeting(name):
    print("Hello {}".format(name))

"""
Explanation: List (Dropdown)
End of explanation
"""


@interact(word={"One": "Un", "Two": "Deux", "Three": "Trois"})
def translate(word):
    print(word)

x = np.arange(-2 * np.pi, 2 * np.pi, 0.1)

@interact(function={"Sin": np.sin, "Cos": np.cos})
def plot(function):
    y = function(x)
    plt.plot(x, y)

"""
Explanation: Dictionnary (Dropdown)
End of explanation
"""


@interact
def square(num=2):
    print("{} squared is {}".format(num, num*num))

@interact
def square(num=(0, 100)):
    print("{} squared is {}".format(num, num*num))

@interact
def square(num=(0, 100, 10)):
    print("{} squared is {}".format(num, num*num))

"""
Explanation: Using interact as a decorator without parameter
As an alternative, widgets parameters can be set in the function parameters.
Example
End of explanation
"""


from ipywidgets import interactive

def f(a, b):
    display(a + b)
    return a+b

w = interactive(f, a=10, b=20)

display(w)

w.kwargs

w.result

"""
Explanation: The ipywidgets.interactive class
"In addition to interact, IPython provides another function, interactive, that is useful when you want to reuse the widgets that are produced or access the data that is bound to the UI controls.
Note that unlike interact, the return value of the function will not be displayed automatically, but you can display a value inside the function with IPython.display.display."
Documentation: https://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html#interactive
End of explanation
"""


x = np.random.normal(size=1000)

def plot(num):
    x = np.arange(-5, 5, 0.25)
    y = np.arange(-5, 5, 0.25)
    xx,yy = np.meshgrid(x, y)
    z = np.sin(np.sqrt(xx**2 + yy**2) + num)

    fig = plt.figure()
    ax = axes3d.Axes3D(fig)
    ax.set_title("sin(sqrt(x² + y²) + {:0.2f})".format(num))
    ax.plot_wireframe(xx, yy, z)

interact(plot, num=(10., 25., 0.1));

"""
Explanation: Layouts
C.f. https://ipywidgets.readthedocs.io/en/latest/examples/Layout%20Templates.html and https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Styling.html
Ipywidgets usage examples
Plots
End of explanation
"""


interactive_plot = interactive(plot, num=(10., 25., 0.1))
output = interactive_plot.children[-1]
output.layout.height = '350px'
interactive_plot

"""
Explanation: Flickering and jumping output
"On occasion, you may notice interact output flickering and jumping, causing the notebook scroll position to change as the output is updated. The interactive control has a layout, so we can set its height to an appropriate value (currently chosen manually) so that it will not change size as it is updated."
https://ipywidgets.readthedocs.io/en/latest/examples/Using%20Interact.html#Flickering-and-jumping-output
End of explanation
"""


from matplotlib.ticker import FuncFormatter

X, Y = np.random.normal(size=(2, 100000))

def plot(num, name):
    fig = plt.figure(figsize=(8.0, 8.0))
    ax = fig.add_subplot(111)
    
    x = np.log10(X) if name in ("xlog", "loglog") else X
    y = np.log10(Y) if name in ("ylog", "loglog") else Y

    im = ax.hexbin(x, y, gridsize=num)
    fig.colorbar(im, ax=ax)

    # Use "10^n" instead "n" as ticks label
    func_formatter = lambda x, pos: r'$10^{{{}}}$'.format(int(x))
    ax.xaxis.set_major_formatter(FuncFormatter(func_formatter))
    ax.yaxis.set_major_formatter(FuncFormatter(func_formatter))

    ax.set_title(name)

interact(plot, num=(10, 60), name=["linear", "xlog", "ylog", "loglog"]);

"""
Explanation: Dataviz
End of explanation
"""


import scipy.optimize

xmin = [-4., -5.]
xmax = [4., 10.]
f = scipy.optimize.rosen

def plot(cl1, cl2):
    
    # Setup
    
    x1_space = np.linspace(xmin[0], xmax[0], 100)
    x2_space = np.linspace(xmin[1], xmax[1], 100)

    x1_mesh, x2_mesh = np.meshgrid(x1_space, x2_space)

    z = [f([x, y]) for x, y in zip(x1_mesh.ravel(), x2_mesh.ravel())]

    zz = np.array(z).reshape(x1_mesh.shape)

    # Plot
    
    fig, ax = plt.subplots(figsize=(15, 7))

    im = ax.pcolormesh(x1_mesh, x2_mesh, zz,
                       shading='gouraud',
                       norm=matplotlib.colors.LogNorm(), # TODO
                       cmap='gnuplot2') # 'jet' # 'gnuplot2'

    plt.colorbar(im, ax=ax)

    levels = (cl1, cl2)          # TODO

    cs = plt.contour(x1_mesh, x2_mesh, zz, levels,
                     linewidths=(2, 3),
                     linestyles=('dashed', 'solid'),  # 'dotted', '-.', 
                     alpha=0.5,
                     colors='red')

    ax.clabel(cs, inline=False, fontsize=12)

#interact(plot, cl1=(0.1, 10., 0.1), cl2=(0.1, 100., 0.1));
interactive_plot = interactive(plot, cl1=(1., 99., 0.1), cl2=(100., 10000., 0.1))
output = interactive_plot.children[-1]
output.layout.height = '500px'
interactive_plot

"""
Explanation: Contour line
End of explanation
"""


import statsmodels.api as sm

# https://www.statsmodels.org/devel/datasets/index.html
data = sm.datasets.elnino.load_pandas()
df = data.data
df.index = df.YEAR
df = df.drop(['YEAR'], axis=1)

def plot(year):
    ax = df.loc[year,:].plot()
    ax.set_ylim(15, 30)
    ax.set_title("El Nino - Sea Surface Temperatures")

interact(plot, year=(1950, 2010));

"""
Explanation: Time series exploration
End of explanation
"""


# Activation functions ########################################################

def identity(x):
    return x

def tanh(x):
    return np.tanh(x)

def relu(x):
    x_and_zeros = np.array([x, np.zeros(x.shape)])
    return np.max(x_and_zeros, axis=0)

# Dense Multi-Layer Neural Network ############################################

IN_SIZE = 2
OUT_SIZE = 1
H_SIZE = 4   # H_SIZE = number of neurons on the hidden layer

# Set the neural network activation functions (one function per layer)
activation_functions = (relu, tanh)

# Make a neural network with 2 hidden layers of `H_SIZE` units
weights = (np.random.random(size=[IN_SIZE + 1, H_SIZE]),
           np.random.random(size=[H_SIZE + 1, OUT_SIZE]))

def feed_forward(inputs, weights, activation_functions, verbose=False):

    x = inputs.copy()
    for layer_weights, layer_activation_fn in zip(weights, activation_functions):

        y = np.dot(x, layer_weights[1:])
        y += layer_weights[0]
        
        layer_output = layer_activation_fn(y)

        if verbose:
            print("x", x)
            print("bias", layer_weights[0])
            print("W", layer_weights[1:])
            print("y", y)
            print("z", layer_output)

        x = layer_output

    return layer_output

xmin, xmax = -10., 10.
res = 100

def plot(w11, w12, w13, w14, w21, w22, w23, w24, w31, w32, w33, w34, wo1, wo2, wo3, wo4, wo5):

    weights = (np.array([[w11, w12, w13, w14],
                         [w21, w22, w23, w24],
                         [w31, w32, w33, w34]]),
               np.array([[wo1],
                         [wo2],
                         [wo3],
                         [wo4],
                         [wo5]]))
    
    x1_space = np.linspace(xmin, xmax, res)
    x2_space = np.linspace(xmin, xmax, res)

    x1_mesh, x2_mesh = np.meshgrid(x1_space, x2_space)

    z = [feed_forward(inputs=[x, y],
                      weights=weights,
                      activation_functions=activation_functions) for x, y in zip(x1_mesh.ravel(), x2_mesh.ravel())]

    zz = np.array(z).reshape(x1_mesh.shape)
    
    fig, ax = plt.subplots(figsize=(8, 8))

    im = ax.pcolormesh(x1_mesh, x2_mesh, zz,
                       shading='gouraud',
                       #norm=matplotlib.colors.LogNorm(), # TODO
                       cmap='magma') # 'jet' # 'gnuplot2'

    plt.colorbar(im, ax=ax)

from ipywidgets import GridspecLayout, FloatSlider, Layout
import random

grid = GridspecLayout(5, 4)

for i in range(5):
    for j in range(4):
        grid[i, j] = FloatSlider(value=random.random(), min=-5., max=5., step=0.1, description="w{}{}".format(i, j), layout=Layout(width='75%'))

out = ipywidgets.interactive_output(plot, {'w11': grid[0, 0], 'w12': grid[1, 0], 'w13': grid[2, 0], 'w14': grid[3, 0],
                                           'w21': grid[0, 1], 'w22': grid[1, 1], 'w23': grid[2, 1], 'w24': grid[3, 1],
                                           'w31': grid[0, 2], 'w32': grid[1, 2], 'w33': grid[2, 2], 'w34': grid[3, 2],
                                           'wo1': grid[0, 3], 'wo2': grid[1, 3], 'wo3': grid[2, 3], 'wo4': grid[3, 3], 'wo5': grid[4, 3]})

#display(grid, out)
ipywidgets.VBox([grid, out])

"""
Explanation: Understand algorithms: neural networks
End of explanation
"""


