def fit(self, X, y=None, sample_weight=None): """Fit the model with X. Parameters ---------- X : Triangle-like Set of LDFs to which the tail will be applied. y : Ignored sample_weight : Ignored Returns ------- self : object Returns the instance itself. """ super().fit(X, y, sample_weight) xp = cp.get_array_module(X.values) tail = self.tail if self.attachment_age: attach_idx = xp.min(xp.where(X.ddims >= self.attachment_age)) else: attach_idx = len(X.ddims) - 1 self = self._apply_decay(X, tail, attach_idx) obj = Development().fit_transform(X) if 'ldf_' not in X else X xp = cp.get_array_module(X.values) if xp.max(self.tail) != 1.0: sigma, std_err = self._get_tail_stats(obj) self.sigma_.values[..., -1] = sigma[..., -1] self.std_err_.values[..., -1] = std_err[..., -1] return self
def _get_tail_prediction(self, tail_ldf): xp = cp.get_array_module(tail_ldf) accum_point = self.ldf_.shape[-1] - 1 ave = 1 + tail_ldf[..., :accum_point] all = xp.prod(1 + tail_ldf[..., accum_point:], -1)[..., None] tail = xp.concatenate((ave, all), -1) return tail
def predict(self, X, sample_weight=None): """Predicts the chainladder ultimate on a new triangle **X** Parameters ---------- X : Triangle The data used to compute the mean and standard deviation used for later scaling along the features axis. sample_weight : Triangle For exposure-based methods, the exposure to be used for predictions Returns ------- X_new: Triangle """ obj = copy.deepcopy(X) xp = cp.get_array_module(obj.values) obj.ldf_ = self.ldf_ if obj.ldf_.shape[0] != obj.shape[0]: obj.ldf_.values = xp.repeat(obj.ldf_.values, len(obj.index), 0) obj.ldf_.kdims = obj.kdims obj.ldf_.key_labels = obj.key_labels obj.ultimate_ = self._get_ultimate(obj, sample_weight) return obj
def fit(self, X, y=None, sample_weight=None): """Fit the model with X. Parameters ---------- X : Triangle-like Set of LDFs to which the munich adjustment will be applied. y : Ignored sample_weight : Ignored Returns ------- self : object Returns the instance itself. """ obj = copy.copy(X) xp = cp.get_array_module(obj.values) obj.values = xp.ones(X.shape)[..., :-1] ldf = xp.array([float(self.patterns[item]) for item in obj.ddims[:-1]]) if self.style == 'cdf': ldf = xp.concatenate((ldf[:-1] / ldf[1:], xp.array([ldf[-1]]))) ldf = ldf[xp.newaxis, xp.newaxis, xp.newaxis, ...] obj.values = obj.values * ldf obj.ddims = X.link_ratio.ddims obj.valuation = obj._valuation_triangle(obj.ddims) obj.nan_override = True obj._set_slicers() self.ldf_ = obj self.cdf_ = self._get_cdf(self) self.sigma_ = self.ldf_ * 0 + 1 self.std_err_ = self.ldf_ * 0 + 1 return self
def to_frame(self, *args, **kwargs): """ Converts a triangle to a pandas.DataFrame. Requires an individual index and column selection to appropriately grab the 2D DataFrame. Returns ------- pandas.DataFrame representation of the Triangle. """ xp = cp.get_array_module(self.values) axes = [num for num, item in enumerate(self.shape) if item > 1] if self.shape[:2] == (1, 1): return self._repr_format() elif len(axes) == 2 or len(axes) == 1: tri = xp.squeeze(self.values) axes_lookup = { 0: self.kdims, 1: self.vdims, 2: self.origin, 3: self.ddims } if len(axes) == 2: return pd.DataFrame(tri, index=axes_lookup[axes[0]], columns=axes_lookup[axes[1]]).fillna(0) if len(axes) == 1: return pd.Series(tri, index=axes_lookup[axes[0]]).fillna(0) else: raise ValueError('len(index) and len(columns) must be 1.')
def agg_func(self, axis=None, *args, **kwargs): obj = copy.deepcopy(self) if axis is None: axis = min([num for num, _ in enumerate(obj.shape) if _ != 1]) else: axis = self._get_axis(axis) xp = cp.get_array_module(obj.values) func = getattr(xp, v) kwargs.update({'keepdims': True}) obj.values = func(obj.values, axis=axis, *args, **kwargs) if axis == 0 and obj.values.shape[axis] == 1: obj.kdims = np.array([None]) obj.key_labels = [None] if axis == 1 and obj.values.shape[axis] == 1: obj.vdims = np.array([None]) if axis == 2 and obj.values.shape[axis] == 1: obj.odims = np.array([None]) if axis == 3 and obj.values.shape[axis] == 1: obj.ddims = np.array([None]) obj._set_slicers() obj.values = obj.values * obj._expand_dims(obj._nan_triangle()) obj.values[obj.values == 0] = np.nan if obj.shape == (1, 1, 1, 1): return obj.values[0, 0, 0, 0] else: return obj
def _get_hat(self, X, exp_incr_triangle): """ The hat matrix adjustment (Shapland eq3.23)""" xp = cp.get_array_module(X.values) weight_matrix = xp.diag( pd.DataFrame(exp_incr_triangle).unstack().dropna().values) design_matrix = self.design_matrix_ hat = xp.matmul( xp.matmul( xp.matmul( design_matrix, xp.linalg.inv( xp.matmul(design_matrix.T, xp.matmul(weight_matrix, design_matrix)))), design_matrix.T), weight_matrix) hat = xp.diagonal( xp.sqrt(xp.divide(1, abs(1 - hat), where=(1 - hat) != 0))) total_length = X._nan_triangle().shape[0] reshaped_hat = xp.reshape(hat[:total_length], (1, total_length)) indices = xp.nansum(X._nan_triangle(), axis=0).cumsum().astype(int) for num, item in enumerate(indices[:-1]): col_length = int(indices[num + 1] - indices[num]) col = xp.reshape(hat[int(indices[num]):int(indices[num + 1])], (1, col_length)) nans = xp.repeat(xp.expand_dims(xp.array([xp.nan]), 0), total_length - col_length, axis=1) col = xp.concatenate((col, nans), axis=1) reshaped_hat = xp.concatenate((reshaped_hat, col), axis=0) return reshaped_hat.T
def infer_x_w(self): xp = cp.get_array_module(self.y) if self.w is None: self.w = xp.ones(self.y.shape) if self.x is None: self.x = xp.cumsum(xp.ones(self.y.shape), self.axis) return self
def _mack_recursion(self, est): obj = copy.copy(self.X_) xp = cp.get_array_module(obj.values) nans = self.X_._nan_triangle()[xp.newaxis, xp.newaxis] nans = nans * xp.ones(self.X_.shape) nans = xp.concatenate((nans, xp.ones( (*self.X_.shape[:3], 1)) * xp.nan), 3) nans = 1 - xp.nan_to_num(nans) properties = self.full_triangle_ obj.valuation = properties.valuation obj.ddims = np.concatenate((properties.ddims[:len(self.X_.ddims)], np.array([properties.ddims[-1]]))) obj.nan_override = True risk_arr = xp.zeros((*self.X_.shape[:3], 1)) if est == 'param_risk': obj.values = self._get_risk(nans, risk_arr, obj.std_err_.values) obj._set_slicers() self.parameter_risk_ = obj elif est == 'process_risk': obj.values = self._get_risk(nans, risk_arr, self.full_std_err_.values) obj._set_slicers() self.process_risk_ = obj else: risk_arr = risk_arr[..., 0:1, :] obj.values = self._get_tot_param_risk(risk_arr) obj.odims = ['Total param risk'] obj._set_slicers() self.total_parameter_risk_ = obj
def mack_std_err_(self): obj = copy.copy(self.parameter_risk_) xp = cp.get_array_module(obj.values) obj.values = xp.sqrt(self.parameter_risk_.values**2 + self.process_risk_.values**2) obj._set_slicers() return obj
def link_ratio(self): xp = cp.get_array_module(self.values) obj = copy.deepcopy(self) if hasattr(obj, '_nan_triangle_'): del obj._nan_triangle_ temp = obj.values.copy() temp[temp == 0] = np.nan val_array = obj.valuation.values.reshape(obj.shape[-2:], order='f')[:, 1:] obj.values = temp[..., 1:] / temp[..., :-1] obj.ddims = np.array([ '{}-{}'.format(obj.ddims[i], obj.ddims[i + 1]) for i in range(len(obj.ddims) - 1) ]) # Check whether we want to eliminate the last origin period if xp.max(xp.sum(~xp.isnan(self.values[..., -1, :]), 2) - 1) == 0: obj.values = obj.values[..., :-1, :] obj.odims = obj.odims[:-1] val_array = val_array[:-1, :] obj.valuation = pd.DatetimeIndex( pd.DataFrame(val_array).unstack().values).to_period( self._lowest_grain()) if hasattr(obj, 'w_'): obj = obj * obj.w_[..., 0:1, :len(obj.odims), :] return obj
def predict(self, X, sample_weight=None): """Predicts the chainladder ultimate on a new triangle **X** Parameters ---------- X : Triangle The data used to compute the mean and standard deviation used for later scaling along the features axis. sample_weight : Triangle For exposure-based methods, the exposure to be used for predictions Returns ------- X_new: Triangle """ obj = copy.copy(self) xp = cp.get_array_module(X.values) obj.X_ = copy.copy(X) obj.sample_weight = sample_weight if xp.unique(self.cdf_.values, axis=-2).shape[-2] == 1: obj.cdf_.values = xp.repeat( xp.unique(self.cdf_.values, axis=-2), len(X.odims), -2) obj.ldf_.values = xp.repeat( xp.unique(self.ldf_.values, axis=-2), len(X.odims), -2) obj.cdf_.odims = obj.ldf_.odims = obj.X_.odims obj.cdf_.valuation = obj.ldf_.valuation = \ Development().fit(X).cdf_.valuation obj.cdf_._set_slicers() obj.ldf_._set_slicers() return obj
def _get_full_triangle_(self): obj = copy.copy(self.X_) xp = cp.get_array_module(obj.values) w = 1-xp.nan_to_num(obj._nan_triangle()) extend = len(self.ldf_.ddims) - len(self.X_.ddims) ones = xp.ones((w.shape[-2], extend)) w = xp.concatenate((w, ones), -1) obj.nan_override = True e_tri = \ xp.repeat(self.ultimate_.values, self.cdf_.values.shape[3], 3) / \ xp.unique(self.cdf_.values, axis=-2) e_tri = e_tri * w zeros = obj._expand_dims(ones - ones) properties = self.full_expectation_ obj.valuation = properties.valuation obj.valuation_date = properties.valuation_date obj.ddims = properties.ddims obj.values = \ xp.concatenate((xp.nan_to_num(obj.values), zeros), -1) + e_tri obj.values = xp.concatenate((obj.values, self.ultimate_.values), 3) obj.values[obj.values==0] = xp.nan obj._set_slicers() if hasattr(self.X_, '_get_process_variance'): obj = self.X_._get_process_variance(obj) self.ultimate_.values = obj.values[..., -1:] return obj
def fit(self, X, y=None, sample_weight=None): """Fit the model with X. Parameters ---------- X : Triangle-like Set of LDFs to which the tail will be applied. y : Ignored sample_weight : Ignored Returns ------- self : object Returns the instance itself. """ super().fit(X, y, sample_weight) xp = cp.get_array_module(X.values) decay_range = self.ldf_.shape[-1]-X.shape[-1]+1 ldfs = 1+self._get_initial_ldf(xp)*(self.decay**xp.arange(1000)) ldfs = ldfs[:decay_range] ldfs[-1] = self.tail/xp.prod(ldfs[:-1]) ldfs = X._expand_dims(ldfs[xp.newaxis]) self.ldf_.values[..., -decay_range:] = \ self.ldf_.values[..., -decay_range:]*ldfs self.cdf_ = DevelopmentBase._get_cdf(self) return self
def __eq__(self, other): xp = cp.get_array_module(self.values) if xp.all(xp.nan_to_num(self.values) == xp.nan_to_num(other.values)): return True else: return False
def broadcast_axis(self, axis, value): """ Broadcasts (i.e. repeats) triangles along an axis. The axis to be broadcast must be of length 1. Parameters ---------- axis : str or int the axis to be broadcast over. value : axis-like The value of the new axis. TODO: Should convert value to a primitive type """ obj = copy.deepcopy(self) axis = self._get_axis(axis) xp = cp.get_array_module(self.values) if self.shape[axis] != 1: raise ValueError('Axis to be broadcast must be of length 1') elif axis > 1: raise ValueError('Only index and column axes are supported') else: obj.values = xp.repeat(obj.values, len(value), axis) if axis == 0: obj.key_labels = list(value.columns) obj.kdims = value.values obj.index = value if axis == 1: obj.vdims = value.values obj.columns = value return obj
def munich_full_triangle_(self): full_paid = self.p_to_i_X_[0][..., 0:1] xp = cp.get_array_module(full_paid) full_incurred = self.p_to_i_X_[1][..., 0:1] for i in range(self.p_to_i_X_[0].shape[-1] - 1): paid = (self.p_to_i_ldf_[0][..., i:i + 1] + self.lambda_coef_[0] * self.p_to_i_sigma_[0][..., i:i + 1] / self.rho_sigma_[0][..., i:i + 1] * (full_incurred[..., -1:] / full_paid[..., -1:] - self.q_f_[0][..., i:i + 1])) * full_paid[..., -1:] inc = (self.p_to_i_ldf_[1][..., i:i + 1] + self.lambda_coef_[1] * self.p_to_i_sigma_[1][..., i:i + 1] / self.rho_sigma_[1][..., i:i + 1] * (full_paid[..., -1:] / full_incurred[..., -1:] - self.q_f_[1][..., i:i + 1])) * full_incurred[..., -1:] full_incurred = xp.concatenate( (full_incurred, xp.nan_to_num(self.p_to_i_X_[1][..., i + 1:i + 2]) + (1 - xp.nan_to_num(self.p_to_i_X_[1][..., i + 1:i + 2] * 0 + 1)) * inc), axis=3) full_paid = xp.concatenate( (full_paid, xp.nan_to_num(self.p_to_i_X_[0][..., i + 1:i + 2]) + (1 - xp.nan_to_num(self.p_to_i_X_[0][..., i + 1:i + 2] * 0 + 1)) * paid), axis=3) return self._p_to_i_concate(full_paid, full_incurred)
def _predict_tail(self, slope, intercept, extrapolate): xp = cp.get_array_module(extrapolate) if self.curve == 'exponential': tail_ldf = xp.exp(slope * extrapolate + intercept) if self.curve == 'inverse_power': tail_ldf = xp.exp(intercept) * (extrapolate**slope) return self._get_tail_prediction(tail_ldf)
def _get_ultimate_(self, X, sample_weight, obj): ult = copy.copy(obj.X_) xp = cp.get_array_module(ult.values) origin, development = -2, -1 # Set axes by name latest = X.latest_diagonal.values if self.apriori_sigma != 0: random_state = xp.random.RandomState(self.random_state) apriori = random_state.normal( self.apriori, self.apriori_sigma, X.shape[0]) apriori = apriori.reshape(X.shape[0],-1)[..., np.newaxis, np.newaxis] apriori = sample_weight.values * apriori else: apriori = sample_weight.values*self.apriori ult.values = \ obj.cdf_.values[..., :ult.shape[development]]*(ult.values*0+1) cdf = ult.latest_diagonal.values cdf = (1-1/cdf)[xp.newaxis] exponents = xp.arange(self.n_iters+1) exponents = xp.reshape(exponents, tuple([len(exponents)]+[1]*4)) cdf = cdf**exponents ult.values = xp.sum(cdf[:-1, ...], 0)*latest+cdf[-1, ...]*apriori ult.values[~xp.isfinite(ult.values)] = xp.nan ult.ddims = np.array([None]) ult.valuation = pd.DatetimeIndex([pd.to_datetime('2262-04-11')] * ult.shape[origin]) ult._set_slicers() return ult
def fit(self, X, y=None, sample_weight=None): """Fit the model with X. Parameters ---------- X : Triangle-like Set of LDFs to which the tail will be applied. y : Ignored sample_weight : Triangle-like Exposure vector used to invoke the Cape Cod method. Returns ------- self : object Returns the instance itself. """ super().fit(X, y, sample_weight) model = ClarkLDF(growth=self.growth).fit(X, sample_weight=sample_weight) xp = cp.get_array_module(X.values) age_offset = {'Y': 6., 'Q': 1.5, 'M': 0.5}[X.development_grain] tail = 1 / model.G_( xp.array([ item * self._ave_period[1] + X.ddims[-1] - age_offset for item in range(self._ave_period[0] + 1) ])) tail = xp.concatenate((tail.values[..., :-1] / tail.values[..., -1], tail.values[..., -1:]), -1) self.ldf_.values = xp.concatenate( (X.ldf_.values, xp.repeat(tail, X.shape[2], 2)), -1) self.cdf_ = DevelopmentBase._get_cdf(self) return self
def append(self, other): """ Append rows of other to the end of caller, returning a new object. Parameters ---------- other : Triangle The data to append. Returns ------- New Triangle with appended data. """ xp = cp.get_array_module(self.values) return_obj = copy.deepcopy(self) return_obj.kdims = (return_obj.index.append(other.index)).values try: return_obj.values = xp.concatenate( (return_obj.values, other.values), axis=0) except: # For misaligned triangle support self.values = xp.concatenate( (return_obj.values, (return_obj.iloc[:, 0] * 0 + other.values).values), axis=1) return_obj._set_slicers() return return_obj
def __init__(self, old_obj, by): xp = cp.get_array_module(old_obj.values) self.orig_obj = copy.deepcopy(old_obj) if xp == sp: if by != -1: self.idx = self.orig_obj.index.iloc[ self.orig_obj.values.coords[0]].reset_index(drop=True) else: self.idx = pd.DataFrame(np.repeat( np.repeat(np.array([['All']]), old_obj.values.coords.shape[1], 0), len(old_obj.key_labels), 1), columns=old_obj.key_labels) by = old_obj.key_labels groupby = pd.concat( (pd.DataFrame(self.orig_obj.values.coords[1:].T, columns=[1, 2, 3]), self.idx), axis=1) groupby['values'] = self.orig_obj.values.data by = [by] if type(by) is str else by self.obj = groupby.groupby(by + [1, 2, 3]) else: if by != -1: self.idx = self.orig_obj.index.set_index(by).index else: self.idx = pd.DataFrame(np.repeat( np.repeat(np.array([['All']]), old_obj.shape[0], 0), len(old_obj.key_labels), 1), columns=old_obj.key_labels).set_index( old_obj.key_labels).index by = old_obj.key_labels groupby = pd.DataFrame(self.orig_obj.values.reshape( (self.orig_obj.shape[0], 1, 1, -1))[:, 0, 0, :], index=self.idx) self.obj = groupby.reset_index().groupby(by)
def quantile(self, q, axis=1, *args, **kwargs): """ Return values at the given quantile over requested axis. If Triangle is convertible to DataFrame then pandas quantile functionality is used instead. Parameters ---------- q: float or array-like, default 0.5 (50% quantile) Value between 0 <= q <= 1, the quantile(s) to compute. axis: {0, 1, ‘index’, ‘columns’} (default 1) Equals 0 or ‘index’ for row-wise, 1 or ‘columns’ for column-wise. Returns ------- Triangle """ xp = cp.get_array_module(self.obj.values) x = self.obj.values * self.old_k_by_new_k ignore_vector = xp.sum(xp.isnan(x), axis=1, keepdims=True) == \ x.shape[1] x = xp.where(ignore_vector, 0, x) self.obj.values = \ getattr(xp, 'nanpercentile')(x, q*100, axis=1, *args, **kwargs) self.obj.values[self.obj.values == 0] = np.nan return self.obj
def agg_func(self, axis=1, *args, **kwargs): obj = copy.deepcopy(self.obj) xp = cp.get_array_module(self.orig_obj.values) obj = getattr(self.obj, v)(*args, **kwargs) if xp == sp: obj = obj.reset_index() new_idx = obj[obj.columns[:-4]].drop_duplicates().reset_index( drop=True).reset_index().set_index(list(obj.columns[:-4])) obj = obj.set_index(list(obj.columns[:-4])).merge(new_idx, how='inner', left_index=True, right_index=True) self.orig_obj.values.coords = obj[['index', 1, 2, 3]].values.T self.orig_obj.values.data = obj['values'].values self.orig_obj.values.shape = tuple( [len(new_idx)] + list(self.orig_obj.values.shape[1:])) self.orig_obj.kdims = np.array(new_idx.index) self.orig_obj.key_labels = list(new_idx.index.names) else: self.orig_obj.values = obj.values.reshape(len(self.idx.unique()), *self.orig_obj.shape[1:]) self.orig_obj.values[self.orig_obj.values == 0] = np.nan self.orig_obj.kdims = np.array(obj.index) self.orig_obj.key_labels = list(self.idx.names) return self.orig_obj
def _repr_format(self): if type(self.odims[0]) == np.datetime64: origin = pd.Series(self.odims).dt.to_period(self.origin_grain) else: origin = pd.Series(self.odims) if len(self.ddims) == 1 and self.ddims[0] is None: ddims = list(self.vdims) else: ddims = self.ddims if cp.get_array_module(self.values).__name__ == 'cupy': out = cp.asnumpy(self.values[0, 0]) else: out = self.values[0, 0] out = pd.DataFrame(out, index=origin, columns=ddims) if str(out.columns[0]).find('-') > 0 and not \ isinstance(out.columns, pd.PeriodIndex): out.columns = [ item.replace('-9999', '-Ult') for item in out.columns ] if len(out.drop_duplicates()) != 1: return out else: return out.drop_duplicates().set_index(pd.Index(['(All)'])) else: return out
def link_ratio(self): xp = cp.get_array_module(self.values) obj = copy.deepcopy(self) temp = obj.values.copy() val_array = obj.valuation.values.reshape(obj.shape[-2:], order='f')[:, 1:] obj.ddims = np.array([ '{}-{}'.format(obj.ddims[i], obj.ddims[i + 1]) for i in range(len(obj.ddims) - 1) ]) if xp != sp: temp[temp == 0] = np.nan obj.values = temp[..., 1:] / temp[..., :-1] # Check whether we want to eliminate the last origin period if xp.max(xp.sum(~xp.isnan(self.values[..., -1, :]), 2) - 1) <= 0: obj.values = obj.values[..., :-1, :] else: temp.fill_value = np.nan temp = temp[..., 1:] / temp[..., :-1] temp.fill_value = 0.0 temp.coords = temp.coords[:, temp.data != 0] temp.data = temp.data[temp.data != 0] temp.shape = tuple(temp.coords.max(1) + 1) obj.values = sp(temp) obj.odims = obj.odims[:obj.values.shape[2]] if hasattr(obj, 'w_'): if obj.shape == obj.w_[..., 0:1, :len(obj.odims), :].shape: obj = obj * obj.w_[..., 0:1, :len(obj.odims), :] return obj
def _align_cdf(self, ultimate): """ Vertically align CDF to ultimate vector """ xp = cp.get_array_module(ultimate.values) ultimate.values = \ self.cdf_.values[..., :ultimate.shape[-1]]*(ultimate.values*0+1) cdf = ultimate.latest_diagonal.values return cdf
def incr_to_cum(self, inplace=False): """Method to convert an incremental triangle into a cumulative triangle. Parameters ---------- inplace: bool Set to True will update the instance data attribute inplace Returns ------- Updated instance of triangle accumulated along the origin """ xp = cp.get_array_module(self.values) if inplace: if not self.is_cumulative: self.values = xp.cumsum(xp.nan_to_num(self.values), axis=3) self.values = self._expand_dims( self.nan_triangle) * self.values self.num_to_nan() self.is_cumulative = True self._set_slicers() return self else: new_obj = copy.deepcopy(self) return new_obj.incr_to_cum(inplace=True)
def _assign_n_periods_weight_int(self, X, n_periods): ''' Zeros out weights depending on number of periods desired Only works for type(n_periods) == int ''' xp = cp.get_array_module(X.values) if n_periods < 1 or n_periods >= X.shape[-2] - 1: return X.values * 0 + 1 else: val_offset = { 'Y': { 'Y': 1 }, 'Q': { 'Y': 4, 'Q': 1 }, 'M': { 'Y': 12, 'Q': 3, 'M': 1 } } val_date_min = \ X.valuation[X.valuation<=X.valuation_date].drop_duplicates().sort_values() val_date_min = \ val_date_min[-n_periods * \ val_offset[X.development_grain][X.origin_grain] - 1] w = X[X.valuation >= val_date_min] return xp.nan_to_num( (w / w).values) * X._expand_dims(X._nan_triangle())
def cum_to_incr(self, inplace=False): """Method to convert an cumlative triangle into a incremental triangle. Parameters ---------- inplace: bool Set to True will update the instance data attribute inplace Returns ------- Updated instance of triangle accumulated along the origin """ xp = cp.get_array_module(self.values) if inplace: if self.is_cumulative or self.is_cumulative is None: temp = xp.nan_to_num(self.values)[..., 1:] - \ xp.nan_to_num(self.values)[..., :-1] temp = xp.concatenate((self.values[..., 0:1], temp), axis=3) temp = temp * self._expand_dims(self.nan_triangle) if xp != sp: temp[temp == 0] = np.nan self.values = temp self.is_cumulative = False self._set_slicers() return self else: new_obj = copy.deepcopy(self) return new_obj.cum_to_incr(inplace=True)