{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import scipy.linalg as sl\n",
    "import scipy.stats as ss\n",
    "\n",
    "from copy import deepcopy\n",
    "import tqdm\n",
    "\n",
    "# additionally requires libcpab (https://github.com/SkafteNicki/libcpab)\n",
    "import nwarp"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 13324 characters]\n",
      "text/plain": [
       "<Figure size 720x216 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Please download the official German Covid-19 data from Robert Koch Institut (https://www.rki.de/EN/) from\n",
    "#    https://npgeo-corona-npgeo-de.hub.arcgis.com/datasets/dd4580c810204019a7b8eb3e0b329dd6_0\n",
    "# manually via the button 'Herunterladen' -> 'Tabelle' and save it in the current working\n",
    "# directory as 'RKI_COVID19.csv'. Alternatively, you can set the variable 'download' below to True to\n",
    "# fetch the data automatically.\n",
    "# The dataset contains official numbers of reported cases in Germany; the number at day t\n",
    "# reflects reported cases at day t-1.\n",
    "\n",
    "download = False\n",
    "if download:\n",
    "    import urllib.request\n",
    "    dl_counter = tqdm.tqdm(desc=\"downloading\")\n",
    "    update_hook = lambda chk_nr, chk_sz, sz: dl_counter.update(chk_nr*chk_sz)\n",
    "    urllib.request.urlretrieve('https://opendata.arcgis.com/datasets/dd4580c810204019a7b8eb3e0b329dd6_0.csv',\n",
    "                               'RKI_COVID19.csv', reporthook=update_hook)\n",
    "    dl_counter.close()\n",
    "\n",
    "rawdata = pd.read_csv(\"RKI_COVID19.csv\", index_col=0, parse_dates=[\"Meldedatum\"])\n",
    "time_period = pd.date_range(start=\"2020-02-24\", end=\"2020-05-24\")\n",
    "rawdata = rawdata[[\"Meldedatum\", \"AnzahlFall\"]].groupby(\"Meldedatum\").sum().squeeze()[time_period]\n",
    "\n",
    "plt.figure(figsize=(10,3))\n",
    "plt.bar(range(len(rawdata)), rawdata, lw=1, alpha=1)\n",
    "plt.xlim(-0.5,len(rawdata)-0.5)\n",
    "plt.ylabel(\"new cases\")\n",
    "plt.xticks(range(0, len(rawdata), 7), labels=[t.date() for t in time_period[::7]], rotation=-45, ha=\"left\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# use the raw data without any preprocessing\n",
    "data = rawdata.values\n",
    "\n",
    "# log-likelihood (LL) under a saturated Poisson model\n",
    "# with one parameter per observation (\\lambda_i = y_i)\n",
    "LL_sat = np.sum(ss.poisson.logpmf(data, data))\n",
    "\n",
    "# define targets y_t=data[t]\n",
    "targets = torch.from_numpy(data.astype(np.float32)).reshape(1,-1,1)\n",
    "N, T, Dout = targets.shape\n",
    "\n",
    "# define covariates z_t=[onehot(day of week, excluding Monday), 1, t]\n",
    "covariates = torch.cat([torch.nn.functional.one_hot(torch.from_numpy(time_period.dayofweek.values))[:,1:].float().unsqueeze(0),\n",
    "                        torch.ones(1, T, 1),\n",
    "                        torch.linspace(0, T-1, T).reshape(1,-1,1)],\n",
    "                        dim=2).expand(N, -1, -1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Segmented Models"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Define model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "ParameterWarp(\n",
      "  (warp): TSPStepWarp(\n",
      "    (loc_net): Constant()\n",
      "  )\n",
      "  (resample): Resample()\n",
      ")\n",
      "GeneralizedLinearModel(\n",
      "  (invlink_func): Exp()\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "# number of segments\n",
    "K = 4\n",
    "\n",
    "# Poisson regression: GLM with logarithmic link function (exponential inverse link function)\n",
    "glm = nwarp.GeneralizedLinearModel(Dout, invlink_func=nwarp.Exp())\n",
    "\n",
    "# TSP-based parameter warping with constant mode vector\n",
    "Dparamg = 6 # number of global parameters (same in every segment)\n",
    "Dparaml = 2 # number of local parameters (different in every segment)\n",
    "paramwarpTSPB = nwarp.ParameterWarp(K, Dparamg, Dparaml,\n",
    "                                    nwarp.TSPStepWarp(nwarp.Constant((K-1,)),\n",
    "                                                      width=0.5, power=16.,\n",
    "                                                      min_step=0.0001, max_step=0.9999))\n",
    "\n",
    "paramwarpCPAB = nwarp.ParameterWarp(K, Dparamg, Dparaml,\n",
    "                       nwarp.CPABWarp(K+1, nwarp.Constant((K,))))\n",
    "\n",
    "paramwarpVanilla = nwarp.ParameterWarp(K, Dparamg, Dparaml,\n",
    "                       nwarp.VanillaWarp(nwarp.Constant((T-1,)), nonneg_trans='square'))\n",
    "\n",
    "paramwarp = paramwarpTSPB\n",
    "\n",
    "print(paramwarp)\n",
    "print(glm)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_restarts    = 10 # number of randomized restarts\n",
    "n_epochs      = 10000\n",
    "n_epochs_hard = 2048\n",
    "show_plots    = True\n",
    "\n",
    "loss_fn       = nn.PoissonNLLLoss(log_input=False, full=True, reduction='sum') # with invlink_func = nwarp.Exp()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "restart  1/10: 100%|██████████| 10000/10000 [00:32<00:00, 304.07it/s, loss=2.25e+3]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 46776 characters]\n",
      "text/plain": [
       "<Figure size 1080x144 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "restart  2/10: 100%|██████████| 10000/10000 [00:32<00:00, 309.00it/s, loss=2.26e+3]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 40860 characters]\n",
      "text/plain": [
       "<Figure size 1080x144 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "restart  3/10: 100%|██████████| 10000/10000 [00:32<00:00, 312.30it/s, loss=1.46e+3]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 48416 characters]\n",
      "text/plain": [
       "<Figure size 1080x144 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "restart  4/10: 100%|██████████| 10000/10000 [00:33<00:00, 301.61it/s, loss=1.32e+3]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 47076 characters]\n",
      "text/plain": [
       "<Figure size 1080x144 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "restart  5/10: 100%|██████████| 10000/10000 [00:35<00:00, 281.79it/s, loss=1.33e+4]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 31304 characters]\n",
      "text/plain": [
       "<Figure size 1080x144 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "restart  6/10: 100%|██████████| 10000/10000 [00:33<00:00, 298.34it/s, loss=1.83e+3]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 46088 characters]\n",
      "text/plain": [
       "<Figure size 1080x144 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "restart  7/10: 100%|██████████| 10000/10000 [00:32<00:00, 312.16it/s, loss=1.36e+3]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 43588 characters]\n",
      "text/plain": [
       "<Figure size 1080x144 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "restart  8/10: 100%|██████████| 10000/10000 [00:31<00:00, 315.16it/s, loss=1.33e+3]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 45312 characters]\n",
      "text/plain": [
       "<Figure size 1080x144 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "restart  9/10: 100%|██████████| 10000/10000 [00:32<00:00, 312.48it/s, loss=1.87e+4]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 29536 characters]\n",
      "text/plain": [
       "<Figure size 1080x144 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "restart 10/10: 100%|██████████| 10000/10000 [00:30<00:00, 322.64it/s, loss=1.87e+3]\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 45644 characters]\n",
      "text/plain": [
       "<Figure size 1080x144 with 3 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "best loss = 1322.0264\n"
     ]
    }
   ],
   "source": [
    "best_loss = np.inf\n",
    "for r in range(n_restarts):\n",
    "    # reset everything\n",
    "    optimizer = torch.optim.Adam(paramwarp.parameters(), lr=1e-2)\n",
    "    param_norm = []\n",
    "    grad_norm = []\n",
    "    train_losses = []\n",
    "    resample_kernel = \"linear\"\n",
    "    epoch_counter = tqdm.tqdm(range(n_epochs), desc=f\"restart {(r+1):2d}/{n_restarts:2d}\")\n",
    "\n",
    "    # initialize GLM parameters in reasonable range\n",
    "    nn.init.uniform_(paramwarp.thetag, -0.1, 0.2) # day of week\n",
    "    nn.init.uniform_(paramwarp.thetal[:,0], 3, 10) # bias\n",
    "    nn.init.uniform_(paramwarp.thetal[:,1], -0.1, 0.2) # time\n",
    "    \n",
    "    # initialize segmentation\n",
    "    try:\n",
    "        # for TSP-based and CPA-based warping\n",
    "        nn.init.uniform_(paramwarp.warp.loc_net.const, -1., 1.)\n",
    "    except AttributeError:\n",
    "        _ = paramwarp.apply(nwarp.reset_parameters)   \n",
    "\n",
    "    # perform training\n",
    "    paramwarp.train()\n",
    "    for epoch in epoch_counter:\n",
    "        optimizer.zero_grad()\n",
    "\n",
    "        if epoch == n_epochs - n_epochs_hard:\n",
    "            resample_kernel = \"integer\"\n",
    "\n",
    "        param_hat = paramwarp(targets, resample_kernel=resample_kernel)[0]\n",
    "        y_hat = glm(covariates, param_hat)\n",
    "        loss = loss_fn(y_hat, targets)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        train_losses.append(loss.item())\n",
    "        param_norm.append([sl.norm(p.detach()) for p in paramwarp.parameters()])\n",
    "        grad_norm.append([sl.norm(p.grad.detach()) for p in paramwarp.parameters()])\n",
    "        epoch_counter.set_postfix({\"loss\":loss.item()})\n",
    "\n",
    "    if train_losses[-1] < best_loss:\n",
    "        best_paramwarp_state = deepcopy(paramwarp.state_dict())\n",
    "        best_loss = train_losses[-1]\n",
    "\n",
    "    if show_plots:\n",
    "        plt.figure(figsize=(15,2))\n",
    "        plt.subplot(131)\n",
    "        plt.title(\"loss\")\n",
    "        plt.ylim(np.min(train_losses), np.percentile(train_losses, 95))\n",
    "        plt.plot(train_losses)\n",
    "\n",
    "        plt.subplot(132)\n",
    "        plt.title(\"parameter norm\")\n",
    "        normalized_param_norm = np.array(param_norm)/np.array(param_norm).max(axis=0)\n",
    "        if not np.isnan(normalized_param_norm).any():\n",
    "            lines = plt.plot(normalized_param_norm)\n",
    "            plt.legend(lines, [\" x \".join([str(d) for d in p.size()]) for p in paramwarp.parameters() if len(p)>0])\n",
    "\n",
    "        plt.subplot(133)\n",
    "        plt.title(\"gradient norm\")\n",
    "        normalized_grad_norm = (np.array(grad_norm)/np.array(grad_norm).max(axis=0))\n",
    "        if not np.isnan(normalized_grad_norm).any():\n",
    "            lines = plt.plot(normalized_grad_norm)\n",
    "            plt.legend(lines, [\" x \".join([str(d) for d in p.size()]) for p in paramwarp.parameters() if len(p)>0])\n",
    "            plt.ylim(np.min(normalized_grad_norm), np.percentile(normalized_grad_norm, 95))\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "paramwarp.eval()\n",
    "paramwarp.load_state_dict(best_paramwarp_state)\n",
    "print(f\"best loss = {best_loss:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 36716 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "break points at:\n",
      "    2020-03-16\n",
      "    2020-03-30\n",
      "    2020-04-25\n",
      "\n",
      "incidence rate ratios: (exponentiated Poisson coefficients)\n",
      "  time, daily growth rate, per segment\n",
      "    (S1) = 1.3118\n",
      "    (S2) = 1.0603\n",
      "    (S3) = 0.9514\n",
      "    (S4) = 0.9626\n",
      "  day of week\n",
      "    (Mo) := 1.0000 (baseline)\n",
      "    (Tu)  = 1.4266\n",
      "    (We)  = 1.6050\n",
      "    (Th)  = 1.6139\n",
      "    (Fr)  = 1.4770\n",
      "    (Sa)  = 1.1243\n",
      "    (Su)  = 0.7088\n",
      "    mean  = 1.2337 (geometric)\n"
     ]
    }
   ],
   "source": [
    "param_hat, almat_hat, gamma_hat = paramwarp(targets, resample_kernel=resample_kernel)\n",
    "y_hat = glm(covariates, param_hat)\n",
    "\n",
    "bps = almat_hat[0].sum(dim=0).cumsum(dim=0)[:-1].detach()\n",
    "segments = torch.cat((torch.Tensor([0]), bps, torch.Tensor([T]))).int()\n",
    "for bp in bps:\n",
    "    plt.axvline(bp-0.5, color=\"black\", lw=1, ls=\"--\")\n",
    "plt.bar(range(T), data, lw=1, alpha=0.25)\n",
    "for i in range(len(segments)-1):\n",
    "    plt.plot(range(segments[i], segments[i+1]), y_hat[0,range(segments[i], segments[i+1]),0].detach(),\n",
    "             color=\"steelblue\", lw=3)\n",
    "plt.xlim(-0.5,T-0.5)\n",
    "plt.ylabel(\"new cases\")\n",
    "plt.xticks(range(0, T, 7), labels=[t.date() for t in time_period[::7]], rotation=-45, ha=\"left\")\n",
    "\n",
    "# average day of week effect (geometric mean, including effect of log(1)=0 for Mo)\n",
    "no_dow_covariates = deepcopy(covariates[:,:,6:].detach())\n",
    "no_dow_param_hat = deepcopy(param_hat[:,:,6:].detach())\n",
    "no_dow_param_hat[0,:,0] += torch.cat((paramwarp.thetag.detach(), torch.Tensor([0]))).mean()\n",
    "no_dow_y_hat = glm(no_dow_covariates, no_dow_param_hat).detach()\n",
    "for i in range(len(segments)-1):\n",
    "    plt.plot(range(segments[i], segments[i+1]), no_dow_y_hat[0,range(segments[i], segments[i+1]),0],\n",
    "             color=\"black\", lw=1)\n",
    "\n",
    "plt.show()\n",
    "\n",
    "print(\"break points at:\")\n",
    "for i, bp in enumerate(bps.int()):\n",
    "    print(f\"    {time_period[bp.item()].date()}\")\n",
    "print()\n",
    "print(\"incidence rate ratios: (exponentiated Poisson coefficients)\")\n",
    "print(\"  time, daily growth rate, per segment\")\n",
    "for s, beta in enumerate(torch.exp(paramwarp.thetal[:,1].detach())):\n",
    "    print(f\"    (S{s+1}) = {beta:.4f}\")\n",
    "print(\"  day of week\")\n",
    "print(f\"    (Mo) := 1.0000 (baseline)\")\n",
    "days = [\"Tu\", \"We\", \"Th\", \"Fr\", \"Sa\", \"Su\"]\n",
    "for d, irr in enumerate(torch.exp(paramwarp.thetag).detach()):\n",
    "    print(f\"    ({days[d]})  = {irr:.4f}\")\n",
    "print(f\"    mean  = {torch.exp(torch.cat((paramwarp.thetag.detach(), torch.Tensor([0]))).mean()):.4f} (geometric)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model diagnostics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LL sat   = -393.2237\n",
      "LL GLM   = -1322.1016\n",
      "deviance = 1857.7559 (residual df=77)\n",
      "  (1/df) = 24.1267\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 11884 characters]\n",
      "text/plain": [
       "<Figure size 648x144 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 31604 characters]\n",
      "text/plain": [
       "<Figure size 648x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# log likelihood\n",
    "LL_glm = np.sum(ss.poisson.logpmf(targets[0,:,0].detach(), y_hat[0,:,0].detach()))\n",
    "\n",
    "# deviance\n",
    "D_glm = 2*(LL_sat-LL_glm)\n",
    "\n",
    "# residual degress of freedom\n",
    "dof_glm = T-Dparaml*K-Dparamg\n",
    "\n",
    "# deviance residuals (McCullagh & Nelder 1989, section 2.4)\n",
    "# sanity check: D_glm = np.sum(np.square(D_glm_resid))\n",
    "D_glm_resid = (torch.sign(targets[0,:,0]-y_hat[0,:,0])\n",
    "                * torch.sqrt(2*(targets[0,:,0]*torch.log(targets[0,:,0]/y_hat[0,:,0])\n",
    "                                 - targets[0,:,0] + y_hat[0,:,0]))).detach().numpy()\n",
    "\n",
    "print(\"LL sat   = %.4f\" % LL_sat)\n",
    "print(\"LL GLM   = %.4f\" % LL_glm)\n",
    "print(\"deviance = %.4f (residual df=%d)\" % (D_glm, dof_glm))\n",
    "print(\"  (1/df) = %.4f\" % (D_glm/dof_glm))\n",
    "\n",
    "# McCullagh & Nelder (1989, section 12.6.1)\n",
    "plt.figure(figsize=(9,2))\n",
    "plt.title(\"A\")\n",
    "plt.xlabel(\"predictor (time)\")\n",
    "plt.ylabel(\"residual\")\n",
    "for i in range(len(segments)-1):\n",
    "    plt.bar(np.linspace(0,T-1,T)[range(segments[i], segments[i+1])],\n",
    "            D_glm_resid[range(segments[i], segments[i+1])])\n",
    "plt.xlim(-0.5,T-0.5)\n",
    "plt.xticks(range(0, T, 7), labels=[t.date() for t in time_period[::7]], rotation=-45, ha=\"left\")\n",
    "plt.show()\n",
    "\n",
    "# McCullagh & Nelder (1989, sections 12.6.1 and 12.6.3)\n",
    "plt.figure(figsize=(9,4))\n",
    "plt.subplot(121)\n",
    "plt.title(\"B\")\n",
    "plt.xlabel(\"(transformed) prediction\")\n",
    "plt.ylabel(\"residual\")\n",
    "y_hat_transformed = 2*np.sqrt(y_hat[0,:,0].detach())\n",
    "for i in range(len(segments)-1):\n",
    "    plt.scatter(y_hat_transformed[range(segments[i], segments[i+1])],\n",
    "                D_glm_resid[range(segments[i], segments[i+1])],\n",
    "                label=\"%d\"%(i+1), alpha=0.75)\n",
    "plt.axhline(0, ls=\"--\", lw=0.5, c=\"black\")\n",
    "plt.legend()\n",
    "plt.subplot(122)\n",
    "plt.title(\"C\")\n",
    "plt.xlabel(\"log(targets)\")\n",
    "plt.ylabel(\"log(prediction)\")\n",
    "plt.plot(np.linspace(1,8), np.linspace(1,8), lw=0.5, color=\"black\", ls=\"--\")\n",
    "for i in range(len(segments)-1):\n",
    "    plt.scatter(np.log(targets[0,:,0].detach())[range(segments[i], segments[i+1])],\n",
    "                np.log(y_hat[0,:,0].detach())[range(segments[i], segments[i+1])],\n",
    "                label=\"%d\"%(i+1), alpha=0.75)\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Plots are colorized by segment identifiers. Null patterns for informal checks:\n",
    "* **A:** zero mean, constant variance\n",
    "* **B:** zero mean, constant variance\n",
    "* **C:** straight line\n",
    "\n",
    "We observe the null patterns. Only the plot of targets against prediction (C) indicates that the smallest three targets are overestimated, unless their form their own segment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.8.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
