def eng_string(x: float, unit: str = None, format='%.3g', si=True) -> str: ''' Taken from: https://stackoverflow.com/questions/17973278/python-decimal-engineering-notation-for-mili-10e-3-and-micro-10e-6/40691220 Returns float/int value <x> formatted in a simplified engineering format - using an exponent that is a multiple of 3. Args: x: The value to be formatted. Float or int. unit: A unit of the quantity to be expressed, given as a string. Example: Newtons -> "N" format: A printf-style string used to format the value before the exponent. si: if true, use SI suffix for exponent. (k instead of e3, n instead of e-9, etc.) Examples: With format='%.2f': 1.23e-08 -> 12.30e-9 123 -> 123.00 1230.0 -> 1.23e3 -1230000.0 -> -1.23e6 With si=True: 1230.0 -> "1.23k" -1230000.0 -> "-1.23M" With unit="N" and si=True: 1230.0 -> "1.23 kN" -1230000.0 -> "-1.23 MN" ''' sign = '' if x < 0: x = -x sign = '-' elif x == 0: return format % 0 elif np.isnan(x): return "NaN" exp = int(np.floor(np.log10(x))) exp3 = exp - (exp % 3) x3 = x / (10**exp3) if si and exp3 >= -24 and exp3 <= 24 and exp3 != 0: exp3_text = 'yzafpnμm kMGTPEZY'[(exp3 + 24) // 3] elif exp3 == 0: exp3_text = '' else: exp3_text = f'e{exp3}' if unit is not None: if si: exp3_text = " " + exp3_text + unit else: exp3_text = exp3_text + " " + unit return ('%s' + format + '%s') % (sign, x3, exp3_text)
def test_interpn_fill_value(): def value_func_3d(x, y, z): return 2 * x + 3 * y - z x = np.linspace(0, 5, 10) y = np.linspace(0, 5, 20) z = np.linspace(0, 5, 30) points = (x, y, z) values = value_func_3d(*np.meshgrid(*points, indexing="ij")) point = np.array([5.21, 3.12, 1.15]) value = np.interpn( points, values, point, method="bspline", bounds_error=False, fill_value=-17 ) assert value == pytest.approx(-17) value = np.interpn( points, values, point, method="bspline", bounds_error=False, ) assert np.isnan(value) value = np.interpn( points, values, point, method="bspline", bounds_error=None, fill_value=None ) assert value == pytest.approx(value_func_3d(5, 3.12, 1.15))
def remove_nans(array): """ Removes NaN values in a 1D array. Args: array: a 1D array of data. Returns: The array with all NaN values stripped. """ return array[~np.isnan(array)]
def patch_nans( array): # TODO remove modification on incoming values; only patch nans """ Patches NaN values in a 2D array. Can patch holes or entire regions. Uses Laplacian smoothing. :param array: :return: """ original_nans = np.isnan(array) nanfrac = lambda array: np.sum(np.isnan(array)) / len(array.flatten()) def item(i, j): if i < 0 or j < 0: # don't allow wrapping other than what's controlled here return np.nan try: return array[i, j % array.shape[1]] # allow wrapping around day of year except IndexError: return np.nan print_title = lambda name: print(f"{name}\nIter | NaN Fraction") print_progress = lambda iter: print(f"{iter:4} | {nanfrac(array):.6f}") # Bridging print_title("Bridging") print_progress(0) iter = 1 last_nanfrac = nanfrac(array) making_progress = True while making_progress: for i in range(array.shape[0]): for j in range(array.shape[1]): if not np.isnan(array[i, j]): continue pairs = [ [item(i, j - 1), item(i, j + 1)], [item(i - 1, j), item(i + 1, j)], [item(i - 1, j + 1), item(i + 1, j - 1)], [item(i - 1, j - 1), item(i + 1, j + 1)], ] for pair in pairs: a = pair[0] b = pair[1] if not (np.isnan(a) or np.isnan(b)): array[i, j] = (a + b) / 2 continue print_progress(iter) making_progress = nanfrac(array) != last_nanfrac last_nanfrac = nanfrac(array) iter += 1 # Spreading for neighbors_to_spread in [4, 3, 2, 1]: print_title(f"Spreading with {neighbors_to_spread} neighbors") print_progress(0) iter = 1 last_nanfrac = nanfrac(array) making_progress = True while making_progress: for i in range(array.shape[0]): for j in range(array.shape[1]): if not np.isnan(array[i, j]): continue neighbors = np.array([ item(i, j - 1), item(i, j + 1), item(i - 1, j), item(i + 1, j), item(i - 1, j + 1), item(i + 1, j - 1), item(i - 1, j - 1), item(i + 1, j + 1), ]) valid_neighbors = neighbors[np.logical_not( np.isnan(neighbors))] if len(valid_neighbors) > neighbors_to_spread: array[i, j] = np.mean(valid_neighbors) print_progress(iter) making_progress = nanfrac(array) != last_nanfrac last_nanfrac = nanfrac(array) iter += 1 if last_nanfrac == 0: break assert last_nanfrac == 0, "Could not patch all NaNs!" # Diffusing print_title( "Diffusing" ) # TODO Perhaps use skimage gaussian blur kernel or similar instead of "+" stencil? for iter in range(50): print(f"{iter + 1:4}") for i in range(array.shape[0]): for j in range(array.shape[1]): if original_nans[i, j]: neighbors = np.array([ item(i, j - 1), item(i, j + 1), item(i - 1, j), item(i + 1, j), ]) valid_neighbors = neighbors[np.logical_not( np.isnan(neighbors))] array[i, j] = np.mean(valid_neighbors) return array