diff --git a/notebooks/wp3/uncertainty_densityplot.ipynb b/notebooks/wp3/uncertainty_densityplot.ipynb
new file mode 100644
index 0000000..fd3044a
--- /dev/null
+++ b/notebooks/wp3/uncertainty_densityplot.ipynb
@@ -0,0 +1,320 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "43f58c96",
+ "metadata": {},
+ "source": [
+ "# Uncertainty in seasonal forecasts for a single model system"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "c7ce5c2e",
+ "metadata": {},
+ "source": [
+ "## Import packages"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "48246700",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import calendar\n",
+ "\n",
+ "import numpy as np\n",
+ "import plotly.figure_factory as ff\n",
+ "import xarray as xr\n",
+ "from c3s_eqc_automatic_quality_control import diagnostics, download, utils"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "5586e956",
+ "metadata": {},
+ "source": [
+ "## Define Parameters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3a3dfa6f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Model\n",
+ "centre = \"ecmwf\"\n",
+ "system = \"51\"\n",
+ "\n",
+ "# Time\n",
+ "year_forecast = 2023\n",
+ "year_start_hindcast = 1993\n",
+ "year_stop_hindcast = 2016\n",
+ "month = 6\n",
+ "\n",
+ "# Region\n",
+ "region_name = \"Southern Norway\"\n",
+ "lat_slice = slice(64, 58)\n",
+ "lon_slice = slice(4, 14)\n",
+ "\n",
+ "# Download parameters\n",
+ "chunks = {\"year\": 1}\n",
+ "n_jobs = 1"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "62c65518",
+ "metadata": {},
+ "source": [
+ "## Define request"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9ca06bda",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "collection_id = \"seasonal-monthly-single-levels\"\n",
+ "\n",
+ "request = {\n",
+ " \"format\": \"grib\",\n",
+ " \"originating_centre\": centre,\n",
+ " \"system\": system,\n",
+ " \"variable\": \"2m_temperature\",\n",
+ " \"product_type\": \"monthly_mean\",\n",
+ " \"leadtime_month\": list(map(str, range(1, 7))),\n",
+ " \"area\": [89.5, -179.5, -89.5, 179.5],\n",
+ " \"grid\": \"1/1\",\n",
+ " \"month\": f\"{month:02d}\",\n",
+ "}"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "51a4fdbf",
+ "metadata": {},
+ "source": [
+ "## Functions to cache"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b859c808-ea82-4f63-a6eb-bf58229c2e9d",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def regionalised_mean(ds, lon_slice, lat_slice, weights):\n",
+ " ds = utils.regionalise(ds, lon_slice=lon_slice, lat_slice=lat_slice)\n",
+ " ds = diagnostics.spatial_weighted_mean(ds, weights=weights)\n",
+ " with xr.set_options(keep_attrs=True):\n",
+ " ds[\"t2m\"] -= 273.15\n",
+ " ds[\"t2m\"].attrs[\"units\"] = \"°C\"\n",
+ " return ds"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "90606022",
+ "metadata": {},
+ "source": [
+ "## Download and transform"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2a939f1e-7e91-4501-9ffc-1eca70253a36",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "datasets = {}\n",
+ "for model, years in {\n",
+ " \"hindcast\": range(year_start_hindcast, year_stop_hindcast + 1),\n",
+ " \"forecast\": [year_forecast],\n",
+ "}.items():\n",
+ " ds = download.download_and_transform(\n",
+ " collection_id,\n",
+ " request | {\"year\": list(map(str, years))},\n",
+ " chunks=chunks,\n",
+ " n_jobs=n_jobs,\n",
+ " transform_func=regionalised_mean,\n",
+ " transform_func_kwargs={\n",
+ " \"lon_slice\": lon_slice,\n",
+ " \"lat_slice\": lat_slice,\n",
+ " \"weights\": False,\n",
+ " },\n",
+ " backend_kwargs={\n",
+ " \"time_dims\": (\n",
+ " \"forecastMonth\",\n",
+ " \"indexing_time\" if centre in [\"ukmo\", \"jma\", \"ncep\"] else \"time\",\n",
+ " )\n",
+ " },\n",
+ " cached_open_mfdataset_kwargs={\n",
+ " \"combine\": \"nested\",\n",
+ " \"concat_dim\": \"forecast_reference_time\",\n",
+ " },\n",
+ " )\n",
+ " datasets[model] = ds"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "id": "5ecce3e6",
+ "metadata": {},
+ "source": [
+ "## Density plot"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5b2d38a8",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def get_limits(data, xfactor, yfactor):\n",
+ " ylim = [0, max([max(d.y) for d in data]) * yfactor]\n",
+ " xlim = [func([func(d.x) for d in data]) for func in (min, max)]\n",
+ " xshift = abs(xlim[1] - xlim[0]) * xfactor\n",
+ " xlim = [x + xshift * sign for x, sign in zip(xlim, (-1, 1))]\n",
+ " return xlim, ylim\n",
+ "\n",
+ "\n",
+ "# Density plot for each lead time\n",
+ "for leadtime_month, ds_forecast in datasets[\"forecast\"].groupby(\"leadtime_month\"):\n",
+ " ds_hindcast = datasets[\"hindcast\"].sel(leadtime_month=leadtime_month)\n",
+ "\n",
+ " colors = [(26, 150, 65), (100, 50, 150)]\n",
+ " values = [\n",
+ " ds_hindcast[\"t2m\"].values.flatten(),\n",
+ " ds_forecast[\"t2m\"].values.flatten(),\n",
+ " ]\n",
+ " labels = [centre + \" climatology\", centre + \" forecast\"]\n",
+ " fig = ff.create_distplot(\n",
+ " values,\n",
+ " labels,\n",
+ " show_hist=False,\n",
+ " show_rug=True,\n",
+ " colors=[f\"rgb{color}\" for color in colors],\n",
+ " curve_type=\"kde\",\n",
+ " )\n",
+ " for color, data in zip(colors, fig.data):\n",
+ " # Fill area under distline\n",
+ " fig.add_scatter(\n",
+ " x=data.x,\n",
+ " y=data.y,\n",
+ " fill=\"tozeroy\",\n",
+ " mode=\"none\",\n",
+ " fillcolor=f\"rgba{color + (.4,)}\",\n",
+ " showlegend=False,\n",
+ " )\n",
+ "\n",
+ " xlim, ylim = get_limits(fig.data[:2], xfactor=0.03, yfactor=1.4)\n",
+ " quantiles = np.quantile(values[0], [1 / 3, 2 / 3]).tolist()\n",
+ " scatter_dicts = {\n",
+ " \"lower tercile\": {\n",
+ " \"color\": (0, 180, 250, 0.1),\n",
+ " \"text\": \"COLD\",\n",
+ " \"mask\": values[1] <= quantiles[0],\n",
+ " \"xlim\": [xlim[0], quantiles[0]],\n",
+ " },\n",
+ " \"middle tercile\": {\n",
+ " \"color\": (230, 230, 0, 0.1),\n",
+ " \"text\": \"NEAR AVERAGE\",\n",
+ " \"mask\": (values[1] > quantiles[0]) & (values[1] <= quantiles[1]),\n",
+ " \"xlim\": quantiles,\n",
+ " },\n",
+ " \"upper tercile\": {\n",
+ " \"color\": (250, 50, 0, 0.1),\n",
+ " \"text\": \"WARM\",\n",
+ " \"mask\": values[1] > quantiles[1],\n",
+ " \"xlim\": [quantiles[1], xlim[1]],\n",
+ " },\n",
+ " }\n",
+ " for i, (name, scatter_dict) in enumerate(scatter_dicts.items()):\n",
+ " # Add background color and text\n",
+ " fig.add_scatter(\n",
+ " x=scatter_dict[\"xlim\"],\n",
+ " y=[ylim[1]] * 2,\n",
+ " fill=\"tozeroy\",\n",
+ " mode=\"none\",\n",
+ " fillcolor=f\"rgba{scatter_dict['color']}\",\n",
+ " name=name,\n",
+ " )\n",
+ " percentage = 100 * scatter_dict[\"mask\"].sum() / values[1].size\n",
+ " text_color = tuple(c // 2 for c in scatter_dict[\"color\"][:-1]) + (0.4,)\n",
+ " fig.add_scatter(\n",
+ " x=[sum(scatter_dict[\"xlim\"]) / 2],\n",
+ " y=[ylim[1] * 0.98],\n",
+ " mode=\"text\",\n",
+ " name=\"\",\n",
+ " text=f\"{scatter_dict['text']}
{round(percentage)}%\",\n",
+ " textfont=dict(size=18, color=f\"rgba{text_color}\"),\n",
+ " textposition=\"bottom center\",\n",
+ " showlegend=False,\n",
+ " )\n",
+ "\n",
+ " # Title and labels\n",
+ " forecast_reference_time = ds_forecast[\"forecast_reference_time\"].dt.date.values\n",
+ " title = (\n",
+ " f\"Density plot of {ds_forecast['t2m'].attrs['long_name']}\"\n",
+ " f\" over {region_name} for {calendar.month_abbr[leadtime_month]} {year_forecast},\"\n",
+ " f\"
from {centre} with start time {forecast_reference_time}.\"\n",
+ " f\" Hindcast period from {year_start_hindcast} to {year_stop_hindcast}.\"\n",
+ " )\n",
+ " fig.update_layout(\n",
+ " title=dict(text=title, font={\"size\": 22}),\n",
+ " yaxis_range=ylim,\n",
+ " xaxis_range=xlim,\n",
+ " font_size=17,\n",
+ " autosize=False,\n",
+ " width=900,\n",
+ " height=500,\n",
+ " )\n",
+ " fig.update_xaxes(\n",
+ " title_text=(\n",
+ " f\"Mean {ds_forecast['t2m'].attrs['long_name']}\"\n",
+ " f\" ({ds_forecast['t2m'].attrs['units']})\"\n",
+ " )\n",
+ " )\n",
+ " fig.show()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}