Visualización Interactiva

En esta clase buscamos agregar valor a las visualizaciones con interactividad.

Jupyter Widgets

Ya sabemos que no hay mejor manera de describir algo que la forma con que los autores lo hacen, en el respositorio del proyecto nos cuentan lo siguiente:

  • ipywidgets are interactive HTML widgets for Jupyter notebooks and the IPython kernel.

  • Notebooks come alive when interactive widgets are used. Users gain control of their data and can visualize changes in the data.

  • Learning becomes an immersive, fun experience. Researchers can easily see how changing inputs to a model impact the results. We hope you will add ipywidgets to your notebooks, and we’re here to help you get started.

Toda la documentación la puedes encontrar en el siguiente link. En resumen, los widgets son mini-herramientas que brindan interactividad. En esta clase los utilizaremos para visualizaciones, con tal de entregar mayour control y facilitar la exploración.

Para motivar el uso de estos, se hará uso del Atractor de Lorenz.

˙x=σ(yx)˙y=ρxyxz˙z=βz+xy
from ipywidgets import interactive, fixed
import ipywidgets as widgets
from lorenz import solve_lorenz

%matplotlib inline
Copy to clipboard
w=interactive(solve_lorenz,sigma=(0.0,50.0),rho=(0.0,50.0))
w
Copy to clipboard
10.00
2.67
28.00

Para el conjunto de parámetros por defecto observamos trayectorias girando alrededor de dos puntos, llamados atractores.

El objeto devuelto por interactive es de tipo Widget y posee atributos que contienen el resultado actual y los argumentos.

t, x_t = w.result
Copy to clipboard
w.kwargs  ## Cambia el valor de algún widget de w y vuelve a ejecutar esta celda, verás que el valor cambió.
Copy to clipboard
{'sigma': 10.0, 'beta': 2.6666666666666665, 'rho': 28.0}
Copy to clipboard

Instalación

En la documentación oficial se encuentra detallada la forma de instalar ipywidgets.

Para efectos del curso, se instalará en el mismo ambiente virtual que se ha utilizado a lo largo del semestre. Las instrucciones son:

  1. En la terminal correspondiente activa el entorno virtual del curso, es decir, ejecuta conda activate mat281.

  2. Ejecutar en la terminal conda install -c conda-forge ipywidgets, recuerda que mat281 es el nombre del ambiente virtual.

  3. Si has seguido todas las instrucciones ya deberías tener instalado nodejs, puedes verificarlo ejecutando conda list nodejs, ahí debería aparecer la versión instalada.

  4. En la misma terminal (con el ambiente activado, no lo olvides!) ejecuta jupyter labextension install @jupyter-widgets/jupyterlab-manager

from ipywidgets import interact, interactive, fixed, interact_manual
Copy to clipboard

Interact

La función interact crea automáticamente una interfaz de usuario (UI) de control para explorar código y datos interactivatemente.

def f(x):
    return x
Copy to clipboard

interact genera automáticamente la interfaz dde control y luego llama a la función utilizando esos parámetros como argumentos para la función.

interact(f, x=10);
Copy to clipboard
10

Si el argumento es booleano crea una checkbox en lugar de un slicer.

interact(f, x=True);
Copy to clipboard

Si se le entrega un string interact genera un text box.

interact(f, x='Hi there!');
Copy to clipboard

Otra manera es utilizar interact como un decorator. Esto permite definir una función e interactuar con ella en un solo paso.

Los decoradores se escapan de los contenidos del curso, pero básicamente son funciones tienen como argumento una función y extienden el comportamiento de esta última sin modificarla explícitamente.

@interact(x=True, y=1.0)
def g(x, y):
    return (x, y)
Copy to clipboard
1.00

Interactive

Además de ìnteract, IPython proporciona otra función, interactive, que es útil cuando desea reutilizar los widgets que se producen o acceder a los datos vinculados a los controles de la interfaz de usuario.

from IPython.display import display
Copy to clipboard
def f(a, b):
    output = a + b
    display(output)
    return output
Copy to clipboard
w = interactive(f, a=10, b=20)
Copy to clipboard
type(w)
Copy to clipboard
ipywidgets.widgets.interaction.interactive
Copy to clipboard
w.children  # The children of the interactive are two integer-valued sliders and an output widget
Copy to clipboard
(IntSlider(value=10, description='a', max=30, min=-10),
 IntSlider(value=20, description='b', max=60, min=-20),
 Output())
Copy to clipboard

Para ver el widget, basta con utilizar la función display de IPython.

display(w)
Copy to clipboard
10
20

Finalmente, un ejemplo con un gráfico:

import matplotlib.pyplot as plt
import numpy as np

def f(m, b):
    plt.figure(2)
    x = np.linspace(-10, 10, num=1000)
    plt.plot(x, m * x + b)
    plt.ylim(-5, 5)
    plt.show()

interactive_plot = interactive(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))
Copy to clipboard
interactive_plot
Copy to clipboard
0.00
0.00

Si quisieras por ejemplo, que la pendiente m solo sean números enteros y que el coeficiente de posición b solo pueda ser escogido entre 0 y 3 puedes explicitar el tipo de widget para cada argumento.

Más detalle en la documentación! Link

interactive(
    f,
    m=widgets.IntSlider(0, -2, 2),
    b=widgets.Dropdown(options=[0, 3])
)
Copy to clipboard
0

Altair Interactivo

import altair as alt
from vega_datasets import data
Copy to clipboard

Para que un gráfico en altair sea interactivo basta con agregar al final del chart el método .interactive(). Para agregar mayor interactividad el encoding tooltip es de mucha ayuda.

source = data.cars()

alt.Chart(source).mark_circle(size=60).encode(
    x='Horsepower',
    y='Miles_per_Gallon',
    color='Origin',
    tooltip=['Name', 'Origin', 'Horsepower', 'Miles_per_Gallon']
).interactive()
Copy to clipboard

También es posible crear selecciones entrelazadas.

source = data.cars()

brush = alt.selection(type='interval', resolve='global')

base = alt.Chart(source).mark_point().encode(
    y='Miles_per_Gallon',
    color=alt.condition(brush, 'Origin', alt.ColorValue('gray')),
).add_selection(
    brush
).properties(
    width=250,
    height=250
)

base.encode(x='Horsepower') | base.encode(x='Acceleration')
Copy to clipboard

O que la selección repercuta en otro tipo de gráfico.

source = data.cars()

brush = alt.selection(type='interval')

points = alt.Chart(source).mark_point().encode(
    x='Horsepower:Q',
    y='Miles_per_Gallon:Q',
    color=alt.condition(brush, 'Origin:N', alt.value('lightgray'))
).add_selection(
    brush
)

bars = alt.Chart(source).mark_bar().encode(
    y='Origin:N',
    color='Origin:N',
    x='count(Origin):Q'
).transform_filter(
    brush
)

points & bars
Copy to clipboard

Podríamos estar horas hablando de esto. Como siempre, más ejemplos en la galería de ejemplos, en particular la Sección de Gráficos Interactivos.

Interactive Scatter Plot and Linked Layered Histogram

import pandas as pd

# generate fake data
source = pd.DataFrame({'gender': ['M']*1000 + ['F']*1000,
               'height':np.concatenate((np.random.normal(69, 7, 1000),
                                       np.random.normal(64, 6, 1000))),
               'weight': np.concatenate((np.random.normal(195.8, 144, 1000),
                                        np.random.normal(167, 100, 1000))),
               'age': np.concatenate((np.random.normal(45, 8, 1000),
                                        np.random.normal(51, 6, 1000)))
        })

selector = alt.selection_single(empty='all', fields=['gender'])

color_scale = alt.Scale(domain=['M', 'F'],
                        range=['#1FC3AA', '#8624F5'])

base = alt.Chart(source).properties(
    width=250,
    height=250
).add_selection(selector)

points = base.mark_point(filled=True, size=200).encode(
    x=alt.X('mean(height):Q',
            scale=alt.Scale(domain=[0,84])),
    y=alt.Y('mean(weight):Q',
            scale=alt.Scale(domain=[0,250])),
    color=alt.condition(selector,
                        'gender:N',
                        alt.value('lightgray'),
                        scale=color_scale),
)

hists = base.mark_bar(opacity=0.5, thickness=100).encode(
    x=alt.X('age',
            bin=alt.Bin(step=5), # step keeps bin size the same
            scale=alt.Scale(domain=[0,100])),
    y=alt.Y('count()',
            stack=None,
            scale=alt.Scale(domain=[0,350])),
    color=alt.Color('gender:N',
                    scale=color_scale)
).transform_filter(
    selector
)


points | hists
Copy to clipboard

Multi-Line Tooltip

np.random.seed(42)
source = pd.DataFrame(np.cumsum(np.random.randn(100, 3), 0).round(2),
                    columns=['A', 'B', 'C'], index=pd.RangeIndex(100, name='x'))
source = source.reset_index().melt('x', var_name='category', value_name='y')

# Create a selection that chooses the nearest point & selects based on x-value
nearest = alt.selection(type='single', nearest=True, on='mouseover',
                        fields=['x'], empty='none')

# The basic line
line = alt.Chart(source).mark_line(interpolate='basis').encode(
    x='x:Q',
    y='y:Q',
    color='category:N'
)

# Transparent selectors across the chart. This is what tells us
# the x-value of the cursor
selectors = alt.Chart(source).mark_point().encode(
    x='x:Q',
    opacity=alt.value(0),
).add_selection(
    nearest
)

# Draw points on the line, and highlight based on selection
points = line.mark_point().encode(
    opacity=alt.condition(nearest, alt.value(1), alt.value(0))
)

# Draw text labels near the points, and highlight based on selection
text = line.mark_text(align='left', dx=5, dy=-5).encode(
    text=alt.condition(nearest, 'y:Q', alt.value(' '))
)

# Draw a rule at the location of the selection
rules = alt.Chart(source).mark_rule(color='gray').encode(
    x='x:Q',
).transform_filter(
    nearest
)

# Put the five layers into a chart and bind the data
alt.layer(
    line, selectors, points, rules, text
).properties(
    width=600, height=300
)
Copy to clipboard