From 71d441e9134d66322b1953c74201edce1aeab221 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Thu, 16 Jul 2026 07:44:13 +0100 Subject: [PATCH 1/6] update capacity plot --- notebooks/capacity.ipynb | 52 +++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/notebooks/capacity.ipynb b/notebooks/capacity.ipynb index 463398d..3e3a412 100644 --- a/notebooks/capacity.ipynb +++ b/notebooks/capacity.ipynb @@ -67,16 +67,30 @@ "source": [ "# The assets.csv file contains info about which assets were invested in and when\n", "assets = pd.read_csv(OUTPUT_DIR / \"assets.csv\")\n", + "# The asset_capacities.csv file contains info about the capacities for each asset\n", + "# along the simulation\n", + "asset_capacities = pd.read_csv(OUTPUT_DIR / \"asset_capacities.csv\")\n", "\n", - "# Assets with no decommission_year are effectively decommissioned after time horizon\n", - "assets[\"decommission_year\"] = assets[\"decommission_year\"].fillna(years[-1] + 1)\n", + "\n", + "# We define a helper function to bring some useful information from 'assets' into\n", + "#'assets_capacity'.\n", + "def get_agent_and_process(x: pd.Series) -> pd.Series:\n", + " \"\"\"Collects \"agent_id\", \"process_id\", \"commission_year\" from assets.\"\"\"\n", + " col, val = (\n", + " (\"asset_id\", x.asset_id) if x.asset_id is not None else (\"group_id\", x.group_id)\n", + " )\n", + " row = assets[assets[col] == val].iloc[0]\n", + " return row[[\"agent_id\", \"process_id\", \"commission_year\"]]\n", + "\n", + "\n", + "asset_capacities = pd.concat(\n", + " [asset_capacities, asset_capacities.apply(get_agent_and_process, axis=1)], axis=1\n", + ")\n", "\n", "# Calculate capacity for each type of process for each agent\n", "capacity = pd.DataFrame()\n", "for year in years:\n", - " active = assets[\n", - " (year >= assets[\"commission_year\"]) & (year < assets[\"decommission_year\"])\n", - " ]\n", + " active = asset_capacities[year >= asset_capacities[\"commission_year\"]]\n", "\n", " # This only works because each agent is responsible for one and only one commodity\n", " cap_sum = active.groupby([\"agent_id\", \"process_id\"])[\"capacity\"].sum().reset_index()\n", @@ -112,7 +126,7 @@ "import matplotlib.pyplot as plt\n", "\n", "agents = capacity[\"agent_id\"].unique()\n", - "_, axes = plt.subplots(1, len(agents))\n", + "_, axes = plt.subplots(1, len(agents), figsize=(4 * len(agents), 4))\n", "for ax, agent in zip(axes, agents):\n", " capacity[capacity[\"agent_id\"] == agent].pivot(\n", " index=\"year\", columns=\"process_id\", values=\"capacity\"\n", @@ -120,13 +134,33 @@ " ax.set_title(agent)\n", " ax.set_xlabel(\"Year\")\n", " ax.set_ylabel(\"Capacity\")\n", - " ax.legend(title=\"Process\")" + " ax.legend(\n", + " title=\"Process\", bbox_to_anchor=(1.05, 1), loc=\"upper left\", borderaxespad=0.0\n", + " )\n", + "\n", + "plt.tight_layout()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "muse2-data-analysis", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -140,7 +174,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.2" + "version": "3.14.6" } }, "nbformat": 4, From 6cf4d6828dcf13b94b77dd91c03ed9f1146d538d Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Thu, 16 Jul 2026 07:45:35 +0100 Subject: [PATCH 2/6] Move legend in prices plot --- notebooks/prices.ipynb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/notebooks/prices.ipynb b/notebooks/prices.ipynb index 12deee7..94c58f3 100644 --- a/notebooks/prices.ipynb +++ b/notebooks/prices.ipynb @@ -50,13 +50,23 @@ "\n", "ax.set_xlabel(\"Milestone year\")\n", "ax.set_ylabel(\"Price\")\n", - "ax.legend(title=\"Time slice\");" + "ax.legend(\n", + " title=\"Time slice\", bbox_to_anchor=(1.05, 1), loc=\"upper left\", borderaxespad=0.0\n", + ");" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "muse2-data-analysis", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -70,7 +80,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.2" + "version": "3.14.6" } }, "nbformat": 4, From e524b00ae4e91f791ac56fcf52af109b3682ee71 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Fri, 17 Jul 2026 09:07:35 +0100 Subject: [PATCH 3/6] Simplify code following review --- notebooks/capacity.ipynb | 101 ++++++++------------------------------- 1 file changed, 19 insertions(+), 82 deletions(-) diff --git a/notebooks/capacity.ipynb b/notebooks/capacity.ipynb index 3e3a412..636e336 100644 --- a/notebooks/capacity.ipynb +++ b/notebooks/capacity.ipynb @@ -10,11 +10,7 @@ "This notebook contains example data processing using the output of an example\n", "model.\n", "\n", - "Output files are mostly in CSV format. The format of output files is documented [here][output-format].\n", - "\n", - "We begin by loading the `model.toml` input file to get the list of milestone years.\n", - "\n", - "[output-format]: https://energysystemsmodellinglab.github.io/MUSE2/file_formats/output_files.html" + "Output files are mostly in CSV format. The format of output files is documented [here][output-format]." ] }, { @@ -24,20 +20,11 @@ "metadata": {}, "outputs": [], "source": [ - "import tomllib\n", - "\n", "import pandas as pd\n", "\n", - "from muse2_data_analysis.helpers import get_example_input_dir, get_example_output_dir\n", - "\n", - "INPUT_DIR = get_example_input_dir()\n", - "OUTPUT_DIR = get_example_output_dir()\n", + "from muse2_data_analysis.helpers import get_example_output_dir\n", "\n", - "with (INPUT_DIR / \"model.toml\").open(\"rb\") as f:\n", - " model = tomllib.load(f)\n", - "\n", - "# We need to know the milestone years for processing the assets file\n", - "years = model[\"milestone_years\"]" + "OUTPUT_DIR = get_example_output_dir()" ] }, { @@ -48,14 +35,13 @@ "## Load and process output data\n", "\n", "We next load the output data. In this case, we want to calculate how much capacity was invested in\n", - "different processes for different agents. This information can be found in the `assets.csv` output\n", - "file.\n", + "different processes for different agents. This information can be found in the `asset_capacities.csv`\n", + "output file.\n", "\n", - "The `assets.csv` file contains information about different assets, including when they were\n", - "commissioned and decommissioned as well as their capacity. To calculate the overall capacity for a\n", - "given agent and process type, we have to process this data. Note that different assets owned by the\n", - "same agent may have the same process ID if the agent has reinvested in the same process type in a\n", - "different year." + "We also need some metadata, like the agent that manages the asset and the process the asset corresponds\n", + "to. That time-independent information is contained in the `assets.csv` file. To calculate the overall\n", + "capacity for a given agent and process type as a function of milestone year, we have to combine these\n", + "two files and process the data." ] }, { @@ -65,42 +51,15 @@ "metadata": {}, "outputs": [], "source": [ - "# The assets.csv file contains info about which assets were invested in and when\n", "assets = pd.read_csv(OUTPUT_DIR / \"assets.csv\")\n", - "# The asset_capacities.csv file contains info about the capacities for each asset\n", - "# along the simulation\n", "asset_capacities = pd.read_csv(OUTPUT_DIR / \"asset_capacities.csv\")\n", "\n", - "\n", - "# We define a helper function to bring some useful information from 'assets' into\n", - "#'assets_capacity'.\n", - "def get_agent_and_process(x: pd.Series) -> pd.Series:\n", - " \"\"\"Collects \"agent_id\", \"process_id\", \"commission_year\" from assets.\"\"\"\n", - " col, val = (\n", - " (\"asset_id\", x.asset_id) if x.asset_id is not None else (\"group_id\", x.group_id)\n", - " )\n", - " row = assets[assets[col] == val].iloc[0]\n", - " return row[[\"agent_id\", \"process_id\", \"commission_year\"]]\n", - "\n", - "\n", - "asset_capacities = pd.concat(\n", - " [asset_capacities, asset_capacities.apply(get_agent_and_process, axis=1)], axis=1\n", - ")\n", - "\n", - "# Calculate capacity for each type of process for each agent\n", - "capacity = pd.DataFrame()\n", - "for year in years:\n", - " active = asset_capacities[year >= asset_capacities[\"commission_year\"]]\n", - "\n", - " # This only works because each agent is responsible for one and only one commodity\n", - " cap_sum = active.groupby([\"agent_id\", \"process_id\"])[\"capacity\"].sum().reset_index()\n", - "\n", - " df = pd.DataFrame(cap_sum)\n", - " df[\"year\"] = year\n", - "\n", - " capacity = pd.concat([capacity, df])\n", - "\n", - "capacity" + "merged = asset_capacities.merge(assets, on=[\"asset_id\", \"group_id\"])\n", + "agg_capacities = merged.groupby([\"milestone_year\", \"agent_id\", \"process_id\"])[\n", + " \"capacity\"\n", + "].sum()\n", + "agg_capacities_wide = agg_capacities.unstack([\"agent_id\", \"process_id\"], fill_value=0)\n", + "agg_capacities_wide" ] }, { @@ -125,37 +84,15 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "agents = capacity[\"agent_id\"].unique()\n", - "_, axes = plt.subplots(1, len(agents), figsize=(4 * len(agents), 4))\n", + "agents = agg_capacities_wide.columns.get_level_values(\"agent_id\").unique()\n", + "fig, axes = plt.subplots(1, len(agents), figsize=(4 * len(agents), 4))\n", "for ax, agent in zip(axes, agents):\n", - " capacity[capacity[\"agent_id\"] == agent].pivot(\n", - " index=\"year\", columns=\"process_id\", values=\"capacity\"\n", - " ).plot(kind=\"bar\", ax=ax)\n", - " ax.set_title(agent)\n", - " ax.set_xlabel(\"Year\")\n", - " ax.set_ylabel(\"Capacity\")\n", - " ax.legend(\n", - " title=\"Process\", bbox_to_anchor=(1.05, 1), loc=\"upper left\", borderaxespad=0.0\n", + " agg_capacities_wide[agent].plot(\n", + " kind=\"bar\", stacked=True, ax=ax, title=agent, xlabel=\"Year\", ylabel=\"Capacity\"\n", " )\n", "\n", "plt.tight_layout()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From 3d5ff46a995eb6247eaaf3e9ce2f103ffad60cd0 Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Fri, 17 Jul 2026 12:22:10 +0100 Subject: [PATCH 4/6] Update capacity notebook --- notebooks/capacity.ipynb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/notebooks/capacity.ipynb b/notebooks/capacity.ipynb index 636e336..8f4dcce 100644 --- a/notebooks/capacity.ipynb +++ b/notebooks/capacity.ipynb @@ -10,7 +10,9 @@ "This notebook contains example data processing using the output of an example\n", "model.\n", "\n", - "Output files are mostly in CSV format. The format of output files is documented [here][output-format]." + "Output files are mostly in CSV format. The format of output files is documented [in MUSE2 documentation][output-format].\n", + "\n", + "[output-format]: https://energysystemsmodellinglab.github.io/MUSE2/file_formats/output_files.html" ] }, { @@ -69,10 +71,7 @@ "source": [ "## Plot results\n", "\n", - "Finally, we plot the results.\n", - "\n", - "Note that each of the agents has invested in only one process type; otherwise there would be\n", - "multiple bars per plot here." + "Finally, we plot the results." ] }, { From 008bd046e7c09c7d55d2db3dce850a29bd98a45f Mon Sep 17 00:00:00 2001 From: Diego Alonso Alvarez Date: Fri, 17 Jul 2026 12:24:04 +0100 Subject: [PATCH 5/6] Update pre-commit-hooks to remove empy cells --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9d2e54f..b776ac4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,6 +30,7 @@ repos: rev: 0.9.1 hooks: - id: nbstripout + args: [--drop-empty-cells] - repo: https://github.com/pre-commit/mirrors-mypy rev: v2.1.0 hooks: From b230c863257a7652ebbbd49f3923e9afd3b9364b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:24:25 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- notebooks/prices.ipynb | 8 -------- 1 file changed, 8 deletions(-) diff --git a/notebooks/prices.ipynb b/notebooks/prices.ipynb index 94c58f3..b8d9831 100644 --- a/notebooks/prices.ipynb +++ b/notebooks/prices.ipynb @@ -54,14 +54,6 @@ " title=\"Time slice\", bbox_to_anchor=(1.05, 1), loc=\"upper left\", borderaxespad=0.0\n", ");" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": {