captest 0.12.1__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 +232 -314
- captest/io.py +65 -23
- captest/plotting.py +492 -0
- captest/util.py +13 -5
- {captest-0.12.1.dist-info → captest-0.13.0.dist-info}/METADATA +1 -1
- captest-0.13.0.dist-info/RECORD +13 -0
- {captest-0.12.1.dist-info → captest-0.13.0.dist-info}/WHEEL +1 -1
- captest-0.12.1.dist-info/RECORD +0 -12
- {captest-0.12.1.dist-info → captest-0.13.0.dist-info}/LICENSE.txt +0 -0
- {captest-0.12.1.dist-info → captest-0.13.0.dist-info}/top_level.txt +0 -0
captest/capdata.py
CHANGED
|
@@ -40,7 +40,7 @@ from bokeh.io import show
|
|
|
40
40
|
from bokeh.plotting import figure
|
|
41
41
|
from bokeh.palettes import Category10
|
|
42
42
|
from bokeh.layouts import gridplot
|
|
43
|
-
from bokeh.models import Legend, HoverTool, ColumnDataSource
|
|
43
|
+
from bokeh.models import Legend, HoverTool, ColumnDataSource, NumeralTickFormatter
|
|
44
44
|
|
|
45
45
|
import param
|
|
46
46
|
|
|
@@ -87,6 +87,7 @@ else:
|
|
|
87
87
|
'pvlib package.')
|
|
88
88
|
|
|
89
89
|
from captest import util
|
|
90
|
+
from captest import plotting
|
|
90
91
|
|
|
91
92
|
plot_colors_brewer = {'real_pwr': ['#2b8cbe', '#7bccc4', '#bae4bc', '#f0f9e8'],
|
|
92
93
|
'irr_poa': ['#e31a1c', '#fd8d3c', '#fecc5c', '#ffffb2'],
|
|
@@ -423,7 +424,34 @@ def check_all_perc_diff_comb(series, perc_diff):
|
|
|
423
424
|
return all([perc_difference(x, y) < perc_diff for x, y in c])
|
|
424
425
|
|
|
425
426
|
|
|
426
|
-
def
|
|
427
|
+
def abs_diff_from_average(series, threshold):
|
|
428
|
+
"""Check each value in series <= average of other values.
|
|
429
|
+
|
|
430
|
+
Drops NaNs from series before calculating difference from average for each value.
|
|
431
|
+
|
|
432
|
+
Returns True if there is only one value in the series.
|
|
433
|
+
|
|
434
|
+
Parameters
|
|
435
|
+
----------
|
|
436
|
+
series : pd.Series
|
|
437
|
+
Pandas series of values to check.
|
|
438
|
+
threshold : numeric
|
|
439
|
+
Threshold value for absolute difference from average.
|
|
440
|
+
|
|
441
|
+
Returns
|
|
442
|
+
-------
|
|
443
|
+
bool
|
|
444
|
+
"""
|
|
445
|
+
series = series.dropna()
|
|
446
|
+
if len(series) == 1:
|
|
447
|
+
return True
|
|
448
|
+
abs_diffs = []
|
|
449
|
+
for i, val in enumerate(series):
|
|
450
|
+
abs_diffs.append(abs(val - series.drop(series.index[i]).mean()) <= threshold)
|
|
451
|
+
return all(abs_diffs)
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def sensor_filter(df, threshold, row_filter=check_all_perc_diff_comb):
|
|
427
455
|
"""
|
|
428
456
|
Check dataframe for rows with inconsistent values.
|
|
429
457
|
|
|
@@ -436,8 +464,7 @@ def sensor_filter(df, perc_diff):
|
|
|
436
464
|
Percent difference as decimal.
|
|
437
465
|
"""
|
|
438
466
|
if df.shape[1] >= 2:
|
|
439
|
-
bool_ser = df.apply(
|
|
440
|
-
axis=1)
|
|
467
|
+
bool_ser = df.apply(row_filter, args=(threshold, ), axis=1)
|
|
441
468
|
return df[bool_ser].index
|
|
442
469
|
elif df.shape[1] == 1:
|
|
443
470
|
return df.index
|
|
@@ -1123,7 +1150,7 @@ def determine_pass_or_fail(cap_ratio, tolerance, nameplate):
|
|
|
1123
1150
|
Limits for passing and failing test.
|
|
1124
1151
|
"""
|
|
1125
1152
|
sign = tolerance.split(sep=' ')[0]
|
|
1126
|
-
error =
|
|
1153
|
+
error = float(tolerance.split(sep=' ')[1]) / 100
|
|
1127
1154
|
|
|
1128
1155
|
nameplate_plus_error = nameplate * (1 + error)
|
|
1129
1156
|
nameplate_minus_error = nameplate * (1 - error)
|
|
@@ -1363,17 +1390,60 @@ def overlay_scatters(measured, expected, expected_label='PVsyst'):
|
|
|
1363
1390
|
|
|
1364
1391
|
|
|
1365
1392
|
def index_capdata(capdata, label, filtered=True):
|
|
1393
|
+
"""
|
|
1394
|
+
Like Dataframe.loc but for CapData objects.
|
|
1395
|
+
|
|
1396
|
+
Pass a single label or list of labels to select the columns from the `data` or
|
|
1397
|
+
`data_filtered` DataFrames. The label can be a column name, a column group key, or
|
|
1398
|
+
a regression column key.
|
|
1399
|
+
|
|
1400
|
+
The special label `regcols` will return the columns identified in `regression_cols`.
|
|
1401
|
+
|
|
1402
|
+
Parameters
|
|
1403
|
+
----------
|
|
1404
|
+
capdata : CapData
|
|
1405
|
+
The CapData object to select from.
|
|
1406
|
+
label : str or list
|
|
1407
|
+
The label or list of labels to select from the `data` or `data_filtered`
|
|
1408
|
+
DataFrames. The label can be a column name, a column group key, or a
|
|
1409
|
+
regression column key. The special label `regcols` will return the columns
|
|
1410
|
+
identified in `regression_cols`.
|
|
1411
|
+
filtered : bool, default True
|
|
1412
|
+
By default the method will return columns from the `data_filtered` DataFrame.
|
|
1413
|
+
Set to False to return columns from the `data` DataFrame.
|
|
1414
|
+
|
|
1415
|
+
Returns
|
|
1416
|
+
--------
|
|
1417
|
+
DataFrame
|
|
1418
|
+
"""
|
|
1366
1419
|
if filtered:
|
|
1367
1420
|
data = capdata.data_filtered
|
|
1368
1421
|
else:
|
|
1369
1422
|
data = capdata.data
|
|
1423
|
+
if label == 'regcols':
|
|
1424
|
+
label = list(capdata.regression_cols.values())
|
|
1370
1425
|
if isinstance(label, str):
|
|
1371
1426
|
if label in capdata.column_groups.keys():
|
|
1372
|
-
|
|
1427
|
+
selected_data = data[capdata.column_groups[label]]
|
|
1373
1428
|
elif label in capdata.regression_cols.keys():
|
|
1374
|
-
|
|
1429
|
+
col_or_grp = capdata.regression_cols[label]
|
|
1430
|
+
if col_or_grp in capdata.column_groups.keys():
|
|
1431
|
+
selected_data = data[capdata.column_groups[col_or_grp]]
|
|
1432
|
+
elif col_or_grp in data.columns:
|
|
1433
|
+
selected_data = data[col_or_grp]
|
|
1434
|
+
else:
|
|
1435
|
+
warnings.warn(
|
|
1436
|
+
'Group or column "{}" mapped to the "{}" key of regression_cols '
|
|
1437
|
+
'not found in column_groups keys or columns of CapData.data'.format(
|
|
1438
|
+
col_or_grp, label
|
|
1439
|
+
)
|
|
1440
|
+
)
|
|
1375
1441
|
elif label in data.columns:
|
|
1376
|
-
|
|
1442
|
+
selected_data = data.loc[:, label]
|
|
1443
|
+
if isinstance(selected_data, pd.Series):
|
|
1444
|
+
return selected_data.to_frame()
|
|
1445
|
+
else:
|
|
1446
|
+
return selected_data
|
|
1377
1447
|
elif isinstance(label, list):
|
|
1378
1448
|
cols_to_return = []
|
|
1379
1449
|
for l in label:
|
|
@@ -1420,15 +1490,15 @@ class FilteredLocIndexer(object):
|
|
|
1420
1490
|
|
|
1421
1491
|
class CapData(object):
|
|
1422
1492
|
"""
|
|
1423
|
-
Class to store capacity test data and
|
|
1493
|
+
Class to store capacity test data and column grouping.
|
|
1424
1494
|
|
|
1425
1495
|
CapData objects store a pandas dataframe of measured or simulated data
|
|
1426
|
-
and a dictionary
|
|
1496
|
+
and a dictionary grouping columns by type of measurement.
|
|
1427
1497
|
|
|
1428
1498
|
The `column_groups` dictionary allows maintaining the original column names
|
|
1429
1499
|
while also grouping measurements of the same type from different
|
|
1430
1500
|
sensors. Many of the methods for plotting and filtering data rely on the
|
|
1431
|
-
column groupings
|
|
1501
|
+
column groupings.
|
|
1432
1502
|
|
|
1433
1503
|
Parameters
|
|
1434
1504
|
----------
|
|
@@ -1446,18 +1516,11 @@ class CapData(object):
|
|
|
1446
1516
|
`group_columns` creates an abbreviated name and a list of columns that
|
|
1447
1517
|
contain measurements of that type. The abbreviated names are the keys
|
|
1448
1518
|
and the corresponding values are the lists of columns.
|
|
1449
|
-
trans_keys : list
|
|
1450
|
-
Simply a list of the `column_groups` keys.
|
|
1451
1519
|
regression_cols : dictionary
|
|
1452
1520
|
Dictionary identifying which columns in `data` or groups of columns as
|
|
1453
1521
|
identified by the keys of `column_groups` are the independent variables
|
|
1454
1522
|
of the ASTM Capacity test regression equation. Set using
|
|
1455
1523
|
`set_regression_cols` or by directly assigning a dictionary.
|
|
1456
|
-
trans_abrev : dictionary
|
|
1457
|
-
Enumerated translation dict keys mapped to original column names.
|
|
1458
|
-
Enumerated translation dict keys are used in plot hover tooltip.
|
|
1459
|
-
col_colors : dictionary
|
|
1460
|
-
Original column names mapped to a color for use in plot function.
|
|
1461
1524
|
summary_ix : list of tuples
|
|
1462
1525
|
Holds the row index data modified by the update_summary decorator
|
|
1463
1526
|
function.
|
|
@@ -1482,10 +1545,7 @@ class CapData(object):
|
|
|
1482
1545
|
self.data = pd.DataFrame()
|
|
1483
1546
|
self.data_filtered = None
|
|
1484
1547
|
self.column_groups = {}
|
|
1485
|
-
self.trans_keys = []
|
|
1486
1548
|
self.regression_cols = {}
|
|
1487
|
-
self.trans_abrev = {}
|
|
1488
|
-
self.col_colors = {}
|
|
1489
1549
|
self.summary_ix = []
|
|
1490
1550
|
self.summary = []
|
|
1491
1551
|
self.removed = []
|
|
@@ -1493,8 +1553,9 @@ class CapData(object):
|
|
|
1493
1553
|
self.filter_counts = {}
|
|
1494
1554
|
self.rc = None
|
|
1495
1555
|
self.regression_results = None
|
|
1496
|
-
self.regression_formula = (
|
|
1497
|
-
|
|
1556
|
+
self.regression_formula = (
|
|
1557
|
+
'power ~ poa + I(poa * poa) + I(poa * t_amb) + I(poa * w_vel) - 1'
|
|
1558
|
+
)
|
|
1498
1559
|
self.tolerance = None
|
|
1499
1560
|
self.pre_agg_cols = None
|
|
1500
1561
|
self.pre_agg_trans = None
|
|
@@ -1535,11 +1596,7 @@ class CapData(object):
|
|
|
1535
1596
|
cd_c.data = self.data.copy()
|
|
1536
1597
|
cd_c.data_filtered = self.data_filtered.copy()
|
|
1537
1598
|
cd_c.column_groups = copy.copy(self.column_groups)
|
|
1538
|
-
cd_c.trans_keys = copy.copy(self.trans_keys)
|
|
1539
1599
|
cd_c.regression_cols = copy.copy(self.regression_cols)
|
|
1540
|
-
cd_c.trans_abrev = copy.copy(self.trans_abrev)
|
|
1541
|
-
cd_c.col_colors = copy.copy(self.col_colors)
|
|
1542
|
-
cd_c.col_colors = copy.copy(self.col_colors)
|
|
1543
1600
|
cd_c.summary_ix = copy.copy(self.summary_ix)
|
|
1544
1601
|
cd_c.summary = copy.copy(self.summary)
|
|
1545
1602
|
cd_c.rc = copy.copy(self.rc)
|
|
@@ -1552,37 +1609,9 @@ class CapData(object):
|
|
|
1552
1609
|
|
|
1553
1610
|
def empty(self):
|
|
1554
1611
|
"""Return a boolean indicating if the CapData object contains data."""
|
|
1555
|
-
tests_indicating_empty = [self.data.empty, len(self.
|
|
1556
|
-
len(self.column_groups) == 0]
|
|
1612
|
+
tests_indicating_empty = [self.data.empty, len(self.column_groups) == 0]
|
|
1557
1613
|
return all(tests_indicating_empty)
|
|
1558
1614
|
|
|
1559
|
-
def set_plot_attributes(self):
|
|
1560
|
-
"""Set column colors used in plot method."""
|
|
1561
|
-
# dframe = self.data
|
|
1562
|
-
|
|
1563
|
-
group_id_regex = {
|
|
1564
|
-
'real_pwr': re.compile(r'real_pwr|pwr|meter_power|active_pwr|active_power', re.IGNORECASE),
|
|
1565
|
-
'irr_poa': re.compile(r'poa|irr_poa|poa_irr', re.IGNORECASE),
|
|
1566
|
-
'irr_ghi': re.compile(r'ghi|irr_ghi|ghi_irr', re.IGNORECASE),
|
|
1567
|
-
'temp_amb': re.compile(r'amb|temp.*amb', re.IGNORECASE),
|
|
1568
|
-
'temp_mod': re.compile(r'bom|temp.*bom|module.*temp.*|temp.*mod.*', re.IGNORECASE),
|
|
1569
|
-
'wind': re.compile(r'wind|w_vel|wspd|wind__', re.IGNORECASE),
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
for group_id, cols_in_group in self.column_groups.items():
|
|
1573
|
-
col_key = None
|
|
1574
|
-
for plot_colors_group_key, regex in group_id_regex.items():
|
|
1575
|
-
if regex.match(group_id):
|
|
1576
|
-
col_key = plot_colors_group_key
|
|
1577
|
-
break
|
|
1578
|
-
for i, col in enumerate(cols_in_group):
|
|
1579
|
-
try:
|
|
1580
|
-
j = i % 4
|
|
1581
|
-
self.col_colors[col] = plot_colors_brewer[col_key][j]
|
|
1582
|
-
except KeyError:
|
|
1583
|
-
j = i % 256
|
|
1584
|
-
self.col_colors[col] = cc.glasbey_dark[j]
|
|
1585
|
-
|
|
1586
1615
|
def drop_cols(self, columns):
|
|
1587
1616
|
"""
|
|
1588
1617
|
Drop columns from CapData `data` and `column_groups`.
|
|
@@ -1625,7 +1654,10 @@ class CapData(object):
|
|
|
1625
1654
|
"""
|
|
1626
1655
|
if reg_vars is None:
|
|
1627
1656
|
reg_vars = list(self.regression_cols.keys())
|
|
1628
|
-
|
|
1657
|
+
if filtered_data:
|
|
1658
|
+
df = self.floc[reg_vars].copy()
|
|
1659
|
+
else:
|
|
1660
|
+
df = self.loc[reg_vars].copy()
|
|
1629
1661
|
rename = {df.columns[0]: reg_vars}
|
|
1630
1662
|
|
|
1631
1663
|
if isinstance(reg_vars, list):
|
|
@@ -1643,79 +1675,6 @@ class CapData(object):
|
|
|
1643
1675
|
df.rename(columns=rename, inplace=True)
|
|
1644
1676
|
return df
|
|
1645
1677
|
|
|
1646
|
-
def view(self, tkey, filtered_data=False):
|
|
1647
|
-
"""
|
|
1648
|
-
Convience function returns columns using `column_groups` names.
|
|
1649
|
-
|
|
1650
|
-
Parameters
|
|
1651
|
-
----------
|
|
1652
|
-
tkey: int or str or list of int or strs
|
|
1653
|
-
String or list of strings from self.trans_keys or int postion or
|
|
1654
|
-
list of int postitions of value in self.trans_keys.
|
|
1655
|
-
"""
|
|
1656
|
-
if isinstance(tkey, int):
|
|
1657
|
-
keys = self.column_groups[self.trans_keys[tkey]]
|
|
1658
|
-
elif isinstance(tkey, list) and len(tkey) > 1:
|
|
1659
|
-
keys = []
|
|
1660
|
-
for key in tkey:
|
|
1661
|
-
if isinstance(key, str):
|
|
1662
|
-
keys.extend(self.column_groups[key])
|
|
1663
|
-
elif isinstance(key, int):
|
|
1664
|
-
keys.extend(self.column_groups[self.trans_keys[key]])
|
|
1665
|
-
elif tkey in self.trans_keys:
|
|
1666
|
-
keys = self.column_groups[tkey]
|
|
1667
|
-
|
|
1668
|
-
if filtered_data:
|
|
1669
|
-
return self.data_filtered[keys]
|
|
1670
|
-
else:
|
|
1671
|
-
return self.data[keys]
|
|
1672
|
-
|
|
1673
|
-
def rview(self, ind_var, filtered_data=False):
|
|
1674
|
-
"""
|
|
1675
|
-
Convience fucntion to return regression independent variable.
|
|
1676
|
-
|
|
1677
|
-
Parameters
|
|
1678
|
-
----------
|
|
1679
|
-
ind_var: string or list of strings
|
|
1680
|
-
may be 'power', 'poa', 't_amb', 'w_vel', a list of some subset of
|
|
1681
|
-
the previous four strings or 'all'
|
|
1682
|
-
"""
|
|
1683
|
-
if ind_var == 'all':
|
|
1684
|
-
keys = list(self.regression_cols.values())
|
|
1685
|
-
elif isinstance(ind_var, list) and len(ind_var) > 1:
|
|
1686
|
-
keys = [self.regression_cols[key] for key in ind_var]
|
|
1687
|
-
elif ind_var in met_keys:
|
|
1688
|
-
ind_var = [ind_var]
|
|
1689
|
-
keys = [self.regression_cols[key] for key in ind_var]
|
|
1690
|
-
|
|
1691
|
-
lst = []
|
|
1692
|
-
for key in keys:
|
|
1693
|
-
if key in self.data.columns:
|
|
1694
|
-
lst.extend([key])
|
|
1695
|
-
else:
|
|
1696
|
-
lst.extend(self.column_groups[key])
|
|
1697
|
-
if filtered_data:
|
|
1698
|
-
return self.data_filtered[lst]
|
|
1699
|
-
else:
|
|
1700
|
-
return self.data[lst]
|
|
1701
|
-
|
|
1702
|
-
def __comb_trans_keys(self, grp):
|
|
1703
|
-
comb_keys = []
|
|
1704
|
-
|
|
1705
|
-
for key in self.trans_keys:
|
|
1706
|
-
if key.find(grp) != -1:
|
|
1707
|
-
comb_keys.append(key)
|
|
1708
|
-
|
|
1709
|
-
cols = []
|
|
1710
|
-
for key in comb_keys:
|
|
1711
|
-
cols.extend(self.column_groups[key])
|
|
1712
|
-
|
|
1713
|
-
grp_comb = grp + '_comb'
|
|
1714
|
-
if grp_comb not in self.trans_keys:
|
|
1715
|
-
self.column_groups[grp_comb] = cols
|
|
1716
|
-
self.trans_keys.extend([grp_comb])
|
|
1717
|
-
print('Added new group: ' + grp_comb)
|
|
1718
|
-
|
|
1719
1678
|
def review_column_groups(self):
|
|
1720
1679
|
"""Print `column_groups` with nice formatting."""
|
|
1721
1680
|
if len(self.column_groups) == 0:
|
|
@@ -1746,9 +1705,9 @@ class CapData(object):
|
|
|
1746
1705
|
Plots filtered data when true and all data when false.
|
|
1747
1706
|
"""
|
|
1748
1707
|
if filtered:
|
|
1749
|
-
df = self.
|
|
1708
|
+
df = self.floc[['power', 'poa']]
|
|
1750
1709
|
else:
|
|
1751
|
-
df = self.
|
|
1710
|
+
df = self.loc[['power', 'poa']]
|
|
1752
1711
|
|
|
1753
1712
|
if df.shape[1] != 2:
|
|
1754
1713
|
return warnings.warn('Aggregate sensors before using this '
|
|
@@ -1785,175 +1744,109 @@ class CapData(object):
|
|
|
1785
1744
|
vdims = ['power', 'index']
|
|
1786
1745
|
if all_reg_columns:
|
|
1787
1746
|
vdims.extend(list(df.columns.difference(vdims)))
|
|
1747
|
+
hover = HoverTool(
|
|
1748
|
+
tooltips=[
|
|
1749
|
+
('datetime', '@index{%Y-%m-%d %H:%M}'),
|
|
1750
|
+
('poa', '@poa{0,0.0}'),
|
|
1751
|
+
('power', '@power{0,0.0}'),
|
|
1752
|
+
],
|
|
1753
|
+
formatters={
|
|
1754
|
+
'@index': 'datetime',
|
|
1755
|
+
}
|
|
1756
|
+
)
|
|
1788
1757
|
poa_vs_kw = hv.Scatter(df, 'poa', vdims).opts(
|
|
1789
1758
|
size=5,
|
|
1790
|
-
tools=[
|
|
1759
|
+
tools=[hover, 'lasso_select', 'box_select'],
|
|
1791
1760
|
legend_position='right',
|
|
1792
1761
|
height=400,
|
|
1793
1762
|
width=400,
|
|
1763
|
+
selection_fill_color='red',
|
|
1764
|
+
selection_line_color='red',
|
|
1765
|
+
yformatter=NumeralTickFormatter(format='0,0'),
|
|
1794
1766
|
)
|
|
1795
1767
|
# layout_scatter = (poa_vs_kw).opts(opt_dict)
|
|
1796
1768
|
if timeseries:
|
|
1797
|
-
|
|
1798
|
-
tools=[
|
|
1769
|
+
power_vs_time = hv.Scatter(df, 'index', ['power', 'poa']).opts(
|
|
1770
|
+
tools=[hover, 'lasso_select', 'box_select'],
|
|
1799
1771
|
height=400,
|
|
1800
1772
|
width=800,
|
|
1773
|
+
selection_fill_color='red',
|
|
1774
|
+
selection_line_color='red',
|
|
1801
1775
|
)
|
|
1802
|
-
|
|
1803
|
-
|
|
1776
|
+
power_col, poa_col = self.loc[['power', 'poa']].columns
|
|
1777
|
+
power_vs_time_underlay = hv.Curve(
|
|
1778
|
+
self.data.rename_axis('index', axis='index'),
|
|
1779
|
+
'index',
|
|
1780
|
+
[power_col, poa_col],
|
|
1781
|
+
).opts(
|
|
1782
|
+
tools=['lasso_select', 'box_select'],
|
|
1783
|
+
height=400,
|
|
1784
|
+
width=800,
|
|
1785
|
+
line_color='gray',
|
|
1786
|
+
line_width=1,
|
|
1787
|
+
line_alpha=0.4,
|
|
1788
|
+
yformatter=NumeralTickFormatter(format='0,0'),
|
|
1789
|
+
)
|
|
1790
|
+
layout_timeseries = (poa_vs_kw + power_vs_time * power_vs_time_underlay)
|
|
1791
|
+
DataLink(poa_vs_kw, power_vs_time)
|
|
1804
1792
|
return(layout_timeseries.cols(1))
|
|
1805
1793
|
else:
|
|
1806
1794
|
return(poa_vs_kw)
|
|
1807
1795
|
|
|
1808
|
-
def plot(
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1796
|
+
def plot(
|
|
1797
|
+
self,
|
|
1798
|
+
combine=plotting.COMBINE,
|
|
1799
|
+
default_groups=plotting.DEFAULT_GROUPS,
|
|
1800
|
+
width=1500,
|
|
1801
|
+
height=250,
|
|
1802
|
+
**kwargs,
|
|
1803
|
+
):
|
|
1804
|
+
"""
|
|
1805
|
+
Create a dashboard to explore timeseries plots of the data.
|
|
1806
|
+
|
|
1807
|
+
The dashboard contains three tabs: Groups, Layout, and Overlay. The first tab,
|
|
1808
|
+
Groups, presents a column of plots with a separate plot overlaying the measurements
|
|
1809
|
+
for each group of the `column_groups`. The groups plotted are defined by the
|
|
1810
|
+
`default_groups` argument.
|
|
1811
|
+
|
|
1812
|
+
The second tab, Layout, allows manually selecting groups to plot. The button
|
|
1813
|
+
on this tab can be used to replace the column of plots on the Groups tab with
|
|
1814
|
+
the current figure on the Layout tab. Rerun this method after clicking the button
|
|
1815
|
+
to see the new plots in the Groups tab.
|
|
1816
|
+
|
|
1817
|
+
The third tab, Overlay, allows picking a group or any combination of individual
|
|
1818
|
+
tags to overlay on a single plot. The list of groups and tags can be filtered
|
|
1819
|
+
using regular expressions. Adding a text id in the box and clicking Update will
|
|
1820
|
+
add the current overlay to the list of groups on the Layout tab.
|
|
1824
1821
|
|
|
1825
1822
|
Parameters
|
|
1826
1823
|
----------
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
A new entry is added to `column_groups` with keys following the
|
|
1841
|
-
format 'search str_comb' and the value is a list of column names
|
|
1842
|
-
that contain the search string. The default will combine all
|
|
1843
|
-
irradiance measurements into a group and temperature measurements
|
|
1844
|
-
into a group.
|
|
1845
|
-
|
|
1846
|
-
Pass an empty list to not merge any plots.
|
|
1847
|
-
|
|
1848
|
-
Use 'irr-poa' and 'irr-ghi' to plot clear sky modeled with measured
|
|
1849
|
-
data.
|
|
1850
|
-
subset : list, default None
|
|
1851
|
-
List of the keys of `column_groups` to control the order of to plot
|
|
1852
|
-
only a subset of the plots or control the order of plots.
|
|
1853
|
-
filtered : bool, default False
|
|
1854
|
-
Set to true to plot the filtered data.
|
|
1855
|
-
kwargs
|
|
1856
|
-
Pass additional options to bokeh gridplot. Merge_tools=False will
|
|
1857
|
-
shows the hover tool icon, so it can be turned off.
|
|
1824
|
+
combine : dict, optional
|
|
1825
|
+
Dictionary of group names and regex strings to use to identify groups from
|
|
1826
|
+
column groups and individual tags (columns) to combine into new groups. See the
|
|
1827
|
+
`parse_combine` function for more details.
|
|
1828
|
+
default_groups : list of str, optional
|
|
1829
|
+
List of regex strings to use to identify default groups to plot. See the
|
|
1830
|
+
`plotting.find_default_groups` function for more details.
|
|
1831
|
+
group_width : int, optional
|
|
1832
|
+
The width of the plots on the Groups tab.
|
|
1833
|
+
group_height : int, optional
|
|
1834
|
+
The height of the plots on the Groups tab.
|
|
1835
|
+
**kwargs : optional
|
|
1836
|
+
Additional keyword arguments are passed to the options of the scatter plot.
|
|
1858
1837
|
|
|
1859
1838
|
Returns
|
|
1860
1839
|
-------
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
dframe = self.data
|
|
1872
|
-
dframe.index.name = 'Timestamp'
|
|
1873
|
-
|
|
1874
|
-
names_to_abrev = {val: key for key, val in self.trans_abrev.items()}
|
|
1875
|
-
|
|
1876
|
-
plots = []
|
|
1877
|
-
x_axis = None
|
|
1878
|
-
|
|
1879
|
-
source = ColumnDataSource(dframe)
|
|
1880
|
-
|
|
1881
|
-
hover = HoverTool()
|
|
1882
|
-
hover.tooltips = [
|
|
1883
|
-
("Name", "$name"),
|
|
1884
|
-
("Datetime", "@Timestamp{%F %H:%M}"),
|
|
1885
|
-
("Value", "$y{0,0.00}"),
|
|
1886
|
-
]
|
|
1887
|
-
hover.formatters = {"@Timestamp": "datetime"}
|
|
1888
|
-
|
|
1889
|
-
tools = 'pan, xwheel_pan, xwheel_zoom, box_zoom, save, reset'
|
|
1890
|
-
|
|
1891
|
-
if isinstance(subset, list):
|
|
1892
|
-
plot_keys = subset
|
|
1893
|
-
else:
|
|
1894
|
-
plot_keys = self.trans_keys
|
|
1895
|
-
|
|
1896
|
-
for j, key in enumerate(plot_keys):
|
|
1897
|
-
df = dframe[self.column_groups[key]]
|
|
1898
|
-
cols = df.columns.tolist()
|
|
1899
|
-
|
|
1900
|
-
if x_axis is None:
|
|
1901
|
-
p = figure(title=key, width=width, height=height,
|
|
1902
|
-
x_axis_type='datetime', tools=tools)
|
|
1903
|
-
p.tools.append(hover)
|
|
1904
|
-
x_axis = p.x_range
|
|
1905
|
-
if j > 0:
|
|
1906
|
-
p = figure(title=key, width=width, height=height,
|
|
1907
|
-
x_axis_type='datetime', x_range=x_axis, tools=tools)
|
|
1908
|
-
p.tools.append(hover)
|
|
1909
|
-
legend_items = []
|
|
1910
|
-
for i, col in enumerate(cols):
|
|
1911
|
-
if use_abrev_name:
|
|
1912
|
-
name = names_to_abrev[col]
|
|
1913
|
-
else:
|
|
1914
|
-
name = col
|
|
1915
|
-
|
|
1916
|
-
if col.find('csky') == -1:
|
|
1917
|
-
line_dash = 'solid'
|
|
1918
|
-
else:
|
|
1919
|
-
line_dash = (5, 2)
|
|
1920
|
-
|
|
1921
|
-
if marker == 'line':
|
|
1922
|
-
try:
|
|
1923
|
-
series = p.line('Timestamp', col, source=source,
|
|
1924
|
-
line_color=self.col_colors[col],
|
|
1925
|
-
line_dash=line_dash,
|
|
1926
|
-
name=name)
|
|
1927
|
-
except KeyError:
|
|
1928
|
-
series = p.line('Timestamp', col, source=source,
|
|
1929
|
-
line_dash=line_dash,
|
|
1930
|
-
name=name)
|
|
1931
|
-
elif marker == 'circle':
|
|
1932
|
-
series = p.circle('Timestamp', col,
|
|
1933
|
-
source=source,
|
|
1934
|
-
line_color=self.col_colors[col],
|
|
1935
|
-
size=2, fill_color="white",
|
|
1936
|
-
name=name)
|
|
1937
|
-
if marker == 'line-circle':
|
|
1938
|
-
series = p.line('Timestamp', col, source=source,
|
|
1939
|
-
line_color=self.col_colors[col],
|
|
1940
|
-
name=name)
|
|
1941
|
-
series = p.circle('Timestamp', col,
|
|
1942
|
-
source=source,
|
|
1943
|
-
line_color=self.col_colors[col],
|
|
1944
|
-
size=2, fill_color="white",
|
|
1945
|
-
name=name)
|
|
1946
|
-
legend_items.append((col, [series, ]))
|
|
1947
|
-
|
|
1948
|
-
legend = Legend(items=legend_items, location=(40, -5))
|
|
1949
|
-
legend.label_text_font_size = '8pt'
|
|
1950
|
-
if legends:
|
|
1951
|
-
p.add_layout(legend, 'below')
|
|
1952
|
-
|
|
1953
|
-
plots.append(p)
|
|
1954
|
-
|
|
1955
|
-
grid = gridplot(plots, ncols=ncols, **kwargs)
|
|
1956
|
-
return show(grid)
|
|
1840
|
+
Panel tabbed layout
|
|
1841
|
+
"""
|
|
1842
|
+
return plotting.plot(
|
|
1843
|
+
self,
|
|
1844
|
+
combine=combine,
|
|
1845
|
+
default_groups=default_groups,
|
|
1846
|
+
group_width=width,
|
|
1847
|
+
group_height=height,
|
|
1848
|
+
**kwargs,
|
|
1849
|
+
)
|
|
1957
1850
|
|
|
1958
1851
|
def scatter_filters(self):
|
|
1959
1852
|
"""
|
|
@@ -1966,7 +1859,7 @@ class CapData(object):
|
|
|
1966
1859
|
scatters = []
|
|
1967
1860
|
|
|
1968
1861
|
data = self.get_reg_cols(reg_vars=['power', 'poa'], filtered_data=False)
|
|
1969
|
-
data['index'] = self.data.
|
|
1862
|
+
data['index'] = self.data.index
|
|
1970
1863
|
plt_no_filtering = hv.Scatter(data, 'poa', ['power', 'index']).relabel('all')
|
|
1971
1864
|
scatters.append(plt_no_filtering)
|
|
1972
1865
|
|
|
@@ -1986,6 +1879,16 @@ class CapData(object):
|
|
|
1986
1879
|
scatters.append(plt)
|
|
1987
1880
|
|
|
1988
1881
|
scatter_overlay = hv.Overlay(scatters)
|
|
1882
|
+
hover = HoverTool(
|
|
1883
|
+
tooltips=[
|
|
1884
|
+
('datetime', '@index{%Y-%m-%d %H:%M}'),
|
|
1885
|
+
('poa', '@poa{0,0.0}'),
|
|
1886
|
+
('power', '@power{0,0.0}'),
|
|
1887
|
+
],
|
|
1888
|
+
formatters={
|
|
1889
|
+
'@index': 'datetime',
|
|
1890
|
+
}
|
|
1891
|
+
)
|
|
1989
1892
|
scatter_overlay.opts(
|
|
1990
1893
|
hv.opts.Scatter(
|
|
1991
1894
|
size=5,
|
|
@@ -1994,7 +1897,8 @@ class CapData(object):
|
|
|
1994
1897
|
muted_fill_alpha=0,
|
|
1995
1898
|
fill_alpha=0.4,
|
|
1996
1899
|
line_width=0,
|
|
1997
|
-
tools=[
|
|
1900
|
+
tools=[hover],
|
|
1901
|
+
yformatter=NumeralTickFormatter(format='0,0'),
|
|
1998
1902
|
),
|
|
1999
1903
|
hv.opts.Overlay(
|
|
2000
1904
|
legend_position='right',
|
|
@@ -2014,8 +1918,8 @@ class CapData(object):
|
|
|
2014
1918
|
plots = []
|
|
2015
1919
|
|
|
2016
1920
|
data = self.get_reg_cols(reg_vars='power', filtered_data=False)
|
|
2017
|
-
data.
|
|
2018
|
-
plt_no_filtering
|
|
1921
|
+
data['Timestamp'] = data.index
|
|
1922
|
+
plt_no_filtering = hv.Curve(data, ['Timestamp'], ['power'], label='all')
|
|
2019
1923
|
plt_no_filtering.opts(
|
|
2020
1924
|
line_color='black',
|
|
2021
1925
|
line_width=1,
|
|
@@ -2024,10 +1928,10 @@ class CapData(object):
|
|
|
2024
1928
|
)
|
|
2025
1929
|
plots.append(plt_no_filtering)
|
|
2026
1930
|
|
|
2027
|
-
d1 =
|
|
1931
|
+
d1 = data.loc[self.removed[0]['index'], ['power', 'Timestamp']]
|
|
2028
1932
|
plt_first_filter = hv.Scatter(
|
|
2029
|
-
|
|
2030
|
-
|
|
1933
|
+
d1, ['Timestamp'], ['power'], label=self.removed[0]['name']
|
|
1934
|
+
)
|
|
2031
1935
|
plots.append(plt_first_filter)
|
|
2032
1936
|
|
|
2033
1937
|
for i, filtering_step in enumerate(self.kept):
|
|
@@ -2035,18 +1939,30 @@ class CapData(object):
|
|
|
2035
1939
|
break
|
|
2036
1940
|
else:
|
|
2037
1941
|
flt_legend = self.kept[i + 1]['name']
|
|
2038
|
-
d_flt =
|
|
2039
|
-
plt = hv.Scatter(
|
|
1942
|
+
d_flt = data.loc[filtering_step['index'], :]
|
|
1943
|
+
plt = hv.Scatter(
|
|
1944
|
+
d_flt, ['Timestamp'], ['power'], label=flt_legend
|
|
1945
|
+
)
|
|
2040
1946
|
plots.append(plt)
|
|
2041
1947
|
|
|
2042
1948
|
scatter_overlay = hv.Overlay(plots)
|
|
1949
|
+
hover = HoverTool(
|
|
1950
|
+
tooltips=[
|
|
1951
|
+
('datetime', '@Timestamp{%Y-%m-%d %H:%M}'),
|
|
1952
|
+
('power', '@power{0,0.0}'),
|
|
1953
|
+
],
|
|
1954
|
+
formatters={
|
|
1955
|
+
'@Timestamp': 'datetime',
|
|
1956
|
+
}
|
|
1957
|
+
)
|
|
2043
1958
|
scatter_overlay.opts(
|
|
2044
1959
|
hv.opts.Scatter(
|
|
2045
1960
|
size=5,
|
|
2046
1961
|
muted_fill_alpha=0,
|
|
2047
1962
|
fill_alpha=1,
|
|
2048
1963
|
line_width=0,
|
|
2049
|
-
tools=[
|
|
1964
|
+
tools=[hover],
|
|
1965
|
+
yformatter=NumeralTickFormatter(format='0,0'),
|
|
2050
1966
|
),
|
|
2051
1967
|
hv.opts.Overlay(
|
|
2052
1968
|
legend_position='bottom',
|
|
@@ -2154,8 +2070,9 @@ class CapData(object):
|
|
|
2154
2070
|
self.regression_cols['w_vel']: 'mean'}
|
|
2155
2071
|
|
|
2156
2072
|
dfs_to_concat = []
|
|
2073
|
+
agg_names = {}
|
|
2157
2074
|
for group_id, agg_func in agg_map.items():
|
|
2158
|
-
columns_to_aggregate = self.
|
|
2075
|
+
columns_to_aggregate = self.loc[group_id]
|
|
2159
2076
|
if columns_to_aggregate.shape[1] == 1:
|
|
2160
2077
|
continue
|
|
2161
2078
|
agg_result = columns_to_aggregate.agg(agg_func, axis=1).to_frame()
|
|
@@ -2165,23 +2082,23 @@ class CapData(object):
|
|
|
2165
2082
|
col_name = group_id + '_' + agg_func.__name__ + '_agg'
|
|
2166
2083
|
agg_result.rename(columns={agg_result.columns[0]: col_name}, inplace=True)
|
|
2167
2084
|
dfs_to_concat.append(agg_result)
|
|
2085
|
+
agg_names[group_id] = col_name
|
|
2168
2086
|
|
|
2169
2087
|
dfs_to_concat.append(self.data)
|
|
2170
2088
|
# write over data and data_filtered attributes
|
|
2171
2089
|
self.data = pd.concat(dfs_to_concat, axis=1)
|
|
2172
2090
|
self.data_filtered = self.data.copy()
|
|
2173
2091
|
|
|
2174
|
-
# update regression_cols attribute
|
|
2092
|
+
# update regression_cols attribute
|
|
2175
2093
|
for reg_var, trans_group in self.regression_cols.items():
|
|
2176
|
-
if self.
|
|
2094
|
+
if self.loc[reg_var].shape[1] == 1:
|
|
2177
2095
|
continue
|
|
2178
|
-
if trans_group in
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
self.regression_cols[reg_var] = agg_col
|
|
2096
|
+
if trans_group in agg_names.keys():
|
|
2097
|
+
print(
|
|
2098
|
+
"Regression variable '{}' has been remapped: '{}' to '{}'"
|
|
2099
|
+
.format(reg_var, trans_group, agg_names[trans_group])
|
|
2100
|
+
)
|
|
2101
|
+
self.regression_cols[reg_var] = agg_names[trans_group]
|
|
2185
2102
|
|
|
2186
2103
|
def data_columns_to_excel(self, sort_by_reversed_names=True):
|
|
2187
2104
|
"""
|
|
@@ -2490,7 +2407,7 @@ class CapData(object):
|
|
|
2490
2407
|
Add option to return plot showing envelope with points not removed
|
|
2491
2408
|
alpha decreased.
|
|
2492
2409
|
"""
|
|
2493
|
-
XandY = self.
|
|
2410
|
+
XandY = self.floc[['poa', 'power']]
|
|
2494
2411
|
if XandY.shape[1] > 2:
|
|
2495
2412
|
return warnings.warn('Too many columns. Try running '
|
|
2496
2413
|
'aggregate_sensors before using '
|
|
@@ -2533,7 +2450,7 @@ class CapData(object):
|
|
|
2533
2450
|
Spec pf column
|
|
2534
2451
|
Increase options to specify which columns are used in the filter.
|
|
2535
2452
|
"""
|
|
2536
|
-
for key in self.
|
|
2453
|
+
for key in self.column_groups.keys():
|
|
2537
2454
|
if key.find('pf') == 0:
|
|
2538
2455
|
selection = key
|
|
2539
2456
|
|
|
@@ -2583,7 +2500,7 @@ class CapData(object):
|
|
|
2583
2500
|
power_data = self.get_reg_cols('power')
|
|
2584
2501
|
elif isinstance(columns, str):
|
|
2585
2502
|
if columns in self.column_groups.keys():
|
|
2586
|
-
power_data = self.
|
|
2503
|
+
power_data = self.floc[columns]
|
|
2587
2504
|
multiple_columns = True
|
|
2588
2505
|
else:
|
|
2589
2506
|
power_data = pd.DataFrame(self.data_filtered[columns])
|
|
@@ -2651,7 +2568,8 @@ class CapData(object):
|
|
|
2651
2568
|
self.data_filtered = func(self.data_filtered, *args, **kwargs)
|
|
2652
2569
|
|
|
2653
2570
|
@update_summary
|
|
2654
|
-
def filter_sensors(
|
|
2571
|
+
def filter_sensors(
|
|
2572
|
+
self, perc_diff=None, inplace=True, row_filter=check_all_perc_diff_comb):
|
|
2655
2573
|
"""
|
|
2656
2574
|
Drop suspicious measurments by comparing values from different sensors.
|
|
2657
2575
|
|
|
@@ -2687,16 +2605,18 @@ class CapData(object):
|
|
|
2687
2605
|
poa_trans_key = regression_cols['poa']
|
|
2688
2606
|
perc_diff = {poa_trans_key: 0.05}
|
|
2689
2607
|
|
|
2690
|
-
for key,
|
|
2608
|
+
for key, threshold in perc_diff.items():
|
|
2691
2609
|
if 'index' in locals():
|
|
2692
2610
|
# if index has been assigned then take intersection
|
|
2693
2611
|
sensors_df = df[trans[key]]
|
|
2694
|
-
next_index = sensor_filter(
|
|
2612
|
+
next_index = sensor_filter(
|
|
2613
|
+
sensors_df, threshold, row_filter=row_filter)
|
|
2695
2614
|
index = index.intersection(next_index) # noqa: F821
|
|
2696
2615
|
else:
|
|
2697
2616
|
# if index has not been assigned then assign it
|
|
2698
2617
|
sensors_df = df[trans[key]]
|
|
2699
|
-
index = sensor_filter(
|
|
2618
|
+
index = sensor_filter(
|
|
2619
|
+
sensors_df, threshold, row_filter=row_filter)
|
|
2700
2620
|
|
|
2701
2621
|
df_out = self.data_filtered.loc[index, :]
|
|
2702
2622
|
|
|
@@ -2743,7 +2663,7 @@ class CapData(object):
|
|
|
2743
2663
|
'load_data clear_sky option.')
|
|
2744
2664
|
if ghi_col is None:
|
|
2745
2665
|
ghi_keys = []
|
|
2746
|
-
for key in self.
|
|
2666
|
+
for key in self.column_groups.keys():
|
|
2747
2667
|
defs = key.split('-')
|
|
2748
2668
|
if len(defs) == 1:
|
|
2749
2669
|
continue
|
|
@@ -2758,7 +2678,7 @@ class CapData(object):
|
|
|
2758
2678
|
else:
|
|
2759
2679
|
meas_ghi = ghi_keys[0]
|
|
2760
2680
|
|
|
2761
|
-
meas_ghi = self.
|
|
2681
|
+
meas_ghi = self.floc[meas_ghi]
|
|
2762
2682
|
if meas_ghi.shape[1] > 1:
|
|
2763
2683
|
warnings.warn('Averaging measured GHI data. Pass column name '
|
|
2764
2684
|
'to ghi_col to use a specific column.')
|
|
@@ -2957,8 +2877,7 @@ class CapData(object):
|
|
|
2957
2877
|
pandas DataFrame
|
|
2958
2878
|
If pred=True, then returns a pandas dataframe of results.
|
|
2959
2879
|
"""
|
|
2960
|
-
df = self.
|
|
2961
|
-
filtered_data=True)
|
|
2880
|
+
df = self.floc[['poa', 't_amb', 'w_vel']]
|
|
2962
2881
|
df = df.rename(columns={df.columns[0]: 'poa',
|
|
2963
2882
|
df.columns[1]: 't_amb',
|
|
2964
2883
|
df.columns[2]: 'w_vel'})
|
|
@@ -3046,8 +2965,7 @@ class CapData(object):
|
|
|
3046
2965
|
See pandas Grouper doucmentation for details. Default is left
|
|
3047
2966
|
labeled and left closed.
|
|
3048
2967
|
"""
|
|
3049
|
-
df = self.
|
|
3050
|
-
filtered_data=True)
|
|
2968
|
+
df = self.floc[['poa', 't_amb', 'w_vel', 'power']]
|
|
3051
2969
|
df = df.rename(columns={df.columns[0]: 'poa',
|
|
3052
2970
|
df.columns[1]: 't_amb',
|
|
3053
2971
|
df.columns[2]: 'w_vel',
|
|
@@ -3153,7 +3071,7 @@ class CapData(object):
|
|
|
3153
3071
|
"""
|
|
3154
3072
|
spatial_uncerts = {}
|
|
3155
3073
|
for group in column_groups:
|
|
3156
|
-
df = self.
|
|
3074
|
+
df = self.floc[group]
|
|
3157
3075
|
# prevent aggregation from updating column groups?
|
|
3158
3076
|
# would not need the below line then
|
|
3159
3077
|
df = df[[col for col in df.columns if 'agg' not in col]]
|