compehndly 0.0.1a1__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.
compehndly/__init__.py ADDED
@@ -0,0 +1,70 @@
1
+ import functools
2
+
3
+ from compehndly.core.registry import FunctionRegistry
4
+ from compehndly.adapters import register_all_adapters
5
+
6
+ register_all_adapters()
7
+
8
+ # Internal override hook
9
+ _REGISTRY_BUILDER = None
10
+
11
+
12
+ def _set_registry_builder(builder):
13
+ """
14
+ Test helper: override how the registry is built.
15
+ `builder` must be a callable returning a FunctionRegistry instance.
16
+ """
17
+ global _REGISTRY_BUILDER
18
+ _REGISTRY_BUILDER = builder
19
+ _REGISTRY.cache_clear() # ensures next call uses new builder
20
+
21
+
22
+ @functools.lru_cache
23
+ def _REGISTRY():
24
+ """
25
+ Lazily build the registry, using the override when present.
26
+ """
27
+ if _REGISTRY_BUILDER is not None:
28
+ return _REGISTRY_BUILDER()
29
+
30
+ return FunctionRegistry.build_registry()
31
+
32
+
33
+ class _FunctionAccessor:
34
+ """
35
+ Returned from compehndly.<func_name>
36
+ and provides:
37
+ compehndly.add_one(...)
38
+ compehndly.add_one["0.0.1"](...)
39
+ """
40
+
41
+ def __init__(self, name: str):
42
+ self._name = name
43
+
44
+ def __call__(self, *args, **kwargs):
45
+ """Invoke latest version."""
46
+ registry = _REGISTRY()
47
+ fn = registry.get(self._name, version=None)
48
+ return fn(*args, **kwargs)
49
+
50
+ def __getitem__(self, version: str | None):
51
+ """Invoke specific version (or None for latest)."""
52
+ registry = _REGISTRY()
53
+ fn = registry.get(self._name, version)
54
+ return fn
55
+
56
+
57
+ def __getattr__(name: str):
58
+ """
59
+ Dynamically expose registered functions as attributes:
60
+ compehndly.add_one
61
+ """
62
+ registry = _REGISTRY()
63
+
64
+ if name in registry._functions:
65
+ return _FunctionAccessor(name)
66
+
67
+ raise AttributeError(f"'compehndly' has no function '{name}'")
68
+
69
+
70
+ __all__ = ["FunctionRegistry"]
@@ -0,0 +1,25 @@
1
+ import logging
2
+
3
+ from importlib.metadata import entry_points
4
+ from compehndly.adapters.base import ArrayAdapter
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ _ADAPTERS = {}
9
+
10
+
11
+ def register_adapter(adapter: ArrayAdapter):
12
+ _ADAPTERS[adapter.name] = adapter
13
+
14
+
15
+ def register_all_adapters():
16
+ logger.debug("Registering adapters")
17
+ for ep in entry_points(group="compehndly.adapters"):
18
+ try:
19
+ logger.debug(f"loading {ep}")
20
+ AdapterClass = ep.load()
21
+ register_adapter(AdapterClass())
22
+ except ImportError as e:
23
+ logger.warning(f"Failed to load {ep.name}: {e}")
24
+
25
+ register_adapter(ArrayAdapter())
@@ -0,0 +1,26 @@
1
+ import logging
2
+ import numbers
3
+ import pyarrow as pa
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ class ArrayAdapter:
9
+ name: str = "base"
10
+
11
+ def _to_arrow(self, obj):
12
+ return pa.array(obj)
13
+
14
+ def to_arrow(self, obj) -> pa.Array:
15
+ if isinstance(obj, (pa.Array, pa.Scalar)):
16
+ return obj
17
+
18
+ # Python scalar → pass through unchanged
19
+ elif isinstance(obj, (numbers.Number, bool, str)):
20
+ return obj
21
+
22
+ # Python list/iterable → fallback to Arrow
23
+ return self._to_arrow(obj)
24
+
25
+ def from_arrow(self, obj):
26
+ return obj
@@ -0,0 +1,20 @@
1
+ import logging
2
+ import numpy as np
3
+ import pyarrow as pa
4
+
5
+ from compehndly.adapters.base import ArrayAdapter
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class NumpyAdapter(ArrayAdapter):
11
+ name = "numpy"
12
+
13
+ def matches(self, obj):
14
+ return isinstance(obj, np.ndarray) and obj.ndim == 1
15
+
16
+ def _to_arrow(self, obj):
17
+ return pa.array(obj) # zero copy for many numeric dtypes
18
+
19
+ def from_arrow(self, arrow_obj):
20
+ return arrow_obj.to_numpy() # zero-copy where possible
@@ -0,0 +1,20 @@
1
+ import logging
2
+ import pyarrow as pa
3
+ import pandas as pd
4
+
5
+ from compehndly.adapters.base import ArrayAdapter
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class PandasAdapter(ArrayAdapter):
11
+ name = "pandas"
12
+
13
+ def matches(self, obj):
14
+ return isinstance(obj, pd.Series)
15
+
16
+ def _to_arrow(self, obj):
17
+ return pa.array(obj) # may be zero-copy if ArrowExtensionArray
18
+
19
+ def from_arrow(self, arrow_obj):
20
+ return pd.Series(arrow_obj)
@@ -0,0 +1,22 @@
1
+ import logging
2
+ import polars as pl
3
+ import pyarrow as pa
4
+
5
+ from compehndly.adapters.base import ArrayAdapter
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class PolarsAdapter(ArrayAdapter):
11
+ name = "polars"
12
+
13
+ def matches(self, obj):
14
+ return isinstance(obj, pl.Series)
15
+
16
+ def _to_arrow(self, obj):
17
+ if isinstance(obj, pl.Series):
18
+ return obj.to_arrow()
19
+ return pa.array(obj)
20
+
21
+ def from_arrow(self, arrow_obj):
22
+ return pl.from_arrow(arrow_obj)
@@ -0,0 +1,3 @@
1
+ from compehndly.core.registry import FunctionRegistry
2
+
3
+ __all__ = ["FunctionRegistry"]
@@ -0,0 +1,14 @@
1
+ def arrowize_arguments(func, adapter):
2
+ def wrapper(*args, **kwargs):
3
+ # Convert args → arrow
4
+ arr_args = [adapter.to_arrow(a) for a in args]
5
+
6
+ # Convert kwargs → arrow (only values!)
7
+ arr_kwargs = {k: adapter.to_arrow(v) for k, v in kwargs.items()}
8
+
9
+ result = func(*arr_args, **arr_kwargs)
10
+
11
+ # Convert result → library-specific type
12
+ return adapter.from_arrow(result)
13
+
14
+ return wrapper
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class FunctionMetadata:
6
+ id: str
7
+ name: str
8
+ description: str
9
+ authors: list[str]
@@ -0,0 +1,68 @@
1
+ import importlib
2
+ import logging
3
+
4
+ from collections import defaultdict
5
+ from packaging.version import Version
6
+
7
+ from compehndly.core.conversion import arrowize_arguments
8
+ from compehndly.adapters import _ADAPTERS
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ TO_REGISTER = ["compehndly.utils.example_function"]
13
+
14
+
15
+ class FunctionRegistry:
16
+ def __init__(self, adapter: str | None = None):
17
+ self._functions = defaultdict(dict)
18
+ logging.debug("Running function registry")
19
+ if adapter is None:
20
+ self.adapter = _ADAPTERS["base"]
21
+ else:
22
+ if adapter not in _ADAPTERS:
23
+ raise ValueError(f"Unknown adapter '{adapter}'. " f"Available: {', '.join(_ADAPTERS)}")
24
+ self.adapter = _ADAPTERS[adapter]
25
+
26
+ def register(self, name, version, func):
27
+ version = Version(version)
28
+ if version in self._functions[name]:
29
+ raise ValueError(f"Function {name} version {version} already registered.")
30
+
31
+ wrapped_func = arrowize_arguments(func, adapter=self.adapter)
32
+ self._functions[name][version] = wrapped_func
33
+
34
+ def get(self, name, version=None):
35
+ """Return the function. If version is None, return latest."""
36
+ if name not in self._functions:
37
+ raise KeyError(f"No function registered with name '{name}'")
38
+ versions = sorted(self._functions[name].keys())
39
+ if version is None:
40
+ version = versions[-1]
41
+ else:
42
+ version = Version(version)
43
+ return self._functions[name][version]
44
+
45
+ def list_versions(self, name):
46
+ if name not in self._functions:
47
+ return []
48
+ return sorted(str(v) for v in self._functions[name].keys())
49
+
50
+ @classmethod
51
+ def build_registry(cls, _to_register=TO_REGISTER, adapter: str | None = None):
52
+ registry = cls(adapter=adapter)
53
+
54
+ for module_path in _to_register:
55
+ try:
56
+ module = importlib.import_module(module_path)
57
+ except ImportError as e:
58
+ raise ImportError(f"Failed to import module '{module_path}': {e}")
59
+
60
+ if not hasattr(module, "__registrations__") or not isinstance(module.__registrations__, list):
61
+ continue
62
+
63
+ for registry_name, func_name, version, func in module.__registrations__:
64
+ if registry_name != "default":
65
+ raise ValueError(f"Unsupported registry name '{registry_name}' in module '{module_path}'")
66
+ registry.register(func_name, version, func)
67
+
68
+ return registry
@@ -0,0 +1 @@
1
+ # TODO: allow support for multiple registries
File without changes
@@ -0,0 +1,96 @@
1
+ import pyarrow as pa
2
+ import pyarrow.compute as pc
3
+
4
+ __registrations__ = []
5
+
6
+
7
+ # TODO: move decorator for joint use
8
+ def register(registry_name, name, version):
9
+ def decorator(func):
10
+ __registrations__.append((registry_name, name, version, func))
11
+ return func
12
+
13
+ return decorator
14
+
15
+
16
+ # ---------------- STANDARDIZE ----------------------
17
+
18
+
19
+ def _standardize_v0_0_1_reference(
20
+ measured: float,
21
+ standard: float,
22
+ ) -> float:
23
+ return 100 * measured / standard
24
+
25
+
26
+ @register(registry_name="default", name="standardize", version="0.0.1")
27
+ def _standardize_v0_0_1_arrow(measured: pa.Array, standard: pa.Array) -> pa.Array:
28
+ return pc.divide(pc.multiply(measured, 100), standard)
29
+
30
+
31
+ # ---------------- URINARY BIOMARKERS ----------------------
32
+
33
+
34
+ def _standardize_creatinine_v0_0_1_reference(
35
+ measured: float,
36
+ crt: float,
37
+ ) -> float:
38
+ "measured in micrograms/L, crt in mg/dL"
39
+ return _standardize_v0_0_1_reference(measured, crt)
40
+
41
+
42
+ @register(registry_name="default", name="standardize_creatinine", version="0.0.1")
43
+ def _standardize_creatinine_v0_0_1_arrow(measured: pa.Array, crt: pa.Array) -> pa.Array:
44
+ return _standardize_v0_0_1_arrow(measured, crt)
45
+
46
+
47
+ def _normalize_specific_gravity_v0_0_1_reference(
48
+ measured: float,
49
+ sg_measured: float,
50
+ sg_ref: float,
51
+ ) -> float:
52
+ ret = measured * (sg_ref - 1) / sg_measured
53
+
54
+ return ret
55
+
56
+
57
+ @register(registry_name="default", name="normalize_specific_gravity", version="0.0.1")
58
+ def _normalize_specific_gravity_v0_0_1_arrow(measured: pa.Array, sg_measured: pa.Array, sg_ref: float) -> pa.Array:
59
+ # Compute (sg_ref - 1) as a scalar
60
+ sg_factor = pa.scalar(sg_ref - 1, type=pa.float64())
61
+
62
+ # measured * (sg_ref - 1)
63
+ numerator = pc.multiply(measured, sg_factor)
64
+
65
+ # (measured * (sg_ref - 1)) / sg_measured
66
+ ret = pc.divide(numerator, sg_measured)
67
+
68
+ return ret
69
+
70
+
71
+ # ---------------- LIPID SOLUBLE BIOMARKERS ----------------------
72
+
73
+
74
+ def _total_lipid_concentration_v0_0_1_reference(chol: float, trigl: float) -> float:
75
+ """The total blood lipid content was calculated using the formula proposed by
76
+ (Bernert et al., 2007; Phillips et al., 1989) as advised by the analytical experts of HBM4EU.
77
+ Total lipids (mg/dL) = 2.27 * cholesterol (mg/dL) + triglycerides (mg/dL) + 62.3
78
+ """
79
+ return 2.27 * chol + trigl + 62.3
80
+
81
+
82
+ @register(registry_name="default", name="total_lipid_concentration", version="0.0.1")
83
+ def _total_lipid_concentration_v0_0_1_arrow(chol: pa.Array, trigl: pa.Array) -> pa.Array:
84
+ return pc.add(pc.multiply(chol, 2.27), pc.add(trigl, 62.3))
85
+
86
+
87
+ def _standardize_lipid_v0_0_1_reference(
88
+ measured: float,
89
+ lipid_value: float,
90
+ ) -> float:
91
+ return _standardize_v0_0_1_reference(measured, lipid_value)
92
+
93
+
94
+ @register(registry_name="default", name="normalize_specific_gravity", version="0.0.1")
95
+ def _standardize_lipid_v0_0_1_arrow(measured: pa.Array, lipid_value: pa.Array) -> pa.Array:
96
+ return _standardize_v0_0_1_arrow(measured, lipid_value)
@@ -0,0 +1,195 @@
1
+ import numpy as np
2
+ import pyarrow as pa
3
+ import pyarrow.compute as pc
4
+
5
+ from compehndly.derived_variables.statsutils import fit_censored_lognorm
6
+
7
+ __registrations__ = []
8
+
9
+
10
+ # TODO: move decorator for joint use
11
+ def register(registry_name, name, version):
12
+ def decorator(func):
13
+ __registrations__.append((registry_name, name, version, func))
14
+ return func
15
+
16
+ return decorator
17
+
18
+
19
+ def _medium_bound_imputation_v0_0_1_reference(
20
+ measurement: float,
21
+ loq: float,
22
+ lod: float | None = None,
23
+ ) -> float:
24
+ if lod is not None:
25
+ assert lod > 0.0
26
+ assert loq > 0.0
27
+ ret = measurement
28
+
29
+ if lod is None:
30
+ if measurement < loq:
31
+ ret = loq / 2
32
+ else:
33
+ assert lod < loq
34
+ if measurement < lod:
35
+ ret = lod / 2
36
+ elif measurement < loq:
37
+ ret = (loq + lod) / 2
38
+
39
+ return ret
40
+
41
+
42
+ @register(registry_name="default", name="medium_bound_imputation", version="0.0.1")
43
+ def _medium_bound_imputation_v0_0_1_arrow(
44
+ measurement: pa.Array,
45
+ loq: float,
46
+ lod: float | None = None,
47
+ ) -> pa.Array:
48
+ """
49
+ Vectorized medium-bound imputation.
50
+
51
+ """
52
+
53
+ if loq <= 0:
54
+ raise ValueError("loq must be > 0")
55
+
56
+ if lod is not None:
57
+ if lod <= 0:
58
+ raise ValueError("lod must be > 0")
59
+ if lod >= loq:
60
+ raise ValueError("lod must be < loq")
61
+
62
+ # Start with identity (original measurement)
63
+ result = measurement
64
+
65
+ if lod is None:
66
+ # measurement < loq → loq / 2
67
+ mask = pc.less(measurement, loq)
68
+ result = pc.if_else(mask, loq / 2, result)
69
+
70
+ else:
71
+ # measurement < lod → lod / 2
72
+ mask_below_lod = pc.less(measurement, lod)
73
+ result = pc.if_else(mask_below_lod, lod / 2, result)
74
+
75
+ # lod <= measurement < loq → (lod + loq) / 2
76
+ mask_between = pc.and_(
77
+ pc.greater_equal(measurement, lod),
78
+ pc.less(measurement, loq),
79
+ )
80
+ result = pc.if_else(mask_between, (lod + loq) / 2, result)
81
+
82
+ return result
83
+
84
+
85
+ @register(registry_name="default", name="medium_bound_imputation_array", version="0.0.1")
86
+ def _medium_bound_imputation_v0_0_1_arrow_array(
87
+ measurement: pa.Array,
88
+ loq: pa.Array,
89
+ lod: pa.Array | None = None,
90
+ ) -> pa.Array:
91
+ """
92
+ Vectorized medium-bound imputation with row-wise LOQ / LOD arrays.
93
+ """
94
+
95
+ length = len(measurement)
96
+
97
+ if len(loq) != length:
98
+ raise ValueError("measurement and loq must have the same length")
99
+
100
+ if lod is not None and len(lod) != length:
101
+ raise ValueError("measurement and lod must have the same length")
102
+
103
+ result = measurement
104
+
105
+ if lod is None:
106
+ # measurement < loq → loq / 2
107
+ mask = pc.less(measurement, loq)
108
+ result = pc.if_else(mask, pc.divide(loq, 2.0), result)
109
+
110
+ else:
111
+ # measurement < lod → lod / 2
112
+ mask_below_lod = pc.less(measurement, lod)
113
+ result = pc.if_else(mask_below_lod, pc.divide(lod, 2.0), result)
114
+
115
+ # lod <= measurement < loq → (lod + loq) / 2
116
+ mask_between = pc.and_(
117
+ pc.greater_equal(measurement, lod),
118
+ pc.less(measurement, loq),
119
+ )
120
+ midpoint = pc.divide(pc.add(lod, loq), 2.0)
121
+ result = pc.if_else(mask_between, midpoint, result)
122
+
123
+ return result
124
+
125
+
126
+ def _random_single_imputation_reference_v0_0_1(
127
+ measurement: float,
128
+ loq: float,
129
+ lod: float | None = None,
130
+ ) -> float:
131
+ # TODO:
132
+ pass
133
+
134
+
135
+ @register(registry_name="default", name="random_single_imputation", version="0.0.1")
136
+ def _random_single_imputation_arrow_v0_0_1(
137
+ biomarker_pa: pa.Array,
138
+ lod: float,
139
+ loq: float,
140
+ seed: int | None = None,
141
+ ) -> pa.Array:
142
+ """
143
+ Perform random single imputation for left-censored lognormal data
144
+ using PyArrow arrays for maximum compatibility.
145
+
146
+ biomarker_pa : arrow array of floats or censored indicators (-1, -2, -3)
147
+ lod : limit of detection
148
+ loq : limit of quantification
149
+ """
150
+
151
+ # Convert Arrow → NumPy
152
+ # NOTE: suboptimal solution
153
+ biomarker = biomarker_pa.to_numpy(zero_copy_only=False)
154
+
155
+ # Fill NA as -1 (your original convention)
156
+ is_na = np.isnan(biomarker)
157
+ biomarker_filled = np.where(is_na, -1, biomarker)
158
+
159
+ # Censored if negative category code
160
+ censored = biomarker_filled < 0
161
+ values_np = np.where(censored, lod, biomarker_filled)
162
+ dist = fit_censored_lognorm(values_np, censored)
163
+ rng = np.random.default_rng(seed=seed)
164
+
165
+ # Compute sampling bounds (vectorized)
166
+ lower = np.zeros_like(biomarker_filled, dtype=float)
167
+ upper = np.zeros_like(biomarker_filled, dtype=float)
168
+
169
+ cat_below_lod = biomarker_filled == -1
170
+ cat_between = biomarker_filled == -2
171
+ cat_below_loq = biomarker_filled == -3
172
+
173
+ # <LOD -> [0, LOD]
174
+ lower[cat_below_lod] = 0
175
+ upper[cat_below_lod] = lod
176
+ # Between LOD & LOQ -> [LOD, LOQ]
177
+ lower[cat_between] = lod
178
+ upper[cat_between] = loq
179
+
180
+ # <LOQ -> [0, LOQ]
181
+ lower[cat_below_loq] = 0
182
+ upper[cat_below_loq] = loq
183
+
184
+ # Convert bounds to CDF space
185
+ cdf_lo = dist.cdf(lower)
186
+ cdf_hi = dist.cdf(upper)
187
+
188
+ # generate U ~ Uniform(cdf_lo, cdf_hi)
189
+ u = rng.uniform(cdf_lo, cdf_hi)
190
+ imputed = dist.ppf(u)
191
+ # replace censored with imputed
192
+ result = biomarker.copy()
193
+ result[censored] = imputed[censored]
194
+
195
+ return pa.array(result)
@@ -0,0 +1,50 @@
1
+ import numpy as np
2
+
3
+ from scipy.stats import lognorm
4
+ from scipy.optimize import minimize
5
+
6
+
7
+ def fit_censored_lognorm(values_np, censored_np):
8
+ vals = values_np
9
+ cens = censored_np
10
+
11
+ # Require at least some uncensored observations
12
+ if (~cens).sum() == 0:
13
+ raise RuntimeError("Cannot fit lognormal: all observations are censored.")
14
+
15
+ def nll(params):
16
+ sigma, mu = params
17
+ if sigma <= 0:
18
+ return np.inf
19
+
20
+ dist = lognorm(s=sigma, scale=np.exp(mu))
21
+ ll_unc = dist.logpdf(vals[~cens]).sum()
22
+ ll_cens = np.log(dist.cdf(vals[cens])).sum()
23
+ ll = ll_unc + ll_cens
24
+
25
+ # Regularization to avoid pathological sigma
26
+ penalty = 0
27
+ if sigma < 0.05:
28
+ penalty += 1e3 * (0.05 - sigma) ** 2
29
+ if sigma > 5.0:
30
+ penalty += 1e3 * (sigma - 5.0) ** 2
31
+
32
+ return -(ll - penalty)
33
+
34
+ unc = vals[~cens]
35
+ mu0 = np.log(np.median(unc))
36
+ sigma0 = np.std(np.log(unc))
37
+ sigma0 = sigma0 if sigma0 > 0.1 else 0.5 # stability
38
+
39
+ res = minimize(
40
+ nll,
41
+ x0=[sigma0, mu0],
42
+ method="L-BFGS-B",
43
+ bounds=[(1e-6, None), (None, None)],
44
+ )
45
+
46
+ if not res.success:
47
+ raise RuntimeError(f"Censored MLE did not converge: {res.message}")
48
+
49
+ sigma_hat, mu_hat = res.x
50
+ return lognorm(s=sigma_hat, scale=np.exp(mu_hat))
@@ -0,0 +1,63 @@
1
+ import pyarrow as pa
2
+ import pyarrow.compute as pc
3
+
4
+
5
+ __registrations__ = []
6
+
7
+
8
+ # TODO: move decorator for joint use
9
+ def register(registry_name, name, version):
10
+ def decorator(func):
11
+ __registrations__.append((registry_name, name, version, func))
12
+ return func
13
+
14
+ return decorator
15
+
16
+
17
+ def _summation_v0_0_1_reference(
18
+ *arrays: float,
19
+ all_required=True,
20
+ ) -> float | None:
21
+ if not arrays:
22
+ raise ValueError("At least one input array is required")
23
+
24
+ if all_required:
25
+ if not any(x is not None for x in arrays):
26
+ return None
27
+
28
+ ret = 0
29
+ for val in arrays:
30
+ if val is not None:
31
+ ret += val
32
+
33
+ return ret
34
+
35
+
36
+ @register(registry_name="default", name="summation", version="0.0.1")
37
+ def _summation_v0_0_1_arrow(*arrays: pa.Array, all_required=True) -> pa.Array:
38
+ """
39
+ Vectorized summation over multiple Arrow arrays.
40
+
41
+ Semantics:
42
+ - If ANY input array is entirely null, return an all-null array.
43
+ - Otherwise, nulls are treated as zero during summation.
44
+ """
45
+
46
+ if not arrays:
47
+ raise ValueError("At least one input array is required")
48
+
49
+ length = len(arrays[0])
50
+
51
+ for arr in arrays:
52
+ if len(arr) != length:
53
+ raise ValueError("All input arrays must have the same length")
54
+
55
+ if arr.null_count == length and all_required:
56
+ return pa.nulls(length, type=pa.float64())
57
+
58
+ result = pc.fill_null(arrays[0], 0)
59
+
60
+ for arr in arrays[1:]:
61
+ result = pc.add(result, pc.fill_null(arr, 0))
62
+
63
+ return result
compehndly/py.typed ADDED
File without changes
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ import pyarrow as pa
2
+ import pyarrow.compute as pc
3
+
4
+ __registrations__ = []
5
+
6
+
7
+ # TODO: move decorator for joint use
8
+ def register(registry_name, name, version):
9
+ def decorator(func):
10
+ __registrations__.append((registry_name, name, version, func))
11
+ return func
12
+
13
+ return decorator
14
+
15
+
16
+ def _simple_bin_v0_0_1_reference(
17
+ *arrays: float,
18
+ ) -> float:
19
+ pass
20
+
21
+
22
+ @register(registry_name="default", name="summation", version="0.0.1")
23
+ def _simple_bin_v0_0_1_arrow(
24
+ array: pa.Array,
25
+ ) -> pa.Array:
26
+ pass
@@ -0,0 +1,16 @@
1
+ import pyarrow as pa
2
+ import pyarrow.compute as pc
3
+
4
+ __registrations__ = []
5
+
6
+
7
+ # TODO: move decorator for joint use
8
+ def register(registry_name, name, version):
9
+ def decorator(func):
10
+ __registrations__.append((registry_name, name, version, func))
11
+ return func
12
+
13
+ return decorator
14
+
15
+
16
+ # TODO: add conditional helper functions
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: compehndly
3
+ Version: 0.0.1a1
4
+ Summary: Personal Exposure Health Registry
5
+ Author-email: Gertjan Bisschop <gertjan.bisschop@vito.be>, Dirk Devriendt <dirk.devriendt@vito.be>, Caio Mescouto <caio.mescouto@vito.be>
6
+ Requires-Python: <4.0.0,>=3.10
7
+ Requires-Dist: pyarrow<23.0,>=22.0
8
+ Requires-Dist: scipy
9
+ Provides-Extra: numpy
10
+ Requires-Dist: numpy>=2.0; extra == 'numpy'
11
+ Provides-Extra: pandas
12
+ Requires-Dist: pandas<3.0,>=2.0; extra == 'pandas'
13
+ Provides-Extra: polars
14
+ Requires-Dist: polars<2.0,>=1.0; extra == 'polars'
@@ -0,0 +1,25 @@
1
+ compehndly/__init__.py,sha256=3j2PyfmtBi9GAErvWyKBrpDTgGNuInp-5gJ015ufy-Q,1722
2
+ compehndly/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ compehndly/adapters/__init__.py,sha256=OcYxz1SsecEodEOE9PnpswocEFXEStmqoqoSbsKFs4E,647
4
+ compehndly/adapters/base.py,sha256=YVDILO7ipgBXNY6lVjfAMjRES4H6PWPCe7VclK25GsQ,584
5
+ compehndly/adapters/numpy_adapter.py,sha256=Tp1IlvyzxyKK14bpj6Tzr5pfRDIKkoJuGUUbZUQj8xk,488
6
+ compehndly/adapters/pandas_adapter.py,sha256=BoOW52e-vTKg5vTkkj77NOGu4K1CsVmB1i26JTUNyRU,450
7
+ compehndly/adapters/polars_adapter.py,sha256=H8WwG9s0fS2VN3Y7-FoZiuTreY-G_j1dOVylVuoze-Q,484
8
+ compehndly/core/__init__.py,sha256=Phvn2ADN29CJtGXUJjLm8P17k1Hrhg3wvwn4Mde1G8I,86
9
+ compehndly/core/conversion.py,sha256=JEQrzPWZh5fhvr3-Pqb1ePzkzs__v5w-MUEpdY4_D7A,447
10
+ compehndly/core/models.py,sha256=IJFy-2uQzaJ21_tz2cFm8LVKBPATaAUjYSyNIJ5ppWg,141
11
+ compehndly/core/registry.py,sha256=L9a-4Pf3z0uNFjSzpZid5EnzKB-4shPRxajJ3WbdkZ0,2539
12
+ compehndly/core/registrymanager.py,sha256=jbwDyUBJoNl3TbqI8puYnUVLtSw9hBp8YsU3n6gaX5E,46
13
+ compehndly/derived_variables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ compehndly/derived_variables/correction.py,sha256=sCBfw3A4xt7sVFLMae60Z1oolp9OWBV8BrJPUmd2mQQ,2976
15
+ compehndly/derived_variables/imputation.py,sha256=xH06i-bT9TWcU-JHlqZNm97C6-Y4jlX63pWihSdvmyU,5338
16
+ compehndly/derived_variables/statsutils.py,sha256=HTVTpomHzBfYgHa4iP6qHR2v7V-pO9O1a5QzKRbGc1s,1350
17
+ compehndly/derived_variables/summation.py,sha256=Wy0ix3Fn7fjbh2tzZSvlcPjcHsCpJZhsRt7C6mQ7qKw,1539
18
+ compehndly/secondary_variables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ compehndly/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ compehndly/utils/bins.py,sha256=M4qv9kl6pozERRuqvzfNlA4u-kEyO-eUhefxwiEQ80k,519
21
+ compehndly/utils/conditionals.py,sha256=wZ3OSm4nNMiG0yQjf-bBALFnQT6lqY-psl_9QkcCogc,338
22
+ compehndly-0.0.1a1.dist-info/METADATA,sha256=vw6s0GziWcKFvQb4caFVR3RInga_vT1zFKjYu87M7EU,538
23
+ compehndly-0.0.1a1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
24
+ compehndly-0.0.1a1.dist-info/entry_points.txt,sha256=za-jxbZdXnWj98QkoyAD8Pn3fa7KdIiN7sxfXhGFPXQ,193
25
+ compehndly-0.0.1a1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [compehndly.adapters]
2
+ numpy = compehndly.adapters.numpy_adapter:NumpyAdapter
3
+ pandas = compehndly.adapters.pandas_adapter:PandasAdapter
4
+ polars = compehndly.adapters.polars_adapter:PolarsAdapter