Skip to content

Style metrics

StyleStats(source, words_extractor=None, stopwords=None, top_n=10, cliches=None, nlp=None, nausea_stopwords=None)

The style metrics of a text: the SEO metrics of Advego and Text.ru and the markers of the officialese style. The services publish no formulas, so the common definitions are taken. The data source is a text or a Doc object of spaCy.

The SEO metrics are counted on the words of the word extractor, in lower case by default, or on the tokens of a Doc; an extractor passed explicitly is used on the text of a Doc. The markers are counted on the word forms in lower case (forms), whatever the extractor.

Raises:

  • SourceTypeError - a source that is neither a string nor a Doc, an extractor of another type, a list that is not a list of strings;
  • SourceError - a source without words;
  • ParameterError - a top_n that is not an integer of at least one.

The class extends the StyleStats of the anyTS core with the hooks of Spanish, given in the cards of the metrics: the stopwords, the compound prepositions, parenthetical expressions and clichés that the Spanish-language guides to plain language warn about, found with their contractions and the forms of their verbs, and the nouns derived from a verb among the nouns of the spaCy model. The words of a string are extracted by the Spanish WordsExtractor and are not lemmatized, so the forms of one word count as different words, as in Advego; to compute by lemmas, pass a WordsExtractor with use_lexemes=True and lowercase=True. The print_stats table takes the descriptions of ests.constants.STYLE_STATS_DESC.

The verbal nouns need the parts of speech and the lemmas: a Doc gives its own annotation, also when an extractor takes its text, and a string is parsed with es_core_news_sm or with the pipeline passed in nlp the first time verbal_nouns or markers() is read - the other metrics of a string need no model. A string longer than the max_length of the pipeline is parsed in parts of whole sentences. Besides the errors above, a pipeline that is not a spaCy Language raises SourceTypeError and a string with a character without a UTF-8 form raises SourceError from the Spanish tokenizer; reading verbal_nouns or markers() raises SourceError for a Doc without the parts of speech or the lemmas and for a string with a sentence longer than the max_length of the pipeline.

The examples of this page take a notice written in the administrative style:

from ests import StyleStats

official = (
    "En el marco del procedimiento de referencia, y a efectos de dar cumplimiento a lo "
    "dispuesto en la normativa vigente, se procedió a la revisión de la documentación "
    "presentada. No obstante, en virtud de la resolución de la comisión, se llevará a cabo "
    "la notificación de la misma a la mayor brevedad, en tiempo y forma."
)
ss = StyleStats(official)
ss.print_stats()
                  Metric                  |  Value
-----------------------------------------------------
Classic nausea                            |   2.83
Academic nausea (%)                       |  17.86
Water content (%)                         |  57.14
Spam score (%)                            |  37.50
Naturalness by Zipf's law (%)             |  53.57
Verbal nouns (% of nouns)                 |  47.06
Compound prepositions (per 100 words)     |   5.36
Parenthetical expressions (per 100 words) |   1.79
Officialese clichés (per 100 words)       |   8.93

Parameters

Parameter Type Default Description
source str / Doc - Data source
words_extractor WordsExtractor None Word extraction tool; for a Doc it is applied to its text
stopwords list[str] / set[str] None Words of water; is_stopword if not given
top_n int 10 Number of the most frequent words of the academic nausea and the naturalness by Zipf's law (NAUSEA_TOP_N)
cliches list[str] / set[str] None Clichés; OFFICIALESE_CLICHES if not given
nlp Language None spaCy pipeline that parses a string for the verbal nouns; the model es_core_news_sm of ests.utils.get_nlp if not given
nausea_stopwords list[str] / set[str] None Stopwords left out of the most frequent words of the academic nausea; is_stopword if not given

A set of words is taken in sorted order. NAUSEA_TOP_N is a constant of anyts.constants.

check_params(top_n, *, messages=MESSAGES)

Checks the number of the most frequent words: one that is not an integer of at least one raises ParameterError.

Attributes

Attribute Type Description
words tuple[str] Words of the SEO metrics
forms tuple[str] Word forms in lower case, of the markers

The metrics are properties computed on each access.

The pipeline of the parameter nlp is kept in the attribute nlp, None if not given.

Metrics

get_stats gives all the metrics. A metric is computed by the function of its card on words or forms with the hooks of the class.

Metric Measures Range Higher means
classic_nausea Repetition of the most frequent word 0 and above, grows with the text One word pressed harder
academic_nausea Share of the most frequent significant words 0-100 % A narrower vocabulary
water Share of the words without content 0-100 % Less content per word
spam Share of the repeated words 0-100 % More repetition
zipf_naturalness Agreement of the top frequencies with Zipf's law 0-100 %, nan without ranks to compare Closer to a natural text
verbal_nouns Share of the verbal nouns among the nouns 0-100 %, nan without nouns A more nominal style
compound_prepositions Compound prepositions per 100 words 0-100 More officialese
parentheticals Parenthetical expressions per 100 words 0-100 More insertions
cliches Cliches per 100 words 0-100 More cliches

The functions of the cards are imported from ests.style_stats. The cards give the norms of Advego, Text.ru, pr-cy and megaindex; the norms of the services are meant for texts of several hundred words, and on short texts nausea and spam are uninformative. esTS gives the metrics no scales, so describe gives None; the markers of the officialese have no norms.

Classic nausea

classic_nausea · Advego

calc_classic_nausea(text)

How much one word is pressed: the square root of the occurrences \(f_1\) of the most frequent word. The value is not corrected for the length of the text, so it grows with the text; no words give 0.

\[ \sqrt{f_1} \]
Parameter Type Default Description
text list[str] - List of words

The norm of Advego is at most 7, 1-5 in practice.

Academic nausea

academic_nausea · nausea_words() · Advego

calc_academic_nausea(text, top_n=10, stopwords=None)

The share of the occurrences of the top_n most frequent significant words among all the words, in percent. The exact formula of Advego is not published. With the stopwords the top ten of almost any text would be de, la, que, el, and its nausea above the norm. The norm of Advego is 5-15%, for texts of several hundred words: in a short text the top ten is a large share of the words whatever the style.

\[ 100 \cdot \frac{\sum_{w \in \textrm{top}_n} f_w}{\textrm{Number of words}} \]
Parameter Type Default Description
text list[str] - List of words
top_n int 10 Number of the most frequent words (NAUSEA_TOP_N)
stopwords list[str] / set[str] None Stopwords left out of the most frequent words; is_stopword if not given

Raises:

  • SourceTypeError - words that are not a list of strings, stopwords that are not a list or a set of strings;
  • ParameterError - a top_n that is not an integer of at least one.
find_nausea_words(text, top_n=10, stopwords=None)

The top_n most frequent words without the stopwords, those the academic nausea counts, with their occurrences: the most frequent first, words of equal frequency in the order of the text. A stopword is a word of stopwords, in any case, or, without the list, a word of is_stopword. The parameters and the errors are those of calc_academic_nausea.

is_stopword(word)

Whether a word is a stopword, in any case: the 265 forms of ests.constants.STOPWORDS - the closed classes of the grammar: articles and the other determiners (este, cada, mucho), pronouns (él, cuyo, nadie), prepositions, conjunctions and interjections, with the adverbs that point, relate or ask (aquí, así, dónde) and the ones that negate, affirm or focus (no, solo, también) - and the one-word parenthetical expressions of PARENTHETICALS (finalmente, naturalmente). The forms of the old orthography are included (á, ó, tí). StyleStats takes it for the academic nausea without nausea_stopwords and for the water content without stopwords.

Parameter Type Default Description
word str - Word

Raises:

  • SourceTypeError - a word that is not a string.
from ests.style_stats import find_nausea_words, is_stopword

is_stopword("Aquel"), is_stopword("sin"), is_stopword("trigo")
find_nausea_words(["gato", "y", "gato", "y", "perro"], top_n=1)
(True, True, False)
{'gato': 2}

Water content

water · Text.ru

calc_water(text, stopwords=None)

The share of the words without content, in percent: the words of stopwords, in any case, or, without the list, the stopwords of is_stopword. The norms of Text.ru: up to 15% natural, 15-30% excessive, above 30% high.

\[ 100 \cdot \frac{\textrm{Number of water words}}{\textrm{Number of words}} \]
Parameter Type Default Description
text list[str] - List of words
stopwords list[str] / set[str] None Words of water; is_stopword if not given

Raises:

  • SourceTypeError - words that are not a list of strings, stopwords that are not a list or a set of strings.

The water content of Spanish

The norms of Text.ru are set for Russian, which has no articles; a Spanish text has more water by its grammar alone. Compare the water content of texts with each other, not with the norm.

Spam score

spam · Text.ru

calc_spam(text)

The share of the repeated words in percent: every occurrence of a word after the first.

\[ 100 \cdot \left(1 - \frac{\textrm{Number of lexemes}}{\textrm{Number of words}}\right) \]
Parameter Type Default Description
text list[str] - List of words

To compute it by lemmas, extract the words with lemmatization. The norms of Text.ru: up to 30% natural, 30-60% SEO-optimized, above 60% spammed.

Naturalness by Zipf's law

zipf_naturalness · pr-cy, megaindex

calc_zipf_naturalness(text, top_n=10, *, messages=MESSAGES)

The agreement of the frequencies of the most frequent words with the ideal distribution \(f_r = f_1 / r\), where \(r\) is the rank. The first rank is ideal by construction, and above \(f_1\) the ideal frequency falls below one, so the ranks run from 2 to \(R = \min(\textrm{top\_n}, V, f_1)\). Negative values are cut to 0. Without ranks to compare - all the words hapaxes, one lexeme or top_n below 2 - the value is nan.

\[ 100 \cdot \left(1 - \frac{1}{R - 1} \sum_{r=2}^{R} \frac{|r \cdot f_r - f_1|}{f_1}\right) \]
Parameter Type Default Description
text list[str] - List of words
top_n int 10 Number of the most frequent words (NAUSEA_TOP_N)
messages dict[str, str] MESSAGES Messages of the errors by key

A top_n that is not an integer of at least one raises ParameterError.

The ideal distribution is that of Zipf's law; the norm of pr-cy and megaindex is at least 50%.

Verbal nouns

verbal_nouns

calc_verbal_nouns(nouns)

The share of the nouns derived from a verb among the lemmas of the nouns, in percent; nan without nouns. A noun is derived from a verb by its suffix - -ción, -sión, -miento, -anza, -encia, -ancia, -aje, -dura, -azgo (revisión, nombramiento, aprendizaje) - or is one of the nouns whose derivation leaves no suffix behind (uso, pago, envío), as in the split predicates of SyntaxStats. The rule also catches the nouns of other origins with the same endings (ciencia, distancia). The function takes the lemmas of the tokens tagged NOUN, not the words: a noun and a verb form may be spelt alike (uso, viaje).

Parameter Type Default Description
nouns list[str] - Lemmas of the nouns

Raises:

  • SourceTypeError - nouns that are not a list of strings.
noun_lemmas(doc)

The lemmas of the tokens of a Doc tagged NOUN. StyleStats takes the nouns of a Doc the same way, and those of a string from its parse by the pipeline of nlp or get_nlp, run without the parser and the named entities.

Parameter Type Default Description
doc Doc - Doc object

Raises:

  • SourceTypeError - a source that is not a Doc;
  • SourceError - a Doc without the parts of speech or the lemmas.
from ests.style_stats import calc_verbal_nouns, noun_lemmas
from ests.utils import get_nlp

calc_verbal_nouns(["revisión", "proyecto", "nombramiento", "casa"])
lemmas = noun_lemmas(get_nlp()("Se procedió a la revisión de los proyectos"))
lemmas, calc_verbal_nouns(lemmas)
50.0
(['revisión', 'proyecto'], 50.0)

Compound prepositions and clichés

compound_prepositions · cliches

calc_phrase_density(text, phrases)

The occurrences of the phrases of a list per 100 words, with the contractions and the forms of the verbs of expand_phrases. At every position the longest phrase is taken, and the phrases found do not overlap. StyleStats counts this way, over the word forms (forms), the compound prepositions of COMPOUND_PREPOSITIONS (en el marco de, a efectos de, en virtud de) and the clichés of OFFICIALESE_CLICHES (dar cumplimiento, proceder a, a la mayor brevedad) or of the parameter cliches.

\[ 100 \cdot \frac{\textrm{Number of phrases found}}{\textrm{Number of words}} \]
Parameter Type Default Description
text list[str] - List of words
phrases list[str] / set[str] - Phrases, words separated by spaces

Raises:

  • SourceTypeError - words that are not a list of strings, phrases that are not a list or a set of strings.
expand_phrases(text, phrases)

The phrases spelled out in the forms of a text, each with the phrase of the list it spells out. A phrase ending in a or de also takes the contraction with the article (a efectos del, conforme al), and a phrase whose first word is an infinitive takes the forms of the text with that lemma (proceder a - procedió a, ser de aplicación - es de aplicación). The lemma is the one of simplemma, and a pronominal lemma counts for its verb (llévese - llevar); the forms simplemma does not lemmatize - some participles (ha dado, ha hecho) and the imperative dese - are given by IRREGULAR_VERB_FORMS. The words after the verb rule out the readings as a noun (el hecho, el puesto). When two phrases spell out the same words, a phrase written so wins over the forms of another (a efectos del stays itself next to a efectos de), and then the first phrase of a list, or of a set in sorted order.

Parameter Type Default Description
text list[str] - List of words
phrases list[str] / set[str] - Phrases, words separated by spaces

Raises:

  • SourceTypeError - words that are not a list of strings, phrases that are not a list or a set of strings.
from ests.constants import COMPOUND_PREPOSITIONS
from ests.style_stats import calc_phrase_density, expand_phrases

expand_phrases(["se", "procedió", "al", "cierre"], ["proceder a"])
words = "se procedió a la revisión en el marco del proyecto".split()
calc_phrase_density(words, COMPOUND_PREPOSITIONS)
{'proceder a': 'proceder a',
 'proceder al': 'proceder a',
 'procedió a': 'proceder a',
 'procedió al': 'proceder a'}
10.0

The compound prepositions (42) and the clichés (74) are the ones the Spanish-language guides to plain language and style manuals of the administrations flag:

Source Author Year
Libro de estilo de la Justicia RAE, CGPJ 2017
Diccionario panhispánico de dudas, 2nd ed. RAE, ASALE online
Guía panhispánica de lenguaje claro y accesible RAE, ASALE 2024
Claridad y derecho a comprender Comisión de Modernización del Lenguaje Jurídico 2011
Cómo escribir con claridad European Commission 2015
Manual del Lenguaje Administrativo Ayuntamiento de Madrid about 2008
Comunicación Clara. Guía práctica Ayuntamiento de Madrid 2017
Guía de Comunicación Clara Comunidad de Madrid 2021
Manual de Lenguaje y Estilo Administrativo Región de Murcia 2008
Manual de Lenguaje Claro Secretaría de la Función Pública, Mexico 2007
Guía de lenguaje claro para servidores públicos Departamento Nacional de Planeación, Colombia 2015
Guía de lenguaje claro Colombia Compra Eficiente 2024
Manual de lenguaje claro Gobierno de la Ciudad de Buenos Aires 2024
Guía para el uso del Lenguaje Claro Legislatura de la Ciudad de Buenos Aires 2024
Manual de Estilo del Lenguaje para uso de la Administración Pública Provincial Provincia de Salta about 2007

Left out are the forms the guides recommend (sobre la base de) or accept (de acuerdo a) and the phrases with frequent neutral uses (en este sentido), but for proceder a and llevar a cabo. A few compound prepositions have neutral uses too (a través de, en caso de, con respecto a), so the density of a neutral text is not zero: compare texts with each other.

Parenthetical expressions

parentheticals

calc_parentheticals(text)

The parenthetical expressions of PARENTHETICALS (sin embargo, es decir, por ejemplo, finalmente) per 100 words: the phrases of the list with their contractions (expand_phrases) and, outside them, the one-word expressions of the list. The punctuation is not looked at, so the list holds the expressions set off by commas or standing at the edge of a sentence in most of their occurrences in the corpus of literature, with the series of order (en primer lugar, en segundo lugar). The adverbs of doubt (tal vez, quizá), sobre todo, sin duda and además are not in the list.

\[ 100 \cdot \frac{\textrm{Number of parenthetical expressions}}{\textrm{Number of words}} \]
Parameter Type Default Description
text list[str] - List of words

Raises:

  • SourceTypeError - words that are not a list of strings.
find_parentheticals(text)

The bounds of the parenthetical expressions of calc_parentheticals as slices of the list of words, in order. The parameter and the errors are those of calc_parentheticals.

is_parenthetical(word)

Whether a word is one of the one-word expressions of PARENTHETICALS (finalmente, naturalmente, verbigracia), in any case.

Parameter Type Default Description
word str - Word

Raises:

  • SourceTypeError - a word that is not a string.
from ests.style_stats import calc_parentheticals, find_parentheticals, is_parenthetical

words = ["sin", "embargo", "el", "gato", "finalmente", "duerme"]
find_parentheticals(words), calc_parentheticals(words)
is_parenthetical("Finalmente")
([(0, 2), (4, 5)], 33.33333333333333)
True

Keyword density

keyword_density(*keywords) · Text.ru

calc_keyword_density(text, keywords, normalize=str.lower)

The occurrences of every keyword per 100 words. A keyword of several words separated by spaces is a sequence of words whose occurrences may overlap. The words and the keywords are compared by their normalized forms, and the keywords of a set come in sorted order.

Parameter Type Default Description
text list[str] - List of words
keywords list[str] - Keywords or phrases
normalize Callable[[str], str] str.lower Normalized form of a word and of a keyword

To compare by lemmas, extract the words with lemmatization and pass lemmas.

from ests import StyleStats

text = "Tres tristes tigres tragaban trigo en un trigal, en tres tristes trastos"
StyleStats(text).keyword_density("tres tristes", "trigo")
{'tres tristes': 16.666666666666668, 'trigo': 8.333333333333334}

The functions of the core cards check their words too: words and keywords that are not a list of strings, and a normalize that cannot be called, raise SourceTypeError.

Methods

keyword_density(*keywords) gives the density of keywords and phrases in percent (calc_keyword_density with normalize of the class), nausea_words() the most frequent significant words of the academic nausea with their occurrences (find_nausea_words).

markers() returns the markers of the officialese found by metric - verbal_nouns, compound_prepositions, parentheticals, cliches - as the forms write them, in the order of the text.

A phrase is found with its contractions and the forms of its verb (expand_phrases). The verbal nouns need the parts of speech and the lemmas, as their metric does, so a string is parsed by the model and a Doc without them raises SourceError.

ss.markers()
{'verbal_nouns': ('procedimiento',
                  'referencia',
                  'cumplimiento',
                  'revisión',
                  'documentación',
                  'resolución',
                  'comisión',
                  'notificación'),
 'compound_prepositions': ('en el marco del', 'a efectos de', 'en virtud de'),
 'parentheticals': ('no obstante',),
 'cliches': ('dar cumplimiento',
             'procedió a',
             'llevará a cabo',
             'a la mayor brevedad',
             'en tiempo y forma')}

The notice of 56 words has three compound prepositions, five clichés and one parenthetical expression, and almost half of its nouns are derived from a verb. The anaphoric de la misma, which the guides also advise against, is not counted.

The other methods - get_stats, to_dataframe, print_stats and describe - are those of the statistics interface of the anyTS core; the style metrics of esTS have no scales, so describe gives None for the metrics of get_stats. The same notice in plain language:

plain = (
    "Hemos revisado los documentos que nos envió, como pide la ley. La comisión ha "
    "decidido su caso y le avisaremos lo antes posible."
)
StyleStats(plain).get_stats()
ss.describe("water"), ss.describe("cliches")
{'classic_nausea': 1.4142135623730951,
 'academic_nausea': 43.47826086956522,
 'water': 43.47826086956522,
 'spam': 4.3478260869565215,
 'zipf_naturalness': 100.0,
 'verbal_nouns': 25.0,
 'compound_prepositions': 0.0,
 'parentheticals': 0.0,
 'cliches': 0.0}
(None, None)

The plain version has no compound prepositions, parenthetical expressions or clichés, and a quarter of its nouns are derived from a verb. The academic nausea says nothing of the style here: neither notice repeats a significant word, so it is ten words over the length of the text, 10/56 and 10/23.