Coverage for python/lsst/utils/plotting/limits.py: 14%

27 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-04-26 06:05 -0700

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. 

11 

12from __future__ import annotations 

13 

14from collections.abc import Sequence 

15from typing import Iterable, Optional 

16 

17import numpy as np 

18 

19 

20def calculate_safe_plotting_limits( 

21 data_series: Sequence, # a sequence of sequences is still a sequence 

22 percentile: float = 99.9, 

23 constant_extra: Optional[float] = None, 

24 symmetric_around_zero: bool = False, 

25) -> tuple[float, float]: 

26 """Calculate the right limits for plotting for one or more data series. 

27 

28 Given one or more data series with potential outliers, calculated the 

29 values to pass for ymin, ymax so that extreme outliers don't ruin the plot. 

30 If you are plotting several series on a single axis, pass them all in and 

31 the overall plotting range will be given. 

32 

33 Parameters 

34 ---------- 

35 data_series : `iterable` or `iterable` of `iterable` 

36 One or more data series which will be going on the same axis, and 

37 therefore want to have their common plotting limits calculated. 

38 percentile : `float`, optional 

39 The percentile used to clip the outliers from the data. 

40 constant_extra : `float`, optional 

41 The amount that's added on each side of the range so that data does not 

42 quite touch the axes. If the default ``None`` is left then 5% of the 

43 data range is added for cosmetics, but if zero is set this will 

44 overrides this behaviour and zero you will get. 

45 symmetric_around_zero : `bool`, optional 

46 Make the limits symmetric around zero? 

47 Returns 

48 ------- 

49 ymin : `float` 

50 The value to set the ylim minimum to. 

51 ymax : `float` 

52 The value to set the ylim maximum to. 

53 """ 

54 if not isinstance(data_series, Iterable): 

55 raise TypeError("data_series must be either an iterable, or an iterable of iterables") 

56 

57 # now we're sure we have an iterable, if it's just one make it a list of it 

58 # lsst.utils.ensure_iterable is not suitable here as we already have one, 

59 # we would need ensure_iterable_of_iterables here 

60 if not isinstance(data_series[0], Iterable): # np.array are Iterable but not Sequence so isinstance that 

61 # we have a single data series, not multiple, wrap in [] so we can 

62 # iterate over it as if we were given many 

63 data_series = [data_series] 

64 

65 mins = [] 

66 maxs = [] 

67 

68 for data in data_series: 

69 max_val = np.nanpercentile(data, percentile) 

70 min_val = np.nanpercentile(data, 100.0 - percentile) 

71 

72 if constant_extra is None: 

73 data_range = max_val - min_val 

74 constant_extra = 0.05 * data_range 

75 

76 max_val += constant_extra 

77 min_val -= constant_extra 

78 

79 maxs.append(max_val) 

80 mins.append(min_val) 

81 

82 max_val = max(maxs) 

83 min_val = min(mins) 

84 

85 if symmetric_around_zero: 

86 biggest_abs = max(abs(min_val), abs(max_val)) 

87 return -biggest_abs, biggest_abs 

88 

89 return min_val, max_val