Cohesion statistics¶
ests.cohesion_stats.CohesionStats
Description¶
A module for computing the cohesion statistics of a text in the manner of Coh-Metrix and of its Spanish adaptation Coh-Metrix-Esp: the overlap of nouns, of arguments and of content words between sentences, givenness, temporal cohesion and the density of the discourse markers.
Sentences are compared by lemmas, and the features come from the annotation of Universal Dependencies, so the source has to be annotated: a string is parsed with es_core_news_sm or with the pipeline passed in nlp, and a Doc must carry the parts of speech, which come from a morphologizer (or a tagger with an attribute_ruler), and the lemmas, which come from a lemmatizer; a source without either raises SourceError, and without the lemmas the overlaps would be computed on empty strings, where every noun overlaps with every other. Without sentence boundaries - a pipeline with no parser - the sentences are taken from the text by SentsExtractor.
A text longer than the max_length of the pipeline - a million characters by default - raises SourceError: split it into parts, or raise max_length on a pipeline of your own and pass it in nlp.
Note
The statistics are computed when the CohesionStats object is initialized.
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
source |
str/Doc | - |
Data source (a string or a Doc object) |
sents_extractor |
SentsExtractor | None |
Sentence extraction tool, used for a Doc with no sentence boundaries |
connectors |
dict[str, tuple[str, str]] | None |
Dictionary of the connectors - class and kind by connector; without it the dictionary of the library is used |
nlp |
Language | None |
Pipeline of spaCy that parses a string; without it the model es_core_news_sm is loaded |
Referential cohesion¶
The overlap of Coh-Metrix: a pair of sentences is cohesive when they share the lemma of a noun (CRFNO), of an argument - a noun or a pronoun - (CRFAO) or of a content word (CRFSO). The binary measures give the share of such pairs among the adjacent sentences and among all the pairs of the text; the proportional ones (CRFCWO) give the mean Dice coefficient of the sets of lemmas, 2·|A ∩ B| / (|A| + |B|).
| Attribute | Type | Description |
|---|---|---|
noun_overlap_adjacent |
float | Share of adjacent pairs of sentences sharing a noun |
noun_overlap_all |
float | Share of all pairs of sentences sharing a noun |
argument_overlap_adjacent |
float | Share of adjacent pairs sharing a noun or a pronoun |
argument_overlap_all |
float | Share of all pairs sharing a noun or a pronoun |
content_overlap_adjacent |
float | Share of adjacent pairs sharing a content word |
content_overlap_all |
float | Share of all pairs sharing a content word |
content_overlap_prop_adjacent |
float | Mean share of shared content words in adjacent pairs |
content_overlap_prop_all |
float | Mean share of shared content words in all pairs |
p_pronouns |
float | Share of pronouns among the words |
pronoun_noun_ratio |
float | Ratio of the number of pronouns to the number of nouns |
p_demonstratives |
float | Share of demonstratives among the words |
p_given |
float | Share of content words whose lemma was used before in the text |
tense_repetition |
float | Share of adjacent pairs with the same dominant tense |
mood_repetition |
float | Share of adjacent pairs with the same dominant mood |
temporal_cohesion |
float | Mean of the repetition of the tense and of the mood |
A noun is NOUN or PROPN and a content word is a NOUN, PROPN, ADJ, VERB or ADV. A pronoun is a PRON or a determiner that points at something - a possessive (Poss=Yes) or a demonstrative or personal one (PronType=Dem, Prs) - so mi libro and este libro hold a pronoun while el libro and cada libro do not: the quantifiers and the indefinites (cada, todos, ningún, otro, cualquier) point at nothing and would inflate a measure of anaphoric density by about a fifth. A demonstrative carries PronType=Dem.
An argument is a NOUN, PROPN or PRON, the determiners left out even when they count as pronouns: the argument overlap of Coh-Metrix is built on nouns and pronouns proper, and the lemma of este in two sentences is no reference to the same thing.
Temporal cohesion follows SMTEMP of Coh-Metrix: for every sentence the dominant value of the feature of its verbs is taken, and a pair of adjacent sentences counts as cohesive when the values are equal. Spanish has no aspect in Universal Dependencies, so the mood takes its place next to the tense - the shift from the indicative to the subjunctive is what breaks the temporal frame of a Spanish text. Pairs where one of the sentences has no verb with the feature are skipped, and a text shorter than two sentences leaves every measure of this section nan.
Connectors¶
The discourse markers (marcadores del discurso) of the classification of Martín Zorraquino and Portolés, 255 of them in ests/resources/connectors.tsv, in seven classes and two kinds: primary - conjunctions, conjunctive locutions and adverbs (porque, aunque, además) - and secondary, the lexicalized phrases (sin embargo, por lo tanto, es decir). The density is given per 1000 words.
| Attribute | Type | Description |
|---|---|---|
connectors |
float | Connectors per 1000 words |
connectors_causal |
float | Causal connectors (porque, por lo tanto, así que) |
connectors_adversative |
float | Adversative connectors (pero, sin embargo, en cambio) |
connectors_concessive |
float | Concessive connectors (aunque, a pesar de, de todos modos) |
connectors_temporal |
float | Temporal connectors (cuando, a continuación, por último) |
connectors_additive |
float | Additive connectors (y, además, por otra parte) |
connectors_conditional |
float | Conditional connectors (si, a menos que, de lo contrario) |
connectors_reformulative |
float | Reformulative connectors (es decir, en resumen, por ejemplo) |
connectors_primary |
float | Primary connectors per 1000 words |
connectors_secondary |
float | Secondary connectors per 1000 words |
The connectors are looked for by their word forms in lower case: at every position the longest one is taken, so sin embargo does not fall apart into sin, and the ones found do not overlap. The occurrences are in the attribute connector_spans and their distribution in c_connectors.
Two rules keep the ordinary uses of those words out. A one-word connector counts only with a part of speech of CONNECTOR_POS - a conjunction, a particle, an adverb, an adposition, an interjection - and never after a determiner, so el antes y el después holds one connector, y, and not three; a proper noun counts only at the start of a sentence, where the models read a marker as one (Primeramente, Concluyendo), so the surname of Ana, Luego y Mas firmaron is no connector. And a marker that is also the head of a prepositional phrase is dropped there: antes de la reunión, después del informe, por encima de 80, al final de la línea, al principio de la oración, luego de la sesión and sobre todo el texto count nothing, while antes, firmó el acta, encima, no vino, al final, no vino and sobre todo cuando llueve count their marker.
Your own dictionary can be passed in connectors: a mapping from the connector to its class of CONNECTOR_CLASSES and its kind of CONNECTOR_TYPES, an unknown one raising ParameterError.
Warning
A Spanish marker is most often a phrase built of ordinary words, and those words are counted as words like any other: the noun embargo of sin embargo and the noun ejemplo of por ejemplo are nouns and content words of their sentence, and a text that repeats a marker gains a little overlap from it. Measured on the Spanish pages of this site the difference is below 0.002 for every overlap, but on a text built of markers it would be larger.
Counts¶
| Attribute | Type | Description |
|---|---|---|
words |
tuple[tuple[str, ...], ...] | Tuple of the words of every sentence |
lemmas |
tuple[tuple[str, ...], ...] | Tuple of the lemmas of every sentence |
n_sents |
int | Number of sentences containing words |
n_words |
int | Number of words |
n_nouns |
int | Number of nouns |
n_pronouns |
int | Number of pronouns |
n_demonstratives |
int | Number of demonstratives |
n_content_words |
int | Number of content words |
n_given |
int | Number of content words whose lemma was used before |
n_connectors |
int | Number of connectors |
connector_spans |
tuple[Connector] | Tuple of the occurrences of the connectors |
c_connectors |
dict[str, int] | Distribution of the occurrences by connector |
Methods¶
get_stats¶
Returns a dictionary with the computed cohesion statistics.
Example
Code:
# Import the library
from ests import CohesionStats
# Prepare the data
text = (
"El informe fue aprobado por la comisión. Sin embargo, el informe no resuelve "
"el problema. Es decir, la comisión aplazó la decisión. Por lo tanto, el "
"problema sigue abierto."
)
# Compute the statistics
cs = CohesionStats(text)
cs.get_stats()
Result:
{'noun_overlap_adjacent': 0.3333333333333333,
'noun_overlap_all': 0.5,
'argument_overlap_adjacent': 0.3333333333333333,
'argument_overlap_all': 0.5,
'content_overlap_adjacent': 0.3333333333333333,
'content_overlap_all': 0.5,
'content_overlap_prop_adjacent': 0.08333333333333333,
'content_overlap_prop_all': 0.12037037037037039,
'p_pronouns': 0.034482758620689655,
'pronoun_noun_ratio': 0.09090909090909091,
'p_demonstratives': 0.0,
'p_given': 0.17647058823529413,
'tense_repetition': 0.0,
'mood_repetition': 1.0,
'temporal_cohesion': 0.5,
'connectors': 103.44827586206898,
'connectors_causal': 34.48275862068966,
'connectors_adversative': 34.48275862068966,
'connectors_concessive': 0.0,
'connectors_temporal': 0.0,
'connectors_additive': 0.0,
'connectors_conditional': 0.0,
'connectors_reformulative': 34.48275862068966,
'connectors_primary': 0.0,
'connectors_secondary': 103.44827586206898}
The occurrences of the connectors are in an attribute of their own:
cs.connector_spans
# (Connector(sent=1, start=0, end=2, text='sin embargo', cls='adversative', kind='secondary'),
# Connector(sent=2, start=0, end=2, text='es decir', cls='reformulative', kind='secondary'),
# Connector(sent=3, start=0, end=3, text='por lo tanto', cls='causal', kind='secondary'))
print_stats¶
Prints a table with the computed cohesion statistics.
To illustrate the method, we reuse the code from the previous example:
Example
Code:
...
# Print the table of computed statistics
cs.print_stats()
Result:
Statistic | Value
--------------------------------------------------------------------
Noun overlap in adjacent sentences | 0.33
Noun overlap in all pairs of sentences | 0.50
Argument overlap in adjacent sentences | 0.33
Argument overlap in all pairs of sentences | 0.50
Content word overlap in adjacent sentences | 0.33
Content word overlap in all pairs of sentences | 0.50
Share of shared content words in adjacent sentences | 0.08
Share of shared content words in all pairs of sentences | 0.12
Share of pronouns | 0.03
Ratio of pronouns to nouns | 0.09
Share of demonstratives | 0.00
Share of content words seen before | 0.18
Repetition of the tense in adjacent sentences | 0.00
Repetition of the mood in adjacent sentences | 1.00
Temporal cohesion | 0.50
Connectors per 1000 words | 103.45
Causal connectors per 1000 words | 34.48
Adversative connectors per 1000 words | 34.48
Concessive connectors per 1000 words | 0.00
Temporal connectors per 1000 words | 0.00
Additive connectors per 1000 words | 0.00
Conditional connectors per 1000 words | 0.00
Reformulative connectors per 1000 words | 34.48
Primary connectors per 1000 words | 0.00
Secondary connectors per 1000 words | 103.45
Connector search¶
ests.cohesion_stats.find_connectors(), ests.cohesion_stats.load_connectors()
find_connectors(words, connectors=None, sent_index=0, pos=None) finds the connectors of a single sentence and returns their occurrences, and load_connectors() returns the dictionary of the library, the class and the kind by connector; the dictionary is cached and read-only, so a change of it goes through the connectors parameter and not through the object returned.
Example
from ests.cohesion_stats import find_connectors, load_connectors
find_connectors(["Sin", "embargo", "no", "vino"])
# [Connector(sent=0, start=0, end=2, text='sin embargo', cls='adversative', kind='secondary')]
find_connectors(["El", "antes", "y", "el", "después"], pos=["DET", "NOUN", "CCONJ", "DET", "NOUN"])
# [Connector(sent=0, start=2, end=3, text='y', cls='additive', kind='primary')]
load_connectors()["por lo tanto"]
# ('causal', 'secondary')