Source feature clipping
neureptrace.decoding.source_clipping implements source-only feature clipping for robust cross-subject decoding.
The protocol is Category 1 / strict source-only. Lower and upper bounds are estimated from source rows only, then applied to source and held-out feature matrices. Held-out rows are never used to fit the clipping bounds.
Typical usage:
from neureptrace.decoding.source_clipping import fit_source_feature_clipping
result = fit_source_feature_clipping(
source_features=X_source,
test_features=X_target,
config={"lower_quantile": 0.01, "upper_quantile": 0.99},
)
X_source_clip = result.train_features
X_target_clip = result.test_features
neureptrace.decoding.source_clipping
SourceFeatureClippingConfig
dataclass
Source code in src/neureptrace/decoding/source_clipping/__init__.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | @dataclass(frozen=True, slots=True)
class SourceFeatureClippingConfig:
lower_quantile: float = DEFAULT_LOWER_QUANTILE
upper_quantile: float = DEFAULT_UPPER_QUANTILE
copy: bool = True
def __post_init__(self) -> None:
lower = _quantile(self.lower_quantile, name="lower_quantile")
upper = _quantile(self.upper_quantile, name="upper_quantile")
if lower >= upper:
raise ValueError("lower_quantile must be smaller than upper_quantile.")
object.__setattr__(self, "lower_quantile", lower)
object.__setattr__(self, "upper_quantile", upper)
object.__setattr__(self, "copy", _bool_value(self.copy, name="copy"))
|
SourceFeatureClippingResult
dataclass
Source code in src/neureptrace/decoding/source_clipping/__init__.py
| @dataclass(frozen=True, slots=True)
class SourceFeatureClippingResult:
train_features: np.ndarray
test_features: np.ndarray
lower_bounds: np.ndarray
upper_bounds: np.ndarray
metadata: dict[str, Any] = field(default_factory=dict)
|
fit_source_feature_clipping(*, source_features, test_features, config=None)
Source code in src/neureptrace/decoding/source_clipping/__init__.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 | def fit_source_feature_clipping(
*,
source_features: Sequence[Sequence[float]] | np.ndarray,
test_features: Sequence[Sequence[float]] | np.ndarray,
config: SourceFeatureClippingConfig | Mapping[str, Any] | None = None,
) -> SourceFeatureClippingResult:
cfg = source_feature_clipping_config() if config is None else _coerce_config(config)
source = _matrix(source_features, name="source_features")
test = _matrix(test_features, name="test_features")
if source.shape[1] != test.shape[1]:
raise ValueError("source_features and test_features must have the same feature width.")
lower, upper = source_feature_clipping_bounds(
source,
lower_quantile=cfg.lower_quantile,
upper_quantile=cfg.upper_quantile,
)
train = apply_feature_clipping(source, lower_bounds=lower, upper_bounds=upper, copy=cfg.copy)
clipped = apply_feature_clipping(test, lower_bounds=lower, upper_bounds=upper, copy=cfg.copy)
return SourceFeatureClippingResult(
train.astype(np.float32, copy=False),
clipped.astype(np.float32, copy=False),
lower.astype(np.float32, copy=False),
upper.astype(np.float32, copy=False),
_metadata(cfg, source.shape[0], test.shape[0], source.shape[1]),
)
|
source_feature_clipping_config(*, lower_quantile=DEFAULT_LOWER_QUANTILE, upper_quantile=DEFAULT_UPPER_QUANTILE, copy=True)
Source code in src/neureptrace/decoding/source_clipping/__init__.py
40
41
42
43
44
45
46
47
48
49
50 | def source_feature_clipping_config(
*,
lower_quantile: float | str = DEFAULT_LOWER_QUANTILE,
upper_quantile: float | str = DEFAULT_UPPER_QUANTILE,
copy: bool | int | str = True,
) -> SourceFeatureClippingConfig:
lower = _quantile(lower_quantile, name="lower_quantile")
upper = _quantile(upper_quantile, name="upper_quantile")
if lower >= upper:
raise ValueError("lower_quantile must be smaller than upper_quantile.")
return SourceFeatureClippingConfig(lower, upper, _bool_value(copy, name="copy"))
|
source_feature_clipping_bounds(source_features, *, lower_quantile=DEFAULT_LOWER_QUANTILE, upper_quantile=DEFAULT_UPPER_QUANTILE)
Source code in src/neureptrace/decoding/source_clipping/__init__.py
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 | def source_feature_clipping_bounds(
source_features: Sequence[Sequence[float]] | np.ndarray,
*,
lower_quantile: float = DEFAULT_LOWER_QUANTILE,
upper_quantile: float = DEFAULT_UPPER_QUANTILE,
) -> tuple[np.ndarray, np.ndarray]:
source = _matrix(source_features, name="source_features")
lower = _quantile(lower_quantile, name="lower_quantile")
upper = _quantile(upper_quantile, name="upper_quantile")
if lower >= upper:
raise ValueError("lower_quantile must be smaller than upper_quantile.")
return (
np.quantile(source, lower, axis=0).astype(float, copy=False),
np.quantile(source, upper, axis=0).astype(float, copy=False),
)
|
apply_feature_clipping(features, *, lower_bounds, upper_bounds, copy=True)
Source code in src/neureptrace/decoding/source_clipping/__init__.py
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112 | def apply_feature_clipping(
features: Sequence[Sequence[float]] | np.ndarray,
*,
lower_bounds: Sequence[float] | np.ndarray,
upper_bounds: Sequence[float] | np.ndarray,
copy: bool | int | str = True,
) -> np.ndarray:
matrix = _matrix(features, name="features")
lower = np.asarray(lower_bounds, dtype=float).reshape(-1)
upper = np.asarray(upper_bounds, dtype=float).reshape(-1)
if lower.shape[0] != matrix.shape[1] or upper.shape[0] != matrix.shape[1]:
raise ValueError("lower_bounds and upper_bounds must match the feature width.")
if np.any(lower > upper):
raise ValueError("lower_bounds cannot exceed upper_bounds.")
target = matrix.copy() if _bool_value(copy, name="copy") else matrix
return np.clip(target, lower, upper, out=target)
|