{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Generalized Least Squares"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
 
 
 
 
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import statsmodels.api as sm"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Longley dataset is a time series dataset: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
 
 
 
 
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   const  GNPDEFL       GNP   UNEMP   ARMED       POP    YEAR\n",
      "0    1.0     83.0  234289.0  2356.0  1590.0  107608.0  1947.0\n",
      "1    1.0     88.5  259426.0  2325.0  1456.0  108632.0  1948.0\n",
      "2    1.0     88.2  258054.0  3682.0  1616.0  109773.0  1949.0\n",
      "3    1.0     89.5  284599.0  3351.0  1650.0  110929.0  1950.0\n",
      "4    1.0     96.2  328975.0  2099.0  3099.0  112075.0  1951.0\n"
     ]
    }
   ],
   "source": [
    "data = sm.datasets.longley.load()\n",
    "data.exog = sm.add_constant(data.exog)\n",
    "print(data.exog.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    " Let's assume that the data is heteroskedastic and that we know\n",
    " the nature of the heteroskedasticity.  We can then define\n",
    " `sigma` and use it to give us a GLS model\n",
    "\n",
    " First we will obtain the residuals from an OLS fit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
 
 
 
 
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "ols_resid = sm.OLS(data.endog, data.exog).fit().resid"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Assume that the error terms follow an AR(1) process with a trend:\n",
    "\n",
    "$\\epsilon_i = \\beta_0 + \\rho\\epsilon_{i-1} + \\eta_i$\n",
    "\n",
    "where $\\eta \\sim N(0,\\Sigma^2)$\n",
    "\n",
    "and that $\\rho$ is simply the correlation of the residual a consistent estimator for rho is to regress the residuals on the lagged residuals"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
 
 
 
 
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-1.4390229839734248\n",
      "0.17378444788819114\n"
     ]
    }
   ],
   "source": [
    "resid_fit = sm.OLS(\n",
    "    np.asarray(ols_resid)[1:], sm.add_constant(np.asarray(ols_resid)[:-1])\n",
    ").fit()\n",
    "print(resid_fit.tvalues[1])\n",
    "print(resid_fit.pvalues[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    " While we do not have strong evidence that the errors follow an AR(1)\n",
    " process we continue"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
 
 
 
 
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "rho = resid_fit.params[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we know, an AR(1) process means that near-neighbors have a stronger\n",
    " relation so we can give this structure by using a toeplitz matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
 
 
 
 
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1, 2, 3, 4],\n",
       "       [1, 0, 1, 2, 3],\n",
       "       [2, 1, 0, 1, 2],\n",
       "       [3, 2, 1, 0, 1],\n",
       "       [4, 3, 2, 1, 0]])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from scipy.linalg import toeplitz\n",
    "\n",
    "toeplitz(range(5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
 
 
 
 
    },
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "order = toeplitz(range(len(ols_resid)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "so that our error covariance structure is actually rho**order\n",
    " which defines an autocorrelation structure"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "execution": {
 
 
 
 
    }
   },
   "outputs": [],
   "source": [
    "sigma = rho ** order\n",
    "gls_model = sm.GLS(data.endog, data.exog, sigma=sigma)\n",
    "gls_results = gls_model.fit()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, the exact rho in this instance is not known so it it might make more sense to use feasible gls, which currently only has experimental support.\n",
    "\n",
    "We can use the GLSAR model with one lag, to get to a similar result:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "execution": {
 
 
 
 
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                           GLSAR Regression Results                           \n",
      "==============================================================================\n",
      "Dep. Variable:                 TOTEMP   R-squared:                       0.996\n",
      "Model:                          GLSAR   Adj. R-squared:                  0.992\n",
      "Method:                 Least Squares   F-statistic:                     295.2\n",
      "Date:                Fri, 20 Mar 2026   Prob (F-statistic):           6.09e-09\n",
      "Time:                        11:18:46   Log-Likelihood:                -102.04\n",
      "No. Observations:                  15   AIC:                             218.1\n",
      "Df Residuals:                       8   BIC:                             223.0\n",
      "Df Model:                           6                                         \n",
      "Covariance Type:            nonrobust                                         \n",
      "==============================================================================\n",
      "                 coef    std err          t      P>|t|      [0.025      0.975]\n",
      "------------------------------------------------------------------------------\n",
      "const      -3.468e+06   8.72e+05     -3.979      0.004   -5.48e+06   -1.46e+06\n",
      "GNPDEFL       34.5568     84.734      0.408      0.694    -160.840     229.953\n",
      "GNP           -0.0343      0.033     -1.047      0.326      -0.110       0.041\n",
      "UNEMP         -1.9621      0.481     -4.083      0.004      -3.070      -0.854\n",
      "ARMED         -1.0020      0.211     -4.740      0.001      -1.489      -0.515\n",
      "POP           -0.0978      0.225     -0.435      0.675      -0.616       0.421\n",
      "YEAR        1823.1829    445.829      4.089      0.003     795.100    2851.266\n",
      "==============================================================================\n",
      "Omnibus:                        1.960   Durbin-Watson:                   2.554\n",
      "Prob(Omnibus):                  0.375   Jarque-Bera (JB):                1.423\n",
      "Skew:                           0.713   Prob(JB):                        0.491\n",
      "Kurtosis:                       2.508   Cond. No.                     4.80e+09\n",
      "==============================================================================\n",
      "\n",
      "Notes:\n",
      "[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.\n",
      "[2] The condition number is large, 4.8e+09. This might indicate that there are\n",
      "strong multicollinearity or other numerical problems.\n"
     ]
    }
   ],
   "source": [
    "glsar_model = sm.GLSAR(data.endog, data.exog, 1)\n",
    "glsar_results = glsar_model.iterative_fit(1)\n",
    "print(glsar_results.summary())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Comparing gls and glsar results, we see that there are some small\n",
    " differences in the parameter estimates and the resulting standard\n",
    " errors of the parameter estimate. This might be do to the numerical\n",
    " differences in the algorithm, e.g. the treatment of initial conditions,\n",
    " because of the small number of observations in the longley dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "execution": {
 
 
 
 
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "const     -3.797855e+06\n",
      "GNPDEFL   -1.276565e+01\n",
      "GNP       -3.800132e-02\n",
      "UNEMP     -2.186949e+00\n",
      "ARMED     -1.151776e+00\n",
      "POP       -6.805356e-02\n",
      "YEAR       1.993953e+03\n",
      "dtype: float64\n",
      "const     -3.467961e+06\n",
      "GNPDEFL    3.455678e+01\n",
      "GNP       -3.434101e-02\n",
      "UNEMP     -1.962144e+00\n",
      "ARMED     -1.001973e+00\n",
      "POP       -9.780460e-02\n",
      "YEAR       1.823183e+03\n",
      "dtype: float64\n",
      "const      670688.699308\n",
      "GNPDEFL        69.430807\n",
      "GNP             0.026248\n",
      "UNEMP           0.382393\n",
      "ARMED           0.165253\n",
      "POP             0.176428\n",
      "YEAR          342.634628\n",
      "dtype: float64\n",
      "const      871584.051696\n",
      "GNPDEFL        84.733715\n",
      "GNP             0.032803\n",
      "UNEMP           0.480545\n",
      "ARMED           0.211384\n",
      "POP             0.224774\n",
      "YEAR          445.828748\n",
      "dtype: float64\n"
     ]
    }
   ],
   "source": [
    "print(gls_results.params)\n",
    "print(glsar_results.params)\n",
    "print(gls_results.bse)\n",
    "print(glsar_results.bse)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.14.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
