Coverage for python/lsst/utils/plotting/limits.py: 12%
33 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-25 09:27 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-25 09:27 +0000
1# This file is part of utils.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14from collections.abc import Callable, Iterable, Sequence
16import numpy as np
19def calculate_safe_plotting_limits(
20 data_series: Sequence,
21 percentile: float = 99.9,
22 constant_extra: float | None = None,
23 symmetric_around_zero: bool = False,
24) -> tuple[float, float]:
25 """Calculate the right limits for plotting for one or more data series.
27 Given one or more data series with potential outliers, calculated the
28 values to pass for ymin, ymax so that extreme outliers don't ruin the
29 plot. If you are plotting several series on a single axis, pass them
30 all in and the overall plotting range will be given.
32 Parameters
33 ----------
34 data_series : `iterable` or `iterable` of `iterable`
35 One or more data series which will be going on the same axis, and
36 therefore want to have their common plotting limits calculated.
38 Returns
39 -------
40 ymin : `float`
41 The value to set the ylim minimum to.
42 ymax : `float`
43 The value to set the ylim maximum to.
44 """
45 localFunc = make_calculate_safe_plotting_limits(percentile, constant_extra, symmetric_around_zero)
46 return localFunc(data_series)
49def make_calculate_safe_plotting_limits(
50 percentile: float = 99.9,
51 constant_extra: float | None = None,
52 symmetric_around_zero: bool = False,
53) -> Callable[[Sequence], tuple[float, float]]:
54 """Make a ``calculate_safe_plotting_limits`` closure to get the common
55 limits when not all data series are available initially.
57 Parameters
58 ----------
59 percentile : `float`, optional
60 The percentile used to clip the outliers from the data.
61 constant_extra : `float`, optional
62 The amount that's added on each side of the range so that data does not
63 quite touch the axes. If the default ``None`` is left then 5% of the
64 data range is added for cosmetics, but if zero is set this will
65 overrides this behaviour and zero you will get.
66 symmetric_around_zero : `bool`, optional
67 Make the limits symmetric around zero?
69 Returns
70 -------
71 calculate_safe_plotting_limits : `callable`
72 The calculate_safe_plotting_limits function to pass the data series to.
73 """
74 memory: list[Sequence] = []
76 def calculate_safe_plotting_limits(
77 data_series: Sequence, # a sequence of sequences is still a sequence
78 ) -> tuple[float, float]:
79 """Calculate the right limits for plotting for one or more data series.
81 Given one or more data series with potential outliers, calculated the
82 values to pass for ymin, ymax so that extreme outliers don't ruin the
83 plot. If you are plotting several series on a single axis, pass them
84 all in and the overall plotting range will be given.
86 Parameters
87 ----------
88 data_series : `iterable` or `iterable` of `iterable`
89 One or more data series which will be going on the same axis, and
90 therefore want to have their common plotting limits calculated.
92 Returns
93 -------
94 ymin : `float`
95 The value to set the ylim minimum to.
96 ymax : `float`
97 The value to set the ylim maximum to.
98 """
99 nonlocal constant_extra
100 nonlocal percentile
101 nonlocal symmetric_around_zero
103 if not isinstance(data_series, Iterable):
104 raise TypeError("data_series must be either an iterable, or an iterable of iterables")
106 # now we're sure we have an iterable, if it's just one make it a list
107 # of it lsst.utils.ensure_iterable is not suitable here as we already
108 # have one, we would need ensure_iterable_of_iterables here
110 # np.array are Iterable but not Sequence so isinstance that
111 if not isinstance(data_series[0], Iterable):
112 # we have a single data series, not multiple, wrap in [] so we can
113 # iterate over it as if we were given many
114 data_series = [data_series]
116 memory.extend(data_series)
118 mins = []
119 maxs = []
121 for dataSeries in memory:
122 max_val = np.nanpercentile(dataSeries, percentile)
123 min_val = np.nanpercentile(dataSeries, 100.0 - percentile)
125 if constant_extra is None:
126 data_range = max_val - min_val
127 constant_extra = 0.05 * data_range
129 max_val += constant_extra
130 min_val -= constant_extra
132 maxs.append(max_val)
133 mins.append(min_val)
135 max_val = max(maxs)
136 min_val = min(mins)
138 if symmetric_around_zero:
139 biggest_abs = max(abs(min_val), abs(max_val))
140 return -biggest_abs, biggest_abs
142 return min_val, max_val
144 return calculate_safe_plotting_limits