# -*- coding: utf-8 -*-
"""
Created on Mon Dec 17 11:13:44 2018
Authors:
George Meachim,
Ting Sun
"""
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from datetime import *
import matplotlib as plt
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import matplotlib.patches as mpatches
import seaborn as sns
from matplotlib import cm
from ._plot import plot_comp, plot_day_clm
# Linear fitting of QS, QN, deltaQN/dt (entire timeseries)
[docs]def derive_ohm_coef(ser_QS, ser_QN):
"""A function to linearly fit two independant variables to a dependent one.
Input params: QS_Ser: The dependent variable QS (Surface heat storage). Pandas Series.
QN_Ser: The first independent variable (Net all wave radiation). Pandas Series.
dt: The time interval with which the rate of change of QN is calculated. Float (hours).
Returns: a1, a2 coefficients and a3 (intercept)
"""
# derive dt in hours
dt_hr = ser_QN.index.freq/pd.Timedelta('1H')
# Calculate difference between neighbouring QN values
ser_delta_QN_dt = ser_QN.diff() / dt_hr
# Drop NaNs and infinite values
ser_QS = ser_QS.replace([np.inf, -np.inf], np.nan).dropna(how="all")
ser_QN = ser_QN.loc[ser_QS.index]
ser_delta_QN_dt = ser_delta_QN_dt.loc[ser_QS.index]
# Create DataFrame with regression quantities and rename cols
frames = [ser_QS, ser_QN, ser_delta_QN_dt]
regression_df = pd.concat(frames, axis=1)
regression_df.columns = ['QS', 'QN', 'delta_QN_dt']
# Reindex after dropping NaNs
regression_df.reset_index(drop=True, inplace=True)
regression_df.fillna(regression_df.mean(), inplace=True)
feature_cols = ['QN', 'delta_QN_dt']
X = regression_df[feature_cols].replace(
[np.inf, -np.inf], np.nan).dropna(how="all")
y = regression_df.QS
lm = LinearRegression()
lm.fit(X, y)
a1 = lm.coef_[0]
a2 = lm.coef_[1]
a3 = lm.intercept_
return a1, a2, a3
def replace_ohm_coeffs(df_state_init, coefs, land_cover_type):
"""
This function takes as input parameters the model initial state DataFrame,
the new ohm coefficients as calculated by performing linear regression on
AMF Obs and the land cover type for which they were calculated.
Input params: df_state_init: pandas df returned by supy after running SUEWS.
coefs: tuple containing new a1, a2, a3 coefficients.
land_cover_type: String specifying one of seven SUEWS land cover types.
Returns: df_state_init_copy: A copy of df_state_init with changed ohm params.
"""
land_cover_type_dict = {'Paved': '1', 'Buildings': '2', 'Evergreen Trees': '3',
'Deciduous Trees': '4',
'Grass': '5', 'Bare soil': '6', 'Water': '7'}
lc_index = int(land_cover_type_dict.get(land_cover_type))-1
# Instantiate 4x3 matrix of zeros to put old coeffs
coef_matrix = np.zeros((4, 3))
coef_matrix[:, 0] = coefs[0]
coef_matrix[:, 1] = coefs[1]
coef_matrix[:, 2] = coefs[2]
# Copy ohm_coef part of df_state_init
df_ohm = df_state_init.loc[:, 'ohm_coef'].copy()
# Reshape values into matrix form
values_ohm = df_ohm.values.reshape((8, 4, 3))
# Get ohm values corresponding to user specified land cover and assign to matrix
values_ohm[lc_index] = coef_matrix
# Place new ohm values into df_ohm
df_ohm.loc[:, :] = values_ohm.flatten()
# Make copy of df_state_init
df_state_init_copy = df_state_init.copy()
# Replace ohm_coef part of df_state_init with new values
df_state_init_copy.loc[:, 'ohm_coef'] = df_ohm.values
return df_state_init_copy
[docs]def sim_ohm(ser_qn: pd.Series, a1: float, a2: float, a3: float) -> pd.Series:
"""Calculate QS using OHM (Objective Hysteresis Model).
Parameters
----------
ser_qn : pd.Series
net all-wave radiation.
a1 : float
a1 of OHM coefficients.
a2 : float
a2 of OHM coefficients.
a3 : float
a3 of OHM coefficients.
Returns
-------
pd.Series
heat storage flux calculated by OHM.
"""
# derive delta t in hour
try:
dt_hr = ser_qn.index.freq/pd.Timedelta('1h')
except AttributeError as ex:
print('frequency info is missing from input `ser_qn`')
sys.exit(1)
# Calculate rate of change of Net All-wave radiation
ser_qn_dt = ser_qn.diff() / dt_hr
# Derive QS from OBS quantities
ser_qs = a1 * ser_qn + a2 * ser_qn_dt + a3
return ser_qs
def compare_heat_storage(ser_qn_obs, ser_qs_obs, a1, a2, a3):
"""This function compares the storage heat flux calculated with AMF
QN and linear regression coefficients with that output by SUEWS.
Input params: QN_Ser_Obs: A series of OBS net all-wave radiation.
QS_Ser_SUEWS: A series of SUEWS storage heat flux values.
a1, a2, a3: Linear regression coefficients from ohm_linregress_clean.
Returns: MPL plot of diurnal comparison, MPL plot with 1:1 line and fitted line.
"""
# calculate qs using OHM
ser_qs_sim = sim_ohm(ser_qn_obs, a1, a2, a3).rename('Sim')
# re-organise obs and sim info one dataframe
ser_qs_obs = ser_qs_obs.rename('Obs')
df_qs = pd.concat([ser_qs_sim, ser_qs_obs.rename('Obs')], axis=1)
# Plotting
plot1 = plot_day_clm(df_qs)
plot2 = plot_comp(df_qs)
return plot1, plot2