Skip to content

Commit

Permalink
Add histogram convenience for passing Iris objects to plt.hist (#5189)
Browse files Browse the repository at this point in the history
* Add histogram convenience for passing Iris objects to plt.hist

* Fix orientation

* Review actions
  • Loading branch information
lbdreyer authored Mar 17, 2023
1 parent 9f23fa9 commit d099f93
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
env:
IRIS_TEST_DATA_LOC_PATH: benchmarks
IRIS_TEST_DATA_PATH: benchmarks/iris-test-data
IRIS_TEST_DATA_VERSION: "2.18"
IRIS_TEST_DATA_VERSION: "2.19"
# Lets us manually bump the cache to rebuild
ENV_CACHE_BUILD: "0"
TEST_DATA_CACHE_BUILD: "2"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
session: "tests"

env:
IRIS_TEST_DATA_VERSION: "2.18"
IRIS_TEST_DATA_VERSION: "2.19"
ENV_NAME: "ci-tests"

steps:
Expand Down
3 changes: 3 additions & 0 deletions docs/src/whatsnew/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ This document explains the changes made to Iris for this release
:meth:`iris.cube.Cube.rolling_window`). This automatically adapts cube units
if necessary. (:pull:`5084`)

#. `@lbdreyer`_ and `@trexfeathers`_ (reviewer) added :func:`iris.plot.hist`
and :func:`iris.quickplot.hist`. (:pull:`5189`)


🐛 Bugs Fixed
=============
Expand Down
30 changes: 30 additions & 0 deletions lib/iris/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1704,6 +1704,36 @@ def fill_between(x, y1, y2, *args, **kwargs):
)


def hist(x, *args, **kwargs):
"""
Compute and plot a histogram.
Args:
* x:
A :class:`~iris.cube.Cube`, :class:`~iris.coords.Coord`,
:class:`~iris.coords.CellMeasure`, or :class:`~iris.coords.AncillaryVariable`
that will be used as the values that will be used to create the
histogram.
Note that if a coordinate is given, the points are used, ignoring the
bounds.
See :func:`matplotlib.pyplot.hist` for details of additional valid
keyword arguments.
"""
if isinstance(x, iris.cube.Cube):
data = x.data
elif isinstance(x, iris.coords._DimensionalMetadata):
data = x._values
else:
raise TypeError(
"x must be a cube, coordinate, cell measure or "
"ancillary variable."
)
return plt.hist(data, *args, **kwargs)


# Provide convenience show method from pyplot
show = plt.show

Expand Down
25 changes: 25 additions & 0 deletions lib/iris/quickplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,5 +324,30 @@ def fill_between(x, y1, y2, *args, **kwargs):
return result


def hist(x, *args, **kwargs):
"""
Compute and plot a labelled histogram.
See :func:`iris.plot.hist` for details of valid arguments and
keyword arguments.
"""
axes = kwargs.get("axes")
result = iplt.hist(x, *args, **kwargs)
title = _title(x, with_units=False)
label = _title(x, with_units=True)

if axes is None:
axes = plt.gca()

orientation = kwargs.get("orientation")
if orientation == "horizontal":
axes.set_ylabel(label)
else:
axes.set_xlabel(label)
axes.set_title(title)

return result


# Provide a convenience show method from pyplot.
show = plt.show
3 changes: 3 additions & 0 deletions lib/iris/tests/results/imagerepo.json
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@
"iris.tests.test_plot.TestPlotDimAndAuxCoordsKwarg.test_default.0": "b87830b0c786cf269ec766c99399cce998d3b3166f2530d3658c692d30ec6735",
"iris.tests.test_plot.TestPlotDimAndAuxCoordsKwarg.test_yx_order.0": "fa85978e837e68f094d3673089626ad792073985659a9b1a7a15b52869f19f56",
"iris.tests.test_plot.TestPlotDimAndAuxCoordsKwarg.test_yx_order.1": "ea95969c874a63d39ca3ad2a231cdbc9c4973631cd6336c633182cbc61c3d3f2",
"iris.tests.test_plot.TestPlotHist.test_cube.0": "b59cc3dadb433c24c4f16603943a793591a7c3dcb4dcbccc68c697a93b139131",
"iris.tests.test_plot.TestPlotOtherCoordSystems.test_plot_tmerc.0": "e665326d999ecc92b399b32466269326b369cccccccd64d96199631364f33333",
"iris.tests.test_plot.TestQuickplotPlot.test_t.0": "83ffb59a7f00e59a2205d9d6e4619a74d9388c8e884e8da799d30b6dddb47e00",
"iris.tests.test_plot.TestQuickplotPlot.test_t_dates.0": "82fe958b7e046f89a0033bd4d9632c74d8799d3e8d8d826789e487b348dc2f69",
Expand All @@ -216,6 +217,8 @@
"iris.tests.test_quickplot.TestLabels.test_pcolor.0": "eea16affc05ab500956e974ac53f3d80925ac03f2f81c07e3fa12da1c2fe3f80",
"iris.tests.test_quickplot.TestLabels.test_pcolormesh.0": "eea16affc05ab500956e974ac53f3d80925ac03f2f81c07e3fa12da1c2fe3f80",
"iris.tests.test_quickplot.TestLabels.test_pcolormesh_str_symbol.0": "eea16affc05ab500956e974ac53f3d80925ac03f3f80c07e3fa12d21c2ff3f80",
"iris.tests.test_quickplot.TestPlotHist.test_horizontal.0": "b59cc3dadb433c24c4f166039438793591a7dbdcbcdc9ccc68c697a91b139131",
"iris.tests.test_quickplot.TestPlotHist.test_vertical.0": "bf80c7c6c07d7959647e343a33364b699589c6c64ec0312b9e227ad681ffcc68",
"iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_non_cube_coordinate.0": "fe816a85857a957ac07f957ac07f3e80956ac07f3e80c07f3e813e85c07e3f80",
"iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.0": "ea856a95955a956ac17f950a807e3f4e951ac07e3f81c0ff3ea16aa1c0bd3e81",
"iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.1": "ea856a85957a957ac17e954ac17e1ea2950bc07e3e80c07f3e807a85c1ff3f81",
Expand Down
9 changes: 9 additions & 0 deletions lib/iris/tests/test_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,15 @@ def test_non_cube_coordinate(self):
self.draw("contourf", cube, coords=["grid_latitude", x])


@tests.skip_data
@tests.skip_plot
class TestPlotHist(tests.GraphicsTest):
def test_cube(self):
cube = simple_cube()[0]
iplt.hist(cube, bins=np.linspace(287.7, 288.2, 11))
self.check_graphic()


@tests.skip_data
@tests.skip_plot
class TestPlotDimAndAuxCoordsKwarg(tests.GraphicsTest):
Expand Down
19 changes: 19 additions & 0 deletions lib/iris/tests/test_quickplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

# import iris tests first so that some things can be initialised before importing anything else
import iris.tests as tests # isort:skip

import numpy as np

import iris
import iris.tests.test_plot as test_plot

Expand Down Expand Up @@ -281,5 +284,21 @@ def test_without_axes__default(self):
self._check(mappable, self.figure2, self.axes2)


@tests.skip_data
@tests.skip_plot
class TestPlotHist(tests.GraphicsTest):
def test_horizontal(self):
cube = test_plot.simple_cube()[0]
qplt.hist(cube, bins=np.linspace(287.7, 288.2, 11))
self.check_graphic()

def test_vertical(self):
cube = test_plot.simple_cube()[0]
qplt.hist(
cube, bins=np.linspace(287.7, 288.2, 11), orientation="horizontal"
)
self.check_graphic()


if __name__ == "__main__":
tests.main()
51 changes: 51 additions & 0 deletions lib/iris/tests/unit/plot/test_hist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright Iris contributors
#
# This file is part of Iris and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.
"""Unit tests for the `iris.plot.hist` function."""
# Import iris.tests first so that some things can be initialised before
# importing anything else.
import iris.tests as tests # isort:skip

from unittest import mock

import numpy as np
import pytest

from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord
from iris.cube import Cube

if tests.MPL_AVAILABLE:
import iris.plot as iplt


@tests.skip_plot
class Test:
@pytest.fixture(autouse=True)
def create_data(self):
self.data = np.array([0, 100, 110, 120, 200, 320])

@pytest.mark.parametrize(
"x", [AuxCoord, Cube, DimCoord, CellMeasure, AncillaryVariable]
)
def test_simple(self, x):
with mock.patch("matplotlib.pyplot.hist") as mocker:
iplt.hist(x(self.data))
# mocker.assert_called_once_with is not working as expected with
# _DimensionalMetadata objects so we use np.testing array equality
# checks instead.
args, kwargs = mocker.call_args
assert len(args) == 1
np.testing.assert_array_equal(args[0], self.data)

def test_kwargs(self):
cube = Cube(self.data)
bins = [0, 150, 250, 350]
with mock.patch("matplotlib.pyplot.hist") as mocker:
iplt.hist(cube, bins=bins)
mocker.assert_called_once_with(self.data, bins=bins)

def test_unsupported_input(self):
with pytest.raises(TypeError, match="x must be a"):
iplt.hist(self.data)

0 comments on commit d099f93

Please sign in to comment.