{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Test Driven Development\n",
    "\n",
    "- write better code"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install -q numpy pandas\n",
    "\n",
    "import numpy as np\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `assert()` \n",
    "\n",
    "Asserts \n",
    "- are **low overhead**\n",
    "- help the reader understand code\n",
    "- break if something is wrong"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert True\n",
    "assert 1 == 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert False\n",
    "assert 0 == 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Common asserts in machine learning are to check the shapes of your training and test data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x_train = np.array([10, 20, 30]).reshape(3, 1)\n",
    "x_test = np.array([40, 50]).reshape(2, 1)\n",
    "\n",
    "y_train = np.array( ['a', 'b', 'c']).reshape(3, 1)\n",
    "y_test = np.array( ['d', 'e']).reshape(2, 1)\n",
    "\n",
    "assert x_train.shape[0] == y_train.shape[0]\n",
    "assert x_test.shape[1] == x_train.shape[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `.any()` and `.all()`\n",
    "\n",
    "These reduce across iterables:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "any((False, False))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "any((True, False))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "all((True, False))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "all((True, True))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `np.testing`\n",
    "\n",
    "Upgrade on using Python builtins - useful when working with arrays\n",
    "\n",
    "[Documentation](https://docs.scipy.org/doc/numpy/reference/routines.testing.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise\n",
    "\n",
    "Write a one passing case of using `np.testing.assert_array_equal`\n",
    "- you supply two arrays (data & expected)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#  change these to numpy arrays that will pass\n",
    "data = None\n",
    "expected = None\n",
    "\n",
    "np.testing.assert_array_equal(np.array([3, 2]), np.array([3, 2]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And one failing case:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#  change these to numpy arrays that will fail\n",
    "data = None\n",
    "expected = None\n",
    "\n",
    "np.testing.assert_array_equal(np.array([4, 2]), np.array([3, 2]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Test driven development\n",
    "\n",
    "The first step in developing code in a TDD style is to \n",
    "- write the function skeleton\n",
    "- write the test\n",
    "- check the test fails\n",
    "\n",
    "Below I show TTD code for doing **one-hot encoding**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def one_hot(data):\n",
    "    pass\n",
    "\n",
    "\n",
    "def test_one_hot():\n",
    "    data = ['left', 'left', 'right', 'straight']\n",
    "    \n",
    "    encoded = one_hot(data)\n",
    "    \n",
    "    #  check we have the correct number of rows\n",
    "    assert encoded.shape[0] == len(data)\n",
    "    \n",
    "    #  check columns are in the correct order (alphabetical)\n",
    "    assert (encoded.columns == ['left', 'right', 'straight']).all()\n",
    "    \n",
    "    #  check only one category in each row\n",
    "    assert encoded.sum(axis=1).all() == 1\n",
    "    \n",
    "    #  numpy testing is very handy for comparing arrays\n",
    "    np.testing.assert_array_equal(encoded.loc[:, 'left'], [1, 1, 0, 0])\n",
    "    np.testing.assert_array_equal(encoded.loc[:, 'right'], [0, 0, 1, 0])\n",
    "    np.testing.assert_array_equal(encoded.loc[:, 'straight'], [0, 0, 0, 1])\n",
    "    \n",
    "test_one_hot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note how before we have written any functional code, we are already thinking about\n",
    "- what order the columns should be in (alphabetical)\n",
    "- that encoded should return a Pandas DataFrame\n",
    "\n",
    "Also note how we (as the reader) can understand the intention of the function - this is executable documentation.\n",
    "\n",
    "Lets write our function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from answers import one_hot\n",
    "\n",
    "one_hot??\n",
    "\n",
    "test_one_hot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now lets manually check:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "one_hot(['cat', 'dog', 'fish', 'fish', 'dog'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Normalization\n",
    "\n",
    "An alternative to standardization\n",
    "\n",
    "Scaling to between zero and one\n",
    "\n",
    "$$ y = \\frac{x - x_{min}}{x_{max} - x_{min}} $$\n",
    "\n",
    "## Exercise\n",
    "\n",
    "Now follow the same TDD style to write a test & function to normalize a 2D array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def normalize(arr):\n",
    "    pass\n",
    "\n",
    "def test_normalize():\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can see the answers by importing them and using iPython's `??` to print the source code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.arange(16).reshape(2, -1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = np.array([\n",
    "    [0, 1],\n",
    "    [5, 6]\n",
    "])\n",
    "\n",
    "for x in d.flatten():\n",
    "    print(x)\n",
    "    float(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from answers import test_normalize\n",
    "\n",
    "test_normalize()\n",
    "\n",
    "#test_normalize??"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Standardization\n",
    "\n",
    "An alternative to normalization\n",
    "\n",
    "Removing the mean and scaling by the standard deviation\n",
    "\n",
    "$$ y = \\frac{x-\\mu}{\\sigma} $$\n",
    "\n",
    "## Exercise\n",
    "\n",
    "Write a test & function to standardize a 3D array\n",
    "- standardize across the batch dimension\n",
    "- example in ML = sequential data of shape=(batch_size, num_timesteps, features)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can see the answers by importing them and using iPython's `??` to print the source code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from answers import test_standardizer\n",
    "\n",
    "test_standardizer()\n",
    "\n",
    "#test_standardizer??"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Practical - decompose the code so you can test it\n",
    "\n",
    "A common task that\n",
    "- you can do to help\n",
    "- you will need to do to improve a colleagues code\n",
    "\n",
    "You can write at least three functions from the below.  You should also remove any **magic numbers** (hard coded inputs).  There are bugs in the code below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.seed(42)\n",
    "\n",
    "#  20 samples, 4 columns\n",
    "data = np.random.random((20, 4))\n",
    "\n",
    "#  feature engineering - normalize\n",
    "mins = np.min(data, axis=0)\n",
    "maxs = np.max(data, axis=0)\n",
    "data = (data - mins) / (maxs - mins)\n",
    "\n",
    "#  feature engineering - split into x & y\n",
    "x = data[:, :-1]\n",
    "y = data[:, -1]\n",
    "\n",
    "#  feature engineering - one hot encoding days\n",
    "days = np.array([np.arange(0, 7) for _ in range(int(1 + data.shape[0] / 7))]).reshape(-1, 1)\n",
    "days = days[:20, :]\n",
    "days.shape\n",
    "\n",
    "one_hot = np.zeros((len(days), 7))\n",
    "\n",
    "columns = np.arange(0, 7).tolist()\n",
    "for row, d in enumerate(days):\n",
    "    one_hot[row, columns.index(d)] = 1\n",
    "    \n",
    "x = np.concatenate([x, data, one_hot], axis=1)\n",
    "\n",
    "x_train = x[:5, :]\n",
    "y_train = x[:5, :]\n",
    "\n",
    "x_test = x[:5, :]\n",
    "y_test = x[:5, :]"
   ]
  },
  {
   "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.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
