Commit 1dceb447 authored by Pierre Dittgen's avatar Pierre Dittgen

OpenDataFrance dashboard (bar chart + filters)

parent 11c51253
# Dashboard using Dash
## Install
Inside a virtualenv:
```bash
pip install -r requirements.txt
```
## Run
```
python3 app.py
```
from operator import itemgetter
"""Interactive OpenDataFrance dashboard."""
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import app_data
COLORS = [
"rgba(31,119,180,1)",
"rgba(255,127,14,1)",
"rgba(44,160,44,1)",
"rgba(214,39,40,1)",
"rgba(148,103,189,1)",
"rgba(140,86,75,1)",
"rgba(127,119,194,1)",
"rgba(127,127,127,1)",
"rgba(188,189,34,1)",
"rgba(23,190,207,1)",
]
def noneIfEmpty(val):
  • Beware not using camel case in Python names :) We should add the flake8 plugin (I forget the name) for that.

Please register or sign in to reply
"""Return None if empty else original value."""
return None if val == "" else val
def computeDepDropdownOptions(reg_code=None):
"""Compute department dropdown option list."""
dep_list = [
{"label": dep[1], "value": dep[0]}
for dep in app_data.compute_dep_list(reg_code)
]
return [{"label": "-- département --", "value": ""}] + dep_list
def computeBarFigure(reg_code=None, dep_code=None):
"""Build a bar figure from given region and department code."""
df = app_data.df
if dep_code:
df = df[df["dep_code"] == dep_code]
elif reg_code:
df = df[df["reg_code"] == reg_code]
values, labels = app_data.compute_charts_data(df)
def create_figure(town: str = None):
if town is None:
title = "All towns"
data = [
{
"x": town_data.get("x"),
"y": town_data.get("y"),
"type": "bar",
"name": town_data["title"],
}
for town_data in sorted(towns.values(), key=itemgetter("title"))
]
else:
town_data = towns[town]
title = town_data["title"]
data = [
{
"x": town_data.get("x"),
"y": town_data.get("y"),
"type": "bar",
"name": title,
}
]
return {
"data": data,
"layout": {"title": title},
"data": [{"x": labels, "y": values, "type": "bar", "marker": {"color": COLORS}}]
}
towns = {
"NYC": {"title": "New York City"},
"MTL": {"title": "Montréal", "x": [1, 2, 3], "y": [2, 4, 5]},
"SF": {"title": "San Francisco", "x": [1, 2, 3], "y": [4, 1, 2]},
}
initial_town = "MTL"
app = dash.Dash(__name__)
app.layout = html.Div(
children=[
html.H1(children="Hello Dash"),
html.Div(
children="""
Dash: A web application framework for Python.
"""
),
html.H1(children="Observatoire opendata des territoires"),
dcc.Dropdown(
id="town",
id="reg_dd",
options=[
{"label": town_data["title"], "value": value}
for value, town_data in towns.items()
],
value=initial_town,
),
html.Div(id="label1"),
html.Div(
children=[
dcc.Graph(id="graph-selected", figure=create_figure(town=initial_town)),
dcc.Graph(id="graph-all", figure=create_figure()),
{"label": "-- région --", "value": ""},
*[{"label": reg[1], "value": reg[0]} for reg in app_data.reg_list],
],
style={"columnCount": 2},
value="",
),
dcc.Dropdown(id="dep_dd", options=computeDepDropdownOptions(), value="",),
dcc.Graph(id="type_bar", figure=computeBarFigure()),
]
)
@app.callback(
Output(component_id="graph-selected", component_property="figure"),
[Input(component_id="town", component_property="value")],
Output(component_id="dep_dd", component_property="options"),
[Input(component_id="reg_dd", component_property="value")],
)
def update_dep_dropdown_options(reg_code):
"""Update department list when a region has been chosen."""
return computeDepDropdownOptions(reg_code=noneIfEmpty(reg_code))
@app.callback(
Output(component_id="dep_dd", component_property="value"),
[Input(component_id="reg_dd", component_property="value")],
)
def update_dep_dropdown_value(reg_code):
"""Update department list when a region has been chosen."""
return ""
@app.callback(
Output(component_id="type_bar", component_property="figure"),
[
Input(component_id="reg_dd", component_property="value"),
Input(component_id="dep_dd", component_property="value"),
],
)
def update_trucmuche(input_value):
return create_figure(town=input_value)
def update_type_bar(reg_code, dep_code):
"""Update bar chart when one dropdown value changes."""
return computeBarFigure(
reg_code=noneIfEmpty(reg_code), dep_code=noneIfEmpty(dep_code)
)
if __name__ == "__main__":
......
"""Manage data used in dashboard."""
import unicodedata
from collections import defaultdict
from operator import itemgetter
from typing import Dict, List
import pandas as pd
OPENDATA_ORG_CSV_URL = (
"https://git.opendatafrance.net/observatoire/observatoire-data"
+ "/raw/master/organizations.csv"
)
def unaccentize(str_with_accents):
"""Transform 'Hélène à côté' into 'Helene a cote'."""
  • It is possible to use doctest for that.

    >>> unaccentize('Hélène à côté')
    'Helene a cote'

    so that it is tested by pytest in CI for non-regression.

Please register or sign in to reply
return "".join(
c
for c in unicodedata.normalize("NFD", str_with_accents)
if unicodedata.category(c) != "Mn"
)
# Get data and prepare global variables
df = pd.read_csv(OPENDATA_ORG_CSV_URL)
df.columns = [c.replace("-", "_") for c in df.columns]
df = df.astype({"reg_code": str, "dep_code": str})
reg_dep_set = {
tuple(map(str, row.tolist()))
for _, row in df[["reg_code", "reg_nom", "dep_code", "dep_nom"]].iterrows()
}
COLL_TYPES = sorted(df["type"].unique().tolist())
# (reg_code, reg_nom) list
reg_set = {(t[0], t[1]) for t in reg_dep_set}
reg_list = sorted(reg_set, key=lambda t: unaccentize(t[1]))
# (reg_code, dep_code, dep_nom) list
reg_dep_list = [(t[0], t[2], t[3]) for t in sorted(reg_dep_set, key=itemgetter(2))]
# { reg_code : list of dep_code }
reg_dep_list_map: Dict[str, List[str]] = defaultdict(list)
for t in reg_dep_set:
reg_dep_list_map[t[0]].append(t[2])
# { dep_code: reg_code }
dep_reg_map = {t[1]: t[0] for t in reg_dep_list}
def compute_dep_list(reg_code=None):
"""Compute department tuples (name, code)."""
if reg_code is None:
# No selected region, list all department tuples
return [(d[1], d[2]) for d in reg_dep_list]
else:
# Filter on region code
return [(d[1], d[2]) for d in reg_dep_list if d[0] == reg_code]
def compute_charts_data(df):
"""Compute (values, labels) from given dataframe."""
counts_data = df.groupby("type").agg("count").siren
counts_map = dict(zip(counts_data.index.tolist(), counts_data.values.tolist()))
return [counts_map.get(lo, 0) for lo in COLL_TYPES], COLL_TYPES
dash
pandas
\ No newline at end of file
......@@ -16,7 +16,11 @@ future==0.18.2 # via dash
itsdangerous==1.1.0 # via flask
jinja2==2.10.3 # via flask
markupsafe==1.1.1 # via jinja2
numpy==1.18.1 # via pandas
pandas==0.25.3
plotly==4.4.1 # via dash
python-dateutil==2.8.1 # via pandas
pytz==2019.3 # via pandas
retrying==1.3.3 # via plotly
six==1.13.0 # via plotly, retrying
six==1.13.0 # via plotly, python-dateutil, retrying
werkzeug==0.16.0 # via flask
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment