Skip to content

Commit

Permalink
resolves #97
Browse files Browse the repository at this point in the history
  • Loading branch information
jbogaardt committed Sep 5, 2020
1 parent 0c8baa1 commit ebe43a0
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 45 deletions.
86 changes: 43 additions & 43 deletions chainladder/core/slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
import pandas as pd
import numpy as np
from sparse._slicing import normalize_index


class _LocBase:
Expand Down Expand Up @@ -57,59 +58,62 @@ class Location(_LocBase):
""" class to generate .loc[] functionality """

def __getitem__(self, key):
if type(key) == pd.Series:
return self.obj[key]
if type(key) == tuple and type(key[0]) == pd.Series:
return self.obj[key[0]][key[1]]
if type(key) == pd.DataFrame:
if len(self.obj.key_labels) == 1:
key = pd.Index(key.iloc[:, 0])
else:
key = pd.Index(key)
key = (key,) if type(key) is not tuple else key
key = list(key) + [slice(None)] * (4 - len(key))
idx = self.obj.index.reset_index().set_index(self.obj.key_labels)
sliced = idx.loc[key[0]]
if type(sliced) is pd.Series:
sliced = sliced.to_frame().T
sliced.index.rename(idx.index.name, inplace=True)
sliced = sliced.iloc[:, 0]
key[0] = sliced.to_list()
filter_idx = idx.reset_index()[
list(sliced.reset_index().drop("index", 1).columns)
]
s = pd.Series(self.obj.columns).to_frame().reset_index()
key[1] = s.set_index(0).loc[key[1]].values.flatten()
s = pd.Series(self.obj.origin).to_frame().reset_index()
key[2] = s.set_index("origin").loc[key[2]].values.flatten()
s = pd.Series(self.obj.development).to_frame().reset_index()
key[3] = s.set_index("development").loc[key[3]].values.flatten()
key_mask = tuple([i if i is Ellipsis else 0 for i in key])
if len(key_mask) < len(self.obj.shape) and Ellipsis not in key_mask:
key_mask = tuple(list(key_mask) + [Ellipsis])
key_mask = list(normalize_index(key_mask, self.obj.shape))
key = [item for item in key if item is not Ellipsis]
for i in range(len(self.obj.shape)):
if key_mask[i] == 0:
key_mask[i] = key[0]
key.pop(0)
key = key_mask
# Support for multi-index
default = slice(None, None, None)
norm = lambda k: type(k) is slice and (k.start == 0 or k == default)
filter_idx = None
if not norm(key[0]) and not type(key[0]) is pd.Series:
idx = self.obj.index.reset_index().set_index(self.obj.key_labels)
sliced = idx.loc[key[0]]
if type(sliced) is pd.Series:
sliced = sliced.to_frame().T
sliced.index.rename(idx.index.name, inplace=True)
sliced = sliced.iloc[:, 0]
key[0] = sliced.to_list()
filter_idx = list(sliced.reset_index().drop("index", 1).columns)
filter_idx = idx.reset_index()[filter_idx]

def normalize(key, idx):
mapper = {1: "columns", 2: "origin", 3: "development"}
out = key[idx]
if not norm(key[idx]) and not isinstance(key, pd.Series):
s = pd.Series(getattr(self.obj, mapper[idx])).to_frame().reset_index()
out = s.set_index(mapper[idx]).loc[key[idx]].values.flatten()
return out

key = [key[0]] + [normalize(key, 1), normalize(key, 2), normalize(key, 3)]
return self.get_idx(key, filter_idx)


class Ilocation(_LocBase):
""" class to generate .iloc[] functionality """

def __getitem__(self, key):
key = (key,) if type(key) is not tuple else key
key = list(key) + [slice(None)] * (4 - len(key))
return self.get_idx(key)
return self.get_idx(normalize_index(key, self.obj.shape))


class TriangleSlicer:
""" Slicer functionality """

def __getitem__(self, key):
""" Function for pandas style column indexing"""
""" Boolean Slicer functionality """
if type(key) is pd.Series and key.name == "development":
return self._slice(key, "ddims")
if type(key) is np.ndarray:
# Presumes that if I have a 1D array, I will want to slice origin.
if len(key) == np.prod(self.shape[-2:]) and self.shape[-1] > 1:
return self._slice_valuation(key)
return self._slice(key, "odims")
if type(key) is pd.Series:
return self.iloc[list(self.index[key].index), :]
return self.iloc[self.index[key].index]
elif key in self.key_labels:
return self.index[key]
else:
Expand All @@ -118,17 +122,14 @@ def __getitem__(self, key):
return self.iloc[:, idx]

def __setitem__(self, key, value):
""" Function for pandas style column indexing setting """
""" Function for pandas style column setting """
xp = self.get_array_module()
if key in self.vdims:
i = np.where(self.vdims == key)[0][0]
if self.array_backend == "sparse":
before = self.drop(key).values
before.coords[1] = np.where(
before.coords[1, :] >= i,
before.coords[1, :] + 1,
before.coords[1, :],
)
bc = before.coords[1, :]
before.coords[1] = np.where(bc >= i, bc + 1, bc,)
value.values.coords[1] = i
coords = np.concatenate((before.coords, value.values.coords), axis=1)
data = np.concatenate((before.data, value.values.data))
Expand All @@ -141,9 +142,8 @@ def __setitem__(self, key, value):
self.values = xp.concatenate((self.values, value.values), axis=1)
except:
# For misaligned triangle support
self.values = xp.concatenate(
(self.values, (self.iloc[:, 0] * 0 + value).values), axis=1
)
conc = (self.values, (self.iloc[:, 0] * 0 + value).values)
self.values = xp.concatenate(conc, axis=1)

def _slice_valuation(self, key):
""" private method for handling of valuation slicing """
Expand Down
9 changes: 9 additions & 0 deletions chainladder/core/tests/test_triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,3 +637,12 @@ def test_init_vector():
b = cl.Triangle(b, origin="AccYear", columns="premium")
assert np.all(a.valuation == b.valuation)
assert a.valuation_date == b.valuation_date

def test_loc_ellipsis():
assert tri == tri_gt
assert tri.loc['Aegis Grp'] == tri.loc['Adriatic Ins Co':'Aegis Grp'].loc['Aegis Grp']
assert tri.loc['Aegis Grp', ... , :] == tri.loc['Aegis Grp']
assert tri.loc[..., 24:] == tri.loc[..., :, 24:]
assert tri.loc[:, ..., 24:] == tri.loc[..., :, 24:]
assert tri.loc[:, 'CumPaidLoss'] == tri.loc[:, 'CumPaidLoss', ...]
assert tri.loc[..., 'CumPaidLoss', :, :] == tri.loc[:, 'CumPaidLoss', :, :]
2 changes: 1 addition & 1 deletion chainladder/core/triangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def set_index(self, value, inplace=False):

@property
def columns(self):
return pd.Index(self.vdims)
return pd.Index(self.vdims, name="columns")

@columns.setter
def columns(self, value):
Expand Down
5 changes: 4 additions & 1 deletion chainladder/tails/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ def fit(self, X, y=None, sample_weight=None):
self.sigma_.ddims = self.std_err_.ddims = np.concatenate(
(obj.ldf_.ddims, np.array(["{}-9999".format(int(obj.ddims[-1]))]))
)
self.average_ = obj.average_
if hasattr(obj, 'average_'):
self.average_ = obj.average_
else:
self.average_ = None
self.ldf_._set_slicers()
self.sigma_._set_slicers()
self.std_err_._set_slicers()
Expand Down

0 comments on commit ebe43a0

Please sign in to comment.