extendedmosaicperm package
Subpackages
- extendedmosaicperm.experiments package
- Submodules
- Module contents
Submodules
Module contents
extendedmosaicperm: extensions and experiments for mosaic permutation tests.
- class ExtendMosaicFactorTest(*args, sign_flipping=False, seed=123, ridge_alphas=None, adaptive_tiling=False, **kwargs)[source]
Bases:
MosaicFactorTestExtended mosaic factor test.
Extends
mosaicperm.factor.MosaicFactorTestby adding:sign-flip inference instead of standard permutation,
RidgeCV-based residual estimation,
adaptive tiling of data based on the correlation structure.
- Parameters:
*args – Positional arguments forwarded to the base
mosaicperm.factor.MosaicFactorTestconstructor.sign_flipping (
bool) – Whether to use sign-flip inference instead of permutations.seed (
int) – Seed for the internal random number generator used for sign flips.ridge_alphas (
Optional[ndarray]) – Sequence of alpha values forsklearn.linear_model.RidgeCV. IfNone, ordinary least squares (OLS) is used to estimate residuals.adaptive_tiling (
bool) – IfTrue, tiles are built automatically fromoutcomesandexposuresusingextendedmosaicperm.tilings.build_adaptive_tiling().**kwargs – Keyword arguments forwarded to the base
mosaicperm.factor.MosaicFactorTestconstructor.
- sign_flipping
Boolean flag indicating whether sign-flip inference is used.
- rng
NumPy random number generator used for sign flips.
- ridge_alphas
Grid of RidgeCV penalization strengths, or
Nonefor OLS.
Examples
Basic workflow with fixed exposures (2D exposures) and OLS residuals:
>>> import numpy as np >>> from mosaicperm import statistics >>> from extendedmosaicperm.factor import ExtendMosaicFactorTest >>> from extendedmosaicperm.tilings import build_adaptive_tiling >>> >>> T, N, K = 60, 12, 3 >>> rng = np.random.default_rng(0) >>> F = rng.standard_normal((T, K)) >>> B = rng.standard_normal((K, N)) >>> Y = F @ B + rng.standard_normal((T, N)) >>> >>> tiles = build_adaptive_tiling(Y, B.T) # exposures as (N x K) >>> mpt = ExtendMosaicFactorTest( ... outcomes=Y, ... exposures=B.T, ... tiles=tiles, ... test_stat=statistics.mean_maxcorr_stat, ... sign_flipping=True, # enable sign-flip inference ... ridge_alphas=None, # OLS for residuals ... seed=0, ... ) >>> _ = mpt.compute_mosaic_residuals() >>> p = mpt._compute_p_value(nrand=20, verbose=False) >>> 0.0 <= p <= 1.0 True
Ridge-regularized residuals:
>>> import numpy as np >>> from mosaicperm import statistics >>> from extendedmosaicperm.factor import ExtendMosaicFactorTest >>> from extendedmosaicperm.tilings import build_adaptive_tiling >>> >>> T, N, K = 60, 12, 3 >>> rng = np.random.default_rng(1) >>> F = rng.standard_normal((T, K)) >>> B = rng.standard_normal((K, N)) >>> Y = F @ B + rng.standard_normal((T, N)) >>> >>> tiles = build_adaptive_tiling(Y, B.T) >>> alphas = np.logspace(-3, 3, 5) >>> mpt_ridge = ExtendMosaicFactorTest( ... outcomes=Y, ... exposures=B.T, ... tiles=tiles, ... test_stat=statistics.mean_maxcorr_stat, ... ridge_alphas=alphas, # use RidgeCV ... sign_flipping=False, # standard permutation ... seed=1, ... ) >>> _ = mpt_ridge.compute_mosaic_residuals() >>> p_ridge = mpt_ridge._compute_p_value(nrand=20, verbose=False) >>> 0.0 <= p_ridge <= 1.0 True
Adaptive tiling (no need to pass
tilesexplicitly):>>> import numpy as np >>> from mosaicperm import statistics >>> from extendedmosaicperm.factor import ExtendMosaicFactorTest >>> >>> T, N, K = 60, 12, 3 >>> rng = np.random.default_rng(2) >>> F = rng.standard_normal((T, K)) >>> B = rng.standard_normal((K, N)) >>> Y = F @ B + rng.standard_normal((T, N)) >>> >>> mpt_adapt = ExtendMosaicFactorTest( ... outcomes=Y, ... exposures=B.T, ... adaptive_tiling=True, ... test_stat=statistics.mean_maxcorr_stat, ... sign_flipping=True, ... seed=2, ... ) >>> _ = mpt_adapt.compute_mosaic_residuals() >>> p_adapt = mpt_adapt._compute_p_value(nrand=20, verbose=False) >>> 0.0 <= p_adapt <= 1.0 True
- compute_mosaic_residuals()[source]
Compute mosaic residuals for all tiles.
For each (batch, group) tile, factor loadings are estimated and residuals are computed. If
ridge_alphasisNone, residuals are obtained via OLS; otherwise, a separateRidgeCVis fit for each observation in the tile.- Returns:
Residual matrix with the same shape as
self.outcomes.- Return type:
np.ndarray
- class FactorModelDataGenerator(n_timepoints=60, n_assets=10, n_factors=3, seed=123)[source]
Bases:
objectGenerator of synthetic data for factor-model simulations.
Supports multiple residual correlation structures, time-varying exposures, and configurable distributional asymmetry.
- Parameters:
n_timepoints (
int) – Number of time periodsT.n_assets (
int) – Number of assetsN.n_factors (
int) – Number of factorsK.seed (
int) – Random seed for reproducibility.
Examples
Basic usage with constant exposures and symmetric residuals:
>>> from extendedmosaicperm.factor_data import FactorModelDataGenerator >>> gen = FactorModelDataGenerator(n_timepoints=20, n_assets=6, n_factors=2, seed=0) >>> Y, L_time, X, eps = gen.generate_data_random_correlation( ... violation_strength=0.1, ... exposures_update_interval=None, ... symmetric_residuals=True, ... ) >>> Y.shape, L_time.shape, X.shape, eps.shape ((20, 6), (20, 6, 2), (20, 2), (20, 6))
Time-varying exposures, redrawn every 5 periods:
>>> gen = FactorModelDataGenerator(n_timepoints=15, n_assets=5, n_factors=3, seed=1) >>> Y, L_time, X, eps = gen.generate_data_block_correlation( ... violation_strength=0.2, ... block_ratio=0.4, ... exposures_update_interval=5, ... symmetric_residuals=True, ... ) >>> L_time[0].shape (5, 3)
Asymmetric residuals (gamma-based, standardized):
>>> gen = FactorModelDataGenerator(n_timepoints=12, n_assets=7, n_factors=2, seed=2) >>> Y, L_time, X, eps = gen.generate_data_diagonal_plus_common_factor( ... violation_strength=0.3, ... exposures_update_interval=None, ... symmetric_residuals=False, ... gamma_shape=2.0, ... gamma_scale=1.0, ... ) >>> eps.shape == Y.shape == (12, 7) True
- generate_data_block_correlation(violation_strength=0.0, block_ratio=0.5, exposures_update_interval=None, symmetric_residuals=True, gamma_shape=2.0, gamma_scale=1.0)[source]
Generate data with block-correlated residuals.
A subset of assets (a “block”) exhibits stronger cross-sectional correlation than the rest. The size of the block and correlation strength are controlled by
block_ratioandviolation_strength.- Parameters:
violation_strength (
float) – Within-block correlation strength in[0, 1].block_ratio (
float) – Fraction of assets forming the strongly correlated block in(0, 1].exposures_update_interval (
Optional[int]) – Interval (in timepoints) for refreshing exposures;Nonemeans constant exposures.symmetric_residuals (
bool) – Whether residuals are symmetric (Gaussian) or asymmetric (gamma-based).gamma_shape (
float) – Shape parameter for asymmetric (Gamma) noise.gamma_scale (
float) – Scale parameter for asymmetric (Gamma) noise.
- Returns:
A tuple
(Y, L_time, X, eps)where:Y– simulated outcomes, shape(T, N),L_time– exposures, shape(T, N, K),X– latent factors, shape(T, K),eps– residuals, shape(T, N).
- Return type:
tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
Examples
>>> from extendedmosaicperm.factor_data import FactorModelDataGenerator >>> gen = FactorModelDataGenerator(n_timepoints=24, n_assets=10, n_factors=3, seed=0) >>> Y, L_time, X, eps = gen.generate_data_block_correlation( ... violation_strength=0.3, block_ratio=0.5, exposures_update_interval=6 ... ) >>> Y.shape[1], L_time.shape[2], X.shape[1] (10, 3, 3)
- generate_data_diagonal_plus_common_factor(violation_strength=0.0, exposures_update_interval=None, symmetric_residuals=True, gamma_shape=2.0, gamma_scale=1.0)[source]
Generate data with diagonal residual covariance plus a common factor.
Residuals are decomposed into a diagonal idiosyncratic component and a single common factor component with strength controlled by
violation_strength.- Parameters:
violation_strength (
float) – Strength of the common residual factor in[0, 1].exposures_update_interval (
Optional[int]) – Interval (in timepoints) for refreshing exposures;Nonemeans constant exposures.symmetric_residuals (
bool) – Whether residuals are symmetric (Gaussian) or asymmetric (gamma-based).gamma_shape (
float) – Shape parameter for asymmetric (Gamma) noise.gamma_scale (
float) – Scale parameter for asymmetric (Gamma) noise.
- Returns:
A tuple
(Y, L_time, X, eps)where:Y– simulated outcomes, shape(T, N),L_time– exposures, shape(T, N, K),X– latent factors, shape(T, K),eps– residuals, shape(T, N).
- Return type:
tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
Examples
>>> from extendedmosaicperm.factor_data import FactorModelDataGenerator >>> gen = FactorModelDataGenerator(n_timepoints=25, n_assets=9, n_factors=2, seed=0) >>> Y, L_time, X, eps = gen.generate_data_diagonal_plus_common_factor( ... violation_strength=0.2 ... ) >>> X.shape (25, 2)
- generate_data_random_correlation(violation_strength=0.0, exposures_update_interval=None, symmetric_residuals=True, gamma_shape=2.0, gamma_scale=1.0)[source]
Generate data with a random residual correlation structure.
Residuals are generated from a random positive-definite correlation matrix, with the degree of cross-sectional dependence controlled by
violation_strength.- Parameters:
violation_strength (
float) – Strength of cross-sectional residual correlation in[0, 1].0corresponds to independence,1to the raw random correlation structure.exposures_update_interval (
Optional[int]) – Interval (in timepoints) for refreshing exposures;Nonemeans constant exposures.symmetric_residuals (
bool) – Whether residuals are symmetric (Gaussian) or asymmetric (gamma-based).gamma_shape (
float) – Shape parameter for asymmetric (Gamma) noise.gamma_scale (
float) – Scale parameter for asymmetric (Gamma) noise.
- Returns:
A tuple
(Y, L_time, X, eps)where:Y– simulated outcomes, shape(T, N),L_time– exposures, shape(T, N, K),X– latent factors, shape(T, K),eps– residuals, shape(T, N).
- Return type:
tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
Examples
>>> from extendedmosaicperm.factor_data import FactorModelDataGenerator >>> gen = FactorModelDataGenerator(n_timepoints=30, n_assets=8, n_factors=3, seed=0) >>> Y, L_time, X, eps = gen.generate_data_random_correlation(violation_strength=0.2) >>> Y.shape, L_time.shape, X.shape, eps.shape ((30, 8), (30, 8, 3), (30, 3), (30, 8))
- build_adaptive_tiling(outcomes, exposures, batch_size=10, D=None, seed=0)[source]
Build an adaptive tiling of the sample based on residual covariance.
The time dimension is split into batches of size
batch_size. For the first batch, groups are assigned using a random even partition. For subsequent batches, residuals from previous tiles are used to estimate a covariance matrix, and the grouping is updated viagreedy_grouping().- Parameters:
outcomes (
ndarray) – Outcome matrix of shape(T, p).exposures (
ndarray) – Factor loadings, either constant of shape(p, K)or time-varying of shape(T, p, K).batch_size (
int) – Number of observations per batch.D (
Optional[int]) – Number of groups. IfNone, defaults tomax(2, p // (2 * K)), whereKis the number of factors.seed (
int) – Random seed used for initial grouping and subsequent updates.
- Returns:
A tiling object containing all (batch, group) pairs.
- Return type:
Tiling
- Raises:
ValueError – If
outcomesorexposuresisNone, or if the exposure slices cannot be reshaped to(p, K).
Examples
>>> import numpy as np >>> from mosaicperm.tilings import Tiling >>> from extendedmosaicperm.tilings import build_adaptive_tiling >>> rng = np.random.default_rng(0) >>> T, p, K = 12, 6, 2 >>> Y = rng.normal(size=(T, p)) >>> L = rng.normal(size=(p, K)) >>> til = build_adaptive_tiling(Y, L, batch_size=4, D=3, seed=7) >>> isinstance(til, Tiling) True >>> len(til) 9