.. _case_history_zhongshan: ==================================================== Case Study: Zhongshan Land Subsidence Uncertainty ==================================================== .. admonition:: Context: Forecasting in Complex Urban Environments :class: hint Urban areas, particularly coastal and delta regions like Zhongshan in China's Pearl River Delta, face significant challenges from land subsidence. This gradual sinking, driven by complex interactions between groundwater extraction, geological conditions, infrastructure load, and climate factors, poses risks to buildings, flood control, and sustainable development. Accurately forecasting future subsidence is crucial for effective urban planning and hazard mitigation, but requires not only predicting the most likely outcome but also understanding the associated **predictive uncertainty**. This case study demonstrates how various visualization tools within the `k-diagram` package can be applied to analyze and interpret the outputs of a land subsidence forecasting model, using a sample dataset derived from research focused on the Zhongshan area. We will explore how different polar plots help reveal patterns in uncertainty, model performance, and potential prediction anomalies. .. note:: The dataset used in this case study (``min_zhongshan.csv``, accessed via :func:`~kdiagram.datasets.load_zhongshan_subsidence`) is a **sample** derived from larger research model outputs. It is provided for **educational and demonstration purposes only** to illustrate the use of `k-diagram` functions. It does not represent the complete, validated forecast results for the region. The Zhongshan Sample Dataset ------------------------------ The dataset included with `k-diagram` provides a snapshot of predicted subsidence uncertainty for 898 locations over multiple years. **Key Characteristics:** * **Spatial Coordinates:** Includes ``longitude`` and ``latitude`` for each location. * **Target Values:** Contains columns ``subsidence_2022`` and ``subsidence_2023`` representing reference or baseline subsidence values for those years (useful for some diagnostics like coverage). * **Quantile Forecasts:** Provides predicted quantiles (Q10, Q50, Q90) for the years 2022 through 2026 (e.g., ``subsidence_2024_q0.1``, ``subsidence_2024_q0.5``, ``subsidence_2024_q0.9``). This allows analysis of uncertainty intervals and their evolution over time. **Loading the Data:** You can easily load this data using the provided function. By default, it returns a Bunch object containing the DataFrame and useful metadata: .. code-block:: python :linenos: import kdiagram as kd import warnings # Ignore potential download/cache warnings for brevity warnings.filterwarnings("ignore", message=".*already exists.*") # Load data as Bunch (default) try: zhongshan_data = kd.datasets.load_zhongshan_subsidence( download_if_missing=True # Allow download if not found ) print("Zhongshan data loaded successfully.") print(f"DataFrame shape: {zhongshan_data.frame.shape}") print("\nAvailable Columns (Sample):") print(zhongshan_data.frame.columns[:10].tolist(), "...") # Show some columns # print(zhongshan_data.DESCR) # Uncomment to see full description except FileNotFoundError: print("Zhongshan dataset not found. Ensure k-diagram is installed" " correctly with data, or check internet connection.") .. code-block:: text :caption: Example Output Loading dataset from cache: ... or Loading dataset from installed package... Zhongshan data loaded successfully. DataFrame shape: (898, 19) Available Columns (Sample): ['longitude', 'latitude', 'subsidence_2022', 'subsidence_2023', 'subsidence_2022_q0.1', 'subsidence_2022_q0.5', 'subsidence_2022_q0.9', 'subsidence_2023_q0.1', 'subsidence_2023_q0.5', 'subsidence_2023_q0.9'] ... Analysis Examples using k-diagram ----------------------------------- The following sections demonstrate how different `k-diagram` plots can be used with the Zhongshan dataset sample to analyze various aspects of the forecast uncertainty and model behavior. .. raw:: html
Loading Zhongshan Data for Interval Consistency Plot ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This example demonstrates loading the packaged Zhongshan dataset using :func:`~kdiagram.datasets.load_zhongshan_subsidence` (as a Bunch object) and analyzing the temporal consistency of its prediction interval widths using :func:`~kdiagram.plot.uncertainty.plot_interval_consistency`. Includes basic error handling in case the data cannot be loaded. .. code-block:: python :linenos: import kdiagram as kd import matplotlib.pyplot as plt import warnings import pandas as pd # Used by the function internally # Suppress potential download warnings if data exists locally warnings.filterwarnings("ignore", message=".*already exists.*") ax = None # Initialize ax try: # 1. Load data as Bunch, allow download if missing data = kd.datasets.load_zhongshan_subsidence( as_frame=False, download_if_missing=True, ) # 2. Check if data loaded and has necessary columns if (data is not None and hasattr(data, 'frame') and data.q10_cols and data.q50_cols and data.q90_cols): print(f"Loaded Zhongshan data with {len(data.frame)} samples.") print(f"Plotting consistency for {len(data.q10_cols)} periods.") # 3. Create the Interval Consistency plot ax = kd.plot_interval_consistency( df=data.frame, qlow_cols=data.q10_cols, qup_cols=data.q90_cols, q50_cols=data.q50_cols, # Use Q50 for color context use_cv=True, # Use Coefficient of Variation title="Zhongshan Interval Consistency (CV)", cmap='plasma', s=15, alpha=0.7, acov='eighth_circle', mask_angle=True, # Save the plot savefig="../images/dataset_plot_example_zhongshan_consistency.png" ) plt.close() # Close plot after saving else: print("Loaded data object missing required attributes (frame/cols).") except FileNotFoundError as e: print(f"ERROR - Zhongshan data not found: {e}") except Exception as e: print(f"An unexpected error occurred during plotting: {e}") if ax is None: print("Plot generation skipped due to data loading issues.") .. image:: ../images/dataset_plot_example_zhongshan_consistency.png :alt: Example Interval Consistency plot using Zhongshan data :align: center :width: 75% .. topic:: 🧠 Analysis and Interpretation :class: hint This plot uses :func:`~kdiagram.plot.uncertainty.plot_interval_consistency` to assess the stability of the predicted uncertainty range (Q90-Q10 interval width) over time (2022-2026) for the Zhongshan sample dataset. The plot is restricted to a 45-degree angular sector (``acov='eighth_circle'``). **Analysis and Interpretation:** * **Angle (θ):** Represents a subset of the location indices (0-897) mapped onto a 45-degree arc. Labels are masked. * **Radius (r):** Shows the **Coefficient of Variation (CV)** of the interval width (Q90-Q10) calculated across the years 2022-2026 for each location. A higher radius indicates greater *relative* variability in the predicted uncertainty width over time for that location. * **Color:** Represents the **average Q50** (median subsidence prediction) across all years for each location, using the `plasma` colormap (purple=low, yellow=high). The color bar indicates the scale. **🔍 Key Insights from this Example:** * **High General Consistency:** The vast majority of points are clustered very close to the center (radius near 0), indicating a very **low CV**. This suggests that for most locations in this sample and view, the *width* of the predicted uncertainty interval is relatively **stable and consistent** across the forecast horizon (2022-2026). * **Outliers:** A few distinct points have a significantly larger radius (CV > 40). These represent locations where the predicted interval width fluctuates dramatically over the years relative to its average width, signaling **highly inconsistent** or unstable uncertainty predictions. * **Color Context:** The dense cluster of consistent points (low CV) mainly shows purple and dark blue colors, corresponding to lower average Q50 predictions. The few highly inconsistent points (high CV outliers) show a mix of colors, suggesting instability can occur at different average subsidence levels in this dataset. **💡 Use Case Connection:** * This plot helps identify specific locations (the outliers) where the model's uncertainty predictions are unreliable over time, warranting further investigation. * The general consistency for most points (low CV cluster) might increase confidence in the stability of uncertainty estimates for those areas, potentially aiding risk assessment where the average predicted subsidence (color) is also low. .. raw:: html
Loading Zhongshan Data for Coverage Diagnostic (Specific Year) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This example loads the Zhongshan dataset, subsets it to a specific year (2023) and relevant quantiles (Q10, Q90) during the load step, and then uses :func:`~kdiagram.plot.uncertainty.plot_coverage_diagnostic` to visualize point-wise coverage for that year. .. code-block:: python :linenos: import kdiagram as kd import matplotlib.pyplot as plt import warnings import pandas as pd # Suppress potential download warnings warnings.filterwarnings("ignore", message=".*already exists.*") ax = None try: # 1. Load data as Bunch, selecting only 2023 data and Q10/Q90 # Also ensure the target column for 2023 is included. # Note: Target column name is 'subsidence_2023' in this dataset. data = kd.datasets.load_zhongshan_subsidence( as_frame=False, years=[2023], # Select only year 2023 quantiles=[0.1, 0.9], # Select only Q10 and Q90 include_target=True, # Ensure target column is kept download_if_missing=True ) # 2. Check data and identify columns for plotting actual_col = 'subsidence_2023' # Known target column for 2023 q_cols_plot = [] if data is not None and actual_col in data.frame.columns: if data.q10_cols: q_cols_plot.append(data.q10_cols[0]) if data.q90_cols: q_cols_plot.append(data.q90_cols[0]) if len(q_cols_plot) == 2: print(f"Loaded Zhongshan data for {actual_col}.") print(f"Plotting coverage diagnostic using: {q_cols_plot}") # 3. Create the Coverage Diagnostic plot ax = kd.plot_coverage_diagnostic( df=data.frame, actual_col=actual_col, q_cols=q_cols_plot, # Should contain 2023 Q10 & Q90 cols title="Zhongshan Coverage Diagnostic (2023)", as_bars=False, # Use scatter points fill_gradient=True, verbose=1, # Print overall coverage rate # Save the plot savefig="../images/dataset_plot_example_zhongshan_coverage.png" ) plt.close() else: print("Required columns ('subsidence_2023', Q10, Q90) " "not found in loaded data.") except FileNotFoundError as e: print(f"ERROR - Zhongshan data not found: {e}") except Exception as e: print(f"An unexpected error occurred: {e}") if ax is None: print("Plot generation skipped.") .. image:: ../images/dataset_plot_example_zhongshan_coverage.png :alt: Example Velocity plot using Zhongshan data :align: center :width: 75% .. topic:: 🧠 Analysis and Interpretation :class: hint This plot uses :func:`~kdiagram.plot.uncertainty.plot_coverage_diagnostic` to assess the point-wise coverage of the Q10-Q90 prediction interval against the target ``subsidence_2023`` values from the Zhongshan sample dataset for the year 2023. **Analysis and Interpretation:** * **Angle (θ):** Represents the index (0-897) of each specific location in the Zhongshan dataset, mapped around the circle. * **Radius (r):** Indicates coverage: **1** if the actual 2023 subsidence value was within the predicted [Q10, Q90] interval for that location; **0** if it was outside. * **Points:** Scatter points (``as_bars=False``) are used. The vast majority appear clustered at radius 1 (greenish points). Points at radius 0 (uncovered) are difficult to discern visually in this rendering, possibly due to overlap or marker style. * **Average Coverage Line:** The solid **red line** forms a circle at a radius corresponding to the **overall coverage rate**, explicitly labeled in the legend as **0.55 (or 55%)**. * **Gradient Fill:** The green shaded area extends from the center only up to the average coverage radius (0.55). **🔍 Key Insights from this Example:** * **Significant Under-coverage:** The most striking feature is the **low average coverage rate of 55%** (indicated by the red line and legend), despite using a nominal 80% prediction interval (Q10-Q90). This suggests the model's prediction intervals for 2023 were, on average, **too narrow** and failed to capture the true subsidence value almost half the time for this dataset. * **Visual vs. Average Discrepancy:** While visually most *plotted* points seem to indicate success (radius 1), the calculated average (55%) reveals that a substantial number of points must be at radius 0 (uncovered), even if not clearly visible. This highlights the importance of the calculated average line as a reliable summary statistic. * **Potential Issues:** The low coverage rate indicates potential issues with the model's uncertainty calibration for the 2023 forecast period in the original study this sample data is derived from. **💡 When to Use:** * Use this plot to verify if the prediction intervals for a *specific time period* achieve the desired nominal coverage. * Identify if coverage failures are widespread (as suggested by the low average here) or specific to certain samples (which would require examining the points near radius 0 more closely, perhaps with different marker styles or alpha settings). * Assess the practical reliability of the forecast's uncertainty bounds for decision-making in a given period. .. raw:: html
Zhongshan Data: Velocity Plot (Default Coverage) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Load Zhongshan data (as Bunch) and visualize the average velocity of the median (Q50) predictions using the full 360-degree view (`acov='default'`). Color represents the average Q50 magnitude. .. code-block:: python :linenos: import kdiagram as kd import matplotlib.pyplot as plt import warnings import pandas as pd warnings.filterwarnings("ignore", message=".*already exists.*") ax = None try: # 1. Load data as Bunch data = kd.datasets.load_zhongshan_subsidence( as_frame=False, download_if_missing=True ) # 2. Check data if data is not None and data.q50_cols: print(f"Loaded Zhongshan data with {len(data.frame)} samples.") print(f"Plotting velocity using {len(data.q50_cols)} periods.") # 3. Create the Velocity plot ax = kd.plot_velocity( df=data.frame, q50_cols=data.q50_cols, title="Zhongshan Q50 Prediction Velocity", acov='default', # Full circle coverage use_abs_color=True, # Color by Q50 magnitude normalize=True, # Normalize radius cmap='jet_r', cbar=True, s=80, alpha=0.8, mask_angle=True, # Save the plot savefig="../images/dataset_plot_example_zhongshan_velocity.png" ) plt.close() else: print("Loaded data object missing required attributes.") except FileNotFoundError as e: print(f"ERROR - Zhongshan data not found: {e}") except Exception as e: print(f"An unexpected error occurred: {e}") if ax is None: print("Plot generation skipped.") .. image:: ../images/dataset_plot_example_zhongshan_velocity.png :alt: Example Velocity plot using Zhongshan data :align: center :width: 75% .. topic:: 🧠 Analysis and Interpretation :class: hint This plot, generated by :func:`~kdiagram.plot.uncertainty.plot_velocity`, visualizes the **average rate of change (velocity)** of the median (Q50) subsidence predictions across the available years (likely 2022-2026) for each location in the Zhongshan sample dataset. **Analysis and Interpretation:** * **Angle (θ):** Represents the index (0-897) of each location, distributed around the full 360 degrees (``acov='default'``). Angular labels are hidden (``mask_angle=True``). * **Radius (r):** Shows the **normalized average velocity**, scaled to [0, 1] (due to ``normalize=True``). A radius near 1 indicates locations where the Q50 prediction changed most rapidly on average over the years; a radius near 0 indicates very stable Q50 predictions. * **Color:** Represents the **average absolute Q50 magnitude** across all years for each location (since ``use_abs_color=True``). The ``jet_r`` colormap is used (blue=low magnitude, red=high magnitude), with the scale shown on the color bar. * **Marker Size/Alpha:** Larger markers (``s=80``) with some transparency (``alpha=0.8``) are used. **🔍 Key Insights from this Example:** * **Velocity Distribution:** There is a considerable spread in velocities. While many locations show low to moderate normalized velocity (points clustered r < 0.5), a noticeable number exhibit higher velocities (points with r > 0.6), indicating significant variation in the predicted rate of subsidence change across locations. * **Velocity vs. Magnitude:** Visually, there appears to be some correlation between velocity and magnitude. Locations with higher average Q50 magnitude (yellow/orange/red points) seem more prevalent at larger radii (higher velocity) compared to locations with lower average Q50 (blue/cyan points), which are more concentrated near the center (lower velocity). This suggests areas predicted to have higher subsidence might also be predicted to change more rapidly. * **Spatial Pattern:** Without angular labels tied to actual spatial coordinates, identifying precise geographical patterns is hard, but the overall distribution appears somewhat uniform angularly, without extreme clustering in specific index ranges. **💡 Use Case Connection:** * This plot helps identify locations within the Zhongshan sample predicted to undergo the fastest *average* rate of change in subsidence over the forecast period (points furthest from center). * By coloring by average Q50 magnitude, it allows planners to see if these high-velocity areas are also areas of high absolute subsidence risk, potentially requiring priority attention. .. raw:: html
Zhongshan Data: Interval Width Plot (2022, Eighth Circle) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Load Zhongshan data, select the Q10, Q50, and Q90 columns for the first available year (assumed 2022), and plot the interval width using :func:`~kdiagram.plot.uncertainty.plot_interval_width` with Q50 for color, restricted to a 45-degree view (`acov='eighth_circle'`). .. code-block:: python :linenos: import kdiagram as kd import matplotlib.pyplot as plt import warnings import pandas as pd warnings.filterwarnings("ignore", message=".*already exists.*") ax = None try: # 1. Load data as Bunch data = kd.datasets.load_zhongshan_subsidence( as_frame=False, download_if_missing=True ) # 2. Check data and extract columns for the first year (e.g., 2022) if (data is not None and hasattr(data, 'frame') and data.q10_cols and data.q50_cols and data.q90_cols): q10_col_first = data.q10_cols[0] # Assumes list is ordered q50_col_first = data.q50_cols[0] q90_col_first = data.q90_cols[0] year_first = str(data.start_year) # Assumes start_year attr exists print(f"Plotting interval width for Zhongshan, year {year_first}") # 3. Create the Interval Width plot ax = kd.plot_interval_width( df=data.frame, q_cols=[q10_col_first, q90_col_first], # Q10, Q90 for one year z_col=q50_col_first, # Color by Q50 of that year acov='eighth_circle', # <<< Use 45 degree view title=f"Zhongshan Interval Width ({year_first}, 45°)", cmap='YlGnBu', cbar=True, s=55, alpha=0.85, mask_angle=True, # Save the plot savefig="../images/dataset_plot_example_zhongshan_width_45deg.png" ) plt.close() else: print("Loaded data object missing required attributes.") except FileNotFoundError as e: print(f"ERROR - Zhongshan data not found: {e}") except Exception as e: print(f"An unexpected error occurred: {e}") if ax is None: print("Plot generation skipped.") .. image:: ../images/dataset_plot_example_zhongshan_width_45deg.png :alt: Example Interval Width plot using Zhongshan data (45 deg) :align: center :width: 75% .. topic:: 🧠 Analysis and Interpretation :class: hint This plot uses :func:`~kdiagram.plot.uncertainty.plot_interval_width` to display the **magnitude of the predicted uncertainty** (interval width) for the year **2022** in the Zhongshan sample dataset. The view is restricted to a 45-degree sector (``acov='eighth_circle'``). **Analysis and Interpretation:** * **Angle (θ):** Represents a subset of the location indices mapped onto the 45-degree arc. Angular labels are hidden. * **Radius (r):** Directly shows the **raw interval width** calculated as ``subsidence_2022_q90 - subsidence_2022_q10`` for each plotted location. Larger radius indicates greater predicted uncertainty magnitude for 2022. * **Color:** Represents the **median prediction** value (``subsidence_2022_q50``) for each location, using the `YlGnBu` colormap (light yellow/green = low Q50, dark blue = high Q50), indicated by the color bar. **🔍 Key Insights from this Example:** * **Width Distribution:** For the locations visible in this narrow sector, most prediction intervals in 2022 have widths ranging roughly from 0 to 40 units. * **Width vs. Magnitude:** Within the main cluster, there isn't an immediately obvious strong correlation between interval width (radius) and the median prediction Q50 (color) – various widths appear across different Q50 levels. * **Outliers & Potential Data Issues:** Several points exhibit very large positive radii (high uncertainty), and notably, some points have **negative radii** (plotted below the center). Negative radii imply that for those specific locations in the 2022 data sample, the recorded Q10 value was *greater* than the Q90 value, which indicates either a data error or a severe model prediction failure for those points. **💡 When to Use / Connection:** * Use this plot to directly visualize the predicted uncertainty magnitude (interval width) for each sample at a *single point in time*. * The color mapping (`z_col`) helps investigate relationships between uncertainty width and the central tendency (Q50) or other features. * Identifying outliers with extremely large or physically implausible (negative) widths is crucial for model diagnostics and data quality checks. These specific locations from the Zhongshan sample would require further investigation in a real analysis. * Using narrower `acov` settings like `eighth_circle` can help focus on specific subsets if the angular arrangement is meaningful, but limits the overall view. .. raw:: html
Zhongshan Data: Uncertainty Drift Plot (Quarter Circle) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Load Zhongshan data (as Bunch) and visualize the temporal drift of uncertainty patterns using concentric rings with :func:`~kdiagram.plot.uncertainty.plot_uncertainty_drift`, restricted to a 90-degree view (`acov='quarter_circle'`). .. code-block:: python :linenos: import kdiagram as kd import matplotlib.pyplot as plt import warnings import pandas as pd warnings.filterwarnings("ignore", message=".*already exists.*") ax = None try: # 1. Load data as Bunch data = kd.datasets.load_zhongshan_subsidence( as_frame=False, download_if_missing=True ) # 2. Check data and prepare labels if (data is not None and hasattr(data, 'frame') and data.q10_cols and data.q90_cols and hasattr(data, 'start_year') and hasattr(data, 'n_periods')): horizons = [str(data.start_year + i) for i in range(data.n_periods)] print(f"Plotting uncertainty drift for Zhongshan: {horizons}") # 3. Create the Uncertainty Drift plot ax = kd.plot_uncertainty_drift( df=data.frame, qlow_cols=data.q10_cols, qup_cols=data.q90_cols, dt_labels=horizons, acov='quarter_circle', # <<< Use 90 degree view title="Zhongshan Uncertainty Drift (90°)", cmap='viridis', show_legend=True, mask_angle=True, # Save the plot savefig="../images/dataset_plot_example_zhongshan_uncertainty_drift.png" ) plt.close() else: print("Loaded data object missing required attributes.") except FileNotFoundError as e: print(f"ERROR - Zhongshan data not found: {e}") except Exception as e: print(f"An unexpected error occurred: {e}") if ax is None: print("Plot generation skipped.") .. image:: ../images/dataset_plot_example_zhongshan_uncertainty_drift.png :alt: Example Uncertainty Drift plot using Zhongshan data (90 deg) :align: center :width: 75% .. topic:: 🧠 Analysis and Interpretation :class: hint This plot uses :func:`~kdiagram.plot.uncertainty.plot_uncertainty_drift` to visualize how the pattern of predicted uncertainty (relative interval width Q90-Q10) evolves over multiple years (2022-2026) for the Zhongshan sample dataset. The view is focused on a 90-degree sector (``acov='quarter_circle'``). **Analysis and Interpretation:** * **Angle (θ):** Represents a subset of the location indices (0-897) mapped onto the 90-degree arc. Angular labels are hidden (``mask_angle=True``). * **Concentric Rings:** Each ring represents a specific year, as indicated by the legend (2022 is innermost, 2026 is outermost). The colors should ideally follow the `viridis` colormap, though in the rendered example they appear uniformly purple; the legend remains key for identification. * **Radius (r) on Ring:** The radial position of the line for a given year and angle indicates the **relative interval width** (Q90-Q10, normalized across all years and locations) plus a base offset specific to that year's ring. Larger radii on a ring correspond to locations with relatively higher uncertainty in that year. **🔍 Key Insights from this Example:** * **Temporal Drift:** By visually comparing the average radial position of the rings, we can observe a slight tendency for the outer rings (later years like 2025, 2026) to be positioned further from the center than the inner rings (earlier years like 2022, 2023). This suggests a **mild positive drift** – on average, the relative uncertainty tends to increase over the forecast horizon for the locations shown in this quadrant. * **Spatial Variability:** Each individual ring (year) exhibits significant "spikiness" or irregularity. This indicates **high spatial variability** in predicted uncertainty *within* each year; some locations (angles) consistently have much wider or narrower relative intervals than others. * **Pattern Consistency:** While the average radius drifts slightly, the *degree* of irregularity or "bumpiness" looks somewhat similar across the different years within this view. This might suggest that while overall uncertainty increases, the spatial *pattern* of high/low uncertainty locations remains relatively consistent over time. **💡 When to Use / Connection:** * Use this plot to understand how the **entire spatial pattern** (represented by angle) of uncertainty changes from one time period to the next. * Compare it with :func:`~kdiagram.plot.uncertainty.plot_model_drift`. While `plot_model_drift` shows the *average* drift across all locations, this plot reveals if that drift is uniform or if certain locations experience much larger increases in uncertainty than others. * The restricted view (``quarter_circle``) focuses the analysis but only shows a fraction of the locations. .. raw:: html
.. seealso:: The forecasting challenges and visualization techniques discussed in relation to the Zhongshan case study are further detailed in related research publications. For details on how to cite the `k-diagram` software and these specific papers (including submissions to *Nature Sustainability* and the *International Journal of Forecasting*), please refer to the :ref:`Citing k-diagram ` page.