ebm 0.99.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ebm/__init__.py +0 -0
- ebm/__main__.py +152 -0
- ebm/__version__.py +1 -0
- ebm/cmd/__init__.py +0 -0
- ebm/cmd/calibrate.py +83 -0
- ebm/cmd/calibrate_excel_com_io.py +128 -0
- ebm/cmd/heating_systems_by_year.py +18 -0
- ebm/cmd/helpers.py +134 -0
- ebm/cmd/initialize.py +167 -0
- ebm/cmd/migrate.py +92 -0
- ebm/cmd/pipeline.py +227 -0
- ebm/cmd/prepare_main.py +174 -0
- ebm/cmd/result_handler.py +272 -0
- ebm/cmd/run_calculation.py +221 -0
- ebm/data/area.csv +92 -0
- ebm/data/area_new_residential_buildings.csv +3 -0
- ebm/data/area_per_person.csv +12 -0
- ebm/data/building_code_parameters.csv +9 -0
- ebm/data/energy_need_behaviour_factor.csv +6 -0
- ebm/data/energy_need_improvements.csv +7 -0
- ebm/data/energy_need_original_condition.csv +534 -0
- ebm/data/heating_system_efficiencies.csv +13 -0
- ebm/data/heating_system_forecast.csv +9 -0
- ebm/data/heating_system_initial_shares.csv +1113 -0
- ebm/data/holiday_home_energy_consumption.csv +24 -0
- ebm/data/holiday_home_stock.csv +25 -0
- ebm/data/improvement_building_upgrade.csv +9 -0
- ebm/data/new_buildings_residential.csv +32 -0
- ebm/data/population_forecast.csv +51 -0
- ebm/data/s_curve.csv +40 -0
- ebm/energy_consumption.py +307 -0
- ebm/extractors.py +115 -0
- ebm/heating_system_forecast.py +472 -0
- ebm/holiday_home_energy.py +341 -0
- ebm/migrations.py +224 -0
- ebm/model/__init__.py +0 -0
- ebm/model/area.py +403 -0
- ebm/model/bema.py +149 -0
- ebm/model/building_category.py +150 -0
- ebm/model/building_condition.py +78 -0
- ebm/model/calibrate_energy_requirements.py +84 -0
- ebm/model/calibrate_heating_systems.py +180 -0
- ebm/model/column_operations.py +157 -0
- ebm/model/construction.py +827 -0
- ebm/model/data_classes.py +223 -0
- ebm/model/database_manager.py +410 -0
- ebm/model/dataframemodels.py +115 -0
- ebm/model/defaults.py +30 -0
- ebm/model/energy_need.py +6 -0
- ebm/model/energy_need_filter.py +182 -0
- ebm/model/energy_purpose.py +115 -0
- ebm/model/energy_requirement.py +353 -0
- ebm/model/energy_use.py +202 -0
- ebm/model/enums.py +8 -0
- ebm/model/exceptions.py +4 -0
- ebm/model/file_handler.py +388 -0
- ebm/model/filter_scurve_params.py +83 -0
- ebm/model/filter_tek.py +152 -0
- ebm/model/heat_pump.py +53 -0
- ebm/model/heating_systems.py +20 -0
- ebm/model/heating_systems_parameter.py +17 -0
- ebm/model/heating_systems_projection.py +3 -0
- ebm/model/heating_systems_share.py +28 -0
- ebm/model/scurve.py +224 -0
- ebm/model/tek.py +1 -0
- ebm/s_curve.py +515 -0
- ebm/services/__init__.py +0 -0
- ebm/services/calibration_writer.py +262 -0
- ebm/services/console.py +106 -0
- ebm/services/excel_loader.py +66 -0
- ebm/services/files.py +38 -0
- ebm/services/spreadsheet.py +289 -0
- ebm/temp_calc.py +99 -0
- ebm/validators.py +565 -0
- ebm-0.99.3.dist-info/METADATA +217 -0
- ebm-0.99.3.dist-info/RECORD +80 -0
- ebm-0.99.3.dist-info/WHEEL +5 -0
- ebm-0.99.3.dist-info/entry_points.txt +3 -0
- ebm-0.99.3.dist-info/licenses/LICENSE +21 -0
- ebm-0.99.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
import typing
|
2
|
+
from enum import unique, StrEnum, EnumType
|
3
|
+
|
4
|
+
import pandas as pd
|
5
|
+
from loguru import logger
|
6
|
+
|
7
|
+
RESIDENTIAL = 'residential'
|
8
|
+
NON_RESIDENTIAL = 'non_residential'
|
9
|
+
|
10
|
+
|
11
|
+
class MyEnumType(EnumType):
|
12
|
+
def __contains__(cls, value):
|
13
|
+
return value in cls._value2member_map_
|
14
|
+
|
15
|
+
|
16
|
+
@unique
|
17
|
+
class BuildingCategory(StrEnum, metaclass=MyEnumType):
|
18
|
+
HOUSE = 'house'
|
19
|
+
APARTMENT_BLOCK = 'apartment_block'
|
20
|
+
KINDERGARTEN = 'kindergarten'
|
21
|
+
SCHOOL = 'school'
|
22
|
+
UNIVERSITY = 'university'
|
23
|
+
OFFICE = 'office'
|
24
|
+
RETAIL = 'retail'
|
25
|
+
HOTEL = 'hotel'
|
26
|
+
HOSPITAL = 'hospital'
|
27
|
+
NURSING_HOME = 'nursing_home'
|
28
|
+
CULTURE = 'culture'
|
29
|
+
SPORTS = 'sports'
|
30
|
+
STORAGE = 'storage_repairs'
|
31
|
+
|
32
|
+
def __repr__(self):
|
33
|
+
return f'{self.__class__.__name__}.{self.name}'
|
34
|
+
|
35
|
+
def yearly_construction_floor_area(self):
|
36
|
+
logger.warning('Using static yearly_construction_floor_area')
|
37
|
+
raise NotImplementedError(f'yearly_construction_floor_area does not support category {self.name} (yet)')
|
38
|
+
|
39
|
+
def is_residential(self) -> bool:
|
40
|
+
return self == BuildingCategory.HOUSE or self == BuildingCategory.APARTMENT_BLOCK
|
41
|
+
|
42
|
+
def is_non_residential(self) -> bool:
|
43
|
+
return not self.is_residential()
|
44
|
+
|
45
|
+
@staticmethod
|
46
|
+
def from_string(category_name: str) -> 'BuildingCategory':
|
47
|
+
"""Create an enum object from category name
|
48
|
+
Args:
|
49
|
+
category_name (str)
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
building_category (BuildingCategory (Enum))
|
53
|
+
|
54
|
+
Raises:
|
55
|
+
ValueError: category_name not found in BuildingCategory
|
56
|
+
"""
|
57
|
+
search = category_name.lower().replace(' ', '').replace('_', '')
|
58
|
+
for building_category in iter(BuildingCategory):
|
59
|
+
if search == building_category.value.lower().replace('_', ''):
|
60
|
+
return building_category
|
61
|
+
raise ValueError(f'No such building category {category_name}')
|
62
|
+
|
63
|
+
def from_norsk(norsk: str) -> BuildingCategory:
|
64
|
+
if norsk.lower() == 'småhus':
|
65
|
+
return BuildingCategory.HOUSE
|
66
|
+
if norsk.lower() in ('leilighet', 'boligblokk'):
|
67
|
+
return BuildingCategory.APARTMENT_BLOCK
|
68
|
+
if norsk.lower() == 'barnehage':
|
69
|
+
return BuildingCategory.KINDERGARTEN
|
70
|
+
if norsk.lower() == 'kontor':
|
71
|
+
return BuildingCategory.OFFICE
|
72
|
+
if norsk.lower() == 'skole':
|
73
|
+
return BuildingCategory.SCHOOL
|
74
|
+
if norsk.lower() == 'universitet':
|
75
|
+
return BuildingCategory.UNIVERSITY
|
76
|
+
if norsk.lower() == 'sykehjem':
|
77
|
+
return BuildingCategory.NURSING_HOME
|
78
|
+
if norsk.lower() == 'sykehus':
|
79
|
+
return BuildingCategory.HOSPITAL
|
80
|
+
if norsk.lower() == 'hotell':
|
81
|
+
return BuildingCategory.HOTEL
|
82
|
+
if norsk.lower() == 'idrettsbygg':
|
83
|
+
return BuildingCategory.SPORTS
|
84
|
+
if norsk.lower() == 'forretningsbygg':
|
85
|
+
return BuildingCategory.RETAIL
|
86
|
+
if norsk.lower() == 'kulturbygg':
|
87
|
+
return BuildingCategory.CULTURE
|
88
|
+
return BuildingCategory.from_string(norsk)
|
89
|
+
|
90
|
+
|
91
|
+
def expand_building_category(row: pd.Series) -> pd.DataFrame:
|
92
|
+
"""
|
93
|
+
Expand a row of data based on the building category into multiple rows,
|
94
|
+
each representing a specific sub-category of either residential or non-residential buildings.
|
95
|
+
|
96
|
+
Parameters
|
97
|
+
----------
|
98
|
+
row : pd.Series
|
99
|
+
A pandas Series containing the data for a single row, including a 'building_category' field.
|
100
|
+
|
101
|
+
Returns
|
102
|
+
-------
|
103
|
+
pd.DataFrame
|
104
|
+
A DataFrame with expanded rows for each sub-category of the building category.
|
105
|
+
"""
|
106
|
+
if row['building_category'] in BuildingCategory:
|
107
|
+
return pd.DataFrame([row.to_dict()])
|
108
|
+
if row['building_category'] == NON_RESIDENTIAL:
|
109
|
+
categories = [b for b in BuildingCategory if b.is_non_residential()]
|
110
|
+
elif row['building_category'] == RESIDENTIAL:
|
111
|
+
categories = [b for b in BuildingCategory if b.is_residential()]
|
112
|
+
|
113
|
+
values = {k: [v] * len(categories) for k, v in row.to_dict().items() if k != 'building_category'}
|
114
|
+
|
115
|
+
return pd.DataFrame({
|
116
|
+
'building_category': categories,
|
117
|
+
**values
|
118
|
+
})
|
119
|
+
|
120
|
+
|
121
|
+
# Apply the function to each row and concatenate the results
|
122
|
+
def expand_building_categories(df: pd.DataFrame, unique_columns: typing.List[str] = None):
|
123
|
+
"""
|
124
|
+
Transform input dataframe so that building_category within groups (residential/non-residential) are unpacked
|
125
|
+
into all containing categories. Duplicates categories are removed. Specific categories with values area
|
126
|
+
preferred over category groups when there is a conflict.
|
127
|
+
|
128
|
+
Parameters
|
129
|
+
----------
|
130
|
+
df : pandas.core.frame.DataFrame
|
131
|
+
unique_columns : str
|
132
|
+
list of column names that should be treated as joint unique. default: ['building_category']
|
133
|
+
|
134
|
+
|
135
|
+
Returns
|
136
|
+
-------
|
137
|
+
pandas.core.frame.DataFrame
|
138
|
+
"""
|
139
|
+
if unique_columns:
|
140
|
+
df = df.drop_duplicates(subset=unique_columns, ignore_index=True, keep='last')
|
141
|
+
groups = df[df.building_category.isin([RESIDENTIAL, NON_RESIDENTIAL])]
|
142
|
+
specific = df[~df.building_category.isin(groups.building_category)]
|
143
|
+
|
144
|
+
expanded_groups = [expand_building_category(row) for _, row in groups.iterrows()]
|
145
|
+
|
146
|
+
filtered = [d[~d.building_category.isin(specific.building_category)] for d in expanded_groups]
|
147
|
+
|
148
|
+
return pd.concat(filtered + [specific]).reindex()
|
149
|
+
|
150
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
from enum import StrEnum, unique, auto
|
4
|
+
|
5
|
+
|
6
|
+
@unique
|
7
|
+
class BuildingCondition(StrEnum):
|
8
|
+
ORIGINAL_CONDITION = auto()
|
9
|
+
SMALL_MEASURE = auto()
|
10
|
+
RENOVATION = auto()
|
11
|
+
RENOVATION_AND_SMALL_MEASURE = auto()
|
12
|
+
DEMOLITION = auto()
|
13
|
+
|
14
|
+
@classmethod
|
15
|
+
def _missing_(cls, value: str):
|
16
|
+
"""
|
17
|
+
Attempts to create an enum member from a given value by normalizing the string.
|
18
|
+
|
19
|
+
This method is called when a value is not found in the enumeration. It converts the input value
|
20
|
+
to lowercase, replaces spaces and hyphens with underscores, and then checks if this transformed
|
21
|
+
value matches the value of any existing enum member.
|
22
|
+
|
23
|
+
Parameters:
|
24
|
+
- value (str): The input value to convert and check against existing enum members.
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
- Enum member: The corresponding enum member if a match is found.
|
28
|
+
|
29
|
+
Raises:
|
30
|
+
- ValueError: If no matching enum member is found.
|
31
|
+
"""
|
32
|
+
value = value.lower().replace(' ', '_').replace('-', '_')
|
33
|
+
for member in cls:
|
34
|
+
if member.value == value:
|
35
|
+
return member
|
36
|
+
return ValueError(f'No such building condition: {value}')
|
37
|
+
|
38
|
+
def __repr__(self):
|
39
|
+
return f'{self.__class__.__name__}.{self.name}'
|
40
|
+
|
41
|
+
@staticmethod
|
42
|
+
def get_scruve_condition_list() -> typing.List[str]:
|
43
|
+
"""
|
44
|
+
Retrieves a list with the building conditions used in S-curve calculations (in lower case).
|
45
|
+
|
46
|
+
Returns:
|
47
|
+
- condition_list (list[str]): list of building conditions
|
48
|
+
"""
|
49
|
+
condition_list = [BuildingCondition.SMALL_MEASURE.value, BuildingCondition.RENOVATION.value, BuildingCondition.DEMOLITION.value]
|
50
|
+
return condition_list
|
51
|
+
|
52
|
+
@staticmethod
|
53
|
+
def get_full_condition_list() -> typing.List[str]:
|
54
|
+
"""
|
55
|
+
Retrieves a list with all building conditions (in lower case).
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
- condition_list (list[str]): list of building conditions
|
59
|
+
"""
|
60
|
+
condition_list = [condition.value for condition in iter(BuildingCondition)]
|
61
|
+
return condition_list
|
62
|
+
|
63
|
+
@staticmethod
|
64
|
+
def existing_conditions() -> typing.Iterable['BuildingCondition']:
|
65
|
+
"""
|
66
|
+
Returns all BuildingCondition except demolition
|
67
|
+
|
68
|
+
Returns
|
69
|
+
-------
|
70
|
+
Iterable of all BuildingCondition except demolition
|
71
|
+
"""
|
72
|
+
yield from (b for b in BuildingCondition if b != BuildingCondition.DEMOLITION)
|
73
|
+
|
74
|
+
|
75
|
+
if __name__ == '__main__':
|
76
|
+
for building_condition in BuildingCondition:
|
77
|
+
print(building_condition)
|
78
|
+
print(repr(building_condition))
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import pathlib
|
2
|
+
import typing
|
3
|
+
|
4
|
+
from loguru import logger
|
5
|
+
import pandas as pd
|
6
|
+
|
7
|
+
from ebm.model.file_handler import FileHandler
|
8
|
+
|
9
|
+
|
10
|
+
class EnergyRequirementCalibrationWriter:
|
11
|
+
|
12
|
+
def __init__(self):
|
13
|
+
pass
|
14
|
+
|
15
|
+
def load(self, df: pd.DataFrame, to_file: typing.Union[str, pathlib.Path] = None):
|
16
|
+
logger.debug(f'Save {to_file}')
|
17
|
+
if to_file is None:
|
18
|
+
to_file = pathlib.Path('input') / FileHandler.CALIBRATE_ENERGY_REQUIREMENT
|
19
|
+
file_path: pathlib.Path = to_file if isinstance(to_file, pathlib.Path) else pathlib.Path(to_file)
|
20
|
+
df = df[df['group'].isin(['energy_requirements', 'energy_requirement'])]
|
21
|
+
df = df.rename(columns={'variable': 'purpose'})
|
22
|
+
df = df[['building_category', 'purpose', 'heating_rv_factor']].reset_index(drop=True)
|
23
|
+
if file_path.suffix == '.csv':
|
24
|
+
df.to_csv(file_path, index=False)
|
25
|
+
elif file_path.suffix == '.xlsx':
|
26
|
+
df.to_excel(file_path, index=False)
|
27
|
+
logger.info(f'Wrote {to_file}')
|
28
|
+
|
29
|
+
|
30
|
+
class EnergyConsumptionCalibrationWriter:
|
31
|
+
df: pd.DataFrame
|
32
|
+
|
33
|
+
def __init__(self):
|
34
|
+
pass
|
35
|
+
|
36
|
+
def transform(self, df):
|
37
|
+
df = df[df['group'] == 'energy_consumption']
|
38
|
+
df = df[['building_category', 'variable', 'extra', 'heating_rv_factor']].reset_index(drop=True)
|
39
|
+
df = df.rename(columns={'variable': 'to',
|
40
|
+
'extra': 'from',
|
41
|
+
'heating_rv_factor': 'factor'}, errors='ignore')
|
42
|
+
|
43
|
+
self.df = df
|
44
|
+
return df
|
45
|
+
|
46
|
+
def load(self, df: pd.DataFrame, to_file: typing.Union[str, pathlib.Path] = None):
|
47
|
+
logger.debug(f'Save {to_file}')
|
48
|
+
if to_file is None:
|
49
|
+
to_file = pathlib.Path('input/calibrate_energy_consumption.xlsx')
|
50
|
+
file_path: pathlib.Path = to_file if isinstance(to_file, pathlib.Path) else pathlib.Path(to_file)
|
51
|
+
|
52
|
+
if file_path.suffix == '.csv':
|
53
|
+
df.to_csv(file_path, index=False)
|
54
|
+
elif file_path.suffix == '.xlsx':
|
55
|
+
df.to_excel(file_path, index=False)
|
56
|
+
logger.info(f'Wrote {to_file}')
|
57
|
+
|
58
|
+
|
59
|
+
def transform(heating_rv: pd.Series, heating_rv_factor=None) -> pd.Series:
|
60
|
+
if heating_rv_factor is None:
|
61
|
+
return heating_rv
|
62
|
+
calibrated = heating_rv * heating_rv_factor
|
63
|
+
calibrated.name = heating_rv.name
|
64
|
+
return calibrated
|
65
|
+
|
66
|
+
|
67
|
+
class EbmCalibration:
|
68
|
+
energy_requirement_original_condition: pd.Series
|
69
|
+
pass
|
70
|
+
|
71
|
+
|
72
|
+
class CalibrationReader:
|
73
|
+
def extract(self) -> pd.Series:
|
74
|
+
pass
|
75
|
+
|
76
|
+
def transform(self) -> pd.Series:
|
77
|
+
pass
|
78
|
+
|
79
|
+
def load(self) -> None:
|
80
|
+
pass
|
81
|
+
|
82
|
+
|
83
|
+
class CalibrationWriter:
|
84
|
+
pass
|
@@ -0,0 +1,180 @@
|
|
1
|
+
from loguru import logger
|
2
|
+
import pandas as pd
|
3
|
+
|
4
|
+
from ebm.cmd.run_calculation import calculate_building_category_area_forecast
|
5
|
+
from ebm.cmd.run_calculation import calculate_building_category_energy_requirements, calculate_heating_systems
|
6
|
+
from ebm.model.data_classes import YearRange
|
7
|
+
from ebm.model.building_category import BuildingCategory
|
8
|
+
|
9
|
+
from ebm.energy_consumption import (HEATING_RV_BASE_TOTAL, HEATING_RV_PEAK_TOTAL, BASE_LOAD_ENERGY_PRODUCT, PEAK_LOAD_ENERGY_PRODUCT,
|
10
|
+
TERTIARY_LOAD_ENERGY_PRODUCT, HEATING_RV_TERTIARY_TOTAL, COOLING_TOTAL, OTHER_TOTAL, DHW_TOTAL,
|
11
|
+
DOMESTIC_HOT_WATER_ENERGY_PRODUCT, HEAT_PUMP, HP_ENERGY_SOURCE)
|
12
|
+
|
13
|
+
ELECTRICITY = 'Elektrisitet'
|
14
|
+
DISTRICT_HEATING = 'Fjernvarme'
|
15
|
+
BIO = 'Bio'
|
16
|
+
FOSSIL = 'Fossil'
|
17
|
+
|
18
|
+
DOMESTIC_HOT_WATER = 'Tappevann'
|
19
|
+
|
20
|
+
HEATPUMP_AIR_SOURCE = 'Luft/luft'
|
21
|
+
HEATPUMP_WATER_SOUCE = 'Vannbåren varme'
|
22
|
+
|
23
|
+
CALIBRATION_YEAR = 2023
|
24
|
+
|
25
|
+
model_period = YearRange(2020, 2050)
|
26
|
+
start_year = model_period.start
|
27
|
+
end_year = model_period.end
|
28
|
+
|
29
|
+
|
30
|
+
def extract_area_forecast(database_manager) -> pd.DataFrame:
|
31
|
+
area_forecasts = []
|
32
|
+
for building_category in BuildingCategory:
|
33
|
+
area_forecast_result = calculate_building_category_area_forecast(
|
34
|
+
building_category=building_category,
|
35
|
+
database_manager=database_manager,
|
36
|
+
start_year=start_year,
|
37
|
+
end_year=end_year)
|
38
|
+
area_forecasts.append(area_forecast_result)
|
39
|
+
|
40
|
+
area_forecast = pd.concat(area_forecasts)
|
41
|
+
return area_forecast
|
42
|
+
|
43
|
+
|
44
|
+
def extract_energy_requirements(area_forecast: pd.DataFrame, database_manager) -> pd.DataFrame:
|
45
|
+
en_req = calculate_building_category_energy_requirements(
|
46
|
+
building_category=list(BuildingCategory),
|
47
|
+
area_forecast=area_forecast,
|
48
|
+
database_manager=database_manager,
|
49
|
+
start_year=start_year,
|
50
|
+
end_year=end_year)
|
51
|
+
|
52
|
+
return en_req
|
53
|
+
|
54
|
+
|
55
|
+
def extract_heating_systems(energy_requirements, database_manager) -> pd.DataFrame:
|
56
|
+
heating_systems = calculate_heating_systems(energy_requirements=energy_requirements,
|
57
|
+
database_manager=database_manager)
|
58
|
+
|
59
|
+
# heating_systems[heating_systems['purpose']=='heating_rv']
|
60
|
+
return heating_systems
|
61
|
+
|
62
|
+
|
63
|
+
def transform_by_energy_source(df, energy_class_column, energy_source_column):
|
64
|
+
rv_gl = df.loc[:, [energy_class_column, energy_source_column, 'building_group']]
|
65
|
+
rv_gl = rv_gl[rv_gl[energy_class_column] > 0]
|
66
|
+
rv_gl['typ'] = energy_class_column
|
67
|
+
rv_gl = rv_gl.rename(columns={energy_source_column: 'energy_source',
|
68
|
+
energy_class_column: 'energy_use'})
|
69
|
+
rv_gl = rv_gl.reset_index().set_index(['building_category',
|
70
|
+
'building_condition',
|
71
|
+
'purpose',
|
72
|
+
'building_code',
|
73
|
+
'year',
|
74
|
+
'heating_systems',
|
75
|
+
'typ'])
|
76
|
+
return rv_gl
|
77
|
+
|
78
|
+
|
79
|
+
def group_heating_systems_by_energy_carrier(df: pd.DataFrame) -> pd.DataFrame:
|
80
|
+
df = df.reindex()
|
81
|
+
df = df.sort_index()
|
82
|
+
df['building_group'] = 'yrkesbygg'
|
83
|
+
try:
|
84
|
+
df.loc[('house', slice(None),slice(None),slice(None),slice(None), slice(None),), 'building_group'] = 'bolig'
|
85
|
+
except KeyError as key_error:
|
86
|
+
logger.error('Missing key when setting group bolig for house')
|
87
|
+
logger.error(key_error)
|
88
|
+
try:
|
89
|
+
df.loc[('apartment_block', slice(None),slice(None),slice(None), slice(None), slice(None),), 'building_group'] = 'bolig'
|
90
|
+
except KeyError as key_error:
|
91
|
+
logger.error('Missing key when setting group bolig for apartment_block')
|
92
|
+
logger.error(key_error)
|
93
|
+
|
94
|
+
# df.loc['apartment_block', 'building_group'] = 'bolig'
|
95
|
+
|
96
|
+
df['ALWAYS_ELECTRICITY'] = 'Electricity'
|
97
|
+
rv_gl = transform_by_energy_source(df, HEATING_RV_BASE_TOTAL, BASE_LOAD_ENERGY_PRODUCT)
|
98
|
+
rv_sl = transform_by_energy_source(df, HEATING_RV_PEAK_TOTAL, PEAK_LOAD_ENERGY_PRODUCT)
|
99
|
+
rv_el = transform_by_energy_source(df, HEATING_RV_TERTIARY_TOTAL, TERTIARY_LOAD_ENERGY_PRODUCT)
|
100
|
+
cooling = transform_by_energy_source(df, COOLING_TOTAL, 'ALWAYS_ELECTRICITY')
|
101
|
+
spesifikt_elforbruk = transform_by_energy_source(df, OTHER_TOTAL, 'ALWAYS_ELECTRICITY')
|
102
|
+
tappevann = transform_by_energy_source(df, DHW_TOTAL, DOMESTIC_HOT_WATER_ENERGY_PRODUCT)
|
103
|
+
rv_hp = transform_by_energy_source(df, HEAT_PUMP, HP_ENERGY_SOURCE)
|
104
|
+
|
105
|
+
energy_use = pd.concat([rv_gl, rv_sl, rv_el, cooling, spesifikt_elforbruk, tappevann, rv_hp])
|
106
|
+
|
107
|
+
sums = energy_use.groupby(by=['building_group', 'energy_source', 'year']).sum() / (10**6)
|
108
|
+
df = sums.reset_index()
|
109
|
+
df = df.rename(columns={'building_group': 'building_category'})
|
110
|
+
try:
|
111
|
+
df.loc[df.energy_source == 'DH', 'energy_source'] = 'Fjernvarme'
|
112
|
+
except KeyError as ex:
|
113
|
+
logger.exception(ex)
|
114
|
+
try:
|
115
|
+
df.loc[df.energy_source == 'Electricity', 'energy_source'] = 'Elektrisitet'
|
116
|
+
except KeyError as ex:
|
117
|
+
logger.exception(ex)
|
118
|
+
try:
|
119
|
+
df.loc[df.building_category == 'bolig', 'building_category'] = 'Bolig'
|
120
|
+
except KeyError as ex:
|
121
|
+
logger.exception(ex)
|
122
|
+
try:
|
123
|
+
df.loc[df.building_category == 'yrkesbygg', 'building_category'] = 'Yrkesbygg'
|
124
|
+
except KeyError as ex:
|
125
|
+
logger.exception(ex)
|
126
|
+
return df.set_index(['building_category', 'energy_source', 'year'])
|
127
|
+
|
128
|
+
|
129
|
+
def transform_pumps(df: pd.DataFrame, calibration_year) -> pd.DataFrame:
|
130
|
+
df['building_group'] = 'Yrkesbygg'
|
131
|
+
df.loc['house', 'building_group'] = 'Bolig'
|
132
|
+
df.loc['apartment_block', 'building_group'] = 'Bolig'
|
133
|
+
|
134
|
+
return df
|
135
|
+
|
136
|
+
|
137
|
+
def _calculate_energy_source(df, heating_type, primary_source, secondary_source=None):
|
138
|
+
if secondary_source and primary_source == secondary_source:
|
139
|
+
df.loc[(heating_type, slice(None)), primary_source] = df.loc[(heating_type, slice(None)), HEATING_RV_BASE_TOTAL] + \
|
140
|
+
df.loc[(heating_type, slice(None)), HEATING_RV_PEAK_TOTAL]
|
141
|
+
|
142
|
+
return df
|
143
|
+
df.loc[(heating_type, slice(None)), primary_source] = df.loc[(heating_type, slice(None)), HEATING_RV_BASE_TOTAL]
|
144
|
+
if secondary_source:
|
145
|
+
df.loc[(heating_type, slice(None)), secondary_source] = df.loc[
|
146
|
+
(heating_type, slice(None)), HEATING_RV_PEAK_TOTAL]
|
147
|
+
|
148
|
+
return df
|
149
|
+
|
150
|
+
|
151
|
+
def sort_heating_systems_by_energy_source(transformed):
|
152
|
+
custom_order = [ELECTRICITY, BIO, FOSSIL, DISTRICT_HEATING]
|
153
|
+
|
154
|
+
unsorted = transformed.reset_index()
|
155
|
+
unsorted['energy_source'] = pd.Categorical(unsorted['energy_source'], categories=custom_order, ordered=True)
|
156
|
+
df_sorted = unsorted.sort_values(by=['energy_source'])
|
157
|
+
df_sorted = df_sorted.set_index([('energy_source', '')])
|
158
|
+
|
159
|
+
return df_sorted
|
160
|
+
|
161
|
+
|
162
|
+
class DistributionOfHeatingSystems:
|
163
|
+
@staticmethod
|
164
|
+
def extract(database_manager):
|
165
|
+
return database_manager.get_heating_systems_shares_start_year()
|
166
|
+
|
167
|
+
@staticmethod
|
168
|
+
def transform(df):
|
169
|
+
df = df.reset_index()
|
170
|
+
df['building_group'] = 'Yrkesbygg'
|
171
|
+
|
172
|
+
df = df[df['building_category'] != 'storage_repairs']
|
173
|
+
df.loc[df['building_category'].isin(['apartment_block']), 'building_group'] = 'Boligblokk'
|
174
|
+
df.loc[df['building_category'].isin(['house']), 'building_group'] = 'Småhus'
|
175
|
+
|
176
|
+
distribution_of_heating_systems_by_building_group = df.groupby(by=['building_group', 'heating_systems'])[
|
177
|
+
['heating_system_share']].mean()
|
178
|
+
return distribution_of_heating_systems_by_building_group
|
179
|
+
|
180
|
+
|
@@ -0,0 +1,157 @@
|
|
1
|
+
import pathlib
|
2
|
+
from typing import List, Optional
|
3
|
+
|
4
|
+
import pandas as pd
|
5
|
+
from pandera.typing.common import DataFrameBase
|
6
|
+
|
7
|
+
from ebm.model.building_category import BuildingCategory
|
8
|
+
|
9
|
+
|
10
|
+
def explode_building_category_column(df: pd.DataFrame, unique_columns: List[str]) -> pd.DataFrame:
|
11
|
+
"""
|
12
|
+
Explodes the 'building_category' column in the DataFrame into multiple columns based on residential and non-residential categories.
|
13
|
+
|
14
|
+
Parameters
|
15
|
+
----------
|
16
|
+
df : pd.DataFrame
|
17
|
+
The input DataFrame containing the 'building_category' column.
|
18
|
+
unique_columns : List[str]
|
19
|
+
List of columns to use for de-duplication.
|
20
|
+
|
21
|
+
Returns
|
22
|
+
-------
|
23
|
+
pd.DataFrame
|
24
|
+
The DataFrame with exploded 'building_category' columns.
|
25
|
+
"""
|
26
|
+
df = explode_column_alias(df=df, column='building_category',
|
27
|
+
values=[bc for bc in BuildingCategory if bc.is_residential()],
|
28
|
+
alias='residential',
|
29
|
+
de_dup_by=unique_columns)
|
30
|
+
df = explode_column_alias(df=df, column='building_category',
|
31
|
+
values=[bc for bc in BuildingCategory if not bc.is_residential()],
|
32
|
+
alias='non_residential',
|
33
|
+
de_dup_by=unique_columns)
|
34
|
+
df = explode_column_alias(df=df, column='building_category',
|
35
|
+
values=[bc for bc in BuildingCategory],
|
36
|
+
alias='default',
|
37
|
+
de_dup_by=unique_columns)
|
38
|
+
return df
|
39
|
+
|
40
|
+
|
41
|
+
def explode_building_code_column(df: pd.DataFrame, unique_columns: List[str],
|
42
|
+
default_building_code: None | pd.DataFrame = None) -> pd.DataFrame:
|
43
|
+
"""
|
44
|
+
Explodes the 'building_code' column in the DataFrame into multiple columns based on the provided building_codelist.
|
45
|
+
|
46
|
+
Parameters
|
47
|
+
----------
|
48
|
+
df : pd.DataFrame
|
49
|
+
The input DataFrame containing the 'building_code' column.
|
50
|
+
unique_columns : List[str]
|
51
|
+
List of columns to use for de-duplication.
|
52
|
+
default_building_code : Optional[pd.DataFrame], optional
|
53
|
+
DataFrame containing default building_codevalues. If not provided, building_codevalues are read from 'input/building_codes.csv'.
|
54
|
+
|
55
|
+
Returns
|
56
|
+
-------
|
57
|
+
pd.DataFrame
|
58
|
+
The DataFrame with exploded 'building_code' columns.
|
59
|
+
"""
|
60
|
+
# Hvor skal building_code_list hentes fra?
|
61
|
+
building_code_list = pd.read_csv(pathlib.Path(__file__).parent.parent / 'data' / 'building_code_parameters.csv')['building_code'].unique() if default_building_code is None else default_building_code
|
62
|
+
df = explode_column_alias(df=df,
|
63
|
+
column='building_code',
|
64
|
+
values=building_code_list,
|
65
|
+
de_dup_by=unique_columns)
|
66
|
+
return df
|
67
|
+
|
68
|
+
|
69
|
+
def explode_unique_columns(df: pd.DataFrame| DataFrameBase,
|
70
|
+
unique_columns: List[str],
|
71
|
+
default_building_code: List[str]|None = None) -> pd.DataFrame:
|
72
|
+
"""
|
73
|
+
Explodes 'building_code' and 'building_category' columns in df.
|
74
|
+
|
75
|
+
|
76
|
+
Parameters
|
77
|
+
----------
|
78
|
+
df : pd.DataFrame
|
79
|
+
The input DataFrame containing the columns to be exploded.
|
80
|
+
unique_columns : List[str]
|
81
|
+
List of columns to use for de-duplication.
|
82
|
+
default_building_code : List[str], optional
|
83
|
+
List of TEKs to replace default
|
84
|
+
|
85
|
+
Returns
|
86
|
+
-------
|
87
|
+
pd.DataFrame
|
88
|
+
The DataFrame with exploded columns.
|
89
|
+
"""
|
90
|
+
|
91
|
+
df = explode_building_code_column(df, unique_columns, default_building_code=default_building_code)
|
92
|
+
df = explode_building_category_column(df, unique_columns)
|
93
|
+
return df
|
94
|
+
|
95
|
+
|
96
|
+
def explode_column_alias(df, column, values: list|dict=None, alias='default', de_dup_by: list[str]=None):
|
97
|
+
"""
|
98
|
+
Explodes a specified column in the DataFrame into multiple rows based on provided values and alias.
|
99
|
+
|
100
|
+
Parameters
|
101
|
+
----------
|
102
|
+
df : pd.DataFrame
|
103
|
+
The input DataFrame containing the column to be exploded.
|
104
|
+
column : str
|
105
|
+
The name of the column to be exploded.
|
106
|
+
values : Optional[List[str], dict[str, list[str]], optional
|
107
|
+
List or dict of values to explode the column by. If not provided, unique values from the column excluding the
|
108
|
+
alias are used.
|
109
|
+
alias : str, optional
|
110
|
+
The alias to be used for default values. Default is 'default'.
|
111
|
+
When values is a dict the parameter alias is ignored
|
112
|
+
de_dup_by : Optional[List[str]], optional
|
113
|
+
List of columns to use for de-duplication. If not provided, no de-duplication is performed.
|
114
|
+
|
115
|
+
Returns
|
116
|
+
-------
|
117
|
+
pd.DataFrame
|
118
|
+
The DataFrame with the exploded column.
|
119
|
+
|
120
|
+
Examples
|
121
|
+
--------
|
122
|
+
>>> d_f = pd.DataFrame({'category': ['A', 'B', 'default']})
|
123
|
+
>>> explode_column_alias(d_f, column='category', values=['A', 'B'], alias='default')
|
124
|
+
category
|
125
|
+
0 A
|
126
|
+
1 B
|
127
|
+
2 A
|
128
|
+
2 B
|
129
|
+
"""
|
130
|
+
if column not in df.columns:
|
131
|
+
raise ValueError(f"The DataFrame (df) must contain the column: {column}")
|
132
|
+
|
133
|
+
df = replace_column_alias(df, column=column, values=values, alias=alias, de_dup_by=None)
|
134
|
+
|
135
|
+
df = df.assign(**{column: df[column].str.split('+')}).explode(column)
|
136
|
+
if de_dup_by:
|
137
|
+
df = df.sort_values(by='_explode_column_alias_default', ascending=True)
|
138
|
+
df = df.drop_duplicates(de_dup_by)
|
139
|
+
return df.drop(columns=['_explode_column_alias_default'], errors='ignore')
|
140
|
+
|
141
|
+
|
142
|
+
def replace_column_alias(df: pd.DataFrame, column: str, values: Optional[list|dict]=None, alias: Optional[str]='default',
|
143
|
+
de_dup_by=None) -> pd.DataFrame:
|
144
|
+
values = values if values is not None else [c for c in df[column].unique().tolist() if c != alias]
|
145
|
+
aliases = {alias: values} if not isinstance(values, dict) else values
|
146
|
+
df = df.copy()
|
147
|
+
for k, v in aliases.items():
|
148
|
+
df['_explode_column_alias_default'] = df[column] == k
|
149
|
+
df.loc[df[df[column] == k].index, column] = '+'.join(v)
|
150
|
+
if not de_dup_by:
|
151
|
+
return df
|
152
|
+
return df.drop(columns=['_explode_column_alias_default'], errors='ignore')
|
153
|
+
|
154
|
+
|
155
|
+
def explode_column(df: pd.DataFrame, column: str) -> pd.DataFrame:
|
156
|
+
df = df.assign(**{column: df[column].str.split('+')}).explode(column)
|
157
|
+
return df
|