diff --git a/talks/packages/packages.ipynb b/talks/packages/packages.ipynb
index 8578a8044fef7192d3e11f32f3ad040aa89de1af..7d179c45853dd11b13d627e75635edd65bdb0bb3 100644
--- a/talks/packages/packages.ipynb
+++ b/talks/packages/packages.ipynb
@@ -68,8 +68,8 @@
    "source": [
     "from scipy import optimize\n",
     "def costfunc(params):\n",
-    "    return params[0] ** 2 * (params[1] - 3) ** 2 + (params[0] - 2) ** 2\n",
-    "optimize.minimize(costfunc, x0=[0, 0], method='l-bfgs-b')"
+    "    return (params[0] - 3) ** 2\n",
+    "optimize.minimize(costfunc, x0=[0], method='l-bfgs-b')"
    ]
   },
   {
@@ -130,6 +130,91 @@
     "- [Bokeh](https://bokeh.pydata.org/en/latest/) among many others: interactive plots in the browser (i.e., in javascript)\n",
     "\n",
     "## [Ipython](http://ipython.org/)/[Jupyter](https://jupyter.org/) notebook: interactive python environments\n",
+    "Supports:\n",
+    "- run code in multiple languages"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%bash\n",
+    "for name in python ruby ; do\n",
+    "    echo $name\n",
+    "done"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "- debugging"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from scipy import optimize\n",
+    "def costfunc(params):\n",
+    "    return 1 / params[0] ** 2\n",
+    "optimize.minimize(costfunc, x0=[0], method='l-bfgs-b')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%debug"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "- timing/profiling"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%prun\n",
+    "plt.plot([0, 3])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "- getting help"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "plt.plot?"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "- [and much more...](https://ipython.readthedocs.io/en/stable/interactive/magics.html)\n",
+    "\n",
+    "The next generation is already out: [jupyterlab](https://jupyterlab.readthedocs.io/en/latest/)\n",
+    "\n",
     "There are many [useful extensions available](https://github.com/ipython-contrib/jupyter_contrib_nbextensions).\n",
     "\n",
     "## [Pandas](https://pandas.pydata.org/): Analyzing \"clean\" data\n",
@@ -139,11 +224,11 @@
     "- fast IO to many tabular formats\n",
     "- accurate handling of missing data\n",
     "- Many, many routines to handle data\n",
-    "  - group by categorical data (i.e., male/female, or age groups)\n",
-    "  - joining/merging data\n",
+    "  - group by categorical data (e.g., male/female)\n",
+    "  - joining/merging data (all SQL-like operations and much more)\n",
     "  - time series support\n",
     "- statistical models through [statsmodels](http://www.statsmodels.org/stable/index.html)\n",
-    "- plotting though seaborn [seaborn](https://seaborn.pydata.org/)\n",
+    "- plotting though [seaborn](https://seaborn.pydata.org/)\n",
     "- Use [dask](https://dask.pydata.org/en/latest/) if your data is too big for memory (or if you want to run in parallel)\n",
     "\n",
     "You should also install `numexpr` and `bottleneck` for optimal performance.\n",
@@ -356,7 +441,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "%run test_argparse.py 3 8.5 -q"
+    "%run test_argparse.py 3 8.5"
    ]
   },
   {
@@ -430,7 +515,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "%run test_gooey.py"
+    "!python.app test_gooey.py"
    ]
   },
   {
@@ -495,6 +580,10 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "import numpy as np\n",
+    "import matplotlib.pyplot as plt\n",
+    "plt.ioff()\n",
+    "\n",
     "def plot_sine(amplitude, frequency):\n",
     "    x = np.linspace(0, 2 * np.pi, 100)\n",
     "    y = amplitude * np.sin(frequency * x)\n",
@@ -509,7 +598,8 @@
     "\n",
     "!mkdir plots\n",
     "amplitudes = [plot_sine(A, 1.) for A in [0.1, 0.3, 0.7, 1.0]]\n",
-    "frequencies = [plot_sine(1., F) for F in [1, 2, 3, 4, 5, 6]]"
+    "frequencies = [plot_sine(1., F) for F in [1, 2, 3, 4, 5, 6]]\n",
+    "plt.ion()"
    ]
   },
   {
@@ -564,7 +654,6 @@
    "outputs": [],
    "source": [
     "%%writefile wx_hello_world.py\n",
-    "#!/usr/bin/env python\n",
     "\"\"\"\n",
     "Hello World, but with more meat.\n",
     "\"\"\"\n",
@@ -671,7 +760,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "%run wx_hello_world.py"
+    "!python.app wx_hello_world.py"
    ]
   },
   {
@@ -683,6 +772,102 @@
     "- theano/tensorflow/pytorch\n",
     "  - keras\n",
     "\n",
+    "## [pymc3](http://docs.pymc.io/): Pobabilstic programming"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np\n",
+    "import matplotlib.pyplot as plt\n",
+    "\n",
+    "# Initialize random number generator\n",
+    "np.random.seed(123)\n",
+    "\n",
+    "# True parameter values\n",
+    "alpha, sigma = 1, 1\n",
+    "beta = [1, 2.5]\n",
+    "\n",
+    "# Size of dataset\n",
+    "size = 100\n",
+    "\n",
+    "# Predictor variable\n",
+    "X1 = np.random.randn(size)\n",
+    "X2 = np.random.randn(size) * 0.2\n",
+    "\n",
+    "# Simulate outcome variable\n",
+    "Y = alpha + beta[0]*X1 + beta[1]*X2 + np.random.randn(size)*sigma"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pymc3 as pm\n",
+    "basic_model = pm.Model()\n",
+    "\n",
+    "with basic_model:\n",
+    "\n",
+    "    # Priors for unknown model parameters\n",
+    "    alpha = pm.Normal('alpha', mu=0, sd=10)\n",
+    "    beta = pm.Normal('beta', mu=0, sd=10, shape=2)\n",
+    "    sigma = pm.HalfNormal('sigma', sd=1)\n",
+    "\n",
+    "    # Expected value of outcome\n",
+    "    mu = alpha + beta[0]*X1 + beta[1]*X2\n",
+    "\n",
+    "    # Likelihood (sampling distribution) of observations\n",
+    "    Y_obs = pm.Normal('Y_obs', mu=mu, sd=sigma, observed=Y)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "with basic_model:\n",
+    "\n",
+    "    # obtain starting values via MAP\n",
+    "    start = pm.find_MAP(fmin=optimize.fmin_powell)\n",
+    "\n",
+    "    # instantiate sampler\n",
+    "    step = pm.Slice()\n",
+    "\n",
+    "    # draw 5000 posterior samples\n",
+    "    trace = pm.sample(5000, step=step, start=start)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "_ = pm.traceplot(trace)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pm.summary(trace)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Alternative: [pystan](https://pystan.readthedocs.io/en/latest/): wrapper around the [Stan](http://mc-stan.org/users/) probabilistic programming language.\n",
+    "\n",
+    "\n",
     "## [Pycuda](https://documen.tician.de/pycuda/): Programming the GPU\n",
     "Wrapper around [Cuda](https://developer.nvidia.com/cuda-zone).\n",
     "The alternative [Pyopencl](https://documen.tician.de/pyopencl/) provides a very similar wrapper around [OpenCL](https://www.khronos.org/opencl/)."
@@ -725,89 +910,124 @@
    "source": [
     "Also see [pyopenGL](http://pyopengl.sourceforge.net/): graphics programming in python (used in FSLeyes)\n",
     "## Testing\n",
-    "- [unittest](https://docs.python.org/3.6/library/unittest.html): python built-in testing\n",
-    "> ```\n",
-    "> import unittest\n",
-    ">\n",
-    "> class TestStringMethods(unittest.TestCase):\n",
-    ">\n",
-    ">     def test_upper(self):\n",
-    ">         self.assertEqual('foo'.upper(), 'FOO')\n",
-    ">\n",
-    ">     def test_isupper(self):\n",
-    ">         self.assertTrue('FOO'.isupper())\n",
-    ">         self.assertFalse('Foo'.isupper())\n",
-    ">\n",
-    ">     def test_split(self):\n",
-    ">         s = 'hello world'\n",
-    ">         self.assertEqual(s.split(), ['hello', 'world'])\n",
-    ">         # check that s.split fails when the separator is not a string\n",
-    ">         with self.assertRaises(TypeError):\n",
-    ">             s.split(2)\n",
-    ">\n",
-    "> if __name__ == '__main__':\n",
-    ">     unittest.main()\n",
-    "> ```\n",
-    "- [doctest](https://docs.python.org/3.6/library/doctest.html): checks the example usage in the documentation\n",
-    "> ```\n",
-    "> def factorial(n):\n",
-    ">     \"\"\"Return the factorial of n, an exact integer >= 0.\n",
-    ">\n",
-    ">     >>> [factorial(n) for n in range(6)]\n",
-    ">     [1, 1, 2, 6, 24, 120]\n",
-    ">     >>> factorial(30)\n",
-    ">     265252859812191058636308480000000\n",
-    ">     >>> factorial(-1)\n",
-    ">     Traceback (most recent call last):\n",
-    ">         ...\n",
-    ">     ValueError: n must be >= 0\n",
-    ">\n",
-    ">     Factorials of floats are OK, but the float must be an exact integer:\n",
-    ">     >>> factorial(30.1)\n",
-    ">     Traceback (most recent call last):\n",
-    ">         ...\n",
-    ">     ValueError: n must be exact integer\n",
-    ">     >>> factorial(30.0)\n",
-    ">     265252859812191058636308480000000\n",
-    ">\n",
-    ">     It must also not be ridiculously large:\n",
-    ">     >>> factorial(1e100)\n",
-    ">     Traceback (most recent call last):\n",
-    ">         ...\n",
-    ">     OverflowError: n too large\n",
-    ">     \"\"\"\n",
-    ">\n",
-    ">     import math\n",
-    ">     if not n >= 0:\n",
-    ">         raise ValueError(\"n must be >= 0\")\n",
-    ">     if math.floor(n) != n:\n",
-    ">         raise ValueError(\"n must be exact integer\")\n",
-    ">     if n+1 == n:  # catch a value like 1e300\n",
-    ">         raise OverflowError(\"n too large\")\n",
-    ">     result = 1\n",
-    ">     factor = 2\n",
-    ">     while factor <= n:\n",
-    ">         result *= factor\n",
-    ">         factor += 1\n",
-    ">     return result\n",
-    ">\n",
-    ">\n",
-    "> if __name__ == \"__main__\":\n",
-    ">     import doctest\n",
-    ">     doctest.testmod()\n",
-    "> ```\n",
+    "- [unittest](https://docs.python.org/3.6/library/unittest.html): python built-in testing"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import unittest\n",
+    "\n",
+    "class TestStringMethods(unittest.TestCase):\n",
+    "\n",
+    "    def test_upper(self):\n",
+    "        self.assertEqual('foo'.upper(), 'FOO')\n",
+    "\n",
+    "    def test_isupper(self):\n",
+    "        self.assertTrue('FOO'.isupper())\n",
+    "        self.assertFalse('Foo'.isupper())\n",
+    "\n",
+    "    def test_split(self):\n",
+    "        s = 'hello world'\n",
+    "        self.assertEqual(s.split(), ['hello', 'world'])\n",
+    "        # check that s.split fails when the separator is not a string\n",
+    "        with self.assertRaises(TypeError):\n",
+    "            s.split(2)\n",
+    "\n",
+    "if __name__ == '__main__':\n",
+    "    unittest.main()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "- [doctest](https://docs.python.org/3.6/library/doctest.html): checks the example usage in the documentation"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def factorial(n):\n",
+    "    \"\"\"Return the factorial of n, an exact integer >= 0.\n",
+    "\n",
+    "    >>> [factorial(n) for n in range(6)]\n",
+    "    [1, 1, 2, 6, 24, 120]\n",
+    "    >>> factorial(30)\n",
+    "    265252859812191058636308480000000\n",
+    "    >>> factorial(-1)\n",
+    "    Traceback (most recent call last):\n",
+    "        ...\n",
+    "    ValueError: n must be >= 0\n",
+    "\n",
+    "    Factorials of floats are OK, but the float must be an exact integer:\n",
+    "    >>> factorial(30.1)\n",
+    "    Traceback (most recent call last):\n",
+    "        ...\n",
+    "    ValueError: n must be exact integer\n",
+    "    >>> factorial(30.0)\n",
+    "    265252859812191058636308480000000\n",
+    "\n",
+    "    It must also not be ridiculously large:\n",
+    "    >>> factorial(1e100)\n",
+    "    Traceback (most recent call last):\n",
+    "        ...\n",
+    "    OverflowError: n too large\n",
+    "    \"\"\"\n",
+    "\n",
+    "    import math\n",
+    "    if not n >= 0:\n",
+    "        raise ValueError(\"n must be >= 0\")\n",
+    "    if math.floor(n) != n:\n",
+    "        raise ValueError(\"n must be exact integer\")\n",
+    "    if n+1 == n:  # catch a value like 1e300\n",
+    "        raise OverflowError(\"n too large\")\n",
+    "    result = 1\n",
+    "    factor = 2\n",
+    "    while factor <= n:\n",
+    "        result *= factor\n",
+    "        factor += 1\n",
+    "    return result\n",
+    "\n",
+    "\n",
+    "if __name__ == \"__main__\":\n",
+    "    import doctest\n",
+    "    doctest.testmod()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
     "Two external packages provide more convenient unit tests:\n",
     "- [py.test](https://docs.pytest.org/en/latest/)\n",
-    "- [nose2](http://nose2.readthedocs.io/en/latest/usage.html)\n",
-    "> ```\n",
-    "> # content of test_sample.py\n",
-    "> def inc(x):\n",
-    ">     return x + 1\n",
-    ">\n",
-    "> def test_answer():\n",
-    ">     assert inc(3) == 5\n",
-    "> ```\n",
+    "- [nose2](http://nose2.readthedocs.io/en/latest/usage.html)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# content of test_sample.py\n",
+    "def inc(x):\n",
+    "    return x + 1\n",
     "\n",
+    "def test_answer():\n",
+    "    assert inc(3) == 5"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
     "- [coverage](https://coverage.readthedocs.io/en/coverage-4.5.1/): measures which part of the code is covered by the tests\n",
     "\n",
     "## Linters\n",
@@ -815,7 +1035,38 @@
     "- [pylint](https://pypi.python.org/pypi/pylint): most extensive linter\n",
     "- [pyflake](https://pypi.python.org/pypi/pyflakes): if you think pylint is too strict\n",
     "- [pep8](https://pypi.python.org/pypi/pep8): just checks for style errors\n",
-    "- [mypy](http://mypy-lang.org/): adding explicit typing to python\n",
+    "### Optional static typing\n",
+    "- Document how your method/function should be called\n",
+    "  - Static checking of whether your type hints are still up to date\n",
+    "  - Static checking of whether you call your own function correctly\n",
+    "- Even if you don't assign types yourself, static type checking can still check whether you call typed functions/methods from other packages correctly."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from typing import List\n",
+    "\n",
+    "def greet_all(names: List[str]) -> None:\n",
+    "    for name in names:\n",
+    "        print('Hello, {}'.format(name))\n",
+    "\n",
+    "greet_all(['python', 'java', 'C++'])  # type checker will be fine with this\n",
+    "\n",
+    "greet_all('matlab')  # this will actually run fine, but type checker will raise an error"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Packages:\n",
+    "- [typing](https://docs.python.org/3/library/typing.html): built-in library containing generics, unions, etc.\n",
+    "- [mypy](http://mypy-lang.org/): linter doing static type checking\n",
+    "- [pyAnnotate](https://github.com/dropbox/pyannotate): automatically assign types to most of your functions/methods based on runtime\n",
     "\n",
     "## Web frameworks\n",
     "- [Django2](https://www.djangoproject.com/): includes the most features, but also forces you to do things their way\n",
@@ -824,15 +1075,13 @@
     "\n",
     "There are also many, many libraries to interact with databases, but you will have to google those yourself.\n",
     "\n",
-    "# Several honourable mentions\n",
+    "# Quick mentions\n",
     "- [trimesh](https://github.com/mikedh/trimesh): Triangular mesh algorithms\n",
     "- [Pillow](https://pillow.readthedocs.io/en/latest/): Read/write/manipulate a wide variety of images (png, jpg, tiff, etc.)\n",
     "- [psychopy](http://www.psychopy.org/): equivalent of psychtoolbox (workshop coming up in April in Nottingham)\n",
     "- [Buit-in libraries](https://docs.python.org/3/py-modindex.html)\n",
     "    - [collections](https://docs.python.org/3.6/library/collections.html): deque, OrderedDict, namedtuple, and more\n",
     "    - [datetime](https://docs.python.org/3/library/datetime.html): Basic date and time types\n",
-    "    - [enum](https://docs.python.org/3/library/enum.html): Enumerators\n",
-    "    - [fractions](https://docs.python.org/3/library/fractions.html): rational numbers\n",
     "    - [functools](https://docs.python.org/3/library/functools.html): caching, decorators, and support for functional programming\n",
     "    - [json](https://docs.python.org/3/library/json.html)/[ipaddress](https://docs.python.org/3/library/ipaddress.html)/[xml](https://docs.python.org/3/library/xml.html#module-xml): parsing/writing\n",
     "    - [itertools](https://docs.python.org/3/library/itertools.html): more tools to loop over sequences\n",
@@ -843,10 +1092,38 @@
     "    - [pickle](https://docs.python.org/3/library/pickle.html): Store/load any python object\n",
     "    - [shutil](https://docs.python.org/3/library/shutil.html): copy/move files\n",
     "    - [subprocess](https://docs.python.org/3/library/subprocess.html): call shell commands\n",
-    "    - [time](https://docs.python.org/3/library/time.html)/[timeit](https://docs.python.org/3/library/timeit.html): keeping track of it\n",
-    "    - [turtule](https://docs.python.org/3/library/turtle.html#module-turtle): teach python to your kids!\n",
+    "    - [time](https://docs.python.org/3/library/time.html)/[timeit](https://docs.python.org/3/library/timeit.html): Timing your code\n",
+    "    - [turtle](https://docs.python.org/3/library/turtle.html#module-turtle): teach python to your kids!\n",
     "    - [warnings](https://docs.python.org/3/library/warnings.html#module-warnings): tell people they are not using your code properly"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from turtle import *\n",
+    "color('red', 'yellow')\n",
+    "begin_fill()\n",
+    "speed(10)\n",
+    "while True:\n",
+    "    forward(200)\n",
+    "    left(170)\n",
+    "    if abs(pos()) < 1:\n",
+    "        break\n",
+    "end_fill()\n",
+    "done()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import this"
+   ]
   }
  ],
  "metadata": {},
diff --git a/talks/packages/packages.md b/talks/packages/packages.md
index 6575e3274ab305b096ffc3a2b2807ef0af2de35c..e72e5bd61d1cf83e986677d09fbf08276c22d64e 100644
--- a/talks/packages/packages.md
+++ b/talks/packages/packages.md
@@ -33,8 +33,8 @@ The main strength in scipy lies in its sub-packages:
 ```
 from scipy import optimize
 def costfunc(params):
-    return params[0] ** 2 * (params[1] - 3) ** 2 + (params[0] - 2) ** 2
-optimize.minimize(costfunc, x0=[0, 0], method='l-bfgs-b')
+    return (params[0] - 3) ** 2
+optimize.minimize(costfunc, x0=[0], method='l-bfgs-b')
 ```
 
 Tutorials for all sub-packages can be found [here](https://docs.scipy.org/doc/scipy-1.0.0/reference/).
@@ -69,6 +69,41 @@ Alternatives:
 - [Bokeh](https://bokeh.pydata.org/en/latest/) among many others: interactive plots in the browser (i.e., in javascript)
 
 ## [Ipython](http://ipython.org/)/[Jupyter](https://jupyter.org/) notebook: interactive python environments
+Supports:
+- run code in multiple languages
+```
+%%bash
+for name in python ruby ; do
+    echo $name
+done
+```
+
+- debugging
+```
+from scipy import optimize
+def costfunc(params):
+    return 1 / params[0] ** 2
+optimize.minimize(costfunc, x0=[0], method='l-bfgs-b')
+```
+```
+%debug
+```
+
+- timing/profiling
+```
+%%prun
+plt.plot([0, 3])
+```
+
+- getting help
+```
+plt.plot?
+```
+
+- [and much more...](https://ipython.readthedocs.io/en/stable/interactive/magics.html)
+
+The next generation is already out: [jupyterlab](https://jupyterlab.readthedocs.io/en/latest/)
+
 There are many [useful extensions available](https://github.com/ipython-contrib/jupyter_contrib_nbextensions).
 
 ## [Pandas](https://pandas.pydata.org/): Analyzing "clean" data
@@ -78,11 +113,11 @@ Pandas has excellent support for:
 - fast IO to many tabular formats
 - accurate handling of missing data
 - Many, many routines to handle data
-  - group by categorical data (i.e., male/female, or age groups)
-  - joining/merging data
+  - group by categorical data (e.g., male/female)
+  - joining/merging data (all SQL-like operations and much more)
   - time series support
 - statistical models through [statsmodels](http://www.statsmodels.org/stable/index.html)
-- plotting though seaborn [seaborn](https://seaborn.pydata.org/)
+- plotting though [seaborn](https://seaborn.pydata.org/)
 - Use [dask](https://dask.pydata.org/en/latest/) if your data is too big for memory (or if you want to run in parallel)
 
 You should also install `numexpr` and `bottleneck` for optimal performance.
@@ -191,7 +226,7 @@ if __name__ == '__main__':
 ```
 
 ```
-%run test_argparse.py 3 8.5 -q
+%run test_argparse.py 3 8.5
 ```
 
 Alternatives:
@@ -249,7 +284,7 @@ if __name__ == '__main__':
 ```
 
 ```
-%run test_gooey.py
+!python.app test_gooey.py
 ```
 
 ```
@@ -293,6 +328,10 @@ This can for example be used to produce static HTML output in a highly flexible
 ```
 
 ```
+import numpy as np
+import matplotlib.pyplot as plt
+plt.ioff()
+
 def plot_sine(amplitude, frequency):
     x = np.linspace(0, 2 * np.pi, 100)
     y = amplitude * np.sin(frequency * x)
@@ -308,6 +347,7 @@ def plot_sine(amplitude, frequency):
 !mkdir plots
 amplitudes = [plot_sine(A, 1.) for A in [0.1, 0.3, 0.7, 1.0]]
 frequencies = [plot_sine(1., F) for F in [1, 2, 3, 4, 5, 6]]
+plt.ion()
 ```
 
 ```
@@ -341,7 +381,6 @@ The [nipy](http://nipy.org/) ecosystem covers most of these.
 - [wxpython](https://www.wxpython.org/): Wrapper around the C++ wxWidgets library
 ```
 %%writefile wx_hello_world.py
-#!/usr/bin/env python
 """
 Hello World, but with more meat.
 """
@@ -443,7 +482,7 @@ if __name__ == '__main__':
 ```
 
 ```
-%run wx_hello_world.py
+!python.app wx_hello_world.py
 ```
 
 ## Machine learning
@@ -451,6 +490,71 @@ if __name__ == '__main__':
 - theano/tensorflow/pytorch
   - keras
 
+## [pymc3](http://docs.pymc.io/): Pobabilstic programming
+```
+import numpy as np
+import matplotlib.pyplot as plt
+
+# Initialize random number generator
+np.random.seed(123)
+
+# True parameter values
+alpha, sigma = 1, 1
+beta = [1, 2.5]
+
+# Size of dataset
+size = 100
+
+# Predictor variable
+X1 = np.random.randn(size)
+X2 = np.random.randn(size) * 0.2
+
+# Simulate outcome variable
+Y = alpha + beta[0]*X1 + beta[1]*X2 + np.random.randn(size)*sigma
+```
+
+```
+import pymc3 as pm
+basic_model = pm.Model()
+
+with basic_model:
+
+    # Priors for unknown model parameters
+    alpha = pm.Normal('alpha', mu=0, sd=10)
+    beta = pm.Normal('beta', mu=0, sd=10, shape=2)
+    sigma = pm.HalfNormal('sigma', sd=1)
+
+    # Expected value of outcome
+    mu = alpha + beta[0]*X1 + beta[1]*X2
+
+    # Likelihood (sampling distribution) of observations
+    Y_obs = pm.Normal('Y_obs', mu=mu, sd=sigma, observed=Y)
+```
+
+```
+with basic_model:
+
+    # obtain starting values via MAP
+    start = pm.find_MAP(fmin=optimize.fmin_powell)
+
+    # instantiate sampler
+    step = pm.Slice()
+
+    # draw 5000 posterior samples
+    trace = pm.sample(5000, step=step, start=start)
+```
+
+```
+_ = pm.traceplot(trace)
+```
+
+```
+pm.summary(trace)
+```
+
+Alternative: [pystan](https://pystan.readthedocs.io/en/latest/): wrapper around the [Stan](http://mc-stan.org/users/) probabilistic programming language.
+
+
 ## [Pycuda](https://documen.tician.de/pycuda/): Programming the GPU
 Wrapper around [Cuda](https://developer.nvidia.com/cuda-zone).
 The alternative [Pyopencl](https://documen.tician.de/pyopencl/) provides a very similar wrapper around [OpenCL](https://www.khronos.org/opencl/).
@@ -483,87 +587,87 @@ print(dest-a*b)
 Also see [pyopenGL](http://pyopengl.sourceforge.net/): graphics programming in python (used in FSLeyes)
 ## Testing
 - [unittest](https://docs.python.org/3.6/library/unittest.html): python built-in testing
-> ```
-> import unittest
->
-> class TestStringMethods(unittest.TestCase):
->
->     def test_upper(self):
->         self.assertEqual('foo'.upper(), 'FOO')
->
->     def test_isupper(self):
->         self.assertTrue('FOO'.isupper())
->         self.assertFalse('Foo'.isupper())
->
->     def test_split(self):
->         s = 'hello world'
->         self.assertEqual(s.split(), ['hello', 'world'])
->         # check that s.split fails when the separator is not a string
->         with self.assertRaises(TypeError):
->             s.split(2)
->
-> if __name__ == '__main__':
->     unittest.main()
-> ```
+```
+import unittest
+
+class TestStringMethods(unittest.TestCase):
+
+    def test_upper(self):
+        self.assertEqual('foo'.upper(), 'FOO')
+
+    def test_isupper(self):
+        self.assertTrue('FOO'.isupper())
+        self.assertFalse('Foo'.isupper())
+
+    def test_split(self):
+        s = 'hello world'
+        self.assertEqual(s.split(), ['hello', 'world'])
+        # check that s.split fails when the separator is not a string
+        with self.assertRaises(TypeError):
+            s.split(2)
+
+if __name__ == '__main__':
+    unittest.main()
+```
 - [doctest](https://docs.python.org/3.6/library/doctest.html): checks the example usage in the documentation
-> ```
-> def factorial(n):
->     """Return the factorial of n, an exact integer >= 0.
->
->     >>> [factorial(n) for n in range(6)]
->     [1, 1, 2, 6, 24, 120]
->     >>> factorial(30)
->     265252859812191058636308480000000
->     >>> factorial(-1)
->     Traceback (most recent call last):
->         ...
->     ValueError: n must be >= 0
->
->     Factorials of floats are OK, but the float must be an exact integer:
->     >>> factorial(30.1)
->     Traceback (most recent call last):
->         ...
->     ValueError: n must be exact integer
->     >>> factorial(30.0)
->     265252859812191058636308480000000
->
->     It must also not be ridiculously large:
->     >>> factorial(1e100)
->     Traceback (most recent call last):
->         ...
->     OverflowError: n too large
->     """
->
->     import math
->     if not n >= 0:
->         raise ValueError("n must be >= 0")
->     if math.floor(n) != n:
->         raise ValueError("n must be exact integer")
->     if n+1 == n:  # catch a value like 1e300
->         raise OverflowError("n too large")
->     result = 1
->     factor = 2
->     while factor <= n:
->         result *= factor
->         factor += 1
->     return result
->
->
-> if __name__ == "__main__":
->     import doctest
->     doctest.testmod()
-> ```
+```
+def factorial(n):
+    """Return the factorial of n, an exact integer >= 0.
+
+    >>> [factorial(n) for n in range(6)]
+    [1, 1, 2, 6, 24, 120]
+    >>> factorial(30)
+    265252859812191058636308480000000
+    >>> factorial(-1)
+    Traceback (most recent call last):
+        ...
+    ValueError: n must be >= 0
+
+    Factorials of floats are OK, but the float must be an exact integer:
+    >>> factorial(30.1)
+    Traceback (most recent call last):
+        ...
+    ValueError: n must be exact integer
+    >>> factorial(30.0)
+    265252859812191058636308480000000
+
+    It must also not be ridiculously large:
+    >>> factorial(1e100)
+    Traceback (most recent call last):
+        ...
+    OverflowError: n too large
+    """
+
+    import math
+    if not n >= 0:
+        raise ValueError("n must be >= 0")
+    if math.floor(n) != n:
+        raise ValueError("n must be exact integer")
+    if n+1 == n:  # catch a value like 1e300
+        raise OverflowError("n too large")
+    result = 1
+    factor = 2
+    while factor <= n:
+        result *= factor
+        factor += 1
+    return result
+
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()
+```
 Two external packages provide more convenient unit tests:
 - [py.test](https://docs.pytest.org/en/latest/)
 - [nose2](http://nose2.readthedocs.io/en/latest/usage.html)
-> ```
-> # content of test_sample.py
-> def inc(x):
->     return x + 1
->
-> def test_answer():
->     assert inc(3) == 5
-> ```
+```
+# content of test_sample.py
+def inc(x):
+    return x + 1
+
+def test_answer():
+    assert inc(3) == 5
+```
 
 - [coverage](https://coverage.readthedocs.io/en/coverage-4.5.1/): measures which part of the code is covered by the tests
 
@@ -572,7 +676,28 @@ Linters check the code for any syntax errors, [style errors](https://www.python.
 - [pylint](https://pypi.python.org/pypi/pylint): most extensive linter
 - [pyflake](https://pypi.python.org/pypi/pyflakes): if you think pylint is too strict
 - [pep8](https://pypi.python.org/pypi/pep8): just checks for style errors
-- [mypy](http://mypy-lang.org/): adding explicit typing to python
+### Optional static typing
+- Document how your method/function should be called
+  - Static checking of whether your type hints are still up to date
+  - Static checking of whether you call your own function correctly
+- Even if you don't assign types yourself, static type checking can still check whether you call typed functions/methods from other packages correctly.
+
+```
+from typing import List
+
+def greet_all(names: List[str]) -> None:
+    for name in names:
+        print('Hello, {}'.format(name))
+
+greet_all(['python', 'java', 'C++'])  # type checker will be fine with this
+
+greet_all('matlab')  # this will actually run fine, but type checker will raise an error
+```
+
+Packages:
+- [typing](https://docs.python.org/3/library/typing.html): built-in library containing generics, unions, etc.
+- [mypy](http://mypy-lang.org/): linter doing static type checking
+- [pyAnnotate](https://github.com/dropbox/pyannotate): automatically assign types to most of your functions/methods based on runtime
 
 ## Web frameworks
 - [Django2](https://www.djangoproject.com/): includes the most features, but also forces you to do things their way
@@ -581,15 +706,13 @@ Linters check the code for any syntax errors, [style errors](https://www.python.
 
 There are also many, many libraries to interact with databases, but you will have to google those yourself.
 
-# Several honourable mentions
+# Quick mentions
 - [trimesh](https://github.com/mikedh/trimesh): Triangular mesh algorithms
 - [Pillow](https://pillow.readthedocs.io/en/latest/): Read/write/manipulate a wide variety of images (png, jpg, tiff, etc.)
 - [psychopy](http://www.psychopy.org/): equivalent of psychtoolbox (workshop coming up in April in Nottingham)
 - [Buit-in libraries](https://docs.python.org/3/py-modindex.html)
     - [collections](https://docs.python.org/3.6/library/collections.html): deque, OrderedDict, namedtuple, and more
     - [datetime](https://docs.python.org/3/library/datetime.html): Basic date and time types
-    - [enum](https://docs.python.org/3/library/enum.html): Enumerators
-    - [fractions](https://docs.python.org/3/library/fractions.html): rational numbers
     - [functools](https://docs.python.org/3/library/functools.html): caching, decorators, and support for functional programming
     - [json](https://docs.python.org/3/library/json.html)/[ipaddress](https://docs.python.org/3/library/ipaddress.html)/[xml](https://docs.python.org/3/library/xml.html#module-xml): parsing/writing
     - [itertools](https://docs.python.org/3/library/itertools.html): more tools to loop over sequences
@@ -600,6 +723,24 @@ There are also many, many libraries to interact with databases, but you will hav
     - [pickle](https://docs.python.org/3/library/pickle.html): Store/load any python object
     - [shutil](https://docs.python.org/3/library/shutil.html): copy/move files
     - [subprocess](https://docs.python.org/3/library/subprocess.html): call shell commands
-    - [time](https://docs.python.org/3/library/time.html)/[timeit](https://docs.python.org/3/library/timeit.html): keeping track of it
-    - [turtule](https://docs.python.org/3/library/turtle.html#module-turtle): teach python to your kids!
+    - [time](https://docs.python.org/3/library/time.html)/[timeit](https://docs.python.org/3/library/timeit.html): Timing your code
+    - [turtle](https://docs.python.org/3/library/turtle.html#module-turtle): teach python to your kids!
     - [warnings](https://docs.python.org/3/library/warnings.html#module-warnings): tell people they are not using your code properly
+
+```
+from turtle import *
+color('red', 'yellow')
+begin_fill()
+speed(10)
+while True:
+    forward(200)
+    left(170)
+    if abs(pos()) < 1:
+        break
+end_fill()
+done()
+```
+
+```
+import this
+```