Intro Examples

This page contains introductory examples of pvlib python usage.

Modeling paradigms

The backbone of pvlib-python is well-tested procedural code that implements PV system models. pvlib-python also provides a collection of classes for users that prefer object-oriented programming. These classes can help users keep track of data in a more organized way, provide some “smart” functions with more flexible inputs, and simplify the modeling process for common situations. The classes do not add any algorithms beyond what’s available in the procedural code, and most of the object methods are simple wrappers around the corresponding procedural code.

Let’s use each of these pvlib modeling paradigms to calculate the yearly energy yield for a given hardware configuration at a handful of sites listed below.

In [1]: import pandas as pd

In [2]: import matplotlib.pyplot as plt

In [3]: naive_times = pd.date_range(start='2015', end='2016', freq='1h')

# very approximate
# latitude, longitude, name, altitude, timezone
In [4]: coordinates = [(30, -110, 'Tucson', 700, 'Etc/GMT+7'),
   ...:                (35, -105, 'Albuquerque', 1500, 'Etc/GMT+7'),
   ...:                (40, -120, 'San Francisco', 10, 'Etc/GMT+8'),
   ...:                (50, 10, 'Berlin', 34, 'Etc/GMT-1')]
   ...: 

In [5]: import pvlib

# get the module and inverter specifications from SAM
In [6]: sandia_modules = pvlib.pvsystem.retrieve_sam('SandiaMod')

In [7]: sapm_inverters = pvlib.pvsystem.retrieve_sam('cecinverter')

In [8]: module = sandia_modules['Canadian_Solar_CS5P_220M___2009_']

In [9]: inverter = sapm_inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
   2896             try:
-> 2897                 return self._engine.get_loc(key)
   2898             except KeyError:

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
<ipython-input-9-cb0c5fa8a3e2> in <module>
----> 1 inverter = sapm_inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_']

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/frame.py in __getitem__(self, key)
   2978             if self.columns.nlevels > 1:
   2979                 return self._getitem_multilevel(key)
-> 2980             indexer = self.columns.get_loc(key)
   2981             if is_integer(indexer):
   2982                 indexer = [indexer]

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
   2897                 return self._engine.get_loc(key)
   2898             except KeyError:
-> 2899                 return self._engine.get_loc(self._maybe_cast_indexer(key))
   2900         indexer = self.get_indexer([key], method=method, tolerance=tolerance)
   2901         if indexer.ndim > 1 or indexer.size > 1:

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'

In [10]: temperature_model_parameters = pvlib.temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_glass']

# specify constant ambient air temp and wind for simplicity
In [11]: temp_air = 20

In [12]: wind_speed = 0

Procedural

The straightforward procedural code can be used for all modeling steps in pvlib-python.

The following code demonstrates how to use the procedural code to accomplish our system modeling goal:

In [13]: system = {'module': module, 'inverter': inverter,
   ....:           'surface_azimuth': 180}
   ....: 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-13-8f44442cad28> in <module>
----> 1 system = {'module': module, 'inverter': inverter,
      2           'surface_azimuth': 180}

NameError: name 'inverter' is not defined

In [14]: energies = {}

In [15]: for latitude, longitude, name, altitude, timezone in coordinates:
   ....:     times = naive_times.tz_localize(timezone)
   ....:     system['surface_tilt'] = latitude
   ....:     solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
   ....:     dni_extra = pvlib.irradiance.get_extra_radiation(times)
   ....:     airmass = pvlib.atmosphere.get_relative_airmass(solpos['apparent_zenith'])
   ....:     pressure = pvlib.atmosphere.alt2pres(altitude)
   ....:     am_abs = pvlib.atmosphere.get_absolute_airmass(airmass, pressure)
   ....:     tl = pvlib.clearsky.lookup_linke_turbidity(times, latitude, longitude)
   ....:     cs = pvlib.clearsky.ineichen(solpos['apparent_zenith'], am_abs, tl,
   ....:                                  dni_extra=dni_extra, altitude=altitude)
   ....:     aoi = pvlib.irradiance.aoi(system['surface_tilt'], system['surface_azimuth'],
   ....:                                solpos['apparent_zenith'], solpos['azimuth'])
   ....:     total_irrad = pvlib.irradiance.get_total_irradiance(system['surface_tilt'],
   ....:                                                         system['surface_azimuth'],
   ....:                                                         solpos['apparent_zenith'],
   ....:                                                         solpos['azimuth'],
   ....:                                                         cs['dni'], cs['ghi'], cs['dhi'],
   ....:                                                         dni_extra=dni_extra,
   ....:                                                         model='haydavies')
   ....:     tcell = pvlib.temperature.sapm_cell(total_irrad['poa_global'],
   ....:                                         temp_air, wind_speed,
   ....:                                         **temperature_model_parameters)
   ....:     effective_irradiance = pvlib.pvsystem.sapm_effective_irradiance(
   ....:         total_irrad['poa_direct'], total_irrad['poa_diffuse'],
   ....:         am_abs, aoi, module)
   ....:     dc = pvlib.pvsystem.sapm(effective_irradiance, tcell, module)
   ....:     ac = pvlib.pvsystem.snlinverter(dc['v_mp'], dc['p_mp'], inverter)
   ....:     annual_energy = ac.sum()
   ....:     energies[name] = annual_energy
   ....: 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-15-aa55f107abd4> in <module>
      1 for latitude, longitude, name, altitude, timezone in coordinates:
      2     times = naive_times.tz_localize(timezone)
----> 3     system['surface_tilt'] = latitude
      4     solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
      5     dni_extra = pvlib.irradiance.get_extra_radiation(times)

NameError: name 'system' is not defined

In [16]: energies = pd.Series(energies)

# based on the parameters specified above, these are in W*hrs
In [17]: print(energies.round(0))
Series([], dtype: float64)

In [18]: energies.plot(kind='bar', rot=0)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-18-ce643011a4ea> in <module>
----> 1 energies.plot(kind='bar', rot=0)

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs)
    792                     data.columns = label_name
    793 
--> 794         return plot_backend.plot(data, kind=kind, **kwargs)
    795 
    796     def line(self, x=None, y=None, **kwargs):

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs)
     60             kwargs["ax"] = getattr(ax, "left_ax", ax)
     61     plot_obj = PLOT_CLASSES[kind](data, **kwargs)
---> 62     plot_obj.generate()
     63     plot_obj.draw()
     64     return plot_obj.result

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self)
    277     def generate(self):
    278         self._args_adjust()
--> 279         self._compute_plot_data()
    280         self._setup_subplots()
    281         self._make_plot()

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self)
    412         # no non-numeric frames or series allowed
    413         if is_empty:
--> 414             raise TypeError("no numeric data to plot")
    415 
    416         # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to

TypeError: no numeric data to plot

In [19]: plt.ylabel('Yearly energy yield (W hr)')
Out[19]: Text(0, 0.5, 'Yearly energy yield (W hr)')
_images/proc-energies.png

Object oriented (Location, PVSystem, ModelChain)

The first object oriented paradigm uses a model where a PVSystem object represents an assembled collection of modules, inverters, etc., a Location object represents a particular place on the planet, and a ModelChain object describes the modeling chain used to calculate PV output at that Location. This can be a useful paradigm if you prefer to think about the PV system and its location as separate concepts or if you develop your own ModelChain subclasses. It can also be helpful if you make extensive use of Location-specific methods for other calculations. pvlib-python also includes a SingleAxisTracker class that is a subclass of PVSystem.

The following code demonstrates how to use Location, PVSystem, and ModelChain objects to accomplish our system modeling goal. ModelChain objects provide convenience methods that can provide default selections for models and can also fill necessary input with modeled data. For example, no air temperature or wind speed data is provided in the input weather DataFrame, so the ModelChain object defaults to 20 C and 0 m/s. Also, no irradiance transposition model is specified (keyword argument transposition for ModelChain) so the ModelChain defaults to the haydavies model. In this example, ModelChain infers the DC power model from the module provided by examining the parameters defined for the module.

In [20]: from pvlib.pvsystem import PVSystem

In [21]: from pvlib.location import Location

In [22]: from pvlib.modelchain import ModelChain

In [23]: system = PVSystem(module_parameters=module,
   ....:                   inverter_parameters=inverter,
   ....:                   temperature_model_parameters=temperature_model_parameters)
   ....: 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-23-8644c4c6fde8> in <module>
      1 system = PVSystem(module_parameters=module,
----> 2                   inverter_parameters=inverter,
      3                   temperature_model_parameters=temperature_model_parameters)

NameError: name 'inverter' is not defined

In [24]: energies = {}

In [25]: for latitude, longitude, name, altitude, timezone in coordinates:
   ....:     times = naive_times.tz_localize(timezone)
   ....:     location = Location(latitude, longitude, name=name, altitude=altitude,
   ....:                         tz=timezone)
   ....:     weather = location.get_clearsky(times)
   ....:     mc = ModelChain(system, location,
   ....:                     orientation_strategy='south_at_latitude_tilt')
   ....:     mc.run_model(times=times, weather=weather)
   ....:     annual_energy = mc.ac.sum()
   ....:     energies[name] = annual_energy
   ....: 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-25-09581f5c3eaf> in <module>
      4                         tz=timezone)
      5     weather = location.get_clearsky(times)
----> 6     mc = ModelChain(system, location,
      7                     orientation_strategy='south_at_latitude_tilt')
      8     mc.run_model(times=times, weather=weather)

NameError: name 'system' is not defined

In [26]: energies = pd.Series(energies)

# based on the parameters specified above, these are in W*hrs
In [27]: print(energies.round(0))
Series([], dtype: float64)

In [28]: energies.plot(kind='bar', rot=0)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-28-ce643011a4ea> in <module>
----> 1 energies.plot(kind='bar', rot=0)

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs)
    792                     data.columns = label_name
    793 
--> 794         return plot_backend.plot(data, kind=kind, **kwargs)
    795 
    796     def line(self, x=None, y=None, **kwargs):

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs)
     60             kwargs["ax"] = getattr(ax, "left_ax", ax)
     61     plot_obj = PLOT_CLASSES[kind](data, **kwargs)
---> 62     plot_obj.generate()
     63     plot_obj.draw()
     64     return plot_obj.result

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self)
    277     def generate(self):
    278         self._args_adjust()
--> 279         self._compute_plot_data()
    280         self._setup_subplots()
    281         self._make_plot()

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self)
    412         # no non-numeric frames or series allowed
    413         if is_empty:
--> 414             raise TypeError("no numeric data to plot")
    415 
    416         # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to

TypeError: no numeric data to plot

In [29]: plt.ylabel('Yearly energy yield (W hr)')
Out[29]: Text(0, 0.5, 'Yearly energy yield (W hr)')
_images/modelchain-energies.png

Object oriented (LocalizedPVSystem)

The second object oriented paradigm uses a model where a LocalizedPVSystem represents a PV system at a particular place on the planet. This can be a useful paradigm if you’re thinking about a power plant that already exists.

The LocalizedPVSystem inherits from both PVSystem and Location, while the LocalizedSingleAxisTracker inherits from SingleAxisTracker (itself a subclass of PVSystem) and Location. The LocalizedPVSystem and LocalizedSingleAxisTracker classes may contain bugs due to the relative difficulty of implementing multiple inheritance. The LocalizedPVSystem and LocalizedSingleAxisTracker may be deprecated in a future release. We recommend that most modeling workflows implement Location, PVSystem, and ModelChain.

The following code demonstrates how to use a LocalizedPVSystem object to accomplish our modeling goal:

In [30]: from pvlib.pvsystem import LocalizedPVSystem

In [31]: energies = {}

In [32]: for latitude, longitude, name, altitude, timezone in coordinates:
   ....:     localized_system = LocalizedPVSystem(module_parameters=module,
   ....:                                          inverter_parameters=inverter,
   ....:                                          temperature_model_parameters=temperature_model_parameters,
   ....:                                          surface_tilt=latitude,
   ....:                                          surface_azimuth=180,
   ....:                                          latitude=latitude,
   ....:                                          longitude=longitude,
   ....:                                          name=name,
   ....:                                          altitude=altitude,
   ....:                                          tz=timezone)
   ....:     times = naive_times.tz_localize(timezone)
   ....:     clearsky = localized_system.get_clearsky(times)
   ....:     solar_position = localized_system.get_solarposition(times)
   ....:     total_irrad = localized_system.get_irradiance(solar_position['apparent_zenith'],
   ....:                                                   solar_position['azimuth'],
   ....:                                                   clearsky['dni'],
   ....:                                                   clearsky['ghi'],
   ....:                                                   clearsky['dhi'])
   ....:     tcell = localized_system.sapm_celltemp(total_irrad['poa_global'],
   ....:                                            temp_air, wind_speed)
   ....:     aoi = localized_system.get_aoi(solar_position['apparent_zenith'],
   ....:                                    solar_position['azimuth'])
   ....:     airmass = localized_system.get_airmass(solar_position=solar_position)
   ....:     effective_irradiance = localized_system.sapm_effective_irradiance(
   ....:         total_irrad['poa_direct'], total_irrad['poa_diffuse'],
   ....:         airmass['airmass_absolute'], aoi)
   ....:     dc = localized_system.sapm(effective_irradiance, tcell)
   ....:     ac = localized_system.snlinverter(dc['v_mp'], dc['p_mp'])
   ....:     annual_energy = ac.sum()
   ....:     energies[name] = annual_energy
   ....: 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-32-5b001ffad2db> in <module>
      1 for latitude, longitude, name, altitude, timezone in coordinates:
      2     localized_system = LocalizedPVSystem(module_parameters=module,
----> 3                                          inverter_parameters=inverter,
      4                                          temperature_model_parameters=temperature_model_parameters,
      5                                          surface_tilt=latitude,

NameError: name 'inverter' is not defined

In [33]: energies = pd.Series(energies)

# based on the parameters specified above, these are in W*hrs
In [34]: print(energies.round(0))
Series([], dtype: float64)

In [35]: energies.plot(kind='bar', rot=0)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-35-ce643011a4ea> in <module>
----> 1 energies.plot(kind='bar', rot=0)

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs)
    792                     data.columns = label_name
    793 
--> 794         return plot_backend.plot(data, kind=kind, **kwargs)
    795 
    796     def line(self, x=None, y=None, **kwargs):

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs)
     60             kwargs["ax"] = getattr(ax, "left_ax", ax)
     61     plot_obj = PLOT_CLASSES[kind](data, **kwargs)
---> 62     plot_obj.generate()
     63     plot_obj.draw()
     64     return plot_obj.result

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self)
    277     def generate(self):
    278         self._args_adjust()
--> 279         self._compute_plot_data()
    280         self._setup_subplots()
    281         self._make_plot()

~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self)
    412         # no non-numeric frames or series allowed
    413         if is_empty:
--> 414             raise TypeError("no numeric data to plot")
    415 
    416         # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to

TypeError: no numeric data to plot

In [36]: plt.ylabel('Yearly energy yield (W hr)')
Out[36]: Text(0, 0.5, 'Yearly energy yield (W hr)')
_images/localized-pvsystem-energies.png