def get_solar( latitude, longitude, start, end, DT ): # Provides solar angle for each time step site_location = location.Location(latitude, longitude) times = pd.date_range( start, end, freq=(str(int(DT / 60)) + "T"), ) # Get solar azimuth and zenith to pass to the transposition function solar_position = site_location.get_solarposition(times=times, method="ephemeris") solar_df = pd.DataFrame( { # "ghics": clearsky["ghi"], # "difcs": clearsky["dhi"], # "zen": solar_position["zenith"], "sea": np.radians(solar_position["elevation"]), } ) solar_df.loc[solar_df["sea"] < 0, "sea"] = 0 solar_df.index = solar_df.index.set_names(["When"]) solar_df = solar_df.reset_index() return solar_df
def _calculate_poa(tmy, PV): """ Input: tmy irradiance data Output: PV.poa -- plane of array Remember, PV GIS (C) defines the folowing: G(h): Global irradiance on the horizontal plane (W/m2) === GHI Gb(n): Beam/direct irradiance on a plane always normal to sun rays (W/m2) === DNI Gd(h): Diffuse irradiance on the horizontal plane (W/m2) === DHI """ # define site location for getting solar positions tmy.site = location.Location(tmy.lat, tmy.lon, tmy.tz) # Get solar azimuth and zenith to pass to the transposition function solar_position = tmy.site.get_solarposition(times=tmy.index) # Use get_total_irradiance to transpose, based on solar position POA_irradiance = irradiance.get_total_irradiance( surface_tilt=PV.tilt, surface_azimuth=PV.azimuth, dni=tmy["Gb(n)"], ghi=tmy["G(h)"], dhi=tmy["Gd(h)"], solar_zenith=solar_position["apparent_zenith"], solar_azimuth=solar_position["azimuth"], ) # Return DataFrame PV.poa = POA_irradiance["poa_global"] return
def albuquerque(): """pvlib Location for Albuquerque, NM.""" return location.Location(35.0844, -106.6504, name='Albuquerque', altitude=1500, tx='Etc/GMT+7')
def __init__(self, tz, lat, lon): # For this example, we will be using Golden, Colorado self.tz = tz self.lat = lat self.lon = lon # Create location object to store lat, lon, timezone self.site = location.Location(lat, lon, tz=tz)
def simulate_sun_positions_by_station(station_index, solar_position_method, days, lead_times, latitudes, longitudes): """ This is the worker function of calculating sun positions at a specified station index. This function should be called from the parallel version of this function, `simulate_sun_positions`. Direct use of this function is discouraged. Please use the parallel version of this fucntion, `simulate_sun_positions`. :param station_index: A station index for simulation :param days: See `simulate_sun_positions` :param lead_times: See `simulate_sun_positions` :param latitudes: See `simulate_sun_positions` :param longitudes: See `simulate_sun_positions` :param solar_position_method: See `simulate_sun_positions` :return: A list with DNI, air mass, zenith, apparent zenith, and azimuth. """ # Initialization num_lead_times, num_days = len(lead_times), len(days) dni_extra = np.zeros((num_lead_times, num_days)) air_mass = np.zeros((num_lead_times, num_days)) zenith = np.zeros((num_lead_times, num_days)) apparent_zenith = np.zeros((num_lead_times, num_days)) azimuth = np.zeros((num_lead_times, num_days)) # Determine the current location current_location = location.Location(latitude=latitudes[station_index], longitude=longitudes[station_index]) for day_index in range(num_days): for lead_time_index in range(num_lead_times): # Determine the current time current_posix = days[day_index] + lead_times[lead_time_index] current_time = pd.Timestamp(current_posix, tz="UTC", unit='s') # Calculate sun position solar_position = current_location.get_solarposition( current_time, method=solar_position_method, numthreads=1) # Calculate extraterrestrial DNI dni_extra[lead_time_index, day_index] = irradiance.get_extra_radiation(current_time) # Calculate air mass air_mass[lead_time_index, day_index] = atmosphere.get_relative_airmass( solar_position["apparent_zenith"]) # Store other keys zenith[lead_time_index, day_index] = solar_position["zenith"] apparent_zenith[lead_time_index, day_index] = solar_position["apparent_zenith"] azimuth[lead_time_index, day_index] = solar_position["azimuth"] return [dni_extra, air_mass, zenith, apparent_zenith, azimuth]
def get_poa_and_ghi_irradiance(self, df=None): # For this example, we will be using Golden, Colorado tz = 'UTC' lat = 50.33 lon = -4.034 # lat, lon = 39.755, -105.221 # Create location object to store lat, lon, timezone site = location.Location(lat, lon, tz=tz) # Calculate clear-sky GHI and transpose to plane of array # Define a function so that we can re-use the sequence of operations with # different locations def get_irradiance(site_location, date, tilt, surface_azimuth, periods): # Creates one day's worth of 10 min intervals times = pd.date_range(date, freq='30min', periods=periods, tz=site_location.tz) # Generate clearsky data using the Ineichen model, which is the default # The get_clearsky method returns a dataframe with values for GHI, DNI, # and DHI # print(times) clearsky = site_location.get_clearsky(times,model='ineichen') # Get solar azimuth and zenith to pass to the transposition function solar_position = site_location.get_solarposition(times=times) # Use the get_total_irradiance function to transpose the GHI to POA POA_irradiance = irradiance.get_total_irradiance( surface_tilt=tilt, surface_azimuth=surface_azimuth, dni=clearsky['dni'], ghi=clearsky['ghi'], dhi=clearsky['dhi'], solar_zenith=solar_position['apparent_zenith'], solar_azimuth=solar_position['azimuth']) # Return DataFrame with only GHI and POA return pd.DataFrame({'GHI': clearsky['ghi'], 'POA': POA_irradiance['poa_global']}) if df is None: df_solar_irr = get_irradiance(site, '{}-{}-{}'.format(self.df.index.date[0].year, self.df.index.date[0].month, self.df.index.date[0].day), 90, 180, len(self.df)) # new_df = df.copy() self.df['GHI'] = df_solar_irr['GHI'].values self.df['POA'] = df_solar_irr['POA'].values return self.df else: df_solar_irr = get_irradiance(site, '{}-{}-{}'.format(df.index.date[0].year, df.index.date[0].month, df.index.date[0].day), 90, 180, len(df)) # new_df = df.copy() df['GHI'] = df_solar_irr['GHI'].values df['POA'] = df_solar_irr['POA'].values return df
def setup(self): self.times = pd.date_range(start='20180601', freq='1min', periods=14400) self.days = pd.date_range(start='20180601', freq='d', periods=30) self.location = location.Location(40, -80) self.solar_position = self.location.get_solarposition(self.times) self.clearsky_irradiance = self.location.get_clearsky(self.times) self.tilt = 20 self.azimuth = 180 self.aoi = irradiance.aoi(self.tilt, self.azimuth, self.solar_position.apparent_zenith, self.solar_position.azimuth)
def bifacial(PV_instance, tmy, return_model=False): PV = PV_instance # get weather in correct format weather = PVlibweather(tmy) # define site location for getting solar positions tmy.site = location.Location(tmy.lat, tmy.lon, tmy.tz) # Get solar azimuth and zenith to pass to the transposition function sun = tmy.site.get_solarposition(times=tmy.index) # utility dataframe to supply arguments in correct dimensions setup = pd.DataFrame(index=tmy.index) setup["azimuth"] = 90 setup["tilt"] = 90 setup["axis"] = 0 poa_front, poa_back, front, back = pvlib.bifacial.pvfactors_timeseries( solar_azimuth=sun.azimuth, solar_zenith=sun.zenith, surface_azimuth=setup.azimuth, surface_tilt=setup.tilt, axis_azimuth=setup.axis, timestamps=tmy.index, dni=weather.dni, dhi=weather.dhi, gcr=0.55, pvrow_height=2, pvrow_width=4, albedo=0.23, n_pvrows=3, index_observed_pvrow=1, rho_front_pvrow=0.03, rho_back_pvrow=0.05, horizon_band_angle=15.0, ) front.index, back.index = PV.state.index, PV.state.index front.fillna(0, inplace=True) back.fillna(0, inplace=True) effective = front + back * PV.bifacial_factor poa = poa_front bifacial_irradiance = pd.DataFrame(index=PV.state.index) bifacial_irradiance["effective_irradiance"] = pd.Series( effective, index=PV.state.index) bifacial_irradiance["poa_global"] = pd.Series(poa, index=PV.state.index) bifacial_irradiance["wind_speed"] = pd.Series(tmy["WS"].values, index=PV.state.index) bifacial_irradiance["temp_air"] = pd.Series(tmy["T"].values, index=PV.state.index) return bifacial_irradiance
def test_module_temperature(): """Module temperature is correlated with GHI.""" albuquerque = location.Location(35.0844, -106.6504, altitude=5312, tz='MST') times = pd.date_range(start='01/01/2020', end='03/01/2020', freq='H', tz='MST') clearsky = albuquerque.get_clearsky(times, model='simplified_solis') assert weather.module_temperature_check(clearsky['ghi'] * 0.6, clearsky['ghi']) assert not weather.module_temperature_check(clearsky['ghi'] * (-0.6), clearsky['ghi'])
def test_orientation_fit_pvwatts_missing_data(naive_times): tilt = 30 azimuth = 100 system_location = location.Location(35, -106) local_time = naive_times.tz_localize('MST') clearsky = system_location.get_clearsky(local_time, model='simplified_solis') clearsky.loc['3/1/2020':'3/15/2020'] = np.nan solar_position = system_location.get_solarposition(clearsky.index) solar_position.loc['3/1/2020':'3/15/2020'] = np.nan poa = irradiance.get_total_irradiance(tilt, azimuth, solar_position['zenith'], solar_position['azimuth'], **clearsky) temp_cell = pvlib.temperature.sapm_cell( poa['poa_global'], 25, 0, **pvlib.temperature.TEMPERATURE_MODEL_PARAMETERS['sapm'] ['open_rack_glass_glass']) pdc = pvsystem.pvwatts_dc(poa['poa_global'], temp_cell, 100, -0.002) pac = pvsystem.inverter.pvwatts(pdc, 120) solar_position.dropna(inplace=True) with pytest.raises(ValueError, match=".* must not contain undefined values"): system.infer_orientation_fit_pvwatts( pac, solar_zenith=solar_position['zenith'], solar_azimuth=solar_position['azimuth'], **clearsky) pac.dropna(inplace=True) with pytest.raises(ValueError, match=".* must not contain undefined values"): system.infer_orientation_fit_pvwatts( pac, solar_zenith=solar_position['zenith'], solar_azimuth=solar_position['azimuth'], **clearsky) clearsky.dropna(inplace=True) tilt_out, azimuth_out, rsquared = system.infer_orientation_fit_pvwatts( pac, solar_zenith=solar_position['zenith'], solar_azimuth=solar_position['azimuth'], **clearsky) assert rsquared > 0.9 assert tilt_out == pytest.approx(tilt, abs=10) assert azimuth_out == pytest.approx(azimuth, abs=10)
# array using :py:func:`pvlib.bifacial.pvfactors.pvfactors_timeseries`. import pandas as pd from pvlib import location from pvlib.bifacial.pvfactors import pvfactors_timeseries import matplotlib.pyplot as plt import warnings # supressing shapely warnings that occur on import of pvfactors warnings.filterwarnings(action='ignore', module='pvfactors') # %% # First, generate the usual modeling inputs: times = pd.date_range('2021-06-21', '2021-06-22', freq='1T', tz='Etc/GMT+5') loc = location.Location(latitude=40, longitude=-80, tz=times.tz) sp = loc.get_solarposition(times) cs = loc.get_clearsky(times) # example array geometry pvrow_height = 1 pvrow_width = 4 pitch = 10 gcr = pvrow_width / pitch axis_azimuth = 180 albedo = 0.2 # %% # Now the trick: since pvfactors only wants to model single-axis tracking # arrays, we have to pretend our fixed tilt array is a single-axis tracking # array that never rotates. In that case, the "axis of rotation" is
azimuth_subset = solar_azimuth.resample('15min').first() tracking_data_15min = tracking.singleaxis( zenith_subset, azimuth_subset, self.axis_tilt, self.axis_azimuth, self.max_angle, self.backtrack, self.gcr, self.cross_axis_tilt) # propagate the 15-minute positions to 1-minute stair-stepped values: tracking_data_1min = tracking_data_15min.reindex(solar_zenith.index, method='ffill') return tracking_data_1min # %% # Let's take a look at the tracker rotation curve it produces: times = pd.date_range('2019-06-01', '2019-06-02', freq='1min', tz='US/Eastern') loc = location.Location(40, -80) solpos = loc.get_solarposition(times) mount = DiscontinuousTrackerMount(axis_azimuth=180, gcr=0.4) tracker_data = mount.get_orientation(solpos.apparent_zenith, solpos.azimuth) tracker_data['tracker_theta'].plot() plt.ylabel('Tracker Rotation [degree]') plt.show() # %% # With our custom tracking logic defined, we can create the corresponding # Array and PVSystem, and then run a ModelChain as usual: module_parameters = {'pdc0': 1, 'gamma_pdc': -0.004, 'b': 0.05} temp_params = TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_polymer'] array = pvsystem.Array(mount=mount, module_parameters=module_parameters,
def albuquerque(): return location.Location(35, -106, elevation=2000, tz='MST')
def test_orientation_fit_pvwatts_temp_wind_as_series(naive_times): tilt = 30 azimuth = 100 system_location = location.Location(35, -106) local_time = naive_times.tz_localize('MST') clearsky = system_location.get_clearsky(local_time, model='simplified_solis') solar_position = system_location.get_solarposition(clearsky.index) poa = irradiance.get_total_irradiance(tilt, azimuth, solar_position['zenith'], solar_position['azimuth'], **clearsky) temp_cell = pvlib.temperature.sapm_cell( poa['poa_global'], 25, 1, **pvlib.temperature.TEMPERATURE_MODEL_PARAMETERS['sapm'] ['open_rack_glass_glass']) temperature = pd.Series(25, index=clearsky.index) wind_speed = pd.Series(1, index=clearsky.index) temperature_missing = temperature.copy() temperature_missing.loc['4/5/2020':'4/10/2020'] = np.nan wind_speed_missing = wind_speed.copy() wind_speed_missing.loc['5/5/2020':'5/15/2020'] = np.nan pdc = pvsystem.pvwatts_dc(poa['poa_global'], temp_cell, 100, -0.002) pac = pvsystem.inverter.pvwatts(pdc, 120) with pytest.raises(ValueError, match=".* must not contain undefined values"): system.infer_orientation_fit_pvwatts( pac, solar_zenith=solar_position['zenith'], solar_azimuth=solar_position['azimuth'], temperature=temperature_missing, wind_speed=wind_speed_missing, **clearsky) with pytest.raises(ValueError, match="temperature must not contain undefined values"): system.infer_orientation_fit_pvwatts( pac, solar_zenith=solar_position['zenith'], solar_azimuth=solar_position['azimuth'], temperature=temperature_missing, wind_speed=wind_speed, **clearsky) with pytest.raises(ValueError, match="wind_speed must not contain undefined values"): system.infer_orientation_fit_pvwatts( pac, solar_zenith=solar_position['zenith'], solar_azimuth=solar_position['azimuth'], temperature=temperature, wind_speed=wind_speed_missing, **clearsky) # ValueError if indices don't match with pytest.raises(ValueError): system.infer_orientation_fit_pvwatts( pac, solar_zenith=solar_position['zenith'], solar_azimuth=solar_position['azimuth'], temperature=temperature_missing.dropna(), wind_speed=wind_speed_missing.dropna(), **clearsky) tilt_out, azimuth_out, rsquared = system.infer_orientation_fit_pvwatts( pac, solar_zenith=solar_position['zenith'], solar_azimuth=solar_position['azimuth'], **clearsky) assert rsquared > 0.9 assert tilt_out == pytest.approx(tilt, abs=10) assert azimuth_out == pytest.approx(azimuth, abs=10)
def system_location(request): """Location of the system.""" return location.Location(request.param[0], request.param[1], tz=request.param[2])
def irradiance_summer_winter(lat, lon, tz): site = location.Location(lat, lon, tz=tz) # Calculate clear-sky GHI and transpose to plane of array # Define a function so that we can re-use the sequence of operations with # different locations def get_irradiance(site_location, date, tilt, surface_azimuth): # Creates one day's worth of hour intervals times = pd.date_range(date, freq='h', periods=24, tz=site_location.tz) # Generate clearsky data using the Ineichen model, which is the default # The get_clearsky method returns a dataframe with values for GHI, DNI, # and DHI clearsky = site_location.get_clearsky(times) # Get solar azimuth and zenith to pass to the transposition function solar_position = site_location.get_solarposition(times=times) # Use the get_total_irradiance function to transpose the GHI to POA POA_irradiance = irradiance.get_total_irradiance( surface_tilt=tilt, surface_azimuth=surface_azimuth, dni=clearsky['dni'], ghi=clearsky['ghi'], dhi=clearsky['dhi'], solar_zenith=solar_position['apparent_zenith'], solar_azimuth=solar_position['azimuth']) # Return DataFrame with only GHI and POA return pd.DataFrame({ 'GHI': clearsky['ghi'], 'POA': POA_irradiance['poa_global'] }) # Get irradiance data for summer and winter solstice, assuming 25 degree tilt # and a south facing array if lon > 0: winter = '12-20-2020' summer = '06-21-2020' else: summer = '12-20-2020' winter = '06-21-2020' tilt = abs(lat) if lon > 0: face = 180 summer_irradiance = get_irradiance(site, summer, tilt, 0) winter_irradiance = get_irradiance(site, winter, tilt, 0) # Convert Dataframe Indexes to Hour:Minute format to make plotting easier summer_irradiance.index = summer_irradiance.index.strftime("%H:%M") winter_irradiance.index = winter_irradiance.index.strftime("%H:%M") # Plot GHI vs. POA for winter and summer fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True) summer_irradiance['GHI'].plot(ax=ax1, label='GHI') summer_irradiance['POA'].plot(ax=ax1, label='POA') winter_irradiance['GHI'].plot(ax=ax2, label='GHI') winter_irradiance['POA'].plot(ax=ax2, label='POA') ax1.set_xlabel('Time of day (Summer)') ax2.set_xlabel('Time of day (Winter)') ax1.set_ylabel('Irradiance ($W/m^2$)') ax1.legend() ax2.legend() #plt.show() return fig
# :py:meth:`pvlib.location.Location.get_clearsky` method to generate clearsky # GHI data as well as how to use the # :py:meth:`pvlib.irradiance.get_total_irradiance` function to transpose # GHI data to Plane of Array (POA) irradiance. from pvlib import location from pvlib import irradiance import pandas as pd from matplotlib import pyplot as plt # For this example, we will be using Golden, Colorado tz = 'MST' lat, lon = 39.755, -105.221 # Create location object to store lat, lon, timezone site = location.Location(lat, lon, tz=tz) # Calculate clear-sky GHI and transpose to plane of array # Define a function so that we can re-use the sequence of operations with # different locations def get_irradiance(site_location, date, tilt, surface_azimuth): # Creates one day's worth of 10 min intervals times = pd.date_range(date, freq='10min', periods=6*24, tz=site_location.tz) # Generate clearsky data using the Ineichen model, which is the default # The get_clearsky method returns a dataframe with values for GHI, DNI, # and DHI clearsky = site_location.get_clearsky(times) # Get solar azimuth and zenith to pass to the transposition function solar_position = site_location.get_solarposition(times=times)
from pvlib import irradiance from pvlib import location from pvlib.pvsystem import PVSystem #from pvlib.location import Location from pvlib.modelchain import ModelChain from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS import weatherData latitude, longitude, tz = 63.42024, 10.40122, 'Europe/Oslo' # specify location start = pd.Timestamp(datetime.date.today(), tz=tz) end = start + pd.Timedelta(days=1) irrad_vars = ['ghi', 'dni', 'dhi'] # Create location object to store lat, lon, timezone #location = Location(latitude=63.42024, longitude=10.40122) # Bredd/lengde-grad for Trondheim site = location.Location(latitude, longitude, tz=tz) temperature_model_parameters = TEMPERATURE_MODEL_PARAMETERS['sapm'][ 'open_rack_glass_glass'] # Bestemmer hvilke moduler og invertere vi ønsker å modelere. sandia_modules = pvlib.pvsystem.retrieve_sam('SandiaMod') cec_inverters = pvlib.pvsystem.retrieve_sam('cecinverter') sandia_module = sandia_modules['Canadian_Solar_CS5P_220M___2009_'] cec_inverter = cec_inverters['ABB__MICRO_0_25_I_OUTD_US_208__208V_'] # Calculate clear-sky GHI and transpose to plane of array def get_irradiance(site_location, date, tilt, surface_azimuth): # Creates one day's worth of 10 min intervals times = pd.date_range(date, freq='10min',
gcr = 0.35 max_angle = 60 pvrow_height = 3 pvrow_width = 4 albedo = 0.2 bifaciality = 0.75 # load temperature parameters and module/inverter specifications temp_model_parameters = PARAMS['sapm']['open_rack_glass_glass'] cec_modules = pvsystem.retrieve_sam('CECMod') cec_module = cec_modules['Trina_Solar_TSM_300DEG5C_07_II_'] cec_inverters = pvsystem.retrieve_sam('cecinverter') cec_inverter = cec_inverters['ABB__MICRO_0_25_I_OUTD_US_208__208V_'] # create a location for site, and get solar position and clearsky data site_location = location.Location(lat, lon, tz=tz, name='Greensboro, NC') solar_position = site_location.get_solarposition(times) cs = site_location.get_clearsky(times) # load solar position and tracker orientation for use in pvsystem object sat_mount = pvsystem.SingleAxisTrackerMount(axis_tilt=axis_tilt, axis_azimuth=axis_azimuth, max_angle=max_angle, backtrack=True, gcr=gcr) # created for use in pvfactors timeseries orientation = sat_mount.get_orientation(solar_position['apparent_zenith'], solar_position['azimuth']) # get rear and front side irradiance from pvfactors transposition engine
def deviation(start_time, end_time, location_coor, input_solar_file, x_name): try: obs = pd.read_csv(input_solar_file) except: raise ValueError('Invalid input solar file name') obs = obs.set_index(pd.DatetimeIndex(pd.to_datetime(obs['datetime']))) obs = obs.iloc[:, 1:] if start_time == None: start_time = str(obs.index[0]) if end_time == None: end_time = str(obs.index[-1]) start_time = pd.to_datetime(start_time + ' 00:00:00') end_time = pd.to_datetime(end_time + ' 00:00:00') if (obs.index[0] > start_time) or (obs.index[-1] < end_time): raise ValueError('Invalid start or end date. The input file has date range {} to {}'.format( obs.index[0], obs.index[-1])) obs = obs[(obs.index >= start_time) & (obs.index < end_time)] cap = max(obs[x_name]) times = pd.date_range(start=start_time, end=end_time, freq='60min', closed='left') location_coor = list(location_coor[0].split()) for i in range(len(location_coor)): location_coor[i] = int(location_coor[i]) length = int(len(location_coor)) POA = pd.DataFrame() for i in range(int(length/2)): lat = location_coor[2*i] lon = location_coor[2*i+1] site = location.Location(lat, lon) POA_single = get_irradiance(site, 15, 180, times) POA = pd.concat([POA, POA_single], axis=1) POA = POA.max(axis=1) POA = POA.to_frame() POA.rename(columns={POA.columns[0]: "poa_global"}, inplace=True) n = POA.size # calculate csi norm_max = POA.max() csi = POA.div(norm_max) max_csi_d = [] for i in range(int(n/24)): max_csi_d.append(float(csi.iloc[i*24:i*24+24].max().values)) csi_list = csi['poa_global'].tolist() csi_list = flatten(csi_list) # calculate p max_p_d = [] forecast = obs.iloc[:, 0] forecast = forecast.to_frame() actual = obs.iloc[:, 1] actual_list = actual.tolist() actual = actual.to_frame() for i in range(int(n/24)): max_p_d.append(float(actual.iloc[i*24:i*24+24].max())) # calculate G G = div(max_p_d, max_csi_d) # make G non-decreasing for i in range(len(G)-1): if G[i+1] < G[i]: G[i+1] = G[i] # calculate T T = [] interm = div(actual_list, csi_list) for i in range(int(n/24)): T.append(max(interm[i*24: i*24+24])/G[i]) upper = [] for i in range(int(n/24)): for j in range(24): upper.append(G[i]*min(max_csi_d[i], T[i]*csi_list[i*24+j])) upper_df = pd.DataFrame(upper, index=times, columns=['upper bound']) upper_df.index.name = 'datetimes' forecast_div = upper_df.values-forecast.values deviation_df = pd.DataFrame( forecast_div, index=times, columns=['forecasts']) deviation_df['actuals'] = upper_df.values-actual.values deviation_df.to_csv('deviation.csv') # input for mape_maker, needed return upper_df, cap