solarposition.py tutorial

This tutorial needs your help to make it better!

Table of contents:

  1. Setup
  2. SPA output
  3. Speed tests

This tutorial has been tested against the following package versions:

  • pvlib 0.8.0
  • Python 3.8.5
  • IPython 7.18
  • Pandas 1.1.1

It should work with other Python and Pandas versions. It requires pvlib > 0.3.0 and IPython > 3.0.

Setup

In [1]:
import datetime

# scientific python add-ons
import numpy as np
import pandas as pd

# plotting stuff
# first line makes the plots appear in the notebook
%matplotlib inline 
import matplotlib.pyplot as plt

# finally, we import the pvlib library
import pvlib
In [2]:
import pvlib
from pvlib.location import Location

SPA output

In [3]:
tus = Location(32.2, -111, 'US/Arizona', 700, 'Tucson')
print(tus)
golden = Location(39.742476, -105.1786, 'America/Denver', 1830, 'Golden')
print(golden)
golden_mst = Location(39.742476, -105.1786, 'MST', 1830, 'Golden MST')
print(golden_mst)
berlin = Location(52.5167, 13.3833, 'Europe/Berlin', 34, 'Berlin')
print(berlin)
Location: 
  name: Tucson
  latitude: 32.2
  longitude: -111
  altitude: 700
  tz: US/Arizona
Location: 
  name: Golden
  latitude: 39.742476
  longitude: -105.1786
  altitude: 1830
  tz: America/Denver
Location: 
  name: Golden MST
  latitude: 39.742476
  longitude: -105.1786
  altitude: 1830
  tz: MST
Location: 
  name: Berlin
  latitude: 52.5167
  longitude: 13.3833
  altitude: 34
  tz: Europe/Berlin
In [4]:
times = pd.date_range(start=datetime.datetime(2014,6,23), end=datetime.datetime(2014,6,24), freq='1Min')
times_loc = times.tz_localize(tus.pytz)
In [5]:
times
Out[5]:
DatetimeIndex(['2014-06-23 00:00:00', '2014-06-23 00:01:00',
               '2014-06-23 00:02:00', '2014-06-23 00:03:00',
               '2014-06-23 00:04:00', '2014-06-23 00:05:00',
               '2014-06-23 00:06:00', '2014-06-23 00:07:00',
               '2014-06-23 00:08:00', '2014-06-23 00:09:00',
               ...
               '2014-06-23 23:51:00', '2014-06-23 23:52:00',
               '2014-06-23 23:53:00', '2014-06-23 23:54:00',
               '2014-06-23 23:55:00', '2014-06-23 23:56:00',
               '2014-06-23 23:57:00', '2014-06-23 23:58:00',
               '2014-06-23 23:59:00', '2014-06-24 00:00:00'],
              dtype='datetime64[ns]', length=1441, freq='T')
In [6]:
pyephemout = pvlib.solarposition.pyephem(times_loc, tus.latitude, tus.longitude)
spaout = pvlib.solarposition.spa_python(times_loc, tus.latitude, tus.longitude)

pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
spaout['elevation'].plot(label='spa')
plt.legend(ncol=2)
plt.title('elevation')

print('pyephem')
print(pyephemout.head())
print('spa')
print(spaout.head())
pyephem
                           apparent_elevation  apparent_azimuth  elevation  \
2014-06-23 00:00:00-07:00          -34.028887        352.757414 -34.028887   
2014-06-23 00:01:00-07:00          -34.055060        353.032425 -34.055060   
2014-06-23 00:02:00-07:00          -34.080223        353.307627 -34.080223   
2014-06-23 00:03:00-07:00          -34.104374        353.583047 -34.104374   
2014-06-23 00:04:00-07:00          -34.127518        353.858659 -34.127518   

                              azimuth  apparent_zenith      zenith  
2014-06-23 00:00:00-07:00  352.757414       124.028887  124.028887  
2014-06-23 00:01:00-07:00  353.032425       124.055060  124.055060  
2014-06-23 00:02:00-07:00  353.307627       124.080223  124.080223  
2014-06-23 00:03:00-07:00  353.583047       124.104374  124.104374  
2014-06-23 00:04:00-07:00  353.858659       124.127518  124.127518  
spa
                           apparent_zenith      zenith  apparent_elevation  \
2014-06-23 00:00:00-07:00       124.028842  124.028842          -34.028842   
2014-06-23 00:01:00-07:00       124.055012  124.055012          -34.055012   
2014-06-23 00:02:00-07:00       124.080175  124.080175          -34.080175   
2014-06-23 00:03:00-07:00       124.104329  124.104329          -34.104329   
2014-06-23 00:04:00-07:00       124.127471  124.127471          -34.127471   

                           elevation     azimuth  equation_of_time  
2014-06-23 00:00:00-07:00 -34.028842  352.757345         -2.150130  
2014-06-23 00:01:00-07:00 -34.055012  353.032330         -2.150281  
2014-06-23 00:02:00-07:00 -34.080175  353.307536         -2.150431  
2014-06-23 00:03:00-07:00 -34.104329  353.582953         -2.150582  
2014-06-23 00:04:00-07:00 -34.127471  353.858574         -2.150733  
In [7]:
plt.figure()
pyephemout['elevation'].plot(label='pyephem')
spaout['elevation'].plot(label='spa')
(pyephemout['elevation'] - spaout['elevation']).plot(label='diff')
plt.legend(ncol=3)
plt.title('elevation')

plt.figure()
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
spaout['elevation'].plot(label='spa')
(pyephemout['apparent_elevation'] - spaout['elevation']).plot(label='diff')
plt.legend(ncol=3)
plt.title('elevation')

plt.figure()
pyephemout['apparent_zenith'].plot(label='pyephem apparent')
spaout['zenith'].plot(label='spa')
(pyephemout['apparent_zenith'] - spaout['zenith']).plot(label='diff')
plt.legend(ncol=3)
plt.title('zenith')

plt.figure()
pyephemout['apparent_azimuth'].plot(label='pyephem apparent')
spaout['azimuth'].plot(label='spa')
(pyephemout['apparent_azimuth'] - spaout['azimuth']).plot(label='diff')
plt.legend(ncol=3)
plt.title('azimuth');
In [8]:
pyephemout = pvlib.solarposition.pyephem(times.tz_localize(golden.tz), golden.latitude, golden.longitude)
spaout = pvlib.solarposition.spa_python(times.tz_localize(golden.tz), golden.latitude, golden.longitude)

pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
spaout['elevation'].plot(label='spa')
plt.legend(ncol=2)
plt.title('elevation')

print('pyephem')
print(pyephemout.head())
print('spa')
print(spaout.head())
pyephem
                           apparent_elevation  apparent_azimuth  elevation  \
2014-06-23 00:00:00-06:00          -25.154820        344.064195 -25.154820   
2014-06-23 00:01:00-06:00          -25.207201        344.310956 -25.207201   
2014-06-23 00:02:00-06:00          -25.258784        344.558018 -25.258784   
2014-06-23 00:03:00-06:00          -25.309568        344.805380 -25.309568   
2014-06-23 00:04:00-06:00          -25.359550        345.053043 -25.359550   

                              azimuth  apparent_zenith      zenith  
2014-06-23 00:00:00-06:00  344.064195       115.154820  115.154820  
2014-06-23 00:01:00-06:00  344.310956       115.207201  115.207201  
2014-06-23 00:02:00-06:00  344.558018       115.258784  115.258784  
2014-06-23 00:03:00-06:00  344.805380       115.309568  115.309568  
2014-06-23 00:04:00-06:00  345.053043       115.359550  115.359550  
spa
                           apparent_zenith      zenith  apparent_elevation  \
2014-06-23 00:00:00-06:00       115.154766  115.154766          -25.154766   
2014-06-23 00:01:00-06:00       115.207146  115.207146          -25.207146   
2014-06-23 00:02:00-06:00       115.258730  115.258730          -25.258730   
2014-06-23 00:03:00-06:00       115.309514  115.309514          -25.309514   
2014-06-23 00:04:00-06:00       115.359497  115.359497          -25.359497   

                           elevation     azimuth  equation_of_time  
2014-06-23 00:00:00-06:00 -25.154766  344.064134         -2.141078  
2014-06-23 00:01:00-06:00 -25.207146  344.310880         -2.141229  
2014-06-23 00:02:00-06:00 -25.258730  344.557936         -2.141380  
2014-06-23 00:03:00-06:00 -25.309514  344.805299         -2.141531  
2014-06-23 00:04:00-06:00 -25.359497  345.052965         -2.141682  
In [9]:
pyephemout = pvlib.solarposition.pyephem(times.tz_localize(golden.tz), golden.latitude, golden.longitude)
ephemout = pvlib.solarposition.ephemeris(times.tz_localize(golden.tz), golden.latitude, golden.longitude)

pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('elevation')

print('pyephem')
print(pyephemout.head())
print('ephem')
print(ephemout.head())
pyephem
                           apparent_elevation  apparent_azimuth  elevation  \
2014-06-23 00:00:00-06:00          -25.154820        344.064195 -25.154820   
2014-06-23 00:01:00-06:00          -25.207201        344.310956 -25.207201   
2014-06-23 00:02:00-06:00          -25.258784        344.558018 -25.258784   
2014-06-23 00:03:00-06:00          -25.309568        344.805380 -25.309568   
2014-06-23 00:04:00-06:00          -25.359550        345.053043 -25.359550   

                              azimuth  apparent_zenith      zenith  
2014-06-23 00:00:00-06:00  344.064195       115.154820  115.154820  
2014-06-23 00:01:00-06:00  344.310956       115.207201  115.207201  
2014-06-23 00:02:00-06:00  344.558018       115.258784  115.258784  
2014-06-23 00:03:00-06:00  344.805380       115.309568  115.309568  
2014-06-23 00:04:00-06:00  345.053043       115.359550  115.359550  
ephem
                           apparent_elevation  elevation     azimuth  \
2014-06-23 00:00:00-06:00          -25.149499 -25.149499  344.061394   
2014-06-23 00:01:00-06:00          -25.201890 -25.201890  344.308126   
2014-06-23 00:02:00-06:00          -25.253483 -25.253483  344.555170   
2014-06-23 00:03:00-06:00          -25.304277 -25.304277  344.802520   
2014-06-23 00:04:00-06:00          -25.354270 -25.354270  345.050172   

                           apparent_zenith      zenith  solar_time  
2014-06-23 00:00:00-06:00       115.149499  115.149499   22.952125  
2014-06-23 00:01:00-06:00       115.201890  115.201890   22.968789  
2014-06-23 00:02:00-06:00       115.253483  115.253483   22.985453  
2014-06-23 00:03:00-06:00       115.304277  115.304277   23.002118  
2014-06-23 00:04:00-06:00       115.354270  115.354270   23.018782  
In [10]:
loc = berlin

pyephemout = pvlib.solarposition.pyephem(times.tz_localize(loc.tz), loc.latitude, loc.longitude)
ephemout = pvlib.solarposition.ephemeris(times.tz_localize(loc.tz), loc.latitude, loc.longitude)

pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
ephemout['apparent_elevation'].plot(label='ephem apparent')
plt.legend(ncol=2)
plt.title('elevation')

print('pyephem')
print(pyephemout.head())
print('ephem')
print(ephemout.head())
pyephem
                           apparent_elevation  apparent_azimuth  elevation  \
2014-06-23 00:00:00+02:00          -12.598822        343.918876 -12.598822   
2014-06-23 00:01:00+02:00          -12.640668        344.149982 -12.640668   
2014-06-23 00:02:00+02:00          -12.681923        344.381225 -12.681923   
2014-06-23 00:03:00+02:00          -12.722587        344.612605 -12.722587   
2014-06-23 00:04:00+02:00          -12.762658        344.844121 -12.762658   

                              azimuth  apparent_zenith      zenith  
2014-06-23 00:00:00+02:00  343.918876       102.598822  102.598822  
2014-06-23 00:01:00+02:00  344.149982       102.640668  102.640668  
2014-06-23 00:02:00+02:00  344.381225       102.681923  102.681923  
2014-06-23 00:03:00+02:00  344.612605       102.722587  102.722587  
2014-06-23 00:04:00+02:00  344.844121       102.762658  102.762658  
ephem
                           apparent_elevation  elevation     azimuth  \
2014-06-23 00:00:00+02:00          -12.593452 -12.593452  343.916041   
2014-06-23 00:01:00+02:00          -12.635306 -12.635306  344.147120   
2014-06-23 00:02:00+02:00          -12.676569 -12.676569  344.378348   
2014-06-23 00:03:00+02:00          -12.717240 -12.717240  344.609723   
2014-06-23 00:04:00+02:00          -12.757320 -12.757320  344.841244   

                           apparent_zenith      zenith  solar_time  
2014-06-23 00:00:00+02:00       102.593452  102.593452   22.857453  
2014-06-23 00:01:00+02:00       102.635306  102.635306   22.874117  
2014-06-23 00:02:00+02:00       102.676569  102.676569   22.890781  
2014-06-23 00:03:00+02:00       102.717240  102.717240   22.907446  
2014-06-23 00:04:00+02:00       102.757320  102.757320   22.924110  
In [11]:
loc = berlin
times = pd.date_range(start=datetime.date(2015,3,28), end=datetime.date(2015,3,29), freq='5min')

pyephemout = pvlib.solarposition.pyephem(times.tz_localize(loc.tz), loc.latitude, loc.longitude)
ephemout = pvlib.solarposition.ephemeris(times.tz_localize(loc.tz), loc.latitude, loc.longitude)

pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('elevation')

plt.figure()
pyephemout['azimuth'].plot(label='pyephem')
ephemout['azimuth'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('azimuth')

print('pyephem')
print(pyephemout.head())
print('ephem')
print(ephemout.head())
pyephem
                           apparent_elevation  apparent_azimuth  elevation  \
2015-03-28 00:00:00+01:00          -34.669825        356.421155 -34.669825   
2015-03-28 00:05:00+01:00          -34.705895        357.939260 -34.705895   
2015-03-28 00:10:00+01:00          -34.721813        359.458321 -34.721813   
2015-03-28 00:15:00+01:00          -34.717561          0.977630 -34.717561   
2015-03-28 00:20:00+01:00          -34.693143          2.496380 -34.693143   

                              azimuth  apparent_zenith      zenith  
2015-03-28 00:00:00+01:00  356.421155       124.669825  124.669825  
2015-03-28 00:05:00+01:00  357.939260       124.705895  124.705895  
2015-03-28 00:10:00+01:00  359.458321       124.721813  124.721813  
2015-03-28 00:15:00+01:00    0.977630       124.717561  124.717561  
2015-03-28 00:20:00+01:00    2.496380       124.693143  124.693143  
ephem
                           apparent_elevation  elevation     azimuth  \
2015-03-28 00:00:00+01:00          -34.667077 -34.667077  356.419216   
2015-03-28 00:05:00+01:00          -34.703175 -34.703175  357.937292   
2015-03-28 00:10:00+01:00          -34.719120 -34.719120  359.456355   
2015-03-28 00:15:00+01:00          -34.714893 -34.714893    0.975640   
2015-03-28 00:20:00+01:00          -34.690500 -34.690500    2.494378   

                           apparent_zenith      zenith  solar_time  
2015-03-28 00:00:00+01:00       124.667077  124.667077   23.803474  
2015-03-28 00:05:00+01:00       124.703175  124.703175   23.886825  
2015-03-28 00:10:00+01:00       124.719120  124.719120   23.970175  
2015-03-28 00:15:00+01:00       124.714893  124.714893    0.053526  
2015-03-28 00:20:00+01:00       124.690500  124.690500    0.136877  
In [12]:
loc = berlin
times = pd.date_range(start=datetime.date(2015,3,30), end=datetime.date(2015,3,31), freq='5min')

pyephemout = pvlib.solarposition.pyephem(times.tz_localize(loc.tz), loc.latitude, loc.longitude)
ephemout = pvlib.solarposition.ephemeris(times.tz_localize(loc.tz), loc.latitude, loc.longitude)

pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('elevation')

plt.figure()
pyephemout['azimuth'].plot(label='pyephem')
ephemout['azimuth'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('azimuth')

print('pyephem')
print(pyephemout.head())
print('ephem')
print(ephemout.head())
pyephem
                           apparent_elevation  apparent_azimuth  elevation  \
2015-03-30 00:00:00+02:00          -31.976429        338.920871 -31.976429   
2015-03-30 00:05:00+02:00          -32.239797        340.360894 -32.239797   
2015-03-30 00:10:00+02:00          -32.485100        341.809413 -32.485100   
2015-03-30 00:15:00+02:00          -32.712074        343.265937 -32.712074   
2015-03-30 00:20:00+02:00          -32.920477        344.729920 -32.920477   

                              azimuth  apparent_zenith      zenith  
2015-03-30 00:00:00+02:00  338.920871       121.976429  121.976429  
2015-03-30 00:05:00+02:00  340.360894       122.239797  122.239797  
2015-03-30 00:10:00+02:00  341.809413       122.485100  122.485100  
2015-03-30 00:15:00+02:00  343.265937       122.712074  122.712074  
2015-03-30 00:20:00+02:00  344.729920       122.920477  122.920477  
ephem
                           apparent_elevation  elevation     azimuth  \
2015-03-30 00:00:00+02:00          -31.973191 -31.973191  338.919052   
2015-03-30 00:05:00+02:00          -32.236587 -32.236587  340.359056   
2015-03-30 00:10:00+02:00          -32.481918 -32.481918  341.807546   
2015-03-30 00:15:00+02:00          -32.708921 -32.708921  343.264033   
2015-03-30 00:20:00+02:00          -32.917353 -32.917353  344.727991   

                           apparent_zenith      zenith  solar_time  
2015-03-30 00:00:00+02:00       121.973191  121.973191   22.813319  
2015-03-30 00:05:00+02:00       122.236587  122.236587   22.896670  
2015-03-30 00:10:00+02:00       122.481918  122.481918   22.980021  
2015-03-30 00:15:00+02:00       122.708921  122.708921   23.063372  
2015-03-30 00:20:00+02:00       122.917353  122.917353   23.146722  
In [13]:
loc = berlin
times = pd.date_range(start=datetime.date(2015,6,28), end=datetime.date(2015,6,29), freq='5min')

pyephemout = pvlib.solarposition.pyephem(times.tz_localize(loc.tz), loc.latitude, loc.longitude)
ephemout = pvlib.solarposition.ephemeris(times.tz_localize(loc.tz), loc.latitude, loc.longitude)

pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('elevation')

plt.figure()
pyephemout['azimuth'].plot(label='pyephem')
ephemout['azimuth'].plot(label='ephem')
plt.legend(ncol=2)
plt.title('azimuth')

print('pyephem')
print(pyephemout.head())
print('ephem')
print(ephemout.head())
pyephem
                           apparent_elevation  apparent_azimuth  elevation  \
2015-06-28 00:00:00+02:00          -12.679250        343.659411 -12.679250   
2015-06-28 00:05:00+02:00          -12.885989        344.817210 -12.885989   
2015-06-28 00:10:00+02:00          -13.077872        345.978615 -13.077872   
2015-06-28 00:15:00+02:00          -13.254779        347.143381 -13.254779   
2015-06-28 00:20:00+02:00          -13.416591        348.311289 -13.416591   

                              azimuth  apparent_zenith      zenith  
2015-06-28 00:00:00+02:00  343.659411       102.679250  102.679250  
2015-06-28 00:05:00+02:00  344.817210       102.885989  102.885989  
2015-06-28 00:10:00+02:00  345.978615       103.077872  103.077872  
2015-06-28 00:15:00+02:00  347.143381       103.254779  103.254779  
2015-06-28 00:20:00+02:00  348.311289       103.416591  103.416591  
ephem
                           apparent_elevation  elevation     azimuth  \
2015-06-28 00:00:00+02:00          -12.674183 -12.674183  343.658144   
2015-06-28 00:05:00+02:00          -12.880940 -12.880940  344.815897   
2015-06-28 00:10:00+02:00          -13.072843 -13.072843  345.977260   
2015-06-28 00:15:00+02:00          -13.249769 -13.249769  347.141996   
2015-06-28 00:20:00+02:00          -13.411601 -13.411601  348.309859   

                           apparent_zenith      zenith  solar_time  
2015-06-28 00:00:00+02:00       102.674183  102.674183   22.840578  
2015-06-28 00:05:00+02:00       102.880940  102.880940   22.923900  
2015-06-28 00:10:00+02:00       103.072843  103.072843   23.007221  
2015-06-28 00:15:00+02:00       103.249769  103.249769   23.090542  
2015-06-28 00:20:00+02:00       103.411601  103.411601   23.173864  
In [14]:
pyephemout['elevation'].plot(label='pyephem')
pyephemout['apparent_elevation'].plot(label='pyephem apparent')
ephemout['elevation'].plot(label='ephem')
ephemout['apparent_elevation'].plot(label='ephem apparent')
plt.legend(ncol=2)
plt.title('elevation')
plt.xlim(pd.Timestamp('2015-06-28 02:00:00+02:00'), pd.Timestamp('2015-06-28 06:00:00+02:00'))
plt.ylim(-10,10);
In [15]:
# use calc_time to find the time at which a solar angle occurs.
pvlib.solarposition.calc_time(
    datetime.datetime(2020, 9, 14, 12),
    datetime.datetime(2020, 9, 14, 15),
    32.2,
    -110.9,
    'alt',
    0.05235987755982988,  # 3 degrees in radians
)
Out[15]:
datetime.datetime(2020, 9, 14, 13, 24, 13, 861913, tzinfo=<UTC>)
In [16]:
pvlib.solarposition.calc_time(
    datetime.datetime(2020, 9, 14, 22),
    datetime.datetime(2020, 9, 15, 4),
    32.2,
    -110.9,
    'alt',
    0.05235987755982988,  # 3 degrees in radians
)
Out[16]:
datetime.datetime(2020, 9, 15, 1, 13, 2, 720384, tzinfo=<UTC>)

Speed tests

In [17]:
times = pd.date_range(start='20180601', freq='1min', periods=14400)
times_loc = times.tz_localize(loc.tz)
In [18]:
%%timeit
# NBVAL_SKIP

pyephemout = pvlib.solarposition.pyephem(times_loc, loc.latitude, loc.longitude)
#ephemout = pvlib.solarposition.ephemeris(times, loc)
421 ms ± 4.04 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [19]:
%%timeit
# NBVAL_SKIP

#pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.ephemeris(times_loc, loc.latitude, loc.longitude)
25.9 ms ± 798 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [20]:
%%timeit
# NBVAL_SKIP

#pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.get_solarposition(times_loc, loc.latitude, loc.longitude,
                                                 method='nrel_numpy')
134 ms ± 1.28 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

This numba test will only work properly if you have installed numba.

In [21]:
%%timeit
# NBVAL_SKIP

#pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.get_solarposition(times_loc, loc.latitude, loc.longitude,
                                                 method='nrel_numba')
/Users/holmgren/git_repos/pvlib-python/pvlib/solarposition.py:266: UserWarning: Reloading spa to use numba
  warnings.warn('Reloading spa to use numba')
31.4 ms ± 2.17 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

The numba calculation takes a long time the first time that it's run because it uses LLVM to compile the Python code to machine code. After that it's about 4-10 times faster depending on your machine. You can pass a numthreads argument to this function. The optimum numthreads depends on your machine and is equal to 4 by default.

In [22]:
%%timeit
# NBVAL_SKIP

#pyephemout = pvlib.solarposition.pyephem(times, loc)
ephemout = pvlib.solarposition.get_solarposition(times_loc, loc.latitude, loc.longitude,
                                                 method='nrel_numba', numthreads=16)
26 ms ± 652 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [23]:
%%timeit
# NBVAL_SKIP

ephemout = pvlib.solarposition.spa_python(times_loc, loc.latitude, loc.longitude,
                                          how='numba', numthreads=16)
25.9 ms ± 1.04 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [ ]: