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 CHANGED
@@ -1,6 +1,6 @@
1
1
  """Open Energy ID Python SDK."""
2
2
 
3
- __version__ = "0.1.7"
3
+ __version__ = "0.1.9"
4
4
 
5
5
  from .enums import Granularity
6
6
  from .models import TimeSeries
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(
@@ -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
- else:
167
- self._list_of_fits.append(best_fit)
168
- all_model_terms_dict.pop(best_x)
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openenergyid
3
- Version: 0.1.7
3
+ Version: 0.1.9
4
4
  Summary: Open Source Python library for energy analytics and simulations
5
5
  Project-URL: Homepage, https://energyid.eu
6
6
  Project-URL: Repository, https://github.com/EnergieID/OpenEnergyID
@@ -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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.18.0
2
+ Generator: hatchling 1.21.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -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,,