Skip to content

Commit b505e47

Browse files
committed
Metric class
1 parent ffb109b commit b505e47

5 files changed

Lines changed: 54 additions & 52 deletions

File tree

src/flexibleSubsetSelection/algorithm.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# --- Imports ------------------------------------------------------------------
22

3+
import math
4+
35
# Third party
46
import cvxpy as cp
57
import gurobipy as gp
@@ -34,8 +36,8 @@ def randomSample(
3436
"""
3537
rng = np.random.default_rng(seed)
3638
indices = rng.choice(datasetSize[0], size=subsetSize, replace=False)
37-
z = np.zeros(datasetSize[0])
38-
z[indices] = 1
39+
z = np.zeros(datasetSize[0], dtype=bool)
40+
z[indices] = True
3941
return z, indices
4042

4143

src/flexibleSubsetSelection/dataset.py

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ def metrics(self) -> list[str]:
195195
Returns:
196196
A list of metric names.
197197
"""
198-
return list(self._metrics)
198+
return self._metrics
199199

200200
def compute(
201201
self,
@@ -220,24 +220,16 @@ def compute(
220220
if array is None:
221221
array = "original"
222222

223-
if features is not None:
224-
try:
225-
indices = [self.features.index(f) for f in features]
226-
except ValueError as e:
227-
errorMessage = "Feature not found in 'compute()'."
228-
log.exception(errorMessage)
229-
raise RuntimeError(errorMessage) from e
230223
for name, function in metric.items():
231224
self._metrics[name] = Metric(
232225
dataset = self,
233226
name = name,
234227
function = function,
235228
array = array,
236229
params = params,
237-
indices = indices
230+
features = features
238231
)
239232

240-
241233
def scale(
242234
self,
243235
interval: tuple[float, float] | None = None,
@@ -248,8 +240,7 @@ def scale(
248240
249241
Args:
250242
interval: The interval to scale the data to.
251-
features: The features to scale. All features will be scaled if
252-
unspecified.
243+
features: The features to scale. All will scale if unspecified
253244
254245
Raises:
255246
ValueError: if features are not found
@@ -283,8 +274,7 @@ def discretize(
283274
284275
Args:
285276
bins: Number of bins to use, bins in each feature, or bin edges.
286-
features: List of features to use for the binning. All features will
287-
be encoded if unspecified.
277+
features: List of features to bin. All will bin if unspecified.
288278
strategy: sklearn KBinsDiscretizer strategy to use.
289279
290280
Raises:
@@ -315,8 +305,7 @@ def encode(
315305
assuming they are discrete.
316306
317307
Args:
318-
features: The features to use for the binning. All features will be
319-
encoded if unspecified.
308+
features: The features to encode. All will encode if unspecified.
320309
dimensions: The number of dimensions to take the encoding in
321310
322311
Raises:
@@ -373,34 +362,34 @@ def save(
373362
log.info("Data successfully saved at '%s'.", filePath)
374363

375364
def __repr__(self) -> str:
376-
transformString = (
365+
transformStr = (
377366
f", transforms=original→{'→'.join(self._transforms.queued[1:])}"
378367
if len(self._transforms) > 1
379368
else ""
380369
)
381370
return (
382371
f"Dataset(name='{self.name}', size={self.size}, "
383-
f"features={self.features} {transformString})"
372+
f"features={self.features} {transformStr})"
384373
)
385374

386375
def __str__(self) -> str:
387376
transforms = self._transforms.queued[1:]
388377
if len(transforms) == 0:
389-
transformString = ""
378+
transformStr = ""
390379
elif len(transforms) == 1:
391-
transformString = f"{transforms[0]}"
380+
transformStr = f"{transforms[0]}"
392381
elif len(transforms) == 2:
393-
transformString = f"{transforms[0]} and {transforms[1]}"
382+
transformStr = f"{transforms[0]} and {transforms[1]}"
394383
else:
395-
transformString = f"{', '.join(transforms[:-1])} and {transforms[-1]}"
384+
transformStr = f"{', '.join(transforms[:-1])} and {transforms[-1]}"
396385

397-
featureString = (
386+
featureStr = (
398387
f"[{', '.join(map(str, self.features[:3]))}"
399388
f"{', ...' if len(self.features) > 3 else ''}]"
400389
)
401390
return (
402391
f"Dataset {self.name}: {self.size[0]} rows x "
403-
f"{self.size[1]} features {featureString} {transformString}"
392+
f"{self.size[1]} features {featureStr} {transformStr}"
404393
)
405394

406395
def __len__(self) -> int:
@@ -411,13 +400,15 @@ def __len__(self) -> int:
411400

412401
def __getattr__(self, attr: str) -> np.ndarray:
413402
"""
414-
Returns the specified transformed version of the dataset.
403+
Returns paramters of the dataset.
415404
416405
Args:
417-
attr: Specify the name of a transform function
406+
attr: Specify the parameter to get
418407
"""
419408
if attr in self._transforms.queued:
420409
return self._transforms[attr]
410+
elif attr in self._metrics:
411+
return self._metrics[attr]()
421412
else:
422413
raise AttributeError(f"'Dataset' object has no attribute '{attr}'")
423414

src/flexibleSubsetSelection/metric.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from sklearn.cluster import KMeans
1111

1212
# Local files
13-
from .dataset import Dataset
1413
from . import logger
1514

1615
# Setup logger
@@ -25,12 +24,12 @@ class Metric:
2524

2625
def __init__(
2726
self,
28-
dataset: Dataset,
27+
dataset: "Dataset",
2928
name: str,
3029
function: Callable,
3130
array: str,
3231
params: dict[str, Any] | None = None,
33-
indices: list[int]| None = None
32+
features: list[int]| None = None
3433
):
3534
"""
3635
Initialize a metric with a metric function and additional parameters
@@ -39,7 +38,7 @@ def __init__(
3938
self.name = name
4039
self.params = params
4140
self.array = array
42-
self._indices = indices
41+
self._features = features
4342
self._dataset = dataset
4443
self._value = None
4544

@@ -48,8 +47,15 @@ def __init__(
4847
def __call__(self):
4948
if self._value is None:
5049
array = getattr(self._dataset, self.array)
51-
if self._indices is not None:
52-
array = array[:, self._indices]
50+
if self._features is not None:
51+
try:
52+
features = self._dataset.features
53+
indices = [features.index(f) for f in self._features]
54+
array = array[:, indices]
55+
except ValueError as e:
56+
errorMessage = "Feature not found in 'compute()'."
57+
log.exception(errorMessage)
58+
raise RuntimeError(errorMessage) from e
5359
if self.params is None:
5460
self._value = self.function(array)
5561
else:
@@ -113,7 +119,6 @@ def discreteDistribution(array: np.ndarray) -> np.ndarray:
113119
"""
114120
return np.mean(array, axis=0)
115121

116-
117122
def clusterCenters(array: np.ndarray, k: int) -> np.ndarray:
118123
"""
119124
Returns the cluster centers of the k-means clustering for k clusters of the

src/flexibleSubsetSelection/objective.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ def emdCategorical(subset, dataset, features, categorical, categories):
171171

172172

173173
def entropy(array: np.ndarray) -> float:
174-
counts = Counter(map(tuple, array))
175-
total = sum(counts.values())
176-
probabilities = np.array(list(counts.values())) / total
177-
return np.sum(probabilities * np.log(probabilities))
174+
_, counts = np.unique(array, axis=0, return_counts=True)
175+
probs = counts / counts.sum()
176+
return np.sum(probs * np.log(probs))

src/flexibleSubsetSelection/subset.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,6 @@ def __init__(
6262
self.loss = loss
6363
log.info("Created %s.", self)
6464

65-
@property
66-
def transforms(self) -> list[str]:
67-
"""
68-
Expose available dataset transformations (mirrored from dataset).
69-
"""
70-
return self.dataset.transforms
71-
7265
@classmethod
7366
def load(
7467
cls,
@@ -112,7 +105,7 @@ def save(
112105
name: str = None,
113106
) -> None:
114107
"""
115-
Save the subset to pickle for convenience or to csv for portability.
108+
Save the subset to pickle for convenience or csv for portability.
116109
117110
Args:
118111
fileType: The file format to save the data: 'pickle' or 'csv'.
@@ -183,23 +176,35 @@ def __getattr__(self, attr: str) -> np.ndarray:
183176
def __len__(self) -> int:
184177
return self.size[0]
185178

179+
@property
180+
def transforms(self) -> list[str]:
181+
"""
182+
Expose available dataset transformations (mirrored from dataset).
183+
"""
184+
return self.dataset.transforms
185+
186186
@staticmethod
187-
def select(array: np.ndarray, z: np.ndarray, selectBy: str) -> np.ndarray:
187+
def select(
188+
array: np.ndarray,
189+
z: np.ndarray[bool],
190+
selectBy: str
191+
) -> np.ndarray:
188192
"""
189-
Selects a subset from array according to indicator z
193+
Selects a subset from array according to indicator vector z.
190194
191195
Args:
192196
array: The array to select from.
193197
z: The indicator vector indicating which elements to select.
194-
selectBy: The method to select the subset (row or matrix).
198+
selectBy: 'row' for row selection of tabular data or 'matrix' for
199+
selection from square matrix e.g. distance matrix
195200
196201
Returns: The selected subset from the array.
197202
198203
Raises: ValueError: If an unknown selection method is specified.
199204
"""
200205
if selectBy == "row":
201-
return array[z == 1]
206+
return array[z]
202207
elif selectBy == "matrix":
203-
return array[z == 1][:, z == 1]
208+
return array[z][:, z]
204209
else:
205210
raise ValueError("Unknown selection method specified.")

0 commit comments

Comments
 (0)