{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Stationarity and detrending (ADF/KPSS)\n",
    "\n",
    "Stationarity means that the statistical properties of a time series i.e. mean, variance and covariance do not change over time. Many statistical models require the series to be stationary to make effective and precise predictions.\n",
    "\n",
    "Two statistical tests would be used to check the stationarity of a time series – Augmented Dickey Fuller (“ADF”) test and Kwiatkowski-Phillips-Schmidt-Shin (“KPSS”) test. A method to convert a non-stationary time series into stationary series shall also be used."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This first cell imports standard packages and sets plots to appear inline."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import statsmodels.api as sm"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sunspots dataset is used. It contains yearly (1700-2008) data on sunspots from the National Geophysical Data Center."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sunspots = sm.datasets.sunspots.load_pandas().data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some preprocessing is carried out on the data. The \"YEAR\" column is used in creating index."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sunspots.index = pd.Index(sm.tsa.datetools.dates_from_range(\"1700\", \"2008\"))\n",
    "del sunspots[\"YEAR\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The data is plotted now."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sunspots.plot(figsize=(12, 8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ADF test\n",
    "\n",
    "ADF test is used to determine the presence of unit root in the series, and hence helps in understand if the series is stationary or not. The null and alternate hypothesis of this test are:\n",
    "\n",
    "Null Hypothesis: The series has a unit root.\n",
    "\n",
    "Alternate Hypothesis: The series has no unit root.\n",
    "\n",
    "If the null hypothesis in failed to be rejected, this test may provide evidence that the series is non-stationary.\n",
    "\n",
    "A function is created to carry out the ADF test on a time series."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from statsmodels.tsa.stattools import adfuller\n",
    "\n",
    "\n",
    "def adf_test(timeseries):\n",
    "    print(\"Results of Dickey-Fuller Test:\")\n",
    "    dftest = adfuller(timeseries, autolag=\"AIC\")\n",
    "    dfoutput = pd.Series(\n",
    "        dftest[0:4],\n",
    "        index=[\n",
    "            \"Test Statistic\",\n",
    "            \"p-value\",\n",
    "            \"#Lags Used\",\n",
    "            \"Number of Observations Used\",\n",
    "        ],\n",
    "    )\n",
    "    for key, value in dftest[4].items():\n",
    "        dfoutput[\"Critical Value (%s)\" % key] = value\n",
    "    print(dfoutput)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## KPSS test\n",
    "\n",
    "KPSS is another test for checking the stationarity of a time series. The null and alternate hypothesis for the KPSS test are opposite that of the ADF test.\n",
    "\n",
    "Null Hypothesis: The process is trend stationary.\n",
    "\n",
    "Alternate Hypothesis: The series has a unit root (series is not stationary).\n",
    "\n",
    "A function is created to carry out the KPSS test on a time series."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from statsmodels.tsa.stattools import kpss\n",
    "\n",
    "\n",
    "def kpss_test(timeseries):\n",
    "    print(\"Results of KPSS Test:\")\n",
    "    kpsstest = kpss(timeseries, regression=\"c\", nlags=\"auto\")\n",
    "    kpss_output = pd.Series(\n",
    "        kpsstest[0:3], index=[\"Test Statistic\", \"p-value\", \"Lags Used\"]\n",
    "    )\n",
    "    for key, value in kpsstest[3].items():\n",
    "        kpss_output[\"Critical Value (%s)\" % key] = value\n",
    "    print(kpss_output)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ADF tests gives the following results – test statistic, p value and the critical value at 1%, 5% , and 10% confidence intervals.\n",
    "\n",
    "ADF test is now applied on the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "adf_test(sunspots[\"SUNACTIVITY\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Based upon the significance level of 0.05 and the p-value of ADF test, the null hypothesis can not be rejected. Hence, the series is non-stationary."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The KPSS tests gives the following results – test statistic, p value and the critical value at 1%, 5% , and 10% confidence intervals.\n",
    "\n",
    "KPSS test is now applied on the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "kpss_test(sunspots[\"SUNACTIVITY\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Based upon the significance level of 0.05 and the p-value of KPSS test, there is evidence for rejecting the null hypothesis in favor of the alternative. Hence, the series is non-stationary as per the KPSS test.\n",
    "\n",
    "It is always better to apply both the tests, so that it can be ensured that the series is truly stationary. Possible outcomes of applying these stationary tests are as follows:\n",
    "\n",
    "Case 1: Both tests conclude that the series is not stationary - The series is not stationary  \n",
    "Case 2: Both tests conclude that the series is stationary - The series is stationary  \n",
    "Case 3: KPSS indicates stationarity and ADF indicates non-stationarity - The series is trend stationary. Trend needs to be removed to make series strict stationary. The detrended series is checked for stationarity.  \n",
    "Case 4: KPSS indicates non-stationarity and ADF indicates stationarity - The series is difference stationary. Differencing is to be used to make series stationary. The differenced series is checked for stationarity.  \n",
    "\n",
    "Here, due to the difference in the results from ADF test and KPSS test, it can be inferred that the series is trend stationary and not strict stationary. The series can be detrended by differencing or by model fitting."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Detrending by Differencing\n",
    "\n",
    "It is one of the simplest methods for detrending a time series. A new series is constructed where the value at the current time step is calculated as the difference between the original observation and the observation at the previous time step.\n",
    "\n",
    "Differencing is applied on the data and the result is plotted."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sunspots[\"SUNACTIVITY_diff\"] = sunspots[\"SUNACTIVITY\"] - sunspots[\"SUNACTIVITY\"].shift(\n",
    "    1\n",
    ")\n",
    "sunspots[\"SUNACTIVITY_diff\"].dropna().plot(figsize=(12, 8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ADF test is now applied on these detrended values and stationarity is checked."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "adf_test(sunspots[\"SUNACTIVITY_diff\"].dropna())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Based upon the p-value of ADF test, there is evidence for rejecting the null hypothesis in favor of the alternative. Hence, the series is strict stationary now."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "KPSS test is now applied on these detrended values and stationarity is checked."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "kpss_test(sunspots[\"SUNACTIVITY_diff\"].dropna())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Based upon the p-value of KPSS test, the null hypothesis can not be rejected. Hence, the series is stationary."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "\n",
    "Two tests for checking the stationarity of a time series are used, namely ADF test and KPSS test. Detrending is carried out by using differencing. Trend stationary time series is converted into strict stationary time series. Requisite forecasting model can now be applied on a stationary time series data."
   ]
  }
 ],
 "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.9.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
