Text highlighting¶
HighlightedText(source, layers=None, long_sent_word_factor=30, complex_syl_factor=4, stopwords=None, cliches=None, alliteration_threshold=0.001)
Highlighting of a text by layers, in the manner of the style checkers: every layer marks the fragments of a text that a statistic counts - long sentences, complex words, the passive. The data source is a text or a Doc object of spaCy. The result shows in Jupyter as HTML with styles and a legend; to_html returns the markup, and highlights keeps the fragments for a rendering of one's own. Fragments of different layers may overlap.
Raises:
SourceTypeError- a source that is neither a string nor aDoc;SourceError- a source without words;ParameterError- layers that are not a name or a list of names, a layer that is unknown or one that needs an annotation the source lacks.
highlight(source, layers=None, long_sent_word_factor=30, complex_syl_factor=4, stopwords=None, cliches=None, alliteration_threshold=0.001)
The function highlight returns a HighlightedText object of esTS with the layers of the statistics of the library:
| Group | Layer | What it marks | Statistic |
|---|---|---|---|
| Readability | long_sents |
Sentences of long_sent_word_factor words or more |
BasicStats |
complex_words |
Words of complex_syl_factor syllables or more |
BasicStats | |
rare_words |
Words with a lemma beyond the embedded top 10000; numbers, words with a hyphen or a digit and stopwords are not highlighted | LexicalStats | |
| Syntax | passive |
Passive verb forms with their auxiliary ser or their se; the note tells the two apart and marks the passive with ser without an agent |
SyntaxStats |
participle_clauses |
Participial clauses | SyntaxStats | |
gerund_clauses |
Gerund clauses | SyntaxStats | |
de_chains |
Chains of complements with de with their head |
SyntaxStats | |
split_predicates |
Split predicates from the verb to the noun | SyntaxStats | |
| Officialese | verbal_nouns |
Nouns derived from a verb | StyleStats |
compound_prepositions |
Compound prepositions of COMPOUND_PREPOSITIONS |
StyleStats | |
cliches |
Clichés of OFFICIALESE_CLICHES or of the parameter cliches |
StyleStats | |
anaphoric_mismo |
The anaphoric el mismo with its article (se archivó el mismo, la duración del mismo) |
- | |
| Style | stopwords |
Stopwords of STOPWORDS or of the parameter stopwords, the water of the text |
StyleStats |
parentheticals |
Parenthetical expressions | StyleStats | |
connectors |
Discourse markers, the class and the kind in the note | CohesionStats | |
| Phonics | alliteration |
Repetitions of a consonant sound in neighbouring words, unlikely by the frequencies of the Spanish sounds; the note gives the sound and the letters that write it | PhonStats |
The groups are defined in ests.constants.HIGHLIGHT_LAYER_GROUPS. The layers of the group "Syntax" need a Doc with a dependency parse and the lemmas (the models es_core_news_sm, es_core_news_md, es_core_news_lg), as SyntaxStats does; verbal_nouns needs a Doc with the parts of speech and the lemmas, and anaphoric_mismo one with the dependency parse, the parts of speech and the lemmas. The layers of HIGHLIGHT_DEFAULT_LAYERS the source allows are on by default - long sentences, complex words, passive, chains of de, split predicates, clichés; layers="all" turns on every layer allowed. The layers overlap (a compound preposition is made of stopwords), so pick the ones you need.
A sentence is long from 30 words, the bound of the Spanish-language guides to plain language (the Comunidad de Madrid and the Gobierno de la Ciudad de Buenos Aires), and a word is complex from four syllables; complex_syl_factor=3, the bound of BasicStats, shows the words of n_complex_words.
Alliteration
An alliteration is looked for inside a sentence as a run of two neighbouring words or more with the same consonant sound in their stems. The sounds are the ones of the transcription, so casa and queso repeat k; the stem is the common start of the transcriptions of the word form and of its lemma (cantaban - k a n t a), which leaves out the endings that repeat by agreement (las casas blancas). The probability of a run under an independent spread of the sounds is the product over its words of the probability to meet the consonant among the sounds of the stem, \(1 - (1 - f)^n\), where \(f\) is the frequency of the sound in the corpus of literature and \(n\) the number of the sounds of the stem; a run is highlighted when the probability is below alliteration_threshold. A repetition of a rare sound shows in two or three words (deje la abeja), while the s of los suspiros se escapan is too frequent to count in two words. Words shorter than three letters (de, la, el), stopwords (que, los, con) and words without vowels neither break nor continue a run. The alliteration index of PhonStats measures how the repetitions cluster over the whole text; the highlighting shows where they are.
The class extends the HighlightedText of the anyTS core with the hooks of Spanish from ests.constants: the layers with their names in the legend are HIGHLIGHT_LAYERS_DESC, the layers on by default HIGHLIGHT_DEFAULT_LAYERS, the annotations of a Doc a layer needs HIGHLIGHT_LAYER_ANNOTATIONS and the styles of the layers HIGHLIGHT_LAYER_STYLES; the prefix of the CSS classes is ests. The words of a string are those of WordsExtractor, without the punctuation, and its sentences those of SentsExtractor; a Doc without the sentence boundaries (a blank pipeline, a pipeline without the parser) is split into sentences by the rules of SentsExtractor too. A string with a character without a UTF-8 form raises SourceError from the Spanish tokenizer. The fragments of every layer are found by the finders of esTS.
import spacy
from ests.visualizers import highlight
nlp = spacy.load("es_core_news_sm")
text = (
"El proyecto, elaborado durante el verano, fue aprobado por el consejo sin debate. "
"El aumento de la eficiencia del uso de los recursos públicos se analizó, siguiendo "
"las normas del reglamento. "
"Los representantes de los ministerios regionales no consiguieron hacer una revisión "
"conjunta de las cuestiones de financiación y de reparto de responsabilidades entre los "
"organismos, puesto que cada uno de ellos defendía su propia interpretación de las "
"disposiciones del acuerdo. "
"En el marco de la reunión se procedió a la votación, y la decisión quedó aplazada "
"hasta la próxima sesión."
)
ht = highlight(nlp(text))
ht.counts
ht.highlights[:2]
highlight(nlp(text), layers="all").counts
{'long_sents': 1,
'complex_words': 16,
'passive': 2,
'de_chains': 3,
'split_predicates': 2,
'cliches': 1}
(Highlight(start=13, end=22, layer='complex_words', note='complex word, 5 syllables'),
Highlight(start=42, end=54, layer='passive', note='passive with ser'))
{'long_sents': 1,
'complex_words': 16,
'rare_words': 0,
'passive': 2,
'participle_clauses': 2,
'gerund_clauses': 1,
'de_chains': 3,
'split_predicates': 2,
'verbal_nouns': 9,
'compound_prepositions': 1,
'cliches': 1,
'anaphoric_mismo': 0,
'stopwords': 48,
'parentheticals': 0,
'connectors': 3,
'alliteration': 0}
In Jupyter the object ht displays like this (hover over a fragment to see its note):
A string without a model of spaCy allows every layer but the syntactic ones and verbal_nouns: long sentences, complex words and clichés by default, and the alliteration on request.
from ests.visualizers import highlight
# Antonio Machado, Poesías completas (the corpus of literature)
poem = "El cierzo corre por el campo yerto alborotando en blancos torbellinos la nieve silenciosa."
highlight(poem, layers="alliteration").highlights
text = "Se procedió a la revisión del expediente en el marco del plan a la mayor brevedad."
highlight(text, layers=["compound_prepositions", "cliches"]).highlights
(Highlight(start=35, end=78, layer='alliteration', note='alliteration on /b/ (b, v)'),)
(Highlight(start=3, end=13, layer='cliches', note='cliché: «proceder a»'),
Highlight(start=41, end=56, layer='compound_prepositions', note='compound preposition: «en el marco de»'),
Highlight(start=62, end=81, layer='cliches', note='cliché: «a la mayor brevedad»'))
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
source |
str / Doc |
- | Data source (a string or a Doc object) |
layers |
list[str] / str |
None |
Layers of the highlighting; if not given, the default layers the source allows; "all" - every layer allowed |
The parameters of the layers of esTS:
| Parameter | Type | Default | Description |
|---|---|---|---|
long_sent_word_factor |
int |
30 |
Minimum number of words of a long sentence |
complex_syl_factor |
int |
4 |
Minimum number of syllables of a complex word |
stopwords |
list[str] / set[str] |
None |
List or set of stopwords; if not given, STOPWORDS and the one-word parenthetical expressions |
cliches |
list[str] / set[str] |
None |
List or set of clichés; if not given, OFFICIALESE_CLICHES |
alliteration_threshold |
float |
0.001 |
Probability of a repetition of a consonant under an independent spread of the sounds, below which the repetition is alliteration |
A number of words or syllables that is not an integer of at least one or a threshold outside (0, 1] raises ParameterError, stopwords or clichés that are not a list or a set of strings SourceTypeError.
Attributes¶
| Attribute | Type | Description |
|---|---|---|
text |
str |
Text of the data source |
layers |
tuple[str] |
Layers turned on, in the order of drawing |
highlights |
tuple[Highlight] |
Highlighted fragments, sorted by their start and then by descending end |
counts |
dict[str, int] |
Number of fragments of every layer |
Highlight(start, end, layer, note='')
A fragment of the highlighting: an immutable object with the parameters as its fields.
| Field | Type | Default | Description |
|---|---|---|---|
start |
int |
- | Position of the first character of the fragment in the text |
end |
int |
- | Position after the last character of the fragment |
layer |
str |
- | Layer of the highlighting |
note |
str |
'' |
Explanation of the fragment for the tooltip |
The notes of esTS are in English: they give the number of words of the sentence or of the clause, of syllables of the word, the length of the chain, the phrase of the list, the class and the kind of the connector, the sound of the alliteration and its letters.
Methods¶
to_html(legend=True, css=True) returns the HTML markup of the highlighted text: a div of the class <prefix>-highlight with the legend, its counts and the text, where a highlighted segment is a span of the classes <prefix>-hl and <prefix>-hl-<layer> with the notes in its title; <prefix> is css_prefix. Overlapping fragments of different layers give segments with several classes, in the order of drawing. Line breaks (\n, \r\n, \r) become character references, one per break, so the markup can go into Markdown.
| Parameter | Type | Default | Description |
|---|---|---|---|
legend |
bool |
True |
Add the legend with the counts of the fragments |
css |
bool |
True |
Add the styles of the layers |
The class method css() returns the styles to_html adds: the container, the legend and the text under the classes of css_prefix, the declarations of layer_styles for every layer, and a dark colour of the text on the layers with a background.
from ests.visualizers import highlight
poem = "El cierzo corre por el campo yerto alborotando en blancos torbellinos la nieve silenciosa."
highlight(poem, layers="alliteration").to_html(legend=False, css=False)
'<div class="ests-highlight"><div class="ests-highlight-text">El cierzo corre por el campo yerto <span class="ests-hl ests-hl-alliteration" title="alliteration on /b/ (b, v)">alborotando en blancos torbellinos la nieve</span> silenciosa.</div></div>'
Helpers of the layers¶
The words and the sentences the finders of the layers take; the helpers are in ests.visualizers.highlight.
Word(start, end, text, pos=None, lemma=None)
A word with its position in the text and, when known, its part of speech and lemma.
Sent(start, end, n_words)
A sentence with its position and number of words.
group_words_by_sents(words, sents)
The words of every sentence; the words out of the sentences are skipped.
Finders of the layers¶
The fragments of every layer are found by a function of ests.visualizers.highlight that returns a list of Highlight: from the words and the sentences of the text, or from the Doc for the syntactic layers (SYNTAX_FINDERS gives them by layer). All but the passive, the participial and gerund clauses, the chains of de and the anaphoric el mismo are the finders of the core with the hooks of Spanish.
Long sentences and complex words¶
find_long_sents(sents, long_sent_word_factor)
The layer long_sents: the sentences of long_sent_word_factor words or more, the note with their number of words.
find_complex_words(words, complex_syl_factor)
The layer complex_words: the words of complex_syl_factor syllables or more by count_syllables, the note with their number of syllables.
| Parameter | Type | Default | Description |
|---|---|---|---|
sents |
list[Sent] |
- | Sentences with their positions and numbers of words |
long_sent_word_factor |
int |
- | Minimum number of words of a long sentence |
words |
list[Word] |
- | Words with their positions |
complex_syl_factor |
int |
- | Minimum number of syllables of a complex word |
Raises:
ParameterError- a number of words or syllables that is not an integer of at least one.
from ests.utils import iter_text_words
from ests.visualizers.highlight import Sent, Word, find_complex_words, find_long_sents
text = "El felinólogo examinó al gato. Lo devolvieron."
words = [Word(*word) for word in iter_text_words(text)]
find_long_sents([Sent(0, 30, 5), Sent(31, 46, 2)], 5)
find_complex_words(words, 4)
[Highlight(start=0, end=30, layer='long_sents', note='long sentence, 5 words')]
[Highlight(start=3, end=13, layer='complex_words', note='complex word, 5 syllables'),
Highlight(start=14, end=21, layer='complex_words', note='complex word, 4 syllables'),
Highlight(start=34, end=45, layer='complex_words', note='complex word, 4 syllables')]
Single words¶
find_rare_words(words)
The layer rare_words: the words whose key (lemma_key) is beyond the 10,000 most frequent lemmas (get_rank), as p_beyond_top10000 of LexicalStats counts them; a word tagged PROPN keeps its form. Only the words of two Spanish letters or more that are not stopwords are looked at.
find_stopwords(words, stopwords=None)
The layer stopwords: the words of stopwords in any case or, without the list, the stopwords of is_stopword.
find_verbal_nouns(words)
The layer verbal_nouns: the words tagged NOUN whose lemma is derived from a verb, as for the verbal nouns of StyleStats.
| Parameter | Type | Default | Description |
|---|---|---|---|
words |
list[Word] |
- | Words with their positions; with their parts of speech and lemmas for find_verbal_nouns |
stopwords |
list[str] / set[str] |
None |
List or set of stopwords; is_stopword if not given |
Raises:
SourceTypeError- stopwords that are not a list or a set of strings.
from ests.utils import iter_text_words
from ests.visualizers.highlight import Word, find_rare_words, find_stopwords
text = "El felinólogo examinó minuciosamente al gato."
words = [Word(*word) for word in iter_text_words(text)]
find_rare_words(words)
find_stopwords(words)
[Highlight(start=3, end=13, layer='rare_words', note='rare word: beyond the top 10000'),
Highlight(start=22, end=36, layer='rare_words', note='rare word: beyond the top 10000')]
[Highlight(start=0, end=2, layer='stopwords', note='stopword'),
Highlight(start=37, end=39, layer='stopwords', note='stopword')]
Phrases¶
find_phrase_highlights(words, phrases, layer, label)
The fragments of the phrases of a list, matched with their contractions and the forms of their verbs (expand_phrases); the note gives the label and the phrase of the list (cliché: «proceder a»).
find_compound_prepositions(words)
The layer compound_prepositions: the phrases of COMPOUND_PREPOSITIONS.
find_cliches(words, cliches=None)
The layer cliches: the phrases of cliches, of OFFICIALESE_CLICHES if not given.
find_parentheticals(words)
The layer parentheticals: the phrases of PARENTHETICALS.
| Parameter | Type | Default | Description |
|---|---|---|---|
words |
list[Word] |
- | Words with their positions |
phrases |
list[str] / set[str] |
- | Phrases, words separated by spaces |
layer |
str |
- | Layer of the highlighting |
label |
str |
- | Label of the fragment in the note |
cliches |
list[str] / set[str] |
None |
List or set of clichés |
Raises:
SourceTypeError- phrases or clichés that are not a list or a set of strings.
from ests.utils import iter_text_words
from ests.visualizers.highlight import Word, find_cliches, find_compound_prepositions
text = "Se procedió a la revisión del expediente en el marco del plan."
words = [Word(*word) for word in iter_text_words(text)]
find_cliches(words)
find_compound_prepositions(words)
[Highlight(start=3, end=13, layer='cliches', note='cliché: «proceder a»')]
[Highlight(start=41, end=56, layer='compound_prepositions', note='compound preposition: «en el marco de»')]
Connectors¶
find_connector_highlights(words, sents, text=None, doc=None)
The layer connectors: the connectors of CohesionStats inside every sentence, found by its matcher SpanishConnectorMatcher, a one-word connector checked by its part of speech when the words have it; the note gives the connector, its class and its kind. With the text, the rules of the matcher read no word across a punctuation mark between two words, so así, que is no connector; with the Doc, no connector starts at a si that opens no condition, as in CohesionStats (is_connector_blocked: no sé si vendrá). HighlightedText passes the text of its source and its Doc; in a string, which it does not parse, only the rules of the matcher drop a si (como si fuera).
| Parameter | Type | Default | Description |
|---|---|---|---|
words |
list[Word] |
- | Words with their positions and parts of speech |
sents |
list[Sent] |
- | Sentences with their positions |
text |
str |
None |
Text of the words, for the punctuation marks between them |
doc |
Doc |
None |
Doc of the words, for the rules by its tokens |
Raises:
SourceTypeError- a text that is not a string.
from ests.utils import iter_text_words
from ests.visualizers.highlight import Sent, Word, find_connector_highlights
text = "Sin embargo, el plan no se aprobó."
words = [Word(*word) for word in iter_text_words(text)]
find_connector_highlights(words, [Sent(0, len(text), len(words))], text)
[Highlight(start=0, end=11, layer='connectors', note='connector «sin embargo»: adversative, secondary')]
Alliteration¶
get_stem_sounds(word)
The sounds of the stem of a word form: the common start of the transcriptions of the form and of its lemma (cantaban - k a n t a, cogió - k o x). When it is shorter than two sounds, as for the suppletive forms (fue - ser), the stem is the whole form.
calc_alliteration_runs(text, threshold=0.001)
The runs of calc_alliteration_runs of the core over the stems of get_stem_sounds and the consonant sounds of ALLITERATION_FREQUENCIES, their frequencies in SOUND_FREQUENCIES; the words shorter than three letters, the stopwords and the words without vowels neither break nor continue a run. A run is a triple of the index of its first word, the index after its last word and its sound.
find_alliteration(words, threshold=0.001, sents=None)
The layer alliteration: the runs of calc_alliteration_runs inside every sentence, in the whole text without the sentences; the note gives the sound and the letters that write it (alliteration on /b/ (b, v)).
| Parameter | Type | Default | Description |
|---|---|---|---|
word |
str |
- | Word form in lower case |
text |
list[str] |
- | List of words |
threshold |
float |
0.001 |
Probability threshold (ALLITERATION_THRESHOLD of anyts.constants) |
words |
list[Word] |
- | Words with their positions |
sents |
list[Sent] |
None |
Sentences with their positions |
Raises:
SourceTypeError- a word that is not a string, words ofcalc_alliteration_runsthat are not a list of strings;ParameterError- a threshold outside (0, 1].
from ests.visualizers.highlight import calc_alliteration_runs, get_stem_sounds
get_stem_sounds("cantaban"), get_stem_sounds("fue")
calc_alliteration_runs(["deje", "la", "abeja"])
calc_alliteration_runs(["los", "suspiros", "se", "escapan"])
(('k', 'a', 'n', 't', 'a'), ('f', 'u', 'e'))
[(0, 3, 'x')]
[]
Syntactic layers¶
find_passive(doc)
The layer passive: every passive form of is_passive with its auxiliary ser (fue construida) or its se (se construyó); the note tells the passive with se from the one with ser and marks a passive with ser without an agent (is_agentless).
find_participle_clauses(doc)
The layer participle_clauses: the subtree of every head of a participial clause, the note with its number of words.
find_gerund_clauses(doc)
The layer gerund_clauses: the subtree of every head of a gerund clause, the note with its number of words.
find_de_chains(doc)
The layer de_chains: the chains of de of two complements or more, a fragment from the head to the last complement (el aumento de la eficiencia del uso de los recursos), the note with the length of the chain.
find_split_predicate_highlights(doc)
The layer split_predicates: the pairs of a light verb and a noun of find_split_predicates, a fragment from the first word of the pair to the last (hacer una revisión), the note with their lemmas.
| Parameter | Type | Default | Description |
|---|---|---|---|
doc |
Doc |
- | Doc object with a dependency parse and the lemmas |
from ests.utils import get_nlp
from ests.visualizers.highlight import (
find_de_chains,
find_gerund_clauses,
find_participle_clauses,
find_passive,
find_split_predicate_highlights,
)
doc = get_nlp()(
"El informe, redactado por el comité, fue aprobado tras hacer una revisión, "
"siguiendo las normas del uso de los recursos."
)
find_passive(doc)
find_participle_clauses(doc)
find_gerund_clauses(doc)
find_de_chains(doc)
find_split_predicate_highlights(doc)
[Highlight(start=37, end=49, layer='passive', note='passive with ser, no agent')]
[Highlight(start=12, end=35, layer='participle_clauses', note='participial clause, 4 words')]
[Highlight(start=75, end=119, layer='gerund_clauses', note='gerund clause, 8 words')]
[Highlight(start=89, end=119, layer='de_chains', note='chain of 2 complements with de')]
[Highlight(start=55, end=73, layer='split_predicates', note='split predicate: hacer revisión')]
Anaphoric el mismo¶
is_anaphoric_mismo(token)
Checks whether a word is an anaphoric el mismo: a form of mismo that heads a noun phrase of its own with its article (el, la, los, las, del, al) and no other dependent but a clause after a comma, as an argument of a verb or a noun (se archivó el mismo, la duración del mismo, contra la misma, que pone fin a la vía administrativa, cabe recurso). The Diccionario panhispánico de dudas takes this use of the administrative and the journalistic style for unnecessary and suggests a demonstrative, a possessive or a personal pronoun instead (su duración, dentro de ella). The identity is no anaphora: el mismo día, es el mismo, lo mismo, él mismo, a form after ser, estar, parecer, permanecer or resultar, by the lemma of the model or of simplemma (no parecía la misma, siempre serás el mismo), right before que (el mismo que el año pasado, while del mismo, que... is anaphoric) and before a conjunction and an adjective or a determiner other than an article or a possessive, which share its noun (de la misma o superior categoría, a la misma u otra sede). The models attach some anaphoric forms to their article (en la misma hay oficinas): such a form counts when no noun, adjective, numeral or pronoun follows it in its sentence; an adjective whose noun after it disagrees with the form in gender or number is a verb the model misread (contra la misma cabe recurso, cabe tagged as an adjective of recurso), while la misma angosta calle stays a noun phrase. A token of a Doc without a parse is none. Whitespace and punctuation are no dependents of the form, so a line break after it changes nothing.
find_anaphoric_mismo(doc)
The layer anaphoric_mismo: every form of is_anaphoric_mismo with its article (la misma, del mismo), the two words in the note.
| Parameter | Type | Default | Description |
|---|---|---|---|
token |
Token |
- | Token |
doc |
Doc |
- | Doc object with a dependency parse, the parts of speech and the lemmas |
from ests.utils import get_nlp
from ests.visualizers.highlight import find_anaphoric_mismo, is_anaphoric_mismo
doc = get_nlp()("Se recibió la solicitud el mismo día, y se procedió al estudio de la misma.")
[token.text for token in doc if is_anaphoric_mismo(token)]
find_anaphoric_mismo(doc)
['misma']
[Highlight(start=66, end=74, layer='anaphoric_mismo', note='anaphoric la misma')]