Skip to content

Commit 250e412

Browse files
return results instead of none when CODS soiling signal is small (#400)
* return results instead of none * change dep. less_precise to rtol in assert * flake8 fix missing whitespace * flake8 fix missing whitespace * specify dtype seasonal_samples * Re run notebook * add CODS small signal test * update arch version * update packages re-run notebook * update change log * Add arch bump to the changelog --------- Co-authored-by: Michael Deceglie <[email protected]> Co-authored-by: Michael Deceglie <[email protected]>
1 parent 147962b commit 250e412

9 files changed

+4111
-4153
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,6 @@ rdtools.egg-info*
3535
.\#*
3636

3737
*.pickle
38+
39+
# ignore vscode settings
40+
.vscode/

docs/TrendAnalysis_example_pvdaq4.ipynb

+4,030-4,123
Large diffs are not rendered by default.

docs/sphinx/source/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
RdTools Change Log
22
==================
3+
.. include:: changelog/v2.2.0-beta.2.rst
34
.. include:: changelog/v2.2.0-beta.1.rst
45
.. include:: changelog/v2.1.8.rst
56
.. include:: changelog/v2.1.7.rst
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
********************************
2+
v2.2.0-beta.2 (December 1, 2023)
3+
********************************
4+
5+
Enhancements
6+
------------
7+
* Return CODS results without bootstrapping when soiling signal
8+
is small but raise warning ``soiling.py`` (:issue:`367` :pull:`400`)
9+
10+
Bug fixes
11+
---------
12+
* Fix flake8 missing whitespaces ``bootstrap_test.py``, ``soiling_cods_test.py`` (:pull:`400`)
13+
* Specify dtype for seasonal samples ``soiling.py`` (:pull:`400`)
14+
* Update deprecated `check_less_precise` to `rtol` ``soiling_cods_test.py`` (:pull:`400`)
15+
16+
Requirements
17+
------------
18+
* Bump arch to 5.6.0 in ``requirements.txt``
19+
20+
Contributors
21+
------------
22+
* Martin Springer (:ghuser:`martin-springer`)
23+
* Michael Deceglie (:ghuser:`mdeceglie`)

rdtools/soiling.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -1762,11 +1762,11 @@ def run_bootstrap(self,
17621762
self.soiling_loss = [0, 0, (1 - result_df.soiling_ratio).mean()]
17631763
self.small_soiling_signal = True
17641764
self.errors = (
1765-
'Soiling signal is small relative to the noise.'
1766-
'Iterative decomposition not possible.\n'
1767-
'Degradation found by RdTools YoY')
1768-
print(self.errors)
1769-
return
1765+
'Soiling signal is small relative to the noise. '
1766+
'Iterative decomposition not possible. '
1767+
'Degradation found by RdTools YoY.')
1768+
warnings.warn(self.errors)
1769+
return self.result_df, self.degradation, self.soiling_loss
17701770
self.small_soiling_signal = False
17711771

17721772
# Aggregate all bootstrap samples
@@ -2507,7 +2507,8 @@ def _make_seasonal_samples(list_of_SCs, sample_nr=10, min_multiplier=0.5,
25072507
''' Generate seasonal samples by perturbing the amplitude and the phase of
25082508
a seasonal components found with the fitted CODS model '''
25092509
samples = pd.DataFrame(index=list_of_SCs[0].index,
2510-
columns=range(int(sample_nr*len(list_of_SCs))))
2510+
columns=range(int(sample_nr*len(list_of_SCs))),
2511+
dtype=float)
25112512
# From each fitted signal, we will generate new seaonal components
25122513
for i, signal in enumerate(list_of_SCs):
25132514
# Remove beginning and end of signal

rdtools/test/bootstrap_test.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'''Bootstrap module tests.'''
22

3-
from rdtools.bootstrap import _construct_confidence_intervals,\
3+
from rdtools.bootstrap import _construct_confidence_intervals, \
44
_make_time_series_bootstrap_samples
55
from rdtools.degradation import degradation_year_on_year
66

rdtools/test/conftest.py

+10
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ def cods_normalized_daily(cods_normalized_daily_wo_noise):
126126
return cods_normalized_daily
127127

128128

129+
@pytest.fixture()
130+
def cods_normalized_daily_small_soiling(cods_normalized_daily_wo_noise):
131+
N = len(cods_normalized_daily_wo_noise)
132+
np.random.seed(1977)
133+
noise = 1 + 0.02 * (np.random.rand(N) - 0.5)
134+
cods_normalized_daily_small_soiling = cods_normalized_daily_wo_noise.apply(
135+
lambda row: 1-(1-row)*0.1) * noise
136+
return cods_normalized_daily_small_soiling
137+
138+
129139
# %% Availability fixtures
130140

131141
ENERGY_PARAMETER_SPACE = list(itertools.product(

rdtools/test/soiling_cods_test.py

+35-22
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ def test_iterative_signal_decomposition(cods_normalized_daily):
1212
cods = soiling.CODSAnalysis(cods_normalized_daily)
1313
df_out, results_dict = \
1414
cods.iterative_signal_decomposition()
15-
assert 0.080641 == pytest.approx(results_dict['degradation'], abs=1e-6),\
15+
assert 0.080641 == pytest.approx(results_dict['degradation'], abs=1e-6), \
1616
'Degradation rate different from expected value'
17-
assert 3.305136 == pytest.approx(results_dict['soiling_loss'], abs=1e-6),\
17+
assert 3.305136 == pytest.approx(results_dict['soiling_loss'], abs=1e-6), \
1818
'Soiling loss different from expected value'
19-
assert 0.999359 == pytest.approx(results_dict['residual_shift'], abs=1e-6),\
19+
assert 0.999359 == pytest.approx(results_dict['residual_shift'], abs=1e-6), \
2020
'Residual shift different from expected value'
21-
assert 0.008144 == pytest.approx(results_dict['RMSE'], abs=1e-6),\
21+
assert 0.008144 == pytest.approx(results_dict['RMSE'], abs=1e-6), \
2222
'RMSE different from expected value'
2323
assert not results_dict['small_soiling_signal'], \
2424
'Small soiling signal assertion different from expected value'
25-
assert 7.019626e-11 == pytest.approx(results_dict['adf_res'][1], abs=1e-6),\
25+
assert 7.019626e-11 == pytest.approx(results_dict['adf_res'][1], abs=1e-6), \
2626
'p-value of Augmented Dickey-Fuller test different from expected value'
2727

2828
# Check result dataframe
@@ -31,10 +31,10 @@ def test_iterative_signal_decomposition(cods_normalized_daily):
3131
'seasonal_component', 'degradation_trend', 'total_model', 'residuals']
3232
actual_columns = df_out.columns.values
3333
for x in actual_columns:
34-
assert x in expected_columns,\
34+
assert x in expected_columns, \
3535
"'{}' not an expected column in result_df]".format(x)
3636
for x in expected_columns:
37-
assert x in actual_columns,\
37+
assert x in actual_columns, \
3838
"'{}' was expected as a column, but not in result_df".format(x)
3939
assert isinstance(df_out, pd.DataFrame), 'result_df not a dataframe'
4040
expected_means = pd.Series({'soiling_ratio': 0.9669486267086722,
@@ -48,7 +48,7 @@ def test_iterative_signal_decomposition(cods_normalized_daily):
4848
['soiling_ratio', 'soiling_rates', 'cleaning_events',
4949
'seasonal_component', 'degradation_trend', 'total_model', 'residuals']]
5050
pd.testing.assert_series_equal(expected_means, df_out.mean(),
51-
check_exact=False, check_less_precise=True)
51+
check_exact=False, rtol=1e-3)
5252

5353

5454
def test_iterative_signal_decomposition_with_nan_interval(cods_normalized_daily):
@@ -59,17 +59,17 @@ def test_iterative_signal_decomposition_with_nan_interval(cods_normalized_daily)
5959
cods = soiling.CODSAnalysis(normalized_corrupt)
6060
df_out, results_dict = \
6161
cods.iterative_signal_decomposition()
62-
assert -0.004968 == pytest.approx(results_dict['degradation'], abs=1e-5),\
62+
assert -0.004968 == pytest.approx(results_dict['degradation'], abs=1e-5), \
6363
'Degradation rate different from expected value'
64-
assert 3.232171 == pytest.approx(results_dict['soiling_loss'], abs=1e-5),\
64+
assert 3.232171 == pytest.approx(results_dict['soiling_loss'], abs=1e-5), \
6565
'Soiling loss different from expected value'
66-
assert 1.000108 == pytest.approx(results_dict['residual_shift'], abs=1e-5),\
66+
assert 1.000108 == pytest.approx(results_dict['residual_shift'], abs=1e-5), \
6767
'Residual shift different from expected value'
68-
assert 0.008184 == pytest.approx(results_dict['RMSE'], abs=1e-5),\
68+
assert 0.008184 == pytest.approx(results_dict['RMSE'], abs=1e-5), \
6969
'RMSE different from expected value'
7070
assert not results_dict['small_soiling_signal'], \
7171
'Small soiling signal assertion different from expected value'
72-
assert 1.230754e-8 == pytest.approx(results_dict['adf_res'][1], abs=1e-6),\
72+
assert 1.230754e-8 == pytest.approx(results_dict['adf_res'][1], abs=1e-6), \
7373
'p-value of Augmented Dickey-Fuller test different from expected value'
7474

7575
# Check result dataframe
@@ -85,21 +85,21 @@ def test_iterative_signal_decomposition_with_nan_interval(cods_normalized_daily)
8585
['soiling_ratio', 'soiling_rates', 'cleaning_events',
8686
'seasonal_component', 'degradation_trend', 'total_model', 'residuals']]
8787
pd.testing.assert_series_equal(expected_means, df_out.mean(),
88-
check_exact=False, check_less_precise=True)
88+
check_exact=False, rtol=1e-3)
8989

9090

9191
def test_soiling_cods(cods_normalized_daily):
9292
''' Test the CODS algorithm with fixed test case and 16 repetitions'''
9393
reps = 16
9494
np.random.seed(1977)
9595
sr, sr_ci, deg, deg_ci, result_df = soiling.soiling_cods(cods_normalized_daily, reps=reps)
96-
assert 0.962207 == pytest.approx(sr, abs=0.5),\
96+
assert 0.962207 == pytest.approx(sr, abs=0.5), \
9797
'Soiling ratio different from expected value'
98-
assert np.array([0.96662419, 0.95692131]) == pytest.approx(sr_ci, abs=0.5),\
98+
assert np.array([0.96662419, 0.95692131]) == pytest.approx(sr_ci, abs=0.5), \
9999
'Confidence interval of SR different from expected value'
100-
assert 0.09 == pytest.approx(deg, abs=0.5),\
100+
assert 0.09 == pytest.approx(deg, abs=0.5), \
101101
'Degradation rate different from expected value'
102-
assert np.array([-0.17143952, 0.39313724]) == pytest.approx(deg_ci, abs=0.5),\
102+
assert np.array([-0.17143952, 0.39313724]) == pytest.approx(deg_ci, abs=0.5), \
103103
'Confidence interval of degradation rate different from expected value'
104104

105105
# Check result dataframe
@@ -111,13 +111,26 @@ def test_soiling_cods(cods_normalized_daily):
111111
'model_high']
112112
actual_summary_columns = result_df.columns.values
113113
for x in actual_summary_columns:
114-
assert x in expected_summary_columns,\
114+
assert x in expected_summary_columns, \
115115
"'{}' not an expected column in result_df]".format(x)
116116
for x in expected_summary_columns:
117-
assert x in actual_summary_columns,\
117+
assert x in actual_summary_columns, \
118118
"'{}' was expected as a column, but not in result_df".format(x)
119119

120120

121+
def test_soiling_cods_small_signal(cods_normalized_daily_small_soiling):
122+
''' Test the CODS algorithm with small soiling signal'''
123+
reps = 16
124+
np.random.seed(1977)
125+
warn_small_signal = (
126+
'Soiling signal is small relative to the noise. '
127+
'Iterative decomposition not possible. '
128+
'Degradation found by RdTools YoY.')
129+
130+
with pytest.warns(UserWarning, match=warn_small_signal):
131+
soiling.soiling_cods(cods_normalized_daily_small_soiling, reps=reps)
132+
133+
121134
def test_Kalman_filter_for_SR(cods_normalized_daily):
122135
'''Test the Kalman Filter method in CODS'''
123136
cods = soiling.CODSAnalysis(cods_normalized_daily)
@@ -131,10 +144,10 @@ def test_Kalman_filter_for_SR(cods_normalized_daily):
131144
'soiling_rates', 'cleaning_events', 'days_since_ce']
132145
actual_columns = dfk.columns.values
133146
for x in actual_columns:
134-
assert x in expected_columns,\
147+
assert x in expected_columns, \
135148
"'{}' not an expected column in Kalman Filter results]".format(x)
136149
for x in expected_columns:
137-
assert x in actual_columns,\
150+
assert x in actual_columns, \
138151
"'{}' was expected as a column, but not in Kalman Filter results".format(x)
139152
assert Ps.shape == (732, 2, 2), "Shape of array of covariance matrices (Ps) not as expected"
140153

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pvlib==0.9.0
1818
pyparsing==2.4.7
1919
python-dateutil==2.8.1
2020
pytz==2019.3
21-
arch==4.11
21+
arch==5.6.0
2222
filterpy==1.4.5
2323
requests==2.31.0
2424
retrying==1.3.3

0 commit comments

Comments
 (0)