Concise Example Capacity Test using pvcaptest

This example performs the same test as the Complete Capacity Testing example, but uses the run_test function to apply the filters and then displays the filtering steps visually using the scatter_filters method.

Imports

[1]:
import warnings
warnings.filterwarnings('ignore')

import pandas as pd

# import captest as pvc
import captest as ct
from captest import capdata as pvc
from bokeh.io import output_notebook, show

# uncomment below two lines to use cptest.scatter_hv in notebook
import holoviews as hv
from holoviews import opts
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()
Loading BokehJS ...

Load and Plot Measured Data

Load the measured data with the load_data method, which returns a CapData object. This example uses a column grouping defined in an excel file.

[2]:
das = ct.load_data('./data/example_meas_data.csv', group_columns='./data/column_groups.xlsx')
before calling get common timestep
5min
[3]:
das.column_groups
[3]:
irr_ghi:
    Example Project_Weather Station 1 (Standard w/ POA GHI)_Weather Station 1 (Standard w/ POA GHI), Sun2
    Example Project_Weather Station 2 (Standard with POA GHI)_Weather Station 2 (Standard with POA GHI), Sun2
irr_poa:
    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
real_pwr_inv:
    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
real_pwr_mtr:
    Example Project_Elkor Production Meter_Elkor Production Meter, KW
temp_amb:
    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
temp_bom:
    Example Project_Weather Station 1 (Standard w/ POA GHI)_Weather Station 1 (Standard w/ POA GHI), Temp1
    Example Project_Weather Station 2 (Standard with POA GHI)_Weather Station 2 (Standard with POA GHI), Temp1
wind_speed:
    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
[4]:
das.regression_cols = {
    'power': 'real_pwr_mtr',
    'poa': ('irr_poa', 'mean'),
    't_amb': ('temp_amb', 'mean'),
    'w_vel': ('wind_speed', 'mean')
}
[5]:
das.process_regression_columns()
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 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


[6]:
das.agg_sensors(agg_map={'real_pwr_inv':'sum'})

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.

[7]:
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']
das.plot(combine=combine, default_groups=default_groups)
[7]:

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.

[8]:
# Uncomment and run to copy over the filtered dataset with the unfiltered data.
das.reset_filter()
[9]:
measured_filters = [
    (pvc.CapData.filter_custom, (pd.DataFrame.dropna, ), {}),
    (pvc.CapData.filter_irr, (200, 2000), {}),
    (pvc.CapData.filter_outliers, (), {}),
    (pvc.CapData.fit_regression, (), {'filter':True, 'summary':False}),
    (pvc.CapData.rep_cond, (), {}),
    (pvc.CapData.filter_irr, (0.5, 1.5), {'ref_val':'self_val'}),
    (pvc.CapData.fit_regression, (), {}),
]
[10]:
pvc.run_test(das, measured_filters)
NOTE: Regression used to filter outlying points.


Reporting conditions saved to rc attribute.
          poa      t_amb     w_vel
0  633.780378  24.756064  2.138319
                                 OLS Regression Results
=======================================================================================
Dep. Variable:                  power   R-squared (uncentered):                   0.999
Model:                            OLS   Adj. R-squared (uncentered):              0.999
Method:                 Least Squares   F-statistic:                          9.138e+04
Date:                Wed, 13 May 2026   Prob (F-statistic):                        0.00
Time:                        01:02:49   Log-Likelihood:                         -5162.4
No. Observations:                 390   AIC:                                  1.033e+04
Df Residuals:                     386   BIC:                                  1.035e+04
Df Model:                           4
Covariance Type:            nonrobust
==================================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
----------------------------------------------------------------------------------
poa             8002.0163    107.704     74.296      0.000    7790.256    8213.777
I(poa * poa)      -0.1504      0.063     -2.386      0.017      -0.274      -0.026
I(poa * t_amb)   -71.5317      4.460    -16.039      0.000     -80.300     -62.763
I(poa * w_vel)     1.8387      9.296      0.198      0.843     -16.438      20.115
==============================================================================
Omnibus:                       26.707   Durbin-Watson:                   0.918
Prob(Omnibus):                  0.000   Jarque-Bera (JB):               30.495
Skew:                          -0.630   Prob(JB):                     2.39e-07
Kurtosis:                       3.537   Cond. No.                     8.40e+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, 8.4e+03. This might indicate that there are
strong multicollinearity or other numerical problems.
[11]:
das.get_summary()
[11]:
pts_after_filter pts_removed filter_arguments
meas filter_custom 1424 16 DataFrame.dropna, ,
filter_irr 552 872 200,  2000,
filter_outliers 529 23 Default arguments
fit_regression 492 37 filter: True, summary: False
rep_cond 492 0 Default arguments
filter_irr-1 390 102 0.5,  1.5, ref_val: 633.78
fit_regression-1 390 0 Default arguments
[12]:
das_scatter = das.scatter_filters()
das_scatter
[12]:
[13]:
das.timeseries_filters().opts(width=1200)
[13]:

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.

[14]:
sim = ct.load_pvsyst('./data/pvsyst_example_HourlyRes_2.CSV')
[15]:
sim.column_groups
[15]:
_inv_:
    EOutInv
temp_amb_:
    T_Amb
temp_mod_:
    TArray
wind__:
    WindVel
pvsyt_losses__:
    IL Pmax
    IL Pmin
    IL Vmax
    IL Vmin
irr_poa_:
    GlobInc
shade__:
    FShdBm
irr_ghi_:
    GlobHor
index__:
    index
real_pwr__:
    E_Grid
[16]:
sim.set_regression_cols(power='real_pwr__', poa='irr_poa_', t_amb='temp_amb_', w_vel='wind__')
[17]:
# Write over cptest.flt_sim dataframe with a copy of the original unfiltered dataframe
sim.reset_filter()
[18]:
simulated_filters = [
    (pvc.CapData.filter_time, (), {'test_date':'10/11/1990', 'days':60}),
    (pvc.CapData.filter_irr, (200, 930), {}),
    (pvc.CapData.filter_pvsyst, (), {}),
    (pvc.CapData.filter_irr, (0.5, 1.5), {'ref_val':das.rc['poa'][0]}),
    (pvc.CapData.fit_regression, (), {}),
]
[19]:
pvc.run_test(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:                          2.124e+06
Date:                Wed, 13 May 2026   Prob (F-statistic):                        0.00
Time:                        01:02:50   Log-Likelihood:                         -3671.2
No. Observations:                 318   AIC:                                      7350.
Df Residuals:                     314   BIC:                                      7365.
Df Model:                           4
Covariance Type:            nonrobust
==================================================================================
                     coef    std err          t      P>|t|      [0.025      0.975]
----------------------------------------------------------------------------------
poa             7621.3183     16.550    460.511      0.000    7588.756    7653.881
I(poa * poa)      -0.7798      0.013    -59.311      0.000      -0.806      -0.754
I(poa * t_amb)   -31.3087      0.534    -58.627      0.000     -32.359     -30.258
I(poa * w_vel)    -1.4696      1.249     -1.177      0.240      -3.927       0.988
==============================================================================
Omnibus:                       21.571   Durbin-Watson:                   2.113
Prob(Omnibus):                  0.000   Jarque-Bera (JB):                8.858
Skew:                          -0.137   Prob(JB):                       0.0119
Kurtosis:                       2.229   Cond. No.                     5.85e+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, 5.85e+03. This might indicate that there are
strong multicollinearity or other numerical problems.
[20]:
sim.get_summary()
[20]:
pts_after_filter pts_removed filter_arguments
pvsyst filter_time 1441 7319 test_date: 10/11/1990, days: 60
filter_irr 397 1044 200,  930,
filter_pvsyst 397 0 Default arguments
filter_irr-1 318 79 0.5,  1.5, ref_val: np.float64(633.78)
fit_regression 318 0 Default arguments
[21]:
sim_scatter = sim.scatter_filters()
sim_scatter
[21]:

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.

[22]:
ts = ct.CapTest(meas=das, sim=sim, ac_nameplate=6_000, test_tolerance='+/- 7')
[23]:
ts.get_summary()
[23]:
pts_after_filter pts_removed filter_arguments
meas filter_custom 1424 16 DataFrame.dropna, ,
filter_irr 552 872 200,  2000,
filter_outliers 529 23 Default arguments
fit_regression 492 37 filter: True, summary: False
rep_cond 492 0 Default arguments
filter_irr-1 390 102 0.5,  1.5, ref_val: 633.78
fit_regression-1 390 0 Default arguments
pvsyst filter_time 1441 7319 test_date: 10/11/1990, days: 60
filter_irr 397 1044 200,  930,
filter_pvsyst 397 0 Default arguments
filter_irr-1 318 79 0.5,  1.5, ref_val: np.float64(633.78)
fit_regression 318 0 Default arguments
[24]:
das.rc
[24]:
poa t_amb w_vel
0 633.780378 24.756064 2.138319
[25]:
ts.captest_results_check_pvalues(print_res=True)
Using reporting conditions from meas.

Capacity Test Result:         PASS
Modeled test output:          4023785.231
Actual test output:           3891281.253
Tested output ratio:          0.967
Tested Capacity:              5802.419
Bounds:                       5580.0, 6420.0


Using reporting conditions from meas.

Capacity Test Result:         PASS
Modeled test output:          4025776.936
Actual test output:           3888789.450
Tested output ratio:          0.966
Tested Capacity:              5795.834
Bounds:                       5580.0, 6420.0


96.710% - Cap Ratio
96.600% - Cap Ratio after pval check
[25]:
  das_pvals sim_pvals das_params sim_params
poa 0.00000 0.00000 8,002.01627 7,621.31829
I(poa * poa) 0.01749 0.00000 -0.15038 -0.77981
I(poa * t_amb) 0.00000 0.00000 -71.53174 -31.30874
I(poa * w_vel) 0.84331 0.24020 1.83866 -1.46965

Overlaying scatter plots from the measured and PVsyst data. This plot can be generated using CapTest.overlay_scatters when running a test using an instance of CapTest.

[26]:
(
    das.scatter_hv().relabel('Measured') *
    sim.scatter_hv().relabel('PVsyst')
).opts(
    opts.Scatter(alpha=0.3, width=600)
)
[26]: