tpattern 0.1.0__tar.gz

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.
tpattern-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrew Callaway
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: tpattern
3
+ Version: 0.1.0
4
+ Summary: Open, auditable temporal & sequential pattern analysis (T-patterns) for sport and behaviour
5
+ Author-email: Andrew Callaway <acallaway@bournemouth.ac.uk>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ajcallaway/TPattern
8
+ Project-URL: Repository, https://github.com/ajcallaway/TPattern
9
+ Project-URL: Archive (DOI), https://doi.org/10.5281/zenodo.21397543
10
+ Project-URL: Tagging tool (OpenTag), https://opentag.studio
11
+ Keywords: t-pattern,THEME,temporal,sequence,sport,behaviour,notation analysis
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Intended Audience :: Science/Research
21
+ Classifier: Topic :: Scientific/Engineering
22
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: numpy>=1.21
27
+ Requires-Dist: scipy>=1.7
28
+ Requires-Dist: matplotlib>=3.4
29
+ Provides-Extra: test
30
+ Requires-Dist: pytest>=7; extra == "test"
31
+ Provides-Extra: gui
32
+ Requires-Dist: ipywidgets>=8; extra == "gui"
33
+ Requires-Dist: pandas>=1.3; extra == "gui"
34
+ Requires-Dist: openpyxl>=3; extra == "gui"
35
+ Dynamic: license-file
36
+
37
+ # tpattern — open, auditable analysis of recurring patterns in event sequences
38
+
39
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ajcallaway/TPattern/blob/main/colab/tpattern_guided_analysis.ipynb)
40
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21397543.svg)](https://doi.org/10.5281/zenodo.21397543)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
42
+
43
+ `tpattern` detects recurring temporal patterns (T-patterns; Magnusson, 2000) in
44
+ **any sequence of timed, labelled events** — behaviour, sport, clinical or
45
+ physiological logs, interaction data — validates them properly against chance, and
46
+ produces clean, publication-ready output (tables, figures, pattern dendrograms). It
47
+ is a free, transparent, reproducible alternative to the THEME software, and the
48
+ analysis companion to the [OpenTag.Studio](https://opentag.studio) tagging tool.
49
+
50
+ ## Two ways to use it
51
+
52
+ - 🚀 **Google Colab — no coding.** [**Open the guided notebook in Colab**](https://colab.research.google.com/github/ajcallaway/TPattern/blob/main/colab/tpattern_guided_analysis.ipynb): upload your events, follow the guided steps, download a report with a plain-English verdict for every pattern and a paste-ready Methods paragraph. See [`colab/`](colab/).
53
+ - 🐍 **Python library.** `pip install` and script it — see [Quickstart](#quickstart) and [`examples/`](examples/).
54
+
55
+ Bring your own data (one row per event — see [`SCHEMA.md`](SCHEMA.md)) or try the
56
+ included [`data/`](data/) example.
57
+
58
+ ## Why it exists
59
+
60
+ T-pattern analysis finds behaviourally meaningful structure that event counts and
61
+ simple transition tables miss. It is most established through Magnusson's THEME
62
+ software, the reference implementation. `tpattern` is an open, complementary
63
+ implementation that reproduces THEME's detection and adds a reproducible chance
64
+ model and clear reporting:
65
+
66
+ - **reproduces** THEME's core detection (critical intervals, Nx/T baseline,
67
+ completeness competition) — validated to the pattern for published datasets;
68
+ - **adds a chance model** — every pattern is tested against a surrogate null, with
69
+ p-values **corrected for repeated testing** (false-discovery rate and
70
+ family-wise);
71
+ - **handles concurrency explicitly** — events sharing a timestamp are treated as
72
+ co-occurrence rather than sequence;
73
+ - **reports clearly** — tidy tables, effect sizes with confidence intervals,
74
+ purposeful figures, and clean **dendrograms** of the detected patterns.
75
+
76
+ ## Installation
77
+
78
+ From source (until published on PyPI):
79
+
80
+ ```bash
81
+ git clone https://github.com/ajcallaway/TPattern.git
82
+ cd tpattern
83
+ pip install -e .
84
+ ```
85
+
86
+ Requires Python ≥ 3.9 (depends on numpy, scipy, matplotlib).
87
+
88
+ ## Quickstart
89
+
90
+ ```python
91
+ from tpattern import read_table, Config, recommend, calibrate, report, methods_text
92
+
93
+ # 1. Load your events — one row per event (observation, event, start).
94
+ # Pass time_unit="ms" if your times are in milliseconds (default is seconds).
95
+ # See SCHEMA.md for the (simple) required format.
96
+ observations = read_table("events.csv", time_unit="s")
97
+
98
+ # 2. Let the tool inspect the data and recommend the method — which surrogate
99
+ # null, whether to require a genuine lag, which error control — with reasons.
100
+ print(recommend(observations))
101
+
102
+ # 3. Detect and calibrate in one call: every pattern is tested against a
103
+ # surrogate null with correction for repeated testing.
104
+ # (calibrate() runs the detection for you — you don't call detect() first.)
105
+ result = calibrate(observations, Config(), null="profile", B=200, q_target=0.05)
106
+ survivors = result.kept("fdr") # patterns that survive false-discovery control
107
+
108
+ # 4. Report: a tidy table (with q-values), pattern dendrograms and a summary —
109
+ # plus a paste-ready Methods paragraph stating every setting and what it does.
110
+ report(result, "report_out", title="My analysis")
111
+ print(methods_text(Config(), observations=observations, calibration=result))
112
+ ```
113
+
114
+ Two runnable examples:
115
+
116
+ ```bash
117
+ pip install -e .
118
+ python examples/quickstart.py # no data needed (synthetic ground-truth demo)
119
+ python examples/reproduce_worldcup.py # a real read_table + full analysis on shipped data
120
+ ```
121
+
122
+ Just want the detected patterns without calibration? `Engine(observations,
123
+ Config()).detect()` returns them directly.
124
+
125
+ **Comparing two groups** (winner vs loser, goal vs non-goal, condition A vs B)?
126
+ `group_contrast(observations, group_of)` tests each pattern's occurrence between
127
+ the groups (Fisher's exact test, odds ratio + 95% CI) and feeds `forest_plot`.
128
+
129
+ ## The workflow
130
+
131
+ ```
132
+ tag events export analyse report
133
+ (OpenTag / Sportscode) ──────▶ standard ──▶ tpattern ──▶ tables · figures · dendrograms
134
+ table (detect · validate ·
135
+ calibrate)
136
+ ```
137
+
138
+ 1. **Tag** your video/events in any coding tool.
139
+ 2. **Export** to the standard input structure — one row per event
140
+ (`observation, event, start`, plus optional `end`, descriptor and group
141
+ columns). See [`SCHEMA.md`](SCHEMA.md).
142
+ 3. **Set up your coding** so the export is analysis-ready — one event per action,
143
+ explicit outcomes, unit boundaries. See [`CODING_GUIDE.md`](CODING_GUIDE.md).
144
+ 4. **Detect** T-patterns, **validate** them against a surrogate null with corrected
145
+ significance (`recommend` advises the settings; `calibrate` runs it), and
146
+ **report** (tables, dendrograms, and a `methods_text` Methods paragraph).
147
+
148
+ ## Relationship to OpenTag.Studio
149
+
150
+ [OpenTag.Studio](https://opentag.studio) is a free, browser-based sports video
151
+ tagging tool. It is the natural front end for `tpattern`: tag your footage, export
152
+ a session, and analyse it here — no proprietary software anywhere in the pipeline.
153
+ OpenTag's **THEME & GSEQ setup guide** covers how to design a coding window so the
154
+ export is analysis-ready, and that guidance applies directly to `tpattern`
155
+ (`CODING_GUIDE.md` is the short bridge). Together they form a complete, open
156
+ tag → analysis pipeline.
157
+
158
+ ## Reproducing the published results
159
+
160
+ The derived 2022 World Cup event sequences ship in [`data/`](data/), so the
161
+ published analyses reproduce from this repository alone:
162
+
163
+ ```bash
164
+ python examples/reproduce_worldcup.py # concordance, exclusion, concurrency (~5 s)
165
+ python examples/reproduce_worldcup.py --calibrate # also the surrogate null (~10 min)
166
+ ```
167
+
168
+ This regenerates the tpattern-vs-THEME pattern counts by level, the interception
169
+ exclusion divergence, the co-occurrence-vs-sequence analysis, and how many patterns
170
+ survive the surrogate null. All seeded and deterministic. See
171
+ [`data/README.md`](data/README.md) for provenance and how the data was derived.
172
+
173
+ ## Documentation
174
+
175
+ - [`SCHEMA.md`](SCHEMA.md) — the input data structure (what your export must contain).
176
+ - [`CODING_GUIDE.md`](CODING_GUIDE.md) — how to set up a coding window to produce it.
177
+ - [`data/`](data/) — the derived World Cup sequences, in the canonical format.
178
+ - [`examples/`](examples/) — a runnable quickstart and the reproduction script.
179
+
180
+ ## Tests
181
+
182
+ ```bash
183
+ pip install -e .[test]
184
+ pytest -q
185
+ ```
186
+
187
+ Both the synthetic ground-truth tests and the THEME reproduction tests run
188
+ everywhere — the latter check the detector still reproduces THEME's published
189
+ occurrence counts to the exact N.
190
+
191
+ ## Citation
192
+
193
+ If you use `tpattern` in research, please cite the software (a machine-readable
194
+ version is in [`CITATION.cff`](CITATION.cff)):
195
+
196
+ Callaway, A. (2026). *tpattern: open, auditable temporal & sequential analysis for
197
+ sport and behaviour* [Computer software]. Zenodo.
198
+ https://doi.org/10.5281/zenodo.21397543
199
+
200
+ This DOI is permanent and always resolves to the current release. To pin a specific
201
+ version instead, use that release's own DOI, listed on the Zenodo record.
202
+
203
+ And the method:
204
+
205
+ Magnusson, M. S. (2000). Discovering hidden time patterns in behavior: T-patterns
206
+ and their detection. *Behavior Research Methods, Instruments, & Computers*, 32(1),
207
+ 93–110. https://doi.org/10.3758/BF03200792
208
+
209
+ ## License
210
+
211
+ MIT — see [`LICENSE`](LICENSE).
@@ -0,0 +1,175 @@
1
+ # tpattern — open, auditable analysis of recurring patterns in event sequences
2
+
3
+ [![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ajcallaway/TPattern/blob/main/colab/tpattern_guided_analysis.ipynb)
4
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21397543.svg)](https://doi.org/10.5281/zenodo.21397543)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ `tpattern` detects recurring temporal patterns (T-patterns; Magnusson, 2000) in
8
+ **any sequence of timed, labelled events** — behaviour, sport, clinical or
9
+ physiological logs, interaction data — validates them properly against chance, and
10
+ produces clean, publication-ready output (tables, figures, pattern dendrograms). It
11
+ is a free, transparent, reproducible alternative to the THEME software, and the
12
+ analysis companion to the [OpenTag.Studio](https://opentag.studio) tagging tool.
13
+
14
+ ## Two ways to use it
15
+
16
+ - 🚀 **Google Colab — no coding.** [**Open the guided notebook in Colab**](https://colab.research.google.com/github/ajcallaway/TPattern/blob/main/colab/tpattern_guided_analysis.ipynb): upload your events, follow the guided steps, download a report with a plain-English verdict for every pattern and a paste-ready Methods paragraph. See [`colab/`](colab/).
17
+ - 🐍 **Python library.** `pip install` and script it — see [Quickstart](#quickstart) and [`examples/`](examples/).
18
+
19
+ Bring your own data (one row per event — see [`SCHEMA.md`](SCHEMA.md)) or try the
20
+ included [`data/`](data/) example.
21
+
22
+ ## Why it exists
23
+
24
+ T-pattern analysis finds behaviourally meaningful structure that event counts and
25
+ simple transition tables miss. It is most established through Magnusson's THEME
26
+ software, the reference implementation. `tpattern` is an open, complementary
27
+ implementation that reproduces THEME's detection and adds a reproducible chance
28
+ model and clear reporting:
29
+
30
+ - **reproduces** THEME's core detection (critical intervals, Nx/T baseline,
31
+ completeness competition) — validated to the pattern for published datasets;
32
+ - **adds a chance model** — every pattern is tested against a surrogate null, with
33
+ p-values **corrected for repeated testing** (false-discovery rate and
34
+ family-wise);
35
+ - **handles concurrency explicitly** — events sharing a timestamp are treated as
36
+ co-occurrence rather than sequence;
37
+ - **reports clearly** — tidy tables, effect sizes with confidence intervals,
38
+ purposeful figures, and clean **dendrograms** of the detected patterns.
39
+
40
+ ## Installation
41
+
42
+ From source (until published on PyPI):
43
+
44
+ ```bash
45
+ git clone https://github.com/ajcallaway/TPattern.git
46
+ cd tpattern
47
+ pip install -e .
48
+ ```
49
+
50
+ Requires Python ≥ 3.9 (depends on numpy, scipy, matplotlib).
51
+
52
+ ## Quickstart
53
+
54
+ ```python
55
+ from tpattern import read_table, Config, recommend, calibrate, report, methods_text
56
+
57
+ # 1. Load your events — one row per event (observation, event, start).
58
+ # Pass time_unit="ms" if your times are in milliseconds (default is seconds).
59
+ # See SCHEMA.md for the (simple) required format.
60
+ observations = read_table("events.csv", time_unit="s")
61
+
62
+ # 2. Let the tool inspect the data and recommend the method — which surrogate
63
+ # null, whether to require a genuine lag, which error control — with reasons.
64
+ print(recommend(observations))
65
+
66
+ # 3. Detect and calibrate in one call: every pattern is tested against a
67
+ # surrogate null with correction for repeated testing.
68
+ # (calibrate() runs the detection for you — you don't call detect() first.)
69
+ result = calibrate(observations, Config(), null="profile", B=200, q_target=0.05)
70
+ survivors = result.kept("fdr") # patterns that survive false-discovery control
71
+
72
+ # 4. Report: a tidy table (with q-values), pattern dendrograms and a summary —
73
+ # plus a paste-ready Methods paragraph stating every setting and what it does.
74
+ report(result, "report_out", title="My analysis")
75
+ print(methods_text(Config(), observations=observations, calibration=result))
76
+ ```
77
+
78
+ Two runnable examples:
79
+
80
+ ```bash
81
+ pip install -e .
82
+ python examples/quickstart.py # no data needed (synthetic ground-truth demo)
83
+ python examples/reproduce_worldcup.py # a real read_table + full analysis on shipped data
84
+ ```
85
+
86
+ Just want the detected patterns without calibration? `Engine(observations,
87
+ Config()).detect()` returns them directly.
88
+
89
+ **Comparing two groups** (winner vs loser, goal vs non-goal, condition A vs B)?
90
+ `group_contrast(observations, group_of)` tests each pattern's occurrence between
91
+ the groups (Fisher's exact test, odds ratio + 95% CI) and feeds `forest_plot`.
92
+
93
+ ## The workflow
94
+
95
+ ```
96
+ tag events export analyse report
97
+ (OpenTag / Sportscode) ──────▶ standard ──▶ tpattern ──▶ tables · figures · dendrograms
98
+ table (detect · validate ·
99
+ calibrate)
100
+ ```
101
+
102
+ 1. **Tag** your video/events in any coding tool.
103
+ 2. **Export** to the standard input structure — one row per event
104
+ (`observation, event, start`, plus optional `end`, descriptor and group
105
+ columns). See [`SCHEMA.md`](SCHEMA.md).
106
+ 3. **Set up your coding** so the export is analysis-ready — one event per action,
107
+ explicit outcomes, unit boundaries. See [`CODING_GUIDE.md`](CODING_GUIDE.md).
108
+ 4. **Detect** T-patterns, **validate** them against a surrogate null with corrected
109
+ significance (`recommend` advises the settings; `calibrate` runs it), and
110
+ **report** (tables, dendrograms, and a `methods_text` Methods paragraph).
111
+
112
+ ## Relationship to OpenTag.Studio
113
+
114
+ [OpenTag.Studio](https://opentag.studio) is a free, browser-based sports video
115
+ tagging tool. It is the natural front end for `tpattern`: tag your footage, export
116
+ a session, and analyse it here — no proprietary software anywhere in the pipeline.
117
+ OpenTag's **THEME & GSEQ setup guide** covers how to design a coding window so the
118
+ export is analysis-ready, and that guidance applies directly to `tpattern`
119
+ (`CODING_GUIDE.md` is the short bridge). Together they form a complete, open
120
+ tag → analysis pipeline.
121
+
122
+ ## Reproducing the published results
123
+
124
+ The derived 2022 World Cup event sequences ship in [`data/`](data/), so the
125
+ published analyses reproduce from this repository alone:
126
+
127
+ ```bash
128
+ python examples/reproduce_worldcup.py # concordance, exclusion, concurrency (~5 s)
129
+ python examples/reproduce_worldcup.py --calibrate # also the surrogate null (~10 min)
130
+ ```
131
+
132
+ This regenerates the tpattern-vs-THEME pattern counts by level, the interception
133
+ exclusion divergence, the co-occurrence-vs-sequence analysis, and how many patterns
134
+ survive the surrogate null. All seeded and deterministic. See
135
+ [`data/README.md`](data/README.md) for provenance and how the data was derived.
136
+
137
+ ## Documentation
138
+
139
+ - [`SCHEMA.md`](SCHEMA.md) — the input data structure (what your export must contain).
140
+ - [`CODING_GUIDE.md`](CODING_GUIDE.md) — how to set up a coding window to produce it.
141
+ - [`data/`](data/) — the derived World Cup sequences, in the canonical format.
142
+ - [`examples/`](examples/) — a runnable quickstart and the reproduction script.
143
+
144
+ ## Tests
145
+
146
+ ```bash
147
+ pip install -e .[test]
148
+ pytest -q
149
+ ```
150
+
151
+ Both the synthetic ground-truth tests and the THEME reproduction tests run
152
+ everywhere — the latter check the detector still reproduces THEME's published
153
+ occurrence counts to the exact N.
154
+
155
+ ## Citation
156
+
157
+ If you use `tpattern` in research, please cite the software (a machine-readable
158
+ version is in [`CITATION.cff`](CITATION.cff)):
159
+
160
+ Callaway, A. (2026). *tpattern: open, auditable temporal & sequential analysis for
161
+ sport and behaviour* [Computer software]. Zenodo.
162
+ https://doi.org/10.5281/zenodo.21397543
163
+
164
+ This DOI is permanent and always resolves to the current release. To pin a specific
165
+ version instead, use that release's own DOI, listed on the Zenodo record.
166
+
167
+ And the method:
168
+
169
+ Magnusson, M. S. (2000). Discovering hidden time patterns in behavior: T-patterns
170
+ and their detection. *Behavior Research Methods, Instruments, & Computers*, 32(1),
171
+ 93–110. https://doi.org/10.3758/BF03200792
172
+
173
+ ## License
174
+
175
+ MIT — see [`LICENSE`](LICENSE).
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tpattern"
7
+ version = "0.1.0"
8
+ description = "Open, auditable temporal & sequential pattern analysis (T-patterns) for sport and behaviour"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Andrew Callaway", email = "acallaway@bournemouth.ac.uk" },
14
+ ]
15
+ keywords = ["t-pattern", "THEME", "temporal", "sequence", "sport", "behaviour", "notation analysis"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Operating System :: OS Independent",
25
+ "Intended Audience :: Science/Research",
26
+ "Topic :: Scientific/Engineering",
27
+ "Topic :: Scientific/Engineering :: Information Analysis",
28
+ ]
29
+ dependencies = [
30
+ "numpy>=1.21",
31
+ "scipy>=1.7",
32
+ "matplotlib>=3.4",
33
+ ]
34
+
35
+ [project.optional-dependencies]
36
+ test = ["pytest>=7"]
37
+ gui = ["ipywidgets>=8", "pandas>=1.3", "openpyxl>=3"]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/ajcallaway/TPattern"
41
+ Repository = "https://github.com/ajcallaway/TPattern"
42
+ "Archive (DOI)" = "https://doi.org/10.5281/zenodo.21397543"
43
+ "Tagging tool (OpenTag)" = "https://opentag.studio"
44
+
45
+ [tool.setuptools]
46
+ packages = ["tpattern"]
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ """Regression guards for the calibrated pipeline (panel Review-Extras §C):
2
+ the headline q-value the validation paper turns on, detection determinism, and
3
+ the occurrence-equivalence invariant. Nothing else pins these."""
4
+ import sys
5
+ from pathlib import Path
6
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
7
+ from tpattern import read_table, Config, Engine, calibrate # noqa: E402
8
+ from tpattern.synthetic import SyntheticSpec, make_dataset # noqa: E402
9
+
10
+ DATA = Path(__file__).resolve().parents[1] / "data"
11
+
12
+ def test_headline_calibrated_number_is_pinned():
13
+ obs = read_table(DATA / "worldcup_goals.csv", obs_start="obs_start",
14
+ obs_end="obs_end", time_unit="ms")
15
+ r = calibrate(obs, Config(min_lag=1), null="profile", B=2000, seed=20260714)
16
+ kept = r.kept("fdr")
17
+ assert len(kept) == 1
18
+ c = kept[0]
19
+ assert c.pattern.signature() == "(Cross_Incomplete_NoPressure Shot_Goal_Pressure)"
20
+ assert c.N == 14
21
+ assert abs(c.fdr_q - 0.006) < 1e-3 # MC p is deterministic at fixed seed
22
+
23
+ def test_detection_is_deterministic():
24
+ obs, _ = make_dataset(SyntheticSpec(seed=7))
25
+ a = {p.signature(): p.N for p in Engine(obs, Config()).detect()}
26
+ b = {p.signature(): p.N for p in Engine(obs, Config()).detect()}
27
+ assert a == b
28
+
29
+ def test_no_occurrence_duplicates_after_collapse():
30
+ obs = read_table(DATA / "worldcup_nongoals.csv", obs_start="obs_start",
31
+ obs_end="obs_end", time_unit="ms")
32
+ pats = [p for p in Engine(obs, Config()).detect() if p.level >= 1]
33
+ keys = [frozenset((i.obs, tuple(sorted(i.tokens))) for i in p.instances) for p in pats]
34
+ assert len(keys) == len(set(keys)) # one representative per occurrence set
@@ -0,0 +1,40 @@
1
+ """group_contrast must recover a pattern planted more heavily in one group, with
2
+ an odds ratio > 1 and a small p-value (ground truth)."""
3
+
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
8
+ from tpattern import group_contrast # noqa: E402
9
+ from tpattern.synthetic import SyntheticSpec, make_dataset # noqa: E402
10
+
11
+
12
+ def _labelled_sample():
13
+ hi, _ = make_dataset(SyntheticSpec(n_seq=120, planted=[("A", 0), ("B", 1500)],
14
+ plant_fraction=0.40, seed=1))
15
+ lo, _ = make_dataset(SyntheticSpec(n_seq=120, planted=[("A", 0), ("B", 1500)],
16
+ plant_fraction=0.05, seed=2))
17
+ for o in hi:
18
+ o.name = "hi_" + o.name
19
+ for o in lo:
20
+ o.name = "lo_" + o.name
21
+ return hi + lo
22
+
23
+
24
+ def test_group_contrast_recovers_planted_difference():
25
+ obs = _labelled_sample()
26
+ rows = group_contrast(obs, group_of=lambda o: "high" if o.name.startswith("hi")
27
+ else "low")
28
+ planted = next(r for r in rows if r["signature"] == "(A B)")
29
+ assert planted["odds_ratio"] > 2.0
30
+ assert planted["p"] < 0.01
31
+ assert planted["more_common_in"] == "high"
32
+
33
+
34
+ def test_group_contrast_requires_two_groups():
35
+ obs = _labelled_sample()
36
+ try:
37
+ group_contrast(obs, group_of=lambda o: "only_one")
38
+ assert False, "should reject a single group"
39
+ except ValueError:
40
+ pass
@@ -0,0 +1,33 @@
1
+ """Occurrence-identical patterns (co-timed reversals, equivalent bracketings) must
2
+ collapse to one representative when collapse_equivalent is on, so the pattern count
3
+ and the multiple-comparison family reflect distinct hypotheses (panel Essential 1)."""
4
+ import sys
5
+ from pathlib import Path
6
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
7
+ from tpattern import read_table, Config, Engine # noqa: E402
8
+
9
+ DATA = Path(__file__).resolve().parents[1] / "data"
10
+
11
+ def _occ_keys(pats):
12
+ return [frozenset((i.obs, tuple(sorted(i.tokens))) for i in p.instances)
13
+ for p in pats if p.level >= 1]
14
+
15
+ def test_collapse_removes_occurrence_identical_duplicates():
16
+ obs = read_table(DATA / "worldcup_nongoals.csv", obs_start="obs_start",
17
+ obs_end="obs_end", time_unit="ms")
18
+ on = [p for p in Engine(obs, Config(min_lag=0, collapse_equivalent=True)).detect()
19
+ if p.level >= 1]
20
+ off = [p for p in Engine(obs, Config(min_lag=0, collapse_equivalent=False)).detect()
21
+ if p.level >= 1]
22
+ keys_on = _occ_keys(on)
23
+ # with collapse on, every composite has a distinct occurrence set
24
+ assert len(keys_on) == len(set(keys_on)), "duplicates survived collapse"
25
+ # collapse actually removed duplicates that existed without it
26
+ assert len(on) < len(off), "expected duplicates to be present without collapse"
27
+
28
+ def test_collapse_does_not_touch_headline():
29
+ obs = read_table(DATA / "worldcup_goals.csv", obs_start="obs_start",
30
+ obs_end="obs_end", time_unit="ms")
31
+ pats = [p for p in Engine(obs, Config(min_lag=1)).detect() if p.level >= 1]
32
+ keys = _occ_keys(pats)
33
+ assert len(keys) == len(set(keys)) # headline path has no duplicates anyway
@@ -0,0 +1,28 @@
1
+ """The guided wizard's engine (run_analysis) must complete end-to-end and write
2
+ the expected outputs. The widget layer (launch) is UI-only and not tested here."""
3
+
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
10
+ from tpattern import run_analysis # noqa: E402
11
+
12
+ DATA = Path(__file__).resolve().parents[1] / "data" / "worldcup_goals.csv"
13
+
14
+ pytestmark = pytest.mark.skipif(not DATA.exists(), reason="shipped data not present")
15
+
16
+
17
+ def test_run_analysis_end_to_end(tmp_path):
18
+ res = run_analysis(str(DATA), obs_start="obs_start", obs_end="obs_end",
19
+ time_unit="ms", null="profile", min_lag=1, B=50,
20
+ outdir=str(tmp_path / "out"), title="test")
21
+ # it read the 163 goal sequences and captured a recommendation
22
+ assert "163 observations" in res.recommendation
23
+ assert res.n_detected > 0
24
+ # it produced a table, a summary and a Methods statement
25
+ assert Path(res.files["table"]).exists()
26
+ assert Path(res.files["summary"]).exists()
27
+ assert "T-pattern detection was performed with tpattern" in res.methods
28
+ assert "min_lag = 1" in res.methods # the chosen setting is documented
@@ -0,0 +1,61 @@
1
+ """
2
+ Reproduction test — the anchor for the paper's reproducibility claim.
3
+
4
+ The detector must reproduce THEME's published occurrence counts for the World Cup
5
+ Goals sample to the exact N. These anchors were read from the audited manuscript.
6
+ The derived data ships in data/, so this runs everywhere (including CI) rather
7
+ than being skipped.
8
+ """
9
+
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ import pytest
14
+
15
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
16
+ from tpattern import read_table, Engine, Config # noqa: E402
17
+
18
+ DATA = Path(__file__).resolve().parents[1] / "data" / "worldcup_goals.csv"
19
+
20
+ # (A, B, expected N) — from the audited World Cup manuscript (Goals sample).
21
+ ANCHORS = [
22
+ ("Challenge_Pressure", "Shot_Goal_Pressure", 21),
23
+ ("Challenge_Pressure", "Pass_Complete_Pressure", 21),
24
+ ("Cross_Incomplete_NoPressure", "Shot_Goal_Pressure", 14),
25
+ ("Cross_Incomplete_NoPressure", "Shot_Goal_NoPressure", 8),
26
+ ("Shot_Goal_Pressure", "Challenge_Pressure", 16),
27
+ ("Shot_Goal_NoPressure", "Challenge_Pressure", 7),
28
+ ]
29
+
30
+
31
+ @pytest.fixture(scope="module")
32
+ def goals_patterns():
33
+ obs = read_table(DATA, obs_start="obs_start", obs_end="obs_end", time_unit="ms")
34
+ assert len(obs) == 163, "expected the 163 goal sequences"
35
+ return {p.signature(): p for p in Engine(obs, Config()).detect()}
36
+
37
+
38
+ @pytest.mark.parametrize("a,b,expected", ANCHORS)
39
+ def test_level1_anchor_counts(goals_patterns, a, b, expected):
40
+ sig = f"({a} {b})"
41
+ assert sig in goals_patterns, f"pattern {a} -> {b} not detected"
42
+ assert goals_patterns[sig].N == expected
43
+
44
+
45
+ def test_univariate_count_matches_theme(goals_patterns):
46
+ # THEME reported 19 univariate (Level 0) event types for Goals.
47
+ l0 = [p for p in goals_patterns.values() if p.level == 0]
48
+ assert len(l0) == 19
49
+
50
+
51
+ def test_key_trivariate_present(goals_patterns):
52
+ sig = "(Cross_Incomplete_NoPressure (Challenge_Pressure Shot_Goal_Pressure))"
53
+ assert sig in goals_patterns
54
+ assert goals_patterns[sig].N == 6
55
+
56
+
57
+ def test_observation_window_is_preserved():
58
+ """The NX/T baseline depends on the observation window, so the shipped data
59
+ must carry it explicitly rather than have it inferred from the events."""
60
+ obs = read_table(DATA, obs_start="obs_start", obs_end="obs_end", time_unit="ms")
61
+ assert sum(max(o.T, 1) for o in obs) == 1064634