subsetmatrix 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- subsetmatrix/__init__.py +0 -0
- subsetmatrix/dataset_payload.py +74 -0
- subsetmatrix/engine.py +52 -0
- subsetmatrix/payload_args_validation.py +139 -0
- subsetmatrix/selecting_subsets.py +70 -0
- subsetmatrix-0.1.0.dist-info/METADATA +671 -0
- subsetmatrix-0.1.0.dist-info/RECORD +10 -0
- subsetmatrix-0.1.0.dist-info/WHEEL +5 -0
- subsetmatrix-0.1.0.dist-info/licenses/LICENSE +21 -0
- subsetmatrix-0.1.0.dist-info/top_level.txt +1 -0
subsetmatrix/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from math import comb
|
|
3
|
+
from subsetmatrix.engine import generateMatrix
|
|
4
|
+
from subsetmatrix.selecting_subsets import normalize_k_values
|
|
5
|
+
from typing import Any
|
|
6
|
+
from subsetmatrix.payload_args_validation import validate_args, validate_kwargs
|
|
7
|
+
|
|
8
|
+
class ObservationSet:
|
|
9
|
+
"""This class instantiates objects with a set of points and keeps a
|
|
10
|
+
matrix with all possible combinations for subsets, the main dataset and
|
|
11
|
+
performs methods on them for several usecases.
|
|
12
|
+
### properties:
|
|
13
|
+
- X is a list of labels for the observations. They might come as small strings
|
|
14
|
+
or numbers, dates or whatever else picks your fancy, or not come at all, case
|
|
15
|
+
in which we will generate a list of integers as index to the observations;
|
|
16
|
+
- Y is a list of observations. For scientific ends, this one should be some
|
|
17
|
+
type of numeric value, but since this tool is here to answer diversefied needs,
|
|
18
|
+
no use limiting its reach through that, so it might also be a list of anything.
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
21
|
+
# grabbing the first significant argument
|
|
22
|
+
args_dict = validate_args(*args) if args else {}
|
|
23
|
+
kwargs_dict = validate_kwargs(
|
|
24
|
+
**{k:v for k, v in kwargs.items() if k != "indexing_as_one"}
|
|
25
|
+
) if kwargs else {}
|
|
26
|
+
|
|
27
|
+
if not args_dict and not kwargs_dict:
|
|
28
|
+
raise ValueError("No data was detected while initiating this object.")
|
|
29
|
+
|
|
30
|
+
self.Y = args_dict.get("Y")
|
|
31
|
+
if self.Y is None:
|
|
32
|
+
self.Y = kwargs_dict.get("Y")
|
|
33
|
+
|
|
34
|
+
if self.Y is None:
|
|
35
|
+
raise ValueError("No values for Y were detected while initiating this object.")
|
|
36
|
+
|
|
37
|
+
self.n = len(self.Y)
|
|
38
|
+
|
|
39
|
+
self.X = args_dict.get("X")
|
|
40
|
+
if self.X is None:
|
|
41
|
+
self.X = kwargs_dict.get("X")
|
|
42
|
+
|
|
43
|
+
if self.X is None:
|
|
44
|
+
self.indexing_as_one = bool(kwargs.get("indexing_as_one", True))
|
|
45
|
+
start = 1 if self.indexing_as_one else 0
|
|
46
|
+
self.X = list(range(start, self.n + start))
|
|
47
|
+
|
|
48
|
+
if len(self.X) != len(self.Y):
|
|
49
|
+
raise ValueError(f"The length of the X set and of the Y set are of different sizes: X: {len(self.X)} and Y: {len(self.Y)}")
|
|
50
|
+
|
|
51
|
+
def get_subsets(self, k:int|list[int]) -> list[list[list[Any]]]:
|
|
52
|
+
k_values = normalize_k_values(k, self.n)
|
|
53
|
+
temp_matrix = generateMatrix(self.n, k_values)
|
|
54
|
+
|
|
55
|
+
# one bulk nonzero pass over the whole matrix instead of one
|
|
56
|
+
# np.flatnonzero call per row: rows are grouped by k in k_values
|
|
57
|
+
# order and every row within a k-group has exactly k ones, so the
|
|
58
|
+
# flat column-index stream can be reshaped per group without
|
|
59
|
+
# re-touching numpy per row.
|
|
60
|
+
_, col_idx = np.nonzero(temp_matrix)
|
|
61
|
+
|
|
62
|
+
res = []
|
|
63
|
+
pos = 0
|
|
64
|
+
for k_val in k_values:
|
|
65
|
+
count = comb(self.n, k_val)
|
|
66
|
+
# .tolist() converts the whole block in one bulk C call; iterating
|
|
67
|
+
# a numpy array element-by-element boxes each entry into a numpy
|
|
68
|
+
# scalar, which is far costlier than plain-int indexing below.
|
|
69
|
+
block = col_idx[pos:pos + count * k_val].reshape(count, k_val).tolist()
|
|
70
|
+
pos += count * k_val
|
|
71
|
+
for combo in block:
|
|
72
|
+
res.append([[self.X[j], self.Y[j]] for j in combo]) # type: ignore
|
|
73
|
+
return res
|
|
74
|
+
|
subsetmatrix/engine.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import numpy.typing as npt
|
|
3
|
+
from math import comb
|
|
4
|
+
from typing import Iterator
|
|
5
|
+
|
|
6
|
+
def iter_k_masks(n: int, k: int) -> Iterator[int]:
|
|
7
|
+
"""Generator yielding subsets with the same ammount
|
|
8
|
+
of points (k) to the limit of observations on the set."""
|
|
9
|
+
if not 0 < k <= n:
|
|
10
|
+
raise ValueError("K must satisfy 0 < k <= n")
|
|
11
|
+
limit = (1 << n) # assuming n = 3, we would have 1 -> 2 -> 4 -> 8, thus the limit is 8
|
|
12
|
+
mask = (1 << k) - 1 # assuming k = 2, first k would be 1 -> 2 -> 4, 4 - 1 = 3
|
|
13
|
+
|
|
14
|
+
while mask < limit:
|
|
15
|
+
yield mask
|
|
16
|
+
|
|
17
|
+
# Gosper's hack: next integer with the same number of set bits, implying subsets of the same size
|
|
18
|
+
c = mask & -mask
|
|
19
|
+
r = mask + c
|
|
20
|
+
mask = (((r ^ mask) >> 2) // c) | r
|
|
21
|
+
|
|
22
|
+
def cardinality(mask: int) -> int:
|
|
23
|
+
"""Useful if you're getting a mask outside of context and needs to know where it belongs."""
|
|
24
|
+
return mask.bit_count()
|
|
25
|
+
|
|
26
|
+
def generateMatrix(n:int, K:list=[]) -> npt.NDArray[np.uint32]:
|
|
27
|
+
"""The matrix won't follow the cardinal order of growth, but the k-sized groups."""
|
|
28
|
+
if not K:
|
|
29
|
+
K = list(range(2, n + 1)) # skip singletons (k=1); include the full set (k=n)
|
|
30
|
+
if n < 3:
|
|
31
|
+
raise ValueError("The number of observations should be > 2.")
|
|
32
|
+
if n > 20:
|
|
33
|
+
raise ValueError("Dense uint32 generation is temporarily capped at n <=20")
|
|
34
|
+
|
|
35
|
+
total_rows = sum(comb(n, k) for k in K) # sized to whatever K actually requests, not the full 2**n - 2 span
|
|
36
|
+
matr = np.empty((total_rows, n), dtype=np.uint32)
|
|
37
|
+
bits = np.arange(n, dtype=np.uint32)
|
|
38
|
+
row_start = 0
|
|
39
|
+
for k in K:
|
|
40
|
+
# mask generation stays a plain-Python loop (cheap); the mask -> row
|
|
41
|
+
# expansion is done ONCE per k-group via broadcasting instead of once
|
|
42
|
+
# per row, since numpy's per-call dispatch overhead otherwise dwarfs
|
|
43
|
+
# the actual work on rows this small.
|
|
44
|
+
count = comb(n, k)
|
|
45
|
+
masks = np.fromiter(iter_k_masks(n, k), dtype=np.uint32, count=count)
|
|
46
|
+
matr[row_start:row_start + count] = (masks[:, None] >> bits[None, :]) & 1
|
|
47
|
+
row_start += count
|
|
48
|
+
return matr
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# if __name__ == "__main__":
|
|
52
|
+
# generateMatrix(3)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
def validate_args(*args) -> dict:
|
|
2
|
+
"""Validates args and delivers them organized in a preconceived dict."""
|
|
3
|
+
res = {}
|
|
4
|
+
all_args = [arg for arg in args]
|
|
5
|
+
if not all_args:
|
|
6
|
+
return res
|
|
7
|
+
|
|
8
|
+
if isinstance(all_args[0], dict):
|
|
9
|
+
points = all_args[0]
|
|
10
|
+
y_temp = points.get("Y")
|
|
11
|
+
x_temp = points.get("X")
|
|
12
|
+
if isinstance(y_temp, dict) or isinstance(x_temp, dict):
|
|
13
|
+
raise ValueError("Y and X should be lists or tuples, like so: 'Y': [1, 2, 3, 4, 5].")
|
|
14
|
+
if isinstance(y_temp, list|tuple) and len(y_temp) > 2:
|
|
15
|
+
# for now we keep iterables out of the story. Later on it makes sense to consider
|
|
16
|
+
pairs = [p for p in y_temp if isinstance(p, list|tuple) and len(p) == 2]
|
|
17
|
+
if len(pairs) > 2:
|
|
18
|
+
res["Y"] = []
|
|
19
|
+
res["X"] = []
|
|
20
|
+
for i in pairs:
|
|
21
|
+
if isinstance(i[0], int|float|complex|str) and isinstance(i[1], int|float|complex|str):
|
|
22
|
+
res["X"].append(i[0])
|
|
23
|
+
res["Y"].append(i[1])
|
|
24
|
+
else:
|
|
25
|
+
res["Y"] = [x for x in y_temp if isinstance(x, int|float|complex|str)]
|
|
26
|
+
elif isinstance(y_temp, dict) and len(y_temp) > 2:
|
|
27
|
+
res["Y"] = []
|
|
28
|
+
res["X"] = []
|
|
29
|
+
for k, v in y_temp.items():
|
|
30
|
+
if isinstance(k, int|float|complex|str) and isinstance(v, int|float|complex|str):
|
|
31
|
+
res["X"].append(k)
|
|
32
|
+
res["Y"].append(v)
|
|
33
|
+
if isinstance(x_temp, list|tuple):
|
|
34
|
+
res["X"] = [x for x in x_temp if isinstance(x, int|float|complex|str)]
|
|
35
|
+
|
|
36
|
+
elif isinstance(all_args[0], list|tuple) and len(all_args[0]) > 2:
|
|
37
|
+
y_temp = all_args[0]
|
|
38
|
+
# I could seed directly into res["Y"] and res["X"], but checking if this fits sounds better
|
|
39
|
+
pairs = [p for p in y_temp if isinstance(p, list|tuple) and len(p) == 2]
|
|
40
|
+
if len(pairs) > 2:
|
|
41
|
+
res["Y"] = []
|
|
42
|
+
res["X"] = []
|
|
43
|
+
for i in pairs:
|
|
44
|
+
if isinstance(i[0], int|float|complex|str) and isinstance(i[1], int|float|complex|str):
|
|
45
|
+
res["X"].append(i[0])
|
|
46
|
+
res["Y"].append(i[1])
|
|
47
|
+
elif not pairs:
|
|
48
|
+
res["Y"] = [y for y in y_temp if isinstance(y, int|float|complex|str)]
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
# by now we stablished args[0] wasn't a list|tuple, so it should
|
|
52
|
+
# make no sense to treat args as pairs, therefore I'll just grab
|
|
53
|
+
# whatever is args and not an iterable
|
|
54
|
+
res["Y"] = [y for y in all_args if isinstance(y, int|float|complex|str)]
|
|
55
|
+
res["residual_args"] = [r for r in all_args[1:] if not isinstance(r, int|float|complex|str)]
|
|
56
|
+
|
|
57
|
+
if not res.get("Y"):
|
|
58
|
+
return {"residual_args": all_args}
|
|
59
|
+
|
|
60
|
+
if len(res["Y"]) < 3:
|
|
61
|
+
raise ValueError("There should be more than two 'Y' entries.")
|
|
62
|
+
if len(all_args) > 1 and not res.get("residual_args"):
|
|
63
|
+
res["residual_args"] = all_args[1:]
|
|
64
|
+
|
|
65
|
+
# I shouldn't worry about building X from list(range(len(res["Y"]))) now, only after calling both methods on object creation
|
|
66
|
+
return res
|
|
67
|
+
|
|
68
|
+
def validate_kwargs(**kwargs) -> dict:
|
|
69
|
+
"""Validates kwargs and delivers them organized in a preconceived dict."""
|
|
70
|
+
res = {}
|
|
71
|
+
all_kwargs = dict(kwargs)
|
|
72
|
+
if not all_kwargs:
|
|
73
|
+
return res
|
|
74
|
+
|
|
75
|
+
if "Y" in all_kwargs:
|
|
76
|
+
y_temp = all_kwargs.get("Y") # it makes no sense to validate it to points since the
|
|
77
|
+
# argument came as "Y" inside a kwarg, so I won't.
|
|
78
|
+
|
|
79
|
+
if not isinstance(y_temp, list|tuple):
|
|
80
|
+
raise ValueError("If you're trying to send Y values via kwarg, the value should be a list, like so: 'Y': [1, 2, 3, 4, 5].")
|
|
81
|
+
|
|
82
|
+
if len(y_temp) > 2:
|
|
83
|
+
res["Y"] = [y for y in y_temp if isinstance(y, int|float|complex|str)]
|
|
84
|
+
|
|
85
|
+
res["residual_kwargs"] = {k:v for k, v in all_kwargs.items() if k != "Y" and k != "X"}
|
|
86
|
+
|
|
87
|
+
if "X" in all_kwargs:
|
|
88
|
+
x_temp = all_kwargs.get("X")
|
|
89
|
+
if isinstance(x_temp, list|tuple) and len(x_temp) > 2:
|
|
90
|
+
res["X"] = [x for x in x_temp if isinstance(x, int|float|complex|str)]
|
|
91
|
+
if "residual_kwargs" not in res:
|
|
92
|
+
res["residual_kwargs"] = {k: v for k, v in all_kwargs.items() if k != "Y" and k != "X"}
|
|
93
|
+
|
|
94
|
+
if not "Y" in res and not "X" in res:
|
|
95
|
+
if "points" in all_kwargs:
|
|
96
|
+
points = all_kwargs.get("points")
|
|
97
|
+
if not points:
|
|
98
|
+
raise ValueError("Points should have values into it.")
|
|
99
|
+
|
|
100
|
+
if "Y" in points:
|
|
101
|
+
y_temp = points.get("Y")
|
|
102
|
+
y_temp_temp = [y for y in y_temp if isinstance(y, int|float|complex|str)]
|
|
103
|
+
if not y_temp_temp or len(y_temp_temp) < 3:
|
|
104
|
+
raise ValueError("There should be more than two 'Y' entries.")
|
|
105
|
+
res["Y"] = y_temp_temp
|
|
106
|
+
|
|
107
|
+
if "Y" in res and len(res["Y"]) > 2 and "X" in points:
|
|
108
|
+
x_temp = points.get("X")
|
|
109
|
+
x_temp_temp = [x for x in x_temp if isinstance(x, int|float|complex|str)]
|
|
110
|
+
if not x_temp_temp or len(x_temp_temp) != len(res["Y"]):
|
|
111
|
+
raise ValueError("There should be the same amount of values in X than there are in Y.")
|
|
112
|
+
res["X"] = x_temp_temp
|
|
113
|
+
|
|
114
|
+
res["residual_kwargs"] = {k:v for k, v in all_kwargs.items() if k != "X" and k != "Y"}
|
|
115
|
+
|
|
116
|
+
if not "points" in all_kwargs and not "Y" in all_kwargs:
|
|
117
|
+
res["Y"] = []
|
|
118
|
+
res["X"] = []
|
|
119
|
+
for k, v in all_kwargs.items():
|
|
120
|
+
if isinstance(k, int|float|complex|str) and isinstance(v, int|float|complex|str):
|
|
121
|
+
res["X"].append(k)
|
|
122
|
+
res["Y"].append(v)
|
|
123
|
+
res["residual_kwargs"] = {k:v for k, v in all_kwargs.items() if not isinstance(k, int|float|complex|str) or not isinstance(v, int|float|complex|str)}
|
|
124
|
+
|
|
125
|
+
if not "Y" in res:
|
|
126
|
+
raise ValueError("No valid entry for Y values was detected in your keyword arguments.")
|
|
127
|
+
|
|
128
|
+
if len(res["Y"]) < 3:
|
|
129
|
+
raise ValueError("There should be more than two 'Y' entries.")
|
|
130
|
+
|
|
131
|
+
if not "X" in res and "X" in all_kwargs:
|
|
132
|
+
x_temp = all_kwargs.get("X")
|
|
133
|
+
if not isinstance(x_temp, list|tuple):
|
|
134
|
+
raise ValueError("X should be a list or a tuple.")
|
|
135
|
+
x_temp_temp = [x for x in x_temp if isinstance(x, int|float|complex|str)]
|
|
136
|
+
if len(x_temp_temp) != res["Y"]:
|
|
137
|
+
raise ValueError("There should be an equal ammount of itens in X and Y.")
|
|
138
|
+
|
|
139
|
+
return res
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import numpy.typing as npt
|
|
3
|
+
from math import comb
|
|
4
|
+
from operator import index
|
|
5
|
+
|
|
6
|
+
def total_window_size(k_values:list[int], n:int) -> int:
|
|
7
|
+
"""Calculates the window size for a list of k values. Mainly for array building purposes."""
|
|
8
|
+
if not k_values:
|
|
9
|
+
raise ValueError("k_values cannot be empty.")
|
|
10
|
+
return sum(comb(n, k) for k in k_values)
|
|
11
|
+
|
|
12
|
+
def validate_k(k: int, n: int) -> int:
|
|
13
|
+
"""Validates k, and normalizes it in cases like np.int64(3), against an n-observation matrix."""
|
|
14
|
+
if isinstance(k, bool):
|
|
15
|
+
raise TypeError(f"k must be an integer, not bool. Received {k}.")
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
k = index(k)
|
|
19
|
+
except TypeError as exc:
|
|
20
|
+
raise TypeError(f"k must be an integer. Received {k}.") from exc
|
|
21
|
+
|
|
22
|
+
if not 0 < k <= n:
|
|
23
|
+
raise ValueError(f"k must satisfy 0 < k <= n. Received k={k}, n={n}.")
|
|
24
|
+
|
|
25
|
+
return k
|
|
26
|
+
|
|
27
|
+
def normalize_k_values(k: int | list[int], n: int) -> list[int]:
|
|
28
|
+
"""Normalize k input into a sorted list of valid integer k values."""
|
|
29
|
+
if isinstance(k, list):
|
|
30
|
+
if not k:
|
|
31
|
+
raise ValueError("k cannot be an empty list.")
|
|
32
|
+
|
|
33
|
+
k_values = [validate_k(x, n) for x in k]
|
|
34
|
+
return sorted(set(k_values))
|
|
35
|
+
|
|
36
|
+
return [validate_k(k, n)]
|
|
37
|
+
|
|
38
|
+
def extract_k_window(
|
|
39
|
+
matrix: npt.NDArray[np.uint32],
|
|
40
|
+
k: int | list[int],
|
|
41
|
+
) -> npt.NDArray[np.uint32]:
|
|
42
|
+
"""
|
|
43
|
+
Extract one or more k-sized subset groups from a generated subset matrix.
|
|
44
|
+
|
|
45
|
+
If k is a list, groups are returned in ascending k order.
|
|
46
|
+
|
|
47
|
+
Offsets are computed from n and k alone, assuming `matrix` was built
|
|
48
|
+
with the canonical full k=1..n-1 sweep (e.g. engine.generateMatrix(n,
|
|
49
|
+
list(range(1, n)))). engine.generateMatrix's bare-call default no
|
|
50
|
+
longer produces that layout (it skips k=1 and includes k=n), so
|
|
51
|
+
passing a bare generateMatrix(n) result here silently extracts the
|
|
52
|
+
wrong rows instead of raising.
|
|
53
|
+
"""
|
|
54
|
+
if matrix.ndim != 2:
|
|
55
|
+
raise ValueError("matrix must be a 2D array filled with integers.")
|
|
56
|
+
|
|
57
|
+
n = matrix.shape[1]
|
|
58
|
+
k_values = normalize_k_values(k, n)
|
|
59
|
+
window_size = total_window_size(k_values, n)
|
|
60
|
+
base = np.empty((window_size, n), dtype=matrix.dtype)
|
|
61
|
+
current_row = 0
|
|
62
|
+
for i in k_values:
|
|
63
|
+
start = sum(comb(n, j) for j in range(1, i))
|
|
64
|
+
size = comb(n, i)
|
|
65
|
+
end = start + size
|
|
66
|
+
|
|
67
|
+
base[current_row: current_row + size] = matrix[start:end]
|
|
68
|
+
current_row += size
|
|
69
|
+
|
|
70
|
+
return base
|
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: subsetmatrix
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate, select, and materialize subset membership matrices from observation sets.
|
|
5
|
+
Author: Lucas Dornelles Cherobim
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/EngDornelles/subsetMatrix
|
|
8
|
+
Project-URL: Repository, https://github.com/EngDornelles/subsetMatrix
|
|
9
|
+
Project-URL: Issues, https://github.com/EngDornelles/subsetMatrix/issues
|
|
10
|
+
Keywords: subsets,combinations,matrix,numpy,combinatorics,dataset,mask
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy>=1.26
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# subsetMatrix
|
|
31
|
+
|
|
32
|
+
`subsetMatrix` is a small Python library for generating, selecting, and materializing subsets from an observation set.
|
|
33
|
+
|
|
34
|
+
It starts with a simple idea:
|
|
35
|
+
|
|
36
|
+
> Given `n` observations, generate a binary matrix where each row represents one subset.
|
|
37
|
+
|
|
38
|
+
Each column represents one observation.
|
|
39
|
+
Each row represents one subset.
|
|
40
|
+
A value of `1` means the observation belongs to that subset.
|
|
41
|
+
A value of `0` means it does not.
|
|
42
|
+
|
|
43
|
+
For `n = 3`, the generated matrix is:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
[[1 0 0]
|
|
47
|
+
[0 1 0]
|
|
48
|
+
[0 0 1]
|
|
49
|
+
[1 1 0]
|
|
50
|
+
[1 0 1]
|
|
51
|
+
[0 1 1]]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The empty subset `[0 0 0]` and the full subset `[1 1 1]` are excluded by default.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Why this exists
|
|
59
|
+
|
|
60
|
+
Many workflows need to explore combinations of observations, points, features, candidates, or records.
|
|
61
|
+
|
|
62
|
+
`subsetMatrix` provides a deterministic substrate for that kind of work.
|
|
63
|
+
|
|
64
|
+
It can be useful for:
|
|
65
|
+
|
|
66
|
+
* combinatorial analysis;
|
|
67
|
+
* subset generation;
|
|
68
|
+
* fixed-size subset selection;
|
|
69
|
+
* dataset slicing;
|
|
70
|
+
* candidate generation;
|
|
71
|
+
* research prototypes;
|
|
72
|
+
* model experimentation;
|
|
73
|
+
* matrix-based workflows;
|
|
74
|
+
* observation-subset analysis.
|
|
75
|
+
|
|
76
|
+
The library intentionally keeps interpretation out of the core.
|
|
77
|
+
|
|
78
|
+
It does not decide what a subset means.
|
|
79
|
+
It only helps generate, select, and materialize subsets.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Core behavior
|
|
84
|
+
|
|
85
|
+
For `n` observations, there are:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
2^n
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
possible subsets.
|
|
92
|
+
|
|
93
|
+
`subsetMatrix` excludes the empty and full subsets, so the generated matrix has:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
2^n - 2
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
rows.
|
|
100
|
+
|
|
101
|
+
The matrix shape is:
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
(2^n - 2, n)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Examples:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
n = 3 → 6 rows
|
|
111
|
+
n = 4 → 14 rows
|
|
112
|
+
n = 20 → 1,048,574 rows
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Rows are grouped by subset size `k`.
|
|
116
|
+
|
|
117
|
+
For `n = 4`, rows are ordered as:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
k = 1 → subsets with one active observation
|
|
121
|
+
k = 2 → subsets with two active observations
|
|
122
|
+
k = 3 → subsets with three active observations
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The groups `k = 0` and `k = n` are skipped.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Installation
|
|
130
|
+
|
|
131
|
+
Clone the repository:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git clone https://github.com/EngDornelles/subsetMatrix.git
|
|
135
|
+
cd subsetMatrix
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Create a virtual environment.
|
|
139
|
+
|
|
140
|
+
On Windows PowerShell:
|
|
141
|
+
|
|
142
|
+
```powershell
|
|
143
|
+
py -m venv .venv
|
|
144
|
+
.\.venv\Scripts\activate
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Install the package in editable mode:
|
|
148
|
+
|
|
149
|
+
```powershell
|
|
150
|
+
py -m pip install -e .
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Install test dependencies:
|
|
154
|
+
|
|
155
|
+
```powershell
|
|
156
|
+
py -m pip install pytest
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Run tests:
|
|
160
|
+
|
|
161
|
+
```powershell
|
|
162
|
+
py -m pytest -v
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Quick start
|
|
168
|
+
|
|
169
|
+
### Generate a subset matrix
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from subsetmatrix.engine import generateMatrix
|
|
173
|
+
|
|
174
|
+
matrix = generateMatrix(3)
|
|
175
|
+
|
|
176
|
+
print(matrix)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
By default (no `K` given), rows are grouped by k, skipping singletons
|
|
180
|
+
(k=1) and including the full set (k=n):
|
|
181
|
+
|
|
182
|
+
Output:
|
|
183
|
+
|
|
184
|
+
```text
|
|
185
|
+
[[1 1 0]
|
|
186
|
+
[1 0 1]
|
|
187
|
+
[0 1 1]
|
|
188
|
+
[1 1 1]]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
To include singletons or restrict to specific subset sizes, pass `K`
|
|
192
|
+
explicitly:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
matrix = generateMatrix(3, [1, 2, 3])
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## User-facing dataset workflow
|
|
201
|
+
|
|
202
|
+
The easiest way to use the library is through `ObservationSet`.
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from subsetmatrix.dataset_payload import ObservationSet
|
|
206
|
+
|
|
207
|
+
obs = ObservationSet(
|
|
208
|
+
{
|
|
209
|
+
"Y": [10, 20, 30, 40],
|
|
210
|
+
"X": ["A", "B", "C", "D"],
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
subsets = obs.get_subsets(2)
|
|
215
|
+
|
|
216
|
+
print(subsets)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Output:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
[
|
|
223
|
+
[["A", 10], ["B", 20]],
|
|
224
|
+
[["A", 10], ["C", 30]],
|
|
225
|
+
[["B", 20], ["C", 30]],
|
|
226
|
+
[["A", 10], ["D", 40]],
|
|
227
|
+
[["B", 20], ["D", 40]],
|
|
228
|
+
[["C", 30], ["D", 40]],
|
|
229
|
+
]
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`X` contains labels.
|
|
233
|
+
`Y` contains observations.
|
|
234
|
+
|
|
235
|
+
If `X` is not provided, labels are generated automatically.
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
obs = ObservationSet(
|
|
239
|
+
{
|
|
240
|
+
"Y": [10, 20, 30, 40],
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
print(obs.X)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Output:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
[1, 2, 3, 4]
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
By default, generated labels are one-based.
|
|
254
|
+
|
|
255
|
+
To use zero-based labels:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
obs = ObservationSet(
|
|
259
|
+
{
|
|
260
|
+
"Y": [10, 20, 30, 40],
|
|
261
|
+
},
|
|
262
|
+
indexing_as_one=False,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
print(obs.X)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Output:
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
[0, 1, 2, 3]
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Selecting subset windows by `k`
|
|
277
|
+
|
|
278
|
+
You can extract only the rows for a specific subset size.
|
|
279
|
+
|
|
280
|
+
`extract_k_window` computes row offsets assuming `matrix` was built
|
|
281
|
+
with the full k=1..n-1 sweep, so pass that explicit `K` to
|
|
282
|
+
`generateMatrix` — its own default (no `K`) skips k=1 and includes
|
|
283
|
+
k=n, which no longer matches those offsets. If you just want specific
|
|
284
|
+
k-sized subsets, prefer calling `generateMatrix(n, K)` directly (see
|
|
285
|
+
above) instead of going through `extract_k_window`.
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from subsetmatrix.engine import generateMatrix
|
|
289
|
+
from subsetmatrix.selecting_subsets import extract_k_window
|
|
290
|
+
|
|
291
|
+
matrix = generateMatrix(4, list(range(1, 4)))
|
|
292
|
+
|
|
293
|
+
k2_matrix = extract_k_window(matrix, 2)
|
|
294
|
+
|
|
295
|
+
print(k2_matrix)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Output:
|
|
299
|
+
|
|
300
|
+
```text
|
|
301
|
+
[[1 1 0 0]
|
|
302
|
+
[1 0 1 0]
|
|
303
|
+
[0 1 1 0]
|
|
304
|
+
[1 0 0 1]
|
|
305
|
+
[0 1 0 1]
|
|
306
|
+
[0 0 1 1]]
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
You can also extract multiple `k` groups:
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
selected = extract_k_window(matrix, [1, 3])
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The list is normalized, sorted, and deduplicated.
|
|
316
|
+
|
|
317
|
+
So this:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
extract_k_window(matrix, [3, 1, 1])
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
behaves like:
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
extract_k_window(matrix, [1, 3])
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Fixed-size mask generation
|
|
332
|
+
|
|
333
|
+
`subsetMatrix` uses integer masks internally to generate subset rows.
|
|
334
|
+
|
|
335
|
+
You can generate masks directly for a fixed subset size `k`:
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
from subsetmatrix.engine import iter_k_masks
|
|
339
|
+
|
|
340
|
+
for mask in iter_k_masks(n=4, k=2):
|
|
341
|
+
print(mask)
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Output:
|
|
345
|
+
|
|
346
|
+
```text
|
|
347
|
+
3
|
|
348
|
+
5
|
|
349
|
+
6
|
|
350
|
+
9
|
|
351
|
+
10
|
|
352
|
+
12
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Those masks correspond to:
|
|
356
|
+
|
|
357
|
+
```text
|
|
358
|
+
0011
|
|
359
|
+
0101
|
|
360
|
+
0110
|
|
361
|
+
1001
|
|
362
|
+
1010
|
|
363
|
+
1100
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Each mask has exactly two active bits.
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Cardinality
|
|
371
|
+
|
|
372
|
+
You can check how many active observations a mask contains:
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
from subsetmatrix.engine import cardinality
|
|
376
|
+
|
|
377
|
+
print(cardinality(5))
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Output:
|
|
381
|
+
|
|
382
|
+
```text
|
|
383
|
+
2
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Because:
|
|
387
|
+
|
|
388
|
+
```text
|
|
389
|
+
5 = 0101
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
has two active bits.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Current API
|
|
397
|
+
|
|
398
|
+
### `generateMatrix(n: int, K: list[int] = [])`
|
|
399
|
+
|
|
400
|
+
Generates the subset membership matrix for the requested subset sizes,
|
|
401
|
+
grouped by k. If `K` is omitted, defaults to `range(2, n + 1)` —
|
|
402
|
+
singletons (k=1) are skipped and the full set (k=n) is included.
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
from subsetmatrix.engine import generateMatrix
|
|
406
|
+
|
|
407
|
+
matrix = generateMatrix(4)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
For `n = 4`, the shape is:
|
|
411
|
+
|
|
412
|
+
```text
|
|
413
|
+
(11, 4)
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Pass `K` explicitly to select specific subset sizes:
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
matrix = generateMatrix(4, [2]) # only pairs
|
|
420
|
+
matrix = generateMatrix(4, [1, 2, 3]) # the pre-1.0 default: full k=1..n-1 sweep
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
### `iter_k_masks(n: int, k: int)`
|
|
426
|
+
|
|
427
|
+
Yields integer masks with exactly `k` active observations.
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
from subsetmatrix.engine import iter_k_masks
|
|
431
|
+
|
|
432
|
+
masks = list(iter_k_masks(4, 2))
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
### `cardinality(mask: int)`
|
|
438
|
+
|
|
439
|
+
Returns how many active bits exist in a mask.
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
from subsetmatrix.engine import cardinality
|
|
443
|
+
|
|
444
|
+
cardinality(12)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
### `extract_k_window(matrix, k)`
|
|
450
|
+
|
|
451
|
+
Extracts rows for one or more subset sizes.
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
from subsetmatrix.selecting_subsets import extract_k_window
|
|
455
|
+
|
|
456
|
+
k2 = extract_k_window(matrix, 2)
|
|
457
|
+
mixed = extract_k_window(matrix, [1, 3])
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
### `ObservationSet(points).get_subsets(k)`
|
|
463
|
+
|
|
464
|
+
Materializes actual dataset subsets.
|
|
465
|
+
|
|
466
|
+
```python
|
|
467
|
+
from subsetmatrix.dataset_payload import ObservationSet
|
|
468
|
+
|
|
469
|
+
obs = ObservationSet(
|
|
470
|
+
{
|
|
471
|
+
"Y": [10, 20, 30, 40],
|
|
472
|
+
"X": ["A", "B", "C", "D"],
|
|
473
|
+
}
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
obs.get_subsets(2)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Repository structure
|
|
482
|
+
|
|
483
|
+
```text
|
|
484
|
+
subsetMatrix/
|
|
485
|
+
├── LICENSE
|
|
486
|
+
├── README.md
|
|
487
|
+
├── pyproject.toml
|
|
488
|
+
├── src/
|
|
489
|
+
│ └── subsetmatrix/
|
|
490
|
+
│ ├── __init__.py
|
|
491
|
+
│ ├── engine.py
|
|
492
|
+
│ ├── selecting_subsets.py
|
|
493
|
+
│ └── dataset_payload.py
|
|
494
|
+
└── tests/
|
|
495
|
+
├── test_engine.py
|
|
496
|
+
├── test_selecting_subsets.py
|
|
497
|
+
└── test_dataset_payload.py
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## Design notes
|
|
503
|
+
|
|
504
|
+
### Matrix generation
|
|
505
|
+
|
|
506
|
+
The generated matrix is a binary membership matrix.
|
|
507
|
+
|
|
508
|
+
Each row is a subset.
|
|
509
|
+
Each column is an observation.
|
|
510
|
+
|
|
511
|
+
Example:
|
|
512
|
+
|
|
513
|
+
```text
|
|
514
|
+
[1 0 1 0]
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
means:
|
|
518
|
+
|
|
519
|
+
```text
|
|
520
|
+
include observation 0
|
|
521
|
+
exclude observation 1
|
|
522
|
+
include observation 2
|
|
523
|
+
exclude observation 3
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
### Cardinality grouping
|
|
529
|
+
|
|
530
|
+
Rows are grouped by subset size `k`.
|
|
531
|
+
|
|
532
|
+
This makes it possible to extract all subsets of a specific size without scanning the whole matrix.
|
|
533
|
+
|
|
534
|
+
For example, if you only need subsets with `k = 3`, you can extract only that window.
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
### Empty and full subsets
|
|
539
|
+
|
|
540
|
+
The empty subset and full subset are excluded.
|
|
541
|
+
|
|
542
|
+
They are usually not useful for workflows where subsets are being compared, sampled, scored, or transformed.
|
|
543
|
+
|
|
544
|
+
Excluded rows:
|
|
545
|
+
|
|
546
|
+
```text
|
|
547
|
+
[0 0 0 ... 0]
|
|
548
|
+
[1 1 1 ... 1]
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
### Dense matrix warning
|
|
554
|
+
|
|
555
|
+
The full dense matrix grows quickly.
|
|
556
|
+
|
|
557
|
+
```text
|
|
558
|
+
n = 20 → 1,048,574 rows
|
|
559
|
+
n = 26 → 67,108,862 rows
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
Future versions may add:
|
|
563
|
+
|
|
564
|
+
* mask-only output;
|
|
565
|
+
* chunked generation;
|
|
566
|
+
* memory estimation;
|
|
567
|
+
* packed storage;
|
|
568
|
+
* optional export formats;
|
|
569
|
+
* lazy payload materialization.
|
|
570
|
+
|
|
571
|
+
The current version prioritizes clarity and deterministic behavior.
|
|
572
|
+
|
|
573
|
+
---
|
|
574
|
+
|
|
575
|
+
## Testing
|
|
576
|
+
|
|
577
|
+
Run:
|
|
578
|
+
|
|
579
|
+
```powershell
|
|
580
|
+
py -m pytest -v
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Current test coverage validates:
|
|
584
|
+
|
|
585
|
+
* matrix shape;
|
|
586
|
+
* exact output for `n = 3`;
|
|
587
|
+
* cardinality grouping;
|
|
588
|
+
* exclusion of empty and full rows;
|
|
589
|
+
* invalid `n`;
|
|
590
|
+
* k-window extraction;
|
|
591
|
+
* sorted and deduplicated `k` lists;
|
|
592
|
+
* rejection of invalid `k`;
|
|
593
|
+
* NumPy integer support;
|
|
594
|
+
* dataset payload materialization;
|
|
595
|
+
* default generated labels;
|
|
596
|
+
* custom labels;
|
|
597
|
+
* invalid input handling.
|
|
598
|
+
|
|
599
|
+
Example current test result:
|
|
600
|
+
|
|
601
|
+
```text
|
|
602
|
+
18 passed
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## Development status
|
|
608
|
+
|
|
609
|
+
`subsetMatrix` is in early development.
|
|
610
|
+
|
|
611
|
+
Current stable layers:
|
|
612
|
+
|
|
613
|
+
```text
|
|
614
|
+
engine.py
|
|
615
|
+
→ generate subset matrix
|
|
616
|
+
|
|
617
|
+
selecting_subsets.py
|
|
618
|
+
→ extract k-window slices
|
|
619
|
+
|
|
620
|
+
dataset_payload.py
|
|
621
|
+
→ materialize dataset subsets
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Planned improvements may include:
|
|
625
|
+
|
|
626
|
+
* snake_case aliases;
|
|
627
|
+
* chunked matrix generation;
|
|
628
|
+
* mask-first public workflows;
|
|
629
|
+
* memory estimation helpers;
|
|
630
|
+
* optional pandas helpers;
|
|
631
|
+
* optional export utilities;
|
|
632
|
+
* expanded documentation;
|
|
633
|
+
* performance benchmarks.
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## Naming
|
|
638
|
+
|
|
639
|
+
The GitHub repository is named:
|
|
640
|
+
|
|
641
|
+
```text
|
|
642
|
+
subsetMatrix
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
The Python package is imported as:
|
|
646
|
+
|
|
647
|
+
```python
|
|
648
|
+
import subsetmatrix
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
This follows Python package naming conventions while preserving the repository’s public name.
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## License
|
|
656
|
+
|
|
657
|
+
This project is licensed under the MIT License.
|
|
658
|
+
|
|
659
|
+
See:
|
|
660
|
+
|
|
661
|
+
```text
|
|
662
|
+
LICENSE
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
667
|
+
## Author
|
|
668
|
+
|
|
669
|
+
Created by Lucas Dornelles Cherobim.
|
|
670
|
+
|
|
671
|
+
GitHub: [EngDornelles](https://github.com/EngDornelles)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
subsetmatrix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
subsetmatrix/dataset_payload.py,sha256=oTMZf-0-bmHrSQl5iYmhRJlLbWSWI00XqDc_IWroUok,3299
|
|
3
|
+
subsetmatrix/engine.py,sha256=6j3P13R0gqWUoPVMgoBOcY05913vc0xyHy18p53-Ygg,2155
|
|
4
|
+
subsetmatrix/payload_args_validation.py,sha256=PEnVcHsMfYx4sBHZGkey4eh4CoLpzfLPSzw1Lm0rMzI,6489
|
|
5
|
+
subsetmatrix/selecting_subsets.py,sha256=PGbx9_IdT4-C7zAOMlAJHSXp_rukHeos_r9adTZF_sU,2421
|
|
6
|
+
subsetmatrix-0.1.0.dist-info/licenses/LICENSE,sha256=OBAWiIKPH-1aydk7x5_lVYVqJh8dFJZp_iH4uv9F4f8,1081
|
|
7
|
+
subsetmatrix-0.1.0.dist-info/METADATA,sha256=wOchTwwvvl-bT-TE1KZISAX6ZNGJc5wnBb3VKL5BsGg,11261
|
|
8
|
+
subsetmatrix-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
9
|
+
subsetmatrix-0.1.0.dist-info/top_level.txt,sha256=Q-tNlDpdWaJvJ64Of70xUTDDqDyVk9BwXVyqfCv7ByA,13
|
|
10
|
+
subsetmatrix-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucas Dornelles Cherobim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
subsetmatrix
|