diff --git a/content/blog/2026-11-09-tsgap.md b/content/blog/2026-11-09-tsgap.md new file mode 100644 index 00000000..880b2240 --- /dev/null +++ b/content/blog/2026-11-09-tsgap.md @@ -0,0 +1,142 @@ +--- +title: "TSGap: Reproducible missingness simulation for time-series benchmarks" +date: "2026-11-09" +type: blog +url: "/blog/tsgap-time-series-missingness.html" +excerpt: >- + TSGap is a NumPy-native Python package for creating reproducible artificial + missingness scenarios for time-series imputation benchmarks. +author: "Feruz Oripov" +blog_topic: software +toc: true +lastmod: "2026-11-09" +image: + src: "images/blog/2026/tsgap-before-after.png" + alt: >- + A complete multivariate time series beside six examples with missing values + highlighted in red: pointwise, block, monotone, decay, Markov, and + Gilbert-Elliott patterns. +tags: + - time series + - missing data + - imputation + - scientific Python +show_author: true +learn_more: + - title: "TSGap documentation" + url: "https://feruzoripov.github.io/tsgap/" + - title: "TSGap on GitHub" + url: "https://github.com/feruzoripov/tsgap" + - title: "TSGap on PyPI" + url: "https://pypi.org/project/tsgap/" +comments: true +--- + +Evaluating a time-series imputation method requires known values that can be +hidden and reconstructed. The way those values are hidden matters: isolated +missing points, sensor outages, dropout near the end of a recording, and bursty +packet loss create very different reconstruction problems. + +[TSGap](https://github.com/feruzoripov/tsgap) is a lightweight, NumPy-native +Python package for creating these artificial missingness scenarios. It was +recently [peer reviewed and accepted by +pyOpenSci](https://github.com/pyOpenSci/software-submission/issues/354). + +## Why separate mechanism from pattern + +TSGap describes a scenario using two ideas: + +* The **mechanism** describes why a value is selected for masking. TSGap + supports missing completely at random (MCAR), missing at random (MAR), and + missing not at random (MNAR). +* The **pattern** describes how missing values are arranged over time. + Available patterns include pointwise, block, monotone, decay, Markov, and + Gilbert-Elliott burst loss. + +Separating these concepts makes experiments easier to describe and reproduce. +For example, researchers can compare isolated MCAR gaps with contiguous MCAR +outages while keeping the requested missing rate fixed. + +There is an important interpretation detail. Pointwise MAR and MNAR preserve a +direct relationship between the mechanism score and each masked value. +Non-pointwise patterns reshape a mechanism-informed missingness budget into a +temporal structure. They should therefore be described as structured, +mechanism-informed masks rather than automatically treated as strict MAR or +MNAR data-generating processes. + +## Create a block-missingness scenario + +Install TSGap from PyPI: + +```bash +pip install tsgap +``` + +The following example masks 20% of the eligible values in a two-feature time +series. Each block spans between 2% and 10% of the series length. + +```python +import numpy as np + +from tsgap import simulate_missingness + +rng = np.random.default_rng(42) +X = rng.normal(size=(1_000, 2)) + +X_missing, observed_mask = simulate_missingness( + X, + mechanism="mcar", + pattern="block", + missing_rate=0.20, + block_frac=(0.02, 0.10), + seed=42, +) + +artificially_missing = ~observed_mask & np.isfinite(X) +achieved_rate = artificially_missing.sum() / np.isfinite(X).sum() + +print(f"Artificial missingness rate: {achieved_rate:.1%}") +``` + +`X_missing` contains `NaN` at the newly hidden positions. The Boolean +`observed_mask` is `False` at those positions and `True` where data remain +observed. The same seed produces the same mask, which supports reproducible +benchmarking. + +The missing-rate denominator is the number of originally observed, eligible +entries in the selected target dimensions. Existing `NaN` values are preserved +but do not count toward the requested artificial missingness rate. This +distinction is useful when working with real sensor recordings that already +contain gaps. + +## From simulation to evaluation + +A typical benchmark can use TSGap in four steps: + +1. Start with complete data, or record which values were originally observed. +2. Generate a missingness scenario with a fixed seed. +3. Run an imputation method on `X_missing`. +4. Evaluate predictions only at positions that TSGap artificially masked. + +TSGap supports both two-dimensional arrays shaped as time by features and +three-dimensional arrays shaped as samples by time by features. Target +dimensions can be selected so that auxiliary variables remain available while +another variable is masked. + +Deterministic patterns aim for exact rate control among eligible entries. +Stochastic state models such as Markov and Gilbert-Elliott masks can vary +between seeds, so studies using them should report achieved rates and aggregate +results across multiple seeds. + +## Try TSGap + +TSGap is available under the MIT license: + +* [Documentation](https://feruzoripov.github.io/tsgap/) +* [Source code](https://github.com/feruzoripov/tsgap) +* [PyPI package](https://pypi.org/project/tsgap/) +* [Version 0.8.0 + release](https://github.com/feruzoripov/tsgap/releases/tag/v0.8.0) + +Bug reports, feature requests, and contributions are welcome through the +[TSGap issue tracker](https://github.com/feruzoripov/tsgap/issues). diff --git a/static/images/blog/2026/tsgap-before-after.png b/static/images/blog/2026/tsgap-before-after.png new file mode 100644 index 00000000..b93eee51 Binary files /dev/null and b/static/images/blog/2026/tsgap-before-after.png differ