Completed
Pull Request — master (#372)
by
unknown
01:41
created

_next_non_masked_element()   B

Complexity

Conditions 3

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
dl 0
loc 25
rs 8.8571
1
# Copyright (c) 2008-2015 MetPy Developers.
2
# Distributed under the terms of the BSD 3-Clause License.
3
# SPDX-License-Identifier: BSD-3-Clause
4
"""Contains a collection of generally useful calculation tools."""
5
6
import numpy as np
7
import numpy.ma as ma
8
9
from ..package_tools import Exporter
10
11
exporter = Exporter(globals())
12
13
14
@exporter.export
15
def resample_nn_1d(a, centers):
16
    """Return one-dimensional nearest-neighbor indexes based on user-specified centers.
17
18
    Parameters
19
    ----------
20
    a : array-like
21
        1-dimensional array of numeric values from which to
22
        extract indexes of nearest-neighbors
23
    centers : array-like
24
        1-dimensional array of numeric values representing a subset of values to approximate
25
26
    Returns
27
    -------
28
        An array of indexes representing values closest to given array values
29
    """
30
    ix = []
31
    for center in centers:
32
        index = (np.abs(a - center)).argmin()
33
        if index not in ix:
34
            ix.append(index)
35
    return ix
36
37
38
@exporter.export
39
def nearest_intersection_idx(a, b):
40
    """Determine the index of the point just before two lines with common x values.
41
42
    Parameters
43
    ----------
44
    a : array-like
45
        1-dimensional array of y-values for line 1
46
    b : array-like
47
        1-dimensional array of y-values for line 2
48
49
    Returns
50
    -------
51
        An array of indexes representing the index of the values
52
        just before the intersection(s) of the two lines.
53
    """
54
    # Difference in the two y-value sets
55
    difference = a - b
56
57
    # Determine the point just before the intersection of the lines
58
    # Will return multiple points for multiple intersections
59
    sign_change_idx, = np.nonzero(np.diff(np.sign(difference)))
60
61
    return sign_change_idx
62
63
64
@exporter.export
65
def find_intersections(x, a, b, direction='all'):
66
    """Calculate the best estimate of intersection.
67
68
    Calculates the best estimates of the intersection of two y-value
69
    data sets that share a common x-value set.
70
71
    Parameters
72
    ----------
73
    x : array-like
74
        1-dimensional array of numeric x-values
75
    a : array-like
76
        1-dimensional array of y-values for line 1
77
    b : array-like
78
        1-dimensional array of y-values for line 2
79
    direction : string
80
        specifies direction of crossing. 'all', 'increasing' (a becoming greater than b),
81
        or 'decreasing' (b becoming greater than a).
82
83
    Returns
84
    -------
85
        A tuple (x, y) of array-like with the x and y coordinates of the
86
        intersections of the lines.
87
    """
88
    # Find the index of the points just before the intersection(s)
89
    nearest_idx = nearest_intersection_idx(a, b)
90
    next_idx = nearest_idx + 1
91
92
    # Determine the sign of the change
93
    sign_change = np.sign(a[next_idx] - b[next_idx])
94
95
    # x-values around each intersection
96
    _, x0 = _next_non_masked_element(x, nearest_idx)
97
    _, x1 = _next_non_masked_element(x, next_idx)
98
99
    # y-values around each intersection for the first line
100
    _, a0 = _next_non_masked_element(a, nearest_idx)
101
    _, a1 = _next_non_masked_element(a, next_idx)
102
103
    # y-values around each intersection for the second line
104
    _, b0 = _next_non_masked_element(b, nearest_idx)
105
    _, b1 = _next_non_masked_element(b, next_idx)
106
107
    # Calculate the x-intersection. This comes from finding the equations of the two lines,
108
    # one through (x0, a0) and (x1, a1) and the other through (x0, b0) and (x1, b1),
109
    # finding their intersection, and reducing with a bunch of algebra.
110
    delta_y0 = a0 - b0
111
    delta_y1 = a1 - b1
112
    intersect_x = (delta_y1 * x0 - delta_y0 * x1) / (delta_y1 - delta_y0)
113
114
    # Calculate the y-intersection of the lines. Just plug the x above into the equation
115
    # for the line through the a points. One could solve for y like x above, but this
116
    # causes weirder unit behavior and seems a little less good numerically.
117
    intersect_y = ((intersect_x - x0) / (x1 - x0)) * (a1 - a0) + a0
118
119
    # Make a mask based on the direction of sign change desired
120
    if direction == 'increasing':
121
        mask = sign_change > 0
122
    elif direction == 'decreasing':
123
        mask = sign_change < 0
124
    elif direction == 'all':
125
        return intersect_x, intersect_y
126
    else:
127
        raise ValueError('Unknown option for direction: {0}'.format(str(direction)))
128
    return intersect_x[mask], intersect_y[mask]
129
130
131
@exporter.export
132
def interpolate_nans(x, y, kind='linear'):
133
    """Interpolate NaN values in y.
134
135
    Interpolate NaN values in the y dimension. Works with unsorted x values.
136
137
    Parameters
138
    ----------
139
    x : array-like
140
        1-dimensional array of numeric x-values
141
    y : array-like
142
        1-dimensional array of numeric y-values
143
    kind : string
144
        specifies the kind of interpolation x coordinate - 'linear' or 'log'
145
146
    Returns
147
    -------
148
        An array of the y coordinate data with NaN values interpolated.
149
    """
150
    x_sort_args = np.argsort(x)
151
    x = x[x_sort_args]
152
    y = y[x_sort_args]
153
    nans = np.isnan(y)
154
    if kind is 'linear':
155
        y[nans] = np.interp(x[nans], x[~nans], y[~nans])
156
    elif kind is 'log':
157
        y[nans] = np.interp(np.log(x[nans]), np.log(x[~nans]), y[~nans])
158
    else:
159
        raise ValueError('Unknown option for kind: {0}'.format(str(kind)))
160
    return y[x_sort_args]
161
162
163
def _next_non_masked_element(a, idx):
164
    """Return the next non masked element of a masked array.
165
166
    If an array is masked, return the next non-masked element (if the given index is masked).
167
    If no other unmasked points are after the given masked point, returns none.
168
169
    Parameters
170
    ----------
171
    a : array-like
172
        1-dimensional array of numeric values
173
    idx : integer
174
        index of requested element
175
176
    Returns
177
    -------
178
        Index of next non-masked element and next non-masked element
179
    """
180
    try:
181
        next_idx = idx + a[idx:].mask.argmin()
182
        if ma.is_masked(a[next_idx]):
183
            return None, None
184
        else:
185
            return next_idx, a[next_idx]
186
    except (AttributeError, TypeError, IndexError):
187
        return idx, a[idx]
188