Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
0f4a768
remove pvlib restriction
martin-springer Nov 4, 2025
bf74f61
run tests with pvlib 0.13.1
martin-springer Nov 4, 2025
b76a855
re-run TA NSRDB notebook
martin-springer Jan 30, 2026
2427d6d
update changelog
martin-springer Jan 30, 2026
2b68e14
restrict pandas to <3.0.0
martin-springer Jan 30, 2026
bf00799
add nbval workflow filterwarnings
martin-springer Jan 30, 2026
f7140c9
update changelog
martin-springer Jan 30, 2026
e9cbd50
try limiting numpy version for eager tests
martin-springer Jan 30, 2026
bd94a2e
update python versions in test matrix
martin-springer Jan 30, 2026
fdd1f3d
increase pandas requirement to 2.2.3 for python 3.13 compatibility
martin-springer Jan 30, 2026
e8b42f0
increase scipy version for py 3.13 compatibility
martin-springer Jan 30, 2026
6b8440a
update changelog
martin-springer Jan 30, 2026
97efaa4
increase h5py requirement for py 3.13 compatiblity
martin-springer Jan 30, 2026
11f9b4c
update changelog
martin-springer Jan 30, 2026
c2dc17e
increase scikit-learn version for py 3.13 compatibility
martin-springer Jan 30, 2026
bdd8230
update changelog
martin-springer Jan 30, 2026
febf3c3
update plotly for py 3.13 compatibility
martin-springer Jan 30, 2026
177ee05
update setuptools-scm for py 3.13 support
martin-springer Jan 30, 2026
747e2a7
update six to support py 3.13
martin-springer Jan 30, 2026
366f257
update statsmodels for py 3.13 support
martin-springer Jan 30, 2026
d573f79
update threadpoolctl for py 3.13 support
martin-springer Jan 30, 2026
0305a0b
update tomli for py 3.13 support
martin-springer Jan 30, 2026
18bd82d
update typing_extensions for py 3.13
martin-springer Jan 30, 2026
1ed42ba
update urllib3 for py 3.13
martin-springer Jan 30, 2026
f86813c
update xgboost for py 3.13
martin-springer Jan 30, 2026
4d0c25b
update fonttools for py 3.13
martin-springer Jan 30, 2026
7d22561
update idna for py 3.13 support
martin-springer Jan 30, 2026
3c05063
update joblib for py 3.13
martin-springer Jan 30, 2026
5456eb0
updated kiwisolver for py 3.13
martin-springer Jan 30, 2026
d0765c0
updated matplotlib for py 3.13
martin-springer Jan 30, 2026
0599a42
update packaging for py 3.13
martin-springer Jan 30, 2026
4be18fb
update patsy for py 3.13 support
martin-springer Jan 30, 2026
5458392
update Pillow for py 3.13
martin-springer Jan 30, 2026
b9b811d
update pyparsing for py 3.13
martin-springer Jan 30, 2026
b7f14b9
update pytz for py 3.13
martin-springer Jan 30, 2026
23aec01
update changelog
martin-springer Jan 30, 2026
8679ddc
try new scikit-learn version
martin-springer Jan 30, 2026
dc9f209
scikit-learn version 1.7.2 for py 3.13 and xgboost compatibility
martin-springer Jan 30, 2026
cc11b5f
update changelog
martin-springer Jan 30, 2026
b56bd28
bump urllib3 to satisfy dependabot
martin-springer Jan 30, 2026
8308d66
update changelog
martin-springer Jan 30, 2026
4f0ac4e
set pv power or energy for clip filter
martin-springer Feb 3, 2026
814329f
add sub-hourly check to TA before applying clip_filter
martin-springer Feb 3, 2026
1fad660
add test for sub-hourly check in TA clip_filter
martin-springer Feb 3, 2026
35a5e10
update changelog
martin-springer Feb 3, 2026
06a70f7
fix linting
martin-springer Feb 3, 2026
f516d4e
disable clip_filter in existing tests that use daily aggregated data
martin-springer Feb 3, 2026
7462f46
Merge branch 'development' into allow-clip-filter-for-energy
martin-springer Feb 4, 2026
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
10 changes: 10 additions & 0 deletions docs/sphinx/source/changelog/pending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ Requirements
* Updated pytz version in requirements.txt from 2024.1 to 2025.2 for python 3.13 compatibility.


Enhancements
------------
* Modified ``TrendAnalysis._filter()`` to allow ``clip_filter`` to use ``pv_energy``
when ``pv_power`` is not available. This enables clipping detection for energy-based
analyses with sub-hourly data.
* Added frequency validation for ``clip_filter`` in ``TrendAnalysis._filter()`` that
raises a ``ValueError`` if the time series has a median time step greater than 60
minutes, as clipping detection requires higher resolution data.


Warnings
--------
* Added filter to ignore deprecation warning related to IPyNbFile in setup.cfg.
20 changes: 11 additions & 9 deletions rdtools/analysis_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,15 +571,17 @@ def _call_clearsky_filter(filter_string):
f = filtering.tcell_filter(cell_temp, **self.filter_params["tcell_filter"])
filter_components["tcell_filter"] = f
if "clip_filter" in self.filter_params:
if self.pv_power is None:
raise ValueError(
"PV power (not energy) is required for the clipping filter. "
"Either omit the clipping filter, provide PV power at "
"instantiation, or explicitly assign TrendAnalysis.pv_power."
)
f = filtering.clip_filter(
self.pv_power, **self.filter_params["clip_filter"]
)
# Check that the time series frequency is 60 minutes or less
clip_data = self.pv_power if self.pv_power is not None else self.pv_energy
if clip_data is not None and len(clip_data) > 1:
median_freq = pd.Series(clip_data.index).diff().median()
if median_freq > pd.Timedelta(minutes=60):
raise ValueError(
f"clip_filter requires time series frequency of 60 minutes or less. "
f"Median time step is {median_freq}."
)

f = filtering.clip_filter(clip_data, **self.filter_params["clip_filter"])
filter_components["clip_filter"] = f
if "hour_angle_filter" in self.filter_params:
if not hasattr(self, "pvlib_location"):
Expand Down
31 changes: 28 additions & 3 deletions rdtools/test/analysis_chains_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,8 @@ def soiling_parameters(basic_parameters, soiling_normalized_daily, cs_input):
def soiling_analysis_sensor(soiling_parameters):
soiling_analysis = TrendAnalysis(**soiling_parameters)
np.random.seed(1977)
# Disable clip_filter for daily data (doesn't apply to aggregated data)
del soiling_analysis.filter_params["clip_filter"]
soiling_analysis.sensor_analysis(analyses=["srr_soiling"], srr_kwargs={"reps": 10})
return soiling_analysis

Expand All @@ -778,6 +780,8 @@ def soiling_analysis_clearsky(soiling_parameters, cs_input):
soiling_analysis.set_clearsky(**cs_input)
np.random.seed(1977)
soiling_analysis.filter_params["clearsky_filter"] = {"model": "csi"}
# Disable clip_filter for daily data (doesn't apply to aggregated data)
del soiling_analysis.filter_params["clip_filter"]
with pytest.warns(UserWarning, match="20% or more of the daily data"):
soiling_analysis.clearsky_analysis(
analyses=["srr_soiling"], srr_kwargs={"reps": 10}
Expand All @@ -791,15 +795,15 @@ def test_srr_soiling(soiling_analysis_sensor):
ci = srr_results["sratio_confidence_interval"]
renorm_factor = srr_results["calc_info"]["renormalizing_factor"]
print(f"soiling ci:{ci}")
assert 0.965 == pytest.approx(
assert 0.967 == pytest.approx(
sratio, abs=1e-3
), "Soiling ratio different from expected value in TrendAnalysis.srr_soiling"
assert [0.96, 0.97] == pytest.approx(
assert [0.966, 0.968] == pytest.approx(
ci, abs=1e-2
), "Soiling confidence interval different from expected value in TrendAnalysis.srr_soiling"
assert pytest.approx(
renorm_factor, abs=1e-3
) == 0.977, "Renormalization factor different from expected value in TrendAnalysis.srr_soiling"
) == 0.982, "Renormalization factor different from expected value in TrendAnalysis.srr_soiling"


def test_plot_degradation(sensor_analysis):
Expand Down Expand Up @@ -865,6 +869,27 @@ def test_errors(sensor_parameters, clearsky_analysis):
clearsky_analysis._clearsky_preprocess()


def test_clip_filter_frequency_error(basic_parameters):
# Test that clip_filter raises an error when data frequency > 60 minutes
times = pd.date_range("2019-01-01", "2022-01-01", freq="2h", tz="UTC")
pv = pd.Series(1.0, index=times)
poa_global = pd.Series(1000.0, index=times)
temperature_ambient = pd.Series(25.0, index=times)

rd_analysis = TrendAnalysis(
pv,
poa_global=poa_global,
temperature_ambient=temperature_ambient,
pv_input="energy",
**basic_parameters,
)
rd_analysis.filter_params = {"clip_filter": {}}
with pytest.raises(
ValueError, match="clip_filter requires time series frequency of 60 minutes"
):
rd_analysis.sensor_analysis(analyses=["yoy_degradation"])


@pytest.mark.parametrize(
"method_name",
[
Expand Down