openenergyid 0.1.7__py2.py3-none-any.whl → 0.1.9__py2.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.
Potentially problematic release.
This version of openenergyid might be problematic. Click here for more details.
- openenergyid/__init__.py +1 -1
- openenergyid/const.py +13 -0
- openenergyid/models.py +42 -0
- openenergyid/mvlr/main.py +2 -0
- openenergyid/mvlr/models.py +22 -0
- openenergyid/mvlr/mvlr.py +50 -3
- {openenergyid-0.1.7.dist-info → openenergyid-0.1.9.dist-info}/METADATA +1 -1
- openenergyid-0.1.9.dist-info/RECORD +13 -0
- {openenergyid-0.1.7.dist-info → openenergyid-0.1.9.dist-info}/WHEEL +1 -1
- openenergyid-0.1.7.dist-info/RECORD +0 -12
- {openenergyid-0.1.7.dist-info → openenergyid-0.1.9.dist-info}/licenses/LICENSE +0 -0
openenergyid/__init__.py
CHANGED
openenergyid/const.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Constants for the Open Energy ID package."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
# METRICS
|
|
6
|
+
|
|
7
|
+
ELECTRICITY_DELIVERED: Literal["electricity_delivered"] = "electricity_delivered"
|
|
8
|
+
ELECTRICITY_EXPORTED: Literal["electricity_exported"] = "electricity_exported"
|
|
9
|
+
ELECTRICITY_PRODUCED: Literal["electricity_produced"] = "electricity_produced"
|
|
10
|
+
|
|
11
|
+
PRICE_DAY_AHEAD: Literal["price_day_ahead"] = "price_day_ahead"
|
|
12
|
+
PRICE_IMBALANCE_UPWARD: Literal["price_imbalance_upward"] = "price_imbalance_upward"
|
|
13
|
+
PRICE_IMBALANCE_DOWNWARD: Literal["price_imbalance_downward"] = "price_imbalance_downward"
|
openenergyid/models.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Data models for the Open Energy ID."""
|
|
2
2
|
|
|
3
3
|
import datetime as dt
|
|
4
|
+
from typing import Optional, overload
|
|
4
5
|
|
|
5
6
|
import pandas as pd
|
|
6
7
|
from pydantic import BaseModel
|
|
@@ -23,3 +24,44 @@ class TimeSeries(BaseModel):
|
|
|
23
24
|
frame = pd.DataFrame(self.data, columns=self.columns, index=self.index)
|
|
24
25
|
frame.index = pd.to_datetime(frame.index, utc=True)
|
|
25
26
|
return frame.tz_convert(timezone)
|
|
27
|
+
|
|
28
|
+
@overload
|
|
29
|
+
def to_json(self, path: None = None, **kwargs) -> str:
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
@overload
|
|
33
|
+
def to_json(self, path: str, **kwargs) -> None:
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
def to_json(self, path: Optional[str] = None, **kwargs) -> Optional[str]:
|
|
37
|
+
"""Save the TimeSeries to a JSON file or return as string."""
|
|
38
|
+
if path is None:
|
|
39
|
+
return self.model_dump_json(**kwargs)
|
|
40
|
+
else:
|
|
41
|
+
encoding = kwargs.pop("encoding", "UTF-8")
|
|
42
|
+
with open(path, "w", encoding=encoding) as file:
|
|
43
|
+
file.write(self.model_dump_json(**kwargs))
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_json(cls, string: str, **kwargs) -> "TimeSeries":
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
@overload
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_json(cls, path: str, **kwargs) -> "TimeSeries":
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_json(
|
|
57
|
+
cls, string: Optional[str] = None, path: Optional[str] = None, **kwargs
|
|
58
|
+
) -> "TimeSeries":
|
|
59
|
+
"""Load the TimeSeries from a JSON file or string."""
|
|
60
|
+
if string:
|
|
61
|
+
return cls.model_validate_json(string, **kwargs)
|
|
62
|
+
elif path:
|
|
63
|
+
encoding = kwargs.pop("encoding", "UTF-8")
|
|
64
|
+
with open(path, "r", encoding=encoding) as file:
|
|
65
|
+
return cls.model_validate_json(file.read(), **kwargs)
|
|
66
|
+
else:
|
|
67
|
+
raise ValueError("Either string or path must be provided.")
|
openenergyid/mvlr/main.py
CHANGED
|
@@ -17,6 +17,8 @@ def find_best_mvlr(
|
|
|
17
17
|
y=data.dependent_variable,
|
|
18
18
|
granularity=granularity,
|
|
19
19
|
allow_negative_predictions=data.allow_negative_predictions,
|
|
20
|
+
single_use_exog_prefixes=data.single_use_exog_prefixes,
|
|
21
|
+
exogs__disallow_negative_coefficient=data.get_disallowed_negative_coefficients(),
|
|
20
22
|
)
|
|
21
23
|
mvlr.do_analysis()
|
|
22
24
|
if mvlr.validate(
|
openenergyid/mvlr/models.py
CHANGED
|
@@ -51,6 +51,11 @@ class IndependentVariableInput(BaseModel):
|
|
|
51
51
|
"Eg. `HDD_16.5` will be Heating Degree Days with a base temperature of 16.5°C, "
|
|
52
52
|
"`CDD_0` will be Cooling Degree Days with a base temperature of 0°C.",
|
|
53
53
|
)
|
|
54
|
+
allow_negative_coefficient: bool = Field(
|
|
55
|
+
default=True,
|
|
56
|
+
alias="allowNegativeCoefficient",
|
|
57
|
+
description="Whether the coefficient can be negative.",
|
|
58
|
+
)
|
|
54
59
|
|
|
55
60
|
|
|
56
61
|
class MultiVariableRegressionInput(BaseModel):
|
|
@@ -67,6 +72,12 @@ class MultiVariableRegressionInput(BaseModel):
|
|
|
67
72
|
validation_parameters: ValidationParameters = Field(
|
|
68
73
|
alias="validationParameters", default=ValidationParameters()
|
|
69
74
|
)
|
|
75
|
+
single_use_exog_prefixes: Optional[List[str]] = Field(
|
|
76
|
+
# default=["HDD", "CDD", "FDD"],
|
|
77
|
+
default=None,
|
|
78
|
+
alias="singleUseExogPrefixes",
|
|
79
|
+
description="List of prefixes to be used as single-use exogenous variables.",
|
|
80
|
+
)
|
|
70
81
|
|
|
71
82
|
def model_post_init(self, __context: Any) -> None:
|
|
72
83
|
"""Post init hook."""
|
|
@@ -117,6 +128,17 @@ class MultiVariableRegressionInput(BaseModel):
|
|
|
117
128
|
|
|
118
129
|
return frame
|
|
119
130
|
|
|
131
|
+
def get_disallowed_negative_coefficients(self) -> List[str]:
|
|
132
|
+
"""Get independent variables that are not allowed to have a negative coefficient."""
|
|
133
|
+
result = []
|
|
134
|
+
for iv in self.independent_variables: # pylint: disable=not-an-iterable
|
|
135
|
+
if iv.name == COLUMN_TEMPERATUREEQUIVALENT and iv.variants is not None:
|
|
136
|
+
if not iv.allow_negative_coefficient:
|
|
137
|
+
result.extend(iv.variants)
|
|
138
|
+
elif not iv.allow_negative_coefficient:
|
|
139
|
+
result.append(iv.name)
|
|
140
|
+
return result
|
|
141
|
+
|
|
120
142
|
|
|
121
143
|
######################
|
|
122
144
|
# MVLR Result Models #
|
openenergyid/mvlr/mvlr.py
CHANGED
|
@@ -40,6 +40,8 @@ class MultiVariableLinearRegression:
|
|
|
40
40
|
cross_validation: bool = False,
|
|
41
41
|
allow_negative_predictions: bool = False,
|
|
42
42
|
granularity: Granularity = None,
|
|
43
|
+
single_use_exog_prefixes: list[str] = None,
|
|
44
|
+
exogs__disallow_negative_coefficient: list[str] = None,
|
|
43
45
|
):
|
|
44
46
|
"""Parameters
|
|
45
47
|
----------
|
|
@@ -62,6 +64,17 @@ class MultiVariableLinearRegression:
|
|
|
62
64
|
If True, allow predictions to be negative.
|
|
63
65
|
For gas consumption or PV production, this is not physical
|
|
64
66
|
so allow_negative_predictions should be False
|
|
67
|
+
granularity : Granularity, default=None
|
|
68
|
+
Granularity of the data. Is only used for the output of the model.
|
|
69
|
+
If None, the granularity is not set.
|
|
70
|
+
single_use_exog_prefixes : list of str, default=None
|
|
71
|
+
List of variable prefixes that indicate a variable type that should only be used once.
|
|
72
|
+
For example, if the list contains "HDD", only one of the columns "HDD1", "HDD2", "HDD3" etc.
|
|
73
|
+
will be used as an independent variable.
|
|
74
|
+
Once the best fit using a variable with a given prefix is found, the other variables with the same
|
|
75
|
+
prefix will not be used as independent variables.
|
|
76
|
+
exogs__disallow_negative_coefficient : list of str, default=None
|
|
77
|
+
List of variable names for which the coefficient is not allowed to be negative.
|
|
65
78
|
"""
|
|
66
79
|
self.data = data.copy()
|
|
67
80
|
if y not in self.data.columns:
|
|
@@ -76,6 +89,8 @@ class MultiVariableLinearRegression:
|
|
|
76
89
|
self.cross_validation = cross_validation
|
|
77
90
|
self.allow_negative_predictions = allow_negative_predictions
|
|
78
91
|
self.granularity = granularity
|
|
92
|
+
self.single_use_exog_prefixes = single_use_exog_prefixes
|
|
93
|
+
self.exogs__disallow_negative_coefficient = exogs__disallow_negative_coefficient
|
|
79
94
|
self._fit = None
|
|
80
95
|
self._list_of_fits = []
|
|
81
96
|
self.list_of_cverrors = []
|
|
@@ -150,6 +165,15 @@ class MultiVariableLinearRegression:
|
|
|
150
165
|
ref_fit.model.formula.rhs_termlist + [term],
|
|
151
166
|
)
|
|
152
167
|
fit = fm.ols(model_desc, data=self.data).fit()
|
|
168
|
+
|
|
169
|
+
# Check if the coefficient of the variable is allowed to be negative
|
|
170
|
+
if (
|
|
171
|
+
self.exogs__disallow_negative_coefficient is not None
|
|
172
|
+
and x in self.exogs__disallow_negative_coefficient
|
|
173
|
+
and fit.params[x] < 0
|
|
174
|
+
):
|
|
175
|
+
continue
|
|
176
|
+
|
|
153
177
|
if fit.bic < best_bic:
|
|
154
178
|
best_bic = fit.bic
|
|
155
179
|
best_fit = fit
|
|
@@ -163,9 +187,21 @@ class MultiVariableLinearRegression:
|
|
|
163
187
|
ref_fit.model.formula.rhs_termlist,
|
|
164
188
|
):
|
|
165
189
|
break
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
190
|
+
|
|
191
|
+
self._list_of_fits.append(best_fit)
|
|
192
|
+
all_model_terms_dict.pop(best_x)
|
|
193
|
+
|
|
194
|
+
# Check if `best_x` starts with a prefix that should only be used once
|
|
195
|
+
# If so, remove all other variables with the same prefix from the list of candidates
|
|
196
|
+
if self.single_use_exog_prefixes:
|
|
197
|
+
for prefix in self.single_use_exog_prefixes:
|
|
198
|
+
if best_x.startswith(prefix):
|
|
199
|
+
all_model_terms_dict = {
|
|
200
|
+
k: v
|
|
201
|
+
for k, v in all_model_terms_dict.items()
|
|
202
|
+
if not k.startswith(prefix)
|
|
203
|
+
}
|
|
204
|
+
|
|
169
205
|
self._fit = self._list_of_fits[-1]
|
|
170
206
|
|
|
171
207
|
def _do_analysis_cross_validation(self):
|
|
@@ -237,6 +273,17 @@ class MultiVariableLinearRegression:
|
|
|
237
273
|
# next iteration with the found exog removed
|
|
238
274
|
all_model_terms_dict.pop(best_x)
|
|
239
275
|
|
|
276
|
+
# Check if `best_x` starts with a prefix that should only be used once
|
|
277
|
+
# If so, remove all other variables with the same prefix from the list of candidates
|
|
278
|
+
if self.single_use_exog_prefixes:
|
|
279
|
+
for prefix in self.single_use_exog_prefixes:
|
|
280
|
+
if best_x.startswith(prefix):
|
|
281
|
+
all_model_terms_dict = {
|
|
282
|
+
k: v
|
|
283
|
+
for k, v in all_model_terms_dict.items()
|
|
284
|
+
if not k.startswith(prefix)
|
|
285
|
+
}
|
|
286
|
+
|
|
240
287
|
self._fit = self._list_of_fits[-1]
|
|
241
288
|
|
|
242
289
|
def _prune(self, fit: fm.ols, p_max: float) -> fm.ols:
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
openenergyid/__init__.py,sha256=_KWarhaNa9a-pxb9L4P7NUtWUi8A2CVl9nD29g21mYs,160
|
|
2
|
+
openenergyid/const.py,sha256=bF-U-r0Qj2GWCYBBxReg8fbv2D0V1JzfPMwSEQ5ZWds,569
|
|
3
|
+
openenergyid/enums.py,sha256=jdw4CB1gkisx0re_SesrTEyh_T-UxYp6uieE7iYlHdA,357
|
|
4
|
+
openenergyid/models.py,sha256=pUJpQCodph0NukiIpFdc9X6Zj6qEGQPSWoztYDwqyuE,2214
|
|
5
|
+
openenergyid/mvlr/__init__.py,sha256=Glrc218oqa8tq_Y2G9LXaSoN4Yba-vsjXUi9r9iPzaY,471
|
|
6
|
+
openenergyid/mvlr/helpers.py,sha256=fsx-gSvBdU31BjncFkRd1RySmSPPYgwflCnmSFzox2Q,961
|
|
7
|
+
openenergyid/mvlr/main.py,sha256=cn7jZ98cHn2eh-0zG9q8Pad0Ft_FuI-u3a-eeHeF8jA,1304
|
|
8
|
+
openenergyid/mvlr/models.py,sha256=SdZYroi3EM4D1U6OnnyfBv6ygzfAQM2UzICg0jaQD6w,8616
|
|
9
|
+
openenergyid/mvlr/mvlr.py,sha256=F7WvWnZQtqUmK1vsguemsn9n8pDDk3tQ1weOlv-bo0c,18626
|
|
10
|
+
openenergyid-0.1.9.dist-info/METADATA,sha256=8BhGlQs47GfSa2XnCp3Hb9vQE_eE0vtSo7RoSAH3Mx8,2431
|
|
11
|
+
openenergyid-0.1.9.dist-info/WHEEL,sha256=ccEkY-EGGllEs7ySpwBlD8G4u70wR77CNej8Q6tzIqA,105
|
|
12
|
+
openenergyid-0.1.9.dist-info/licenses/LICENSE,sha256=NgRdcNHwyXVCXZ8sJwoTp0DCowThJ9LWWl4xhbV1IUY,1074
|
|
13
|
+
openenergyid-0.1.9.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
openenergyid/__init__.py,sha256=LWqiRKaHvmuTOpoTZcvQkgPhotMEc7tsuqzZ47Y1AKs,160
|
|
2
|
-
openenergyid/enums.py,sha256=jdw4CB1gkisx0re_SesrTEyh_T-UxYp6uieE7iYlHdA,357
|
|
3
|
-
openenergyid/models.py,sha256=w6YJHi1fysmLZYEI6peTfQAbMS92Kf5sk0VtXw7HrAM,813
|
|
4
|
-
openenergyid/mvlr/__init__.py,sha256=Glrc218oqa8tq_Y2G9LXaSoN4Yba-vsjXUi9r9iPzaY,471
|
|
5
|
-
openenergyid/mvlr/helpers.py,sha256=fsx-gSvBdU31BjncFkRd1RySmSPPYgwflCnmSFzox2Q,961
|
|
6
|
-
openenergyid/mvlr/main.py,sha256=r4o3394jUd72sSJtIae67WDrGB7XP9ZMAiytpr3YFPw,1142
|
|
7
|
-
openenergyid/mvlr/models.py,sha256=F8GrqtAK6bfR3Eur7hp3XjGj433bh4hZVRtGvg3DeUY,7625
|
|
8
|
-
openenergyid/mvlr/mvlr.py,sha256=Cxgc-GeZpVuxvoRgP1Po7ttFdFArA5tW0fB8sihtikI,16063
|
|
9
|
-
openenergyid-0.1.7.dist-info/METADATA,sha256=nDBrDk5TFlmzLFbMAiEnP_4A7QFceNL_RO5F6Mj1UmE,2431
|
|
10
|
-
openenergyid-0.1.7.dist-info/WHEEL,sha256=fagL_Tj29mg80flwlxJNW45nBDbboxF04Tnbc_jt3Bg,105
|
|
11
|
-
openenergyid-0.1.7.dist-info/licenses/LICENSE,sha256=NgRdcNHwyXVCXZ8sJwoTp0DCowThJ9LWWl4xhbV1IUY,1074
|
|
12
|
-
openenergyid-0.1.7.dist-info/RECORD,,
|
|
File without changes
|