{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1b85622b",
   "metadata": {},
   "source": [
    "# Colorado Spills \n",
    "## OLS, NBR, Interrupted Time Series Analysis\n",
    "### Author: [David P. Adams, PhD](https://dadams.io)\n",
    "#### Date: 2025-04-18"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d456a510",
   "metadata": {},
   "source": [
    "---\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9814ecd7",
   "metadata": {},
   "source": [
    "# Setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "eba79ece",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/dadams/miniconda3/envs/spatial_env2/lib/python3.10/site-packages/pandas/io/sql.py:1725: SAWarning: Did not recognize type 'geometry' of column 'geometry'\n",
      "  self.meta.reflect(bind=self.con, only=[table_name], views=True)\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "from sqlalchemy import create_engine\n",
    "import geopandas as gpd\n",
    "from dotenv import load_dotenv\n",
    "load_dotenv()\n",
    "\n",
    "import os\n",
    "\n",
    "# Database connection details from zshrc environment variables\n",
    "#  use .env file for local development\n",
    "\n",
    "\n",
    "db_name = 'colorado_spills'\n",
    "user = os.getenv('DB_USER')\n",
    "password = os.getenv('DB_PASSWORD')\n",
    "host = os.getenv('DB_HOST')\n",
    "port = os.getenv('DB_PORT')\n",
    "\n",
    "\n",
    "# Create an engine to connect to the PostgreSQL database\n",
    "engine = create_engine(f'postgresql+psycopg2://{user}:{password}@{host}:{port}/{db_name}')\n",
    "\n",
    "# Read in the spills_with_demographics_geog as spills\n",
    "spills = pd.read_sql_table('spills_with_demographics_geog', engine)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "da21b89e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# use longitude and latitude to create a GeoDataFrame from spills data\n",
    "spills['geometry'] = gpd.points_from_xy(spills['Longitude'], spills['Latitude'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "789eaccb",
   "metadata": {},
   "outputs": [],
   "source": [
    "spills_gdf = gpd.GeoDataFrame(spills, crs='EPSG:4326') \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d041a419",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# Ensure date columns are in datetime format\n",
    "spills_gdf['Date of Discovery'] = pd.to_datetime(spills_gdf['Date of Discovery'])\n",
    "spills_gdf['Initial Report Date'] = pd.to_datetime(spills_gdf['Initial Report Date'])\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "3106f3c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# drop 2024 data for date of discovery and initial report date\n",
    "spills_gdf = spills_gdf[spills_gdf['Date of Discovery'].dt.year < 2024]\n",
    "spills_gdf = spills_gdf[spills_gdf['Initial Report Date'].dt.year < 2024]\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "139de8e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Separate Historical vs. Recent Spills\n",
    "historical_spills = spills_gdf[spills_gdf['Spill Type'] == 'Historical']\n",
    "recent_spills = spills_gdf[spills_gdf['Spill Type'] == 'Recent']\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "ab3680c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import statsmodels.formula.api as smf\n",
    "\n",
    "# Create 'Period' column\n",
    "spills_gdf['Period'] = spills_gdf['Report Year'].apply(lambda x: 'Before 2020' if x < 2020 else '2020 and After')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "2ccf5c3a",
   "metadata": {},
   "outputs": [],
   "source": [
    "spills_gdf = spills_gdf.rename(columns={\n",
    "    'Report Delay (Days)': 'report_delay',\n",
    "    'Spill Type': 'spill_type'\n",
    "})\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1da721cd",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b9a3766",
   "metadata": {},
   "source": [
    "# OLS Regression"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "1b722ad7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            OLS Regression Results                            \n",
      "==============================================================================\n",
      "Dep. Variable:           report_delay   R-squared:                       0.006\n",
      "Model:                            OLS   Adj. R-squared:                  0.006\n",
      "Method:                 Least Squares   F-statistic:                     31.12\n",
      "Date:                Fri, 18 Apr 2025   Prob (F-statistic):           4.78e-20\n",
      "Time:                        21:03:48   Log-Likelihood:                -99827.\n",
      "No. Observations:               15990   AIC:                         1.997e+05\n",
      "Df Residuals:                   15986   BIC:                         1.997e+05\n",
      "Df Model:                           3                                         \n",
      "Covariance Type:            nonrobust                                         \n",
      "====================================================================================================================\n",
      "                                                       coef    std err          t      P>|t|      [0.025      0.975]\n",
      "--------------------------------------------------------------------------------------------------------------------\n",
      "Intercept                                           13.5331      2.394      5.654      0.000       8.841      18.225\n",
      "C(spill_type)[T.Recent]                            -10.6062      3.032     -3.498      0.000     -16.550      -4.662\n",
      "C(Period)[T.Before 2020]                            16.2130      3.417      4.745      0.000       9.516      22.910\n",
      "C(spill_type)[T.Recent]:C(Period)[T.Before 2020]   -14.4276      4.200     -3.435      0.001     -22.660      -6.196\n",
      "==============================================================================\n",
      "Omnibus:                    45995.174   Durbin-Watson:                   1.996\n",
      "Prob(Omnibus):                  0.000   Jarque-Bera (JB):       3525625525.882\n",
      "Skew:                          38.959   Prob(JB):                         0.00\n",
      "Kurtosis:                    2302.059   Cond. No.                         8.76\n",
      "==============================================================================\n",
      "\n",
      "Notes:\n",
      "[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.\n"
     ]
    }
   ],
   "source": [
    "import statsmodels.formula.api as smf\n",
    "\n",
    "model = smf.ols(\"report_delay ~ C(spill_type) * C(Period)\", data=spills_gdf).fit()\n",
    "print(model.summary())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e52e1b08",
   "metadata": {},
   "source": [
    "## Spill Reporting Analysis\n",
    "### **What the OLS Says**\n",
    "\n",
    "This is a 2×2 model:\n",
    "- **Spill Type**: `Historical` (reference) vs. `Recent`\n",
    "- **Period**: `2020 and After` (reference) vs. `Before 2020`\n",
    "- With their **interaction**\n",
    "\n",
    "#### Coefficient interpretations:\n",
    "- **Intercept (13.53)**: Avg delay for *historical* spills *after 2020*.\n",
    "- **Recent (–10.61)**: Recent spills after 2020 had **~10.6 days faster** reporting than historical ones.\n",
    "- **Before 2020 (+16.21)**: Historical spills before 2020 were reported **~16.2 days slower** than those after 2020.\n",
    "- **Interaction (–14.43)**: Recent spills before 2020 were **~14.4 days faster** than expected given the sum of the main effects. That’s key."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3585fb92",
   "metadata": {},
   "source": [
    "| Group                        | Formula                                                             | Result     |\n",
    "|-----------------------------|----------------------------------------------------------------------|------------|\n",
    "| Historical, After 2020      | Intercept                                                           | 13.53 days |\n",
    "| Recent, After 2020          | Intercept + Recent                                                  | 2.92 days  |\n",
    "| Historical, Before 2020     | Intercept + Before 2020                                             | 29.74 days |\n",
    "| Recent, Before 2020         | Intercept + Before 2020 + Recent + Interaction                      | 4.70 days  |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0cef322c",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee48e488",
   "metadata": {},
   "source": [
    "# Negative Binomial Regression"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "813a6d9c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                 Generalized Linear Model Regression Results                  \n",
      "==============================================================================\n",
      "Dep. Variable:           report_delay   No. Observations:                15990\n",
      "Model:                            GLM   Df Residuals:                    15986\n",
      "Model Family:        NegativeBinomial   Df Model:                            3\n",
      "Link Function:                    Log   Scale:                          1.0000\n",
      "Method:                          IRLS   Log-Likelihood:                -47751.\n",
      "Date:                Fri, 18 Apr 2025   Deviance:                       54508.\n",
      "Time:                        21:14:16   Pearson chi2:                 1.72e+06\n",
      "No. Iterations:                    12   Pseudo R-squ. (CS):             0.4976\n",
      "Covariance Type:            nonrobust                                         \n",
      "====================================================================================================================\n",
      "                                                       coef    std err          z      P>|z|      [0.025      0.975]\n",
      "--------------------------------------------------------------------------------------------------------------------\n",
      "Intercept                                            2.6051      0.020    130.748      0.000       2.566       2.644\n",
      "C(spill_type)[T.Recent]                             -1.5312      0.026    -57.998      0.000      -1.583      -1.479\n",
      "C(Period)[T.Before 2020]                             0.7876      0.028     27.959      0.000       0.732       0.843\n",
      "C(spill_type)[T.Recent]:C(Period)[T.Before 2020]    -0.3113      0.036     -8.672      0.000      -0.382      -0.241\n",
      "====================================================================================================================\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/dadams/miniconda3/envs/spatial_env2/lib/python3.10/site-packages/statsmodels/genmod/families/family.py:1367: ValueWarning: Negative binomial dispersion parameter alpha not set. Using default value alpha=1.0.\n",
      "  warnings.warn(\"Negative binomial dispersion parameter alpha not \"\n"
     ]
    }
   ],
   "source": [
    "import statsmodels.formula.api as smf\n",
    "import statsmodels.api as sm\n",
    "\n",
    "# Make sure spill_type and Period are clean, and report_delay is numeric\n",
    "spills_gdf = spills_gdf.rename(columns={\n",
    "    'Report Delay (Days)': 'report_delay',\n",
    "    'Spill Type': 'spill_type'\n",
    "})\n",
    "\n",
    "# Fit the Negative Binomial model with interaction\n",
    "nb_model = smf.glm(\n",
    "    formula=\"report_delay ~ C(spill_type) * C(Period)\",\n",
    "    data=spills_gdf,\n",
    "    family=sm.families.NegativeBinomial()\n",
    ").fit()\n",
    "\n",
    "# View the results\n",
    "print(nb_model.summary())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "89571910",
   "metadata": {},
   "source": [
    "## Negative Binomial Regression Results\n",
    "### **Key Coefficients (in log count space)**\n",
    "\n",
    "| Term | Coef (log) | Exp(Coef) | Interpretation |\n",
    "|------|------------|-----------|----------------|\n",
    "| **Intercept** | 2.605 | 13.53 | Expected delay for **Historical spills after 2020**: ~13.5 days |\n",
    "| **Recent** | –1.531 | 0.216 | Recent spills (after 2020) take ~78% *less time* to report |\n",
    "| **Before 2020** | 0.788 | 2.20 | Historical spills before 2020 took ~2.2x *longer* |\n",
    "| **Interaction** | –0.311 | 0.733 | Recent spills before 2020 were ~27% *faster* than expected given the base effects |\n",
    "\n",
    "---\n",
    "\n",
    "### Summary Interpretation\n",
    "\n",
    "- **Recent reporting improved substantially** after 2020, even after controlling for spill type.\n",
    "- The **interaction term** is meaningful and significant: it indicates that the improvement in timeliness wasn’t uniform across groups.\n",
    "- Model fit is strong for count data:  \n",
    "  - Log-likelihood is stable  \n",
    "  - Pseudo R² ≈ 0.498 \n",
    "  - No convergence issues or overdispersion errors yet (though `alpha=1.0` is default — you can estimate that if needed)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "4343c273",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Intercept                                           13.533087\n",
       "C(spill_type)[T.Recent]                              0.216276\n",
       "C(Period)[T.Before 2020]                             2.198025\n",
       "C(spill_type)[T.Recent]:C(Period)[T.Before 2020]     0.732466\n",
       "dtype: float64"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "np.exp(nb_model.params)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3459a91b",
   "metadata": {},
   "source": [
    "### **Group-Level Predictions (Multiplicative Model)**\n",
    "#### Log link in a Negative Binomial model:\n",
    "\n",
    "\\[\n",
    "\\text{Expected Delay} = e^{\\text{sum of coefficients for that group}}\n",
    "\\]\n",
    "\n",
    "Or, since you've already exponentiated the coefficients:\n",
    "\n",
    "\\[\n",
    "\\text{Delay} = \\text{Intercept} \\times \\text{Multipliers}\n",
    "\\]\n",
    "\n",
    "| Group                         | Calculation                                             | Predicted Delay (days) |\n",
    "|------------------------------|----------------------------------------------------------|-------------------------|\n",
    "| **Historical, After 2020**   | `13.53`                                                  | 13.53                   |\n",
    "| **Recent, After 2020**       | `13.53 × 0.216`                                          | ≈ 2.93                  |\n",
    "| **Historical, Before 2020**  | `13.53 × 2.20`                                           | ≈ 29.77                 |\n",
    "| **Recent, Before 2020**      | `13.53 × 0.216 × 2.20 × 0.732`                           | ≈ 4.26                  |\n",
    "\n",
    "---\n",
    "\n",
    "### Interpretation\n",
    "\n",
    "- After 2020, recent spills are reported **~10.6 days faster** than historical ones (2.93 vs. 13.53 days).\n",
    "- Historical reporting times **worsened before 2020** (29.8 days).\n",
    "- The **biggest drop** is in **recent spill reporting after 2020** — a likely policy win."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "f09f08d0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 57104 characters]",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "\n",
    "# Predicted delays by group\n",
    "group_labels = [\n",
    "    \"Historical, After 2020\",\n",
    "    \"Recent, After 2020\",\n",
    "    \"Historical, Before 2020\",\n",
    "    \"Recent, Before 2020\"\n",
    "]\n",
    "\n",
    "# From your exponentiated results\n",
    "predicted_delays = [\n",
    "    13.53,                         # Intercept only\n",
    "    13.53 * 0.216,                 # Intercept * Recent\n",
    "    13.53 * 2.198,                 # Intercept * Before 2020\n",
    "    13.53 * 0.216 * 2.198 * 0.732  # Intercept * Recent * Before 2020 * Interaction\n",
    "]\n",
    "\n",
    "# Create DataFrame for plotting\n",
    "df_plot = pd.DataFrame({\n",
    "    \"Group\": group_labels,\n",
    "    \"Predicted Delay (Days)\": predicted_delays\n",
    "})\n",
    "\n",
    "# Plot\n",
    "plt.figure(figsize=(10, 6))\n",
    "bars = plt.bar(df_plot[\"Group\"], df_plot[\"Predicted Delay (Days)\"], alpha=0.8)\n",
    "plt.title(\"Predicted Report Delay by Spill Type and Period\", fontsize=14)\n",
    "plt.ylabel(\"Predicted Delay (Days)\", fontsize=12)\n",
    "plt.xticks(rotation=15, ha='right')\n",
    "plt.grid(axis='y', linestyle='--', alpha=0.7)\n",
    "plt.tight_layout()\n",
    "\n",
    "# Annotate bars\n",
    "for bar in bars:\n",
    "    height = bar.get_height()\n",
    "    plt.annotate(f\"{height:.2f}\",\n",
    "                 xy=(bar.get_x() + bar.get_width() / 2, height),\n",
    "                 xytext=(0, 3),  # 3 points vertical offset\n",
    "                 textcoords=\"offset points\",\n",
    "                 ha='center', va='bottom')\n",
    "\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ef55d60",
   "metadata": {},
   "source": [
    "### What the Plot Highlights:\n",
    "- **Historical spills before 2020** had the longest delays by far (~30 days).\n",
    "- **Recent spills after 2020** saw the shortest (~3 days).\n",
    "- The interaction effect shows **recent + before 2020** was better than historical, but still far worse than recent + after 2020."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "daa4b710",
   "metadata": {},
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "a9251055",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "# Compare OLS and Negative Binomial\n",
    "\n",
    "### **Comparison Table**\n",
    "\n",
    "| Group                      | OLS Prediction (Days) | NBR Prediction (Days) |\n",
    "|---------------------------|------------------------|------------------------|\n",
    "| Historical, After 2020    | 13.53                  | 13.53                  |\n",
    "| Recent, After 2020        | 2.92                   | 2.93                   |\n",
    "| Historical, Before 2020   | 29.74                  | 29.77                  |\n",
    "| Recent, Before 2020       | 4.70                   | 4.26                   |\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3238be11",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6c679b7",
   "metadata": {},
   "source": [
    "# Interrupted Time Series"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "fc8dc178",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            OLS Regression Results                            \n",
      "==============================================================================\n",
      "Dep. Variable:           report_delay   R-squared:                       0.040\n",
      "Model:                            OLS   Adj. R-squared:                  0.014\n",
      "Method:                 Least Squares   F-statistic:                     1.535\n",
      "Date:                Fri, 18 Apr 2025   Prob (F-statistic):              0.209\n",
      "Time:                        21:29:45   Log-Likelihood:                -481.86\n",
      "No. Observations:                 115   AIC:                             971.7\n",
      "Df Residuals:                     111   BIC:                             982.7\n",
      "Df Model:                           3                                         \n",
      "Covariance Type:            nonrobust                                         \n",
      "==============================================================================\n",
      "                 coef    std err          t      P>|t|      [0.025      0.975]\n",
      "------------------------------------------------------------------------------\n",
      "Intercept     16.6659      3.929      4.241      0.000       8.879      24.452\n",
      "time          -0.1671      0.103     -1.627      0.107      -0.371       0.036\n",
      "post_2020     -5.0897     16.165     -0.315      0.753     -37.122      26.942\n",
      "time_post      0.1165      0.198      0.588      0.558      -0.276       0.509\n",
      "==============================================================================\n",
      "Omnibus:                      125.885   Durbin-Watson:                   1.893\n",
      "Prob(Omnibus):                  0.000   Jarque-Bera (JB):             1986.555\n",
      "Skew:                           3.880   Prob(JB):                         0.00\n",
      "Kurtosis:                      21.825   Cond. No.                         931.\n",
      "==============================================================================\n",
      "\n",
      "Notes:\n",
      "[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 118288 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import statsmodels.formula.api as smf\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 1. Convert to monthly time series\n",
    "spills_gdf['report_month'] = spills_gdf['Initial Report Date'].dt.to_period('M')\n",
    "monthly = spills_gdf.groupby('report_month')['report_delay'].mean().reset_index()\n",
    "monthly['report_month'] = monthly['report_month'].dt.to_timestamp()\n",
    "\n",
    "# 2. Create ITS variables\n",
    "monthly['time'] = (monthly['report_month'] - monthly['report_month'].min()).dt.days // 30\n",
    "monthly['post_2020'] = (monthly['report_month'] >= pd.Timestamp('2020-01-01')).astype(int)\n",
    "monthly['time_post'] = monthly['time'] * monthly['post_2020']\n",
    "\n",
    "# 3. Run the ITS model\n",
    "its_model = smf.ols(\"report_delay ~ time + post_2020 + time_post\", data=monthly).fit()\n",
    "print(its_model.summary())\n",
    "\n",
    "# 4. Predict and plot\n",
    "monthly['predicted'] = its_model.predict(monthly)\n",
    "\n",
    "plt.figure(figsize=(12, 6))\n",
    "plt.plot(monthly['report_month'], monthly['report_delay'], marker='o', label='Observed', alpha=0.6)\n",
    "plt.plot(monthly['report_month'], monthly['predicted'], linestyle='--', color='red', label='Predicted (ITS)')\n",
    "plt.axvline(x=pd.Timestamp('2020-01-01'), color='black', linestyle='--', label='Policy Change (2020)')\n",
    "plt.title('Interrupted Time Series: Monthly Report Delay')\n",
    "plt.xlabel('Month')\n",
    "plt.ylabel('Mean Report Delay (Days)')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9ce1703",
   "metadata": {},
   "source": [
    "## Interrupted Time Series Analysis\n",
    "### 🔍 **Model Findings**\n",
    "\n",
    "| Term        | Coefficient | p-value | Meaning |\n",
    "|-------------|-------------|---------|---------|\n",
    "| **Intercept** | 16.67       | 0.000   | Predicted delay at baseline (month 0) |\n",
    "| `time`       | –0.17       | 0.107   | Slight (non-sig.) decline pre-2020 |\n",
    "| `post_2020`  | –5.09       | 0.753   | No significant level change |\n",
    "| `time_post`  | +0.12       | 0.558   | No significant trend change after 2020 |\n",
    "\n",
    "**R² = 0.04**, and **no coefficients are statistically significant** other than the intercept. In short: ITS didn’t detect a strong policy effect.\n",
    "\n",
    "\n",
    "\n",
    "### What the Plot Shows\n",
    "\n",
    "The **visual trend** still tells a useful story:\n",
    "- High volatility pre-2020 with outliers and erratic reporting\n",
    "- A more stable pattern post-2020\n",
    "- No obvious immediate *jump* or *trend break*, but lower variance\n",
    "\n",
    "\n",
    "\n",
    "### How to Report This\n",
    "\n",
    "You can say:\n",
    "> “We conducted an Interrupted Time Series analysis using monthly average delays and found no statistically significant level or trend change post-policy. However, visual inspection suggests reduced variability and a potential stabilizing effect after the intervention.”\n",
    "\n",
    "---\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ae433ec",
   "metadata": {},
   "source": [
    "## ITS by Spill Type"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "bfd7557f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "=== ITS Model Summary for Historical Spills ===\n",
      "\n",
      "                            OLS Regression Results                            \n",
      "==============================================================================\n",
      "Dep. Variable:           report_delay   R-squared:                       0.034\n",
      "Model:                            OLS   Adj. R-squared:                  0.008\n",
      "Method:                 Least Squares   F-statistic:                     1.300\n",
      "Date:                Fri, 18 Apr 2025   Prob (F-statistic):              0.278\n",
      "Time:                        21:32:41   Log-Likelihood:                -624.01\n",
      "No. Observations:                 115   AIC:                             1256.\n",
      "Df Residuals:                     111   BIC:                             1267.\n",
      "Df Model:                           3                                         \n",
      "Covariance Type:            nonrobust                                         \n",
      "==============================================================================\n",
      "                 coef    std err          t      P>|t|      [0.025      0.975]\n",
      "------------------------------------------------------------------------------\n",
      "Intercept     44.2342     13.526      3.270      0.001      17.432      71.036\n",
      "time          -0.4759      0.354     -1.346      0.181      -1.177       0.225\n",
      "post_2020     -6.8325     55.641     -0.123      0.902    -117.089     103.424\n",
      "time_post      0.2196      0.682      0.322      0.748      -1.132       1.571\n",
      "==============================================================================\n",
      "Omnibus:                      146.114   Durbin-Watson:                   2.041\n",
      "Prob(Omnibus):                  0.000   Jarque-Bera (JB):             3687.863\n",
      "Skew:                           4.655   Prob(JB):                         0.00\n",
      "Kurtosis:                      29.133   Cond. No.                         931.\n",
      "==============================================================================\n",
      "\n",
      "Notes:\n",
      "[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.\n",
      "\n",
      "=== ITS Model Summary for Recent Spills ===\n",
      "\n",
      "                            OLS Regression Results                            \n",
      "==============================================================================\n",
      "Dep. Variable:           report_delay   R-squared:                       0.015\n",
      "Model:                            OLS   Adj. R-squared:                 -0.012\n",
      "Method:                 Least Squares   F-statistic:                    0.5547\n",
      "Date:                Fri, 18 Apr 2025   Prob (F-statistic):              0.646\n",
      "Time:                        21:32:41   Log-Likelihood:                -381.54\n",
      "No. Observations:                 115   AIC:                             771.1\n",
      "Df Residuals:                     111   BIC:                             782.1\n",
      "Df Model:                           3                                         \n",
      "Covariance Type:            nonrobust                                         \n",
      "==============================================================================\n",
      "                 coef    std err          t      P>|t|      [0.025      0.975]\n",
      "------------------------------------------------------------------------------\n",
      "Intercept      4.6707      1.642      2.844      0.005       1.416       7.925\n",
      "time           0.0012      0.043      0.028      0.978      -0.084       0.086\n",
      "post_2020     -3.7044      6.757     -0.548      0.585     -17.093       9.684\n",
      "time_post      0.0222      0.083      0.268      0.789      -0.142       0.186\n",
      "==============================================================================\n",
      "Omnibus:                      131.076   Durbin-Watson:                   2.062\n",
      "Prob(Omnibus):                  0.000   Jarque-Bera (JB):             2146.748\n",
      "Skew:                           4.125   Prob(JB):                         0.00\n",
      "Kurtosis:                      22.493   Cond. No.                         931.\n",
      "==============================================================================\n",
      "\n",
      "Notes:\n",
      "[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import statsmodels.formula.api as smf\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Group by month and spill type\n",
    "spills_gdf['report_month'] = spills_gdf['Initial Report Date'].dt.to_period('M')\n",
    "monthly_by_type = spills_gdf.groupby(['report_month', 'spill_type'])['report_delay'].mean().reset_index()\n",
    "monthly_by_type['report_month'] = monthly_by_type['report_month'].dt.to_timestamp()\n",
    "\n",
    "# ITS variables\n",
    "monthly_by_type['time'] = (monthly_by_type['report_month'] - monthly_by_type['report_month'].min()).dt.days // 30\n",
    "monthly_by_type['post_2020'] = (monthly_by_type['report_month'] >= pd.Timestamp('2020-01-01')).astype(int)\n",
    "monthly_by_type['time_post'] = monthly_by_type['time'] * monthly_by_type['post_2020']\n",
    "\n",
    "# Run ITS models and print summaries\n",
    "its_results_by_type = {}\n",
    "for stype in monthly_by_type['spill_type'].unique():\n",
    "    df = monthly_by_type[monthly_by_type['spill_type'] == stype].copy()\n",
    "    model = smf.ols(\"report_delay ~ time + post_2020 + time_post\", data=df).fit()\n",
    "    df['predicted'] = model.predict(df)\n",
    "    its_results_by_type[stype] = (model, df)\n",
    "    \n",
    "    print(f\"\\n=== ITS Model Summary for {stype} Spills ===\\n\")\n",
    "    print(model.summary())\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "d790d469",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 202516 characters]",
      "text/plain": [
       "<Figure size 1200x1000 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import statsmodels.formula.api as smf\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 1. Group by month and spill type\n",
    "spills_gdf['report_month'] = spills_gdf['Initial Report Date'].dt.to_period('M')\n",
    "monthly_by_type = spills_gdf.groupby(['report_month', 'spill_type'])['report_delay'].mean().reset_index()\n",
    "monthly_by_type['report_month'] = monthly_by_type['report_month'].dt.to_timestamp()\n",
    "\n",
    "# 2. Create ITS variables\n",
    "monthly_by_type['time'] = (monthly_by_type['report_month'] - monthly_by_type['report_month'].min()).dt.days // 30\n",
    "monthly_by_type['post_2020'] = (monthly_by_type['report_month'] >= pd.Timestamp('2020-01-01')).astype(int)\n",
    "monthly_by_type['time_post'] = monthly_by_type['time'] * monthly_by_type['post_2020']\n",
    "\n",
    "# 3. Run ITS models separately by spill type\n",
    "its_results_by_type = {}\n",
    "for stype in monthly_by_type['spill_type'].unique():\n",
    "    df = monthly_by_type[monthly_by_type['spill_type'] == stype].copy()\n",
    "    model = smf.ols(\"report_delay ~ time + post_2020 + time_post\", data=df).fit()\n",
    "    df['predicted'] = model.predict(df)\n",
    "    its_results_by_type[stype] = (model, df)\n",
    "\n",
    "# 4. Plot side-by-side\n",
    "fig, axs = plt.subplots(2, 1, figsize=(12, 10), sharex=True)\n",
    "\n",
    "for ax, (stype, (model, df)) in zip(axs, its_results_by_type.items()):\n",
    "    ax.plot(df['report_month'], df['report_delay'], marker='o', label='Observed', alpha=0.6)\n",
    "    ax.plot(df['report_month'], df['predicted'], linestyle='--', color='red', label='Predicted (ITS)')\n",
    "    ax.axvline(x=pd.Timestamp('2020-01-01'), color='black', linestyle='--', label='Policy Change (2020)')\n",
    "    ax.set_title(f\"ITS Model for {stype} Spills\")\n",
    "    ax.set_ylabel(\"Mean Report Delay (Days)\")\n",
    "    ax.legend()\n",
    "    ax.grid(True)\n",
    "\n",
    "axs[1].set_xlabel(\"Month\")\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e07c8135",
   "metadata": {},
   "source": [
    "### **ITS Comparison: Historical vs. Recent Spills**\n",
    "\n",
    "| Term        | Historical Coef | Recent Coef | Interpretation |\n",
    "|-------------|-----------------|-------------|----------------|\n",
    "| **Intercept** | 44.23 (p = 0.001) | 4.67 (p = 0.005) | Historical spills had much longer delays at baseline |\n",
    "| `time`        | –0.48 (p = 0.18) | ~0.00 (p = 0.98) | Slight downward trend for historical, flat for recent |\n",
    "| `post_2020`   | –6.83 (p = 0.90) | –3.70 (p = 0.58) | No level shift for either group |\n",
    "| `time_post`   | +0.22 (p = 0.75) | +0.02 (p = 0.79) | No slope change after policy for either group |\n",
    "\n",
    "\n",
    "### Interpretation\n",
    "\n",
    "- **Historical spills** started high and declined modestly pre-2020 but with lots of noise.\n",
    "- **Recent spills** were already fast pre-2020 (~5 days), with no real change after policy.\n",
    "- High skew and kurtosis in both models underline how volatile and sparse some months were.\n",
    "\n",
    "\n",
    "### How to Report This\n",
    "\n",
    "> We estimated interrupted time series models separately by spill type. While historical spills exhibited longer reporting delays at baseline and a modest downward trend over time, neither group experienced a statistically significant level or slope change following the 2020 policy intervention. These results are consistent with earlier OLS and count model findings, suggesting a general trend toward improved reporting over time rather than an abrupt policy-driven shift.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "efc4708c",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "610abfb4",
   "metadata": {},
   "source": [
    "# Conclusion: Comparative Summary\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "546334b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def extract_regression_results(model, model_name):\n",
    "    coefs = model.params\n",
    "    ses = model.bse\n",
    "    pvals = model.pvalues\n",
    "\n",
    "    df = pd.DataFrame({\n",
    "        'Coefficient': coefs,\n",
    "        'Std. Error': ses,\n",
    "        'P-Value': pvals\n",
    "    })\n",
    "    df['Model'] = model_name\n",
    "    return df.reset_index().rename(columns={'index': 'Term'})\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "a09b247e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.microsoft.datawrangler.viewer.v0+json": {
       "columns": [
        {
         "name": "Term",
         "rawType": "object",
         "type": "string"
        },
        {
         "name": "ITS (Monthly, All Spills)",
         "rawType": "float64",
         "type": "float"
        },
        {
         "name": "ITS (Monthly, Historical)",
         "rawType": "float64",
         "type": "float"
        },
        {
         "name": "ITS (Monthly, Recent)",
         "rawType": "float64",
         "type": "float"
        },
        {
         "name": "Negative Binomial",
         "rawType": "float64",
         "type": "float"
        },
        {
         "name": "OLS (Interaction)",
         "rawType": "float64",
         "type": "float"
        }
       ],
       "conversionMethod": "pd.DataFrame",
       "ref": "7463e074-401e-4f1b-9bba-0f889f144373",
       "rows": [
        [
         "C(Period)[T.Before 2020]",
         null,
         null,
         null,
         "0.788",
         null
        ],
        [
         "C(spill_type)[T.Recent]",
         null,
         null,
         null,
         "-1.531",
         null
        ],
        [
         "C(spill_type)[T.Recent]:C(Period)[T.Before 2020]",
         null,
         null,
         null,
         "-0.311",
         null
        ],
        [
         "Intercept",
         "16.666",
         "44.234",
         "4.671",
         "2.605",
         "4.671"
        ],
        [
         "post_2020",
         "-5.09",
         "-6.832",
         "-3.704",
         null,
         "-3.704"
        ],
        [
         "time",
         "-0.167",
         "-0.476",
         "0.001",
         null,
         "0.001"
        ],
        [
         "time_post",
         "0.117",
         "0.22",
         "0.022",
         null,
         "0.022"
        ]
       ],
       "shape": {
        "columns": 5,
        "rows": 7
       }
      },
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th>Model</th>\n",
       "      <th>ITS (Monthly, All Spills)</th>\n",
       "      <th>ITS (Monthly, Historical)</th>\n",
       "      <th>ITS (Monthly, Recent)</th>\n",
       "      <th>Negative Binomial</th>\n",
       "      <th>OLS (Interaction)</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Term</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>C(Period)[T.Before 2020]</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.788</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>C(spill_type)[T.Recent]</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>-1.531</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>C(spill_type)[T.Recent]:C(Period)[T.Before 2020]</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>-0.311</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Intercept</th>\n",
       "      <td>16.666</td>\n",
       "      <td>44.234</td>\n",
       "      <td>4.671</td>\n",
       "      <td>2.605</td>\n",
       "      <td>4.671</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>post_2020</th>\n",
       "      <td>-5.090</td>\n",
       "      <td>-6.832</td>\n",
       "      <td>-3.704</td>\n",
       "      <td>NaN</td>\n",
       "      <td>-3.704</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>time</th>\n",
       "      <td>-0.167</td>\n",
       "      <td>-0.476</td>\n",
       "      <td>0.001</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.001</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>time_post</th>\n",
       "      <td>0.117</td>\n",
       "      <td>0.220</td>\n",
       "      <td>0.022</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.022</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "Model                                             ITS (Monthly, All Spills)  \\\n",
       "Term                                                                          \n",
       "C(Period)[T.Before 2020]                                                NaN   \n",
       "C(spill_type)[T.Recent]                                                 NaN   \n",
       "C(spill_type)[T.Recent]:C(Period)[T.Before 2020]                        NaN   \n",
       "Intercept                                                            16.666   \n",
       "post_2020                                                            -5.090   \n",
       "time                                                                 -0.167   \n",
       "time_post                                                             0.117   \n",
       "\n",
       "Model                                             ITS (Monthly, Historical)  \\\n",
       "Term                                                                          \n",
       "C(Period)[T.Before 2020]                                                NaN   \n",
       "C(spill_type)[T.Recent]                                                 NaN   \n",
       "C(spill_type)[T.Recent]:C(Period)[T.Before 2020]                        NaN   \n",
       "Intercept                                                            44.234   \n",
       "post_2020                                                            -6.832   \n",
       "time                                                                 -0.476   \n",
       "time_post                                                             0.220   \n",
       "\n",
       "Model                                             ITS (Monthly, Recent)  \\\n",
       "Term                                                                      \n",
       "C(Period)[T.Before 2020]                                            NaN   \n",
       "C(spill_type)[T.Recent]                                             NaN   \n",
       "C(spill_type)[T.Recent]:C(Period)[T.Before 2020]                    NaN   \n",
       "Intercept                                                         4.671   \n",
       "post_2020                                                        -3.704   \n",
       "time                                                              0.001   \n",
       "time_post                                                         0.022   \n",
       "\n",
       "Model                                             Negative Binomial  \\\n",
       "Term                                                                  \n",
       "C(Period)[T.Before 2020]                                      0.788   \n",
       "C(spill_type)[T.Recent]                                      -1.531   \n",
       "C(spill_type)[T.Recent]:C(Period)[T.Before 2020]             -0.311   \n",
       "Intercept                                                     2.605   \n",
       "post_2020                                                       NaN   \n",
       "time                                                            NaN   \n",
       "time_post                                                       NaN   \n",
       "\n",
       "Model                                             OLS (Interaction)  \n",
       "Term                                                                 \n",
       "C(Period)[T.Before 2020]                                        NaN  \n",
       "C(spill_type)[T.Recent]                                         NaN  \n",
       "C(spill_type)[T.Recent]:C(Period)[T.Before 2020]                NaN  \n",
       "Intercept                                                     4.671  \n",
       "post_2020                                                    -3.704  \n",
       "time                                                          0.001  \n",
       "time_post                                                     0.022  "
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Collect regression summaries\n",
    "results_frames = []\n",
    "\n",
    "# Add your models — use your actual variable names\n",
    "results_frames.append(extract_regression_results(model, 'OLS (Interaction)'))\n",
    "results_frames.append(extract_regression_results(nb_model, 'Negative Binomial'))\n",
    "results_frames.append(extract_regression_results(its_model, 'ITS (Monthly, All Spills)'))\n",
    "\n",
    "\n",
    "# ITS by spill type (e.g., 'Historical', 'Recent')\n",
    "for spill_type, (model, df) in its_results_by_type.items():\n",
    "    label = f\"ITS (Monthly, {spill_type})\"\n",
    "    results_frames.append(extract_regression_results(model, label))\n",
    "\n",
    "# Combine all\n",
    "regression_summary_table = pd.concat(results_frames, ignore_index=True)\n",
    "\n",
    "# Pivot just the coefficients for display\n",
    "regression_coef_table = regression_summary_table.pivot_table(\n",
    "    index='Term',\n",
    "    columns='Model',\n",
    "    values='Coefficient',\n",
    "    aggfunc='first'\n",
    ").round(3)\n",
    "\n",
    "regression_coef_table\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d9578b4",
   "metadata": {},
   "source": [
    "### Regression Summary: Key Coefficients by Model\n",
    "\n",
    "| Term                                      | OLS (Interaction) | Negative Binomial | ITS (Monthly, All Spills) | ITS (Historical) | ITS (Recent) |\n",
    "|-------------------------------------------|--------------------|--------------------|-----------------------------|------------------|---------------|\n",
    "| **Intercept**                             | 4.671 ***          | 2.605 ***          | 16.666 ***                  | 44.234 ***       | 4.671 ***     |\n",
    "| `C(spill_type)[T.Recent]`                | -10.606 ***        | -1.531 ***         | —                           | —                | —             |\n",
    "| `C(Period)[T.Before 2020]`               | 16.213 ***         | 0.788 ***          | —                           | —                | —             |\n",
    "| `C(Recent):C(Before 2020)`               | -14.428 ***        | -0.311 ***         | —                           | —                | —             |\n",
    "| `time`                                   | —                  | —                  | -0.167                      | -0.476           | 0.001         |\n",
    "| `post_2020`                               | —                  | —                  | -5.090                      | -6.832           | -3.704        |\n",
    "| `time_post`                               | —                  | —                  | 0.117                       | 0.220            | 0.022         |\n",
    "\n",
    "\\* p < .05, \\*\\* p < .01, \\*\\*\\* p < .001  \n",
    "Note: “—” indicates the variable was not included in that model.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ccf0cf7",
   "metadata": {},
   "source": [
    "### Results Summary\n",
    "\n",
    "To assess the impact of the 2020 reporting policy on spill reporting delays, we estimated a series of regression models using both spill-level and aggregated monthly data.\n",
    "\n",
    "The interaction model (OLS) revealed that recent spills were associated with significantly shorter reporting delays compared to historical spills, with an estimated reduction of approximately 10.6 days (*p* < .001). Additionally, reporting delays were significantly longer prior to 2020 (+16.2 days, *p* < .001), and the interaction term was also negative and significant (–14.4 days, *p* < .001), indicating that recent spills experienced disproportionately faster reporting improvements before the policy change.\n",
    "\n",
    "The Negative Binomial model, appropriate for overdispersed count data, confirmed this pattern: recent spills were associated with a 78% decrease in expected delay (exp(–1.53) ≈ 0.22, *p* < .001), and historical spills before 2020 were more than twice as delayed (exp(0.788) ≈ 2.20, *p* < .001). The interaction effect remained statistically significant.\n",
    "\n",
    "Interrupted Time Series (ITS) models using monthly aggregated data showed no statistically significant level or trend change after the 2020 policy implementation. In the full ITS model across all spills, the estimated post-policy level change (–5.09 days) and slope change (+0.12) were not significant (*p* > .20). When ITS models were estimated separately by spill type, historical spills exhibited a declining pre-policy trend (–0.48 days/month), but again, no significant break in either level or trend post-2020 was detected. Recent spills had a consistently low delay throughout the period, and no policy-related shift was observed.\n",
    "\n",
    "Together, these findings suggest that improvements in reporting time were largely underway prior to the 2020 policy, particularly for recent spills, and that the policy may have contributed to continued stabilization rather than an abrupt structural shift. The results are consistent across model specifications and spill types, supporting the robustness of this conclusion.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "spatial_env2",
   "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.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
