This page was generated from /home/docs/checkouts/readthedocs.org/user_builds/pvcaptest/checkouts/stable/docs/examples/captest_class_bifi.ipynb.
Interactive online version:
Bifacial Capacity Test with CapTest Class
This example shows use of the CapTest class to conduct a capacity test for a plant with bifacial modules by substituting POA irradiance for total POA irradiance. The CapTest class provides an entry point to define the type of test (regression formula) and define other test level parameters like the test tolerance, reporting conditions, and bifaciality.
Imports
[1]:
import inspect
import warnings
# warnings.filterwarnings('ignore')
from IPython.display import display, Markdown
import pandas as pd
# import captest as pvc
import captest as ct
from captest import capdata as pvc
from captest.captest import CapTest
from bokeh.io import output_notebook, show
# uncomment below two lines to use cptest.scatter_hv in notebook
import holoviews as hv
hv.extension('bokeh')
#if working offline with the CapData.plot() method may fail
#run 'export BOKEH_RESOURCES=inline' at the command line before
#running the jupyter notebook
output_notebook()
Load Data and Calculate Regressors
By specifying the test_setup as bifi_e2848_etotal when instantiating the CapTest instance the \(E_{total}\) regressors are calculated, added as columns to data and data_filtered, and the regression_columns is updated to map poa to e_total. These steps are documented by default in output printed.
[2]:
## uncomment line below to review options for pre-defined capacity tests
# ct.captest.TEST_SETUPS.keys()
[3]:
## uncomment line below to review the full docstring with all parameters for the CapTest class
# display(Markdown(f"```\n{inspect.getdoc(CapTest)}\n```"))
[4]:
ts = CapTest.from_params(
test_setup='bifi_e2848_etotal',
meas_path='./data/example_meas_data_bifi.csv',
sim_path='./data/pvsyst_example_HourlyRes_2_bifi.CSV',
test_tolerance='+/- 3',
ac_nameplate=6_000,
bifaciality=0.7,
meas_load_kwargs={
'group_columns': './data/column_groups_bifi.xlsx',
},
)
/home/docs/checkouts/readthedocs.org/user_builds/pvcaptest/checkouts/stable/src/captest/io.py:208: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format.
data_file = pd.read_csv(
/home/docs/checkouts/readthedocs.org/user_builds/pvcaptest/checkouts/stable/src/captest/io.py:238: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format.
data_file = pd.read_csv(
before calling get common timestep
5min
Aggregating the below 1 columns of the real_pwr_mtr group using the sum function. New column name: real_pwr_mtr_sum_agg:
Example Project_Elkor Production Meter_Elkor Production Meter, KW
Aggregating the below 2 columns of the irr_poa group using the mean function. New column name: irr_poa_mean_agg:
Example Project_Weather Station 1 (Standard w/ POA GHI)_Weather Station 1 (Standard w/ POA GHI), Sun
Example Project_Weather Station 2 (Standard with POA GHI)_Weather Station 2 (Standard with POA GHI), Sun
Aggregating the below 2 columns of the irr_rpoa group using the mean function. New column name: irr_rpoa_mean_agg:
Example Project_Weather Station 1_RPOA
Example Project_Weather Station 2_RPOA
Calculating and adding "e_total" column as irr_poa_mean_agg + irr_rpoa_mean_agg * 0.7 * 1 * (1 - 0)
Aggregating the below 2 columns of the temp_amb group using the mean function. New column name: temp_amb_mean_agg:
Example Project_Weather Station 1 (Standard w/ POA GHI)_Weather Station 1 (Standard w/ POA GHI), TempF
Example Project_Weather Station 2 (Standard with POA GHI)_Weather Station 2 (Standard with POA GHI), TempF
Aggregating the below 2 columns of the wind_speed group using the mean function. New column name: wind_speed_mean_agg:
Example Project_Weather Station 1 (Standard w/ POA GHI)_Weather Station 1 (Standard w/ POA GHI), WindSpeed
Example Project_Weather Station 2 (Standard with POA GHI)_Weather Station 2 (Standard with POA GHI), WindSpeed
Calculating and adding "rpoa_pvsyst" column as GlobBak + BackShd.
Calculating and adding "e_total" column as GlobInc + rpoa_pvsyst * 0.7 * 1 * (1 - 0)
[5]:
ts.meas.agg_sensors(agg_map={'real_pwr_inv':'sum'}, verbose=True)
Aggregating the below 8 columns of the real_pwr_inv group using the sum function. New column name: real_pwr_inv_sum_agg:
Example Project_Inverter 1_Inverter 1, KW
Example Project_Inverter 2_Inverter 2, KW
Example Project_Inverter 3_Inverter 3, KW
Example Project_Inverter 4_Inverter 4, KW
Example Project_Inverter 5_Inverter 5, KW
Example Project_Inverter 6_Inverter 6, KW
Example Project_Inverter 7_Inverter 7, KW
Example Project_Inverter 8_Inverter 8, KW
Note, the full functionality of the dashboard requires a live notebook. Try installing to run or using the launch binder button at the top of the page.
[6]:
combine = {'inv_sum_mtr_pwr': ['mtr', 'inv.*agg'], 'irr_all':['irr_poa', 'irr_ghi'], 'temp_all':['temp_amb', 'temp_mod']}
default_groups = ['inv_sum_mtr_pwr', 'irr_all', 'temp_all']
ts.meas.plot(combine=combine, default_groups=default_groups)
[6]:
Filtering Measured Data
The CapData class provides a number of convience methods to apply filtering steps as defined in ASTM E2848. The following section demonstrates the use of the more commonly used filtering steps to remove measured data points.
The regression_formula and regression_cols attributes are printed below for review prior to fitting the regression to enable verifying the regression is fitted against the \(E_{total}\) irradiance.
[7]:
ts.meas.regression_formula
[7]:
'power ~ poa + I(poa * poa) + I(poa * t_amb) + I(poa * w_vel) - 1'
[8]:
ts.meas.regression_cols
[8]:
{'power': 'real_pwr_mtr_sum_agg',
'poa': 'e_total',
't_amb': 'temp_amb_mean_agg',
'w_vel': 'wind_speed_mean_agg'}
[9]:
# Uncomment and run to copy over the filtered dataset with the unfiltered data.
ts.meas.reset_filter()
[10]:
measured_filters = [
(pvc.CapData.filter_custom, (pd.DataFrame.dropna, ), {}),
(pvc.CapData.filter_irr, (ts.min_irr, ts.max_irr), {}),
(pvc.CapData.filter_outliers, (), {}),
(pvc.CapData.fit_regression, (), {'filter':True, 'summary':False}),
(pvc.CapData.rep_cond, (), {}),
(pvc.CapData.filter_irr, (ts.rep_irr_filter_low, ts.rep_irr_filter_high), {'ref_val':'rep_irr'}),
(pvc.CapData.fit_regression, (), {}),
]
[11]:
pvc.run_test(ts.meas, measured_filters)
NOTE: Regression used to filter outlying points.
Reporting conditions saved to rc attribute.
poa t_amb w_vel
0 810.175074 25.142031 2.220261
OLS Regression Results
=======================================================================================
Dep. Variable: power R-squared (uncentered): 0.999
Model: OLS Adj. R-squared (uncentered): 0.999
Method: Least Squares F-statistic: 2.721e+04
Date: Wed, 13 May 2026 Prob (F-statistic): 4.59e-217
Time: 01:02:19 Log-Likelihood: -2105.3
No. Observations: 157 AIC: 4219.
Df Residuals: 153 BIC: 4231.
Df Model: 4
Covariance Type: nonrobust
==================================================================================
coef std err t P>|t| [0.025 0.975]
----------------------------------------------------------------------------------
poa 6963.3980 253.380 27.482 0.000 6462.824 7463.972
I(poa * poa) -0.1401 0.192 -0.731 0.466 -0.518 0.238
I(poa * t_amb) -60.4421 6.969 -8.673 0.000 -74.210 -46.674
I(poa * w_vel) 2.9091 14.604 0.199 0.842 -25.942 31.761
==============================================================================
Omnibus: 7.541 Durbin-Watson: 0.795
Prob(Omnibus): 0.023 Jarque-Bera (JB): 7.000
Skew: -0.454 Prob(JB): 0.0302
Kurtosis: 2.505 Cond. No. 1.31e+04
==============================================================================
Notes:
[1] R² is computed without centering (uncentered) since the model does not contain a constant.
[2] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[3] The condition number is large, 1.31e+04. This might indicate that there are
strong multicollinearity or other numerical problems.
[12]:
ts.meas.get_summary()
[12]:
| pts_after_filter | pts_removed | filter_arguments | ||
|---|---|---|---|---|
| meas | filter_custom | 1424 | 16 | DataFrame.dropna, , |
| filter_irr | 452 | 972 | 400, 1400, | |
| filter_outliers | 433 | 19 | Default arguments | |
| fit_regression | 405 | 28 | filter: True, summary: False | |
| rep_cond | 405 | 0 | Default arguments | |
| filter_irr-1 | 157 | 248 | 0.8, 1.2, ref_val: 810.175 | |
| fit_regression-1 | 157 | 0 | Default arguments |
[13]:
(
ts.meas.scatter_filters().opts(tools=['lasso_select']) +
ts.meas.timeseries_filters().opts(width=1000)
)
[13]:
[14]:
ct.plotting.ScatterPlot(cd=ts.meas, timeseries=True, tc_power=True, tc_mode='replace', split_day=True).view()
/home/docs/checkouts/readthedocs.org/user_builds/pvcaptest/checkouts/stable/src/captest/plotting.py:1170: UserWarning: Column 'ghi_mod_csky' not found in data; falling back to split_time='12:30'.
split_time = self.split_time or util.detect_solar_noon(self.cd.data)
[14]:
Load and Filter PVsyst Data
To load and filter the modeled data, often from PVsyst, we simply create a new CapData object, load the PVsyst data, and apply the filtering methods as appropriate.
To load pvsyst data we use the load_data method with the load_pvsyst option set to True. By default the load_data method will search for a csv file that includes pvsyst in the filename in a data directory in the same directory as this file. If you have saved the pvsyst file in a different location, you can use the path and fname arguments to load it.
[15]:
ts.sim.regression_formula
[15]:
'power ~ poa + I(poa * poa) + I(poa * t_amb) + I(poa * w_vel) - 1'
[16]:
ts.sim.regression_cols
[16]:
{'power': 'E_Grid', 'poa': 'e_total', 't_amb': 'T_Amb', 'w_vel': 'WindVel'}
[17]:
# Write over cptest.flt_sim dataframe with a copy of the original unfiltered dataframe
ts.sim.reset_filter()
[18]:
ts.min_irr
[18]:
400
[19]:
simulated_filters = [
(pvc.CapData.filter_time, (), {'test_date':'10/11/1990', 'days':ts.sim_days}),
(pvc.CapData.filter_irr, (ts.min_irr, 930), {}),
(pvc.CapData.filter_pvsyst, (), {}),
(pvc.CapData.filter_irr, (ts.rep_irr_filter_low, ts.rep_irr_filter_high), {'ref_val':ts.meas.rc['poa'][0]}),
(pvc.CapData.fit_regression, (), {}),
]
[20]:
pvc.run_test(ts.sim, simulated_filters)
OLS Regression Results
=======================================================================================
Dep. Variable: power R-squared (uncentered): 1.000
Model: OLS Adj. R-squared (uncentered): 1.000
Method: Least Squares F-statistic: 1.418e+06
Date: Wed, 13 May 2026 Prob (F-statistic): 3.86e-137
Time: 01:02:20 Log-Likelihood: -647.65
No. Observations: 59 AIC: 1303.
Df Residuals: 55 BIC: 1312.
Df Model: 4
Covariance Type: nonrobust
==================================================================================
coef std err t P>|t| [0.025 0.975]
----------------------------------------------------------------------------------
poa 7155.6845 27.952 256.002 0.000 7099.668 7211.701
I(poa * poa) -0.8113 0.029 -27.579 0.000 -0.870 -0.752
I(poa * t_amb) -29.2335 0.596 -49.046 0.000 -30.428 -28.039
I(poa * w_vel) 0.1077 1.474 0.073 0.942 -2.847 3.063
==============================================================================
Omnibus: 3.365 Durbin-Watson: 1.616
Prob(Omnibus): 0.186 Jarque-Bera (JB): 2.729
Skew: -0.404 Prob(JB): 0.255
Kurtosis: 2.323 Cond. No. 9.49e+03
==============================================================================
Notes:
[1] R² is computed without centering (uncentered) since the model does not contain a constant.
[2] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[3] The condition number is large, 9.49e+03. This might indicate that there are
strong multicollinearity or other numerical problems.
[21]:
ts.sim.get_summary()
[21]:
| pts_after_filter | pts_removed | filter_arguments | ||
|---|---|---|---|---|
| pvsyst | filter_time | 721 | 8039 | test_date: 10/11/1990, days: 30 |
| filter_irr | 114 | 607 | 400, 930, | |
| filter_pvsyst | 114 | 0 | Default arguments | |
| filter_irr-1 | 59 | 55 | 0.8, 1.2, ref_val: np.float64(810.175) | |
| fit_regression | 59 | 0 | Default arguments |
[22]:
ts.sim.scatter_filters()
[22]:
Results
The get_summary and captest_results_check_pvalues functions display the results of filtering on simulated and measured data and the final capacity test results comparing measured capacity to expected capacity, respectively.
[23]:
ts.get_summary()
[23]:
| pts_after_filter | pts_removed | filter_arguments | ||
|---|---|---|---|---|
| meas | filter_custom | 1424 | 16 | DataFrame.dropna, , |
| filter_irr | 452 | 972 | 400, 1400, | |
| filter_outliers | 433 | 19 | Default arguments | |
| fit_regression | 405 | 28 | filter: True, summary: False | |
| rep_cond | 405 | 0 | Default arguments | |
| filter_irr-1 | 157 | 248 | 0.8, 1.2, ref_val: 810.175 | |
| fit_regression-1 | 157 | 0 | Default arguments | |
| pvsyst | filter_time | 721 | 8039 | test_date: 10/11/1990, days: 30 |
| filter_irr | 114 | 607 | 400, 930, | |
| filter_pvsyst | 114 | 0 | Default arguments | |
| filter_irr-1 | 59 | 55 | 0.8, 1.2, ref_val: np.float64(810.175) | |
| fit_regression | 59 | 0 | Default arguments |
[24]:
ts.meas.rc
[24]:
| poa | t_amb | w_vel | |
|---|---|---|---|
| 0 | 810.175074 | 25.142031 | 2.220261 |
[25]:
ts.meas.print_points_summary(ts.hrs_req)
length of test period to date: 5 days
sufficient points have been collected. 150.0 points required; 157 points collected
[26]:
ts.captest_results_check_pvalues()
92.590% - Cap Ratio
94.450% - Cap Ratio after pval check
[26]:
| das_pvals | sim_pvals | das_params | sim_params | |
|---|---|---|---|---|
| poa | 0.00000 | 0.00000 | 6,963.39800 | 7,155.68453 |
| I(poa * poa) | 0.46574 | 0.00000 | -0.14007 | -0.81125 |
| I(poa * t_amb) | 0.00000 | 0.00000 | -60.44211 | -29.23354 |
| I(poa * w_vel) | 0.84237 | 0.94205 | 2.90915 | 0.10767 |
The overlay_scatters function can be used to overlay the final scatter plots from scatter plots of all filtering steps produced above.
[27]:
ts.overlay_scatters()
[27]: