def _get_weights(self, axis=None, mirror_nans=True, weights=None): """ Build array of weights based on individual axes' weights attribute Parameters ---------- axis : int or str, optional if None a N-D weight is created, otherwise 1-D only mirror_nans : bool, optional mirror `values`'s NaNs in created weights (True by default) {weights} Returns ------- full_weights : DimArray of weights, or None See Also -------- DimArray.mean, DimArray.var, DimArray.std Examples -------- Simpler examples are found Dimarray.mean's doc. These are mostly meant as doctest. >>> from dimarray import DimArray >>> np.random.seed(0) # to make results reproducible >>> v = DimArray(np.random.rand(3,2), axes=[[-80, 0, 80], [-180, 180]], dims=['lat','lon']) >>> v._get_weights() # None >>> w = lambda x : np.cos(np.radians(v.lat)) >>> v.axes['lat'].weights = w >>> v._get_weights() dimarray: 6 non-null elements (0 null) 0 / lat (3): -80 to 80 1 / lon (2): -180 to 180 array([[ 0.17364818, 0.17364818], [ 1. , 1. ], [ 0.17364818, 0.17364818]]) >>> v.axes['lat'].weights = None >>> v._get_weights() # None Can pass the weigthts as a dictionary >>> v._get_weights(weights={{'lat':w}}) dimarray: 6 non-null elements (0 null) 0 / lat (3): -80 to 80 1 / lon (2): -180 to 180 array([[ 0.17364818, 0.17364818], [ 1. , 1. ], [ 0.17364818, 0.17364818]]) Or by indicating the axis >>> v._get_weights(weights=w, axis='lat') dimarray: 6 non-null elements (0 null) 0 / lat (3): -80 to 80 1 / lon (2): -180 to 180 array([[ 0.17364818, 0.17364818], [ 1. , 1. ], [ 0.17364818, 0.17364818]]) It possible to mix up axis and dict-input weights >>> v.axes['lat'].weights = w >>> v._get_weights(weights={{'lon':lambda x:((x+180)/100)**2}}) dimarray: 6 non-null elements (0 null) 0 / lat (3): -80 to 80 1 / lon (2): -180 to 180 array([[ 0. , 1.5628336], [ 0. , 9. ], [ 0. , 1.5628336]]) But if the transformation is required on one axis only, weights on other axes are not accounted for since they would not affect the result of the transformation. >>> v._get_weights(weights={{'lon':lambda x:((x+180)/100)**2}}, axis='lat') dimarray: 6 non-null elements (0 null) 0 / lat (3): -80 to 80 1 / lon (2): -180 to 180 array([[ 0.17364818, 0.17364818], [ 1. , 1. ], [ 0.17364818, 0.17364818]]) What is provided as argument overrides axis attribute. Here when the weights are provided for one axis only: >>> v._get_weights(weights=lambda x:((x+180)/100)**2, axis='lat') dimarray: 6 non-null elements (0 null) 0 / lat (3): -80 to 80 1 / lon (2): -180 to 180 array([[ 1., 1.], [ 1., 1.], [ 4., 4.]]) Or when the weight is provided as a numpy array >>> w = np.array([[ 1., 1.], ... [ 1., 1.], ... [ 4., 4.]]) >>> v._get_weights(weights=w) dimarray: 6 non-null elements (0 null) 0 / lat (3): -80 to 80 1 / lon (2): -180 to 180 array([[ 1., 1.], [ 1., 1.], [ 4., 4.]]) Or even as DimArray (which will be broadcast with the appropriate shape) >>> wa = DimArray(w[:,0], axes=[v.axes['lat']]) >>> v._get_weights(weights=wa) dimarray: 6 non-null elements (0 null) 0 / lat (3): -80 to 80 1 / lon (2): -180 to 180 array([[ 1., 1.], [ 1., 1.], [ 4., 4.]]) Any nans in the data will induce 0 in the weights, since the element will then be discarded or the result be NaN anyway. >>> v.values[1, 0] = np.nan >>> v._get_weights(weights=w) dimarray: 6 non-null elements (0 null) 0 / lat (3): -80 to 80 1 / lon (2): -180 to 180 array([[ 1., 1.], [ 0., 1.], [ 4., 4.]]) """ # avoid circular import from dimarray import DimArray # dims: axes from which the weights should be computed if axis is None: dims = self.dims elif type(axis) is tuple: dims = axis else: dims = (axis,) # common case: no weight is provided and no Axis has weights # if is enough to check on all axes to be reduced, weights on other axes # make no difference to the reduction transformation if weights is None and np.all([self.axes[dim].weights is None for dim in dims]): return None # for 1-D array, same as axis is None is like axis=0 if axis is None and self.ndim == 1: axis = 0 # check `weights` type # special case: if weights is DimArray, just use it as full weight if isinstance(weights, DimArray): full_weights = weights.broadcast(self.axes) # broadcast weights on appropriate dimension # special case II: numpy array of appropriate dimension ==> just use it as full weight elif isinstance(weights, np.ndarray) and weights.ndim > 1: assert weights.shape == self.shape, "`weights` must be either 1-D or have same shape as DimArray" full_weights = DimArray(weights, self.axes) # make axis weights based by combining single-axis weights (standard form) else: array_type = isinstance(weights, np.ndarray) and weights.dim == 1 dict_type = isinstance(weights, dict) assert weights is None or callable(weights) or array_type or dict_type, "unexpected type for `weights`" assert weights is None or not (not dict_type and axis is None), "`weights` type incompatible with flattening operation, provide `axis` or use dict-type weights" assert weights is None or not (not dict_type and type(axis) is tuple), "`weights` type incompatible with flattening operation, use dict-type weights" # make weights dict-like (operation on single axis) if not dict_type: weights = {axis: weights} # Create weights from the axes (only compute for the axes to be reduced) full_weights = 1. for axis in dims: ax = self.axes[axis] # get proper weights array: from `Axis` or user-provided on-the-fly if weights is not None and axis in weights.keys(): axis_weights = weights[axis] else: axis_weights = ax.weights # if weight is None, just skip it if axis_weights is None: continue # broadcast and multiply weights for all axes involved full_weights = ax._get_weights(axis_weights).reshape(dims) * full_weights # SPECIAL CASE: NO weights (e.g. weights parameter provided as {} or {'lat':None} and no axis weights attributes): if full_weights is 1.: return None full_weights = full_weights.broadcast(self.axes) # set 0 in a where there are NaNs. if mirror_nans and anynan(self.values): full_weights.values[np.isnan(self.values)] = 0 assert full_weights is not None return full_weights