{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# default_exp data.scalers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Scalers \n",
    "> Utils for scaling data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "import numpy as np\n",
    "import statsmodels.api as sm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "import numpy as np\n",
    "import statsmodels.api as sm\n",
    "\n",
    "#TODO: rehacer todo, es codigo provisional porque corre\n",
    "#TODO: filtrar por adelantado con offset\n",
    "#TODO: codigo duplicado en clases muy parecidas\n",
    "#TODO: usar scaler sklearn?\n",
    "#TODO: shift scale abuso notacion con min, max\n",
    "#TODO: comentar cosas\n",
    "#TODO: hacer funcion devoluciondora de cosos particulares\n",
    "#TODO: subclase para cada scaler\n",
    "#TODO: funciona solo para una serie\n",
    "\n",
    "class Scaler(object):\n",
    "    def __init__(self, normalizer):\n",
    "        assert (normalizer in ['std', 'invariant', 'norm', 'norm1', 'median']), 'Normalizer not defined'\n",
    "        self.normalizer = normalizer\n",
    "        self.x_shift = None\n",
    "        self.x_scale = None\n",
    "\n",
    "    def scale(self, x, mask):\n",
    "        if self.normalizer == 'invariant':\n",
    "            x_scaled, x_shift, x_scale = invariant_scaler(x, mask)\n",
    "        elif self.normalizer == 'median':\n",
    "            x_scaled, x_shift, x_scale = median_scaler(x, mask)\n",
    "        elif self.normalizer == 'std':\n",
    "            x_scaled, x_shift, x_scale = std_scaler(x, mask)\n",
    "        elif self.normalizer == 'norm':\n",
    "            x_scaled, x_shift, x_scale = norm_scaler(x, mask)\n",
    "        elif self.normalizer == 'norm1':\n",
    "            x_scaled, x_shift, x_scale = norm1_scaler(x, mask)\n",
    "        \n",
    "        assert len(x[mask==1] == np.sum(mask)), 'Something weird is happening, call Cristian'\n",
    "        nan_before_scale = np.sum(np.isnan(x))\n",
    "        nan_after_scale = np.sum(np.isnan(x_scaled))\n",
    "        assert nan_before_scale == nan_after_scale, 'Scaler induced nans'\n",
    "\n",
    "        self.x_shift = x_shift\n",
    "        self.x_scale = x_scale\n",
    "        return np.array(x_scaled)\n",
    "\n",
    "    def inv_scale(self, x):\n",
    "        assert self.x_shift is not None\n",
    "        assert self.x_scale is not None\n",
    "\n",
    "        if self.normalizer == 'invariant':\n",
    "            x_inv_scaled = inv_invariant_scaler(x, self.x_shift, self.x_scale)\n",
    "        elif self.normalizer == 'median':\n",
    "            x_inv_scaled = inv_median_scaler(x, self.x_shift, self.x_scale)\n",
    "        elif self.normalizer == 'std':\n",
    "            x_inv_scaled = inv_std_scaler(x, self.x_shift, self.x_scale)\n",
    "        elif self.normalizer == 'norm':\n",
    "            x_inv_scaled = inv_norm_scaler(x, self.x_shift, self.x_scale)\n",
    "        elif self.normalizer == 'norm1':\n",
    "            x_inv_scaled = inv_norm1_scaler(x, self.x_shift, self.x_scale)\n",
    "\n",
    "        return np.array(x_inv_scaled)\n",
    "\n",
    "# Norm\n",
    "def norm_scaler(x, mask):    \n",
    "    x_max = np.max(x[mask==1])\n",
    "    x_min = np.min(x[mask==1])\n",
    "    \n",
    "    x = (x - x_min) / (x_max - x_min) #TODO: cuidado dividir por zero\n",
    "    return x, x_min, x_max\n",
    "\n",
    "def inv_norm_scaler(x, x_min, x_max):\n",
    "    return x * (x_max - x_min) + x_min\n",
    "\n",
    "# Norm1\n",
    "def norm1_scaler(x, mask):\n",
    "    x_max = np.max(x[mask==1])\n",
    "    x_min = np.min(x[mask==1])\n",
    "\n",
    "    x = (x - x_min) / (x_max - x_min) #TODO: cuidado dividir por zero\n",
    "    x = x * (2) - 1\n",
    "    return x, x_min, x_max\n",
    "\n",
    "def inv_norm1_scaler(x, x_min, x_max):\n",
    "    x = (x + 1) / 2\n",
    "    return x * (x_max - x_min) + x_min\n",
    "\n",
    "# Std\n",
    "def std_scaler(x, mask):\n",
    "    x_mean = np.mean(x[mask==1])\n",
    "    x_std = np.std(x[mask==1])\n",
    "\n",
    "    x = (x - x_mean) / x_std #TODO: cuidado dividir por zero\n",
    "    return x, x_mean, x_std\n",
    "\n",
    "def inv_std_scaler(x, x_mean, x_std):\n",
    "    return (x * x_std) + x_mean\n",
    "\n",
    "# Median\n",
    "def median_scaler(x, mask):\n",
    "    x_median = np.median(x[mask==1])\n",
    "    x_mad = sm.robust.scale.mad(x[mask==1])\n",
    "    if x_mad == 0:\n",
    "        x_mad = np.std(x[mask==1], ddof = 1) / 0.6744897501960817\n",
    "    x = (x - x_median) / x_mad\n",
    "    return x, x_median, x_mad\n",
    "\n",
    "def inv_median_scaler(x, x_median, x_mad):\n",
    "    return x * x_mad + x_median\n",
    "\n",
    "# Invariant\n",
    "def invariant_scaler(x, mask):\n",
    "    x_median = np.median(x[mask==1])\n",
    "    x_mad = sm.robust.scale.mad(x[mask==1])\n",
    "    if x_mad == 0:\n",
    "        x_mad = np.std(x[mask==1], ddof = 1) / 0.6744897501960817\n",
    "    x = np.arcsinh((x - x_median) / x_mad)\n",
    "    return x, x_median, x_mad\n",
    "\n",
    "def inv_invariant_scaler(x, x_median, x_mad):\n",
    "    return np.sinh(x) * x_mad + x_median\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
