{ "metadata": { "name": "cell_comparison" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Cell Comparisons\n", "\n", "### Introduction\n", "\n", "Individual data points within Iris cubes are actually cells, which can be made up of *both* a point and a set of bounds. Cells do not have to be bounded, however in this example we are interested in cells that are.\n", "\n", "In a bounded cell, the point defines the precise position at which the point's value holds, while the bounds define the extent over which the point's value is *also true*.\n", "\n", "To conceptualise this, imagine that our cell contains one temperature field from a UKV model run. Our cell's point tells us that the value of this given cell is 300K, but our cell is also bounded. This means that our cell's value also holds across the volume described by the bounds. If the UKV model has a resolution of 1.5km and we assume this is true in x, y and z, then we state that our value of 300K is true for a cube 1.5km on a side centred on our cell's point.\n", "\n", "This is, of course, best shown with a picture:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![A graphical demonstration of an Iris cell.](files/img/cell.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can add to the complexity by now imagining that this model runs in 3-hourly timesteps. Now our cell has gained a time bound too, so our cell becomes a 4D hypercube with our point centred in the middle of our four dimensions' worth of bounds." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The Problem\n", "\n", "To demonstrate the problem, we will choose a much simpler example than our UKV model cell from above.\n", "\n", "Our example will be a 1D coordinate with point values ranging from 0 to 10 inclusive, with each point having bounds of +/- 2 from the point's value.\n", "\n", "This is displayed graphically in the following image. Cell points are the dots and the 1D bounds are illustrated by the whiskers from each point. The scalar comparison value is shown by the dashed grey line:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![Graphical presentation of the 1D coordinate](files/img/1d_coord.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's set this up using Python and Iris. We will start by importing Iris and checking its version." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import iris\n", "import numpy as np\n", "iris.__version__" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 1, "text": [ "'1.5.1'" ] } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we will set up a 1D coordinate, with bounds, as above:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "points = np.arange(11)\n", "bounds = np.array(([(x-2, x+2) for x in points]))\n", "example_coord = iris.coords.AuxCoord(points, bounds=bounds, long_name='example')\n", "print example_coord" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "AuxCoord(array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), bounds=array([[-2, 2],\n", " [-1, 3],\n", " [ 0, 4],\n", " [ 1, 5],\n", " [ 2, 6],\n", " [ 3, 7],\n", " [ 4, 8],\n", " [ 5, 9],\n", " [ 6, 10],\n", " [ 7, 11],\n", " [ 8, 12]]), standard_name=None, units=Unit('1'), long_name='example')\n" ] } ], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have a coordinate, we can start investigating some cell comparisons. We will compare each cell within our coordinate to the scalar value of 5." ] }, { "cell_type": "code", "collapsed": false, "input": [ "value = 5\n", "for cell in example_coord.cells():\n", " print('{!r} == {!r} is {}'.format(cell, value, cell==value))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Cell(point=0, bound=(-2, 2)) == 5 is False\n", "Cell(point=1, bound=(-1, 3)) == 5 is False\n", "Cell(point=2, bound=(0, 4)) == 5 is False\n", "Cell(point=3, bound=(1, 5)) == 5 is True\n", "Cell(point=4, bound=(2, 6)) == 5 is True\n", "Cell(point=5, bound=(3, 7)) == 5 is True\n", "Cell(point=6, bound=(4, 8)) == 5 is True\n", "Cell(point=7, bound=(5, 9)) == 5 is True\n", "Cell(point=8, bound=(6, 10)) == 5 is False\n", "Cell(point=9, bound=(7, 11)) == 5 is False\n", "Cell(point=10, bound=(8, 12)) == 5 is False\n" ] } ], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The behaviour we observe is that whenever the value is *within the range of the bounds* the equality test returns true. Thus the cells with point values 3 ≤ p ≤ 7 are all classified as equalling our test value of 5." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The Solution\n", "\n", "It is worth noting right at the start that the above is not a *problem* with Iris. It is valid equality testing — otherwise, we would not be able to test for equality within bounds at all.\n", "\n", "Clearly, however, there will be occasions when it is necessary to equality test bounded cells more strictly by testing for equality to the point *only*." ] }, { "cell_type": "code", "collapsed": false, "input": [ "for cell in example_coord.cells():\n", " print('{!r} == {!r} is {}'.format(cell.point, value, cell.point==value))" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "0 == 5 is False\n", "1 == 5 is False\n", "2 == 5 is False\n", "3 == 5 is False\n", "4 == 5 is False\n", "5 == 5 is True\n", "6 == 5 is False\n", "7 == 5 is False\n", "8 == 5 is False\n", "9 == 5 is False\n", "10 == 5 is False\n" ] } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, instead of equality testing to the whole cell, which as can be seen above is composed of both a point value and a bounds range, we equality test to the value of the cell's point specifically. Doing so returns what may be considered the more expected or logical result of cell comparison; that is, cell equality to a scalar value *only when* the scalar value equals the value of the cell's point." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example: Cell comparison on an Iris cube\n", "\n", "Let's briefly look at performing cell comparisons like this on an Iris cube. We will start by constructing an Iris `DimCoord` with our points and bounds from above and add this to a simple dummy cube:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "example_coord = iris.coords.DimCoord(points, bounds=bounds, long_name='example')\n", "dummy_cube = iris.cube.Cube(np.random.random(11))\n", "dummy_cube.add_dim_coord(example_coord, 0)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 5 }, { "cell_type": "markdown", "metadata": {}, "source": [ "To perform cell comparisons on our cube we can use an Iris constraint, as shown below. Here the first constraint performs cell comparisons on each cell (points and bounds), and the second performs cell comparisons against each cell's points only:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "cell_comparison_constraint = iris.Constraint(example=lambda cell: cell == 5)\n", "cell_point_comparison_constraint = iris.Constraint(example=lambda cell: cell.point == 5)\n", "\n", "print dummy_cube.extract(cell_comparison_constraint).coords()\n", "print dummy_cube.extract(cell_point_comparison_constraint).coords()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "[DimCoord(array([3, 4, 5, 6, 7]), bounds=array([[1, 5],\n", " [2, 6],\n", " [3, 7],\n", " [4, 8],\n", " [5, 9]]), standard_name=None, units=Unit('1'), long_name='example')]\n", "[DimCoord(array([5]), bounds=array([[3, 7]]), standard_name=None, units=Unit('1'), long_name='example')]\n" ] } ], "prompt_number": 6 } ], "metadata": {} } ] }