Area Variable Accounting¶
AreaPriceCalculator
¶
Bases: AreaVariableCalculatorBase
Calculates area-level prices from node prices using simple or weighted averaging.
This calculator aggregates node-level electricity prices to area-level (e.g., bidding zones, countries) using either simple averaging or weighted averaging based on demand, supply, or other energy quantities. It's particularly useful in energy market analysis where different regions may have multiple price nodes that need to be consolidated into representative area prices.
The class inherits from AreaVariableCalculatorBase and provides energy-aware price aggregation that handles edge cases like zero weights and missing data appropriately.
Typical use cases: - Aggregating nodal prices to bidding zone prices - Creating country-level price indices from multiple market nodes - Volume-weighted price calculations for regional analysis
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_model_df
|
DataFrame
|
DataFrame with node-area mappings |
required |
area_column
|
str
|
Column name containing area identifiers |
required |
Example:
>>> import pandas as pd
>>> import numpy as np
>>>
>>> # Node model with area mapping
>>> node_model = pd.DataFrame({
... 'bidding_zone': ['DE_LU', 'DE_LU', 'FR', 'FR']
... }, index=['DE1', 'DE2', 'FR1', 'FR2'])
>>>
>>> # Price calculator
>>> calc = AreaPriceCalculator(node_model, 'bidding_zone')
>>>
>>> # Node prices
>>> prices = pd.DataFrame({
... 'DE1': [50.0, 45.0], 'DE2': [52.0, 47.0],
... 'FR1': [55.0, 48.0], 'FR2': [53.0, 46.0]
... }, index=pd.date_range('2024-01-01', periods=2, freq='h'))
>>>
>>> # Simple average
>>> area_prices = calc.calculate(prices)
>>> print(area_prices)
bidding_zone DE_LU FR
datetime
2024-01-01 00:00:00 51.0 54.0
2024-01-01 01:00:00 46.0 47.0
Source code in submodules/mesqual/mesqual/energy_data_handling/area_accounting/area_variable_price_calculator.py
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | |
calculate
¶
calculate(node_price_df: DataFrame, weighting_factor_df: DataFrame = None) -> DataFrame
Calculate area prices with different weighting options.
Aggregates node-level prices to area-level using simple averaging (when no weights provided) or weighted averaging (when weights provided). The method handles missing nodes gracefully and ensures proper handling of zero weights and NaN values.
In case you want to exclude certain nodes from the aggregation (e.g. because they are virtual or synthetic nodes), you can simply remove them from the node_price_df before passing it to this method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_price_df
|
DataFrame
|
Node-level price time series with datetime index and node columns. Values represent electricity prices in €/MWh or similar units. |
required |
weighting_factor_df
|
DataFrame
|
Optional weighting factor DataFrame with same structure as
node_price_df. Common weighting factors include:
- node_demand_df: Demand-weighted prices
- node_supply_df: Supply-weighted prices |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with area-level prices. Index matches input time series, columns |
DataFrame
|
represent areas with prices in same units as input. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If node_price_df structure is invalid |
KeyError
|
If required nodes are missing from weighting_factor_df |
Example:
>>> # Simple average
>>> area_prices = calc.calculate(node_prices)
>>>
>>> # Demand-weighted average
>>> weighted_prices = calc.calculate(node_prices, node_demand)
Source code in submodules/mesqual/mesqual/energy_data_handling/area_accounting/area_variable_price_calculator.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
AreaSumCalculator
¶
Bases: AreaVariableCalculatorBase
General calculator for summing node-level extensive quantities to area level.
This calculator aggregates extensive quantities (values that scale with system size) from node-level to area-level using summation. Typical use cases include power generation, demand, energy volumes, reserves, and other additive quantities in energy systems analysis.
Unlike intensive quantities (like prices), extensive quantities should be summed when aggregating to higher geographic levels, making this calculator appropriate for many physical quantities in energy modeling.
Inherits from AreaVariableCalculatorBase and provides the MESQUAL framework's standard approach for area-level aggregation of extensive variables.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_model_df
|
DataFrame
|
DataFrame mapping nodes to areas |
required |
area_column
|
str
|
Column name containing area identifiers |
required |
Example:
>>> import pandas as pd
>>> import numpy as np
>>>
>>> # Node model
>>> node_model = pd.DataFrame({
... 'bidding_zone': ['DE_LU', 'DE_LU', 'FR', 'FR']
... }, index=['DE1', 'DE2', 'FR1', 'FR2'])
>>>
>>> # Sum calculator
>>> calc = AreaSumCalculator(node_model, 'bidding_zone')
>>> # Node generation data
>>> generation = pd.DataFrame({
... 'DE1': [800, 850], 'DE2': [750, 780],
... 'FR1': [900, 920], 'FR2': [850, 870]
... }, index=pd.date_range('2024-01-01', periods=2, freq='h'))
>>>
>>> # Sum to areas
>>> area_generation = calc.calculate(generation)
>>> print(area_generation)
bidding_zone DE_LU FR
2024-01-01 00:00:00 1550 1750
2024-01-01 01:00:00 1630 1790
Source code in submodules/mesqual/mesqual/energy_data_handling/area_accounting/area_variable_sum_calculator.py
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
calculate
¶
calculate(node_data_df: DataFrame) -> DataFrame
Calculate area sums from node-level extensive quantity data.
Sums node-level values within each area to create area-level aggregates. This method is designed for extensive quantities where summation is the appropriate aggregation method (e.g., generation, demand, volumes).
Missing nodes are handled gracefully - if a node exists in the node model but not in the data, it's simply ignored. Areas with no available nodes are omitted from the output.
In case you want to exclude certain nodes from the aggregation (e.g. because they are virtual or synthetic nodes), you can simply remove them from the node_data_df before passing it to this method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_data_df
|
DataFrame
|
DataFrame with node-level time series data. Index should be datetime, columns should be node identifiers. Values represent extensive quantities (MW, MWh, etc.) that should be summed. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with area-level aggregated data. Index matches input time series, |
DataFrame
|
columns represent areas. Units are preserved from input data. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If node_data_df structure is invalid |
Example:
>>> # Sum generation across nodes
>>> area_generation = calc.calculate(node_generation_df)
>>>
>>> # Sum demand across nodes
>>> area_demand = calc.calculate(node_demand_df)
Source code in submodules/mesqual/mesqual/energy_data_handling/area_accounting/area_variable_sum_calculator.py
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
AreaVariableCalculatorBase
¶
Bases: ABC
Abstract base class for calculating energy variables aggregated at area level.
This base class provides common functionality for aggregating node-level energy data (such as generation, demand, prices) to higher-level areas (countries, bidding zones, market areas). It handles the mapping between nodes and areas and provides validation and utility methods for area-based calculations.
The class is designed to be subclassed for specific variable types, with each subclass implementing its own calculation logic while leveraging the common area mapping and validation functionality provided here.
Energy market context: In electricity markets, many variables are naturally defined at the nodal level (generators, loads, prices) but need to be aggregated to market or geographical areas for analysis, reporting, and trading. This aggregation must handle missing data, different node counts per area, and preserve energy-specific semantics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_model_df
|
DataFrame
|
DataFrame containing node information with area assignments. Index should be node identifiers, must contain the specified area_column. |
required |
area_column
|
str
|
Name of the column in node_model_df that contains area assignments. Each node should be assigned to exactly one area (NaN values are allowed). |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
node_model_df |
The input node model DataFrame |
|
area_column |
Name of the area assignment column |
|
node_to_area_map |
Dictionary mapping node IDs to area names |
|
areas |
Sorted list of unique area names (excluding NaN) |
Raises:
| Type | Description |
|---|---|
ValueError
|
If area_column is not found in node_model_df |
Example:
>>> import pandas as pd
>>> # Node model with area assignments
>>> node_model = pd.DataFrame({
... 'country': ['DE', 'DE', 'FR', 'FR', 'BE'],
... 'voltage': [380, 220, 380, 220, 380]
... }, index=['DE1', 'DE2', 'FR1', 'FR2', 'BE1'])
>>>
>>> # Subclass implementation
>>> class MyAreaCalculator(AreaVariableCalculatorBase):
... def calculate(self, **kwargs):
... return pd.DataFrame() # Implementation here
>>>
>>> calculator = MyAreaCalculator(node_model, 'country')
>>> print(calculator.areas) # ['BE', 'DE', 'FR']
>>> print(calculator.get_area_nodes('DE')) # ['DE1', 'DE2']
Source code in submodules/mesqual/mesqual/energy_data_handling/area_accounting/area_variable_base.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
__init__
¶
__init__(node_model_df: DataFrame, area_column: str)
Initialize the area variable calculator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
node_model_df
|
DataFrame
|
DataFrame with node-to-area mapping |
required |
area_column
|
str
|
Column name containing area assignments |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If area_column not found in node_model_df |
Source code in submodules/mesqual/mesqual/energy_data_handling/area_accounting/area_variable_base.py
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
get_area_nodes
¶
get_area_nodes(area: str) -> list[str]
Get all nodes belonging to a specific area.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
area
|
str
|
Area name to get nodes for |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
List of node IDs that belong to the specified area |
Example:
>>> calculator = MyAreaCalculator(node_model, 'country')
>>> german_nodes = calculator.get_area_nodes('DE')
>>> print(german_nodes) # ['DE1', 'DE2']
Source code in submodules/mesqual/mesqual/energy_data_handling/area_accounting/area_variable_base.py
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | |
calculate
abstractmethod
¶
calculate(**kwargs) -> DataFrame
Calculate the area variable. Must be implemented by subclasses.
This method should contain the specific logic for aggregating node-level data to area level for the particular variable type. The implementation will vary depending on whether the variable is extensive (additive like energy volumes) or intensive (averaged like prices).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Variable-specific parameters for the calculation |
{}
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
DataFrame with area-level aggregated data. Index should be datetime |
DataFrame
|
for time series data, columns should be area identifiers. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
This is an abstract method |
Source code in submodules/mesqual/mesqual/energy_data_handling/area_accounting/area_variable_base.py
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
ExampleSumCalculator
¶
Bases: AreaVariableCalculatorBase
Example implementation that sums node-level data to area level.
Source code in submodules/mesqual/mesqual/energy_data_handling/area_accounting/area_variable_base.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
calculate
¶
calculate(node_data: DataFrame) -> DataFrame
Sum node data for each area (extensive variable aggregation).
Source code in submodules/mesqual/mesqual/energy_data_handling/area_accounting/area_variable_base.py
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |