captest 0.13.3rc1__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.
- captest/__init__.py +21 -0
- captest/_version.py +34 -0
- captest/capdata.py +3380 -0
- captest/columngroups.py +163 -0
- captest/io.py +564 -0
- captest/plotting.py +505 -0
- captest/prtest.py +416 -0
- captest/util.py +134 -0
- captest-0.13.3rc1.dist-info/METADATA +173 -0
- captest-0.13.3rc1.dist-info/RECORD +13 -0
- captest-0.13.3rc1.dist-info/WHEEL +5 -0
- captest-0.13.3rc1.dist-info/licenses/LICENSE.txt +21 -0
- captest-0.13.3rc1.dist-info/top_level.txt +1 -0
captest/prtest.py
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import param
|
|
6
|
+
|
|
7
|
+
from captest import capdata
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
emp_heat_coeff = {
|
|
11
|
+
"open_rack": {
|
|
12
|
+
"glass_cell_glass": {"a": -3.47, "b": -0.0594, "del_tcnd": 3},
|
|
13
|
+
"glass_cell_poly": {"a": -3.56, "b": -0.0750, "del_tcnd": 3},
|
|
14
|
+
"poly_tf_steel": {"a": -3.58, "b": -0.1130, "del_tcnd": 3},
|
|
15
|
+
},
|
|
16
|
+
"close_roof_mount": {"glass_cell_glass": {"a": -2.98, "b": -0.0471, "del_tcnd": 1}},
|
|
17
|
+
"insulated_back": {"glass_cell_poly": {"a": -2.81, "b": -0.0455, "del_tcnd": 0}},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_common_timestep(data, units="m", string_output=True):
|
|
22
|
+
"""
|
|
23
|
+
Get the most commonly occuring timestep of data as frequency string.
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
data : Series or DataFrame
|
|
27
|
+
Data with a DateTimeIndex.
|
|
28
|
+
units : str, default 'm'
|
|
29
|
+
String representing date/time unit, such as (D)ay, (M)onth, (Y)ear,
|
|
30
|
+
(h)ours, (m)inutes, or (s)econds.
|
|
31
|
+
string_output : bool, default True
|
|
32
|
+
Set to False to return a numeric value.
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
str
|
|
36
|
+
frequency string
|
|
37
|
+
"""
|
|
38
|
+
units_abbrev = {
|
|
39
|
+
"D": "Day",
|
|
40
|
+
"M": "Months",
|
|
41
|
+
"Y": "Year",
|
|
42
|
+
"h": "hours",
|
|
43
|
+
"m": "minutes",
|
|
44
|
+
"s": "seconds",
|
|
45
|
+
}
|
|
46
|
+
common_timestep = data.index.to_series().diff().mode().values[0]
|
|
47
|
+
common_timestep_tdelta = common_timestep.astype("timedelta64[m]")
|
|
48
|
+
freq = common_timestep_tdelta / np.timedelta64(1, units)
|
|
49
|
+
if string_output:
|
|
50
|
+
return str(freq) + " " + units_abbrev[units]
|
|
51
|
+
else:
|
|
52
|
+
return freq
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def temp_correct_power(power, power_temp_coeff, cell_temp, base_temp=25):
|
|
56
|
+
"""Apply temperature correction to PV power.
|
|
57
|
+
|
|
58
|
+
Divides `power` by the temperature correction, so low power values that
|
|
59
|
+
are above `base_temp` will be increased and high power values that are
|
|
60
|
+
below the `base_temp` will be decreased.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
power : numeric or Series
|
|
65
|
+
PV power (in watts) to correct to the `base_temp`.
|
|
66
|
+
power_temp_coeff : numeric
|
|
67
|
+
Module power temperature coefficient as percent per degree celsius.
|
|
68
|
+
Ex. -0.36
|
|
69
|
+
cell_temp : numeric or Series
|
|
70
|
+
Cell temperature (in Celsius) used to calculate temperature
|
|
71
|
+
differential from the `base_temp`.
|
|
72
|
+
base_temp : numeric, default 25
|
|
73
|
+
Base temperature (in Celsius) to correct power to. Default is the
|
|
74
|
+
STC of 25 degrees Celsius.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
type matches `power`
|
|
79
|
+
Power corrected for temperature.
|
|
80
|
+
"""
|
|
81
|
+
corr_power = power / (1 + ((power_temp_coeff / 100) * (cell_temp - base_temp)))
|
|
82
|
+
return corr_power
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def back_of_module_temp(
|
|
86
|
+
poa, temp_amb, wind_speed, module_type="glass_cell_poly", racking="open_rack"
|
|
87
|
+
):
|
|
88
|
+
"""Calculate back of module temperature from measured weather data.
|
|
89
|
+
|
|
90
|
+
Calculate back of module temperature from POA irradiance, ambient
|
|
91
|
+
temperature, wind speed (at height of 10 meters), and empirically
|
|
92
|
+
derived heat transfer coefficients.
|
|
93
|
+
|
|
94
|
+
Equation from NREL Weather Corrected Performance Ratio Report.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
poa : numeric or Series
|
|
99
|
+
POA irradiance in W/m^2.
|
|
100
|
+
temp_amb : numeric or Series
|
|
101
|
+
Ambient temperature in degrees C.
|
|
102
|
+
wind_speed : numeric or Series
|
|
103
|
+
Measured wind speed (m/sec) corrected to measurement height of
|
|
104
|
+
10 meters.
|
|
105
|
+
module_type : str, default 'glass_cell_poly'
|
|
106
|
+
Any of glass_cell_poly, glass_cell_glass, or 'poly_tf_steel'.
|
|
107
|
+
racking: str, default 'open_rack'
|
|
108
|
+
Any of 'open_rack', 'close_roof_mount', or 'insulated_back'
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
numeric or Series
|
|
113
|
+
Back of module temperatures.
|
|
114
|
+
"""
|
|
115
|
+
a = emp_heat_coeff[racking][module_type]["a"]
|
|
116
|
+
b = emp_heat_coeff[racking][module_type]["b"]
|
|
117
|
+
return poa * np.exp(a + b * wind_speed) + temp_amb
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def cell_temp(bom, poa, module_type="glass_cell_poly", racking="open_rack"):
|
|
121
|
+
"""Calculate cell temp from BOM temp, POA, and heat transfer coefficient.
|
|
122
|
+
|
|
123
|
+
Equation from NREL Weather Corrected Performance Ratio Report.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
bom : numeric or Series
|
|
128
|
+
Back of module temperature (degrees C). Strictly followin the NREL
|
|
129
|
+
procedure this value would be obtained from the `back_of_module_temp`
|
|
130
|
+
function.
|
|
131
|
+
|
|
132
|
+
Alternatively, a measured BOM temperature may be used.
|
|
133
|
+
|
|
134
|
+
Refer to p.7 of NREL Weather Corrected Performance Ratio Report.
|
|
135
|
+
poa : numeric or Series
|
|
136
|
+
POA irradiance in W/m^2.
|
|
137
|
+
module_type : str, default 'glass_cell_poly'
|
|
138
|
+
Any of glass_cell_poly, glass_cell_glass, or 'poly_tf_steel'.
|
|
139
|
+
racking: str, default 'open_rack'
|
|
140
|
+
Any of 'open_rack', 'close_roof_mount', or 'insulated_back'
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
numeric or Series
|
|
145
|
+
Cell temperature(s).
|
|
146
|
+
"""
|
|
147
|
+
return bom + (poa / 1000) * emp_heat_coeff[racking][module_type]["del_tcnd"]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def avg_typ_cell_temp(poa, cell_temp):
|
|
151
|
+
"""Calculate irradiance weighted cell temperature.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
poa : Series
|
|
156
|
+
POA irradiance (W/m^2).
|
|
157
|
+
cell_temp : Series
|
|
158
|
+
Cell temperature for each interval (degrees C).
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
float
|
|
163
|
+
Average irradiance-weighted cell temperature.
|
|
164
|
+
"""
|
|
165
|
+
return (poa * cell_temp).sum() / poa.sum()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
"""DIRECTLY BELOW DRAFT PR FUNCTION TO DO ALL VERSIONS OF CALC
|
|
169
|
+
DECIDED TO BREAK INTO SMALLER FUNCTIONS, LEAVE TEMPORARILY"""
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def perf_ratio_inputs_ok(ac_energy, dc_nameplate, poa, availability=1):
|
|
173
|
+
"""Check types of perf_ratio arguments.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
ac_energy : Series
|
|
178
|
+
Measured energy production (Wh) from system meter.
|
|
179
|
+
dc_nameplate : numeric
|
|
180
|
+
Summation of nameplate ratings (W) for all installed modules of system
|
|
181
|
+
under test.
|
|
182
|
+
poa : Series
|
|
183
|
+
POA irradiance (W/m^2) for each time interval of the test.
|
|
184
|
+
availability : numeric or Series, default 1
|
|
185
|
+
Apply an adjustment for plant availability to the expected power
|
|
186
|
+
(denominator).
|
|
187
|
+
"""
|
|
188
|
+
if not isinstance(ac_energy, pd.Series):
|
|
189
|
+
warnings.warn("ac_energy must be a Pandas Series.")
|
|
190
|
+
return False
|
|
191
|
+
elif not isinstance(poa, pd.Series):
|
|
192
|
+
warnings.warn("poa must be a Pandas Series.")
|
|
193
|
+
return False
|
|
194
|
+
elif not ac_energy.index.equals(poa.index):
|
|
195
|
+
warnings.warn("indices of poa and ac_energy must match.")
|
|
196
|
+
return False
|
|
197
|
+
elif isinstance(availability, pd.Series):
|
|
198
|
+
if not availability.index.equals(poa.index):
|
|
199
|
+
warnings.warn(
|
|
200
|
+
"Index of availability must match the index of the poa and ac_energy."
|
|
201
|
+
)
|
|
202
|
+
return False
|
|
203
|
+
else:
|
|
204
|
+
return True
|
|
205
|
+
else:
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def perf_ratio(
|
|
210
|
+
ac_energy,
|
|
211
|
+
dc_nameplate,
|
|
212
|
+
poa,
|
|
213
|
+
unit_adj=1,
|
|
214
|
+
degradation=0,
|
|
215
|
+
year=1,
|
|
216
|
+
availability=1,
|
|
217
|
+
):
|
|
218
|
+
"""Calculate performance ratio.
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
ac_energy : Series
|
|
223
|
+
Measured energy production (Wh) from system meter.
|
|
224
|
+
dc_nameplate : numeric
|
|
225
|
+
Summation of nameplate ratings (W) for all installed modules of system
|
|
226
|
+
under test.
|
|
227
|
+
poa : Series
|
|
228
|
+
POA irradiance (W/m^2) for each time interval of the test.
|
|
229
|
+
unit_adj : numeric, default 1
|
|
230
|
+
Scale factor to adjust units of `ac_energy`. For exmaple pass 1000
|
|
231
|
+
to convert measured energy from kWh to Wh within PR calculation.
|
|
232
|
+
degradation : numeric, default None
|
|
233
|
+
Apply a derate (percent, Ex: 0.5%) for degradation to the expected
|
|
234
|
+
power (denominator). Must also pass specify a value for the `year`
|
|
235
|
+
argument.
|
|
236
|
+
NOTE: Percent is divided by 100 to convert to decimal within function.
|
|
237
|
+
year : numeric
|
|
238
|
+
Year of operation to use in degradation calculation.
|
|
239
|
+
availability : numeric or Series, default 1
|
|
240
|
+
Apply an adjustment for plant availability to the expected power
|
|
241
|
+
(denominator).
|
|
242
|
+
|
|
243
|
+
Returns
|
|
244
|
+
-------
|
|
245
|
+
PrResults
|
|
246
|
+
Instance of class PrResults.
|
|
247
|
+
"""
|
|
248
|
+
if not perf_ratio_inputs_ok(
|
|
249
|
+
ac_energy, dc_nameplate, poa, availability=availability
|
|
250
|
+
):
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
timestep = get_common_timestep(poa, units="h", string_output=False)
|
|
254
|
+
timestep_str = get_common_timestep(poa, units="h", string_output=True)
|
|
255
|
+
|
|
256
|
+
expected_dc = (
|
|
257
|
+
availability
|
|
258
|
+
* dc_nameplate
|
|
259
|
+
* poa
|
|
260
|
+
/ 1000
|
|
261
|
+
* (1 - degradation / 100) ** year
|
|
262
|
+
* timestep
|
|
263
|
+
)
|
|
264
|
+
pr = ac_energy.sum() * unit_adj / expected_dc.sum()
|
|
265
|
+
|
|
266
|
+
input_cd = capdata.CapData("input_cd")
|
|
267
|
+
input_cd.data = pd.concat([poa, ac_energy], axis=1)
|
|
268
|
+
|
|
269
|
+
pr_per_timestep = ac_energy * unit_adj / expected_dc
|
|
270
|
+
results_data = pd.concat([ac_energy, expected_dc, pr_per_timestep], axis=1)
|
|
271
|
+
results_data.columns = ["ac_energy", "expected_dc", "pr_per_timestep"]
|
|
272
|
+
|
|
273
|
+
results = PrResults(
|
|
274
|
+
timestep=(timestep, timestep_str),
|
|
275
|
+
pr=pr,
|
|
276
|
+
dc_nameplate=dc_nameplate,
|
|
277
|
+
input_data=input_cd,
|
|
278
|
+
results_data=results_data,
|
|
279
|
+
)
|
|
280
|
+
return results
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def perf_ratio_temp_corr_nrel(
|
|
284
|
+
ac_energy,
|
|
285
|
+
dc_nameplate,
|
|
286
|
+
poa,
|
|
287
|
+
power_temp_coeff=None,
|
|
288
|
+
temp_amb=None,
|
|
289
|
+
wind_speed=None,
|
|
290
|
+
base_temp=25,
|
|
291
|
+
module_type="glass_cell_poly",
|
|
292
|
+
racking="open_rack",
|
|
293
|
+
unit_adj=1,
|
|
294
|
+
degradation=None,
|
|
295
|
+
year=None,
|
|
296
|
+
availability=1,
|
|
297
|
+
):
|
|
298
|
+
"""Calculate performance ratio.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
ac_energy : Series
|
|
303
|
+
Measured energy production (kWh) from system meter.
|
|
304
|
+
dc_nameplate : numeric
|
|
305
|
+
Summation of nameplate ratings (W) for all installed modules of system
|
|
306
|
+
under test.
|
|
307
|
+
poa : Series
|
|
308
|
+
POA irradiance (W/m^2) for each time interval of the test.
|
|
309
|
+
power_temp_coeff : numeric, default None
|
|
310
|
+
Module power temperature coefficient as percent per degree celsius.
|
|
311
|
+
Ex. -0.36
|
|
312
|
+
temp_amb : Series
|
|
313
|
+
Ambient temperature (degrees C) measurements.
|
|
314
|
+
wind_speed : Series
|
|
315
|
+
Measured wind speed (m/sec) corrected to measurement height of
|
|
316
|
+
10 meters.
|
|
317
|
+
base_temp : numeric, default 25
|
|
318
|
+
Base temperature (in Celsius) to correct power to. Default is the
|
|
319
|
+
STC of 25 degrees Celsius. The NREL Weather-Corrected Performance
|
|
320
|
+
Ratio technical report uses the term 'Tcell_typ_avg' for this value.
|
|
321
|
+
module_type : str, default 'glass_cell_poly'
|
|
322
|
+
Any of glass_cell_poly, glass_cell_glass, or 'poly_tf_steel'.
|
|
323
|
+
racking: str, default 'open_rack'
|
|
324
|
+
Any of 'open_rack', 'close_roof_mount', or 'insulated_back'
|
|
325
|
+
unit_adj : numeric, default 1
|
|
326
|
+
Scale factor to adjust units of `ac_energy`. For exmaple pass 1000
|
|
327
|
+
to convert measured energy from kWh to Wh within PR calculation.
|
|
328
|
+
degradation : numeric, default None
|
|
329
|
+
NOT IMPLEMENTED
|
|
330
|
+
Apply a derate for degradation to the expected power (denominator).
|
|
331
|
+
Must also pass specify a value for the `year` argument.
|
|
332
|
+
year : numeric
|
|
333
|
+
NOT IMPLEMENTED
|
|
334
|
+
Year of operation to use in degradation calculation.
|
|
335
|
+
availability : numeric or Series, default 1
|
|
336
|
+
NOT IMPLEMENTED
|
|
337
|
+
Apply an adjustment for plant availability to the expected power
|
|
338
|
+
(denominator).
|
|
339
|
+
|
|
340
|
+
Returns
|
|
341
|
+
-------
|
|
342
|
+
"""
|
|
343
|
+
timestep = get_common_timestep(poa, units="h", string_output=False)
|
|
344
|
+
timestep_str = get_common_timestep(poa, units="h", string_output=True)
|
|
345
|
+
|
|
346
|
+
temp_bom = back_of_module_temp(poa, temp_amb, wind_speed, module_type, racking)
|
|
347
|
+
temp_cell = cell_temp(temp_bom, poa, module_type, racking)
|
|
348
|
+
dc_nameplate_temp_corr = temp_correct_power(
|
|
349
|
+
dc_nameplate, power_temp_coeff, temp_cell, base_temp=base_temp
|
|
350
|
+
)
|
|
351
|
+
# below is same as the perf_ratio function
|
|
352
|
+
# move to a separate function?
|
|
353
|
+
expected_dc = (
|
|
354
|
+
availability
|
|
355
|
+
* dc_nameplate_temp_corr
|
|
356
|
+
* poa
|
|
357
|
+
/ 1000
|
|
358
|
+
# * (1 - degradation / 100)**year
|
|
359
|
+
* timestep
|
|
360
|
+
)
|
|
361
|
+
pr = ac_energy.sum() * unit_adj / expected_dc.sum()
|
|
362
|
+
|
|
363
|
+
input_cd = capdata.CapData("input_cd")
|
|
364
|
+
input_cd.data = pd.concat([poa, ac_energy], axis=1)
|
|
365
|
+
|
|
366
|
+
pr_per_timestep = ac_energy * unit_adj / expected_dc
|
|
367
|
+
results_data = pd.concat([ac_energy, expected_dc, pr_per_timestep], axis=1)
|
|
368
|
+
results_data.columns = ["ac_energy", "expected_dc", "pr_per_timestep"]
|
|
369
|
+
|
|
370
|
+
results = PrResults(
|
|
371
|
+
timestep=(timestep, timestep_str),
|
|
372
|
+
pr=pr,
|
|
373
|
+
dc_nameplate=dc_nameplate,
|
|
374
|
+
input_data=input_cd,
|
|
375
|
+
results_data=results_data,
|
|
376
|
+
)
|
|
377
|
+
return results
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class PrResults(param.Parameterized):
|
|
381
|
+
"""
|
|
382
|
+
Results from a PR calculation.
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
dc_nameplate = param.Number(
|
|
386
|
+
bounds=(0, None),
|
|
387
|
+
doc=("Summation of nameplate ratings (W) for all installed modules of system."),
|
|
388
|
+
)
|
|
389
|
+
pr = param.Number(doc="Performance ratio result decimal fraction.")
|
|
390
|
+
timestep = param.Tuple(doc="Timestep of series.")
|
|
391
|
+
expected_pr = param.Number(
|
|
392
|
+
bounds=(0, 1), doc="Expected Performance ratio result decimal fraction."
|
|
393
|
+
)
|
|
394
|
+
input_data = param.ClassSelector(capdata.CapData)
|
|
395
|
+
results_data = param.ClassSelector(pd.DataFrame)
|
|
396
|
+
|
|
397
|
+
def print_pr_result(self):
|
|
398
|
+
"""Print summary of PR result - passing / failing and by how much"""
|
|
399
|
+
if self.pr >= self.expected_pr:
|
|
400
|
+
print(
|
|
401
|
+
"The test is PASSING with a measured PR of {:.2f}, "
|
|
402
|
+
"which is {:.2f} above the expected PR of {:.2f}".format(
|
|
403
|
+
self.pr * 100,
|
|
404
|
+
(self.pr - self.expected_pr) * 100,
|
|
405
|
+
self.expected_pr * 100,
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
else:
|
|
409
|
+
print(
|
|
410
|
+
"The test is FAILING with a measured PR of {:.2f}, "
|
|
411
|
+
"which is {:.2f} below the expected PR of {:.2f}".format(
|
|
412
|
+
self.pr * 100,
|
|
413
|
+
(self.expected_pr - self.pr) * 100,
|
|
414
|
+
self.expected_pr * 100,
|
|
415
|
+
)
|
|
416
|
+
)
|
captest/util.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
import yaml
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def read_json(path):
|
|
9
|
+
with open(path) as f:
|
|
10
|
+
json_data = json.load(f)
|
|
11
|
+
return json_data
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def read_yaml(path):
|
|
15
|
+
with open(path, "r") as stream:
|
|
16
|
+
try:
|
|
17
|
+
data = yaml.safe_load(stream)
|
|
18
|
+
except yaml.YAMLError as exc:
|
|
19
|
+
print(exc)
|
|
20
|
+
return data
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_common_timestep(data, units="m", string_output=True):
|
|
24
|
+
"""
|
|
25
|
+
Get the most commonly occuring timestep of data as frequency string.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
data : Series or DataFrame
|
|
30
|
+
Data with a DateTimeIndex.
|
|
31
|
+
units : str, default 'm'
|
|
32
|
+
String representing date/time unit, such as (D)ay, (M)onth, (Y)ear,
|
|
33
|
+
(h)ours, (m)inutes, or (s)econds.
|
|
34
|
+
string_output : bool, default True
|
|
35
|
+
Set to False to return a numeric value.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
str or numeric
|
|
40
|
+
If the `string_output` is True and the most common timestep is an integer
|
|
41
|
+
in the specified units then a valid pandas frequency or offset alias is
|
|
42
|
+
returned.
|
|
43
|
+
If `string_output` is false, then a numeric value is returned.
|
|
44
|
+
"""
|
|
45
|
+
units_abbrev = {"D": "D", "M": "M", "Y": "Y", "h": "H", "m": "min", "s": "S"}
|
|
46
|
+
common_timestep = data.index.to_series().diff().mode().values[0]
|
|
47
|
+
common_timestep_tdelta = common_timestep.astype("timedelta64[m]")
|
|
48
|
+
freq = common_timestep_tdelta / np.timedelta64(1, units)
|
|
49
|
+
if string_output:
|
|
50
|
+
try:
|
|
51
|
+
return str(int(freq)) + units_abbrev[units]
|
|
52
|
+
except Exception:
|
|
53
|
+
return str(freq) + units_abbrev[units]
|
|
54
|
+
else:
|
|
55
|
+
return freq
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def reindex_datetime(data, report=False):
|
|
59
|
+
"""
|
|
60
|
+
Find dataframe index frequency and reindex to add any missing intervals.
|
|
61
|
+
|
|
62
|
+
Sorts index of passed dataframe before reindexing.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
data : DataFrame
|
|
67
|
+
DataFrame to be reindexed.
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
Reindexed DataFrame
|
|
72
|
+
"""
|
|
73
|
+
data_index_length = data.shape[0]
|
|
74
|
+
df = data.copy()
|
|
75
|
+
df.sort_index(inplace=True)
|
|
76
|
+
|
|
77
|
+
freq_str = get_common_timestep(data, string_output=True)
|
|
78
|
+
full_ix = pd.date_range(start=df.index[0], end=df.index[-1], freq=freq_str)
|
|
79
|
+
df = df.reindex(index=full_ix)
|
|
80
|
+
df_index_length = df.shape[0]
|
|
81
|
+
missing_intervals = df_index_length - data_index_length
|
|
82
|
+
|
|
83
|
+
if report:
|
|
84
|
+
print("Frequency determined to be " + freq_str + " minutes.")
|
|
85
|
+
print("{:,} intervals added to index.".format(missing_intervals))
|
|
86
|
+
print("")
|
|
87
|
+
|
|
88
|
+
return df, missing_intervals, freq_str
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def generate_irr_distribution(lowest_irr, highest_irr, rng=np.random.default_rng(82)):
|
|
92
|
+
"""
|
|
93
|
+
Create a list of increasing values similar to POA irradiance data.
|
|
94
|
+
|
|
95
|
+
Default parameters result in increasing values where the difference
|
|
96
|
+
between each subsquent value is randomly chosen from the typical range
|
|
97
|
+
of steps for a POA tracker.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
lowest_irr : numeric
|
|
102
|
+
Lowest value in the list of values returned.
|
|
103
|
+
highest_irr : numeric
|
|
104
|
+
Highest value in the list of values returned.
|
|
105
|
+
rng : Numpy Random Generator
|
|
106
|
+
Instance of the default Generator.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
irr_values : list
|
|
111
|
+
"""
|
|
112
|
+
irr_values = [
|
|
113
|
+
lowest_irr,
|
|
114
|
+
]
|
|
115
|
+
possible_steps = rng.integers(1, high=8, size=10000) + rng.random(size=10000) - 1
|
|
116
|
+
below_max = True
|
|
117
|
+
while below_max:
|
|
118
|
+
next_val = irr_values[-1] + rng.choice(possible_steps, replace=False)
|
|
119
|
+
if next_val >= highest_irr:
|
|
120
|
+
below_max = False
|
|
121
|
+
else:
|
|
122
|
+
irr_values.append(next_val)
|
|
123
|
+
return irr_values
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def tags_by_regex(tag_list, regex_str):
|
|
127
|
+
regex = re.compile(regex_str, re.IGNORECASE)
|
|
128
|
+
return [tag for tag in tag_list if regex.search(tag) is not None]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def append_tags(sel_tags, tags, regex_str):
|
|
132
|
+
new_list = sel_tags.copy()
|
|
133
|
+
new_list.extend(tags_by_regex(tags, regex_str))
|
|
134
|
+
return new_list
|