def set_backend(self, backend, inplace=False, deep=False): """ Converts triangle array_backend. Parameters ---------- backend : str Currently supported options are 'numpy', 'sparse', and 'cupy' inplace : bool Whether to mutate the existing Triangle instance or return a new one. Returns ------- Triangle with updated array_backend """ if hasattr(self, "array_backend"): old_backend = self.array_backend else: if hasattr(self, "ldf_"): old_backend = self.ldf_.array_backend else: raise ValueError("Unable to determine array backend.") if inplace: if backend in ["numpy", "sparse", "cupy"]: lookup = { "numpy": { "sparse": lambda x: x.todense(), "cupy": lambda x: cp.asnumpy(x), }, "cupy": { "numpy": lambda x: cp.array(x), "sparse": lambda x: cp.array(x.todense()), }, "sparse": { "numpy": lambda x: sp.array(x), "cupy": lambda x: sp.array(cp.asnumpy(x)), }, } if hasattr(self, "values"): self.values = lookup[backend].get(old_backend, lambda x: x)( self.values ) if deep: for k, v in vars(self).items(): if isinstance(v, Common): v.set_backend(backend, inplace=True, deep=True) if hasattr(self, "array_backend"): self.array_backend = backend else: raise AttributeError(backend, "backend is not supported.") return self else: obj = self.copy() return obj.set_backend(backend=backend, inplace=True, deep=deep)
def set_backend(self, backend, inplace=False): ''' Converts triangle array_backend. Parameters ---------- backend : str Currently supported options are 'numpy', 'sparse', and 'cupy' inplace : bool Whether to mutate the existing Triangle instance or return a new one. Returns ------- Triangle with updated array_backend ''' if hasattr(self, 'array_backend'): old_backend = self.array_backend else: if hasattr(self, 'ldf_'): old_backend = self.ldf_.array_backend else: raise ValueError('Unable to determine array backend.') if inplace: if backend in ['numpy', 'sparse', 'cupy']: lookup = { 'numpy': { 'sparse': lambda x: x.todense(), 'cupy': lambda x: cp.asnumpy(x) }, 'cupy': { 'numpy': lambda x: cp.array(x), 'sparse': lambda x: cp.array(x.todense()) }, 'sparse': { 'numpy': lambda x: sp.array(x), 'cupy': lambda x: sp.array(cp.asnumpy(x)) } } if hasattr(self, 'values'): self.values = lookup[backend].get(old_backend, lambda x: x)(self.values) for k, v in vars(self).items(): if isinstance(v, Common): v.set_backend(backend, inplace=True) if hasattr(self, 'array_backend'): self.array_backend = backend else: raise AttributeError(backend, 'backend is not supported.') return self else: obj = copy.deepcopy(self) return obj.set_backend(backend=backend, inplace=True)
def _slice_valuation(self, key): ''' private method for handling of valuation slicing ''' obj = copy.deepcopy(self) obj.valuation_date = min(obj.valuation[key].max(), obj.valuation_date) key = key.reshape(self.shape[-2:], order='f') nan_tri = np.ones(self.shape[-2:]) nan_tri = key * nan_tri nan_tri[nan_tri == 0] = np.nan o, d = nan_tri.shape o_idx = np.arange(o)[list(np.sum(np.isnan(nan_tri), 1) != d)] d_idx = np.arange(d)[list(np.sum(np.isnan(nan_tri), 0) != o)] obj.odims = obj.odims[np.sum(np.isnan(nan_tri), 1) != d] if len(obj.ddims) > 1: obj.ddims = obj.ddims[np.sum(np.isnan(nan_tri), 0) != o] xp = cp.get_array_module(obj.values) if xp == cp: nan_tri = cp.array(nan_tri) if xp == sp: nan_tri = sp(nan_tri) obj.values = (obj.values * nan_tri) if np.all(o_idx == np.array(range(o_idx[0], o_idx[-1] + 1))): o_idx = slice(o_idx[0], o_idx[-1] + 1) if np.all(d_idx == np.array(range(d_idx[0], d_idx[-1] + 1))): d_idx = slice(d_idx[0], d_idx[-1] + 1) if type(o_idx) is slice or type(d_idx) is slice: # If contiguous slices, this is faster obj.values = obj.values[..., o_idx, d_idx] else: obj.values = xp.take(xp.take(obj.values, o_idx, -2), d_idx, -1) return obj
def _slice_development(self, key): ''' private method for handling of development slicing ''' obj = copy.deepcopy(self) obj.ddims = obj.ddims[key] if cp.get_array_module(obj.values) == cp: key = cp.array(key) obj.values = obj.values[..., key] return self._cleanup_slice(obj)
def read_json(json_str, array_backend=None): def sparse_in(json_str, dtype, shape): k, v, o, d = shape x = json.loads(json_str) y = np.array([tuple([int(idx) for idx in item[1:-1].split(',')]) for item in x.keys()]) new = coo_matrix( (np.array(list(x.values())), (y[:, 0], y[:, 1])), shape=(k*v*o, d), dtype=dtype).toarray().reshape(k,v,o,d) new[new==0] = np.nan return new if array_backend is None: from chainladder import ARRAY_BACKEND array_backend = ARRAY_BACKEND json_dict = json.loads(json_str) if type(json_dict) is list: import chainladder as cl return Pipeline(steps=[ (item['name'], cl.__dict__[item['__class__']]().set_params(**item['params'])) for item in json_dict]) elif 'kdims' in json_dict.keys(): tri = Triangle(array_backend=array_backend) arrays = ['kdims', 'vdims', 'odims', 'ddims'] for array in arrays: setattr(tri, array, np.array( json_dict[array]['array'], dtype=json_dict[array]['dtype'])) shape = (len(tri.kdims), len(tri.vdims), len(tri.odims), len(tri.ddims)) properties = ['key_labels', 'origin_grain', 'development_grain', 'nan_override', 'is_cumulative'] for prop in properties: setattr(tri, prop, json_dict[prop]) if json_dict.get('is_val_tri', False): tri.ddims = pd.PeriodIndex(tri.ddims, freq=tri.development_grain).to_timestamp(how='e') tri.valuation_date = pd.to_datetime( json_dict['valuation_date'], format='%Y-%m-%d').to_period('M').to_timestamp(how='e') tri._set_slicers() tri.valuation = tri._valuation_triangle() if json_dict['values'].get('sparse', None): tri.values = sparse_in(json_dict['values']['array'], json_dict['values']['dtype'], shape) if tri.is_cumulative: tri.is_cumulative = False tri = tri.incr_to_cum() else: tri.values = np.array(json_dict['values']['array'], dtype=json_dict['values']['dtype']) if array_backend == 'cupy': tri.values = cp.array(tri.values) return tri else: import chainladder as cl return cl.__dict__[ json_dict['__class__']]().set_params(**json_dict['params'])
def _slice_valuation(self, key): ''' private method for handling of valuation slicing ''' obj = copy.deepcopy(self) obj.valuation_date = min(obj.valuation[key].max(), obj.valuation_date) key = key.reshape(self.shape[-2:], order='f') nan_tri = np.ones(self.shape[-2:]) nan_tri = key * nan_tri nan_tri[nan_tri == 0] = np.nan o, d = nan_tri.shape o_idx = np.arange(o)[list(np.sum(np.isnan(nan_tri), 1) != d)] d_idx = np.arange(d)[list(np.sum(np.isnan(nan_tri), 0) != o)] obj.odims = obj.odims[np.sum(np.isnan(nan_tri), 1) != d] if len(obj.ddims) > 1: obj.ddims = obj.ddims[np.sum(np.isnan(nan_tri), 0) != o] xp = cp.get_array_module(obj.values) if xp == cp: nan_tri = cp.array(nan_tri) obj.values = (obj.values * nan_tri) obj.values = xp.take(xp.take(obj.values, o_idx, -2), d_idx, -1) return self._cleanup_slice(obj)
def _validate_arithmetic(self, other): ''' Common functionality BEFORE arithmetic operations ''' obj = copy.deepcopy(self) xp = cp.get_array_module(obj.values) other = other if type(other) in [int, float] else copy.deepcopy(other) ddims = None odims = None if type(other) not in [int, float, np.float64, np.int64]: if len(self.vdims) != len(other.vdims): raise ValueError('Triangles must have the same number of ' + 'columns') if len(self.kdims) != len(other.kdims): raise ValueError('Triangles must have the same number of ' + 'index') if len(self.vdims) == 1: other.vdims = np.array([None]) # If broadcasting doesn't work, then try union of origin/developments # before failure a, b = self.shape[-2:], other.shape[-2:] if not (a[0] == 1 or b[0] == 1 or a[0] == b[0]) or \ not (a[1] == 1 or b[1] == 1 or a[1] == b[1]): ddims = pd.concat((pd.Series(self.ddims, index=self.ddims), pd.Series(other.ddims, index=other.ddims)), axis=1) odims = pd.concat((pd.Series(self.odims, index=self.odims), pd.Series(other.odims, index=other.odims)), axis=1) other_arr = xp.zeros( (other.shape[0], other.shape[1], len(odims), len(ddims))) other_arr[:] = xp.nan o_arr1 = odims[1].isna().values o_arr0 = odims[0].isna().values d_arr1 = ddims[1].isna().values d_arr0 = ddims[0].isna().values if xp == cp: o_arr1 = cp.array(o_arr1) o_arr0 = cp.array(o_arr0) d_arr1 = cp.array(d_arr1) d_arr0 = cp.array(d_arr0) ol = int(xp.where(~o_arr1 == 1)[0].min()) oh = int(xp.where(~o_arr1 == 1)[0].max()+1) if np.any(self.ddims != other.ddims): dl = int(xp.where(~d_arr1 == 1)[0].min()) dh = int(xp.where(~d_arr1 == 1)[0].max()+1) other_arr[:, :, ol:oh, dl:dh] = other.values else: other_arr[:, :, ol:oh, :] = other.values obj_arr = xp.zeros( (self.shape[0], self.shape[1], len(odims), len(ddims))) obj_arr[:] = xp.nan ol = int(xp.where(~o_arr0 == 1)[0].min()) oh = int(xp.where(~o_arr0 == 1)[0].max()+1) if np.any(self.ddims != other.ddims): dl = int(xp.where(~d_arr0 == 1)[0].min()) dh = int(xp.where(~d_arr0 == 1)[0].max()+1) obj_arr[:, :, ol:oh, dl:dh] = self.values else: obj_arr[:, :, ol:oh, :] = self.values odims = np.array(odims.index) ddims = np.array(ddims.index) obj.ddims = ddims obj.odims = odims obj.values = obj_arr other.values = other_arr obj._set_slicers() obj.valuation = obj._valuation_triangle() if hasattr(obj, '_nan_triangle_'): # Force update on _nan_triangle at next access. del obj._nan_triangle_ other = other.values return obj, other
def read_json(json_str, array_backend=None): def sparse_in(json_str, dtype, shape): k, v, o, d = shape x = json.loads(json_str) y = np.array([ tuple([int(idx) for idx in item[1:-1].split(",")]) for item in x.keys() ]) new = (coo_matrix( (np.array(list(x.values())), (y[:, 0], y[:, 1])), shape=(k * v * o, d), dtype=dtype, ).toarray().reshape(k, v, o, d)) new[new == 0] = np.nan return new if array_backend is None: from chainladder import ARRAY_BACKEND array_backend = ARRAY_BACKEND json_dict = json.loads(json_str) if type(json_dict) is list: import chainladder as cl return Pipeline(steps=[( item["name"], cl.__dict__[item["__class__"]]().set_params(**item["params"]), ) for item in json_dict]) elif "kdims" in json_dict.keys(): tri = Triangle() tri.array_backend = array_backend arrays = ["kdims", "vdims", "odims", "ddims"] for array in arrays: setattr( tri, array, np.array(json_dict[array]["array"], dtype=json_dict[array]["dtype"]), ) shape = (len(tri.kdims), len(tri.vdims), len(tri.odims), len(tri.ddims)) properties = [ "key_labels", "origin_grain", "development_grain", "is_cumulative", ] for prop in properties: setattr(tri, prop, json_dict[prop]) if json_dict.get("is_val_tri", False): tri.ddims = pd.PeriodIndex( tri.ddims, freq=tri.development_grain).to_timestamp(how="e") tri.valuation_date = (pd.to_datetime( json_dict["valuation_date"], format="%Y-%m-%d").to_period("M").to_timestamp(how="e")) if json_dict["values"].get("sparse", None): tri.values = sparse_in(json_dict["values"]["array"], json_dict["values"]["dtype"], shape) else: tri.values = np.array(json_dict["values"]["array"], dtype=json_dict["values"]["dtype"]) if array_backend == "cupy": tri.values = cp.array(tri.values) if tri.is_cumulative: tri.is_cumulative = False tri = tri.incr_to_cum() if "sub_tris" in json_dict.keys(): for k, v in json_dict["sub_tris"].items(): setattr(tri, k, read_json(v, array_backend)) if "dfs" in json_dict.keys(): for k, v in json_dict["dfs"].items(): df = pd.read_json(v) if len(df.columns) == 1: df = df.iloc[:, 0] setattr(tri, k, df) tri._set_slicers() return tri else: import chainladder as cl return cl.__dict__[json_dict["__class__"]]().set_params( **json_dict["params"])