rdtools 2.2.0b0__tar.gz → 2.2.0b2__tar.gz
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.
- {rdtools-2.2.0b0/rdtools.egg-info → rdtools-2.2.0b2}/PKG-INFO +1 -1
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/_version.py +3 -3
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/analysis_chains.py +99 -9
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/clearsky_temperature.py +3 -1
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/degradation.py +22 -23
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/filtering.py +4 -4
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/normalization.py +2 -3
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/plotting.py +2 -2
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/soiling.py +7 -6
- {rdtools-2.2.0b0 → rdtools-2.2.0b2/rdtools.egg-info}/PKG-INFO +1 -1
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools.egg-info/requires.txt +6 -6
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/setup.py +8 -6
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/LICENSE +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/MANIFEST.in +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/README.md +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/__init__.py +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/_deprecation.py +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/aggregation.py +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/availability.py +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/bootstrap.py +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/data/temperature.hdf5 +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools/models/xgboost_clipping_model.json +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools.egg-info/SOURCES.txt +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools.egg-info/dependency_links.txt +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools.egg-info/not-zip-safe +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/rdtools.egg-info/top_level.txt +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/setup.cfg +0 -0
- {rdtools-2.2.0b0 → rdtools-2.2.0b2}/versioneer.py +0 -0
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "2023-12-01T15:24:38-0700",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "2.2.0-beta.
|
|
14
|
+
"full-revisionid": "250e412bda8199491d8dc45673752374913b9c65",
|
|
15
|
+
"version": "2.2.0-beta.2"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -75,6 +75,14 @@ class TrendAnalysis():
|
|
|
75
75
|
filter_params defaults to empty dicts for each function in rdtools.filtering,
|
|
76
76
|
in which case those functions use default parameter values, `ad_hoc_filter`
|
|
77
77
|
defaults to None. See examples for more information.
|
|
78
|
+
filter_params_aggregated: dict
|
|
79
|
+
parameters to be passed to rdtools.filtering functions that specifically handle
|
|
80
|
+
aggregated data (dily filters, etc). Keys are the names of the rdtools.filtering functions.
|
|
81
|
+
Values are dicts of parameters to be passed to those functions. Also has a special key
|
|
82
|
+
`ad_hoc_filter`; this filter is a boolean mask joined with the rest of the filters.
|
|
83
|
+
filter_params_aggregated defaults to empty dicts for each function in rdtools.filtering,
|
|
84
|
+
in which case those functions use default parameter values, `ad_hoc_filter`
|
|
85
|
+
defaults to None. See examples for more information.
|
|
78
86
|
results : dict
|
|
79
87
|
Nested dict used to store the results of methods ending with `_analysis`
|
|
80
88
|
'''
|
|
@@ -133,6 +141,9 @@ class TrendAnalysis():
|
|
|
133
141
|
'csi_filter': {},
|
|
134
142
|
'ad_hoc_filter': None # use this to include an explict filter
|
|
135
143
|
}
|
|
144
|
+
self.filter_params_aggregated = {
|
|
145
|
+
'ad_hoc_filter': None
|
|
146
|
+
}
|
|
136
147
|
# remove tcell_filter from list if power_expected is passed in
|
|
137
148
|
if power_expected is not None and temperature_cell is None:
|
|
138
149
|
del self.filter_params['tcell_filter']
|
|
@@ -252,7 +263,8 @@ class TrendAnalysis():
|
|
|
252
263
|
clearsky_poa = clearsky_poa['poa_global']
|
|
253
264
|
|
|
254
265
|
if aggregate:
|
|
255
|
-
interval_id = pd.Series(
|
|
266
|
+
interval_id = pd.Series(
|
|
267
|
+
range(len(self.poa_global)), index=self.poa_global.index)
|
|
256
268
|
interval_id = interval_id.reindex(times, method='backfill')
|
|
257
269
|
clearsky_poa = clearsky_poa.groupby(interval_id).mean()
|
|
258
270
|
clearsky_poa.index = self.poa_global.index
|
|
@@ -383,7 +395,8 @@ class TrendAnalysis():
|
|
|
383
395
|
self.filter_params, which is a dict, the keys of which are names of
|
|
384
396
|
functions in rdtools.filtering, and the values of which are dicts
|
|
385
397
|
containing the associated parameters with which to run the filtering
|
|
386
|
-
functions.
|
|
398
|
+
functions. This private method is specifically for the original indexed
|
|
399
|
+
data. See examples for details on how to modify filter parameters.
|
|
387
400
|
|
|
388
401
|
Parameters
|
|
389
402
|
----------
|
|
@@ -405,7 +418,8 @@ class TrendAnalysis():
|
|
|
405
418
|
# at once. However, we add a default value of True, with the same index as
|
|
406
419
|
# energy_normalized, so that the output is still correct even when all
|
|
407
420
|
# filters have been disabled.
|
|
408
|
-
filter_components = {'default': pd.Series(
|
|
421
|
+
filter_components = {'default': pd.Series(
|
|
422
|
+
True, index=energy_normalized.index)}
|
|
409
423
|
|
|
410
424
|
if case == 'sensor':
|
|
411
425
|
poa = self.poa_global
|
|
@@ -455,14 +469,16 @@ class TrendAnalysis():
|
|
|
455
469
|
ad_hoc_filter = self.filter_params['ad_hoc_filter']
|
|
456
470
|
|
|
457
471
|
if ad_hoc_filter.isnull().any():
|
|
458
|
-
warnings.warn(
|
|
472
|
+
warnings.warn(
|
|
473
|
+
'ad_hoc_filter contains NaN values; setting to False (excluding)')
|
|
459
474
|
ad_hoc_filter = ad_hoc_filter.fillna(False)
|
|
460
475
|
|
|
461
476
|
if not filter_components.index.equals(ad_hoc_filter.index):
|
|
462
477
|
warnings.warn('ad_hoc_filter index does not match index of other filters; missing '
|
|
463
478
|
'values will be set to True (kept). Align the index with the index '
|
|
464
479
|
'of the filter_components attribute to prevent this warning')
|
|
465
|
-
ad_hoc_filter = ad_hoc_filter.reindex(
|
|
480
|
+
ad_hoc_filter = ad_hoc_filter.reindex(
|
|
481
|
+
filter_components.index).fillna(True)
|
|
466
482
|
|
|
467
483
|
filter_components['ad_hoc_filter'] = ad_hoc_filter
|
|
468
484
|
|
|
@@ -475,6 +491,63 @@ class TrendAnalysis():
|
|
|
475
491
|
self.clearsky_filter = bool_filter
|
|
476
492
|
self.clearsky_filter_components = filter_components
|
|
477
493
|
|
|
494
|
+
def _aggregated_filter(self, aggregated, case):
|
|
495
|
+
"""
|
|
496
|
+
Mirrors the _filter private function, but with aggregated filters applied.
|
|
497
|
+
These aggregated filters are based on those in rdtools.filtering. Uses
|
|
498
|
+
self.filter_params_aggregated, which is a dict, the keys of which are names of
|
|
499
|
+
functions in rdtools.filtering, and the values of which are dicts
|
|
500
|
+
containing the associated parameters with which to run the filtering
|
|
501
|
+
functions. See examples for details on how to modify filter parameters.
|
|
502
|
+
|
|
503
|
+
Parameters
|
|
504
|
+
----------
|
|
505
|
+
aggregated : pandas.Series
|
|
506
|
+
Time series of aggregated normalized AC energy
|
|
507
|
+
case : str
|
|
508
|
+
'sensor' or 'clearsky' which filtering protocol to apply. Affects
|
|
509
|
+
whether result is stored in self.sensor_filter_aggregated or
|
|
510
|
+
self.clearsky_filter_aggregated)
|
|
511
|
+
|
|
512
|
+
Returns
|
|
513
|
+
-------
|
|
514
|
+
None
|
|
515
|
+
"""
|
|
516
|
+
filter_components_aggregated = {'default':
|
|
517
|
+
pd.Series(True, index=aggregated.index)}
|
|
518
|
+
# Add daily aggregate filters as they come online here.
|
|
519
|
+
# Convert the dictionary into a dataframe (after running filters)
|
|
520
|
+
filter_components_aggregated = pd.DataFrame(
|
|
521
|
+
filter_components_aggregated).fillna(False)
|
|
522
|
+
# Run the ad-hoc filter from filter_params_aggregated, if available
|
|
523
|
+
if self.filter_params_aggregated.get('ad_hoc_filter', None) is not None:
|
|
524
|
+
ad_hoc_filter_aggregated = self.filter_params_aggregated['ad_hoc_filter']
|
|
525
|
+
|
|
526
|
+
if ad_hoc_filter_aggregated.isnull().any():
|
|
527
|
+
warnings.warn(
|
|
528
|
+
'aggregated ad_hoc_filter contains NaN values; setting to False (excluding)')
|
|
529
|
+
ad_hoc_filter_aggregated = ad_hoc_filter_aggregated.fillna(False)
|
|
530
|
+
|
|
531
|
+
if not filter_components_aggregated.index.equals(ad_hoc_filter_aggregated.index):
|
|
532
|
+
warnings.warn('Aggregated ad_hoc_filter index does not match index of other '
|
|
533
|
+
'filters; missing values will be set to True (kept). '
|
|
534
|
+
'Align the index with the index of the '
|
|
535
|
+
'filter_components_aggregated attribute to prevent this warning')
|
|
536
|
+
ad_hoc_filter_aggregated = ad_hoc_filter_aggregated.reindex(
|
|
537
|
+
filter_components_aggregated.index).fillna(True)
|
|
538
|
+
|
|
539
|
+
filter_components_aggregated['ad_hoc_filter'] = ad_hoc_filter_aggregated
|
|
540
|
+
|
|
541
|
+
bool_filter_aggregated = filter_components_aggregated.all(axis=1)
|
|
542
|
+
filter_components_aggregated = filter_components_aggregated.drop(
|
|
543
|
+
columns=['default'])
|
|
544
|
+
if case == 'sensor':
|
|
545
|
+
self.sensor_filter_aggregated = bool_filter_aggregated
|
|
546
|
+
self.sensor_filter_components_aggregated = filter_components_aggregated
|
|
547
|
+
elif case == 'clearsky':
|
|
548
|
+
self.clearsky_filter_aggregated = bool_filter_aggregated
|
|
549
|
+
self.clearsky_filter_components_aggregated = filter_components_aggregated
|
|
550
|
+
|
|
478
551
|
def _filter_check(self, post_filter):
|
|
479
552
|
'''
|
|
480
553
|
post-filter check for requisite 730 days of data
|
|
@@ -621,8 +694,16 @@ class TrendAnalysis():
|
|
|
621
694
|
self._filter(energy_normalized, 'sensor')
|
|
622
695
|
aggregated, aggregated_insolation = self._aggregate(
|
|
623
696
|
energy_normalized[self.sensor_filter], insolation[self.sensor_filter])
|
|
624
|
-
|
|
625
|
-
self.
|
|
697
|
+
# Run daily filters on aggregated data
|
|
698
|
+
self._aggregated_filter(aggregated, 'sensor')
|
|
699
|
+
# Apply filter to aggregated data and store
|
|
700
|
+
self.sensor_aggregated_performance = aggregated[self.sensor_filter_aggregated]
|
|
701
|
+
self.sensor_aggregated_insolation = aggregated_insolation[self.sensor_filter_aggregated]
|
|
702
|
+
# Reindex the data after the fact, so it's on the aggregated interval
|
|
703
|
+
self.sensor_aggregated_performance = self.sensor_aggregated_performance.asfreq(
|
|
704
|
+
self.aggregation_freq)
|
|
705
|
+
self.sensor_aggregated_insolation = self.sensor_aggregated_insolation.asfreq(
|
|
706
|
+
self.aggregation_freq)
|
|
626
707
|
|
|
627
708
|
def _clearsky_preprocess(self):
|
|
628
709
|
'''
|
|
@@ -651,8 +732,17 @@ class TrendAnalysis():
|
|
|
651
732
|
self._filter(cs_normalized, 'clearsky')
|
|
652
733
|
cs_aggregated, cs_aggregated_insolation = self._aggregate(
|
|
653
734
|
cs_normalized[self.clearsky_filter], cs_insolation[self.clearsky_filter])
|
|
654
|
-
|
|
655
|
-
self.
|
|
735
|
+
# Run daily filters on aggregated data
|
|
736
|
+
self._aggregated_filter(cs_aggregated, 'clearsky')
|
|
737
|
+
# Apply daily filter to aggregated data and store
|
|
738
|
+
self.clearsky_aggregated_performance = cs_aggregated[self.clearsky_filter_aggregated]
|
|
739
|
+
self.clearsky_aggregated_insolation = \
|
|
740
|
+
cs_aggregated_insolation[self.clearsky_filter_aggregated]
|
|
741
|
+
# Reindex the data after the fact, so it's on the aggregated interval
|
|
742
|
+
self.clearsky_aggregated_performance = self.clearsky_aggregated_performance.asfreq(
|
|
743
|
+
self.aggregation_freq)
|
|
744
|
+
self.clearsky_aggregated_insolation = self.clearsky_aggregated_insolation.asfreq(
|
|
745
|
+
self.aggregation_freq)
|
|
656
746
|
|
|
657
747
|
def sensor_analysis(self, analyses=['yoy_degradation'], yoy_kwargs={}, srr_kwargs={}):
|
|
658
748
|
'''
|
|
@@ -57,7 +57,9 @@ def get_clearsky_tamb(times, latitude, longitude, window_size=40,
|
|
|
57
57
|
freq_actual = times.freq
|
|
58
58
|
|
|
59
59
|
dt_daily = pd.date_range(times.date[0] - buffer, times.date[-1] + buffer,
|
|
60
|
-
freq='D'
|
|
60
|
+
freq='D')
|
|
61
|
+
dt_daily = dt_daily.tz_localize(times.tz, ambiguous='infer',
|
|
62
|
+
nonexistent='shift_forward')
|
|
61
63
|
|
|
62
64
|
f = h5py.File(filepath, "r")
|
|
63
65
|
|
|
@@ -38,7 +38,7 @@ def degradation_ols(energy_normalized, confidence_level=68.2):
|
|
|
38
38
|
|
|
39
39
|
# calculate a years column as x value for regression, ignoring leap years
|
|
40
40
|
day_diffs = (df.index - df.index[0])
|
|
41
|
-
df['days'] = day_diffs.
|
|
41
|
+
df['days'] = day_diffs / pd.Timedelta('1d')
|
|
42
42
|
df['years'] = df.days / 365.0
|
|
43
43
|
|
|
44
44
|
# add intercept-constant to the exogeneous variable
|
|
@@ -123,22 +123,14 @@ def degradation_classical_decomposition(energy_normalized,
|
|
|
123
123
|
|
|
124
124
|
# calculate a years column as x value for regression, ignoring leap years
|
|
125
125
|
day_diffs = (df.index - df.index[0])
|
|
126
|
-
df['days'] = day_diffs.
|
|
126
|
+
df['days'] = day_diffs / pd.Timedelta('1d')
|
|
127
127
|
df['years'] = df.days / 365.0
|
|
128
128
|
|
|
129
129
|
# Compute yearly rolling mean to isolate trend component using
|
|
130
130
|
# moving average
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if row.years - 0.5 >= min(df.years) and \
|
|
135
|
-
row.years + 0.5 <= max(df.years):
|
|
136
|
-
roll = df[(df.years <= row.years + 0.5) &
|
|
137
|
-
(df.years >= row.years - 0.5)]
|
|
138
|
-
energy_ma.append(roll.energy_normalized.mean())
|
|
139
|
-
else:
|
|
140
|
-
energy_ma.append(np.nan)
|
|
141
|
-
|
|
131
|
+
energy_ma = df['energy_normalized'].rolling('365d', center=True).mean()
|
|
132
|
+
has_full_year = (df['years'] >= df['years'][0] + 0.5) & (df['years'] <= df['years'][-1] - 0.5)
|
|
133
|
+
energy_ma[~has_full_year] = np.nan
|
|
142
134
|
df['energy_ma'] = energy_ma
|
|
143
135
|
|
|
144
136
|
# add intercept-constant to the exogeneous variable
|
|
@@ -244,11 +236,19 @@ def degradation_year_on_year(energy_normalized, recenter=True,
|
|
|
244
236
|
raise ValueError('energy_normalized must not be '
|
|
245
237
|
'more frequent than daily')
|
|
246
238
|
|
|
247
|
-
# Detect less than 2 years of data
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
239
|
+
# Detect less than 2 years of data. This is complicated by two things:
|
|
240
|
+
# - leap days muddle the precise meaning of "two years of data".
|
|
241
|
+
# - can't just check the number of days between the first and last
|
|
242
|
+
# index values, since non-daily (e.g. weekly) inputs span
|
|
243
|
+
# a longer period than their index values directly indicate.
|
|
244
|
+
# See the unit tests for several motivating cases.
|
|
245
|
+
if energy_normalized.index.inferred_freq is not None:
|
|
246
|
+
step = pd.tseries.frequencies.to_offset(energy_normalized.index.inferred_freq)
|
|
247
|
+
else:
|
|
248
|
+
step = energy_normalized.index.to_series().diff().median()
|
|
249
|
+
|
|
250
|
+
if energy_normalized.index[-1] < energy_normalized.index[0] + pd.DateOffset(years=2) - step:
|
|
251
|
+
raise ValueError('must provide at least two years of normalized energy')
|
|
252
252
|
|
|
253
253
|
# If circular block bootstrapping...
|
|
254
254
|
if uncertainty_method == 'circular_block':
|
|
@@ -282,11 +282,12 @@ def degradation_year_on_year(energy_normalized, recenter=True,
|
|
|
282
282
|
tolerance=pd.Timedelta('8D')
|
|
283
283
|
)
|
|
284
284
|
|
|
285
|
-
df['time_diff_years'] = (df.dt - df.dt_right).
|
|
285
|
+
df['time_diff_years'] = (df.dt - df.dt_right) / pd.Timedelta('365d')
|
|
286
286
|
df['yoy'] = 100.0 * (df.energy - df.energy_right) / (df.time_diff_years)
|
|
287
287
|
df.index = df.dt
|
|
288
288
|
|
|
289
289
|
yoy_result = df.yoy.dropna()
|
|
290
|
+
|
|
290
291
|
df_right = df.set_index(df.dt_right).drop_duplicates('dt_right')
|
|
291
292
|
df['usage_of_points'] = df.yoy.notnull().astype(int).add(
|
|
292
293
|
df_right.yoy.notnull().astype(int), fill_value=0)
|
|
@@ -386,10 +387,8 @@ def _mk_test(x, alpha=0.05):
|
|
|
386
387
|
n = len(x)
|
|
387
388
|
|
|
388
389
|
# calculate S
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
for j in range(k + 1, n):
|
|
392
|
-
s += np.sign(x[j] - x[k])
|
|
390
|
+
x = np.array(x)
|
|
391
|
+
s = np.sum(np.triu(np.sign(-np.subtract.outer(x, x)), 1))
|
|
393
392
|
|
|
394
393
|
# calculate the unique data
|
|
395
394
|
unique_x = np.unique(x)
|
|
@@ -424,8 +424,9 @@ def logic_clip_filter(power_ac,
|
|
|
424
424
|
# series sampling frequency is less than 95% consistent.
|
|
425
425
|
_check_data_sampling_frequency(power_ac)
|
|
426
426
|
# Get the sampling frequency of the time series
|
|
427
|
-
time_series_sampling_frequency =
|
|
428
|
-
.
|
|
427
|
+
time_series_sampling_frequency = (
|
|
428
|
+
power_ac.index.to_series().diff() / pd.Timedelta('60s')
|
|
429
|
+
).mode()[0]
|
|
429
430
|
# Make copies of the original inputs for the cases that the data is
|
|
430
431
|
# changes for clipping evaluation
|
|
431
432
|
original_time_series_sampling_frequency = time_series_sampling_frequency
|
|
@@ -651,8 +652,7 @@ def xgboost_clip_filter(power_ac,
|
|
|
651
652
|
# series sampling frequency is less than 95% consistent.
|
|
652
653
|
_check_data_sampling_frequency(power_ac)
|
|
653
654
|
# Get the most common sampling frequency
|
|
654
|
-
sampling_frequency = int(power_ac.index.to_series().diff()
|
|
655
|
-
.astype('timedelta64[m]').mode()[0])
|
|
655
|
+
sampling_frequency = int((power_ac.index.to_series().diff() / pd.Timedelta('60s')).mode()[0])
|
|
656
656
|
freq_string = str(sampling_frequency) + "T"
|
|
657
657
|
# Min-max normalize
|
|
658
658
|
# Resample the series based on the most common sampling frequency
|
|
@@ -380,7 +380,7 @@ def irradiance_rescale(irrad, irrad_sim, max_iterations=100,
|
|
|
380
380
|
'''
|
|
381
381
|
|
|
382
382
|
if method == 'iterative':
|
|
383
|
-
def _rmse(fact):
|
|
383
|
+
def _rmse(fact, filt):
|
|
384
384
|
"""
|
|
385
385
|
Calculates RMSE with a given rescale fact(or) according to global
|
|
386
386
|
filt(er)
|
|
@@ -392,10 +392,9 @@ def irradiance_rescale(irrad, irrad_sim, max_iterations=100,
|
|
|
392
392
|
|
|
393
393
|
def _single_rescale(irrad, irrad_sim, guess):
|
|
394
394
|
"Optimizes rescale factor once"
|
|
395
|
-
global filt
|
|
396
395
|
csi = irrad / (guess * irrad_sim) # clear sky index
|
|
397
396
|
filt = (csi >= 0.8) & (csi <= 1.2) & (irrad > 200)
|
|
398
|
-
min_result = minimize(_rmse, guess, method='Nelder-Mead')
|
|
397
|
+
min_result = minimize(_rmse, guess, (filt), method='Nelder-Mead')
|
|
399
398
|
|
|
400
399
|
factor = min_result['x'][0]
|
|
401
400
|
return factor
|
|
@@ -238,8 +238,8 @@ def soiling_interval_plot(soiling_info, normalized_yield, point_alpha=0.5,
|
|
|
238
238
|
sratio = soiling_info['soiling_ratio_perfect_clean']
|
|
239
239
|
fig, ax = plt.subplots()
|
|
240
240
|
renormalized = normalized_yield / soiling_info['renormalizing_factor']
|
|
241
|
-
ax.plot(renormalized.index, renormalized, 'o')
|
|
242
|
-
ax.plot(sratio.index, sratio, 'o')
|
|
241
|
+
ax.plot(renormalized.index, renormalized, 'o', c=point_color, alpha=point_alpha)
|
|
242
|
+
ax.plot(sratio.index, sratio, 'o', c=profile_color, alpha=profile_alpha)
|
|
243
243
|
ax.set_ylim(ymin, ymax)
|
|
244
244
|
ax.set_ylabel('Renormalized energy')
|
|
245
245
|
|
|
@@ -1762,11 +1762,11 @@ class CODSAnalysis():
|
|
|
1762
1762
|
self.soiling_loss = [0, 0, (1 - result_df.soiling_ratio).mean()]
|
|
1763
1763
|
self.small_soiling_signal = True
|
|
1764
1764
|
self.errors = (
|
|
1765
|
-
'Soiling signal is small relative to the noise.'
|
|
1766
|
-
'Iterative decomposition not possible
|
|
1767
|
-
'Degradation found by RdTools YoY')
|
|
1768
|
-
|
|
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
|
|
1770
1770
|
self.small_soiling_signal = False
|
|
1771
1771
|
|
|
1772
1772
|
# Aggregate all bootstrap samples
|
|
@@ -2507,7 +2507,8 @@ def _make_seasonal_samples(list_of_SCs, sample_nr=10, min_multiplier=0.5,
|
|
|
2507
2507
|
''' Generate seasonal samples by perturbing the amplitude and the phase of
|
|
2508
2508
|
a seasonal components found with the fitted CODS model '''
|
|
2509
2509
|
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)
|
|
2511
2512
|
# From each fitted signal, we will generate new seaonal components
|
|
2512
2513
|
for i, signal in enumerate(list_of_SCs):
|
|
2513
2514
|
# Remove beginning and end of signal
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
matplotlib>=3.0.0
|
|
2
|
-
numpy>=1.
|
|
3
|
-
pandas
|
|
2
|
+
numpy>=1.17.3
|
|
3
|
+
pandas<2.1,>=1.3.0
|
|
4
4
|
statsmodels>=0.11.1
|
|
5
|
-
scipy>=1.
|
|
5
|
+
scipy>=1.2.0
|
|
6
6
|
h5py>=2.8.0
|
|
7
7
|
plotly>=4.0.0
|
|
8
8
|
xgboost>=1.3.3
|
|
9
|
-
pvlib<0.
|
|
9
|
+
pvlib<0.11.0,>=0.7.0
|
|
10
10
|
scikit-learn>=0.22.0
|
|
11
11
|
arch>=4.11
|
|
12
12
|
filterpy>=1.4.2
|
|
@@ -17,7 +17,7 @@ flake8
|
|
|
17
17
|
ipython
|
|
18
18
|
nbsphinx-link==1.3.0
|
|
19
19
|
nbsphinx==0.8.8
|
|
20
|
-
nbval
|
|
20
|
+
nbval==0.9.6
|
|
21
21
|
pytest>=3.6.3
|
|
22
22
|
pytest-mock
|
|
23
23
|
sphinx-gallery==0.8.1
|
|
@@ -36,5 +36,5 @@ sphinx-gallery==0.8.1
|
|
|
36
36
|
pytest>=3.6.3
|
|
37
37
|
coverage
|
|
38
38
|
flake8
|
|
39
|
-
nbval
|
|
39
|
+
nbval==0.9.6
|
|
40
40
|
pytest-mock
|
|
@@ -35,21 +35,23 @@ TESTS_REQUIRE = [
|
|
|
35
35
|
'pytest >= 3.6.3',
|
|
36
36
|
'coverage',
|
|
37
37
|
'flake8',
|
|
38
|
-
'nbval',
|
|
38
|
+
'nbval==0.9.6', # https://github.com/computationalmodelling/nbval/issues/194
|
|
39
39
|
'pytest-mock',
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
INSTALL_REQUIRES = [
|
|
43
43
|
'matplotlib >= 3.0.0',
|
|
44
|
-
'numpy >= 1.
|
|
45
|
-
#
|
|
46
|
-
|
|
44
|
+
'numpy >= 1.17.3',
|
|
45
|
+
# pandas restricted to <2.1 until
|
|
46
|
+
# https://github.com/pandas-dev/pandas/issues/55794
|
|
47
|
+
# is resolved
|
|
48
|
+
'pandas >= 1.3.0, <2.1',
|
|
47
49
|
'statsmodels >= 0.11.1',
|
|
48
|
-
'scipy >= 1.
|
|
50
|
+
'scipy >= 1.2.0',
|
|
49
51
|
'h5py >= 2.8.0',
|
|
50
52
|
'plotly>=4.0.0',
|
|
51
53
|
'xgboost >= 1.3.3',
|
|
52
|
-
'pvlib >= 0.7.0, <0.
|
|
54
|
+
'pvlib >= 0.7.0, <0.11.0',
|
|
53
55
|
'scikit-learn >= 0.22.0',
|
|
54
56
|
'arch >= 4.11',
|
|
55
57
|
'filterpy >= 1.4.2'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|