-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMyNumpyTools.py
326 lines (230 loc) · 8.43 KB
/
MyNumpyTools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# Convenient tools that numpy doesn't have
import numpy as np
import numpy.ma as ma
from pandas import DataFrame
from warnings import filterwarnings
from scipy.signal import convolve
from scipy.interpolate import RegularGridInterpolator
def cosd(angle):
"""cosine with argument in degrees"""
out = np.cos(np.deg2rad(angle))
return out
def sind(angle):
"""sine with argument in degrees"""
out = np.sin(np.deg2rad(angle))
return out
def tand(angle):
"""tan with argument in degrees"""
out = np.tan(np.deg2rad(angle))
return out
def minmax(x, axis=None, astype='float'):
"""returns tuple (min(x), max(x)) while ignoring nans"""
extrema = (np.nanmin(x, axis=axis).astype(astype),
np.nanmax(x, axis=axis).astype(astype))
return extrema
def demean_ma(x, axis=None):
"""Like matplotlib.mlab's detrend_mean but works for masked arrays"""
return x - x.mean(axis=axis)
def nan_or_masked(arr):
"""Returns true if value is either nan or masked"""
try:
masked_vals = arr.mask
nan_vals = np.isnan(arr.data)
invalid_val = np.logical_or(masked_vals, nan_vals)
except AttributeError:
invalid_val = np.isnan(arr)
return invalid_val
def nan_to_value(x, value):
x[np.isnan(x)] = value
return x
def nan_or_masked_to_value(x, value):
x[nan_or_masked(x)] = value
return x
def argmin_nd(arr):
"""Like np.argmin, but return result as N-dimensional tuple"""
# http://stackoverflow.com/questions/3230067/
# numpy-minimum-in-row-column-format
return np.unravel_index(arr.argmin(), arr.shape)
def argmax_nd(arr):
"""Like np.argmax, but return result as N-dimensional tuple"""
# http://stackoverflow.com/questions/3230067/
# numpy-minimum-in-row-column-format
return np.unravel_index(arr.argmax(), arr.shape)
def logical_all(*args):
"""Like logical_and, but for any number of input arrays"""
out = np.ones_like(args[0]).astype(bool)
for arg in args:
out = np.logical_and(out, arg)
return out
def logical_any(*args):
"""Like logical_and, but for any number of input arrays"""
out = np.zeros_like(args[0]).astype(bool)
for arg in args:
out = np.logical_or(out, arg)
return out
def ma_percentile(a, q, axis=None, **kw_args):
"""Equivalent to np.percentile, but works on masked arrays
Inputs a, q, and axis are as for np.percentile. Other inputs can
be passed using kw_args"""
filterwarnings('ignore', 'setting an item on a masked array')
try:
a[a.mask] = np.nan
mask = True
except AttributeError:
# Array has no mask, so nothing to change to np.nan
mask = False
filterwarnings('ignore', 'All-NaN slice encountered')
out = np.nanpercentile(a, q, axis=axis, **kw_args)
# Return masked result
if mask:
out = ma.masked_invalid(out)
return out
def maxabs(x, **kw_args):
return np.nanmax(np.abs(x), **kw_args)
def max_to_lims(x):
"""Find maximum absolute value, and return (-max, max)"""
max_val = maxabs(x)
return np.array((-max_val, max_val))
def neg_pos(x):
"""Convenience function to convert ±x to (-x, x)"""
return max_to_lims(x)
def ma_mad(x, axis=None):
"""Median absolute deviation"""
median_x = ma.median(x, axis=axis)
if axis is not None:
median_x = ma.expand_dims(median_x, axis=axis)
return ma.median(ma.abs(x - median_x), axis=axis)
def fillnan_pad(x, axis=0):
"""Replace NaN values with last finite value"""
x = DataFrame(x).fillna(method='pad', axis=axis)
return np.array(x)
def fillnan_pad3d(x):
"""Replace NaN values with last finite value"""
# Have to use row if we want to take advantage of pandas' method
for i, xi in enumerate(x):
x[i, ...] = np.array(DataFrame(xi).fillna(method='pad', axis=1))
return np.array(x)
def change_wrap(x, wrap_in, wrap_out):
"""Change angle wrapping limits
Inputs
------
x : array
Angles to change
wrap_in : int or float
Wrap value for input array
wrap_out : int or float
Wrap value to convert to
If input data are in range (0, 360), wrap_in is 360
Returns
-------
x : array
Rewrapped with different limits
"""
# Copy x so weird things don't happen
x = x.copy()
# Ensure x is array
x = np.asanyarray(x)
# Other end of wrap
wrap_in_0 = wrap_in - 360
wrap_out_0 = wrap_out - 360
# Ensure all x values are within correct range to start with
assert np.nanmin(x) >= wrap_in_0 and np.nanmax(x) <= wrap_in
filterwarnings('ignore', '.*invalid value encountered in less*.')
filterwarnings('ignore', '.*invalid value encountered in greater*.')
inc_inds = x < wrap_out_0
dec_inds = x > wrap_out
x[inc_inds] = wrap_in + (x[inc_inds] - wrap_in_0)
x[dec_inds] = wrap_in_0 - (wrap_in - x[dec_inds])
return x
def uneven_2D_convolve(in1, x1, y1, in2, Nx=1000, Ny=1000):
"""2D convolution on regular but uneven grid
Interpolates to regular grid, runs convolution, then interpolates back
Inputs
------
in1 : 2D array
First array used in scipy's convolve
x1, y1 : 1D arrays
Cartesian coordinates for in1
in2 : 2D array
| Second array used in scipy's convolve
| in2 is assumed to be on the regular grid defined by edges of x1 and
y1 and number of grid points Nx and Ny
Nx, Ny : integers
Number of points used in intermediate grid
Returns
-------
convolve : 2D array
Result of convolution on x1, y1 grid
"""
# Create temporary, regular grids
tmp_x1 = np.linspace(x1.min(), x1.max(), Nx)
tmp_y1 = np.linspace(y1.min(), y1.max(), Ny)
# Create meshes
tmp_X1, tmp_Y1 = np.meshgrid(tmp_x1, tmp_y1, indexing='ij')
X1, Y1 = np.meshgrid(x1, y1, indexing='ij')
# Interpolate in1 and in2 to regular grid
tmp_in1 = RegularGridInterpolator((x1, y1), in1)((tmp_X1, tmp_Y1))
# Do convolution on regular grid
tmp_conv = convolve(tmp_in1, in2, mode='same')
# Convert convolution result back to original grid
conv = RegularGridInterpolator((tmp_x1, tmp_y1), tmp_conv)((X1, Y1))
return conv
def next_pow_2(x):
return (2**np.ceil(np.log2(x))).astype(int)
def prev_pow_2(x):
return (2**np.floor(np.log2(x))).astype(int)
def midpoint(x, axis=0):
return x[:-1] + np.diff(x, axis=axis)/2
def normalize(arr, axis=None):
ptp_opts = dict(axis=axis, keepdims=True)
ptp = np.max(arr, **ptp_opts) - np.min(arr, **ptp_opts)
return (arr - arr.min(axis=axis, keepdims=True))/ptp
def arange_like(arr):
return np.arange(len(arr))
def fractional_roll(arr, shift):
"""Like np.roll, but allows for fractional, interpolated increments
Only tested on 1D arrays"""
if shift == 0:
return arr
elif np.abs(shift) > arr.size:
print('Shift larger than array')
return None
else:
ceil_shift = np.ceil(np.abs(shift)).astype(int)
tmp_arr = np.r_[arr[-ceil_shift:], arr, arr[:ceil_shift]]
tmp_idx = np.arange(-ceil_shift, len(arr) + ceil_shift)
return np.interp(arange_like(arr) - shift, tmp_idx, tmp_arr)
def arangep1(start, stop, step, dtype=None):
"""Like np.arange, but the last value is >= stop, as opposed to < stop"""
tmp = np.arange(start, stop, step)
return np.r_[tmp, tmp[-1] + step]
def inrange(x, x_range):
"""Return true for all elements xrange[0] < x < xrange[1]"""
return np.logical_and(x > x_range[0], x < x_range[1])
def rms(x):
"""Root-mean square"""
return np.sqrt(x.dot(x)/x.size)
def shift_slice(slice_in, start=0, stop=0, step_multiple=0):
"""Create new slice object based on existing slice
Note: No check to make sure slice_in and other inputs match
Also no check for values changing from +ve to -ve or vice versa
Inputs
------
slice_in: slice object
Existing slice on which new one will be based
start, stop: Integers
Amount by which to move start of stop
step_multiple: Integer
Factor by which to change step
Returns
-------
slice_new: slice object
"""
new_start, new_stop, new_step = None, None, None
if slice_in.start is not None:
new_start = slice_in.start + start
if slice_in.stop is not None:
new_stop = slice_in.stop + stop
if slice_in.step is not None:
new_step = slice_in.step*step_multiple
return slice(new_start, new_stop, new_step)