Commit 33da8852 authored by Samuël Weber's avatar Samuël Weber
Browse files

add component to plot individual pmf

parent 09b98717
......@@ -2,6 +2,7 @@ import dash_core_components as dcc
import dash_bootstrap_components as dbc
import dash_html_components as html
import plotly.graph_objs as go
import plotly.express as px
import pandas as pd
import sqlite3
import pyOPestimator
......@@ -15,7 +16,7 @@ def get_station_dropdown_component(stations):
id="station-dropdown",
options=[{'label': s, 'value': s} for s in stations],
multi=True,
value=stations
value=["CHAM"]
)
],
style={"position": "relative"}
......@@ -282,6 +283,48 @@ def get_rawdata_timeserie_component():
]
return timeserie_component
def get_profile_and_contribution_component():
profile_component = html.Div(
id="pmf-profile-and-contribution-per-station",
children=[
html.H3(
"""PMF solution obtained for a given site
""",
id="title-pmf-per-station",
),
dcc.Markdown(
"""
Percentage of each species apportionned by the given source (in %) and relative
mass contribution to the PM10 mass (in g.g⁻¹). The contribution timeserie is in
µg.m⁻³.
The variability presented here come from the 100 bootstraps (BS) uncertainties
estimation.
"""
),
dbc.Row(
[
dcc.Graph(
id="concentration-graph-per-station",
figure={'data': [], "layout": {"title": "Relative concentration"}},
className="col-12 col-xl-6"
),
dcc.Graph(
id="totalspeciesum-graph-per-station",
figure={'data': [], "layout": {"title": "Contribution to total specie sum"}},
className="col-12 col-xl-6"
),
dcc.Graph(
id="contribution-graph-per-station",
figure={'data': [], "layout": {"title": "Temporal contribution"}},
className="col-12"
)
]
)
]
)
return profile_component
def get_profile_component():
profile_component = html.Div(
id="tab-concentration",
......@@ -772,6 +815,57 @@ def plot_box(df, trace_name, x_var, y_var, groupby=None, plot_type="box"):
return traces
def plot_ts_errorbar(df_mean, df_std, source, hue=None):
if hue is None:
hue = "Station"
levels = df_mean[hue].unique()
traces = []
for level in levels:
mean = df_mean.loc[df_mean[hue]==level].sort_values(by="Date")
std = df_std.loc[df_std[hue]==level].sort_values(by="Date")
# mean
traces.append(
go.Scatter(
x=mean.loc[:, "Date"],
y=mean.loc[:, source],
mode="lines+markers",
marker=dict(
color=get_sourceColor(source),
),
name=source
)
)
# std
traces.append(
go.Scatter(
x=std["Date"],
y=mean[source] + std[source],
mode="lines",
line=dict(width=0),
marker=dict(color="#444"),
showlegend=False,
name='BS std',
)
)
traces.append(
go.Scatter(
x=std["Date"],
y=mean[source] - std[source],
mode="lines",
line=dict(width=0),
marker=dict(color="#444"),
fillcolor='rgba(68, 68, 68, 0.3)',
fill='tonexty',
showlegend=False,
name='BS std',
)
)
return traces
def get_contribution(factors, species, stations):
# contribtmp = contrib.set_index(["station", "date"], drop=True)
# profiletmp = profile.set_index(["station", "specie"], drop=True)
......
......@@ -59,6 +59,7 @@ COMPONENTS = collections.OrderedDict({})
COMPONENTS["rd_ts"] = ac.get_rawdata_timeserie_component()
COMPONENTS["rd_monthly"] = ac.get_rawdata_monthly_component()
COMPONENTS["rd_seasonal"] = ac.get_rawdata_seasonal_component()
COMPONENTS["pmf_profile_and_contribution"] = ac.get_profile_and_contribution_component()
COMPONENTS["pmf_profiles"] = ac.get_profile_component()
COMPONENTS["pmf_deltatool"] = ac.get_deltatool_component()
COMPONENTS["pmf_unc"] = ac.get_uncertainty_component()
......@@ -102,11 +103,12 @@ layout = dbc.Container(
),
dbc.Row(
id="main",
className="justify-content-around",
children=[
# first column
dbc.Col(
id="first-column",
sm=12, md=4, xl=3,
sm=2,
children=[
dbc.Navbar([
dbc.NavbarToggler(id="items-toggler"),
......@@ -118,32 +120,34 @@ layout = dbc.Container(
[
dbc.ListGroupItemHeading("Raw data"),
dbc.NavLink('Timeserie',
href="/results?component=rd_ts", id='item-rd_ts', n_clicks=0),
href="/results?component=rd_ts", id='item-rd_ts'),
dbc.NavLink('Montlhy',
href="/results?component=rd_monthly", id='item-rd_monthly', n_clicks=0),
href="/results?component=rd_monthly", id='item-rd_monthly'),
dbc.NavLink('Seasonal',
href="/results?component=rd_seasonal", id='item-rd_seasonal', n_clicks=0),
href="/results?component=rd_seasonal", id='item-rd_seasonal'),
dbc.ListGroupItemHeading("PMF factor chemistry"),
dbc.NavLink('Profile and contribution per station',
href='/results?component=pmf_profile_and_contribution', id='item-pmf_profile_and_contribution'),
dbc.NavLink('Profiles comparison',
href='/results?component=pmf_profiles', id='item-pmf_profiles', n_clicks=0),
href='/results?component=pmf_profiles', id='item-pmf_profiles'),
dbc.NavLink('DeltaTool',
href="/results?component=pmf_deltatool", id='item-pmf_deltatool', n_clicks=0),
href="/results?component=pmf_deltatool", id='item-pmf_deltatool'),
dbc.NavLink('Uncertainties',
href="/results?component=pmf_unc", id='item-pmf_unc', n_clicks=0),
href="/results?component=pmf_unc", id='item-pmf_unc'),
dbc.NavLink('Species repartition',
href="/results?component=pmf_speciesrepartition", id='item-pmf_speciesrepartition', n_clicks=0),
href="/results?component=pmf_speciesrepartition", id='item-pmf_speciesrepartition'),
dbc.ListGroupItemHeading("OP model"),
dbc.NavLink('Obs. vs. model',
href="/results?component=op_obsvsmodel", id='item-op_obsvsmodel', n_clicks=0),
href="/results?component=op_obsvsmodel", id='item-op_obsvsmodel'),
dbc.NavLink('Intrinsic OP',
href="/results?component=op_beta", id='item-op_beta', n_clicks=0),
href="/results?component=op_beta", id='item-op_beta'),
dbc.NavLink('OP contribution (all)',
href="/results?component=op_contrib", id='item-op_contrib', n_clicks=0),
href="/results?component=op_contrib", id='item-op_contrib'),
dbc.NavLink('OP contribution (timeseries)',
href="/results?component=op_contrib_ts", id='item-op_contrib_ts', n_clicks=0),
href="/results?component=op_contrib_ts", id='item-op_contrib_ts'),
dbc.ListGroupItemHeading("I need help!"),
dbc.NavLink("Don't worry, click here.",
href="/results?component=help", id="item-help", n_clicks=0)
href="/results?component=help", id="item-help")
],
vertical=True,
pills=True
......@@ -157,7 +161,7 @@ layout = dbc.Container(
# second column
dbc.Col(
id="graph-component",
sm=12, md=8, xl=9,
sm=10,
children=ac.get_about_app_results_component()
)
],
......@@ -322,7 +326,7 @@ def get_graph_component(search):
source_disabled = True
specie_disabled = True
OP_disabled = True
if clicked in ['rd_ts', 'rd_monthly', 'rd_seasonal', 'pmf_profiles', 'pmf_deltatool', 'pmf_unc', 'op_beta']:
if clicked in ['rd_ts', 'rd_monthly', 'rd_seasonal', 'pmf_profile_and_contribution', 'pmf_profiles', 'pmf_deltatool', 'pmf_unc', 'op_beta']:
source_disabled = False
if clicked in ['pmf_unc']:
specie_disabled = False
......@@ -751,6 +755,219 @@ def update_concentration_graph(sources, stations):
return figure
@app.callback([
Output('title-pmf-per-station', 'children'),
Output('concentration-graph-per-station', 'figure'),
Output('totalspeciesum-graph-per-station', 'figure'),
Output('contribution-graph-per-station', 'figure'),
],
[
Input('source-dropdown', 'value'),
Input('station-dropdown', 'value'),
])
def update_pmf_details_per_station(sources, stations):
data_per_microgram = []
data_total_specie_sum = []
data_ts = []
figure_per_microgram = {
"data": data_per_microgram,
"layout": {"title": "Relative concentration"}
}
figure_total_specie_sum = {
"data": data_total_specie_sum,
"layout": {"title": "Contribution to total specie sum"}
}
figure_ts = {
"data": data_ts,
"layout": {"title": "Temporal contribution"}
}
figures = [figure_per_microgram, figure_total_specie_sum, figure_ts]
title = "PMF solution obtained for a given site"
if len(stations) == 0:
for figure in figures:
figure["layout"]["title"] = "Select at least one station"
return [title]+figures
if len(sources) == 0:
for figure in figures:
figure["layout"]["title"] = "Select at least one source"
return [title]+figures
if len(stations) != 1:
for figure in figures:
figure["layout"]["title"] = "Select one (and only one) station"
return [title]+figures
query_contrib = "SELECT \"{sources}\" FROM SRC WHERE Station IN ('{stations}');".format(
sources='", "'.join(sources+["Station", "Date"]),
stations="', '".join(stations),
)
query_profile = "SELECT \"{sources}\" FROM profiles_constrained WHERE Station IN ('{stations}');".format(
sources='", "'.join(sources+["Station", "Specie"]),
stations="', '".join(stations),
)
query_BS = (
"SELECT * FROM BS_profiles_constrained "
"WHERE Station IN ('{stations}') "
"AND Specie IN ('{species}') "
).format(
stations="', '".join(stations),
species="', '".join(SPECIES_ORDER_WO_OP),
)
conn = sqlite3.connect(DBPATH)
contrib = pd.read_sql(query_contrib, con=conn, index_col=["Station", "Date"], parse_dates=["Date"])
profiles = pd.read_sql(query_profile, con=conn, index_col=["Station", "Specie"])
BS_profiles = pd.read_sql(query_BS, con=conn, index_col=["Profile", "Specie", "Station"])
conn.close()
# Per microgram ================================================================
profiles_normalized = profiles / profiles.xs("PM10", level="Specie")
profiles_normalized = profiles_normalized.reset_index()
for source in sources:
BS_normalized = (
BS_profiles.xs(source, level="Profile")
/ (BS_profiles.xs(source, level="Profile").loc["PM10"])
).reset_index().melt(id_vars=["Station", "Specie"], value_name=source)
df_plot = BS_normalized.loc[BS_normalized["Station"].isin(stations)]
data_per_microgram.append(go.Box(
x=df_plot["Specie"],
y=df_plot[source],
marker=dict(color=ac.get_sourceColor(source)),
name="{}".format(source)
)
)
# specie to plot
figure_per_microgram = {
'data': data_per_microgram,
'layout': go.Layout(
title="Contribution to the mass of PM10",
yaxis={"title": "g/g of PM10",
"type": "log",
"range": [-6, 1]
},
xaxis={
'categoryorder': 'array',
'categoryarray': SPECIES_ORDER_WO_OP,
},
showlegend=True,
boxmode='group',
margin=go.layout.Margin(
l=50,
r=00,
b=50,
t=50,
pad=0
),
legend=dict(orientation="h")
)
}
# Total specie sum =============================================================
for source in sources:
BS_sum = BS_profiles.groupby("Specie").sum()
species = BS_profiles.index.get_level_values("Specie").unique()
df_plot = pd.DataFrame(columns=BS_sum.columns, index=BS_sum.index)
for BS in BS_sum.columns:
df_plot[BS] = BS_profiles.xs(source, level="Profile").droplevel("Station")[BS].divide(BS_sum[BS]) * 100
df_plot.index.names = ["Specie"]
df_plot = df_plot.reset_index().melt(
id_vars=["Specie"],
value_name="BS"
)
data_total_specie_sum.append(
go.Box(
x=df_plot["Specie"],
y=df_plot["BS"],
marker=dict(color=ac.get_sourceColor(source)),
name=source
)
)
figure_total_specie_sum = {
'data': data_total_specie_sum,
'layout': go.Layout(
title="Contribution to total specie sum",
yaxis={
"title": "% of total specie sum",
"range": [0, 100]
},
xaxis={'categoryorder': 'array',
'categoryarray': SPECIES_ORDER_WO_OP},
showlegend=True,
boxmode='group',
margin=go.layout.Margin(
l=50,
r=00,
b=50,
t=50,
pad=0
),
legend=dict(orientation="h")
)
}
# Temporal contribution ================================================================
contrib_norm = (contrib / profiles.xs("PM10", level="Specie"))
df_mean = pd.DataFrame(
index=contrib_norm.index,
columns=sources
)
df_std = pd.DataFrame(
index=contrib_norm.index,
columns=sources
)
# compute the mean and std given the BS
for specie in ["PM10"]:
for source in sources:
d = pd.DataFrame(
columns=BS_profiles.columns,
index=contrib_norm.index
)
for BS in BS_profiles.columns:
d[BS] = contrib_norm[source] * BS_profiles.xs(source, level="Profile").loc[specie][BS]
df_mean[source] = d.mean(axis=1)
df_std[source] = d.std(axis=1)
for source in sources:
data_ts += ac.plot_ts_errorbar(df_mean.reset_index(), df_std.reset_index(), source)
figure_ts = {
'data': data_ts,
'layout': go.Layout(
title="Contribution to the mass of PM10",
yaxis={
"title": "µg/m⁻³",
},
showlegend=True,
margin=go.layout.Margin(
l=50,
r=00,
b=50,
t=50,
pad=0
),
legend=dict(orientation="h")
)
}
figures[0] = figure_per_microgram
figures[1] = figure_total_specie_sum
figures[2] = figure_ts
title = "PMF solution obtained for the site of {}".format(stations[0])
values = [title] + figures
return values
@app.callback(Output('uncertainty-graph-conc', 'figure'),
[
......
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