Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add histogram convenience for passing Iris objects to plt.hist #5189

Merged
merged 4 commits into from
Mar 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/benchmark.yml
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
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
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
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
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
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
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
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
@@ -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)