Skip to main content

Mine recurrent surface text patterns in a corpus (built on generalized suffix trees)

Project description

patternminer: Mining Recurrent Text Patterns in a Corpus

PyPI version CI

patternminer makes it easy to mine recurrent surface text patterns in a corpus of texts: which token subsequences recur, where, how often, and how they nest inside each other.

It is built on pygstlib (generalized suffix trees — mining is linear-time) and generalizes the pattern-mining approach of dialign / pydialign beyond dyadic dialogue.

Features:

  • cross-document mining: patterns present in ≥ k documents, with every occurrence located as (doc_id, unit_id, start) and both absolute (doc_freq, unit_freq) and relative (doc_support, unit_support) frequencies;
  • within-document mining: patterns a document repeats across its own units (sentences, lines, utterances…);
  • hierarchy: regroup subpatterns under their parent patterns (e.g. parent hello world ! groups hello and world !);
  • filtering: by size, frequency, arbitrary predicates, or down to the maximal patterns;
  • text in, patterns out: built-in basic tokenizers and unit splitters — or bring your own tokens;
  • pandas layer (optional): patterns and occurrences as DataFrames.

Installation

uv add patternminer               # or: pip install patternminer
uv add 'patternminer[analysis]'   # with the pandas layer

Requires Python ≥ 3.10. The only required runtime dependency is pygstlib.

Quickstart

A corpus is a list of documents; each document is a list of units (the granularity at which repetition is observed — by default, one unit per line); each unit is a tuple of string tokens.

from patternminer import Corpus, mine_corpus

corpus = Corpus.from_texts([
    "hello world !\nhow are you today ?",
    "hello world ! how are you doing ?",
])
patterns = mine_corpus(corpus)  # patterns shared by >= 2 documents

for p in patterns:
    print(f"{p.surface!r}: doc_freq={p.doc_freq}, occurrences={list(p.occurrences)}")

print()
print(patterns.hierarchy().render())

Output:

'hello world !': doc_freq=2, occurrences=[(0, 0, 0), (1, 0, 0)]
'how are you': doc_freq=2, occurrences=[(0, 1, 0), (1, 0, 3)]
'?': doc_freq=2, occurrences=[(0, 1, 4), (1, 0, 7)]

hello world !  [doc_freq=2, unit_freq=2]
how are you  [doc_freq=2, unit_freq=2]
?  [doc_freq=2, unit_freq=2]

Subpatterns that never occur independently (are you, world !, you, ! — always enclosed in hello world ! / how are you) are discarded; they would appear if they occurred free somewhere in the corpus.

Tour of the API

from patternminer import Corpus, mine_corpus, mine_document, mine_within, nlp

# Corpus building — raw text with basic helpers…
corpus = Corpus.from_texts(texts, tokenizer=nlp.word_punct_tokenizer,
                           unit_splitter=nlp.split_sentences, lowercase=True)
corpus = Corpus.from_files(paths)
# …or pre-tokenized input:
corpus = Corpus.from_token_lists(list_of_token_lists)   # 1 unit per document
corpus = Corpus.from_token_units(docs_units_tokens)     # nested

# Mining — threshold by absolute freq or relative support (or both)
patterns = mine_corpus(corpus, min_doc_freq=2, is_valid=nlp.has_alphabetic)
patterns = mine_corpus(corpus, min_doc_support=0.5)   # >= half the documents
repetitions = mine_document(corpus[0])       # within one document
per_doc = mine_within(corpus, min_unit_support=0.1)   # doc_id -> PatternSet

# Pattern: frequencies, supports, free/constrained classification
p = patterns.get("world !")
p.doc_freq; p.unit_freq    # absolute counts (documents / units)
p.doc_support              # doc_freq / total documents in the corpus
p.unit_support             # unit_freq / total units in the corpus
p.occurrences              # every occurrence, (doc_id, unit_id, start)
p.free_occurrences         # the subset not enclosed in a larger pattern
p.constrained_occurrences  # the enclosed complement

# PatternSet
patterns.filter(min_size=2, min_unit_freq=3, min_doc_support=0.5, ...)
patterns.maximal()          # patterns contained in no other
patterns.get("world !")     # lookup by surface or token tuple; None if the
                            # key is absent (e.g. a pattern that never
                            # occurs free — see caveats below)
patterns.to_dataframe()     # pandas ([analysis] extra)

# Hierarchy (containment DAG)
hier = patterns.hierarchy()
hier.roots; hier.children(p); hier.parents(p); hier.descendants(p)
print(hier.render())

➡ Tutorials: the quickstart notebook and a real corpus study on three chapters of David Copperfield.

Semantics & caveats

  • Free patterns only. The inventory holds the right-maximal repeats that occur free at least once; a pattern whose every occurrence is enclosed in a free occurrence of a larger pattern is discarded (e.g. are you is dropped when it only ever appears inside how are you). Kept patterns still report all their occurrences, enclosed instances included; Pattern.free_occurrences / constrained_occurrences expose the per-occurrence split. Details in docs/architecture.md.
  • Repetition is counted across units. A pattern occurring twice inside a single unit (and nowhere else) is not detected. Choose the unit granularity accordingly (nlp.split_sentences, nlp.split_lines, …).
  • Surface-level. patternminer sees exactly the tokens you give it — normalize (case, tokenization) upstream, or use the lowercase=True and tokenizer options. The built-in tokenizers are deliberately simple.
  • Determinism. Results come in a canonical order (size desc, then tokens) and are identical across processes.
  • Concurrency. Like pygstlib, mining structures are not thread-safe; the returned Pattern/PatternSet/PatternHierarchy objects are immutable and safe to share once built.

Development

uv venv && uv pip install -e '.[dev]' --group notebook
uv run pytest          # unit, property, randomized naive-reference and notebook tests
uv run ruff check .

The project is managed BMAD-style: see docs/prd.md, docs/architecture.md and the story backlog in docs/stories/.

Contributors

  • Guillaume Dubuisson Duplessis (2026)

Usage for Research Purposes

If you use this library for research purposes, please make reference to it by citing the following paper on recurrent surface text patterns:

  • Dubuisson Duplessis, G.; Charras, F.; Letard, V.; Ligozat, A.-L.; Rosset, S., Utterance Retrieval based on Recurrent Surface Text Patterns, 39th European Conference on Information Retrieval (ECIR), 2017, pp. 199--211 [More DOI]

See also dialign and pydialign for the dialogue-specific measures built on the same mining approach.

License

MIT — see the LICENSE.txt file.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

patternminer-0.1.0.tar.gz (31.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

patternminer-0.1.0-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

File details

Details for the file patternminer-0.1.0.tar.gz.

File metadata

  • Download URL: patternminer-0.1.0.tar.gz
  • Upload date:
  • Size: 31.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for patternminer-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c10dde957de7ebe11b98a72ecfd3bab259d27e4d9eaa46c66349dd9bb57e5198
MD5 cf7ab91dad3f421daec64b1762ef9410
BLAKE2b-256 0acec92f7fc90f6fe2a57bd1421ffcf74889873f545061d7184e75ba129371f9

See more details on using hashes here.

Provenance

The following attestation bundles were made for patternminer-0.1.0.tar.gz:

Publisher: release.yml on GuillaumeDD/patternminer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file patternminer-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: patternminer-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for patternminer-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3112906cccea6fb84116e98e69918099ebb43e7c7b33f5a7b43faa859bc9fe73
MD5 81af88f45d045d6fa80faa71267345aa
BLAKE2b-256 6dd5ebee50001a6692f31d9a908b02ed68ce7f65fbc0e595207693df843b42b8

See more details on using hashes here.

Provenance

The following attestation bundles were made for patternminer-0.1.0-py3-none-any.whl:

Publisher: release.yml on GuillaumeDD/patternminer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page