Robos - dash
Esta es una versión demo de una app, que muestra de una forma comoda los analisis descriptivos y econometricos sobre los Robos en CDMX, si desea ejecutar la app de forma local visite el siguiente repositorio OsvaldoYa22/app_dash
Requirements
from dash import Dash, dcc, html
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
Estilos externos
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css',
[dbc.themes.BOOTSTRAP]]
Se definen dos estilos externos para la aplicación Dash. Uno de ellos es un enlace a una hoja de estilos CSS alojada en CodePen ‘https://codepen.io/chriddyp/pen/bWLwgP.css’. El otro estilo externo es de Bootstrap (dbc.themes.BOOTSTRAP) que se utilizará para darle estilo y formato a la aplicación.
Inicialización de la aplicación Dash
app = Dash(__name__, external_stylesheets = external_stylesheets, suppress_callback_exceptions = True)
Se crea una instancia de la clase Dash y se asigna a la variable app, los parámetros proporcionados son:
__name__
es el nombre del módulo actual.external_stylesheets
se pasasn los estilos externos definidos anteriormentesuppress_callback_exceptions
se establece esta opcion comoTrue
por lo que la aplicación suprimirá las excepciones de devolución de llamada. Esto permite manejar de manera más fexible los errores en las devoluciones de llamada.
Diseño de la aplicación
app.layout = html.Div([
html.Div([
html.H1('ROBOS EN CDMX', style = {'textAlign': 'center', 'height': '75px', 'margin': '0px -10px 10px', 'background-color': '#FFFFFF', 'border-radius': '2px', 'display': 'block'})]),
dcc.Tabs(id = "tabs", value = 'tab-1', children = [
dcc.Tab(label = 'ANALISIS DESCRIPTIVO', value = 'tab-2'),
dcc.Tab(label = 'ANALISIS ECONOMETRICO', value = 'tab-1')]),
html.Div(id = 'tabs-content')
])
El diseño de la aplicación se define utilizando componentes de la librería html
y dcc
de Dash. En este caso, se crea un diseño con dos pestañas dcc.Tabs
y un contenedor de contenido html.Div
donde se mostrará el contenido correspondiente a la pestaña seleccionada.
- Se crea un encabezado con un título (“ROBOS EN CDMX”) dentro de una
html.Div
. - Luego, se definen las pestañas con etiquetas “ANALISIS DESCRIPTIVO” y “ANALISIS ECONOMETRICO”.
- El contenido de cada pestaña se establece mediante un callback
Callbacks
@app.callback(Output('tabs-content', 'children'),
Input('tabs', 'value'))
def render_content_tab(tab):
return render_content_table(tab)
En Dash, los callbacks se utilizan para actualizar dinámicamente el contenido de la aplicación en respuesta a cambios en los componentes de entrada. Los callbacks están definidos utilizando el decorador @app.callback.
- El primer callback se activa cuando se cambia de pestaña
Input('tabs', 'value')
y su función asociada esrender_content_tab(tab)
. Esta función se encargará de mostrar el contenido correspondiente a la pestaña seleccionada en el contenedor de contenidotabs-content
.
Funciónes
Estructura de cada tabla
Llamamos a nuestras librerias
from dash import dcc, html
import dash_bootstrap_components as db
Definimos a nuestro render_content_table(tab)
el cual su contenido va a depender del valor de tab
def render_content_table(tab):
if tab == 'tab-2':
return html.Div([])
elif tab == 'tab-1':
return html.Div([])
Dentro de cada html.Div
se mostrara el analsis “Econometrico” o “Descriptivo” según seleccione el usuario
- Econometrico
html.Div( style = {'display': 'flex', 'flex-wrap': 'wrap'}, children = [ html.Div( style = {'width': '50%'}, children = [ html.Div( style = {'background-color': '#F2F2F2','border-radius': '15px', 'margin': '5px','padding': '7px','position': 'relative', 'box-shadow': '4px 4px 4px 4px lightgrey','overflow-y': 'auto'}, children = [ dcc.Graph(id = 'Mi_grafica_ST_2', figure = {}), ] ) ] )
- Se crea un componente de gráfica
Graph
utilizando Dash. Se asigna el identificador'Mi_grafica_ST_2'
a la gráfica y se inicializa con una figura vacíafigure = {}
. Este identificador será útil para actualizar el contenido de la gráfica más adelante mediante un callback.
- Se crea un componente de gráfica
-
Descriptivo
html.Div([ ## agregamos un espacio html.Br(), ## agregamos texto html.P('Seleccione el origen de los datos', style = {'font-weight': 'bold','background-color': '#F2F2F2', 'border-radius': '3px','margin': '5px', 'padding': '7px','position': 'relative','display': 'inline-block', 'box-shadow': '4px 4px 4px 4px lightgrey', 'overflow-y': 'auto' }), ## agreamos las opciones de nuestros ogiren de datos, como la imagen correspondiente dcc.RadioItems(id = 'origing_data', labelStyle = {'display': 'inline-block',"align-items": "center", 'textAlign': 'center'}, options = [ {'label': [ html.Img(src="/assets/FGJ.png", height=60), ], 'value': 'Data_FGJ'}, {'label': [ html.Img(src="/assets/C5.png", height=60), ], 'value': 'Data_C5'} ], value = 'Data_FGJ', ), html.Br(), ## agregamos un Slider para seleccionar el año de la información que necesitamos dcc.Slider( id="slider-circular", min=2016, max=2023, marks={str(year): str(year) for year in range(2016, 2024)}, value=2016, step=1 ), html.Div(id = 'tabs-content02') ]),
- Se crea un contenedor
Div
que agrupa varios elementos dentro de él - Se agrega un salto de línea
<br>
para introducir un espacio entre elementos. - Se agrega un párrafo
<p>
que muestra el texto “Seleccione el origen de los datos”. El texto tiene algunos estilos CSS definidos, como negrita'font-weight': 'bold'
, un fondo de color'background-color': '#F2F2F2'
, un borde redondeado'border-radius': '3px'
, margen'margin': '5px'
y relleno'padding': '7px'
. El párrafo también tiene un sombreado'box-shadow'
para dar una apariencia visual de relieve. - Se crea un conjunto de elementos de opción
RadioItems
que permite al usuario seleccionar una de las dos opciones: “Data_FGJ” o “Data_C5”. Cada opción tiene una etiqueta que incluye una imagenhtml.Img
que se mostrará junto al texto de la opción. El valor predeterminado seleccionado es “Data_FGJ”. - Se agrega un control deslizante
Slider
que permite al usuario seleccionar un año entre 2016 y 2023. El valor predeterminado es 2016. Se muestran marcas en el control deslizante para cada año entre 2016 y 2023.
- Se crea un contenedor
Graficas
- Alcaldias
def update_graph_01_2016(value): fig = None if value == 'Data_FGJ': fig = px.treemap(conteo_colonia_2016, path=[px.Constant("Ciudad De México"), 'NOMDT'], values='ROBOS', color='NOMDT', color_continuous_scale='orrd', hover_data={'NOMUT': True, 'ROBOS': ':,.0f'}) fig.update_traces(marker=dict(cornerradius=5)) fig.update_traces(hovertemplate='<b>%{label}<br>ROBOS: %{customdata[1]:,.0f}') fig.update_layout(margin = dict(t = 15, l = 7, r = 7, b = 5,)) else: x = np.linspace(0, 10, 100) y = 2 * x fig = px.line(x = x, y = y) return fig
- Se define una función llamada
update_graph_01_2016
que toma un solo argumentovalue
. - Se inicializa la variable
fig
con el valorNone
. - Se realiza una comprobación del valor
value
para determinar qué tipo de gráfica se debe generar. Sivalue
es igual a'Data_FGJ'
, se procederá a generar un gráfico de tipo “Treemap” utilizando el DataFrameconteo_colonia_2016
. De lo contrario, se generará un gráfico de línea simple. - Si el valor es igual a
'Data_FGJ'
, se genera un gráfico de tipo “Treemap” utilizando la biblioteca Plotly Express (px). El gráfico se basará en el DataFrameconteo_colonia_2016
. path
Se define una jerarquía para representar la estructura del Treemap. Aquí, se utilizapath=[px.Constant("Ciudad De México"), 'NOMDT']
, lo que significa que el Treemap tendrá una primera capa con el título “Ciudad De México” y una segunda capa con la columna ‘NOMDT’ del DataFrame.values
Se define la columna del DataFrame que se utilizará para determinar el tamaño de las cajas en el Treemap. Aquí, se utiliza'ROBOS'
.color
Se define la columna del DataFrame que se utilizará para determinar el color de las cajas en el Treemap. Aquí, se utiliza'NOMDT'
.color_continuous_scale
Se define la escala de colores utilizada para el Treemap. Aquí, se utiliza el tema'orrd'
.hover_data
Se define qué información se mostrará cuando se pase el cursor sobre las cajas del Treemap. Aquí, se muestra el nombre de la colonia y el número de robos.
- Se define una función llamada
- Tiempo
def update_graph_02_2016(value): if value == 'Data_FGJ': fig = px.treemap(conteo_fecha_2016, path = [px.Constant("Robos CDMX 2016"), 'mes','dia'], values = 'Frecuencia', color = 'mes', color_continuous_scale = 'orrd', hover_data={'Frecuencia': ':,.0f'},) fig.update_traces(marker=dict(cornerradius=5), hovertemplate='<b>%{label}<br>ROBOS: %{customdata[0]}', insidetextfont=dict(color='white'), # Opcional: para que el texto dentro de las cajas sea blanco textfont_color='black', # Opcional: para que el texto fuera de las cajas sea negro ) fig.update_layout(margin = dict(t = 10, l = 7, r = 7, b = 5)) else: x = np.linspace(0, 10, 100) y = 6 * x fig = px.line(x = x, y = y) return fig
- Si el valor es igual a
'Data_FGJ'
, se genera un gráfico de tipo “Treemap” utilizando la biblioteca Plotly Express (px). El gráfico se basará en el DataFrameconteo_fecha_2016
. path
Se define una jerarquía para representar la estructura del Treemap. Aquí, se utilizapath=[px.Constant("Robos CDMX 2016"), 'mes','dia']
, lo que significa que el Treemap tendrá tres capas: una capa superior con el título “Robos CDMX 2016”, una capa intermedia con la columna ‘mes’ del DataFrame y una capa inferior con la columna ‘dia’ del DataFrame.values
Se define la columna del DataFrame que se utilizará para determinar el tamaño de las cajas en el Treemap. Aquí, se utiliza'Frecuencia'
.- Se retorna la figura de la gráfica generada
fig
ya sea el Treemap o el gráfico de línea.
- Si el valor es igual a
- Mapa
def update_graph_04_2016(value): if value == 'Data_FGJ': fig = px.choropleth_mapbox(shape_2016, geojson = shape_2016.geometry, locations = shape_2016.index, color = "ROBOS", color_continuous_scale = 'orrd', mapbox_style = "open-street-map", zoom = 9.7, center = {"lat": 19.4326, "lon": -99.1332}, opacity = 0.5,) fig.add_trace( go.Scattermapbox( lat=shape_2016.geometry.centroid.y, lon=shape_2016.geometry.centroid.x, mode='markers', hovertext=shape_2016['NOMDT'] + '<br>' + shape_2016['NOMUT'] + '<br>ROBOS: ' + shape_2016['ROBOS'].apply(lambda x: f'{int(x):,}'), hoverinfo='text', marker=dict( size=5, opacity=0), hoverlabel=dict( bgcolor='#FEDDDD'))) fig.update_layout( margin=dict(t=10, l=7, r=7, b=5), coloraxis_showscale=False) else: x = np.linspace(0, 10, 100) y = 6 * x fig = px.line(x=x, y=y)
- Si el valor es igual a
'Data_FGJ'
, se genera un gráfico de tipo “Choropleth Mapbox” utilizando la biblioteca Plotly Express (px). El gráfico se basará en el DataFrameshape_2016
. shape_2016
Es un DataFrame que contiene información geoespacial y datos sobre robos. Específicamente, para crear un mapa coroplético, donde cada polígono representa una región geográfica (como colonias o distritos) y se colorea según la cantidad de robos (columna “ROBOS”) en cada región.geojson
Se utilizashape_2016.geometry
como el objeto GeoJSON que contiene las formas geográficas de las regiones.locations
Se utilizashape_2016.index
para especificar las ubicaciones (índices) en el DataFrame que corresponden a las regiones geográficas.color
Se utiliza la columna “ROBOS” del DataFrame para determinar el color del mapa coroplético, donde los colores representan la cantidad de robos en cada región.mapbox_style
Se utiliza"open-street-map"
para definir el estilo del mapa base de Mapbox.- Se utiliza
zoom=9.7
ycenter={"lat": 19.4326, "lon": -99.1332}
para definir el nivel de zoom y el centro del mapa. - Se agrega una capa adicional de puntos
go.Scattermapbox
a la figura para representar los centroides de las regiones con puntos en el mapa. Estos puntos se utilizan para resaltar información adicional al pasar el cursor sobre elloshovertext
. El tamaño de los puntos es 5 y la opacidad es 0, lo que significa que son puntos invisibles pero permiten activar eventos de hover.
- Si el valor es igual a
Versión movil
Para acceder a los recursos y al codigo completo consulte el siguiente repositorio OsvaldoYa22/app_dash
Esta es una versión demo por lo que algunas funciones no estan completas, como es el caso de los datos de C5