Skip to main content

fmbqml: Structural Break Analysis in High-Dimensional Factor Models

Python PyPI version License: MIT

fmbqml is a Python package for quasi-maximum-likelihood analysis of structural breaks in high-dimensional factor models. It provides tools for single-break testing and estimation, joint multiple-break estimation, sup-LR testing, factor-number selection, break-type classification, and diagnostic plots.

Installation

Install the package from PyPI with:

python -m pip install fmbqml

For development from a source checkout, use:

python -m pip install -e .

Input data

The input data should be a finite two-dimensional NumPy array or pandas DataFrame with observations in rows and variables in columns. Missing values should be imputed before constructing a model.

Single-break analysis

This example generates a panel with 300 observations and 100 variables. The factor loadings change after observation 100. Rows represent observations in time order, and columns represent variables.

import numpy as np
from fmbqml import SingleBreakQML

# Generate a panel with one loading break.
rng = np.random.default_rng(2024)

T, N, r = 300, 100, 3
true_break = 100

factors = rng.normal(size=(T, r))
loadings_before = rng.normal(size=(N, r))
loadings_after = loadings_before + rng.normal(size=(N, r))
errors = rng.normal(scale=np.sqrt(r), size=(T, N))

panel_data = np.empty((T, N))
panel_data[:true_break] = (
    factors[:true_break] @ loadings_before.T
    + errors[:true_break]
)
panel_data[true_break:] = (
    factors[true_break:] @ loadings_after.T
    + errors[true_break:]
)

# Test the null hypothesis of no structural break.
model = SingleBreakQML(
    panel_data,
    trim_ratio=0.15,
    max_factors=10,
    factor_criterion="IC2",
)

model.lr_test(
    alpha=0.05,
    classify=True,
    verbose=False,
    n_sim=1000,
    random_state=12345,
)

# Display the summary and retrieve individual results.
print(model.summary())
print("Reject the no-break null:", model.is_rejected())
print("Estimated breakpoint:", model.get_breakpoint())
print("Break type:", model.get_break_type())

A breakpoint at k separates the first k observations from the remaining observations. If the LR test does not reject the no-break null, get_breakpoint() returns None.

Use estimate_breakpoint() when one break is assumed to exist and only its location needs to be estimated. This estimation procedure does not perform a significance test.

Multiple-break analysis

This example generates a panel with loading changes after observations 100 and 200. The number and locations of the breaks are estimated from the panel data.

import numpy as np
from fmbqml import MultiBreakQML

# Generate a panel with two loading breaks.
rng = np.random.default_rng(42)

T, N, r = 300, 100, 3
break_1, break_2 = 100, 200

factors = rng.normal(size=(T, r))
loadings_1 = rng.normal(size=(N, r))
loadings_2 = rng.normal(size=(N, r))
loadings_3 = rng.normal(size=(N, r))
errors = rng.normal(size=(T, N))

panel_data = np.empty((T, N))
panel_data[:break_1] = (
    factors[:break_1] @ loadings_1.T
    + errors[:break_1]
)
panel_data[break_1:break_2] = (
    factors[break_1:break_2] @ loadings_2.T
    + errors[break_1:break_2]
)
panel_data[break_2:] = (
    factors[break_2:] @ loadings_3.T
    + errors[break_2:]
)

# Select the number of breaks and estimate their locations.
model = MultiBreakQML(
    panel_data,
    max_break=3,
    max_factors=10,
    factor_criterion="IC2",
    trim_ratio=0.10,
)

model.estimate_breaks_jointly(
    min_break=0,
    classify=True,
    verbose=False,
)

# Display the summary and retrieve individual results.
print(model.summary())
print("Selected number of breaks:", model.get_n_breaks())
print("Estimated breakpoints:", model.get_breakpoints())
print("Break types:", model.get_break_types())

# Retrieve and display the information-criterion path.
ic_path = model.get_ic_path()
print(ic_path)

# Continue with the model constructed above.
# Test no break against an alternative with two prespecified breaks.
model.joint_sup_lr_test(
    n_breaks=2,
    alpha=0.05,
    n_sim=1000,
    random_state=123,
    classify=True,
    verbose=False,
)

print(model.summary(mode="joint_lr"))
print("Reject the no-break null:", model.is_rejected())

if model.is_rejected():
    print(
        "Estimated breakpoints:",
        model.get_breakpoints(mode="joint_lr"),
    )
    print(
        "Break types:",
        model.get_break_types(mode="joint_lr"),
    )

max_break sets the maximum number of candidate breaks, while min_break=0 allows selection of a no-break model. The true break locations used to generate the data are not supplied to the estimator.

Factor-number selection

factor_criterion accepts IC1, IC2, IC3, PC1, PC2, PC3, AIC3, or BIC3. Integer indices 0 through 7 are also accepted in that order.

The argument max_factors specifies the upper bound for factor-number selection and must not exceed min(T, N).

Results and model state

The package provides dedicated methods for accessing commonly used results without directly inspecting internal result dictionaries.

For single-break analysis, is_rejected() returns the LR test decision, get_breakpoint() returns the estimated breakpoint, and get_break_type() returns its classification. If the LR test does not reject the no-break null, get_breakpoint() returns None.

For multiple-break analysis, get_n_breaks() returns the number of breaks selected by joint estimation, while get_breakpoints() and get_break_types() return the estimated break locations and their classifications. The get_ic_path() method returns the information-criterion path as a pandas DataFrame. For joint sup-LR testing, is_rejected() returns the test decision, and the breakpoint locations and types can be accessed by specifying mode="joint_lr".

The summary() method provides a formatted overview of the results. Diagnostic profiles can be accessed through dedicated methods and plotting functions.

For advanced analysis, complete result dictionaries remain available through get_test_results(), get_joint_results(), and get_joint_test_results(). These dictionaries contain additional information, including LR and QML profiles, breakpoint classifications, and information-criterion paths. The result accessors return independent copies of the stored results, preventing modifications to the model's internal state.

Calling set_data() or set_parameters() validates the new values and clears previously computed results.

Reproducibility

Monte Carlo procedures used to approximate LR critical values support reproducibility through the random_state argument. Users can specify random_state when calling lr_test(), joint_sup_lr_test(), or LR profile plotting methods that perform Monte Carlo simulations.

The examples use fixed random seeds for data generation and critical-value simulation to facilitate reproducibility.

Example scripts demonstrating the main functionality of fmbqml are available in the project repository:

Dependencies

fmbqml depends on NumPy, SciPy, pandas, matplotlib, and joblib. Python 3.8 or newer is required.

License

fmbqml is distributed under the MIT License. See LICENSE.txt.

Metadata

Release files for fmbqml 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fmbqml 0.1.3
File Size Uploaded
fmbqml-0.1.3.tar.gz 48.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fmbqml 0.1.3
File Interpreter ABI Platform
fmbqml-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 105.6 kB

Release files / fmbqml-0.1.3.tar.gz

Download URL fmbqml-0.1.3.tar.gz
Size 48.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b23e53c1575069ebd5eaf615f072b330c85594150c8461e42544c04c0a40e2d0
BLAKE2b-256 checksum
How to use checksums
f6395723e6bc8421aedf0a51709a7678ce238a1523ad9f5557a1f5719e87b0a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.11

Release files / fmbqml-0.1.3-py3-none-any.whl

Download URL fmbqml-0.1.3-py3-none-any.whl
Size 57.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4b51d8081f05e1fb2dd726e27277ae78b70ae792ea0839c0b0265c252cacbf88
BLAKE2b-256 checksum
How to use checksums
68f6ca362e8e64361c60d6bac83dc230fafeba6eaa0ee5b98a0dcb0f3338aba8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.11

Release history Release notifications | RSS feed

0.1.4

2 release files

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page