TextDescriptives: spaCy Pipeline Components for Readability, Coherence and Descriptive Statistics
A Python library for calculating a large variety of metrics from text
At a glance
- What is it?
- TextDescriptives is a Python library that adds spaCy pipeline components for readability scores, dependency distance, part-of-speech proportions, information theory and semantic coherence. It is convenient if you already run spaCy, and it is not a standalone text statistics engine.
- Who is it for?
- Adopt TextDescriptives if your pipeline is already spaCy-based and you want readability scores, dependency distance, POS proportions and coherence attached to a Doc rather than reimplemented. Do not adopt it if you need a language spaCy does not cover well, or if you cannot accept the pandas and numpy pins in pyproject.toml.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 159 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap TextDescriptives fills between raw spaCy output and a metrics table
spaCy gives you tokens, lemmas, POS tags, dependency parses and sentence boundaries. It does not give you a Flesch reading ease score, a token length distribution, a measure of how far dependency arcs reach, or a coherence number between consecutive sentences. TextDescriptives packages those calculations as spaCy pipeline components and extensions, so the metrics travel with the Doc object instead of living in a separate script.
The audience is fairly narrow and that is a feature. If you are doing corpus linguistics, readability research, or text quality filtering for a training set, and your stack is already spaCy, this library removes a lot of glue code. If you are not using spaCy, adopting TextDescriptives means adopting spaCy first, including its model downloads and its per-language coverage. The README does not present an alternative path.
How the components work: extract_metrics, pipeline components and Doc extensions
There are two entry points, and they behave differently. The functional one is td.extract_metrics, which takes raw text, resolves a spaCy model (either the one you name with spacy_model or one it auto-downloads based on lang), runs the pipeline, and returns a pandas DataFrame with one row per document. The README states that if lang is set, spacy_model is not necessary and vice versa.
The other entry point is the spaCy component route. You load a model yourself, call nlp.add_pipe with a component name prefixed by textdescriptives/, and then read results off the Doc through extensions such as doc._.readability and doc._.token_length. The shorthand textdescriptives/all registers every component at once. The available components are descriptive_stats, readability, dependency_distance, pos_proportions, coherence, and quality, plus the aggregate names all and information_theory in the valid metrics list.
That split matters for cost. extract_metrics is convenient but hides a model download and a full pipeline run per call, which is fine for a script and awkward inside a loop over millions of documents. The component route lets you add TextDescriptives to a pipeline you already run once, so the marginal cost is the metric computation itself. The README does not document batching behaviour or memory use for either path.
Installing TextDescriptives and running a first extraction
Installation is a single pip command. The library requires Python 3.9 or newer according to pyproject.toml, and it depends on spacy[lookups], numpy, pandas, pyphen, ftfy and pydantic. Note the numpy pin: numpy>=1.20.0,<2.0.0, with a comment pointing at a dtype size incompatibility. If your environment is on numpy 2.x, pip will resolve backwards and you may get a downgrade you did not plan for.
pip install textdescriptivesBefore extracting anything, you can list the metric groups the library exposes. The README shows this call returning a set containing quality, readability, all, descriptive_stats, dependency_distance, pos_proportions, information_theory and coherence.
import textdescriptives as td
td.get_valid_metrics()The quickest real use is extract_metrics on a string. Passing lang="en" and metrics=None makes the library download an English model and compute every metric. The README states that the relevant model for English is en_core_web_lg.
import textdescriptives as td
text = "The world is changed. I feel it in the water. I feel it in the earth."
df = td.extract_metrics(text=text, lang="en", metrics=None)If you want to control the model and the metric set, pass both explicitly. The README gives this exact combination for readability and coherence.
import textdescriptives as td
df = td.extract_metrics(text=text, spacy_model="en_core_web_lg", metrics=["readability", "coherence"])The DataFrame you get back is wide. The README's example row includes columns such as flesch_reading_ease, flesch_kincaid_grade, smog, gunning_fog, automated_readability_index, coleman_liau_index, lix, rix, dependency_distance_mean, dependency_distance_std, prop_adjacent_dependency_relation_mean, token_length_mean, sentence_length_mean, syllables_per_token_mean, n_tokens, n_unique_tokens, n_characters and n_sentences, alongside the coherence and POS proportion columns. Expect to select from it rather than consume it whole.
The spaCy component route, and why it is the better fit for a real pipeline
If you already have a model loaded, adding the components avoids a second pipeline run. The README's example loads en_core_web_sm (which you must install yourself, for instance with python -m spacy download en_core_web_sm) and registers the aggregate component.
import spacy
import textdescriptives as td
nlp = spacy.load("en_core_web_sm")
nlp.add_pipe("textdescriptives/all")
doc = nlp("The world is changed. I feel it in the water.")
doc._.readability
doc._.token_lengthThe README also documents two convenience functions for moving from a Doc to tabular output: td.extract_dict(doc) and td.extract_df(doc). Those are the right call when you have already paid for the parse and just want the metrics.
One thing to watch: the component names are registered under the textdescriptives/ prefix, and the README uses the plain string textdescriptives/all. The other components follow the same pattern, for example textdescriptives/readability. If you typo the prefix, spaCy will fail at add_pipe time rather than silently skipping the component.
Where TextDescriptives is the wrong tool
The library is a spaCy extension, so its coverage is spaCy's coverage. Anything spaCy does not parse well, TextDescriptives will not measure well either. The README does not claim language-independent behaviour, and the auto-download logic depends on a spaCy model existing for the lang you pass. For a language without a maintained spaCy pipeline, extract_metrics has nothing to fall back on.
The dependency pin is a second practical limit. numpy is constrained below 2.0.0 and the comment in pyproject.toml ties that to a dtype size incompatibility. A project already standardised on numpy 2.x cannot add TextDescriptives without either isolating it in its own environment or accepting a downgrade. That is a real integration cost, not a hypothetical one.
Readability formulas are the third limit, and it is a conceptual one rather than a bug. Scores like smog, gunning_fog and coleman_liau_index were designed for prose and are sensitive to sentence length and syllable counts. Running them over code, chat logs, or short fragments produces numbers that are arithmetically valid and interpretively meaningless. TextDescriptives will happily compute them; deciding whether they mean anything is your job. The library also exposes a quality component with a passed_quality_check column, but the README does not spell out the thresholds behind it, so treat that flag as a signal to inspect rather than a verdict.
How it compares with writing the metrics yourself or using a dedicated readability package
The obvious alternative is to compute the same numbers directly: count tokens and sentences from a spaCy Doc, count syllables with pyphen, and apply the readability formulas by hand. That is genuinely not much code for Flesch reading ease or a mean token length. What you would have to reimplement is the rest of the surface: dependency distance statistics, the proportion of adjacent dependency relations, POS proportions, the duplicate n-gram fractions, the information theory metrics, and the coherence component, which the release notes describe as calculating semantic coherence between sentences.
A second alternative is a standalone readability library that does not depend on spaCy. That avoids the model download and the numpy constraint, and it is the right choice if readability scores are all you need and you have no parse. The difference in approach is that a standalone readability package tokenises and counts, while TextDescriptives reads from an existing syntactic analysis. If your text is not well formed, or you need metrics that depend on syntax, the parse-first approach is the reason to pick this library. If you only need Flesch and a word count, it is overhead you do not need.
Maintenance, licence and the cost of staying current
The repository is not archived, and the last push was on 2026-05-05. The most recent release listed is v2.8.4 from 2024-12-16, preceded by v2.8.3 a day earlier and v2.8.2 in May 2024, so the release cadence has been uneven: a burst at the end of 2024, then repository activity without a tagged release in the release history provided. Version 2.0 was the breaking change, replacing the old component naming with the textdescriptives/{metric_name} convention and adding the coherence component, so anyone upgrading from 1.x should read the changelog before moving.
The licence is Apache-2.0, which is permissive and includes an explicit patent grant. For most internal analytics use that raises no questions. If you redistribute the library inside a product, the usual Apache obligations apply: keep the licence and notices, and state significant changes. That is a summary of the licence text, not legal advice; check the LICENSE file and your own counsel.
Upgrade cost is dominated by two things. The numpy<2.0.0 pin means an upgrade of numpy elsewhere in your stack can block a TextDescriptives upgrade or force a coordinated change. And because the metrics are attached to Doc extensions, a change in column naming between versions can break downstream code that indexes the DataFrame by column name. The README's example output shows column names like duplicate_5-gram_chr_fraction and top_2-gram_chr_fraction, which are exactly the kind of generated names that shift between versions.
Editorial conclusion
Adopt TextDescriptives if your pipeline is already spaCy-based and you want readability scores, dependency distance, POS proportions and coherence attached to a Doc rather than reimplemented. Do not adopt it if you need a language spaCy does not cover well, or if you cannot accept the pandas and numpy pins in pyproject.toml. Before committing, check on your own corpus that the auto-downloaded model for your language is the one you want, since extract_metrics will pick it when spacy_model is omitted, and confirm that readability formulas such as smog and gunning_fog are meaningful for your text type.
Frequently asked questions
How do I install TextDescriptives?
Install it with pip install textdescriptives. It requires Python 3.9 or newer and pulls in spaCy, numpy, pandas, pyphen, ftfy and pydantic as dependencies.
Which spaCy model does TextDescriptives need?
Either pass spacy_model explicitly, or pass lang and let the library auto-download a model for that language. The README notes that if lang is set, spacy_model is not necessary, and vice versa.
How do I get the TextDescriptives metrics as a pandas DataFrame?
td.extract_metrics returns a DataFrame directly when you pass raw text. If you already have a spaCy Doc, use td.extract_df(doc) or td.extract_dict(doc).
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/hlasse-textdescriptives)