Introducción a Dash#

Introducción#

  • Descargado 600.000 veces al mes, Dash es el marco original de bajo código para construir rápidamente aplicaciones de datos en Python, R, Julia y F#.

  • Escrito sobre Plotly.js y React.js, Dash es ideal para construir y desplegar aplicaciones de datos con interfaces de usuario personalizadas en Python, R, Julia o F#. Es particularmente adecuado para cualquiera que trabaje con datos.

  • A través de un par de patrones simples, Dash abstrae todas las tecnologías y protocolos que se requieren para construir una aplicación web full-stack con visualización de datos interactivos.

  • Dash es lo suficientemente sencillo como para que puedas vincular una interfaz de usuario a tu código Python, R, Julia o F# en menos de 10 minutos.

  • Las aplicaciones de Dash se muestran en el navegador web. Puedes desplegar tus aplicaciones en máquinas virtuales o clusters Kubernetes y luego compartirlas a través de URLs. Dado que las aplicaciones de Dash se ven en el navegador web, Dash es intrínsecamente multiplataforma y está preparado para los dispositivos móviles.

  • Hay mucho detrás del framework. Para saber más sobre cómo está construido y qué motivó a Dash, lea el post Dash es React para Python. Puede encontrar distintos templates de Dash en el siguiente link, los cuales puedes utlizar para tus proyectos: Dash Enterprise App Gallery

  • Dash es una librería de código abierto liberada bajo la permisiva licencia MIT. Plotly desarrolla Dash y también ofrece una plataforma para escribir y desplegar aplicaciones Dash en un entorno empresarial.

_images/dash1.png

Configuración de su entorno#

  • Para la instalación de Dash crearemos un entorno especial, en el cual instalaremos cada una de las librerías que necesitaremos en el transcurso de esta sección, en especial la instalación de Dash mediante la orden

pip install dash
  • En su esencia, el propósito principal de los entornos virtuales (virtual enviroment) de Python es crear un espacio aislado para cada proyecto particular de Python. Esto significa que, cada proyecto puede tener sus propias dependencias, independientemente de las dependencias que tengan los demás proyectos.

  • Lo bueno de esto es que no hay límites en el número de entornos que puedes tener, ya que sólo son directorios que contienen unos pocos scripts. Además, son fácilmente creados usando las herramientas de línea de comandos virtualenv o pyenv, en el caso de anaconda por medio de conda create

  • En el presente curso, para facilitar la creación de un entorno de desarrollo para TensorFlow, se recomienda utilizar Miniconda/Anaconda. El sitio web oficial de TensorFlow directamente realiza la recomendación que se hace en el presente curso. Además de utilizar la versión 3.9 de Python.

_images/python_version_tf.png
  • Para crear el entorno virtual usando Miniconda, puede utilizar la siguiente orden

conda create --name dash_project python=3.9
_images/dash_project_venv.png
  • Para activar y/o desactivar el entorno virtual de Miniconda creado, basta con utilizar las siguientes ordenes. Nótese que no es necesario usar el nombre del entorno virtual para desactivarlo

conda activate dash_project
conda deactivate

Usando Python. No Miniconda#

  • Solo en el caso que no desee utilizar Miniconda. Para crear un entorno virtual usando solo Python, debe crear una carpeta llamada por ejemplo, python_enviroments (o con cualquier otro nombre) en su disco local C para evitar problemas de sincronización, la cual va a contener todos sus entornos virtuales.

  • Escriba la siguiente orden un su terminal, en el caso de Windows, en el Command Prompt o en el Windows PowerShell que tiene un aspecto muy parecido al de Ubuntu. Puede elegir la carpeta donde van a reposar todos sus enviroments, en este caso python_enviroments, y el entorno virtual creado recibe el nombre dash_project a manera de ejemplo

_images/dash2.png
  • Luego debe ubicarse dentro de la carpeta que se creó con el enviroment: dash_project usando el comando cd, y dentro de esta ubicación debe ejecutar la siguiente orden para activar el enviroment:

.\Scripts\activate
  • Tal como se muestra en la figura. Nótese que en la parte izquierda de la línea de comandos, aparece el nombre del enviroment en color verde, indicando que ha sido creado correctamente y está activado. Luego de activado el enviroment se puede mover al directorio donde desea realizar el desarrollo de esta sección. Procedemos ahora a instalar algunas librerías, el procedimiento es el mismo cuando usa Miniconda o Python puro.

_images/dash3.png

Instalación de Librerías#

  • Independientemente de si decidió usar solo Python directamente, o Miniconda como recomienda TensorFlow, el procedimiento de instalación de librerías es el mismo. En su terminal, instale dash.

pip install dash
  • Esto también trae consigo la biblioteca de gráficos plotly. Esta biblioteca está en desarrollo activo, así que instala y actualiza con frecuencia. Si prefieres Jupyter Notebook o JupyterLab como entorno de desarrollo, te recomendamos instalar jupyter-dash:

pip install jupyter-dash
  • También recomendamos instalar Pandas, que es requerido por Plotly Express y utilizado en muchos de nuestros ejemplos.

pip install pandas
  • Estamos listos para crear nuestra primera aplicación Dash. Cada una de estas librerías pueden ser instaladas también a traves de un archivo con requerimientos, el cual podemos crear con el nombre: requirements.txt y copiar en éste sin espacio a la izquierda el nombre de cada librería, así:

dash
jupyter-dash
pandas
  • Luego desde la línea de comando ejecutar

pip install -r requirements.txt
  • Estaremos usando en esta sección JupyterLab debido a que es un editor gratuito, que proporciona una interfaz basada en navegador que le permite utilizar varios cuadernos juntos de forma eficaz, así como también usar Markdown. También estaremos usando el edito VS Code, el cual ofrece soporte para una amplia variedad de lenguajes de programación y marcos de trabajo, lo que lo hace ideal para proyectos multiplataforma, esto es, puedes usarlo en Windows, Mac y Linux. Seleccione aquel editor con el que se sienta más cómodo. Para instalar jupyter lab escriba dentro del mismo enviroment

pip install jupyterlab
_images/dash4.png
  • Iniciamos jupyter lab para crear el archivo requirements.txt con las librerías a instalar. Este archivo podemos ir actualizándolo en el transcurso de la sección a medida que una nueva librería va siendo necesaria.

_images/dash5.png
_images/dash6.png
  • Crea el archivo requirements.txt con librerías a instalar. Nótese que jupyter lab cuenta también con una terminal integrada, por lo tanto, luego de crear el archivo, puede abrir una terminal dentro de jupyter lab, activar el enviroment y luego instalar las librerías usando pip install -r requirements.txt

_images/dash7.png

Diseño de Dash#

  • Estudiaremos el diseño a través de seis aplicaciones autónomas. El diseño de las aplicaciones Dash de producción puede ser estilizado con Dash Enterprise Design Kit. Las aplicaciones Dash se componen de dos partes. La primera parte es el diseño de la aplicación y describe el aspecto de la misma. La segunda parte describe la interactividad de la aplicación y será cubierta más adelante.

  • Mantenemos un conjunto de componentes en la biblioteca dash_core_components y dash_html_components (dash.html a partir de Dash v2.0) pero también puedes construir los suyos con JavaScript y React.js. A lo largo de esta sección, los ejemplos de código Python están pensados para ser guardados como archivos y ejecutados usando python app.py. También puedes usar Jupyter con la biblioteca JupyterDash.

  • Para empezar, crea un archivo llamado app.py usando su editor favorito, copie el código de abajo en él y ejecútalo con

python app.py
import dash
from dash import dcc
from dash import html
import plotly.express as px
import pandas as pd

app = dash.Dash(__name__)

df = pd.DataFrame({
    "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
    "Amount": [4, 1, 2, 2, 4, 5],
    "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
})

fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group")

app.layout = html.Div(children=[
    html.H1(children='Hello Dash'),

    html.Div(children='''
        Dash: A web application framework for your data.
    '''),

    dcc.Graph(
        id='example-graph',
        figure=fig
    )
])

if __name__ == '__main__':
    app.run_server(debug=True)
_images/dash8.png
_images/dash9.png
_images/dash10.png
_images/dash11.png
  • Para tener otro estilo de visualización puede diseñar su propio archivo style.css por ejemplo, o utilizar algunos libres que puede solo importar, como por ejemplo BootstrapCDN y Plotly themes. Para importar los estilos de temas en tu app debe importar la librería en el archivo .py principal de tu app: import dash_bootstrap_components as dbc, además de usar la siguiente línea para seleccionar el tema de interés

app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
  • Antes debe instalarlo en el enviroment usando: pip install dash-bootstrap-components

_images/dash12.png
_images/dash13.png

Observaciones

  • El diseño está compuesto por un árbol de “componentes” como html.Div y dcc.Graph.

  • La biblioteca dash_html_components (dash.html a partir de Dash v2.0) tiene un componente para cada etiqueta HTML. El componente html.H1(children = 'Hola Dash') genera un elemento HTML <h1>Hola Dash</h1> en tu aplicación. h1 en HTML es el primer nivel de encabezado en una página web.

  • No todos los componentes son HTML puro. Los dash_core_components describen componentes de nivel superior que son interactivos y se generan con JavaScript, HTML y CSS a través de la biblioteca React.js.

  • Cada componente se describe completamente a través de atributos de palabras clave. Dash es declarativo: usted describirá principalmente su aplicación a través de estos atributos.

  • La propiedad children es especial. Por convención, siempre es el primer atributo, lo que significa que puedes omitirlo: html.H1(children = 'Hello Dash') es lo mismo que html.H1('Hello Dash'). Puede contener una cadena, un número, un solo componente o una lista de componentes.

  • Las fuentes en su aplicación se verán un poco diferentes de lo que se muestra en la imágen de arriba a menos que cambie el estilo de la hoja y de Plotly. Esta aplicación está utilizando una hoja de estilos BootstrapCDN y temas Plotly

Realizar tu primer cambio

  • Dash incluye “hot-reloading”, esta característica se activa por defecto cuando ejecutas tu aplicación con app.run_server(debug=True). Esto significa que Dash refrescará automáticamente tu navegador cuando hagas un cambio en tu código. Pruébalo: cambia el título “Hello Dash” en tu aplicación o cambia los datos \(x\) o \(y\). Tu aplicación debería refrescarse automáticamente con tu cambio.

Más información sobre los componentes HTML#

  • dash_html_components (dash.html a partir de Dash v2.0) contiene una clase de componente para cada etiqueta HTML, así como argumentos de palabras clave para todos los argumentos HTML. Vamos a personalizar el texto de nuestra aplicación modificando los estilos en línea de los componentes. Crea un archivo llamado app.py con el siguiente código

import dash
from dash import dcc
from dash import html
import plotly.express as px
import pandas as pd

app = dash.Dash(__name__)

colors = {
    'background': '#111111',
    'text': '#7FDBFF'
}

df = pd.DataFrame({
    "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
    "Amount": [4, 1, 2, 2, 4, 5],
    "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
})

fig = px.bar(df, x = "Fruit", y = "Amount", color = "City", barmode = "group")

fig.update_layout(
    plot_bgcolor = colors['background'],
    paper_bgcolor = colors['background'],
    font_color = colors['text']
)

app.layout = html.Div(style={'backgroundColor': colors['background']}, children=[
    html.H1(
        children = 'Hello Dash',
        style = {
            'textAlign': 'center',
            'color': colors['text']
        }
    ),
    
    html.Div(children = 'Dash: A web application framework for your data.', style = {
        'textAlign': 'center',
        'color': colors['text']
    }),
    
    dcc.Graph(
        id='example-graph-2',
        figure=fig
    )
])

if __name__ == '__main__':
    app.run_server(debug=True)
_images/dash14.png
  • En este ejemplo, hemos modificado los estilos inline de los componentes html.Div y html.H1 con la propiedad style.

html.H1('Hello Dash', style={'textAlign': 'center', 'color': '#7FDBFF'})
  • El código anterior se muestra en la aplicación Dash como

<h1 style="text-align: center; color: #7FDBFF">Hola Dash</h1>.
  • Hay algunas diferencias importantes entre los componentes dash_html_ (dash.html a partir de Dash v2.0) y los atributos HTML:

    • La propiedad de estilo en HTML es una cadena separada por punto y coma. En Dash, sólo se puede proporcionar un diccionario.

    • Las claves (keys) en el diccionario de estilo son camelCased. Así, en lugar de text-align, es textAlign. El atributo de clase HTML es className en Dash.

    • Los hijos (children) de la etiqueta HTML se especifican a través del argumento de la palabra clave children. Por convención, éste es siempre el primer argumento, por lo que a menudo se omite.

    • Además de esto, todos los atributos y etiquetas HTML disponibles están a tu disposición dentro del contexto de Python.

Componentes reutilizables#

  • Escribiendo nuestro markup en Python, podemos crear componentes complejos reutilizables como tablas sin cambiar de contexto o de lenguaje. Aquí hay un ejemplo rápido que genera una tabla Table a partir de un dataframe de Pandas (ver html.Tr, html.Td, html.Thead ). Crea un archivo llamado app.py con el siguiente código

import dash
from dash import html
import pandas as pd

df = pd.read_csv('https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/usa-agricultural-exports-2011.csv')

def generate_table(dataframe, max_rows=10):
    return html.Table([
        html.Thead(
            html.Tr([html.Th(col) for col in dataframe.columns])
        ),
        html.Tbody([
            html.Tr([
                html.Td(dataframe.iloc[i][col]) for col in dataframe.columns
            ]) for i in range(min(len(dataframe), max_rows))
        ])
    ])


app = dash.Dash(__name__)

app.layout = html.Div([
    html.H4(children='US Agriculture Exports (2011)'),
    generate_table(df)
])

if __name__ == '__main__':
    app.run_server(debug=True)

_images/dash15.png

Más información sobre la visualización#

  • La librería dash_core_components incluye un componente llamado Graph. Graph muestra visualizaciones de datos interactivas utilizando la librería de gráficos de código abierto plotly.js. Plotly.js admite más de 35 tipos de gráficos y los representa tanto en SVG de calidad vectorial como en WebGL de alto rendimiento.

  • El argumento de figura en el componenteGraph es el mismo argumento de figura que utiliza plotly.py, la biblioteca de gráficos Python de código abierto de Plotly. Consulte la documentación y la galería de plotly.py para obtener más información. Aquí presentamos un ejemplo que crea un gráfico de dispersión a partir de un marco de datos de Pandas. Crear un archivo llamado app.py con el siguiente código

import dash
from dash import dcc
from dash import html
import plotly.express as px
import pandas as pd

app = dash.Dash(__name__)

df = pd.read_csv('https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/gdp-life-exp-2007.csv')

fig = px.scatter(df, x="gdp per capita", y="life expectancy",
                 size="population", color="continent", hover_name="country",
                 log_x=True, size_max=60)

app.layout = html.Div([
    dcc.Graph(
        id='life-exp-vs-gdp',
        figure=fig
    )
])

if __name__ == '__main__':
    app.run_server(debug=True)
_images/dash16.png

Markdown#

  • Aunque Dash expone HTML a través de dash_html_components (dash.html a partir de Dash v2.0) , puede ser tedioso escribir tu copia en HTML. Para escribir bloques de texto, puedes utilizar el componente Markdown en dash_core_components. Crear un archivo llamado app.py con el siguiente código:

import dash
from dash import dcc
from dash import html

app = dash.Dash(__name__)

markdown_text = '''
##Dash and Markdown

- Dash apps can be written in Markdown. 
- Dash uses the [CommonMark](http://commonmark.org/) specification of Markdown. 
- Check out their [60 Second Markdown Tutorial](http://commonmark.org/help/) if this is your first introduction to Markdown!
'''

app.layout = html.Div([
    dcc.Markdown(children=markdown_text)
])

if __name__ == '__main__':
    app.run_server(debug=True)
_images/dash17.png

Componentes principales#

  • dash_core_components incluye un conjunto de componentes de alto nivel como los desplegables, los gráficos, los bloques markdown, etc.

  • Como todos los componentes de Dash, se describen de forma totalmente declarativa. Cada opción configurable está disponible como un argumento de palabra clave del componente.

  • Veremos muchos de estos componentes a lo largo de la sección. Puedes ver todos los componentes disponibles en la Galería de componentes del núcleo de Dash.

  • Aquí están algunos de los componentes disponibles. Crea un archivo llamado app.py con el siguiente código:

import dash
from dash import dcc
from dash import html

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Label('Dropdown'),
    dcc.Dropdown(
        options=[
            {'label': 'New York City', 'value': 'NYC'},
            {'label': u'Montréal', 'value': 'MTL'}, 
            {'label': 'San Francisco', 'value': 'SF'}
        ],
        value='MTL'
    ),

    html.Label('Multi-Select Dropdown'),
    dcc.Dropdown(
        options=[
            {'label': 'New York City', 'value': 'NYC'},
            {'label': u'Montréal', 'value': 'MTL'},
            {'label': 'San Francisco', 'value': 'SF'}
        ],
        value=['MTL', 'SF'],
        multi=True
    ),

    html.Label('Radio Items'),
    dcc.RadioItems(
        options=[
            {'label': 'New York City', 'value': 'NYC'},
            {'label': u'Montréal', 'value': 'MTL'},
            {'label': 'San Francisco', 'value': 'SF'}
        ],
        value='MTL'
    ),

    html.Label('Checkboxes'),
    dcc.Checklist(
        options=[
            {'label': 'New York City', 'value': 'NYC'},
            {'label': u'Montréal', 'value': 'MTL'},
            {'label': 'San Francisco', 'value': 'SF'}
        ],
        value=['MTL', 'SF']
    ),

    html.Label('Text Input'),
    dcc.Input(value='MTL', type='text'),

    html.Label('Slider'),
    dcc.Slider(
        min=0,
        max=9,
        marks={i: 'Label {}'.format(i) if i == 1 else str(i) for i in range(1, 6)},
        value=5,
    ),
], style={'columnCount': 2})

if __name__ == '__main__':
    app.run_server(debug=True)
_images/dash18.png

Resumen

  • El diseño de una aplicación Dash describe el aspecto de la aplicación. El diseño es un árbol jerárquico de componentes. La librería dash_html_components (dash.html a partir de Dash v2.0) proporciona clases para todas las etiquetas HTML y los argumentos de las palabras clave describen los atributos HTML como style, class e id. La biblioteca dash_core_components genera componentes de nivel superior como controles y gráficos.

  • Para consultar la referencia, ver:

La siguiente parte de esta seccíon cubre cómo hacer estas aplicaciones interactivas. Luego estudiaremos con más profundidad algunos Callbacks básicos

Callbacks básicos Dash#

  • Como se ha visto, app.layout describe el aspecto de la aplicación y es un árbol jerárquico de componentes. La biblioteca dash_html_components proporciona clases para todas las etiquetas HTML, y los argumentos de las palabras clave describen los atributos HTML como style, className e id. La biblioteca dash_core_components genera componentes de nivel superior como controles y gráficos.

  • Ahora se describirá cómo hacer que tus aplicaciones Dash utilicen funciones de devolución de llamada (callbacks): funciones que son llamadas automáticamente por Dash cada vez que la propiedad de un componente de entrada cambia. Comencemos con un ejemplo sencillo de una aplicación Dash interactiva.

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

app.layout = html.Div([
    html.H6("Change the value in the text box to see callbacks in action!"),
    html.Div([
        "Input: ",
        dcc.Input(id='my-input', value='initial value', type='text')
    ]),
    html.Br(),
    html.Div(id='my-output'),

])


@app.callback(
    Output(component_id='my-output', component_property='children'),
    Input(component_id='my-input', component_property='value')
)
def update_output_div(input_value):
    return 'Output: {}'.format(input_value)


if __name__ == '__main__':
    app.run_server(debug=True)
_images/initialvalue_callback.png

Observaciones

  • Las “inputs” y “outputs” de la interfaz de nuestra aplicación se describen declarativamente como argumentos del decorador @app.callback.

    a. Al escribir este decorador, le estamos diciendo a Dash que llame a esta función por nosotros cada vez que el valor del componente “input” (la caja de texto) cambie para actualizar los children del componente “output” en la página (el div HTML).

    b. Puedes usar cualquier nombre para la función que está envuelta por el decorador @app.callback. La convención es que el nombre describa la(s) salida(s) del callback.

    c. Puede utilizar cualquier nombre para los argumentos de la función, pero debe utilizar los mismos nombres dentro de la función de devolución de llamada que en su definición, al igual que en una función normal de Python. Los argumentos son posicionales por defecto: primero los elementos de entrada y luego los elementos de estado se dan en el mismo orden que en el decorador. También tiene la opción de utilizar argumentos de palabra clave con nombre, en lugar de posicionales.

    d. Debes utilizar el mismo id que le diste a un componente Dash en el app.layout cuando te refieras a él como entrada o salida del decorador @app.callback.

    e. El decorador @app.callback debe estar directamente encima de la declaración de la función callback. Si hay una línea en blanco entre el decorador y la definición de la función, el registro de la devolución de llamada no tendrá éxito.

  • En Dash, los inputs y outputs de nuestra aplicación son simplemente las propiedades de un componente en particular. En este ejemplo, nuestro input es la propiedad “value” del componente que tiene el IDmy-input”. Nuestra salida es la propiedad “children” del componente con el IDmy-input”.

  • Cada vez que una propiedad de entrada cambia, la función que el decorador callback envuelve será llamada automáticamente. Dash proporciona a la función el nuevo valor de la propiedad de entrada como argumento de entrada y Dash actualiza la propiedad del componente de salida con lo que haya devuelto la función.

  • Las palabras clave component_id y component_property son opcionales (sólo hay dos argumentos para cada uno de esos objetos). Se incluyen en este ejemplo para mayor claridad, pero se omitirán en el resto de la sección en aras de la brevedad y la legibilidad.

  • No confunda el objeto dash.dependencies.Input con el objeto dcc.Input. El primero sólo se utiliza en estas definiciones de callback y el segundo es un componente real.

  • Nótese que no establecemos un valor para la propiedad children del componente my-output en el layout. Cuando la aplicación Dash se inicia, llama automáticamente a todos los callbacks con los valores iniciales de los componentes de entrada para rellenar el estado inicial de los componentes de salida. En este ejemplo, si se especifica algo como html.Div(id='my-output', children='Hola mundo'), se sobrescribiría cuando la aplicación se inicia.

  • Con la interactividad de Dash, podemos actualizar dinámicamente cualquier propiedad a través de una función callback. ¡Frecuentemente actualizaremos los children de un componente para mostrar nuevo texto o la figura de un componente dcc.Graph para mostrar nuevos datos, pero también podríamos actualizar el estilo de un componente o incluso las opciones disponibles de un componente dcc.Dropdown. Veamos otro ejemplo en el que un dcc.Slider actualiza un dcc.Graph.

Dash App Layout con Figura y Sliderm#

import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/gapminderDataFiveYear.csv')
df.head()
country year pop continent lifeExp gdpPercap
0 Afghanistan 1952 8425333.0 Asia 28.801 779.445314
1 Afghanistan 1957 9240934.0 Asia 30.332 820.853030
2 Afghanistan 1962 10267083.0 Asia 31.997 853.100710
3 Afghanistan 1967 11537966.0 Asia 34.020 836.197138
4 Afghanistan 1972 13079460.0 Asia 36.088 739.981106
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import plotly.express as px

import pandas as pd

df = pd.read_csv('https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/gapminderDataFiveYear.csv')

app = dash.Dash(__name__)

app.layout = html.Div([
    dcc.Graph(id='graph-with-slider'),
    dcc.Slider(
        id='year-slider',
        min=df['year'].min(),
        max=df['year'].max(),
        value=df['year'].min(),
        marks={str(year): str(year) for year in df['year'].unique()},
        step=None
    )
])

@app.callback(
    Output('graph-with-slider', 'figure'),
    Input('year-slider', 'value'))
def update_figure(selected_year):
    filtered_df = df[df.year == selected_year]

    fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
                     size="pop", color="continent", hover_name="country",
                     log_x=True, size_max=55)

    fig.update_layout(transition_duration=500)

    return fig


if __name__ == '__main__':
    app.run_server(debug=True)
_images/dash19.png
  • Recuerde que siempre puede cambiar el estilo de su figura y la hoja. Sólo debe cambiar las siguientes lineas

ext_style = "https://cdn.jsdelivr.net/npm/bootswatch@4.5.2/dist/slate/bootstrap.min.css"
app = dash.Dash(external_stylesheets=[ext_style])

fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
                 size="pop", color="continent", hover_name="country",
                 log_x=True, size_max=55, template="plotly_dark")
_images/dash20.png
  • En este ejemplo, la propiedad “value” del dcc.Slider es la entrada de la app y la salida de la app es la propiedad “figure” del dcc.Graph. Cada vez que el valor del dcc.Slider cambia, Dash llama a la función callback update_figure con el nuevo valor. La función filtra el dataframe con este nuevo valor, construye un objeto figura y lo devuelve a la aplicación Dash.

  • Hay algunos patrones interesantes en este ejemplo:

    • Utilizamos la biblioteca Pandas para cargar nuestro dataframe al inicio de la aplicación: df = pd.read_csv('...'). Este dataframe df está en el estado global de la app y puede ser leído dentro de las funciones callback.

    • La carga de datos en memoria puede ser costosa. Al cargar los datos de consulta al inicio de la app en lugar de dentro de las funciones callback, nos aseguramos de que esta operación sólo se realiza cuando se inicia el servidor de la app. Cuando un usuario visita la app o interactúa con ella, esos datos (df) ya están en memoria. Si es posible, la inicialización costosa (como la descarga o la consulta de datos) debería hacerse en el ámbito global de la app en lugar de dentro de las funciones callback.

    • El callback no modifica los datos originales, sólo crea copias del dataframe filtrando a través de los filtros de pandas. Esto es importante: los callbacks nunca deben mutar las variables fuera de su ámbito. Si tus callbacks modifican el estado global, entonces la sesión de un usuario podría afectar a la sesión del siguiente usuario y cuando la aplicación se despliegue en múltiples procesos o hilos, esas modificaciones no se compartirán entre sesiones.

    • Activamos las transiciones con layout.transition para dar una idea de cómo evoluciona el conjunto de datos con el tiempo: las transiciones permiten que el gráfico se actualice de un estado a otro de forma suave, como si estuviera animado, en este caso transition_duration=500 está dado en milésimas de segundos.

Aplicación Dash con múltiples entradas#

  • En Dash, cualquier “Output” puede tener múltiples componentes “Input”. Aquí hay un ejemplo simple que une cinco Inputs (la propiedad value de dos componentes dcc.Dropdown, dos componentes dcc.RadioItems, y un componente dcc.Slider) a un componente Output (la propiedad figure del componente Graph). Observe cómo app.callback enumera los cinco elementos de entrada después de la salida.

df = pd.read_csv('https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/country_indicators.csv')
df.head()
Country Name Indicator Name Year Value
0 Arab World Agriculture, value added (% of GDP) 1962 NaN
1 Arab World CO2 emissions (metric tons per capita) 1962 0.760996
2 Arab World Domestic credit provided by financial sector (... 1962 18.168690
3 Arab World Electric power consumption (kWh per capita) 1962 NaN
4 Arab World Energy use (kg of oil equivalent per capita) 1962 NaN
import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd

app = dash.Dash(__name__)

df = pd.read_csv('https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/country_indicators.csv')

available_indicators = df['Indicator Name'].unique()

app.layout = html.Div([
    html.Div([

        html.Div([
            dcc.Dropdown(
                id='xaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Fertility rate, total (births per woman)'
            ),
            dcc.RadioItems(
                id='xaxis-type',
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={'display': 'inline-block'}
            )
        ], style={'width': '48%', 'display': 'inline-block'}),

        html.Div([
            dcc.Dropdown(
                id='yaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Life expectancy at birth, total (years)'
            ),
            dcc.RadioItems(
                id='yaxis-type',
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={'display': 'inline-block'}
            )
        ], style={'width': '48%', 'float': 'right', 'display': 'inline-block'})
    ]),

    dcc.Graph(id='indicator-graphic'),

    dcc.Slider(
        id='year--slider',
        min=df['Year'].min(),
        max=df['Year'].max(),
        value=df['Year'].max(),
        marks={str(year): str(year) for year in df['Year'].unique()},
        step=None
    )
])


@app.callback(
    Output('indicator-graphic', 'figure'),
    Input('xaxis-column', 'value'),
    Input('yaxis-column', 'value'),
    Input('xaxis-type', 'value'),
    Input('yaxis-type', 'value'),
    Input('year--slider', 'value'))
def update_graph(xaxis_column_name, yaxis_column_name,
                 xaxis_type, yaxis_type,
                 year_value):
    dff = df[df['Year'] == year_value]

    fig = px.scatter(x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
                     y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
                     hover_name=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'])

    fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')

    fig.update_xaxes(title=xaxis_column_name,
                     type='linear' if xaxis_type == 'Linear' else 'log')

    fig.update_yaxes(title=yaxis_column_name,
                     type='linear' if yaxis_type == 'Linear' else 'log')

    return fig


if __name__ == '__main__':
    app.run_server(debug=True)
_images/dash21.png
  • En este ejemplo, la llamada de retorno se ejecuta cada vez que cambia la propiedad value de los componentes dcc.Dropdown, dcc.Slider o dcc.RadioItems. Los argumentos de entrada de la llamada de retorno son el valor nuevo o actual de cada una de las propiedades de entrada, en el orden en que fueron especificadas.

  • Aunque sólo un único Input cambia a la vez (un usuario sólo puede cambiar el valor de un único Dropdown en un momento dado), Dash recoge el estado actual de todas las propiedades Input especificadas y las pasa a su función por usted. Se garantiza que sus funciones callback siempre reciben el estado representativo de la aplicación.

Aplicación Dash con múltiples salidas#

  • Hasta ahora todos los callbacks que hemos escrito sólo actualizan una propiedad de salida. También podemos actualizar varias a la vez. Puede enumerar todas las propiedades que requiera actualizar en app.callback, y devolver ese número de elementos desde el callback. Esto es particularmente bueno si dos salidas dependen del mismo resultado intermedio computacionalmente intenso, como una consulta lenta a la base de datos.

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Input(
        id='num-multi',
        type='number',
        value=5
    ),
    html.Table([
        html.Tr([html.Td(['x', html.Sup(2)]), html.Td(id='square')]),
        html.Tr([html.Td(['x', html.Sup(3)]), html.Td(id='cube')]),
        html.Tr([html.Td([2, html.Sup('x')]), html.Td(id='twos')]),
        html.Tr([html.Td([3, html.Sup('x')]), html.Td(id='threes')]),
        html.Tr([html.Td(['x', html.Sup('x')]), html.Td(id='x^x')]),
    ]),
])


@app.callback(
    Output('square', 'children'),
    Output('cube', 'children'),
    Output('twos', 'children'),
    Output('threes', 'children'),
    Output('x^x', 'children'),
    Input('num-multi', 'value'))
def callback_a(x):
    return x**2, x**3, 2**x, 3**x, x**x

if __name__ == '__main__':
    app.run_server(debug=True)
_images/multiple_outputs_dash.png
  • No siempre es una buena idea combinar las salidas, aunque se pueda. Si los Outputs dependen de algunas pero no todas los mismos Inputs, mantenerlas separadas puede evitar actualizaciones innecesarias. Si tienen los mismos Inputs pero hacen cálculos independientes con estas entradas, mantener las devoluciones de llamada separadas puede permitir que se ejecuten en paralelo.

Dash App con callbacks encadenados#

  • También puedes encadenar salidas y entradas. La salida de una función callback puede ser la entrada de otra función callback. Este patrón puede ser utilizado para crear UIs dinámicas donde un componente de entrada actualiza las opciones disponibles del siguiente componente de entrada

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

all_options = {
    'America': ['New York City', 'San Francisco', 'Cincinnati'],
    'Canada': [u'Montréal', 'Toronto', 'Ottawa']
}

app.layout = html.Div([
    dcc.RadioItems(
        id='countries-radio',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='America'
    ),
    html.Hr(),
    dcc.RadioItems(id='cities-radio'),
    html.Hr(),
    html.Div(id='display-selected-values')
])

@app.callback(
    Output('cities-radio', 'options'), # <- as option
    Input('countries-radio', 'value')) 
def set_cities_options(selected_country):
    return [{'label': i, 'value': i} for i in all_options[selected_country]]

@app.callback(
    Output('cities-radio', 'value'), # <- as value
    Input('cities-radio', 'options'))  # <- as option
def set_cities_value(available_options):
    return available_options[0]['value'] # <- return first city value from the list

@app.callback(
    Output('display-selected-values', 'children'),
    Input('countries-radio', 'value'),
    Input('cities-radio', 'value'))
def set_display_children(selected_country, selected_city):
    return u'{} is a city in {}'.format(
        selected_city, selected_country,
    )

if __name__ == '__main__':
    app.run_server(debug=True)
_images/dash22.png
  • El primer callback actualiza las opciones disponibles en el segundo componente dcc.RadioItems basándose en el valor seleccionado en el primer componente dcc.RadioItems.

  • dcc.RadioItems es un componente para mostrar un conjunto de botones de radio (u opciones). Los usuarios pueden seleccionar una opción del conjunto.

  • El segundo callback establece un valor inicial cuando la propiedad options cambia: lo establece al primer valor de ese array de opciones.

  • La última llamada de retorno muestra el valor seleccionado de cada componente. Si cambia el valor del componente countries dcc.RadioItems, Dash esperará hasta que se actualice el valor del componente cities antes de llamar al callback final. Esto evita que sus callbacks sean llamados con un estado inconsistente como con “America” y “Montréal”.

Dash App con Estado#

  • En algunos casos, puede tener un patrón de tipo “formulario” en su aplicación. En esta situación, es posible que quieras leer el valor del componente de entrada, pero sólo cuando el usuario haya terminado de introducir toda su información en el formulario. Adjuntar una llamada de retorno a los valores de entrada directamente puede tener este aspecto:

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output

external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    dcc.Input(id="input-1", type="text", value="Montréal"),
    dcc.Input(id="input-2", type="text", value="Canada"),
    html.Div(id="number-output"),
])

@app.callback(
    Output("number-output", "children"),
    Input("input-1", "value"),
    Input("input-2", "value"),
)
def update_output(input1, input2):
    return u'Input 1 is "{}" and Input 2 is "{}"'.format(input1, input2)

if __name__ == "__main__":
    app.run_server(debug=True)
_images/dash23.png
  • En este ejemplo, la función callback se dispara cada vez que cambia alguno de los atributos descritos por la entrada Input. Pruébelo usted mismo introduciendo datos en las entradas anteriores. El estado le permite pasar valores adicionales sin disparar las devoluciones de llamada. Aquí está el mismo ejemplo anterior pero con el dcc.Input como State y un botón como Input. Recuerde que, la letra "u" delante de los valores del stirng significa que la cadena es Unicode.

Actualizar gráficos al deslizar el cursor#

  • Actualicemos nuestro ejemplo de indicadores mundiales, actualizando las series de tiempo cuando pasamos por encima de los puntos de nuestro gráfico de dispersión. la opción padding en el dash es utilizada para configurar el área de relleno en los cuatro lados de un elemento a la vez (ver padding).

import pandas as pd
df = pd.read_csv("https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/country_indicators.csv")
df.head()
Country Name Indicator Name Year Value
0 Arab World Agriculture, value added (% of GDP) 1962 NaN
1 Arab World CO2 emissions (metric tons per capita) 1962 0.760996
2 Arab World Domestic credit provided by financial sector (... 1962 18.168690
3 Arab World Electric power consumption (kWh per capita) 1962 NaN
4 Arab World Energy use (kg of oil equivalent per capita) 1962 NaN
  • En este ejemplo, display:inline-block permite establecer un ancho y una altura en el elemento. El padding de un elemento es el espacio entre su contenido y su borde.

import dash
from dash import dcc
from dash import html
import pandas as pd
import plotly.express as px

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

df = pd.read_csv('https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/country_indicators.csv')

available_indicators = df['Indicator Name'].unique()

app.layout = html.Div([
    html.Div([

        html.Div([
            dcc.Dropdown(
                id='crossfilter-xaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Fertility rate, total (births per woman)'
            ),
            dcc.RadioItems(
                id='crossfilter-xaxis-type',
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={'display': 'inline-block', 'marginTop': '5px'}
            )
        ],
        style={'width': '49%', 'display': 'inline-block'}),

        html.Div([
            dcc.Dropdown(
                id='crossfilter-yaxis-column',
                options=[{'label': i, 'value': i} for i in available_indicators],
                value='Life expectancy at birth, total (years)'
            ),
            dcc.RadioItems(
                id='crossfilter-yaxis-type',
                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={'display': 'inline-block', 'marginTop': '5px'}
            )
        ], style={'width': '49%', 'float': 'right', 'display': 'inline-block'})
    ], style={
        'padding': '10px 5px'
    }),

    html.Div([
        dcc.Graph(
            id='crossfilter-indicator-scatter',
            hoverData={'points': [{'customdata': 'Japan'}]}
        )
    ], style={'width': '49%', 'display': 'inline-block', 'padding': '0 20'}),
    html.Div([
        dcc.Graph(id='x-time-series'),
        dcc.Graph(id='y-time-series'),
    ], style={'display': 'inline-block', 'width': '49%'}),

    html.Div(dcc.Slider(
        id='crossfilter-year--slider',
        min=df['Year'].min(),
        max=df['Year'].max(),
        value=df['Year'].max(),
        marks={str(year): str(year) for year in df['Year'].unique()},
        step=None
    ), style={'width': '49%', 'padding': '0px 20px 20px 20px'})
])


@app.callback(
    dash.dependencies.Output('crossfilter-indicator-scatter', 'figure'),
    [dash.dependencies.Input('crossfilter-xaxis-column', 'value'),
     dash.dependencies.Input('crossfilter-yaxis-column', 'value'),
     dash.dependencies.Input('crossfilter-xaxis-type', 'value'),
     dash.dependencies.Input('crossfilter-yaxis-type', 'value'),
     dash.dependencies.Input('crossfilter-year--slider', 'value')])
def update_graph(xaxis_column_name, yaxis_column_name,
                 xaxis_type, yaxis_type,
                 year_value):
    dff = df[df['Year'] == year_value]

    fig = px.scatter(x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
            y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
            hover_name=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name']
            )

    fig.update_traces(customdata=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'])

    fig.update_xaxes(title=xaxis_column_name, type='linear' if xaxis_type == 'Linear' else 'log')

    fig.update_yaxes(title=yaxis_column_name, type='linear' if yaxis_type == 'Linear' else 'log')

    fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')

    return fig


def create_time_series(dff, axis_type, title):

    fig = px.scatter(dff, x='Year', y='Value')

    fig.update_traces(mode='lines+markers')

    fig.update_xaxes(showgrid=False)

    fig.update_yaxes(type='linear' if axis_type == 'Linear' else 'log')

    fig.add_annotation(x=0, y=0.85, xanchor='left', yanchor='bottom',
                       xref='paper', yref='paper', showarrow=False, align='left',
                       text=title)

    fig.update_layout(height=225, margin={'l': 20, 'b': 30, 'r': 10, 't': 10})

    return fig


@app.callback(
    dash.dependencies.Output('x-time-series', 'figure'),
    [dash.dependencies.Input('crossfilter-indicator-scatter', 'hoverData'),
     dash.dependencies.Input('crossfilter-xaxis-column', 'value'),
     dash.dependencies.Input('crossfilter-xaxis-type', 'value')])
def update_y_timeseries(hoverData, xaxis_column_name, axis_type):
    country_name = hoverData['points'][0]['customdata']
    dff = df[df['Country Name'] == country_name]
    dff = dff[dff['Indicator Name'] == xaxis_column_name]
    title = '<b>{}</b><br>{}'.format(country_name, xaxis_column_name)
    return create_time_series(dff, axis_type, title)


@app.callback(
    dash.dependencies.Output('y-time-series', 'figure'),
    [dash.dependencies.Input('crossfilter-indicator-scatter', 'hoverData'),
     dash.dependencies.Input('crossfilter-yaxis-column', 'value'),
     dash.dependencies.Input('crossfilter-yaxis-type', 'value')])
def update_x_timeseries(hoverData, yaxis_column_name, axis_type):
    dff = df[df['Country Name'] == hoverData['points'][0]['customdata']]
    dff = dff[dff['Indicator Name'] == yaxis_column_name]
    return create_time_series(dff, axis_type, yaxis_column_name)


if __name__ == '__main__':
    app.run_server(debug=True)
  • Intente pasar el mouse por encima de los puntos del gráfico de dispersión de la izquierda. ¿Observa cómo los gráficos de líneas de la derecha se actualizan en función del punto sobre el que pasa el mouse?.

_images/dash25.png

Dos ejemplos de Dash#

  • En esta sección estudiaremos dos ejemplos de app dash para poner en practica lo estudiado en las dos primeras secciones. El primero es relacionado con mapas y el segundo con series de tiempo financieras. El objetivo principal es hacer uso de la metodología aprender por ejemplos

Dash App para Mapas#

  • Iniciamos cargando el DataFrame a utlizar en nuestra app. Los datos provienen del archivo intro_bees.csv que contiene información sobre enfermedades que afectan a ciertas colonias de abejas, en determinados estados en USA. Las columnas de interés en este ejemplo son:

['State', 'ANSI', 'Affected by', 'Year', 'state_code']
  • El código ANSI es el Códigos del Instituto Nacional de Normalización: American National Standards Institute codes (ANSI codes)

import pandas as pd

df = pd.read_csv("https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/intro_bees.csv")
df.head()
Program Year Period State ANSI Affected by Pct of Colonies Impacted state_code
0 SURVEY 2019 JAN THRU MAR Alabama 1 Disease 1.8 AL
1 SURVEY 2019 JAN THRU MAR Alabama 1 Other 3.1 AL
2 SURVEY 2019 JAN THRU MAR Alabama 1 Pesticides 0.3 AL
3 SURVEY 2019 JAN THRU MAR Alabama 1 Pests_excl_Varroa 22.7 AL
4 SURVEY 2019 JAN THRU MAR Alabama 1 Unknown 9.0 AL
  • Luego agrupamos nuestro DataFrame basados en las columnas de interés y calculamos el porcentaje promedio de colonias afectadas para esta agrupación 'Pct of Colonies Impacted'

df = df.groupby(['State', 'ANSI', 'Affected by', 'Year', 'state_code'])[['Pct of Colonies Impacted']].mean()
df.reset_index(inplace=True)
df.head()
State ANSI Affected by Year state_code Pct of Colonies Impacted
0 Alabama 1 Disease 2015 AL 0.05
1 Alabama 1 Disease 2016 AL 1.20
2 Alabama 1 Disease 2017 AL 2.25
3 Alabama 1 Disease 2018 AL 1.30
4 Alabama 1 Disease 2019 AL 1.80
  • Comenzamos a implementar nuestro app.layout que como es sabido, esta componente corresponde al “diseño” de la aplicación y describe el aspecto de la misma. html.H1 recuerde que es una envoltura para el elemento <h1> de HTML5, elemento de encabezado de primer nivel. Luego de esto escribimos nuestro dcc.Dropdown con su respectivo id el cual sera invocado por nuesta función callback. El dcc.Dropdown corresponde a nuestro menu de opciones, en las que colocamos por defecto value=2015. Luego escribimos un nuevo html.Div para crear un output que entregue como mensaje cual fué el años seleccionado, esta es una clase children, pues depende del input suministrado en el Dropdown. Luego agregamos el componente dcc.Graph se puede utilizar para renderizar cualquier visualización de datos plotly-powered, recibe como argumento figure. Nótese que se le asgina también un id el cual será leido por nuestro callback

app.layout = html.Div([
    html.H1("Web Application Dashboards with Dash", style={'text-align': 'center'}),
    dcc.Dropdown(id="slct_year",
                 options=[
                     {"label": "2015", "value": 2015},
                     {"label": "2016", "value": 2016},
                     {"label": "2017", "value": 2017},
                     {"label": "2018", "value": 2018}],
                 multi=False,
                 value=2015,
                 style={'width': "40%"}
                 ),
    html.Div(id='output_container', children=[]),
    html.Br(),
    dcc.Graph(id='my_bee_map', figure={})
])
  • Pasamos a implementar nuestra función callback. En nuestro callback nótese que tenemos dos outputs, output_container que es tipo children, recuerde que el propósito de la propiedad children es permitir a los usuarios anidar componentes, tal y como hacemos en HTML, en éste caso Input() permite a un componente father actualizar datos en el componente children. La función update_graph recibe una sola componente, esto es porque tenemos un sólo input, dado que los outputs son dos, uestro callback debe también retornar dos objetos, los cuales en este caso llamamos container y fig.

@app.callback(
    [Output(component_id='output_container', component_property='children'),
     Output(component_id='my_bee_map', component_property='figure')],
    [Input(component_id='slct_year', component_property='value')]
)
def update_graph(option_slctd):
    print(option_slctd)
    print(type(option_slctd))

    container = "The year chosen by user was: {}".format(option_slctd)

    dff = df.copy()
    dff = dff[dff["Year"] == option_slctd]
    dff = dff[dff["Affected by"] == "Varroa_mites"]

    fig = px.choropleth(
        data_frame=dff,
        locationmode='USA-states',
        locations='state_code',
        scope="usa",
        color='Pct of Colonies Impacted',
        hover_data=['State', 'Pct of Colonies Impacted'],
        color_continuous_scale=px.colors.sequential.YlOrRd,
        labels={'Pct of Colonies Impacted': '% of Bee Colonies'},
        template='plotly_dark'
    )

    return container, fig
  • La siguiente función ya estudiada anteriormente nos permite ejecutar nuestra aplicación, colocamos la opción debug=True para poder activar todas las opciones de debug en nuestra app

if __name__ == '__main__':
    app.run_server(debug=True)
  • Así vamos a visualizar nuestra plicación luego de cada uno de los paso mencionados anteriormente

_images/dash26.png
  • El codigo completo utlizado en este ejemplo es el siguiente

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from dash import Dash, dcc, html, Input, Output

app = Dash(__name__)

df = pd.read_csv("https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/intro_bees.csv")

df = df.groupby(['State', 'ANSI', 'Affected by', 'Year', 'state_code'])[['Pct of Colonies Impacted']].mean()
df.reset_index(inplace=True)
print(df[:5])

app.layout = html.Div([
    html.H1("Web Application Dashboards with Dash", style={'text-align': 'center'}),
    dcc.Dropdown(id="slct_year",
                 options=[
                     {"label": "2015", "value": 2015},
                     {"label": "2016", "value": 2016},
                     {"label": "2017", "value": 2017},
                     {"label": "2018", "value": 2018}],
                 multi=False,
                 value=2015,
                 style={'width': "40%"}
                 ),
    html.Div(id='output_container', children=[]),
    html.Br(),
    dcc.Graph(id='my_bee_map', figure={})
])

@app.callback(
    [Output(component_id='output_container', component_property='children'),
     Output(component_id='my_bee_map', component_property='figure')],
    [Input(component_id='slct_year', component_property='value')]
)
def update_graph(option_slctd):

    container = "The year chosen by user was: {}".format(option_slctd)

    dff = df.copy()
    dff = dff[dff["Year"] == option_slctd]
    dff = dff[dff["Affected by"] == "Varroa_mites"]

    fig = px.choropleth(
        data_frame=dff,
        locationmode='USA-states',
        locations='state_code',
        scope="usa",
        color='Pct of Colonies Impacted',
        hover_data=['State', 'Pct of Colonies Impacted'],
        color_continuous_scale=px.colors.sequential.YlOrRd,
        labels={'Pct of Colonies Impacted': '% of Bee Colonies'},
        template='plotly_dark'
    )

    return container, fig

if __name__ == '__main__':
    app.run_server(debug=True)

Dash App Financiera#

  • En esta aplicación graficáremos el precio de distintos stocks, junto al indicador de análisis técnico, Bollinger Bands. Este indicador considera para cualquier stock, la media del orden de preferencia, y a esta le suma y resta la desviación estándar para tener la banda. Este indicador en bastante utilizado en análisis técnico, sobre todo cuando deseamos conocer posibles sectores de compra y venta.

  • Para esta app utlizaremos la librería colorlover la cual nos permitirá obtener una escala de colores a elegir para nuestras bollinger bands. Comenzamos por cargar el DataFrame que contiene los datos de los stocks de interés: AAPL, TSLA, COKE, YHOO, GOOGL, los cuales aparecen en nuestra columna Stock

df = pd.read_csv('https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/dash-stock-ticker-demo.csv')
df.head()
Unnamed: 0 Date Open High Low Close Volume ExDividend SplitRatio AdjOpen AdjHigh AdjLow AdjClose AdjVolume Stock
0 0 2017-12-29 170.52 170.590 169.220 169.23 25643711.0 0.0 1.0 170.52 170.590 169.220 169.23 25643711.0 AAPL
1 1 2017-12-28 171.00 171.850 170.480 171.08 15997739.0 0.0 1.0 171.00 171.850 170.480 171.08 15997739.0 AAPL
2 2 2017-12-27 170.10 170.780 169.710 170.60 21672062.0 0.0 1.0 170.10 170.780 169.710 170.60 21672062.0 AAPL
3 3 2017-12-26 170.80 171.470 169.679 170.57 32968167.0 0.0 1.0 170.80 171.470 169.679 170.57 32968167.0 AAPL
4 4 2017-12-22 174.68 175.424 174.500 175.01 16052615.0 0.0 1.0 174.68 175.424 174.500 175.01 16052615.0 AAPL
  • Luego de cargar nuestro DataFrame nos disponemos a implementar nuestro app.layout o “diseño” de la aplicación, tal como lo hicimos con la aplicación anterior. En este caso usamos html.H2 que es una envoltura para el elemento <h2> de HTML5, elemento de encabezado de segundo nivel, en este caso también agragamos el logo de Plotly, usted puede agragar el logo de su preferencia.

 html.Div([
        html.H2('Finance Explorer',
                style={'display': 'inline',
                       'float': 'left',
                       'font-size': '2.65em',
                       'margin-left': '7px',
                       'font-weight': 'bolder',
                       'font-family': 'Product Sans',
                       'color': "rgba(117, 117, 117, 0.95)",
                       'margin-top': '20px',
                       'margin-bottom': '0'
                       }),
        html.Img(src="https://s3-us-west-1.amazonaws.com/plotly-tutorials/logo/new-branding/dash-logo-by-plotly-stripe.png",
                style={
                    'height': '100px',
                    'float': 'right'
                },
        ),
    ])
  • Luego de esto escribimos nuestro dcc.Dropdown con su respectivo id el cual sera invocado por nuesta función callback. El dcc.Dropdown corresponde a nuestro menu de opciones para los diferentes stocks, en las que colocamos por defecto una lista con dos stocks ['YHOO', 'GOOGL']. También creamo una división donde colocaremos cada una de las graficas asociadas a cada stock

    dcc.Dropdown(
        id='stock-ticker-input',
        options=[{'label': s[0], 'value': str(s[1])}
                 for s in zip(df.Stock.unique(), df.Stock.unique())],
        value=['YHOO', 'GOOGL'],
        multi=True
    ),
    html.Div(id='graphs')
  • Definimos la función encargada de calcular nuestro indicado de análisis tecnico. En esta definimos el periodo para nuestra media movil window_size=10 y además de esto el numero de desviaciones estandar a las que deseamos distar de esta media num_of_std=5

def bbands(price, window_size=10, num_of_std=5):
    rolling_mean = price.rolling(window=window_size).mean()
    rolling_std  = price.rolling(window=window_size).std()
    upper_band = rolling_mean + (rolling_std*num_of_std)
    lower_band = rolling_mean - (rolling_std*num_of_std)
    return rolling_mean, upper_band, lower_band
  • Definimos ahora nuestra función callback la cual tendrá como Output en este caso las figuras correspondiente al indicador para cada stock. El mensaje de encabezado "Select a stock ticker." aparecerá si en la caja no aparece ningún stock, indicando que debe seleccionarse el simbolo ticker del stock de interés. Una vez recibido este ticker filtramos nuestro DataFame por Stock que coincida con el Inpunt ticker y seleccionamos para este nuevo DataFrame las columnas que necesitamos para el indicador bollinger band y seleccionamos de la escala de colores el color a utlizar en nuestras bandas de bollinger que será el input de la función encargada de calcular las bandas bbands, vamos escalando los colores de acuerdo a la banda que se va a dibujar. Vamos a ir agregando a graphs cada una de las graficas que vamos obteniendo para mostrarlas todas en nuestra app una debajo de la otra. La figuras van indezadas con id=ticker y son obtenidas al realizar la adición [candlestick] + bollinger_traces. Los siguiente comandos son usados para las dimensiones del margen en nuestra figura y la ubcación de la leyenda: 'margin': {'b': 0, 'r': 10, 'l': 60, 't': 0}, y 'legend': {'x': 0}.

@app.callback(Output('graphs','children'),
    [Input('stock-ticker-input', 'value')])
def update_graph(tickers):
    graphs = []

    if not tickers:
        graphs.append(html.H3(
            "Select a stock ticker.",
            style={'marginTop': 20, 'marginBottom': 20}
        ))
    else:
        for i, ticker in enumerate(tickers):

            dff = df[df['Stock'] == ticker]

            candlestick = {
                'x': dff['Date'],
                'open': dff['Open'],
                'high': dff['High'],
                'low': dff['Low'],
                'close': dff['Close'],
                'type': 'candlestick',
                'name': ticker,
                'legendgroup': ticker,
                'increasing': {'line': {'color': colorscale[0]}},
                'decreasing': {'line': {'color': colorscale[1]}}
            }
            bb_bands = bbands(dff.Close)
            bollinger_traces = [{
                'x': dff['Date'], 'y': y,
                'type': 'scatter', 'mode': 'lines',
                'line': {'width': 1, 'color': colorscale[(i*2) % len(colorscale)]},
                'hoverinfo': 'none',
                'legendgroup': ticker,
                'showlegend': True if i == 0 else False,
                'name': '{} - bollinger bands'.format(ticker)
            } for i, y in enumerate(bb_bands)]
            graphs.append(dcc.Graph(
                id=ticker,
                figure={
                    'data': [candlestick] + bollinger_traces,
                    'layout': {
                        'margin': {'b': 0, 'r': 10, 'l': 60, 't': 0},
                        'legend': {'x': 0}
                    }
                }
            ))

    return graphs
  • Así vamos a visualizar nuestra plicación luego de cada uno de los paso mencionados anteriormente

_images/dash27.png
  • El código completo de la app lo presentamos a continuación. Ejecútela desde su terminal o editor VS Code por ejemplo. Antes debe instalar colorlover por medio de

pip install colorlover
from dash import Dash, dcc, html, Input, Output
import colorlover as cl
import datetime as dt
import flask
import os
import pandas as pd
import time

app = Dash(__name__)

server = app.server

app.scripts.config.serve_locally = False

colorscale = cl.scales['9']['qual']['Paired']

df = pd.read_csv('https://raw.githubusercontent.com/lihkir/Uninorte/main/AppliedStatisticMS/DataVisualizationRPython/Lectures/Python/PythonDataSets/dash-stock-ticker-demo.csv')

app.layout = html.Div([
    html.Div([
        html.H2('Finance Explorer',
                style={'display': 'inline',
                       'float': 'left',
                       'font-size': '2.65em',
                       'margin-left': '7px',
                       'font-weight': 'bolder',
                       'font-family': 'Product Sans',
                       'color': "rgba(117, 117, 117, 0.95)",
                       'margin-top': '20px',
                       'margin-bottom': '0'
                       }),
        html.Img(src="https://s3-us-west-1.amazonaws.com/plotly-tutorials/logo/new-branding/dash-logo-by-plotly-stripe.png",
                style={
                    'height': '100px',
                    'float': 'right'
                },
        ),
    ]),
    dcc.Dropdown(
        id='stock-ticker-input',
        options=[{'label': s[0], 'value': str(s[1])}
                 for s in zip(df.Stock.unique(), df.Stock.unique())],
        value=['YHOO', 'GOOGL'],
        multi=True
    ),
    html.Div(id='graphs')
], className="container")

def bbands(price, window_size=10, num_of_std=5):
    rolling_mean = price.rolling(window=window_size).mean()
    rolling_std  = price.rolling(window=window_size).std()
    upper_band = rolling_mean + (rolling_std*num_of_std)
    lower_band = rolling_mean - (rolling_std*num_of_std)
    return rolling_mean, upper_band, lower_band

@app.callback(Output('graphs','children'),
    [Input('stock-ticker-input', 'value')])
def update_graph(tickers):
    graphs = []

    if not tickers:
        graphs.append(html.H3(
            "Select a stock ticker.",
            style={'marginTop': 20, 'marginBottom': 20}
        ))
    else:
        for i, ticker in enumerate(tickers):

            dff = df[df['Stock'] == ticker]

            candlestick = {
                'x': dff['Date'],
                'open': dff['Open'],
                'high': dff['High'],
                'low': dff['Low'],
                'close': dff['Close'],
                'type': 'candlestick',
                'name': ticker,
                'legendgroup': ticker,
                'increasing': {'line': {'color': colorscale[0]}},
                'decreasing': {'line': {'color': colorscale[1]}}
            }
            bb_bands = bbands(dff.Close)
            bollinger_traces = [{
                'x': dff['Date'], 'y': y,
                'type': 'scatter', 'mode': 'lines',
                'line': {'width': 1, 'color': colorscale[(i*2) % len(colorscale)]},
                'hoverinfo': 'none',
                'legendgroup': ticker,
                'showlegend': True if i == 0 else False,
                'name': '{} - bollinger bands'.format(ticker)
            } for i, y in enumerate(bb_bands)]
            graphs.append(dcc.Graph(
                id=ticker,
                figure={
                    'data': [candlestick] + bollinger_traces,
                    'layout': {
                        'margin': {'b': 0, 'r': 10, 'l': 60, 't': 0},
                        'legend': {'x': 0}
                    }
                }
            ))

    return graphs

if __name__ == '__main__':
    app.run_server(debug=True)

Conclusión

  • Por medio de estas dos aplicaciones se ha abordado cada concepto básico relacionado con el diseño de una app utilizando Dash y Plotly teniendo en cuenta lo estudiado en secciones anteriores. Ahora procedemos a estudiar como podemos desplegar nuestra aplicación en Docker.

Despliegue de Dash App usando Docker#

Introducción

  • Docker y Dash son herramientas útiles para alojar aplicaciones en diversos entornos. Dockerizar una imagen para Dash resulta conveniente para desplegar tableros de control (dashboards) en múltiples escenarios. En esta sección, se explica cómo Dockerizar Dash en una imagen Docker. El proceso comienza con el uso de una aplicación Dash probada localmente en la sección anterior. Luego, se crea un Dockerfile, que contiene instrucciones para construir un contenedor Docker que hospedará el dashboard.

  • Una vez que haya preparado su Dash, puede empezar a Dockerizarlo creando un archivo nombrado Dockerfile (Dockerfile, sin ninguna extensión). El Dockerfile representa una imagen Docker y contiene instrucciones sobre las operaciones a realizar cuando se construye un contenedor.

  • A continuación presentamos el Dockerfile usado para Dockerizar una aplicación Dash que reposa en el archivo index.py. Cada uno de los siguientes archivos listados, deben estar contenidos en el mismo directorio, como se ve en la siguiente imagen.

_images/docker_project.png
  • El objetivo es crear un directorio para nuestro Dockerfile el cual contenga la aplicación index.py, un README y un archivo de requerimientos requirements.txt. En este ejemplo se ha creado una carpeta nombrada barplot_fruits, donde va a reposar la imagen docker con los archivos mencionados previamente

  • Dentro del archivo .py que usará para su aplicación debe copiar el código del dash que se va a dockerizar en este ejemplo. A continuación puede copiar el código y colocarlo en el archivo index.py. Warning: Para probar su dash utilice el servidor por defecto para depuración

app.run_server(debug=True)
  • Para Dockerizar su dashboard, antes del último paso de esta sección, debe cambiar la función que ejecuta el servidor a:

app.run_server(debug=True, host='0.0.0.0', port=9000)
import dash
from dash import dcc
from dash import html
import plotly.express as px
import pandas as pd

ext_style = "https://cdn.jsdelivr.net/npm/bootswatch@4.5.2/dist/darkly/bootstrap.min.css"
app = dash.Dash(external_stylesheets=[ext_style])

df = pd.DataFrame({
    "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
    "Amount": [4, 1, 2, 2, 4, 5],
    "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
})

fig = px.bar(df, x = "Fruit", y = "Amount", color = "City", barmode = "group", template = "plotly_dark")

app.layout = html.Div(children=[
    html.H1(children = 'Hello Dash'),
    
    html.Div(children='''Dash: A web application framework for your data.'''),

    dcc.Graph(
        id='example-graph',
        figure=fig
    )
])

if __name__ == "__main__":
    app.run_server(debug=True) # <- For testing purposes
    #app.run_server(debug=True, host='0.0.0.0', port=9000) # <- To Dockerize the Dash
  • Antes de Dockerizar su dash, es recomendable crear para este un entorno dedicado, el cual solo se use para crear la imagen Docker de su aplicación. Para crear el ambiente virtual de miniconda recuerde que puede usar el gestor de instalación de paquetes conda. Por ejemplo para crear el entorno virtual dash_venv

conda create --name dash_venv python=3.9
  • Luego de haber creado el entorno virtual dash_venv, el siguiente paso es ejecutar la aplicación de interés, a saber index.py dentro del environment. Dado que el entorno se ha creado recientemente, deberá instalar cada librería necesaria. Por ejemplo, dash, plotly, pandas,….. Para verificar que la aplicación funciona correctamente, desde la consola de miniconda ubicado siempre en el directorio barplot_fruits donde se encuentre el archivo index.py debe ejecutar:

python index.py
  • Al ejecutar la línea anterior surgirán errores asociados con requerimientos de instalación tales como: dash, plotly, pandas, etc,. Realice la instalación de cada uno de estos. Luego de finalizada la instalación, verifique que efectivamente la aplicación está funcionando correctamente.

  • El siguiente paso es crear nuestro archivo de requerimientos requirements.txt. Para esto debe primero que todo ubicarse dentro de la carpeta barplot_fruit desde miniconda. En esta carpeta reposa su aplicación, el archivo index.py y además, todos los archivo para su imagen Docker. Para crear el archivo de requerimientos, dentro de esta carpeta debe ejecutar

pip freeze > requirements.txt
  • En ocasiones el paso anterior genera conflictos, creando algunas líneas extrañas dentro del archivo requirements.txt del tipo @ file:///opt/.... Si este es su caso, puede solucionar este problema de la siguiente forma. Primero que todo, elimine el archivo de requerimientos actual, esto es, el archivo requirements.txt. Una vez eliminado, debe generarlo nuevamente usando

pip list --format=freeze > requirements.txt
  • El siguiente paso es crear nuestro archivo Dockerfile que va a generar nuestra imagen Docker. Para esto, debe crear un archivo nombrado Dockerfile sin ninguna extensión, dentro de la carpeta de su proyecto barplot_fruit. Puede crearlo usando VS Code directamente. Dentro de este archivo debe colocar lo siguiente. Al final se realiza una explicación del contenido de este archivo

FROM python:3.9.17

COPY ./requirements.txt /requirements.txt

RUN pip install --upgrade pip
RUN pip install --no-cache-dir --upgrade -r /requirements.txt

COPY ./index.py /index.py

CMD ["python", "index.py"]
  • Verifique en el Dockerfile, la versión de Python que aparece en la primera línea, coincide con la versión de Python que tiene instalada en el entorno virtual creado para este proyecto. Verifique también que el nombre de la aplicación dash, que aparece en las dos últimas líneas, coincide con el de su archivo .py dentro del directorio de su proyecto.

  • El siguiente proceso consiste en crear un archivo README el cual contiene instrucciones para ejecutar el contenedor con el dashboard. El siguiente es un ejemplo de README

# Execute the following commands to Dockerize the Dash application

docker build -t barplot_fruits .
docker run -h localhost -p 9002:9000 -d --name barplot_fruits barplot_fruits
  • El último paso para crear la imagen Docker es el siguiente. Primero que todo, debe iniciar la aplicación Docker. Posteriormente, dentro de la carpeta de su proyecto y desde miniconda, debe ejecutar el par de líneas indicadas en el archivo README

docker build -t barplot_fruits .
docker run -h localhost -p 9002:9000 -d --name barplot_fruits barplot_fruits
  • Si ningún error ocurrió hasta este momento, usted podrá visualizar dentro de su aplicación Docker, la imagen y y el contenedor asociado a su dashboard, en este caso con el nombre: barplot_fruits. Además, podrá ejecutarla haciendo click en los puertos expuestos para su dash, en este caso: 9002:9000.

_images/docker_dashapp.png
  • En conclusión, en un Dockerfile para Dockerizar una aplicación Dash, se debe agregar lo siguiente:

    • Declarar la imagen base Python: python:3.9.17 (o la que corresponda)

    • Instalar todas las dependencias necesarias, incluyendo las dependencias tanto para Plotly como para Dash

    • Ejecutar antes el dashboard que ha construido en un environment separado. Dentro de este environment, contruya el archivo de requerimientos mediante:

      pip freeze > requirements.txt
      
    • Una vez preparado el Dockerfile, puedes construir el contenedor y ejecutarlo usando las instrucciones del archivo README. Recuerde que debe tener iniciada la aplicación Docker

  • El código del archivo README realiza lo siguiente:

    • La primera línea construye un contenedor Docker basado en el Dockerfile y lo nombra barplot_fruits. Debe asegúrate de incluir "." al final para indicar el directorio del archivo Docker.

    • Una vez construido el contenedor Docker, la segunda línea ejecuta la orden para exponer la aplicación Dash en localhost:9002. 9002:9000 significa mapear el puerto 9000 en el contenedor al puerto 9002 en tu entorno local.

    • La razón para mapear el puerto 9000 del contenedor es porque se declaró la aplicación Dash en el Puerto 9000 en el script Python.

    • Para exponer la aplicación Dash, debe establecer el host = '0.0.0.0' en el entorno del contenedor! No funcionará si lo pones en 127.0.0.1.

Lista de Dockerfiles#

Dockerfile

FROM python:3.9.17

COPY ./requirements.txt /requirements.txt

RUN pip install --upgrade pip
RUN pip install --no-cache-dir --upgrade -r /requirements.txt

COPY ./index.py /index.py

CMD ["python", "index.py"]

index.py

import dash
from dash import dcc
from dash import html
import plotly.express as px
import pandas as pd

ext_style = "https://cdn.jsdelivr.net/npm/bootswatch@4.5.2/dist/darkly/bootstrap.min.css"
app = dash.Dash(external_stylesheets=[ext_style])

df = pd.DataFrame({
    "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
    "Amount": [4, 1, 2, 2, 4, 5],
    "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
})

fig = px.bar(df, x = "Fruit", y = "Amount", color = "City", barmode = "group", template = "plotly_dark")

app.layout = html.Div(children=[
    html.H1(children = 'Hello Dash'),
    
    html.Div(children='''Dash: A web application framework for your data.'''),

    dcc.Graph(
        id='example-graph',
        figure=fig
    )
])

if __name__ == "__main__":
    app.run_server(debug=True, host='0.0.0.0', port=9000)

requirements.txt

ansi2html==1.8.0
certifi==2023.7.22
charset-normalizer==3.3.0
click==8.1.7
colorama==0.4.6
dash==2.14.0
dash-core-components==2.0.0
dash-html-components==2.0.0
dash-table==5.0.0
Flask==2.2.5
idna==3.4
importlib-metadata==6.8.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
nest-asyncio==1.5.8
numpy==1.26.1
packaging==23.2
pandas==2.1.1
plotly==5.17.0
python-dateutil==2.8.2
pytz==2023.3.post1
requests==2.31.0
retrying==1.3.4
six==1.16.0
tenacity==8.2.3
typing_extensions==4.8.0
tzdata==2023.3
urllib3==2.0.7
Werkzeug==2.2.3
zipp==3.17.0

README

# Execute the following commands to Dockerize the Dash application

docker build -t barplot_fruits .
docker run -h localhost -p 9002:9000 -d --name barplot_fruits barplot_fruits

Observación

Dockerizar un tablero Dash es muy fácil, sólo tienes que añadir un Dockerfile con un par de líneas de instrucciones, construir un contenedor con él, ¡y ejecutarlo!

  • A continuación se muestran outputs en consola al ejecutar el Dockerfile, para crear la imagen Docker

_images/docker_build.png
_images/docker_run.png
_images/docker_dashapp.png
  • Si desea desplegar la aplicación, debe dirigirse a la aplicación Docker. Dentro de esta debe buscar la imagen creada en el botón Images. Posteriormente, en contenedores, podrá visualizar el contenedor creado, a saber (barplot_fruits), y a la derecha los puertos donde se ejecuta el dash en el daemon. Haga click sobre este puerto para visualizar su dashboard

_images/barplot_fruits_dockerized.png

Render y Dash Tool#

Introducción

  • Render es una plataforma en la nube que ofrece servicios de alojamiento web y despliegue de aplicaciones. Permite a los desarrolladores implementar fácilmente aplicaciones web, sitios estáticos y API en la nube de manera rápida y sencilla.

  • Render proporciona características como escalabilidad automática, integración continua y despliegue continuo, seguridad y monitoreo avanzado.

  • También ofrece una interfaz de usuario intuitiva con costos razonables, y sin tarifas de inicio. En resumen, Render es una plataforma que simplifica el proceso de despliegue y administración de aplicaciones web y servicios en la nube.

  1. El primer paso consiste en crearse una cuenta en Render. Es recomendable utilizar su cuenta de GitHub para crear esta cuenta

  1. El segundo paso consiste en verificar que su aplicación funciona correctamente. Para este ejemplo de despliegue en Render, necesitaremos nombrar la aplicación como app.py. Luego que cambien el nombre de la aplicación, verifique que funciona correctamente. Para esto, con su entorno activado, siguiendo el mismo ejemplo de la sección anterior, ejecute en la terminal la siguiente orden.

    python app.py
    
    • Luego en su navegador visite http://127.0.0.1:8050/ para ver la aplicación.

    • Recuerde que la aplicación es la siguiente

     import dash
     from dash import dcc
     from dash import html
     import plotly.express as px
     import pandas as pd
     
     ext_style = "https://cdn.jsdelivr.net/npm/bootswatch@4.5.2/dist/darkly/bootstrap.min.css"
     app = dash.Dash(external_stylesheets=[ext_style])
     
     df = pd.DataFrame({
         "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
         "Amount": [4, 1, 2, 2, 4, 5],
         "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
     })
     
     fig = px.bar(df, x = "Fruit", y = "Amount", color = "City", barmode = "group", template = "plotly_dark")
     
     app.layout = html.Div(children=[
         html.H1(children = 'Hello Dash'),
         
         html.Div(children='''Dash: A web application framework for your data.'''),
     
         dcc.Graph(
             id='example-graph',
             figure=fig
         )
     ])
     
     if __name__ == "__main__":
         app.run_server(debug=True)
    
  1. El siguiente paso es crear un nuevo directorio con el nombre que usted decida, por ejemplo DeployWithRender y dentro de esta carpeta, debe crear otro directorio, el cual nombraremos src el cual va a contener el archivo con nuestra aplicación app.py. Si abre la aplicación desde VSCode debe verse de la siguiente forma

_images/render_wit_deploy1.png
  1. El siguiente paso es instalar dash-tools utilizando la siguiente línea de comando desde su entorno virtual

    pip install dash-tools
    
  1. El siguiente paso requiere del uso del cliente de Git. Si no lo tiene instalado, realice la instalación desde el siguiente link Getting-Started-Installing-Git. Luego de haber instalado Git desde la línea de comandos, por ejemplo, de Anaconda Powershell ejecute los siguientes comandos de Git. Recuerde que debe estar desde la terminal, ubicado dentro de la carpeta creada DeployWithRender

    git init
    

    Luego para iniciar dashtools

    dashtools gui
    
_images/render_wit_deploy2.png
_images/render_wit_deploy3.png
  1. El siguiente paso es copiar dentro de la ventana Deploy la ubicación de la carpeta creada para el proyecto. En este caso la carpeta nombrada DeployWithRender. A la derecha podrá visualizar todos los archivos que debe preparar antes de realizar el despliegue de su aplicación. Posterior a esto se activará el botón Deploy to Render. Haga click en Generate random name para asignar un nombre a su aplicación

_images/render_wit_deploy4.png
  1. Luego de haber creado el nombre aleatorio, podrá generar los archivos: render.yaml y requirements.txt. Solo debe hacer click sobre el botón para generar cada archivo que aparece al lado derecho de cada nombre

_images/render_wit_deploy5.png
  1. Luego debajo de la instancia de Dash en su aplicación. Agregue la línea server = app.server tal como lo indica Render. Esto es, la aplicación debería verse de la siguiente forma

     import dash
     from dash import dcc
     from dash import html
     import plotly.express as px
     import pandas as pd
     
     ext_style = "https://cdn.jsdelivr.net/npm/bootswatch@4.5.2/dist/darkly/bootstrap.min.css"
     app = dash.Dash(external_stylesheets=[ext_style])
    
     server = app.server # <- For Render
     
     df = pd.DataFrame({
         "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
         "Amount": [4, 1, 2, 2, 4, 5],
         "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"]
     })
     
     fig = px.bar(df, x = "Fruit", y = "Amount", color = "City", barmode = "group", template = "plotly_dark")
     
     app.layout = html.Div(children=[
         html.H1(children = 'Hello Dash'),
         
         html.Div(children='''Dash: A web application framework for your data.'''),
     
         dcc.Graph(
             id='example-graph',
             figure=fig
         )
     ])
     
     if __name__ == "__main__":
         app.run_server(debug=True)
    

    Va a notar en el Dashboard de Render que aparece el check en verde para Code exists: server = app.server in src/app.py

_images/render_wit_deploy6.png
  1. Ahora debe hacer un Push en GitHub con el proyecto creado DeployWithRender. Para esto, diríjase a su cuenta de GitHub y en la pestaña Your repositories haga click en New. Luego, abra una nueva terminal, y ubíquese dentro de la carpeta DeployWithRender. Dentro de esta carpeta, pegue los comandos de Git, que se han generado al crear el repositorio, como se ve en las siguientes imágenes. Haga Enter en la última línea de código pegada.

_images/render_wit_deploy7.png
_images/render_wit_deploy8.png
_images/render_wit_deploy9.png
_images/render_wit_deploy10.png
  1. El siguiente paso es hacer un Push con todos los archivos asociados al proyecto DeployWithRender creado para la aplicación. Para esto hacemos uso de las siguientes líneas de comandos Git. Su repositorio de Git debe visualizarse tal como aparece en la siguiente imagen

    git add .
    git commit -m "Adding DeployWithRender Files"
    git push
    
_images/render_wit_deploy11.png
  1. El siguiente paso es hacer click en Deploy to Render desde el Dashboard de Render, tal como se muestra en la siguiente imagen. Verifique que cada uno de los requerimientos para el despliegue aparecen con sus respectivos checks en verde. Posterior a esto, asigne un nombre Blueprint, en este caso para identificar la aplicación, hemos utilizado el mismo nombre del repositorio en GitHub. Luego haga click en Apply. El proceso puede tomar algunos minutos, por favor, sea paciente.

_images/render_wit_deploy12.png
_images/render_wit_deploy13.png
  1. Haga click ahora en Create web service .... Podrá ver el link de la página web en la parte superior izquierda, tal como se observa en la siguiente imagen. En este caso el link generado con la aplicación es el siguiente barbeque-talk-tuba-z58u.

_images/render_wit_deploy14.png
  1. Para realizar un cambio en su aplicación debe realizar un commit nuevamente con estos cambios, y además, desde el Dashboard de Render, hacer luego click en Deploy latest commit tal como aparece en la siguiente imagen. Esto es, debe repetir el ítem 10

_images/render_wit_deploy15.png
  • Los pasos abordados en esta sección fueron tomados del tutorial de CharmingData. Pueden visitar el sitio web oficial charming-data.circle.so donde podrán encontrar, en esta gran comunidad, más tutoriales sobre Dash Plotly.

Ejercicio para entregar#

  • Dockerizar los dashboard de los dos ejemplos estudiados en la sección Dos ejemplos de Dash. Esto es, crear dos imágenes Docker para los Dash asociados con: Dash App para Mapas y Dash App Financiera.

  • Posteriormente, realice cambios en el (back/front end) de los dos Dashboards teniendo en cuenta los diferentes templates usados en esta sección. Por ejemplo,

    • Agregue a los dashboard nuevos estilos: Cambiar título, logo, color de fondo,….. Además, agregué un nuevo Dropdown para la columna Affected by. Agregue divisiones con diagramas descriptivos para cada una de las columnas del dataframe. Realice agrupación por State, Affected by, Year y realice gráficos descriptivos que entreguen información valiosa.

    • Agregue además de la ventana de selección de stocks, un nuevo Dropdown, el cual le permita por medio de un menú de opciones, seleccionar entre 4 indicadores de análisis técnico (ver Commonly Used Technical Indicators; Technical Indicators Mathematical Description; 7 Technical Indicators to Build a Trading Toolkit, Best Day Trading Indicators for Beginners), por ejemplo:

      • On-balance volume (OBV)

      • Accumulation/distribution (A/D) line

      • Average directional index

      • Aroon oscillator

      • Moving average convergence divergence (MACD)

      • Relative strength index (RSI)

      • Stochastic oscillator

  • Cada uno de estos indicadores cuenta con parámetros que pueden modificarse desde un Dropdown, por ejemplo, en el caso de la media movil, agregue un Dropdown para seleccionar el periodo. Para las Bollinger Bands, agregue otro Dropdown para, además del periodo de la media móvil, agregar, el número de desviaciones estándar que se desviará de la media cada banda, etc,…

  • Asegúrese de que todos los indicadores seleccionados aparezcan en el mismo candlestick, esto es fundamental para la toma de decisiones. Por ejemplo, en un solo chart, el objetivo es que el inversionista pueda obtener un mínimo de 4 confirmaciones, por medio del análisis visual de cada uno de los indicadores suministrados por el Dashboard, antes de tomar una decisión de compra o venta (long/short). Por lo tanto, debe visualizar todos los 4 indicadores en un solo plot.