Skip to main content

saphes logo

saphes

Readability, lexical diversity, syntactic complexity and loan-word ratio — a small set of metrics, done carefully, with the parameters other implementations hardcode.

saphes — σαφής, "clear, plain, distinct". Aristotle makes clarity the chief virtue of λέξις (style); the other classical axis is ποικιλία, variety. The package began as exactly those two axes — LIX measures clarity, TTR measures variety — and has since grown a third, how hard a sentence is to hold in your head, and a fourth, how much of the vocabulary is still felt as foreign.

Why this exists

textstat, textdescriptives, lexicalrichness and taaled already cover this ground. Two reasons to still build it:

  1. The LIX long-word threshold is hardcoded at 6 everywhere. That 6 comes from Björnsson's Swedish original, and it is wrong for the languages we work on. Hungarian is agglutinative and Ancient Greek heavily inflected, so at threshold 6 nearly every token counts as "long" and the index saturates into a flat line. Measured over the full Hungarian Webcorpus, 44.5% of running tokens are "long" at threshold 6, against 25.7% in Swedish. On real Hungarian prose that pushes LIX to 60.4 — "very difficult" — where the calibrated threshold gives 43.4. Parameterising the threshold is the whole point.
  2. Implementations disagree. They count words, sentences and long words differently, so they rank the same texts differently. So: expose the counts, document every choice, make results auditable.

Non-goal: becoming another kitchen-sink readability library.

Installation

uv add saphes

The core has no dependencies — plain Python and the standard library. Two optional extras stay out of the core path: saphes[snowball] for Hungarian stemming and saphes[punkt] for the NLTK sentence splitter.

Quickstart

from saphes import lix, lexical_diversity

# LIX takes SURFACE FORMS — word length is the signal.
result = lix("The cat sat on it. Complicated sentences generally frighten us.")
result.score                                       # 45.0
result.words, result.sentences, result.long_words  # (10, 2, 4)  -> A, B, C
result.band                                        # 'standard'

# Diversity takes LEMMAS, and `unit` is required — there is no default.
lexical_diversity(lemmas, unit="lemma")

# Comparing texts of different lengths? Use MATTR, not TTR.
lexical_diversity(lemmas, unit="lemma", window=100).mattr

Hungarian needs three things English does not — a retuned threshold, a letter count, and a stand-in for a lemmatiser:

from saphes import hungarian_letter_count, hungarian_stems, lix, recommended_threshold

# `sz` is one letter, not two characters. The threshold is calibrated per policy.
hu = recommended_threshold("hu-letters")          # 8, not Björnsson's Swedish 6
lix(text, length_policy=hungarian_letter_count, long_word_threshold=int(hu))

# No lemmatiser? Snowball is the fallback, declared as its own unit.
lexical_diversity(hungarian_stems(tokens), unit="stem")

Syntactic complexity, from any parser

from saphes import from_conllu, mean_dependency_distance, mean_hierarchical_distance

parses = from_conllu(open("corpus.conllu").read())
mdd = mean_dependency_distance(parses, parser="emtsv tok-dep-conll")
mhd = mean_hierarchical_distance(parses, parser="emtsv tok-dep-conll")
print(mdd.mdd, mhd.mhd)

from_spacy takes a HuSpaCy or spaCy Doc and imports neither. Record parser=: a parser carries the annotation convention of the treebank it was trained on, and that convention decides which word is the head — which is the whole input to a distance measure.

Loan words

from pathlib import Path
from saphes import loanword_ratio

lexicon = set(Path("idegenszavak.txt").read_text(encoding="utf-8").split())
print(loanword_ratio(lemmas, lexicon=lexicon).ratio)

saphes ships no lexicon in the package; lexicon is required. A 15,203-lemma Hungarian list lives in experiments/loanwords/results/.

The data contract

Each metric wants a different input, and two of them want opposites.

Metric Wants Because
lix, rix surface forms Word length is the signal. házakban is 8 characters; its lemma ház is 3.
lexical_diversity lemmas Surface variation is noise — it measures morphology, not vocabulary. Hungarian ház / házak / házban / házakat is four types and one lemma.
loanword_ratio lemmas Morphology buries the root. komputerekkel misses a lexicon holding komputer.
mean_dependency_distance, mean_hierarchical_distance a parse Head indices. No tokeniser produces them; they come from a parser, via saphes.adapters.

Feed the same list to the first two and exactly one is silently wrong — no error, no NaN, just a plausible number. unit is required, the parameter names differ (words= against lemmas= against parses=), a raw string is refused where it could only be wrong, and every result records what it measured.

saphes consumes lemmas; it does not produce them. Lemmatisation is language-specific and heavy — CLTK or a treebank for Greek, huspacy for Hungarian. The caller lemmatises; saphes measures.

The one exception is optional and honest about itself: saphes[snowball] gives Hungarian Snowball stemming for callers with no lemmatiser. Its output is declared as unit="stem", a third stream with its own name, because a stemmer both over- and under-merges and a stem-based number is comparable only to another from the same stemmer.

Documentation

saphes.readthedocs.io, organised by Diátaxis:

  • Tutorial — new here? Measure your first text in about ten minutes, with nothing to download.
  • How-to guides — measure Hungarian text, pass a HuSpaCy doc or emtsv stream, customise the loan-word lexicon, supply a sentence count, count letters rather than characters, stem without a lemmatiser, calibrate a threshold.
  • Reference — every function, its contract and its failure modes, plus the calibration data.
  • Explanation — why the metrics need different input, what dependency distance measures, why loan words need lemmas, why the threshold has to move, and why implementations disagree.

Every code block in the docs is executed by CI, so nothing there can drift.

Roadmap

  • LIX with a parameterised long-word threshold
  • TTR and MATTR with a required, recorded token unit
  • RIX (long words per sentence)
  • Empirically calibrated per-language thresholds, from token-weighted word-length distributions — Hungarian ships as recommended_threshold("hu")
  • Phonotactically aware Hungarian letter counting — sz is one letter, ssz is two, and morpheme boundaries are handled by rule plus an attested table
  • Optional Snowball stemming for callers with no lemmatiser, as a declared third unit
  • Mean dependency distance and mean hierarchical distance, from any parser's output
  • Zero-dependency adapters for HuSpaCy/spaCy and CoNLL-U/emtsv
  • Loan-word ratio (idegenszó-arány) against a caller-supplied lexicon
  • A public-domain Hungarian loan-word lexicon to ship with it
  • The same study for Ancient Greek, for the Homer project
  • POS-filtered diversity, once lemmas carry tags
  • MTLD, HD-D, vocd-D, Maas

Research

  • Diachronic analysis: test MDD and loan word density changes on the ParlaMonitor corpus across time
  • Data diversity benchmarking: benchmark MDD and idegenszó-arány across diverse Hungarian registers (Webcorpus, legal texts, literary prose, social media)
  • Multilingual scaling: test the agnostic MDD calculation engine on Universal Dependencies corpora across multiple languages
  • Dependency motifs: chain motif identification for advanced cognitive load mapping, extending the MDD/MHD pair (Jing & Liu 2017)

Maintenance

  • Logo and README banner
  • Mutation-testing baseline

Explicitly out of scope: Flesch, Kincaid, SMOG and relatives. They need syllabification, which is language-specific and a different project. LIX was chosen precisely because it needs only word length and sentence count, so it travels across languages.

Made by

saphes is made by Crow Intelligence.

License

MIT

Release files for saphes 0.2.1

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

Source distribution (sdist)

Source distribution for saphes 0.2.1
File Size Uploaded
saphes-0.2.1.tar.gz 66.8 kB Details

Built distribution (wheel)

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

Total release size: 143.7 kB

Release files / saphes-0.2.1.tar.gz

Download URL saphes-0.2.1.tar.gz
Size 66.8 kB
Tags Source
SHA-256 checksum
How to use checksums
898b0e933f1536d4c357715c29bc36264f8984885812cc1313c66d620f45f4d0
BLAKE2b-256 checksum
How to use checksums
cbc731759edb1d6911c49ddfe9f7b89821577255a479af16e4aff546d24af87f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release files / saphes-0.2.1-py3-none-any.whl

Download URL saphes-0.2.1-py3-none-any.whl
Size 76.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8ffea86c5aef22d11dde83ac8edce796eb2e50f487d085328ac8e845e8155b9e
BLAKE2b-256 checksum
How to use checksums
830c95927528aba5a26b94919b3feeb70364e946d01750fc32aa0712e2887f13
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

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