Provincial Economics Dynamics Model

Provincial Economics Dynamics Model

Provincial Economics Dynamics Model

The Provincial Economics Dynamics Model provides integrated forecasts for GDP, labour markets, inflation, housing, and trade across Canadian provinces and municipalities. Built on an expenditure-based framework, it uses advanced econometric techniques, population dynamics, and energy sector drivers to capture regional trends.


When run successfully, it produces a spreadsheet containing long term forecasts for Canada's regional economies.

The model accounts for tariff impacts on trade, reflecting Alberta’s exposure to global economic shifts. It supports scenario planning and policy analysis for municipal and provincial governments.

Designed in Python, the model outputs data-ready projections for strategic and operational use.

The Provincial Economics Dynamics Model provides integrated forecasts for GDP, labour markets, inflation, housing, and trade across Canadian provinces and municipalities. Built on an expenditure-based framework, it uses advanced econometric techniques, population dynamics, and energy sector drivers to capture regional trends.


When run successfully, it produces a spreadsheet containing long term forecasts for Canada's regional economies.

The model accounts for tariff impacts on trade, reflecting Alberta’s exposure to global economic shifts. It supports scenario planning and policy analysis for municipal and provincial governments.

Designed in Python, the model outputs data-ready projections for strategic and operational use.

The Provincial Economics Dynamics Model provides integrated forecasts for GDP, labour markets, inflation, housing, and trade across Canadian provinces and municipalities. Built on an expenditure-based framework, it uses advanced econometric techniques, population dynamics, and energy sector drivers to capture regional trends.


When run successfully, it produces a spreadsheet containing long term forecasts for Canada's regional economies.

The model accounts for tariff impacts on trade, reflecting Alberta’s exposure to global economic shifts. It supports scenario planning and policy analysis for municipal and provincial governments.

Designed in Python, the model outputs data-ready projections for strategic and operational use.

The Provincial Economics Dynamics Model provides integrated forecasts for GDP, labour markets, inflation, housing, and trade across Canadian provinces and municipalities. Built on an expenditure-based framework, it uses advanced econometric techniques, population dynamics, and energy sector drivers to capture regional trends.


When run successfully, it produces a spreadsheet containing long term forecasts for Canada's regional economies.

The model accounts for tariff impacts on trade, reflecting Alberta’s exposure to global economic shifts. It supports scenario planning and policy analysis for municipal and provincial governments.

Designed in Python, the model outputs data-ready projections for strategic and operational use.

def macro_dlog_mixed(endog, exo_vars, sheet, coef, start_period, end_period,lags_endog=None, exo_lags=None, diff_exo_vars=None):
    """
    Forecasts 'endog' using a log-difference regression model with optional differencing
    and lagging of exogenous variables. Exogenous variables are sourced from Assump,
    sheet, and (optionally) Projection Sheets.

    Parameters:
        endog (str): Name of the endogenous variable to forecast.
        exo_vars (list): List of exogenous variable names.
        sheet (DataFrame): DataFrame to store forecasts and read some exogenous data from.
        coef (list): Regression coefficients (intercept + exo + lags).
        start_period (int): Start year of the forecast.
        end_period (int): End year of the forecast.
        lags_endog (list): Lags of endogenous variable to include (in differences).
        exo_lags (dict): Dictionary of lags per exogenous variable.
        diff_exo_vars (list): Exogenous variables that should be differenced.

    Returns:
        None: Forecasts are written in-place to `sheet`.
    """
    if lags_endog is None:
        lags_endog = [1]
    if exo_lags is None:
        exo_lags = {var: [0] for var in exo_vars}
    if diff_exo_vars is None:
        diff_exo_vars = []

    global Assump, AB_Projections

    def get_val(var, t):
        """Get value of a variable at time t from Assump, sheet, or AB_Projections."""
        if var in Assump and t in Assump[var]:
            return Assump[var][t]
        elif var in sheet.index and t in sheet.columns:
            return sheet.loc[var, t]
        elif sheet is CER_Projections and var in AB_Projections.index and t in AB_Projections.columns:
            return AB_Projections.loc[var, t]
        else:
            return None

    for i in range(start_period, end_period + 1):
        if i in sheet.columns and pd.notna(sheet.loc[endog, i]) and sheet.loc[endog, i] != '':
            continue

        X = [1]  # Intercept

        # Exogenous variables
        for var in exo_vars:
            for lag in exo_lags.get(var, [0]):
                t = i - lag
                if var in diff_exo_vars:
                    t_minus_1 = t - 1
                    val_t = get_val(var, t)
                    val_tm1 = get_val(var, t_minus_1)
                    if val_t and val_tm1:
                        dlog_val = np.log(val_t) - np.log(val_tm1)
                    else:
                        dlog_val = 0
                    X.append(dlog_val)
                else:
                    val = get_val(var, t)
                    log_val = np.log(val) if val else 0
                    X.append(log_val)

        # Lagged differenced endogenous variable
        for lag in lags_endog:
            t_lag = i - lag
            if t_lag - 1 in sheet.columns and t_lag in sheet.columns:
                y_t = sheet.loc[endog, t_lag]
                y_tm1 = sheet.loc[endog, t_lag - 1]
                if pd.notna(y_t) and pd.notna(y_tm1) and y_t != '' and y_tm1 != '':
                    dlog_y = np.log(y_t) - np.log(y_tm1)
                else:
                    dlog_y = 0
            else:
                dlog_y = 0
            X.append(dlog_y)

        # Predict Δlog(y)
        dlog_y_t = sum(c * x for c, x in zip(coef, X))

        # Get log(y_{t-1})
        if (i - 1) in sheet.columns and pd.notna(sheet.loc[endog, i - 1]) and sheet.loc[endog, i - 1] != '':
            log_y_tm1 = np.log(sheet.loc[endog, i - 1])
        else:
            log_y_tm1 = 0

        # Forecast log(y_t), then convert back
        log_y_t = log_y_tm1 + dlog_y_t
        forecast = round(np.exp(log_y_t), 4)
        sheet.loc[endog, i] = forecast
def macro_log_forecast_mixed(endog, exo_vars, sheet, coef, start_period, end_period,lags_endog=None, exo_lags=None, diff_exo_vars=None, diff_dep=True):
    """
    Forecast with option to keep dependent variable in log-level (diff_dep=False) or difference it (diff_dep=True).
    Similar parameters to your original function.
    """
    if lags_endog is None:
        lags_endog = [1]
    if exo_lags is None:
        exo_lags = {var: [0] for var in exo_vars}
    if diff_exo_vars is None:
        diff_exo_vars = []

    global Assump, AB_Projections

    def get_val(var, t):
        if var in Assump and t in Assump[var]:
            return Assump[var][t]
        elif var in sheet.index and t in sheet.columns:
            return sheet.loc[var, t]
        elif sheet is CER_Projections and var in AB_Projections.index and t in AB_Projections.columns:
            return AB_Projections.loc[var, t]
        else:
            return None

    for i in range(start_period, end_period + 1):
        if i in sheet.columns and pd.notna(sheet.loc[endog, i]) and sheet.loc[endog, i] != '':
            continue

        X = [1]  # intercept

        # Exogenous variables
        for var in exo_vars:
            for lag in exo_lags.get(var, [0]):
                t = i - lag
                if var in diff_exo_vars:
                    t_minus_1 = t - 1
                    val_t = get_val(var, t)
                    val_tm1 = get_val(var, t_minus_1)
                    if val_t and val_tm1 and val_t > 0 and val_tm1 > 0:
                        dlog_val = np.log(val_t) - np.log(val_tm1)
                    else:
                        dlog_val = 0
                    X.append(dlog_val)
                else:
                    val = get_val(var, t)
                    log_val = np.log(val) if val and val > 0 else 0
                    X.append(log_val)

        # Lagged endogenous diffs only if diff_dep True
        if diff_dep:
            for lag in lags_endog:
                t_lag = i - lag
                if (t_lag in sheet.columns) and (t_lag - 1 in sheet.columns):
                    y_t = sheet.loc[endog, t_lag]
                    y_tm1 = sheet.loc[endog, t_lag - 1]
                    if pd.notna(y_t) and pd.notna(y_tm1) and y_t != '' and y_tm1 != '' and y_t > 0 and y_tm1 > 0:
                        dlog_y = np.log(y_t) - np.log(y_tm1)
                    else:
                        dlog_y = 0
                else:
                    dlog_y = 0
                X.append(dlog_y)

        # Calculate predicted value
        pred = sum(c * x for c, x in zip(coef, X))

        if diff_dep:
            # predicted diff(log(y))
            if (i - 1) in sheet.columns and pd.notna(sheet.loc[endog, i - 1]) and sheet.loc[endog, i - 1] != '' and sheet.loc[endog, i - 1] > 0:
                log_y_tm1 = np.log(sheet.loc[endog, i - 1])
            else:
                log_y_tm1 = 0
            log_y_t = log_y_tm1 + pred
        else:
            # predicted log(y_t) directly
            log_y_t = pred

        forecast = round(np.exp(log_y_t), 0)
        sheet.loc[endog, i] = forecast

#8.ALBERTA REGRESSION AND FORECASTS.
#8A.REGRESSION.
Cons_Dur_Param = make_regression('simple', 'Cons_Durables', ['Cons_Conf', 'Disposable_Income'])

NDURS_Param = make_regression('mixed', 'NDURS',['Cons_Conf', 'EXP_PR', 'WTI', 'Disposable_Income'],diff_exo_vars=['Cons_Conf', 
            'EXP_PR', 'WTI', 'Disposable_Income'],exo_lags={'EXP_PR': [1]})

ME_Param = make_regression('mixed', 'M&E',['WTI', 'Cons_Conf', 'EXP_PR'],diff_exo_vars=['WTI', 'Cons_Conf', 'EXP_PR'],
    exo_lags={'EXP_PR': [1]})

Res_Param = make_regression('mixed', 'Res_Investment',['Housing_Starts', '5-Year Bond'],diff_exo_vars=['Housing_Starts', '5-Year Bond'],
    exo_lags={'5-Year Bond': [1], 'CPI': [2]})

IPP_Param = make_regression('mixed', 'IPP', ['EXP_PR'],diff_exo_vars=['EXP_PR'],exo_lags={'EXP_PR': [1]})

NRes_Param = make_regression('mixed', 'Non_Res_Struct',['Capex', 'WTI', 'EXP_PR'],diff_exo_vars=['Capex', 'WTI', 'EXP_PR'],
    exo_lags={'EXP_PR': [1]})

Gov_Param = make_regression('mixed', 'Government',['Debt', 'WTI'],diff_exo_vars=['Debt', 'WTI'],exo_lags={'Debt': [1], 'WTI': [0]})

Export_Param = make_regression('mixed', 'Tot_Exports',['Exchange_Rate', 'USA_GDP', 'WTI', 'Tariff_Dummy','E_USA_TF'],diff_exo_vars=['Exchange_Rate', 'USA_GDP', 'WTI'],
    exo_lags={'Tariff_Dummy':[0],'WTI': [1]})

Import_Param = make_regression('mixed', 'Tot_Imports',['Cons_Conf', 'Exchange_Rate', 'WTI','Tariff_Dummy','E_CAN_TF'],
     diff_exo_vars=['Cons_Conf', 'WTI','Exchange_Rate', 'Tariff_Dummy'],exo_lags=None)

NPISH_Param = make_regression('simple', 'NPISH', [])

Source_Pop_Param = make_regression('simple', 'Source_Pop', [])

Inv_Param = make_regression('linear', 'Investment_Inventories', [])

LF_Param = make_regression(model_type='mixed',target='Labour_Force',exo_vars=['Pop', 'Per_Wage'],diff_exo_vars= ['Pop', 'Per_Wage'],lags=1,
           exo_lags=None)

Emp_Param = make_regression(model_type='mixed',target='Employment',exo_vars=['GDP', 'EXP_PR', 'WTI','Labour_Force'],diff_exo_vars=['GDP'
            , 'WTI','Labour_Force'],lags=0,exo_lags={'EXP_PR': [1],'WTI':[1]})

CPI_Param = make_regression(model_type='mixed',target='CPI',exo_vars=['GDP', 'EXP_PR', 'CAN_CPI','WTI'],
            diff_exo_vars=['GDP', 'EXP_PR','CAN_CPI','WTI'],lags=1,exo_lags={'EXP_PR': [0],'GDP':[1]})

H_Param = make_regression(model_type='mixed',target='Housing_Starts',exo_vars=['Pop','CPI','5-Year Bond'],
          diff_exo_vars=['Pop','CPI'],lags=0,exo_lags={'5-Year Bond': [1],'Pop':[0,1]})

#8B.GDP COMPONENT FORECASTS.
for i in range (start_period, end_period):
    macro_linear('Investment_Inventories',[],AB_Projections,Inv_Param,i,i+1,lags_endog=[1],lags_exo=None)
    macro_diff_log('NPISH',[],AB_Projections,NPISH_Param,i,i+1,lags_endog=[1],lags_exo=None)
    macro_diff_log('Source_Pop',[],AB_Projections,Source_Pop_Param,i,i+1,lags_endog=[1],lags_exo=None)
    macro_diff_log('Cons_Durables',['Cons_Conf','Disposable_Income'],AB_Projections,Cons_Dur_Param,i,i+1,lags_endog=[1],lags_exo=None)
    macro_diff_log_mixed('NDURS',['Cons_Conf','EXP_PR','WTI','Disposable_Income'],AB_Projections,NDURS_Param,i,i+1,lags_endog=[1],exo_lags={'EXP_PR'
    :[1]},diff_exo_vars=['Cons_Conf','EXP_PR','WTI','Disposable_Income'])
    macro_diff_log_mixed('M&E',['WTI','Cons_Conf','EXP_PR'],AB_Projections,ME_Param,i,i+1,lags_endog=[1],exo_lags={'EXP_PR'
    :[1]},diff_exo_vars=['WTI','Cons_Conf','EXP_PR'])
    macro_diff_log_mixed('Res_Investment',['Housing_Starts','5-Year Bond'],AB_Projections,Res_Param,i,i+1,lags_endog=[1],exo_lags={'5-Year Bond':[1],'CPI':[2]},
    diff_exo_vars=['Housing_Starts','5-Year Bond'])
    macro_diff_log_mixed('IPP',['EXP_PR'],AB_Projections,IPP_Param,i,i+1,lags_endog=[1],exo_lags={'EXP_PR':[1]},diff_exo_vars=['EXP_PR'])
    macro_diff_log_mixed('Non_Res_Struct',['Capex','WTI','EXP_PR'],AB_Projections,NRes_Param,i,i+1,lags_endog=[1],exo_lags={'EXP_PR':[1]},
    diff_exo_vars=['Capex','WTI','EXP_PR'])
    macro_diff_log_mixed('Tot_Exports',['Exchange_Rate', 'USA_GDP', 'WTI', 'Tariff_Dummy','E_USA_TF'],AB_Projections,Export_Param,i,i+1,lags_endog=[1],
    exo_lags={'Tariff_Dummy':[0],'WTI': [1]},diff_exo_vars=['Exchange_Rate', 'USA_GDP', 'WTI'])
    macro_diff_log_mixed('Tot_Imports',['Cons_Conf', 'Exchange_Rate', 'WTI','Tariff_Dummy','E_CAN_TF'],AB_Projections,Import_Param,i,i+1,lags_endog=[1],
    exo_lags=None,diff_exo_vars=['Cons_Conf', 'WTI','Exchange_Rate', 'Tariff_Dummy'])
    macro_diff_log_mixed('Government',['Debt','WTI'],AB_Projections,Gov_Param,i,i+1,lags_endog=[1],exo_lags={'Debt':[1],'WTI':[0]},
    diff_exo_vars=['Debt','WTI'])

Interested in the full script?

Get access to the complete python scripts, excel data, pdfs and more

Interested in the full script?

Get access to the complete python scripts, excel data, pdfs and more

Interested in the full script?

Get access to the complete python scripts, excel data, pdfs and more