
.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "auto_examples/tutorial_04_impact_studies.py"
.. LINE NUMBERS ARE GIVEN BELOW.

.. only:: html

    .. note::
        :class: sphx-glr-download-link-note

        :ref:`Go to the end <sphx_glr_download_auto_examples_tutorial_04_impact_studies.py>`
        to download the full example code.

.. rst-class:: sphx-glr-example-title

.. _sphx_glr_auto_examples_tutorial_04_impact_studies.py:


Impact Studies Using SuPy
=========================

Sensitivity analysis for urban climate modelling with SUEWS.

This tutorial demonstrates how to perform sensitivity analysis using SuPy
to investigate the impacts on urban climate of:

1. **Surface properties**: Physical attributes of land covers (e.g., albedo)
2. **Background climate**: Long-term meteorological conditions (e.g., air temperature)

**API approach**: This tutorial uses the :class:`~supy.SUEWSSimulation` OOP interface but
extracts DataFrames for scenario construction. This hybrid pattern is appropriate
for multi-scenario sensitivity analysis where you need to programmatically
modify parameters across many test cases.

**Tutorial structure**:

- Part 1 (Albedo): Build scenario matrix with DataFrame, run all at once
- Part 2 (Climate): Modify forcing in a loop, combine results for analysis

.. GENERATED FROM PYTHON SOURCE LINES 25-29

Setup and Load Sample Data
--------------------------

First, we import the required packages and load the sample dataset.

.. GENERATED FROM PYTHON SOURCE LINES 29-45

.. code-block:: Python


    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd

    from supy import SUEWSSimulation

    # Load sample datasets using the OOP interface
    sim = SUEWSSimulation.from_sample_data()

    # Slice forcing to a shorter period for faster execution (returns SUEWSForcing)
    forcing_sliced = sim.forcing["2012-01":"2012-03"].iloc[1:]

    print("Sample data loaded using SUEWSSimulation.from_sample_data()")
    print(f"Simulation period: {forcing_sliced.df.index[0]} to {forcing_sliced.df.index[-1]}")





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Sample data loaded using SUEWSSimulation.from_sample_data()
    Simulation period: 2012-01-01 00:10:00 to 2012-03-31 23:55:00




.. GENERATED FROM PYTHON SOURCE LINES 46-52

Part 1: Surface Properties - Albedo Study
-----------------------------------------

We investigate how changes in surface albedo affect urban air temperature.
Higher albedo (more reflective surfaces) should reduce absorbed solar
radiation and lower surface and air temperatures.

.. GENERATED FROM PYTHON SOURCE LINES 54-59

Examine Default Albedo Values
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

First, let's look at the default albedo values from the sample dataset.
We access the initial state via the simulation object.

.. GENERATED FROM PYTHON SOURCE LINES 59-63

.. code-block:: Python


    print("Default albedo values by surface type:")
    print(sim.state_init.alb)





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Default albedo values by surface type:
    ind_dim  (0,)  (1,)  (2,)  (3,)  (4,)  (5,)  (6,)
    grid                                             
    1         0.1  0.12   0.1  0.12  0.18  0.18   0.1




.. GENERATED FROM PYTHON SOURCE LINES 64-69

Configure Test Surface
~~~~~~~~~~~~~~~~~~~~~~

Create a test surface with 99% buildings and 1% paved area to isolate
the effect of building albedo. We extract the DataFrame for modification.

.. GENERATED FROM PYTHON SOURCE LINES 69-87

.. code-block:: Python


    # Extract state for scenario construction (DataFrame needed for pd.concat)
    df_state_init = sim.state_init.copy()

    # Create a modified state for testing
    df_state_test = df_state_init.copy()

    # Set surface fractions: 99% buildings, 1% paved
    df_state_test.sfr_surf = 0.0  # Use float to preserve dtype
    df_state_test.loc[:, ("sfr_surf", "(1,)")] = 0.99  # Buildings
    df_state_test.loc[:, ("sfr_surf", "(0,)")] = 0.01  # Paved

    # Verify fractions sum to 1.0
    assert abs(df_state_test.sfr_surf.sum(axis=1).iloc[0] - 1.0) < 1e-6, "Surface fractions must sum to 1.0"

    print("Modified surface fractions:")
    print(df_state_test.sfr_surf)





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Modified surface fractions:
    ind_dim  (0,)  (1,)  (2,)  (3,)  (4,)  (5,)  (6,)
    grid                                             
    1        0.01  0.99   0.0   0.0   0.0   0.0   0.0




.. GENERATED FROM PYTHON SOURCE LINES 88-93

Build Albedo Scenario Matrix
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Construct a multi-scenario DataFrame with different albedo values.
This is the key step where DataFrame manipulation is necessary.

.. GENERATED FROM PYTHON SOURCE LINES 93-115

.. code-block:: Python


    # Test 3 albedo values (0.1 to 0.8) for faster tutorial execution
    n_albedo = 3
    list_albedo = np.linspace(0.1, 0.8, n_albedo).round(2)

    # Create scenario matrix by concatenating copies with different albedo values
    df_state_scenarios = (
        pd.concat(
            {a: df_state_test.copy() for a in list_albedo},
            names=["alb", "grid"],
        )
        .droplevel("grid", axis=0)
        .rename_axis(index="grid")
    )

    # Set building albedo for each scenario (explicit per-row assignment for safety)
    for idx, alb_val in zip(df_state_scenarios.index, list_albedo):
        df_state_scenarios.loc[idx, ("alb", "(1,)")] = alb_val

    print(f"Created {n_albedo} albedo scenarios:")
    print(df_state_scenarios.alb)





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Created 3 albedo scenarios:
    ind_dim  (0,)  (1,)  (2,)  (3,)  (4,)  (5,)  (6,)
    grid                                             
    0.10      0.1  0.10   0.1  0.12  0.18  0.18   0.1
    0.45      0.1  0.45   0.1  0.12  0.18  0.18   0.1
    0.80      0.1  0.80   0.1  0.12  0.18  0.18   0.1




.. GENERATED FROM PYTHON SOURCE LINES 116-121

Run Albedo Simulations
~~~~~~~~~~~~~~~~~~~~~~

Create a simulation from the scenario matrix and run all scenarios at once.
This demonstrates efficient batch execution.

.. GENERATED FROM PYTHON SOURCE LINES 121-128

.. code-block:: Python


    # Create simulation from scenario matrix and run
    sim_albedo = SUEWSSimulation.from_state(df_state_scenarios).update_forcing(forcing_sliced)
    output_albedo = sim_albedo.run(logging_level=90)

    print(f"Completed {n_albedo} albedo simulations")





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Completed 3 albedo simulations




.. GENERATED FROM PYTHON SOURCE LINES 129-133

Analyse Albedo Results
~~~~~~~~~~~~~~~~~~~~~~

Examine the temperature response to albedo changes using the OOP output interface.

.. GENERATED FROM PYTHON SOURCE LINES 133-151

.. code-block:: Python


    # Access results via output.SUEWS (OOP interface)
    df_results = output_albedo.SUEWS.unstack(0)

    # Select last month for analysis
    last_month = f"{df_results.index[-1].year}-{df_results.index[-1].month:02d}"
    df_month = df_results.loc[last_month]

    # Calculate temperature statistics across scenarios
    df_T2_stats = df_month.T2.describe()

    # Calculate temperature difference from baseline (lowest albedo)
    df_T2_diff = df_T2_stats.transform(lambda x: x - df_T2_stats.iloc[:, 0])
    df_T2_diff.columns = list_albedo - list_albedo[0]

    print(f"Temperature statistics for {last_month}:")
    print(df_T2_stats.loc[["mean", "min", "max"]])





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Temperature statistics for 2012-03:
    grid       0.10       0.45       0.80
    mean   8.987208   8.944602   8.897809
    min    1.960719   1.960847   1.953318
    max   22.289814  22.184573  22.068406




.. GENERATED FROM PYTHON SOURCE LINES 152-156

Plot Albedo Impact on Temperature
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Visualise how increasing surface albedo reduces air temperature.

.. GENERATED FROM PYTHON SOURCE LINES 156-167

.. code-block:: Python


    fig, ax = plt.subplots(figsize=(8, 5))
    df_T2_diff.loc[["max", "mean", "min"]].T.plot(ax=ax)
    ax.set_ylabel(r"$\Delta T_2$ ($^\circ$C)")
    ax.set_xlabel(r"$\Delta\alpha$ (albedo change)")
    ax.margins(x=0.2, y=0.2)
    ax.set_title(f"Temperature Response to Albedo Change ({last_month})")
    ax.legend(title="Statistic")
    plt.tight_layout()





.. image-sg:: /auto_examples/images/sphx_glr_tutorial_04_impact_studies_001.png
   :alt: Temperature Response to Albedo Change (2012-03)
   :srcset: /auto_examples/images/sphx_glr_tutorial_04_impact_studies_001.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 169-174

Part 2: Background Climate - Air Temperature Study
--------------------------------------------------

We investigate how changes in background air temperature affect
the simulated 2-metre temperature. This represents climate warming scenarios.

.. GENERATED FROM PYTHON SOURCE LINES 176-181

Examine Monthly Climatology
~~~~~~~~~~~~~~~~~~~~~~~~~~~

View the monthly mean air temperature from the forcing data.
The resample method returns aggregated values.

.. GENERATED FROM PYTHON SOURCE LINES 181-192

.. code-block:: Python


    df_monthly_temp = forcing_sliced.resample("1ME")["Tair"]

    fig, ax = plt.subplots(figsize=(8, 5))
    df_monthly_temp.plot.bar(ax=ax, color="tab:blue")
    ax.set_xticklabels([d.strftime("%b") for d in df_monthly_temp.index], rotation=0)
    ax.set_ylabel(r"Mean Air Temperature ($^\circ$C)")
    ax.set_xlabel("Month")
    ax.set_title("Monthly Mean Air Temperature (2012)")
    plt.tight_layout()




.. image-sg:: /auto_examples/images/sphx_glr_tutorial_04_impact_studies_002.png
   :alt: Monthly Mean Air Temperature (2012)
   :srcset: /auto_examples/images/sphx_glr_tutorial_04_impact_studies_002.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 193-204

Create and Run Climate Scenarios
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Build forcing datasets with different air temperature offsets (0 to +2 deg C)
and run simulations in a loop.

.. note::

   For larger studies, these simulations could be run in parallel using
   ``concurrent.futures.ThreadPoolExecutor``. We use a simple loop here
   for clarity.

.. GENERATED FROM PYTHON SOURCE LINES 204-232

.. code-block:: Python


    # Temperature offsets to test
    n_climate = 3
    list_temp_offset = np.linspace(0.0, 2.0, n_climate).round(2)

    # Extract forcing DataFrame for modification
    df_forcing = forcing_sliced.df

    # Run scenarios and collect results
    list_outputs = []

    for temp_offset in list_temp_offset:
        # Create modified forcing with temperature offset
        df_forcing_modified = df_forcing.copy()
        df_forcing_modified["Tair"] += temp_offset

        # Run simulation using OOP interface
        sim_climate = SUEWSSimulation.from_state(df_state_init).update_forcing(
            df_forcing_modified
        )
        output = sim_climate.run(logging_level=90)

        # Store results with scenario label
        list_outputs.append((temp_offset, output))

    print(f"Completed {n_climate} climate scenarios")
    print(f"Temperature offsets: {list_temp_offset[0]:.1f} to {list_temp_offset[-1]:.1f} deg C")





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Completed 3 climate scenarios
    Temperature offsets: 0.0 to 2.0 deg C




.. GENERATED FROM PYTHON SOURCE LINES 233-237

Combine and Analyse Climate Results
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Combine results from all scenarios and calculate temperature differences.

.. GENERATED FROM PYTHON SOURCE LINES 237-261

.. code-block:: Python


    # Combine results into a single DataFrame
    dict_results = {}
    for temp_offset, output in list_outputs:
        # Access T2 directly from output (OOP interface)
        # T2 is a Series with MultiIndex (grid, datetime) - extract the Series
        dict_results[temp_offset] = output.SUEWS.T2

    df_climate_results = pd.concat(dict_results, axis=1, names=["temp_offset"])

    # Simplify index for analysis (drop grid level since we have only one grid)
    df_climate_results = df_climate_results.droplevel("grid")

    # Calculate difference from baseline
    df_temp_diff = df_climate_results.transform(lambda x: x - df_climate_results[0.0])

    # Focus on last month
    last_month_climate = f"{df_temp_diff.index[-1].year}-{df_temp_diff.index[-1].month:02d}"
    df_temp_diff_month = df_temp_diff.loc[last_month_climate]
    df_temp_diff_stats = df_temp_diff_month.describe().loc[["max", "mean", "min"]].T

    print(f"Temperature response statistics for {last_month_climate}:")
    print(df_temp_diff_stats)





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    Temperature response statistics for 2012-03:
                      max      mean       min
    temp_offset                              
    0.0          0.000000  0.000000  0.000000
    1.0          1.470442  1.043364  0.839985
    2.0          2.809824  2.231593  1.948404




.. GENERATED FROM PYTHON SOURCE LINES 262-267

Plot Climate Impact on Temperature
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Visualise the relationship between background air temperature changes
and simulated 2-metre temperature.

.. GENERATED FROM PYTHON SOURCE LINES 267-277

.. code-block:: Python


    fig, ax = plt.subplots(figsize=(8, 6))
    df_temp_diff_stats.plot(ax=ax, marker="o")
    ax.set_ylabel(r"$\Delta T_2$ ($^\circ$C)")
    ax.set_xlabel(r"$\Delta T_{a}$ ($^\circ$C)")
    ax.set_aspect("equal")
    ax.set_title(f"Temperature Response to Background Climate Change ({last_month_climate})")
    ax.legend(title="Statistic")
    plt.tight_layout()




.. image-sg:: /auto_examples/images/sphx_glr_tutorial_04_impact_studies_003.png
   :alt: Temperature Response to Background Climate Change (2012-03)
   :srcset: /auto_examples/images/sphx_glr_tutorial_04_impact_studies_003.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 278-300

Conclusions
-----------

The results demonstrate two key relationships:

1. **Albedo effect**: Increasing surface albedo reduces urban air temperature,
   with larger impacts on maximum temperatures than mean or minimum. This
   supports cool roof strategies for urban heat mitigation.

2. **Climate warming effect**: Increased background air temperature (T_a) leads
   to proportional increases in 2-metre temperature (T_2):

   - All metrics (min, mean, max) increase with warming
   - The relationship is approximately linear within the tested range

These sensitivity analyses demonstrate how SUEWS can evaluate both mitigation
strategies (surface property changes) and climate change impacts (background
warming scenarios).

**Next steps:**

- :doc:`tutorial_06_external_coupling` - Couple SUEWS with external models


.. rst-class:: sphx-glr-timing

   **Total running time of the script:** (0 minutes 17.706 seconds)


.. _sphx_glr_download_auto_examples_tutorial_04_impact_studies.py:

.. only:: html

  .. container:: sphx-glr-footer sphx-glr-footer-example

    .. container:: sphx-glr-download sphx-glr-download-jupyter

      :download:`Download Jupyter notebook: tutorial_04_impact_studies.ipynb <tutorial_04_impact_studies.ipynb>`

    .. container:: sphx-glr-download sphx-glr-download-python

      :download:`Download Python source code: tutorial_04_impact_studies.py <tutorial_04_impact_studies.py>`

    .. container:: sphx-glr-download sphx-glr-download-zip

      :download:`Download zipped: tutorial_04_impact_studies.zip <tutorial_04_impact_studies.zip>`


.. only:: html

 .. rst-class:: sphx-glr-signature

    `Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>`_
