captest 0.11.2__py2.py3-none-any.whl → 0.13.0__py2.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 +1 -0
- captest/_version.py +3 -3
- captest/capdata.py +275 -339
- captest/io.py +150 -64
- captest/plotting.py +492 -0
- captest/prtest.py +1 -1
- captest/util.py +14 -6
- {captest-0.11.2.dist-info → captest-0.13.0.dist-info}/METADATA +30 -29
- captest-0.13.0.dist-info/RECORD +13 -0
- {captest-0.11.2.dist-info → captest-0.13.0.dist-info}/WHEEL +1 -1
- captest-0.11.2.dist-info/RECORD +0 -12
- {captest-0.11.2.dist-info → captest-0.13.0.dist-info}/LICENSE.txt +0 -0
- {captest-0.11.2.dist-info → captest-0.13.0.dist-info}/top_level.txt +0 -0
captest/plotting.py
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import copy
|
|
3
|
+
import json
|
|
4
|
+
import warnings
|
|
5
|
+
import itertools
|
|
6
|
+
from functools import partial
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import panel as pn
|
|
10
|
+
from panel.interact import fixed
|
|
11
|
+
import holoviews as hv
|
|
12
|
+
from holoviews import opts
|
|
13
|
+
import colorcet as cc
|
|
14
|
+
from bokeh.models import NumeralTickFormatter
|
|
15
|
+
|
|
16
|
+
from .util import tags_by_regex, append_tags, read_json
|
|
17
|
+
|
|
18
|
+
# disable error messages for panel dashboard
|
|
19
|
+
pn.config.console_output = 'disable'
|
|
20
|
+
|
|
21
|
+
COMBINE = {
|
|
22
|
+
'poa_ghi': 'irr.*(poa|ghi)$',
|
|
23
|
+
'poa_csky': '(?=.*poa)(?=.*irr)',
|
|
24
|
+
'ghi_csky': '(?=.*ghi)(?=.*irr)',
|
|
25
|
+
'temp_amb_bom': '(?=.*temp)((?=.*amb)|(?=.*bom))',
|
|
26
|
+
'inv_sum_mtr_pwr': ['(?=.*real)(?=.*pwr)(?=.*mtr)', '(?=.*pwr)(?=.*agg)'],
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
DEFAULT_GROUPS = [
|
|
30
|
+
'inv_sum_mtr_pwr',
|
|
31
|
+
'(?=.*real)(?=.*pwr)(?=.*inv)',
|
|
32
|
+
'(?=.*real)(?=.*pwr)(?=.*mtr)',
|
|
33
|
+
'poa_ghi',
|
|
34
|
+
'poa_csky',
|
|
35
|
+
'ghi_csky',
|
|
36
|
+
'temp_amb_bom',
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def find_default_groups(groups, default_groups):
|
|
41
|
+
"""
|
|
42
|
+
Find the default groups in the list of groups.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
groups : list of str
|
|
47
|
+
The list of groups to search for the default groups.
|
|
48
|
+
default_groups : list of str
|
|
49
|
+
List of regex strings to use to identify default groups.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
list of str
|
|
54
|
+
The default groups found in the list of groups.
|
|
55
|
+
"""
|
|
56
|
+
found_groups = []
|
|
57
|
+
for re_str in default_groups:
|
|
58
|
+
found_grp = tags_by_regex(groups, re_str)
|
|
59
|
+
if len(found_grp) == 1:
|
|
60
|
+
found_groups.append(found_grp[0])
|
|
61
|
+
elif len(found_grp) > 1:
|
|
62
|
+
warnings.warn(
|
|
63
|
+
f'More than one group found for regex string {re_str}. '
|
|
64
|
+
'Refine regex string to find only one group. '
|
|
65
|
+
f'Groups found: {found_grp}'
|
|
66
|
+
|
|
67
|
+
)
|
|
68
|
+
return found_groups
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def parse_combine(combine, column_groups=None, data=None, cd=None):
|
|
72
|
+
"""
|
|
73
|
+
Parse regex strings for identifying groups of columns or tags to combine.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
combine : dict
|
|
78
|
+
Dictionary of group names and regex strings to use to identify groups from
|
|
79
|
+
column groups and individual tags (columns) to combine into new groups.
|
|
80
|
+
Keys should be strings for names of new groups. Values should be either a string
|
|
81
|
+
or a list of two strings. If a string, the string is used as a regex to identify
|
|
82
|
+
groups to combine. If a list, the first string is used to identify groups to
|
|
83
|
+
combine and the second is used to identify individual tags (columns) to combine.
|
|
84
|
+
column_groups : ColumnGroups, optional
|
|
85
|
+
The column groups object to add new groups to. Required if `cd` is not provided.
|
|
86
|
+
data : pd.DataFrame, optional
|
|
87
|
+
The data to use to identify groups and columns to combine. Required if `cd` is
|
|
88
|
+
not provided.
|
|
89
|
+
cd : captest.CapData, optional
|
|
90
|
+
The captest.CapData object with the `data` and `column_groups` attributes set.
|
|
91
|
+
Required if `columng_groups` and `data` are not provided.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
ColumnGroups
|
|
96
|
+
New column groups object with new groups added.
|
|
97
|
+
"""
|
|
98
|
+
if cd is not None:
|
|
99
|
+
data = cd.data
|
|
100
|
+
column_groups = cd.column_groups
|
|
101
|
+
cg_out = copy.deepcopy(column_groups)
|
|
102
|
+
orig_groups = list(cg_out.keys())
|
|
103
|
+
|
|
104
|
+
tags = list(data.columns)
|
|
105
|
+
|
|
106
|
+
for grp_name, re_str in combine.items():
|
|
107
|
+
group_re = None
|
|
108
|
+
tag_re = None
|
|
109
|
+
tags_in_matched_groups = []
|
|
110
|
+
matched_tags = []
|
|
111
|
+
if isinstance(re_str, str):
|
|
112
|
+
group_re = re_str
|
|
113
|
+
elif isinstance(re_str, list):
|
|
114
|
+
if len(re_str) != 2:
|
|
115
|
+
warnings.warn(
|
|
116
|
+
'When passing a list of regex. There should be two strings. One for '
|
|
117
|
+
'identifying groups and one for identifying individual tags (columns).'
|
|
118
|
+
)
|
|
119
|
+
return None
|
|
120
|
+
else:
|
|
121
|
+
group_re = re_str[0]
|
|
122
|
+
tag_re = re_str[1]
|
|
123
|
+
if group_re is not None:
|
|
124
|
+
matched_groups = tags_by_regex(orig_groups, group_re)
|
|
125
|
+
if len(matched_groups) >= 1:
|
|
126
|
+
tags_in_matched_groups = list(
|
|
127
|
+
itertools.chain(*[cg_out[grp] for grp in matched_groups])
|
|
128
|
+
)
|
|
129
|
+
if tag_re is not None:
|
|
130
|
+
matched_tags = tags_by_regex(tags, tag_re)
|
|
131
|
+
cg_out[grp_name] = tags_in_matched_groups + matched_tags
|
|
132
|
+
return cg_out
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def msel_from_column_groups(column_groups, groups=True):
|
|
136
|
+
"""
|
|
137
|
+
Create a multi-select widget from a column groups object.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
column_groups : ColumnGroups
|
|
142
|
+
The column groups object.
|
|
143
|
+
groups : bool, default True
|
|
144
|
+
By default creates list of groups i.e. the keys of `column_groups`,
|
|
145
|
+
otherwise creates list of individual columns i.e. the values of `column_groups`
|
|
146
|
+
concatenated together.
|
|
147
|
+
"""
|
|
148
|
+
if groups:
|
|
149
|
+
keys = list(column_groups.data.keys())
|
|
150
|
+
keys.sort()
|
|
151
|
+
options = {k: column_groups.data[k] for k in keys}
|
|
152
|
+
name = 'Groups'
|
|
153
|
+
value = column_groups.data[list(column_groups.keys())[0]]
|
|
154
|
+
else:
|
|
155
|
+
options = []
|
|
156
|
+
for columns in column_groups.values():
|
|
157
|
+
options += columns
|
|
158
|
+
options.sort()
|
|
159
|
+
name = 'Columns'
|
|
160
|
+
value = [options[0]]
|
|
161
|
+
return pn.widgets.MultiSelect(
|
|
162
|
+
name=name,
|
|
163
|
+
value=value,
|
|
164
|
+
options=options,
|
|
165
|
+
size=8,
|
|
166
|
+
height=400,
|
|
167
|
+
width=400
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def plot_tag(data, tag, width=1500, height=250):
|
|
172
|
+
if len(tag) == 1:
|
|
173
|
+
plot = hv.Curve(data[tag])
|
|
174
|
+
elif len(tag) > 1:
|
|
175
|
+
curves = {}
|
|
176
|
+
for column in tag:
|
|
177
|
+
try:
|
|
178
|
+
curves[column] = hv.Curve(data[column])
|
|
179
|
+
except KeyError:
|
|
180
|
+
continue
|
|
181
|
+
plot = hv.NdOverlay(curves)
|
|
182
|
+
elif len(tag) == 0:
|
|
183
|
+
plot = hv.Curve(pd.DataFrame(
|
|
184
|
+
{'no_data': [np.NaN] * data.shape[0]},
|
|
185
|
+
index=data.index
|
|
186
|
+
))
|
|
187
|
+
plot.opts(
|
|
188
|
+
opts.Curve(
|
|
189
|
+
line_width=1,
|
|
190
|
+
width=width,
|
|
191
|
+
height=height,
|
|
192
|
+
muted_alpha=0,
|
|
193
|
+
tools=['hover'],
|
|
194
|
+
yformatter=NumeralTickFormatter(format='0,0'),
|
|
195
|
+
),
|
|
196
|
+
opts.NdOverlay(width=width, height=height, legend_position='right')
|
|
197
|
+
)
|
|
198
|
+
return plot
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def group_tag_overlay(group_tags, column_tags):
|
|
202
|
+
"""
|
|
203
|
+
Overlay curves of groups and individually selected columns.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
group_tags : list of str
|
|
208
|
+
The tags to plot from the groups selected.
|
|
209
|
+
column_tags : list of str
|
|
210
|
+
The tags to plot from the individually selected columns.
|
|
211
|
+
"""
|
|
212
|
+
joined_tags = [t for tag_list in group_tags for t in tag_list] + column_tags
|
|
213
|
+
return joined_tags
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def plot_group_tag_overlay(data, group_tags, column_tags, width=1500, height=400):
|
|
217
|
+
"""
|
|
218
|
+
Overlay curves of groups and individually selected columns.
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
data : pd.DataFrame
|
|
223
|
+
The data to plot.
|
|
224
|
+
group_tags : list of str
|
|
225
|
+
The tags to plot from the groups selected.
|
|
226
|
+
column_tags : list of str
|
|
227
|
+
The tags to plot from the individually selected columns.
|
|
228
|
+
"""
|
|
229
|
+
joined_tags = group_tag_overlay(group_tags, column_tags)
|
|
230
|
+
return plot_tag(data, joined_tags, width=width, height=height)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def plot_tag_groups(data, tags_to_plot, width=1500, height=250):
|
|
234
|
+
"""
|
|
235
|
+
Plot groups of tags, one of overlayed curves per group.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
data : pd.DataFrame
|
|
240
|
+
The data to plot.
|
|
241
|
+
tags_to_plot : list
|
|
242
|
+
List of lists of strings. One plot for each inner list.
|
|
243
|
+
"""
|
|
244
|
+
group_plots = []
|
|
245
|
+
if len(tags_to_plot) == 0:
|
|
246
|
+
tags_to_plot = [[]]
|
|
247
|
+
for group in tags_to_plot:
|
|
248
|
+
plot = plot_tag(data, group, width=width, height=height)
|
|
249
|
+
group_plots.append(plot)
|
|
250
|
+
return hv.Layout(group_plots).cols(1)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def filter_list(text_input, ms_to_filter, names, event=None):
|
|
254
|
+
"""
|
|
255
|
+
Filter a multi-select widget by a regex string.
|
|
256
|
+
|
|
257
|
+
Parameters
|
|
258
|
+
----------
|
|
259
|
+
text_input : pn.widgets.TextInput
|
|
260
|
+
The text input widget to get the regex string from.
|
|
261
|
+
ms_to_filter : pn.widgets.MultiSelect
|
|
262
|
+
The multi-select widget to update.
|
|
263
|
+
names : list of str
|
|
264
|
+
The list of names to filter.
|
|
265
|
+
event : pn.widgets.event, optional
|
|
266
|
+
Passed by the `param.watch` method. Not used.
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
None
|
|
271
|
+
"""
|
|
272
|
+
if text_input.value == '':
|
|
273
|
+
re_value = '.*'
|
|
274
|
+
else:
|
|
275
|
+
re_value = text_input.value
|
|
276
|
+
names_ = copy.deepcopy(names)
|
|
277
|
+
if isinstance(names_, dict):
|
|
278
|
+
selected_groups = tags_by_regex(list(names_.keys()), re_value)
|
|
279
|
+
selected_groups.sort()
|
|
280
|
+
options = {k: names_[k] for k in selected_groups}
|
|
281
|
+
else:
|
|
282
|
+
options = tags_by_regex(names_, re_value)
|
|
283
|
+
options.sort()
|
|
284
|
+
ms_to_filter.param.update(options=options)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def scatter_dboard(data, **kwargs):
|
|
288
|
+
"""
|
|
289
|
+
Create a dashboard to plot any two columns of data against each other.
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
data : pd.DataFrame
|
|
294
|
+
The data to plot.
|
|
295
|
+
**kwargs : optional
|
|
296
|
+
Pass additional keyword arguments to the holoviews options of the scatter plot.
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
pn.Column
|
|
301
|
+
The dashboard with a scatter plot of the data.
|
|
302
|
+
"""
|
|
303
|
+
cols = list(data.columns)
|
|
304
|
+
cols.sort()
|
|
305
|
+
x = pn.widgets.Select(name='x', value=cols[0], options=cols)
|
|
306
|
+
y = pn.widgets.Select(name='y', value=cols[1], options=cols)
|
|
307
|
+
# slope = pn.widgets.Checkbox(name='Slope', value=False)
|
|
308
|
+
|
|
309
|
+
defaults = {
|
|
310
|
+
'width': 500,
|
|
311
|
+
'height': 500,
|
|
312
|
+
'fill_alpha': 0.4,
|
|
313
|
+
'line_alpha': 0,
|
|
314
|
+
'size': 4,
|
|
315
|
+
'yformatter': NumeralTickFormatter(format='0,0'),
|
|
316
|
+
'xformatter': NumeralTickFormatter(format='0,0'),
|
|
317
|
+
}
|
|
318
|
+
for opt, value in defaults.items():
|
|
319
|
+
kwargs.setdefault(opt, value)
|
|
320
|
+
|
|
321
|
+
def scatter(data, x, y, slope=True, **kwargs):
|
|
322
|
+
scatter_plot = hv.Scatter(data, x, y).opts(**kwargs)
|
|
323
|
+
# if slope:
|
|
324
|
+
# slope_line = hv.Slope.from_scatter(scatter_plot).opts(
|
|
325
|
+
# line_color='red',
|
|
326
|
+
# line_width=1,
|
|
327
|
+
# line_alpha=0.4,
|
|
328
|
+
# line_dash=(5,3)
|
|
329
|
+
# )
|
|
330
|
+
# if slope:
|
|
331
|
+
# return scatter_plot * slope_line
|
|
332
|
+
# else:
|
|
333
|
+
return scatter_plot
|
|
334
|
+
|
|
335
|
+
# dboard = pn.Column(
|
|
336
|
+
# pn.Row(x, y, slope),
|
|
337
|
+
# pn.bind(scatter, data, x, y, slope=slope, **kwargs)
|
|
338
|
+
# )
|
|
339
|
+
dboard = pn.Column(
|
|
340
|
+
pn.Row(x, y),
|
|
341
|
+
pn.bind(scatter, data, x, y, **kwargs)
|
|
342
|
+
)
|
|
343
|
+
return dboard
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def plot(
|
|
347
|
+
cd=None,
|
|
348
|
+
cg=None,
|
|
349
|
+
data=None,
|
|
350
|
+
combine=COMBINE,
|
|
351
|
+
default_groups=DEFAULT_GROUPS,
|
|
352
|
+
group_width=1500,
|
|
353
|
+
group_height=250,
|
|
354
|
+
**kwargs,
|
|
355
|
+
):
|
|
356
|
+
"""
|
|
357
|
+
Create plotting dashboard.
|
|
358
|
+
|
|
359
|
+
NOTE: If a 'plot_defaults.json' file exists in the same directory as the file this
|
|
360
|
+
function is called from called, then the default groups will be read from that file
|
|
361
|
+
instead of using the `default_groups` argument. Delete or manually edit the file to
|
|
362
|
+
change the default groups. Use the `default_groups` or manually edit the file to
|
|
363
|
+
control the order of the plots.
|
|
364
|
+
|
|
365
|
+
Parameters
|
|
366
|
+
----------
|
|
367
|
+
cd : captest.CapData, optional
|
|
368
|
+
The captest.CapData object.
|
|
369
|
+
cg : captest.ColumnGroups, optional
|
|
370
|
+
The captest.ColumnGroups object. `data` must also be provided.
|
|
371
|
+
data : pd.DataFrame, optional
|
|
372
|
+
The data to plot. `cg` must also be provided.
|
|
373
|
+
combine : dict, optional
|
|
374
|
+
Dictionary of group names and regex strings to use to identify groups from
|
|
375
|
+
column groups and individual tags (columns) to combine into new groups. See the
|
|
376
|
+
`parse_combine` function for more details.
|
|
377
|
+
default_groups : list of str, optional
|
|
378
|
+
List of regex strings to use to identify default groups to plot. See the
|
|
379
|
+
`find_default_groups` function for more details.
|
|
380
|
+
group_width : int, optional
|
|
381
|
+
The width of the plots on the Groups tab.
|
|
382
|
+
group_height : int, optional
|
|
383
|
+
The height of the plots on the Groups tab.
|
|
384
|
+
**kwargs : optional
|
|
385
|
+
Pass additional keyword arguments to the holoviews options of the scatter plot
|
|
386
|
+
on the 'Scatter' tab.
|
|
387
|
+
"""
|
|
388
|
+
if cd is not None:
|
|
389
|
+
data = cd.data
|
|
390
|
+
cg = cd.column_groups
|
|
391
|
+
# make sure data is numeric
|
|
392
|
+
data = data.apply(pd.to_numeric, errors='coerce')
|
|
393
|
+
bool_columns = data.select_dtypes(include='bool').columns
|
|
394
|
+
data.loc[:, bool_columns] = data.loc[:, bool_columns].astype(int)
|
|
395
|
+
# setup custom plot for 'Custom' tab
|
|
396
|
+
groups = msel_from_column_groups(cg)
|
|
397
|
+
tags = msel_from_column_groups({'all_tags': list(data.columns)}, groups=False)
|
|
398
|
+
columns_re_input = pn.widgets.TextInput(name='Input regex to filter columns list')
|
|
399
|
+
groups_re_input = pn.widgets.TextInput(name='Input regex to filter groups list')
|
|
400
|
+
|
|
401
|
+
columns_re_input.param.watch(
|
|
402
|
+
partial(filter_list, columns_re_input, tags, tags.options),
|
|
403
|
+
'value'
|
|
404
|
+
)
|
|
405
|
+
groups_re_input.param.watch(
|
|
406
|
+
partial(filter_list, groups_re_input, groups, groups.options),
|
|
407
|
+
'value'
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
custom_plot_name = pn.widgets.TextInput()
|
|
411
|
+
update = pn.widgets.Button(name='Update')
|
|
412
|
+
width_custom = pn.widgets.IntInput(
|
|
413
|
+
name='Plot Width', value=1500, start=200, end=2800, step=100, width=200
|
|
414
|
+
)
|
|
415
|
+
height_custom = pn.widgets.IntInput(
|
|
416
|
+
name='Plot height', value=400, start=150, end=800, step=50, width=200
|
|
417
|
+
)
|
|
418
|
+
custom_plot = pn.Column(
|
|
419
|
+
pn.Row(custom_plot_name, update, width_custom, height_custom),
|
|
420
|
+
pn.Row(
|
|
421
|
+
pn.WidgetBox(groups_re_input, groups),
|
|
422
|
+
pn.WidgetBox(columns_re_input, tags),
|
|
423
|
+
),
|
|
424
|
+
pn.Row(pn.bind(
|
|
425
|
+
plot_group_tag_overlay,
|
|
426
|
+
data,
|
|
427
|
+
groups,
|
|
428
|
+
tags,
|
|
429
|
+
width=width_custom,
|
|
430
|
+
height=height_custom,
|
|
431
|
+
))
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
# setup group plotter for 'Main' tab
|
|
435
|
+
cg_layout = parse_combine(combine, column_groups=cg, data=data)
|
|
436
|
+
main_ms = msel_from_column_groups(cg_layout)
|
|
437
|
+
|
|
438
|
+
def add_custom_plot_group(event):
|
|
439
|
+
column_groups_ = copy.deepcopy(main_ms.options)
|
|
440
|
+
column_groups_ = add_custom_plot(
|
|
441
|
+
custom_plot_name.value,
|
|
442
|
+
column_groups_,
|
|
443
|
+
groups.value,
|
|
444
|
+
tags.value,
|
|
445
|
+
)
|
|
446
|
+
main_ms.options = column_groups_
|
|
447
|
+
update.on_click(add_custom_plot_group)
|
|
448
|
+
plots_to_layout = pn.widgets.Button(name='Set plots to current layout')
|
|
449
|
+
width_main = pn.widgets.IntInput(
|
|
450
|
+
name='Plot Width', value=1500, start=200, end=2800, step=100, width=200
|
|
451
|
+
)
|
|
452
|
+
height_main = pn.widgets.IntInput(
|
|
453
|
+
name='Plot height', value=250, start=150, end=800, step=50, width=200
|
|
454
|
+
)
|
|
455
|
+
main_plot = pn.Column(
|
|
456
|
+
pn.Row(pn.WidgetBox(plots_to_layout, main_ms, pn.Row(width_main, height_main))),
|
|
457
|
+
pn.Row(pn.bind(
|
|
458
|
+
plot_tag_groups, data, main_ms, width=width_main, height=height_main
|
|
459
|
+
)),
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
def set_defaults(event):
|
|
463
|
+
with open('./plot_defaults.json', 'w') as file:
|
|
464
|
+
json.dump(main_ms.value, file)
|
|
465
|
+
plots_to_layout.on_click(set_defaults)
|
|
466
|
+
|
|
467
|
+
# setup default groups
|
|
468
|
+
if Path('./plot_defaults.json').exists():
|
|
469
|
+
default_tags = read_json('./plot_defaults.json')
|
|
470
|
+
else:
|
|
471
|
+
default_groups = find_default_groups(list(cg_layout.keys()), default_groups)
|
|
472
|
+
default_tags = [cg_layout.get(grp, []) for grp in default_groups]
|
|
473
|
+
|
|
474
|
+
# layout dashboard
|
|
475
|
+
plotter = pn.Tabs(
|
|
476
|
+
('Groups', plot_tag_groups(data, default_tags, width=group_width, height=group_height)),
|
|
477
|
+
('Layout', main_plot),
|
|
478
|
+
('Overlay', custom_plot),
|
|
479
|
+
('Scatter', scatter_dboard(data, **kwargs)),
|
|
480
|
+
)
|
|
481
|
+
return plotter
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def add_custom_plot(name, column_groups, group_tags, column_tags):
|
|
485
|
+
"""
|
|
486
|
+
Append a new custom group to column groups for plotting.
|
|
487
|
+
|
|
488
|
+
Parameters
|
|
489
|
+
----------
|
|
490
|
+
"""
|
|
491
|
+
column_groups[name] = group_tag_overlay(group_tags, column_tags)
|
|
492
|
+
return column_groups
|
captest/prtest.py
CHANGED
|
@@ -44,7 +44,7 @@ def get_common_timestep(data, units="m", string_output=True):
|
|
|
44
44
|
"m": "minutes",
|
|
45
45
|
"s": "seconds",
|
|
46
46
|
}
|
|
47
|
-
common_timestep =
|
|
47
|
+
common_timestep = data.index.to_series().diff().mode().values[0]
|
|
48
48
|
common_timestep_tdelta = common_timestep.astype("timedelta64[m]")
|
|
49
49
|
freq = common_timestep_tdelta / np.timedelta64(1, units)
|
|
50
50
|
if string_output:
|
captest/util.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
import json
|
|
2
3
|
import yaml
|
|
3
4
|
import numpy as np
|
|
@@ -50,7 +51,7 @@ def get_common_timestep(data, units='m', string_output=True):
|
|
|
50
51
|
'm': 'min',
|
|
51
52
|
's': 'S'
|
|
52
53
|
}
|
|
53
|
-
common_timestep =
|
|
54
|
+
common_timestep = data.index.to_series().diff().mode().values[0]
|
|
54
55
|
common_timestep_tdelta = common_timestep.astype('timedelta64[m]')
|
|
55
56
|
freq = common_timestep_tdelta / np.timedelta64(1, units)
|
|
56
57
|
if string_output:
|
|
@@ -61,7 +62,7 @@ def get_common_timestep(data, units='m', string_output=True):
|
|
|
61
62
|
else:
|
|
62
63
|
return freq
|
|
63
64
|
|
|
64
|
-
def reindex_datetime(data, report=False
|
|
65
|
+
def reindex_datetime(data, report=False):
|
|
65
66
|
"""
|
|
66
67
|
Find dataframe index frequency and reindex to add any missing intervals.
|
|
67
68
|
|
|
@@ -86,10 +87,6 @@ def reindex_datetime(data, report=False, add_index_col=True):
|
|
|
86
87
|
df_index_length = df.shape[0]
|
|
87
88
|
missing_intervals = df_index_length - data_index_length
|
|
88
89
|
|
|
89
|
-
if add_index_col:
|
|
90
|
-
ix_ser = df.index.to_series()
|
|
91
|
-
df['index'] = ix_ser.apply(lambda x: x.strftime('%m/%d/%Y %H %M'))
|
|
92
|
-
|
|
93
90
|
if report:
|
|
94
91
|
print('Frequency determined to be ' + freq_str + ' minutes.')
|
|
95
92
|
print('{:,} intervals added to index.'.format(missing_intervals))
|
|
@@ -136,3 +133,14 @@ def generate_irr_distribution(
|
|
|
136
133
|
else:
|
|
137
134
|
irr_values.append(next_val)
|
|
138
135
|
return irr_values
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def tags_by_regex(tag_list, regex_str):
|
|
139
|
+
regex = re.compile(regex_str, re.IGNORECASE)
|
|
140
|
+
return [tag for tag in tag_list if regex.search(tag) is not None]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def append_tags(sel_tags, tags, regex_str):
|
|
144
|
+
new_list = sel_tags.copy()
|
|
145
|
+
new_list.extend(tags_by_regex(tags, regex_str))
|
|
146
|
+
return new_list
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: captest
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Framework and methods to facilitate photovoltaic facility capacity testing following ASTM E2848.
|
|
5
5
|
Home-page: http://github.com/bt-/pvcaptest
|
|
6
6
|
Author: Ben Taylor
|
|
@@ -14,62 +14,63 @@ Classifier: Intended Audience :: Science/Research
|
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Topic :: Scientific/Engineering
|
|
17
|
-
Requires-Python: >=3.
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/x-rst
|
|
18
19
|
License-File: LICENSE.txt
|
|
19
|
-
Requires-Dist: pandas
|
|
20
|
-
Requires-Dist: numpy
|
|
21
|
-
Requires-Dist: python-dateutil
|
|
22
|
-
Requires-Dist: matplotlib
|
|
23
|
-
Requires-Dist: statsmodels
|
|
24
|
-
Requires-Dist: scikit-learn
|
|
25
|
-
Requires-Dist: bokeh
|
|
20
|
+
Requires-Dist: pandas >=1
|
|
21
|
+
Requires-Dist: numpy >=1.13.0
|
|
22
|
+
Requires-Dist: python-dateutil >=2.5
|
|
23
|
+
Requires-Dist: matplotlib >=2
|
|
24
|
+
Requires-Dist: statsmodels >=0.8
|
|
25
|
+
Requires-Dist: scikit-learn >=0.19
|
|
26
|
+
Requires-Dist: bokeh >=3.0.0
|
|
26
27
|
Requires-Dist: colorcet
|
|
27
28
|
Requires-Dist: param
|
|
28
29
|
Provides-Extra: all
|
|
29
30
|
Requires-Dist: build ; extra == 'all'
|
|
30
31
|
Requires-Dist: coveralls ; extra == 'all'
|
|
31
|
-
Requires-Dist: docutils ; extra == 'all'
|
|
32
|
-
Requires-Dist: holoviews
|
|
33
|
-
Requires-Dist: nbsphinx ; extra == 'all'
|
|
32
|
+
Requires-Dist: docutils ==0.18.1 ; extra == 'all'
|
|
33
|
+
Requires-Dist: holoviews >=1.14.8 ; extra == 'all'
|
|
34
|
+
Requires-Dist: nbsphinx ==0.9.1 ; extra == 'all'
|
|
34
35
|
Requires-Dist: notebook ; extra == 'all'
|
|
35
36
|
Requires-Dist: openpyxl ; extra == 'all'
|
|
36
37
|
Requires-Dist: panel ; extra == 'all'
|
|
37
|
-
Requires-Dist: pvlib
|
|
38
|
+
Requires-Dist: pvlib >=0.9.0 ; extra == 'all'
|
|
38
39
|
Requires-Dist: pytest ; extra == 'all'
|
|
39
40
|
Requires-Dist: pytest-cov ; extra == 'all'
|
|
40
41
|
Requires-Dist: pytest-mock ; extra == 'all'
|
|
41
42
|
Requires-Dist: pytest-timeout ; extra == 'all'
|
|
42
|
-
Requires-Dist: recommonmark ; extra == 'all'
|
|
43
|
-
Requires-Dist: sphinx ; extra == 'all'
|
|
44
|
-
Requires-Dist: sphinx-rtd-theme ; extra == 'all'
|
|
43
|
+
Requires-Dist: recommonmark ==0.7.1 ; extra == 'all'
|
|
44
|
+
Requires-Dist: sphinx ==6.1.3 ; extra == 'all'
|
|
45
|
+
Requires-Dist: sphinx-rtd-theme ==1.2.0 ; extra == 'all'
|
|
45
46
|
Requires-Dist: twine ; extra == 'all'
|
|
46
47
|
Provides-Extra: build
|
|
47
|
-
Requires-Dist: holoviews
|
|
48
|
+
Requires-Dist: holoviews >=1.14.8 ; extra == 'build'
|
|
48
49
|
Requires-Dist: panel ; extra == 'build'
|
|
49
|
-
Requires-Dist: pvlib
|
|
50
|
+
Requires-Dist: pvlib >=0.9.0 ; extra == 'build'
|
|
50
51
|
Requires-Dist: openpyxl ; extra == 'build'
|
|
51
52
|
Requires-Dist: build ; extra == 'build'
|
|
52
53
|
Requires-Dist: twine ; extra == 'build'
|
|
53
54
|
Provides-Extra: docs
|
|
54
|
-
Requires-Dist: holoviews
|
|
55
|
+
Requires-Dist: holoviews >=1.14.8 ; extra == 'docs'
|
|
55
56
|
Requires-Dist: panel ; extra == 'docs'
|
|
56
|
-
Requires-Dist: pvlib
|
|
57
|
+
Requires-Dist: pvlib >=0.9.0 ; extra == 'docs'
|
|
57
58
|
Requires-Dist: openpyxl ; extra == 'docs'
|
|
58
|
-
Requires-Dist: docutils ; extra == 'docs'
|
|
59
|
-
Requires-Dist: nbsphinx ; extra == 'docs'
|
|
59
|
+
Requires-Dist: docutils ==0.18.1 ; extra == 'docs'
|
|
60
|
+
Requires-Dist: nbsphinx ==0.9.1 ; extra == 'docs'
|
|
60
61
|
Requires-Dist: notebook ; extra == 'docs'
|
|
61
|
-
Requires-Dist: recommonmark ; extra == 'docs'
|
|
62
|
-
Requires-Dist: sphinx ; extra == 'docs'
|
|
63
|
-
Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
|
|
62
|
+
Requires-Dist: recommonmark ==0.7.1 ; extra == 'docs'
|
|
63
|
+
Requires-Dist: sphinx ==6.1.3 ; extra == 'docs'
|
|
64
|
+
Requires-Dist: sphinx-rtd-theme ==1.2.0 ; extra == 'docs'
|
|
64
65
|
Provides-Extra: optional
|
|
65
|
-
Requires-Dist: holoviews
|
|
66
|
+
Requires-Dist: holoviews >=1.14.8 ; extra == 'optional'
|
|
66
67
|
Requires-Dist: panel ; extra == 'optional'
|
|
67
|
-
Requires-Dist: pvlib
|
|
68
|
+
Requires-Dist: pvlib >=0.9.0 ; extra == 'optional'
|
|
68
69
|
Requires-Dist: openpyxl ; extra == 'optional'
|
|
69
70
|
Provides-Extra: test
|
|
70
|
-
Requires-Dist: holoviews
|
|
71
|
+
Requires-Dist: holoviews >=1.14.8 ; extra == 'test'
|
|
71
72
|
Requires-Dist: panel ; extra == 'test'
|
|
72
|
-
Requires-Dist: pvlib
|
|
73
|
+
Requires-Dist: pvlib >=0.9.0 ; extra == 'test'
|
|
73
74
|
Requires-Dist: openpyxl ; extra == 'test'
|
|
74
75
|
Requires-Dist: coveralls ; extra == 'test'
|
|
75
76
|
Requires-Dist: pytest ; extra == 'test'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
captest/__init__.py,sha256=7w9C1xdZ6UMK3jfcKDnCRkXZnedtlGABn3ngNzhQlQw,342
|
|
2
|
+
captest/_version.py,sha256=oNU-WceITboDQ0FEQhD-GzRVxVxo67ipe-DaUEsR8qc,498
|
|
3
|
+
captest/capdata.py,sha256=_5EnJ793VB738NKQsNFZJSm0nxlnlH6z7jRgB4rChH8,119972
|
|
4
|
+
captest/columngroups.py,sha256=dlTBuLAcZg-kJTIRIgbmAZ6BewteNzG4q5jZMlq31Wo,4475
|
|
5
|
+
captest/io.py,sha256=HBH2JFZJvdaYvg1aQQs50HBDlbG9_-D57BGMVp0T1ag,21555
|
|
6
|
+
captest/plotting.py,sha256=EoNCZTdARRbM0YFE93yphGzRG7vJv3gK6dEAMnbCnGI,15889
|
|
7
|
+
captest/prtest.py,sha256=hhqF0tqsW-mB12nQYi8QmGH0G-aBVbYpTQP_BEZJFIA,13835
|
|
8
|
+
captest/util.py,sha256=QVbmxs_rjHtaMd3oQQURR5_0fd9spmdA9UsUqjQrmEM,4008
|
|
9
|
+
captest-0.13.0.dist-info/LICENSE.txt,sha256=PZDXVBJdcB8_m9vxoYiQZSzTuzm0YjikdhtvRwfCSMs,1067
|
|
10
|
+
captest-0.13.0.dist-info/METADATA,sha256=q_CMRt89l0VM6ECt7WTdFDM2Og0uIxD8ab5oeKjUxsU,7351
|
|
11
|
+
captest-0.13.0.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
|
|
12
|
+
captest-0.13.0.dist-info/top_level.txt,sha256=3krgWr0q1Z09eRlfWOGNNx1mXBaWvBvJF26xDWtmznc,8
|
|
13
|
+
captest-0.13.0.dist-info/RECORD,,
|
captest-0.11.2.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
captest/__init__.py,sha256=c3qqcIKR7CLthm9V77_iWT-DnUaUa6698MT9x6Arvwg,328
|
|
2
|
-
captest/_version.py,sha256=7WG_5WJbuiBMs04Zp05zNQ1VrDun166bmrX-Ee1CtJM,498
|
|
3
|
-
captest/capdata.py,sha256=IQAJBZaOb0wbvbSVc1TStFsLrQ8XK35hCYU_bfZgAoo,123504
|
|
4
|
-
captest/columngroups.py,sha256=dlTBuLAcZg-kJTIRIgbmAZ6BewteNzG4q5jZMlq31Wo,4475
|
|
5
|
-
captest/io.py,sha256=ux1t9a-I9w9sWew-zsfuQE01goZlHfyh7Xyk5E0fjec,17292
|
|
6
|
-
captest/prtest.py,sha256=8lTEeZH97OmzUfoXuD3QNazsR_KXMlzUgHg_IDgALSY,13833
|
|
7
|
-
captest/util.py,sha256=jQf0fkC4dcdvcSa-TOexG90R3qmFpaaEMPNdR3MhCjg,3843
|
|
8
|
-
captest-0.11.2.dist-info/LICENSE.txt,sha256=PZDXVBJdcB8_m9vxoYiQZSzTuzm0YjikdhtvRwfCSMs,1067
|
|
9
|
-
captest-0.11.2.dist-info/METADATA,sha256=0Xq04J6n6ct7ItWoOL1euONQDBK5UtKU-zqxcN0JF8c,7262
|
|
10
|
-
captest-0.11.2.dist-info/WHEEL,sha256=a-zpFRIJzOq5QfuhBzbhiA1eHTzNCJn8OdRvhdNX0Rk,110
|
|
11
|
-
captest-0.11.2.dist-info/top_level.txt,sha256=3krgWr0q1Z09eRlfWOGNNx1mXBaWvBvJF26xDWtmznc,8
|
|
12
|
-
captest-0.11.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|