Alberto García Robledo
Visualización de Datos Aplicada
Junio 2024
Dash es código abierto, publicado bajo la licencia MIT.
Está disponible para múltiples lenguajes:
Python
R
Julia
.NET
En una terminal, instala Dash a través de pip:
pip install dash
Si prefieres desarrollar en Jupyter Notebook o Jupyter Lab, instala jupyter-dash:
pip install jupyter-dash
En este taller utilizaremos Google Colab
Una aplicación Dash está integrada de dos partes:
La descripción del UI (layout)
La descripción de la interactividad (callbacks)
Un layout de Dash está integrado de uno o más componentes Dash anidados. Los hay de dos tipos:
import dash_html_components as html
from jupyter_dash import JupyterDash
app = JupyterDash(__name__)
app.layout = html.Div([
html.H1('H1 header!'),
html.H2('H2 header!'),
html.H3('H3 header!'),
html.H4('H4 header!'),
html.H5('H5 header!'),
html.H6('H6 header!'),
])
app.run_server()
dash_html_components provee un componente Dash por cada etiqueta HTML.
Un layout está compuesto por Dash components anidados:
Un layout de Dash es parecido a un árbol DOM.
El componente raíz de un layout es asignado a app.layout.
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
app = JupyterDash(__name__)
markdown = '''
# Lorem Ipsum
**Lorem ipsum dolor sit amet**, consectetur adipiscing elit.
1. Duis ullamcorper nisl eget diam laoreet, ac rutrum enim aliquet.
2. Curabitur aliquam libero et leo consectetur, at pretium urna mollis.
3. Cras et vestibulum nisi, sit amet sollicitudin turpis.
***Nam imperdiet porttitor venenatis***. Aenean vitae maximus enim, ac consectetur augue.
## Vestibulum nec felis ac erat auctor pharetra.
> Vestibulum vitae velit at nisl pretium ultricies.
>> Suspendisse mi urna, lobortis sed ante sit amet, porta venenatis ligula.
Text generated by the [Lorem Ipsum generator](https://www.lipsum.com/).
'''
app.layout = html.Div([
dcc.Markdown(children=markdown)
])
app.run_server()
Puede ser tedioso construir un layout utilizando únicamente los dash_html_components.
Para desplegar bloques de texto con formato de manera rápida, puedes usar el componente Markdown del dash_core_components.
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
df = px.data.iris() # iris is a pandas DataFrame
app = JupyterDash(__name__)
fig = px.scatter(df, x='petal_length', y='sepal_length')
app.layout = html.Div([
dcc.Graph(
figure=fig,
style={'height': 300},
)
])
app.run_server()
Puedes utilizar DataFrames de Pandas como la fuente de datos de tus tableros Dash.
Dash provee Plotly Express, el cual contiene funciones para crear gráficas ricas y complejas de manera fácil.
El componente dcc.Graph puede visualizar figuras generadas por Plotly y Plotly Express:
Un callback es una función de Python que permite definir la interacción de un tablero Dash.
Cada componente Dash puede tener un id único
Un callback contiene uno o más Input y Output:
Cada entrada/salida está asociada al id de un componente y a un atributo del componente.
import dash_html_components as html
import dash_core_components as dcc
from jupyter_dash import JupyterDash
from dash.dependencies import Input, Output
app = JupyterDash(__name__)
app.layout = html.Div([
html.Div(['Text:',
dcc.Input(id='text-input',
value='',
type='text')]),
html.Div(id='echo-div')
])
@app.callback(
Output('echo-div', 'children'),
Input('text-input', 'value')
)
def update_echo_div_callback(text_value):
if text_value:
return f'Echo: {text_value}'
app.run_server()
Es necesario asignar un id único a un componente si deseamos hacer referencia a éste en un callback.
Un Output dado sólo puede aparecer en un callback.
Pero un Input dado puede aparecer en más de un callback.
Los callbacks son disparados de manera automática al iniciar la aplicación.
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = JupyterDash(__name__)
app.layout = html.Div([
dcc.Input(
id='x',
type='number',
value=1
),
dcc.Input(
id='c',
type='number',
value=0
),
html.Table([
html.Tr(html.Td(id='y-1-td')),
html.Tr(html.Td(id='y-2-td')),
html.Tr(html.Td(id='y-3-td')),
html.Tr(html.Td(id='y-4-td')),
html.Tr(html.Td(id='y-5-td'))
])
])
@app.callback(
Output('y-1-td', 'children'),
Output('y-2-td', 'children'),
Output('y-3-td', 'children'),
Output('y-4-td', 'children'),
Output('y-5-td', 'children'),
Input('x', 'value'),
Input('c', 'value'))
def compute_callback(x, c):
if (not x is None) and (not c is None):
return x*1+c, x*2+c, x*3+c, x*4+c, x*5+c
app.run_server(debug=True)
Un callback puede tener múltiples Inputs y Outputs
Tienes que asegurarte que todos los Inputs tengan valores válidos:
Puedes pasar prevent_initial_call = True al decorador de un callback para evitar que el callback se dispare automáticamente
Diseñar callbacks puede ser complicado en un principio ¡No te desanimes!
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
app = JupyterDash(__name__)
app.layout = html.Div([
dcc.Input(
id='name-input',
type='text',
value=''
),
dcc.Input(
id='lastname-input',
type='text',
value=''
),
dcc.Input(
id='age-input',
type='number',
value=18
),
html.Button(id='submit-button', children='Submit'),
html.Table([
html.Tr(html.Td(id='name-td')),
html.Tr(html.Td(id='lastname-td')),
html.Tr(html.Td(id='age-td'))
])
])
@app.callback(
Output('name-td', 'children'),
Output('lastname-td', 'children'),
Output('age-td', 'children'),
Input('submit-button', 'n_clicks'),
State('name-input', 'value'),
State('lastname-input', 'value'),
State('age-input', 'value'))
def compute_callback(n_clicks, name_input, lastname_input, age_input):
if n_clicks and name_input and lastname_input and age_input:
return name_input, lastname_input, age_input
else:
return '', '', ''
app.run_server()
Los "States" nos permiten recolectar valores de componentes sin tener que disparar un callback
Nos permiten pasar valores a un callback adicionales al valor disparador
No olvides proveer valores por defecto a todas las salidas de un callback para el disparo de inicialización
Dash provee herramientas para que puedas desarrollar tus propios componentes
Básicamente, un componente Dash no es más que un envoltorio de un componente React.js
Dash goza de una nutrida comunidad de desarrolladores que han creado sus propios componentes Dash
Provee varias extensiones útiles al framework Dash:
Aplicaciones Dash de múltiples páginas
Grupos de Outputs para permitir que un Output pueda ser utilizado en múltiples callbacks
Outputs del lado del servidor
Provee componentes de UI basados en el framework Bootstrap 4:
Modal
Cards
Form
Jumbotron
Navbar
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd
df = pd.read_csv(
'https://raw.githubusercontent.com/jasonchang0/kaggle-google-apps/master/google-play-store-apps/googleplaystore.csv', delimiter=',')
app = JupyterDash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
navbar = dbc.NavbarSimple(
brand="Google Play Top",
brand_href="#",
color="primary",
dark=True,
)
controls = dbc.Card(
[
dbc.FormGroup(
[
dbc.Label('Category'),
dcc.Dropdown(
id='category-dropdown',
options=[
{'label': col, 'value': col} for col in df['Category'].unique()
],
value='ART_AND_DESIGN',
),
]
),
dbc.FormGroup(
[
dbc.Label('Top'),
dcc.Dropdown(
id='top-dropdown',
options=[
{'label': 5, 'value': 5},
{'label': 10, 'value': 10},
{'label': 20, 'value': 20},
{'label': 30, 'value': 30},
{'label': 40, 'value': 40},
{'label': 50, 'value': 50},
],
value=5,
),
]
)
],
body=True
)
app.layout = dbc.Container(
[
navbar,
dbc.Row(
[
dbc.Col(controls, md=4),
dbc.Col(dcc.Graph(id='top-graph'), md=8),
],
align='center'
),
]
)
@app.callback(
Output('top-graph', 'figure'),
Input('category-dropdown', 'value'),
Input('top-dropdown', 'value')
)
def update_callback(category, top):
df2 = df[df['Category'] == category]
df2 = df2.sort_values(by='Rating', ascending=False).head(top)
fig = px.bar(df2, x='App', y='Rating')
return fig
app.run_server()
Puedes utilizar archivos CSV como fuente de datos de tus tableros Dash utilizando Pandas.
Puedes utilizar Pandas para manipular el dataset dentro de un callback (e.g. filtrarlo u ordenarlo).
La biblioteca Dash Bootstrap Components permite la utilización de componentes de Bootstrap 4 en nuestras aplicaciones Dash.
Elige un dataset de kaggle.com
Desarrolla una aplicación de Dash que utilize un Plotly Express scatter plot para visualizar las potenciales correlaciones entre cualesquiera dos variables numéricas
La aplicación de Dash debe utilizar la biblioteca Dash Bootstrap Components para la UI: dos columnas y un navbar
Paso 0. Haz una copia del siguiente notebook:
Paso 1. Asigna a la variable df el dataset Iris provisto por Plotly Express:
df = px.data.iris() # iris is a pandas DataFrame
Paso 2. Define un Dropdown que tenga como options diferentes columnas del dataset. Asígnale el id xaxis-dropdown:
dcc.Dropdown(
id='xaxis-dropdown',
options=[
{'label': 'sepal_width', 'value': 'sepal_width'},
{'label': 'sepal_length', 'value': 'sepal_length'},
{'label': 'petal_length', 'value': 'petal_length'}
],
value='sepal_width',
)
Paso 3. Define otro Dropdown que tenga como options diferentes columnas del dataset. Asígnale el id yaxis-dropdown:
dcc.Dropdown(
id='yaxis-dropdown',
options=[
{'label': 'sepal_width', 'value': 'sepal_width'},
{'label': 'sepal_length', 'value': 'sepal_length'},
{'label': 'petal_length', 'value': 'petal_length'}
],
value='sepal_width',
)
Paso 4. un objeto dcc.Graph en el layout en donde se ubicará el scatterplot. Asígnale el id top-graph:
dbc.Col(dcc.Graph(id='top-graph'), md=8),
Paso 5. Define un callback que tenga como inputs el valor de xaxis-dropdown y yaxis-dropdown y como output la figura de top-graph:
@app.callback(
Output('top-graph', 'figure'),
Input('xaxis-dropdown', 'value'),
Input('yaxis-dropdown', 'value'),
)
def update_callback(xaxis_col, yaxis_col):
fig = px.scatter(df, x=xaxis_col, y=yaxis_col)
return fig