grammar-inference-engine/docs/adr/0004-frequency-filter-with-min-coverage.md
tobjend e23922a1b7
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat: adaptive multi-assignment clustering; add ADRs 1-10
- Multi-assignment clustering (no greedy 'used' set)
- Adaptive ngram fallback (shrink when (other) > 60%)
- Add docs/adr/ with 10 architecture decision records
- Fix ADR 1 (query modification description)
- Fix ADR 3 (multi-assignment + adaptive shrink)
- Fix ADR 5 (import sort order clarification)
- Fix ADR 6 (remove Kotlin call_suffix references)
- New ADR 9 (adaptive clustering rationale)
- New ADR 10 (universal package mapping via relpath)
2026-07-03 22:58:09 +02:00

2.3 KiB

4. Frequency filter with min_coverage threshold

Date: 2026-07-03

Status: Accepted

Context

A raw method sequence can contain hundreds of unique call tokens, many of which appear in only 1-2 methods. These rare symbols are noise — they inflate grammar size, confuse the inference algorithm, and dilute the signal of common conventions.

We need a principled way to discard rare symbols while keeping the behavioral patterns that define the codebase.

Decision

Apply a frequency filter before clustering: remove any symbol that appears in fewer than min_coverage fraction of method sequences.

Default threshold: 0.2 (20% of methods must contain the symbol).

Filtering is done by frequency_filter() in analyze.py:

n_files = len(sequences)
threshold = max(1, int(n_files * min_coverage))
symbol_file_count = Counter()
for seq in sequences:
    seen = set()
    for _, text, _ in seq:
        symbol_file_count[text] += 1 if text not in seen else 0
        seen.add(text)
keep = {text for text, count in symbol_file_count.items() if count >= threshold}

A symbol is counted once per file (not once per occurrence) to avoid skew from files that repeat the same symbol many times.

Consequences

Positive:

  • Removes noise before clustering, improving cluster quality.
  • Prevents rare one-off function calls from creating spurious n-gram matches.
  • The common vs other cluster distinction is sharper because the filter removes tokens that would appear in neither.

Negative:

  • With large codebases (600+ methods), 20% threshold may be too aggressive — a symbol needs 120+ occurrences to survive.
    • Mitigation: --min-coverage flag lets users tune per codebase.
    • Production code often needs lower values (0.05) because methods are more diverse than tests.
  • The threshold is relative, not absolute. A 3-file project keeps anything in 1+ files (max(1, 3*0.2) = 1).

Alternatives Considered

  • No filter: CRX produces (a+b+c+d+e+f+g+h+i+j+...)+ — the vocabulary is too large to be informative.
  • Absolute threshold: min_occurrences=5. Doesn't scale — works for small projects, wrong for large ones.
  • TF-IDF style weighting: More sophisticated but adds complexity. The simple coverage filter works well in practice.