Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions tests/resources/solar_projection.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
,Elevation,Azimuth,BHI,DHI,BNI,proj_tilt_35_az_154_alb_025
2009-07-12 00:00:00+00:00,-23.15501,358.02192,0.0,0.0,0.0,0.0
2009-07-12 01:00:00+00:00,-22.13249,13.0521,0.0,0.0,0.0,0.0
2009-07-12 02:00:00+00:00,-18.45929,27.35737,0.0,0.0,0.0,0.0
2009-07-12 03:00:00+00:00,-12.53057,40.41785,0.0,0.0,0.0,0.0
2009-07-12 04:00:00+00:00,-4.84902,52.15285,1.2421,4.9005,23.3832,4.59624
2009-07-12 05:00:00+00:00,4.10789,62.80058,51.4441,47.5719,302.5096,59.63713
2009-07-12 06:00:00+00:00,13.93921,72.77237,177.8372,85.0891,537.0757,234.91605
2009-07-12 07:00:00+00:00,24.31203,82.59633,303.2679,121.5737,610.9364,428.0604
2009-07-12 08:00:00+00:00,34.91997,92.97471,479.3531,128.3813,742.788,648.03782
2009-07-12 09:00:00+00:00,45.40703,104.99125,455.9864,222.8379,593.7701,721.2353
2009-07-12 10:00:00+00:00,55.20735,120.57558,182.644,373.4821,216.0035,556.59217
2009-07-12 11:00:00+00:00,63.16619,143.12468,179.1913,430.0031,196.5999,598.58417
2009-07-12 12:00:00+00:00,67.00673,175.30016,185.5595,437.3183,202.4893,606.8154
2009-07-12 13:00:00+00:00,64.68676,209.27264,260.3788,402.0775,297.6538,642.69273
2009-07-12 14:00:00+00:00,57.53711,234.30403,206.3438,368.7997,257.1175,539.49189
2009-07-12 15:00:00+00:00,48.06597,251.31949,331.3794,227.0156,492.3087,495.07234
2009-07-12 16:00:00+00:00,37.68394,264.03145,283.1507,152.701,521.9771,328.96706
2009-07-12 17:00:00+00:00,27.06107,274.70758,213.5861,92.2861,573.8196,155.0263
2009-07-12 18:00:00+00:00,16.58682,284.59487,80.4939,57.7285,383.0992,55.63311
2009-07-12 19:00:00+00:00,6.57083,294.46803,5.5889,12.5657,68.5959,11.83986
2009-07-12 20:00:00+00:00,-2.66638,304.8913,0.0,0.0,0.0,0.0
2009-07-12 21:00:00+00:00,-10.74418,316.30955,0.0,0.0,0.0,0.0
2009-07-12 22:00:00+00:00,-17.20259,329.02074,0.0,0.0,0.0,0.0
2009-07-12 23:00:00+00:00,-21.53409,343.04711,0.0,0.0,0.0,0.0
65 changes: 63 additions & 2 deletions tests/test_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
Interpolate,
ExpressionCombine,
FillOikoMeteo,
AddOikoData,
AddSolarAngles,
ProjectSolarRadOnSurfaces,
)

RESOURCES_PATH = Path(__file__).parent / "resources"
Expand Down Expand Up @@ -695,7 +698,7 @@ def test_combiner(self):

assert res.shape == (3, 6)

@patch("tide.processing.get_oikolab_df", side_effect=mock_get_oikolab_df)
@patch("tide.base.get_oikolab_df", side_effect=mock_get_oikolab_df)
def test_fill_oiko_meteo(self, mock_get_oikolab):
data = pd.read_csv(
RESOURCES_PATH / "meteo_fill_df.csv", parse_dates=True, index_col=0
Expand All @@ -715,7 +718,7 @@ def test_fill_oiko_meteo(self, mock_get_oikolab):
gaps_gte="4h",
lat=-48.87667,
lon=-123.39333,
param_map={
columns_param_map={
"text__°C__outdoor": "temperature",
"gh__W/m²__outdoor": "surface_solar_radiation",
"rh__0-1__outdoor": "relative_humidity",
Expand All @@ -728,3 +731,61 @@ def test_fill_oiko_meteo(self, mock_get_oikolab):
data["gh__W/m²__outdoor"], data_gap["gh__W/m²__outdoor"]
)
assert float(data_gap["text__°C__outdoor"].isnull().sum()) == 13

@patch("tide.base.get_oikolab_df", side_effect=mock_get_oikolab_df)
def test_add_oiko_data(self, mock_get_oikolab):
data_idx = pd.date_range(
start="2009-07-11 16:00:00+00:00",
end="2009-07-12 23:15:00+00:00",
freq="15min",
)
data = pd.DataFrame(
{"tin__°C__Building": np.random.randn(len(data_idx))}, index=data_idx
)
add_oiko = AddOikoData(lat=-48.87667, lon=-123.39333)
res = add_oiko.fit_transform(data)
assert not res.isnull().any().any()
assert res.shape == (126, 13)

def test_add_solar_angles(self):
df = pd.DataFrame(
{"a": np.random.randn(24)},
index=pd.date_range("2024-12-19", freq="h", periods=24),
)

sun_angle = AddSolarAngles()
sun_angle.fit(df.copy())
assert sun_angle.get_feature_names_out() == [
"a",
"sun_el__angle_deg__OTHER__OTHER_SUB_BLOC",
"sun_az__angle_deg__OTHER__OTHER_SUB_BLOC",
]

res = sun_angle.transform(df.copy())
assert res.shape == (24, 3)

def test_processing(self):
test_df = pd.read_csv(
RESOURCES_PATH / "solar_projection.csv", index_col=0, parse_dates=True
)

test_df["GHI"] = test_df["BHI"] + test_df["DHI"]

projector = ProjectSolarRadOnSurfaces(
bni_column_name="BNI",
dhi_column_name="DHI",
ghi_column_name="GHI",
lat=44.844,
lon=-0.564,
surface_azimuth_angles=[180.0, 154],
surface_tilt_angle=[90.0, 35],
albedo=0.25,
surface_name=["proj_180_90", "proj_tilt_35_az_154_alb_025"],
data_bloc="PV",
data_sub_bloc="Pyranometer",
)

projector.fit(test_df)
res = projector.transform(test_df.copy())

assert res.shape == (24, 9)
58 changes: 50 additions & 8 deletions tide/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

import datetime as dt
import typing
from abc import ABC, abstractmethod
Expand All @@ -15,13 +17,11 @@
validate_odd_param,
process_stl_odd_args,
get_data_blocks,
get_freq_delta_or_min_time_interval,
ensure_list,
)


def _ensure_list(item):
if item is None:
return []
return item if isinstance(item, list) else [item]
from tide.meteo import get_oikolab_df


class BaseProcessing(ABC, TransformerMixin, BaseEstimator):
Expand Down Expand Up @@ -110,8 +110,8 @@ def fit_check_features(self, X):

def get_feature_names_out(self, input_features=None):
check_is_fitted(self, attributes=["feature_names_in_"])
added_columns = _ensure_list(self.added_columns)
removed_columns = _ensure_list(self.removed_columns)
added_columns = ensure_list(self.added_columns)
removed_columns = ensure_list(self.removed_columns)

features_out = self.feature_names_in_.copy() + added_columns
return [feature for feature in features_out if feature not in removed_columns]
Expand Down Expand Up @@ -142,7 +142,7 @@ def _transform_implementation(self, X: pd.Series | pd.DataFrame):
pass


class BaseSTL(ABC, BaseEstimator):
class BaseSTL(BaseEstimator):
def __init__(
self,
period: int | str | dt.timedelta = "24h",
Expand Down Expand Up @@ -203,3 +203,45 @@ def get_gaps_mask(self, X: pd.Series | pd.DataFrame):
df_mask[col] = np.zeros_like(X.shape[0]).astype(bool)

return df_mask


class BaseOikoMeteo:
def __init__(
self,
lat: float = 43.47,
lon: float = -1.51,
model: str = "era5",
env_oiko_api_key: str = "OIKO_API_KEY",
):
self.lat = lat
self.lon = lon
self.model = model
self.env_oiko_api_key = env_oiko_api_key

def get_api_key_from_env(self):
self.api_key_ = os.getenv(self.env_oiko_api_key)

def get_meteo_at_x_freq(self, X: pd.Series | pd.DataFrame, param: list[str]):
check_is_fitted(self, attributes=["api_key_"])
x_freq = get_freq_delta_or_min_time_interval(X)
end = (
X.index[-1]
if X.index[-1] <= X.index[-1].replace(hour=23, minute=0)
else X.index[-1] + pd.Timedelta("1h")
)
df = get_oikolab_df(
lat=self.lat,
lon=self.lon,
start=X.index[0],
end=end,
api_key=self.api_key_,
param=param,
model=self.model,
)

df = df[param]
if x_freq < pd.Timedelta("1h"):
df = df.asfreq(x_freq).interpolate("linear")
elif x_freq > pd.Timedelta("1h"):
df = df.resample(x_freq).mean()
return df.loc[X.index, :]
18 changes: 12 additions & 6 deletions tide/plumbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,22 @@
import tide.processing as pc


def _get_pipe_from_proc_list(proc_list: list) -> Pipeline:
def _get_pipe_from_proc_list(proc_list: list, verbose: bool = False) -> Pipeline:
proc_units = [
getattr(pc, proc[0])(
*proc[1] if len(proc) > 1 and isinstance(proc[1], list) else (),
**proc[1] if len(proc) > 1 and isinstance(proc[1], dict) else {},
)
for proc in proc_list
]
return make_pipeline(*proc_units)
return make_pipeline(*proc_units, verbose=verbose)


def _get_column_wise_transformer(
proc_dict, data_columns: pd.Index | list[str], process_name: str = None
proc_dict,
data_columns: pd.Index | list[str],
process_name: str = None,
verbose: bool = False,
) -> ColumnTransformer | None:
col_trans_list = []
for req, proc_list in proc_dict.items():
Expand All @@ -46,7 +49,7 @@ def _get_column_wise_transformer(
col_trans_list.append(
(
f"{process_name}->{name}" if process_name is not None else name,
_get_pipe_from_proc_list(proc_list),
_get_pipe_from_proc_list(proc_list, verbose=verbose),
requested_col,
)
)
Expand All @@ -58,6 +61,7 @@ def _get_column_wise_transformer(
col_trans_list,
remainder="passthrough",
verbose_feature_names_out=False,
verbose=verbose,
).set_output(transform="pandas")


Expand All @@ -70,10 +74,12 @@ def get_pipeline_from_dict(
steps_list = []
for step, op_conf in pipe_dict.items():
if isinstance(op_conf, list):
operation = _get_pipe_from_proc_list(op_conf)
operation = _get_pipe_from_proc_list(op_conf, verbose=verbose)

elif isinstance(op_conf, dict):
operation = _get_column_wise_transformer(op_conf, data_columns, step)
operation = _get_column_wise_transformer(
op_conf, data_columns, step, verbose
)

else:
raise ValueError(f"{op_conf} is an invalid operation config")
Expand Down
Loading
Loading