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 1 commit
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
3 changes: 3 additions & 0 deletions docs/src/whatsnew/latest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ This document explains the changes made to Iris for this release
#. `@rcomer`_ enabled lazy evaluation of :obj:`~iris.analysis.RMS` calcuations
with weights. (:pull:`5017`)

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


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


def hist(x, *args, **kwargs):
"""
Compute and plot a histogram.

Args:

* x: :class:`~iris.cube.Cube`, :class:`~iris.coords.Coord`,
:class:`~iris.coords.CellMeasure`, or :class:`~iris.coords.AncillaryVariable`
trexfeathers marked this conversation as resolved.
Show resolved Hide resolved
A cube, coordinate, cell measure or ancillary variable 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
21 changes: 21 additions & 0 deletions lib/iris/quickplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,5 +324,26 @@ 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)
xlabel = _title(x, with_units=True)

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

axes.set_title(title)
axes.set_xlabel(xlabel)
axes.set_ylabel("Frequency")
trexfeathers marked this conversation as resolved.
Show resolved Hide resolved
return result


# Provide a convenience show method from pyplot.
show = plt.show
2 changes: 2 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,7 @@
"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_cube.0": "b59cc3dadb433c24c4f966039438793591a7cbdcbcdc9ccc68c697a91b139131",
"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
12 changes: 12 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,14 @@ 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_cube(self):
cube = test_plot.simple_cube()[0]
qplt.hist(cube, bins=np.linspace(287.7, 288.2, 11))
self.check_graphic()


if __name__ == "__main__":
tests.main()
50 changes: 50 additions & 0 deletions lib/iris/tests/unit/plot/test_hist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 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 does not work 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)