pyests 0.1.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. pyests-0.3.0/PKG-INFO +714 -0
  2. pyests-0.3.0/README.es.md +675 -0
  3. pyests-0.3.0/README.md +675 -0
  4. {pyests-0.1.0 → pyests-0.3.0}/ests/__init__.py +24 -0
  5. {pyests-0.1.0 → pyests-0.3.0}/ests/basic_stats.py +27 -15
  6. pyests-0.3.0/ests/cohesion_stats.py +839 -0
  7. pyests-0.3.0/ests/components.py +518 -0
  8. pyests-0.3.0/ests/constants.py +830 -0
  9. pyests-0.3.0/ests/corpus/__init__.py +61 -0
  10. pyests-0.3.0/ests/corpus/collocations.py +319 -0
  11. pyests-0.3.0/ests/corpus/compare.py +787 -0
  12. pyests-0.3.0/ests/corpus/dispersion.py +334 -0
  13. pyests-0.3.0/ests/corpus/keyness.py +432 -0
  14. pyests-0.3.0/ests/corpus/kwic.py +169 -0
  15. pyests-0.3.0/ests/corpus/stylometry.py +635 -0
  16. pyests-0.3.0/ests/datasets/__init__.py +4 -0
  17. pyests-0.3.0/ests/datasets/dataset.py +185 -0
  18. pyests-0.3.0/ests/datasets/freq_dict.py +497 -0
  19. pyests-0.3.0/ests/datasets/spanish_literature.py +352 -0
  20. pyests-0.3.0/ests/exceptions.py +93 -0
  21. pyests-0.3.0/ests/lexical_stats.py +352 -0
  22. pyests-0.3.0/ests/morph_stats.py +424 -0
  23. pyests-0.3.0/ests/resources/README.md +15 -0
  24. pyests-0.3.0/ests/resources/connectors.tsv +256 -0
  25. pyests-0.3.0/ests/resources/google_books_top10000.txt +10000 -0
  26. pyests-0.3.0/ests/syntax_stats.py +947 -0
  27. pyests-0.3.0/ests/utils.py +777 -0
  28. pyests-0.3.0/ests/visualizers/__init__.py +25 -0
  29. pyests-0.3.0/ests/visualizers/corpus.py +229 -0
  30. pyests-0.3.0/ests/visualizers/fingerprinting.py +148 -0
  31. pyests-0.3.0/ests/visualizers/sentences.py +113 -0
  32. pyests-0.3.0/ests/visualizers/stylometry.py +182 -0
  33. pyests-0.3.0/ests/visualizers/vocabulary.py +87 -0
  34. pyests-0.3.0/ests/visualizers/word_tree.py +350 -0
  35. pyests-0.3.0/ests/visualizers/zipf.py +128 -0
  36. {pyests-0.1.0 → pyests-0.3.0}/pyproject.toml +17 -2
  37. pyests-0.1.0/PKG-INFO +0 -395
  38. pyests-0.1.0/README.es.md +0 -359
  39. pyests-0.1.0/README.md +0 -359
  40. pyests-0.1.0/ests/constants.py +0 -383
  41. pyests-0.1.0/ests/exceptions.py +0 -83
  42. pyests-0.1.0/ests/utils.py +0 -266
  43. {pyests-0.1.0 → pyests-0.3.0}/.gitignore +0 -0
  44. {pyests-0.1.0 → pyests-0.3.0}/LICENSE.txt +0 -0
  45. {pyests-0.1.0 → pyests-0.3.0}/ests/diversity_stats.py +0 -0
  46. {pyests-0.1.0 → pyests-0.3.0}/ests/extractors.py +0 -0
  47. {pyests-0.1.0 → pyests-0.3.0}/ests/py.typed +0 -0
  48. {pyests-0.1.0 → pyests-0.3.0}/ests/readability_stats.py +0 -0
  49. {pyests-0.1.0 → pyests-0.3.0}/ests/syllables.py +0 -0
pyests-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,714 @@
1
+ Metadata-Version: 2.5
2
+ Name: pyests
3
+ Version: 0.3.0
4
+ Summary: Spanish Texts Statistics
5
+ Project-URL: Homepage, https://github.com/SergeyShk/esTS
6
+ Project-URL: Repository, https://github.com/SergeyShk/esTS
7
+ Project-URL: Documentation, https://sergeyshk.github.io/esTS/
8
+ Project-URL: Issues, https://github.com/SergeyShk/esTS/issues
9
+ Author-email: Sergey Shkarin <kouki.sergey@gmail.com>
10
+ Maintainer-email: Sergey Shkarin <kouki.sergey@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE.txt
13
+ Keywords: CL,NLP,analytics,computational,language,linguistics,natural,processing,spanish,text
14
+ Classifier: Development Status :: 2 - Pre-Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Education
17
+ Classifier: Intended Audience :: Information Technology
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: Natural Language :: Spanish
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Programming Language :: Python :: 3.14
27
+ Classifier: Topic :: Text Processing
28
+ Classifier: Topic :: Text Processing :: Linguistic
29
+ Classifier: Typing :: Typed
30
+ Requires-Python: >=3.11
31
+ Requires-Dist: graphviz>=0.20.3
32
+ Requires-Dist: matplotlib>=3.8
33
+ Requires-Dist: numpy>=1.26
34
+ Requires-Dist: pandas>=2.1
35
+ Requires-Dist: scipy>=1.11
36
+ Requires-Dist: simplemma<3,>=2.0
37
+ Requires-Dist: spacy>=3.7
38
+ Description-Content-Type: text/markdown
39
+
40
+ <p align="center">
41
+ <img src="https://raw.githubusercontent.com/SergeyShk/esTS/master/docs/img/ests.png" alt="esTS" width="340">
42
+ </p>
43
+
44
+ <h1 align="center">esTS</h1>
45
+
46
+ <p align="center">
47
+ <i>¿Cómo esTáS, texto?</i>
48
+ </p>
49
+
50
+ <p align="center">
51
+ <b>Spanish Texts Statistics</b> - a library for statistics extraction from texts in Spanish
52
+ </p>
53
+
54
+ <p align="center">
55
+ <a href="https://sergeyshk.github.io/esTS/">Documentation</a> ·
56
+ <a href="https://pypi.org/project/pyests/">PyPI</a> ·
57
+ <a href="https://github.com/SergeyShk/esTS/blob/master/README.es.md">Español</a>
58
+ </p>
59
+
60
+ <p align="center">
61
+ <a href="https://pypi.org/project/pyests/"><img src="https://img.shields.io/pypi/v/pyests?logo=pypi&logoColor=FFE873" alt="Version"></a>
62
+ <a href="https://pypi.org/project/pyests/"><img src="https://img.shields.io/pypi/pyversions/pyests.svg?logo=python&logoColor=FFE873" alt="Supported Python versions"></a>
63
+ <a href="https://github.com/SergeyShk/esTS/actions/workflows/ci.yml"><img src="https://github.com/SergeyShk/esTS/actions/workflows/ci.yml/badge.svg" alt="Build"></a>
64
+ <a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"></a>
65
+ <a href="https://github.com/SergeyShk/esTS/blob/master/LICENSE.txt"><img src="https://img.shields.io/github/license/sergeyshk/esTS.svg" alt="License"></a>
66
+ <a href="https://doi.org/10.5281/zenodo.22924655"><img src="https://zenodo.org/badge/DOI/10.5281/zenodo.22924655.svg" alt="DOI"></a>
67
+ </p>
68
+
69
+ ---
70
+
71
+ **esTS** computes for Spanish texts what usually requires assembling several separate tools: basic statistics, readability, lexical diversity, lexical sophistication, morphology, syntax and cohesion - by published formulas with the coefficients and the scales of their authors, and by the parts of speech and the features of Universal Dependencies.
72
+
73
+ The library works both with raw strings and with `Doc` objects of [spaCy](https://github.com/explosion/spaCy): sentences, words and character N-grams are extracted by rules, syllables and stress follow from the orthography, and only the morphological, the syntactic, the cohesion and the lexical sophistication statistics, the profile of the function words and the comparison of corpora need a trained model.
74
+
75
+ * **[Object extraction](https://sergeyshk.github.io/esTS/extractors/sentences/)** - configurable sentence, word and character N-gram tokenizers that know the inverted marks, the dialogue dash and the abbreviations of Spanish
76
+ * **[Syllables and stress](https://sergeyshk.github.io/esTS/syllables/)** - rule-based syllabification and the stressed syllable derived from the spelling, with no dictionary
77
+ * **[Basic statistics](https://sergeyshk.github.io/esTS/stats/basic_stats/)** - counts of sentences, words, letters, syllables and punctuation marks by type, with distributions and normalized shares
78
+ * **[Readability metrics](https://sergeyshk.github.io/esTS/stats/readability_stats/)** - Fernández Huerta, Szigriszt-Pazos with the INFLESZ scale, Gutiérrez de Polini, Crawford, Legibilidad µ, SOL, LIX and RIX, with a consensus grade, the school stages of Spain and reading time
79
+ * **[Lexical diversity metrics](https://sergeyshk.github.io/esTS/stats/diversity_stats/)** - TTR and its variations, MATTR, MSTTR, MTLD, HD-D, Simpson's and Yule's indices, entropy, Zipf's and Heaps' laws
80
+ * **[Morphological statistics](https://sergeyshk.github.io/esTS/stats/morph_stats/)** - parts of speech and fifteen grammatical features of Universal Dependencies, with the markers of Spanish: the moods, the non-finite forms, `ser` against `estar`, the adverbs in `-mente`
81
+ * **[Corpus measures](https://sergeyshk.github.io/esTS/corpus/keyness/)** - keywords against a reference corpus, collocations, the dispersion of a word over the parts of a text, a KWIC concordance and the stylometry of authorship: Burrows's Delta with its variants, Zeta, the Mendenhall curve, the profile of the function words; the comparison of two corpora by 132 features of a text with effect sizes
82
+ * **[Visualizers](https://sergeyshk.github.io/esTS/visualizers/zipf/)** - Zipf's law, literature fingerprinting, a word tree, lexical dispersion and keywords, a network of collocations, a dendrogram, PCA and MDS by Delta, vocabulary growth, sentence lengths
83
+ * **[Datasets](https://sergeyshk.github.io/esTS/datasets/spanishliterature/)** - Spanish-language literature in the public domain: 150 works by 33 authors from Spain, Latin America and the Philippines in prose, poems, drama and publicism, with the genre, the years and the country; a frequency dictionary of 83,785 lemmas by Google Books Ngram with ipm, range and dispersion
84
+ * **[spaCy components](https://sergeyshk.github.io/esTS/components/)** - every statistics class as a component of a pipeline, the statistics attached to the `Doc` in one pass
85
+ * **[Cohesion statistics](https://sergeyshk.github.io/esTS/stats/cohesion_stats/)** - the overlap of nouns, arguments and content words between sentences, givenness and temporal cohesion in the manner of Coh-Metrix, with the density of 255 Spanish discourse markers
86
+ * **[Lexical sophistication statistics](https://sergeyshk.github.io/esTS/stats/lexical_stats/)** - how rare the words of a text are in the language: the frequency, range and dispersion of the lemmas by a dictionary of Google Books Ngram, the frequency bands top-1000 to 10000, surprisal, perplexity and lexical density
87
+ * **[Syntactic statistics](https://sergeyshk.github.io/esTS/stats/syntax_stats/)** - the dependency tree by distances, depth, clauses and coordination, with the constructions of the administrative style: the passive with `ser` and with `se`, the participial and the gerund clauses, the chains of `de`, the split predicates
88
+
89
+ Style, phonostatistics, metre and rhyme come in 0.4.
90
+
91
+ ## Installation
92
+
93
+ Requires Python 3.11 or newer.
94
+
95
+ ```bash
96
+ pip install pyests
97
+ ```
98
+
99
+ Or with [uv](https://docs.astral.sh/uv/):
100
+
101
+ ```bash
102
+ uv add pyests
103
+ ```
104
+
105
+ The distribution on PyPI is `pyests`, the package it installs is `ests`. The basic statistics, the readability and the lexical diversity metrics need no spaCy model; the morphological, the syntactic, the cohesion and the lexical sophistication statistics of a string do, and so do the profile of the function words, the features of a text and the comparison of corpora, and parsing a text yourself to pass the `Doc` instead of a string:
106
+
107
+ ```bash
108
+ python -m spacy download es_core_news_sm
109
+ ```
110
+
111
+ The statistics by the frequency dictionary need it downloaded once with `FreqDict().download()`. The datasets go to the directory `ests_data` next to the installed package; another one is passed in `data_dir` or set in the environment variable `ESTS_DATA_DIR` before the package is imported.
112
+
113
+ ## Quick start
114
+
115
+ ```python
116
+ >>> from ests import BasicStats, DiversityStats, ReadabilityStats
117
+
118
+ >>> text = "Hay tres clases de mentiras: mentiras, malditas mentiras y estadísticas"
119
+
120
+ >>> BasicStats(text).get_stats()
121
+ {'c_letters': {1: 1, 2: 1, 3: 1, 4: 1, 6: 1, 8: 4, 12: 1},
122
+ 'c_syllables': {1: 4, 2: 1, 3: 4, 5: 1},
123
+ 'n_sents': 1,
124
+ 'n_words': 10,
125
+ 'n_unique_words': 8,
126
+ 'n_long_words': 5,
127
+ 'n_complex_words': 5,
128
+ 'n_simple_words': 5,
129
+ 'n_monosyllable_words': 4,
130
+ 'n_polysyllable_words': 6,
131
+ 'n_chars': 71,
132
+ 'n_letters': 60,
133
+ 'n_spaces': 9,
134
+ 'n_syllables': 23,
135
+ 'n_punctuations': 2,
136
+ 'c_punctuations': {'comma': 1, 'period': 0, 'question': 0, 'exclamation': 0,
137
+ 'ellipsis': 0, 'colon': 1, 'semicolon': 0, 'dash': 0,
138
+ 'hyphen': 0, 'angle_quotes': 0, 'straight_quotes': 0,
139
+ 'parentheses': 0, 'other': 0}}
140
+
141
+ >>> ReadabilityStats(text).flesch_reading_easy
142
+ 53.545000000000016
143
+
144
+ >>> DiversityStats(text).ttr
145
+ 0.8
146
+ ```
147
+
148
+ ## Features
149
+
150
+ ### Object extraction
151
+
152
+ The library allows creating your own tools for sentence, word and character N-gram extraction from a text, which can be further employed for counting statistics. The sentence splitter knows the inverted marks, the dash of a line of dialogue with the remark of the narrator, the abbreviations and the initials of Spanish; the word tokenizer keeps clitics, ordinals and numbers written the Spanish way together, and lemmas come from [simplemma](https://github.com/adbar/simplemma), which needs no model.
153
+
154
+ ```python
155
+ >>> from ests import CharNgramsExtractor, SentsExtractor, WordsExtractor
156
+
157
+ >>> SentsExtractor().extract("—¿Vienes? —preguntó María. ¡Claro que sí!")
158
+ ('—¿Vienes? —preguntó María.', '¡Claro que sí!')
159
+
160
+ >>> we = WordsExtractor(use_lexemes=True, stopwords=["de", "y"], filter_nums=True, ngram_range=(1, 2))
161
+ >>> we.extract("Hay 3 clases de mentiras y estadísticas")
162
+ ('haber', 'clase', 'mentira', 'estadística', 'haber_clase', 'clase_mentira', 'mentira_estadística')
163
+
164
+ >>> CharNgramsExtractor(n=3, lowercase=True).extract("estadísticas")[:5]
165
+ ('est', 'sta', 'tad', 'adí', 'dís')
166
+ ```
167
+
168
+ More in the [documentation](https://sergeyshk.github.io/esTS/extractors/sentences/).
169
+
170
+ <details>
171
+ <summary><b>Syllables and stress</b></summary>
172
+
173
+ <br>
174
+
175
+ Spanish spelling encodes both the syllable boundaries and the stress, so the library needs no dictionary: diphthongs, hiatuses and triphthongs, the silent `u` of `qu` and `gu`, the vocalic `y`, the `h` inside a diphthong and the consonant clusters give the syllables; the written accent, or the ending of the word when there is none, gives the stressed syllable. Adverbs in `-mente` and hyphenated compounds carry two stresses.
176
+
177
+ ```python
178
+ >>> from ests.syllables import stress_type, syllabify, word_stress, word_stresses
179
+
180
+ >>> syllabify("murciélago")
181
+ ['mur', 'cié', 'la', 'go']
182
+
183
+ >>> syllabify("averiguáis")
184
+ ['a', 've', 'ri', 'guáis']
185
+
186
+ >>> word_stress("construir"), stress_type("construir")
187
+ (1, 'aguda')
188
+
189
+ >>> word_stresses("fácilmente")
190
+ [0, 2]
191
+ ```
192
+
193
+ More in the [documentation](https://sergeyshk.github.io/esTS/syllables/).
194
+
195
+ </details>
196
+
197
+ <details>
198
+ <summary><b>Basic statistics</b></summary>
199
+
200
+ <br>
201
+
202
+ The library allows extracting the following statistics from a text:
203
+
204
+ * the number of sentences
205
+ * the number of words
206
+ * the number of unique words
207
+ * the number of long words
208
+ * the number of complex words
209
+ * the number of simple words
210
+ * the number of monosyllable words
211
+ * the number of polysyllable words
212
+ * the number of characters
213
+ * the number of letters
214
+ * the number of spaces
215
+ * the number of syllables
216
+ * the number of punctuation marks and their distribution by type
217
+ * the distribution of words by the number of letters
218
+ * the distribution of words by the number of syllables
219
+
220
+ A complex word has three or more syllables and a long word seven or more letters, as the Spanish readability formulas count them. Any statistic can be printed in a readable form:
221
+
222
+ ```python
223
+ >>> from ests import BasicStats
224
+
225
+ >>> text = "Hay tres clases de mentiras: mentiras, malditas mentiras y estadísticas"
226
+ >>> BasicStats(text).print_stats()
227
+ Statistic | Value
228
+ ------------------------------
229
+ Sentences | 1
230
+ Words | 10
231
+ Unique words | 8
232
+ Long words | 5
233
+ Complex words | 5
234
+ Simple words | 5
235
+ Monosyllabic words | 4
236
+ Polysyllabic words | 6
237
+ Characters | 71
238
+ Letters | 60
239
+ Spaces | 9
240
+ Syllables | 23
241
+ Punctuation marks | 2
242
+ ```
243
+
244
+ More in the [documentation](https://sergeyshk.github.io/esTS/stats/basic_stats/).
245
+
246
+ </details>
247
+
248
+ <details>
249
+ <summary><b>Readability metrics</b></summary>
250
+
251
+ <br>
252
+
253
+ The library allows counting the following readability metrics:
254
+
255
+ * Flesch reading ease with the coefficients of Szigriszt-Pazos or of Fernández Huerta
256
+ * Gutiérrez de Polini comprehensibility formula
257
+ * Crawford grade
258
+ * Legibilidad µ
259
+ * SOL grade, the SMOG index converted to Spanish
260
+ * LIX readability measure
261
+ * RIX readability measure
262
+
263
+ An interpretation layer works on top of the formulas: the band of a scale for the reading ease and for Legibilidad µ, a consensus grade as the median of the grade formulas, the school stage and the reader age of Spain, and reading time by the norms of Spanish-speaking readers.
264
+
265
+ The coefficients of the Flesch reading ease are selected by the `preset` argument: by default the *fórmula de perspicuidad* of Szigriszt-Pazos with the INFLESZ scale validated on texts for patients (`general`); the coefficients of Fernández Huerta with the bands of their author are available as `classic`.
266
+
267
+ ```python
268
+ >>> from pprint import pprint
269
+ >>> from ests import ReadabilityStats
270
+
271
+ >>> text = "Hay tres clases de mentiras: mentiras, malditas mentiras y estadísticas"
272
+ >>> rs = ReadabilityStats(text)
273
+
274
+ >>> pprint(rs.get_stats(), sort_dicts=False)
275
+ {'flesch_reading_easy': 53.545000000000016,
276
+ 'gutierrez_polini_index': 33.5,
277
+ 'crawford_grade': 5.812999999999999,
278
+ 'mu_index': 50.943396226415096,
279
+ 'sol_grade': 9.258359866374562,
280
+ 'lix': 60.0,
281
+ 'rix': 5.0,
282
+ 'consensus_grade': 9.0,
283
+ 'reading_time': 0.03597122302158273}
284
+
285
+ >>> rs.print_stats()
286
+ Metric | Value
287
+ -------------------------------------------------------
288
+ Flesch reading ease (Szigriszt-Pazos) | 53.55
289
+ Gutiérrez de Polini comprehensibility | 33.50
290
+ Crawford grade | 5.81
291
+ Legibilidad µ | 50.94
292
+ SOL grade (SMOG for Spanish) | 9.26
293
+ LIX readability index | 60.00
294
+ RIX readability index | 5.00
295
+ Consensus grade | 9.00
296
+ Reading time (min) | 0.04
297
+
298
+ >>> rs.describe_level()
299
+ 'algo difícil'
300
+
301
+ >>> rs.describe_grade()
302
+ 'ESO (12-16 years)'
303
+ ```
304
+
305
+ More in the [documentation](https://sergeyshk.github.io/esTS/stats/readability_stats/).
306
+
307
+ </details>
308
+
309
+ <details>
310
+ <summary><b>Lexical diversity metrics</b></summary>
311
+
312
+ <br>
313
+
314
+ The library allows counting 32 lexical diversity metrics, among them:
315
+
316
+ * Type-Token Ratio and its variations: RTTR, CTTR, Herdan, Summer, Maas, Dugast
317
+ * Moving Average Type-Token Ratio and Mean Segmental Type-Token Ratio
318
+ * Measure of Textual Lexical Diversity and its moving-window variants MA-MTLD and MTLD-W
319
+ * Hypergeometric Distribution D
320
+ * Simpson's index, its reciprocal and the Gini-Simpson index
321
+ * the hapax index (Honoré's R), the measures of Yule, Herdan, Sichel, Michéa, Brunet, Dugast and Baayen
322
+ * Shannon entropy, evenness and perplexity
323
+ * the slope of Zipf's law, the Zipf-Mandelbrot fit and the exponent of Heaps' law
324
+
325
+ Any metric can be computed over windows of equal length, which is the standard way to compare texts of different lengths: the mean over the windows comes with a confidence interval.
326
+
327
+ ```python
328
+ >>> from ests import DiversityStats
329
+
330
+ >>> text = "Hay tres clases de mentiras: mentiras, malditas mentiras y estadísticas"
331
+ >>> ds = DiversityStats(text)
332
+
333
+ >>> ds.ttr, ds.mtld, ds.yule_k
334
+ (0.8, 14.000000000000004, 600.0)
335
+
336
+ >>> ds.frequency_spectrum
337
+ {1: 7, 3: 1}
338
+
339
+ >>> DiversityStats("La legibilidad de un texto depende de la longitud de sus oraciones y de sus "
340
+ ... "palabras. Las fórmulas clásicas miden esas dos magnitudes y las combinan en "
341
+ ... "un solo número. Ninguna de ellas mide la comprensión: miden la superficie "
342
+ ... "del texto.").windowed("ttr", window_len=10)
343
+ WindowStats(mean=0.875, std=0.1258305739211792, lower=0.6747754774664074, upper=1.0752245225335926, n_windows=4)
344
+ ```
345
+
346
+ More in the [documentation](https://sergeyshk.github.io/esTS/stats/diversity_stats/).
347
+
348
+ </details>
349
+
350
+ <details>
351
+ <summary><b>Morphological statistics</b></summary>
352
+
353
+ <br>
354
+
355
+ The library annotates a text with the parts of speech and the grammatical features of Universal Dependencies, as the Spanish models of spaCy give them, and counts them:
356
+
357
+ * the part of speech and fifteen features: case, definiteness, degree, gender, mood, numeral type, number, person, polarity, politeness, possessive, pronoun type, reflexive, tense and verb form
358
+ * the distribution of the words by the values of any feature, and the parse of the text word by word
359
+ * the markers of Spanish: the moods among the finite forms, the non-finite forms, `ser` against `estar`, the adverbs in `-mente`
360
+
361
+ ```python
362
+ >>> from ests import MorphStats
363
+
364
+ >>> ms = MorphStats("Si tuviera tiempo, leería el libro que me recomendaste ayer")
365
+
366
+ >>> ms.get_stats("mood", "tense", filter_none=True)
367
+ {'mood': {'Sub': 1, 'Cnd': 1, 'Ind': 1}, 'tense': {'Imp': 1, 'Pres': 1}}
368
+
369
+ >>> ms.tags[1]
370
+ 'Mood=Sub|Number=Sing|Person=3|Tense=Imp|VerbForm=Fin'
371
+
372
+ >>> ms.explain_text("pos", "mood", filter_none=True)[1]
373
+ ('tuviera', {'pos': 'VERB', 'mood': 'Sub'})
374
+
375
+ >>> MorphStats("Ella es alta pero hoy está cansada y habla lentamente").get_markers()["p_ser"]
376
+ 0.5
377
+ ```
378
+
379
+ The statistics need a spaCy model: a text is parsed with `es_core_news_sm`, and any other pipeline can be passed in `nlp`.
380
+
381
+ More in the [documentation](https://sergeyshk.github.io/esTS/stats/morph_stats/).
382
+
383
+ </details>
384
+
385
+ <details>
386
+ <summary><b>Syntactic statistics</b></summary>
387
+
388
+ <br>
389
+
390
+ The library measures the dependency tree of Universal Dependencies and the constructions that the Spanish guides to clear language warn about:
391
+
392
+ * the complexity of the tree: dependency distances, depth, leaves and subtrees, valency of the finite verbs, coordination chains, clauses and subordinate clauses, modifiers per noun
393
+ * the constructions: the passive with `ser` and with `se`, the participial and the gerund clauses, the chains of `de`, the split predicates, the impersonal `se`, the words of negation, the ratio of nouns to verbs
394
+
395
+ ```python
396
+ >>> from ests import SyntaxStats
397
+
398
+ >>> text = ("La revisión de las cuentas fue realizada por el comité. "
399
+ ... "Se llevó a cabo la reforma sin que nadie hiciera mención de los problemas.")
400
+ >>> ss = SyntaxStats(text)
401
+
402
+ >>> ss.n_sents, ss.n_words
403
+ (2, 24)
404
+
405
+ >>> ss.p_passive, ss.noun_verb_ratio
406
+ (0.6666666666666666, 2.3333333333333335)
407
+
408
+ >>> ss.split_predicates
409
+ ('llevó cabo', 'hiciera mención')
410
+
411
+ >>> round(ss.mean_dependency_distance, 2)
412
+ 2.05
413
+ ```
414
+
415
+ The statistics need a parse: a text is parsed with `es_core_news_sm`, and any other pipeline can be passed in `nlp`.
416
+
417
+ More in the [documentation](https://sergeyshk.github.io/esTS/stats/syntax_stats/).
418
+
419
+ </details>
420
+
421
+ <details>
422
+ <summary><b>Cohesion statistics</b></summary>
423
+
424
+ <br>
425
+
426
+ The library measures referential cohesion in the manner of Coh-Metrix and of its Spanish adaptation Coh-Metrix-Esp:
427
+
428
+ * the overlap of nouns, of arguments and of content words between adjacent sentences and between all pairs of sentences, binary and proportional
429
+ * givenness: pronouns, demonstratives and the content words whose lemma was already used
430
+ * temporal cohesion: the repetition of the tense and of the mood of the verbs of adjacent sentences
431
+ * the density of 255 Spanish discourse markers by class - causal, adversative, concessive, temporal, additive, conditional, reformulative - and by kind
432
+
433
+ ```python
434
+ >>> from ests import CohesionStats
435
+
436
+ >>> text = ("El informe fue aprobado por la comisión. Sin embargo, el informe no resuelve el problema. "
437
+ ... "Por lo tanto, la comisión aplazó la decisión.")
438
+ >>> cs = CohesionStats(text)
439
+
440
+ >>> cs.n_sents, cs.n_words
441
+ (3, 23)
442
+
443
+ >>> cs.noun_overlap_adjacent, round(cs.p_given, 3)
444
+ (0.5, 0.167)
445
+
446
+ >>> cs.c_connectors
447
+ {'por lo tanto': 1, 'sin embargo': 1}
448
+
449
+ >>> round(cs.connectors, 2), round(cs.connectors_causal, 2)
450
+ (86.96, 43.48)
451
+ ```
452
+
453
+ The statistics need the annotation: a text is parsed with `es_core_news_sm`, and any other pipeline can be passed in `nlp`.
454
+
455
+ More in the [documentation](https://sergeyshk.github.io/esTS/stats/cohesion_stats/).
456
+
457
+ </details>
458
+
459
+ <details>
460
+ <summary><b>Lexical sophistication statistics</b></summary>
461
+
462
+ <br>
463
+
464
+ How rare the words of a text are in the language, in the manner of TAALES: the mean frequency, range and dispersion of the lemmas by the frequency dictionary of Google Books Ngram, the shares of the words of the frequency bands top-1000, 2000, 5000 and 10000, the surprisal and the perplexity by the unigram model of the dictionary, the lexical density. The bands and the density work out of the box; the statistics by the dictionary need it downloaded once.
465
+
466
+ ```python
467
+ >>> from ests import LexicalStats
468
+ >>> from ests.datasets import FreqDict
469
+
470
+ >>> FreqDict().download()
471
+ >>> ls = LexicalStats("El felinólogo examinaba al minino con parsimonia")
472
+ >>> ls.coverage, ls.p_top1000, round(ls.surprisal, 2)
473
+ (0.8571428571428571, 0.42857142857142855, 13.71)
474
+ ```
475
+
476
+ More in the [documentation](https://sergeyshk.github.io/esTS/stats/lexical_stats/).
477
+
478
+ </details>
479
+
480
+ <details>
481
+ <summary><b>spaCy components</b></summary>
482
+
483
+ <br>
484
+
485
+ Every statistics class is also a component of a pipeline, so a text is annotated and measured in one pass and the statistics travel with the `Doc`:
486
+
487
+ ```python
488
+ >>> import ests
489
+ >>> import spacy
490
+
491
+ >>> nlp = spacy.load("es_core_news_sm")
492
+ >>> for factory in ("basic", "morph", "syntax"):
493
+ ... _ = nlp.add_pipe(f"ests_{factory}", name=factory, last=True)
494
+
495
+ >>> nlp.pipe_names[-3:]
496
+ ['basic', 'morph', 'syntax']
497
+
498
+ >>> doc = nlp("El gato duerme en la ventana. Los niños juegan en el parque.")
499
+ >>> doc._.basic.n_words, doc._.morph.pos[:2], doc._.syntax.tree_depth
500
+ (12, ('DET', 'NOUN'), 2.0)
501
+ ```
502
+
503
+ The factories are `ests_basic`, `ests_readability`, `ests_diversity`, `ests_morph`, `ests_syntax`, `ests_cohesion` and `ests_lexical`; the name of the pipe is free and is what the extension is called.
504
+
505
+ More in the [documentation](https://sergeyshk.github.io/esTS/components/).
506
+
507
+ </details>
508
+
509
+ <details>
510
+ <summary><b>Corpus measures</b></summary>
511
+
512
+ <br>
513
+
514
+ The library compares corpora and describes the use of a word, with the measures of corpus linguistics:
515
+
516
+ * keywords of a target corpus against a reference one or against the frequency dictionary of Google Books Ngram: the log-likelihood with its p-value, Log Ratio, chi-square, %DIFF, BIC, ELL and the odds ratio
517
+ * collocations by logDice, MI, MI³, t-score, Dice, log-likelihood, NPMI and minimum sensitivity, checked against NLTK
518
+ * the dispersion of a word over the parts of a text: DP of Gries, normalized DP, Juilland's D, Carroll's D2, Rosengren's S and the Kullback-Leibler divergence
519
+ * a KWIC concordance by word form or by lemma
520
+ * stylometry: the distances between texts by Burrows's Delta and its variants, with the attribution of a text to reference authors, the markers of preferred and avoided words by Zeta, Kilgarriff's chi-square, the Mendenhall curve and the profile of the function words
521
+ * the comparison of two corpora by 132 features of a text over windows of about the same size: Cliff's delta, Cohen's d and the AUC of every feature, the Mann-Whitney test with Holm's correction and a bootstrap interval of the difference of the medians that resamples whole texts
522
+
523
+ ```python
524
+ >>> from ests import WordsExtractor
525
+ >>> from ests.corpus import collocations, keyness, kwic, zeta
526
+
527
+ >>> we = WordsExtractor(use_lexemes=True, lowercase=True)
528
+ >>> target = we.extract("El gato estaba en la ventana y miraba a los pájaros. Los pájaros se fueron y el gato "
529
+ ... "se durmió en la ventana. Mañana el gato volverá a estar en la ventana y mirará a los pájaros.")
530
+ >>> reference = we.extract("El perro estaba en el suelo y dormía. Después el perro comió y volvió a dormir. "
531
+ ... "Mañana el perro saldrá a pasear.")
532
+
533
+ >>> [(k.word, round(k.g2, 2)) for k in keyness(target, reference, top_n=2)]
534
+ [('gato', 2.8), ('pájaro', 2.8)]
535
+
536
+ >>> [(c.left, c.right, round(c.score, 1)) for c in collocations(target, window=2, top_n=2)]
537
+ [('en', 'ventana', 13.0), ('estar', 'en', 12.7)]
538
+
539
+ >>> [line.keyword for line in kwic("Los gatos juegan y el gato duerme.", "gato", by_lemma=True)]
540
+ ['gatos', 'gato']
541
+
542
+ >>> scores = zeta(target, reference, segment_size=10)
543
+ >>> [z.word for z in scores[:2]], [z.word for z in scores[-2:]]
544
+ (['gato', 'ventana'], ['dormir', 'perro'])
545
+ ```
546
+
547
+ Words are compared as they are, so case, lemmas and stop words are chosen at the extraction.
548
+
549
+ More in the [documentation](https://sergeyshk.github.io/esTS/corpus/keyness/).
550
+
551
+ </details>
552
+
553
+ <details>
554
+ <summary><b>Datasets</b></summary>
555
+
556
+ <br>
557
+
558
+ * [spanish_literature](https://sergeyshk.github.io/esTS/datasets/spanishliterature/) - Spanish-language literature in the public domain: 150 works by 33 authors from Spain, Latin America and the Philippines, from Cervantes to the 1920s, in prose, poems, drama and publicism; 65 million characters
559
+ * [freq_dict](https://sergeyshk.github.io/esTS/datasets/freqdict/) - a frequency dictionary of 83,785 Spanish lemmas from the books of Google Books Ngram of 1980-2019 (63 billion words): ipm, range and Juilland's D over the years, the number of books and the part of speech (CC BY 3.0)
560
+
561
+ The texts are cut to the text of the author, without title pages, notes of the transcribers and the editors, tables of contents and footnotes, and come with the genre, the author, the title, the years of the first publication and the country; the records can be filtered by any of them and by the length of the text.
562
+
563
+ ```python
564
+ >>> from ests.datasets import SpanishLiterature
565
+
566
+ >>> sl = SpanishLiterature()
567
+ >>> sl.download()
568
+ >>> for record in sl.get_records(author="galdos", year_from=1880, year_to=1884):
569
+ ... print(record["title"], record["year_from"], len(record["text"]))
570
+ La desheredada 1881 820454
571
+ El amigo Manso 1882 521156
572
+ La de Bringas 1884 413730
573
+ Tormento 1884 477286
574
+ ```
575
+
576
+ The archive (19 MB) is downloaded once by `download()` into the data directory and verified against its SHA-256 checksum; before the download `get_texts()` and `get_records()` raise `DatasetNotFoundError` with a hint.
577
+
578
+ More in the [documentation](https://sergeyshk.github.io/esTS/datasets/spanishliterature/).
579
+
580
+ </details>
581
+
582
+ <details>
583
+ <summary><b>Visualizers</b></summary>
584
+
585
+ <br>
586
+
587
+ * [Zipf's law](https://sergeyshk.github.io/esTS/visualizers/zipf/) with the theoretical curve and the Zipf-Mandelbrot fit
588
+ * [Literature fingerprinting](https://sergeyshk.github.io/esTS/visualizers/fingerprinting/) (Literature Fingerprinting)
589
+ * [Word tree](https://sergeyshk.github.io/esTS/visualizers/word_tree/) (Word Tree)
590
+ * [Corpus plots](https://sergeyshk.github.io/esTS/visualizers/corpus/): lexical dispersion, a chart of keywords, a network of collocations
591
+ * [Stylometric plots](https://sergeyshk.github.io/esTS/visualizers/stylometry/): a dendrogram, the principal components and the multidimensional scaling by Delta, the Mendenhall curves
592
+ * [Vocabulary growth and frequency spectrum](https://sergeyshk.github.io/esTS/visualizers/vocabulary/), [sentence lengths](https://sergeyshk.github.io/esTS/visualizers/sentences/) with a moving average
593
+
594
+ The matplotlib plots take the axes `ax` and return `Axes`, so they can be laid out on one figure; the network of collocations and the word tree are graphs of graphviz, whose executables render them. Cosine Delta separates three novels by Galdós from three by Unamuno:
595
+
596
+ ```python
597
+ import matplotlib.pyplot as plt
598
+ from ests import WordsExtractor
599
+ from ests.corpus import delta
600
+ from ests.datasets import SpanishLiterature
601
+ from ests.visualizers import dendrogram_plot, pca_plot
602
+
603
+ # six novels by Galdós and Unamuno from the corpus of literature
604
+ titles = (
605
+ "Marianela",
606
+ "Misericordia",
607
+ "Torquemada en la hoguera",
608
+ "Niebla",
609
+ "Abel Sánchez",
610
+ "La tía Tula",
611
+ )
612
+ sl = SpanishLiterature()
613
+ sl.download()
614
+ novels = {
615
+ record["title"]: record["text"]
616
+ for author in ("galdos", "unamuno")
617
+ for record in sl.get_records(author=author)
618
+ if record["title"] in titles
619
+ }
620
+ we = WordsExtractor(lowercase=True)
621
+ corpus = {title: we.extract(novels[title]) for title in titles}
622
+ distances = delta(corpus, n_mfw=100, variant="cosine")
623
+
624
+ fig, (left, right) = plt.subplots(1, 2, figsize=(13, 5))
625
+ dendrogram_plot(distances, ax=left)
626
+ pca_plot(corpus, n_mfw=100, ax=right)
627
+ ```
628
+
629
+ <p align="center">
630
+ <img src="https://raw.githubusercontent.com/SergeyShk/esTS/master/docs/img/stylometry.png" alt="Stylometric plots" width="760">
631
+ </p>
632
+
633
+ More in the [documentation](https://sergeyshk.github.io/esTS/visualizers/zipf/).
634
+
635
+ </details>
636
+
637
+ ## Development
638
+
639
+ The project uses [uv](https://docs.astral.sh/uv/) for dependency management and [ruff](https://docs.astral.sh/ruff/) for linting and formatting.
640
+
641
+ ```bash
642
+ git clone https://github.com/SergeyShk/esTS.git
643
+ cd esTS
644
+
645
+ make deps # create the environment and install dependencies
646
+ make test # run the tests and docstring examples (doctest)
647
+ make lint # ruff + mypy
648
+ ```
649
+
650
+ Run `make help` for the full list of commands.
651
+
652
+ The documentation is bilingual: English pages are `docs/*.md`, Spanish ones are `docs/*.es.md` next to them ([mkdocs-static-i18n](https://github.com/ultrabug/mkdocs-static-i18n)); when editing a page, update both versions.
653
+
654
+ The installed version is `ests.__version__`. All exceptions inherit `ests.EstsError` and one of the built-in classes (`SourceError`, `ParameterError` and `DataFileError` - `ValueError`, `SourceTypeError` - `TypeError`, `UnknownStatError` - `KeyError`, `DatasetNotFoundError` - `OSError`, `DownloadError` - `RuntimeError`), so `except ValueError` keeps working. The library prints nothing on its own: its messages go to the `ests` logger (`logging.getLogger("ests")`) and are silent by default.
655
+
656
+ Before submitting changes, install the hooks that run the linters on commit and the tests on push:
657
+
658
+ ```bash
659
+ uv run pre-commit install
660
+ ```
661
+
662
+ ## Contributing
663
+
664
+ Bug reports, ideas and pull requests are welcome - [issues](https://github.com/SergeyShk/esTS/issues) are open. The workflow, the checks to run before submitting a pull request and how to shape the changes are described in [CONTRIBUTING.md](https://github.com/SergeyShk/esTS/blob/master/CONTRIBUTING.md); the rules of conduct are in the [code of conduct](https://github.com/SergeyShk/esTS/blob/master/CODE_OF_CONDUCT.md).
665
+
666
+ <details>
667
+ <summary><b>Project structure</b></summary>
668
+
669
+ <br>
670
+
671
+ * **docs** - project documentation
672
+ * **ests**:
673
+ * basic_stats.py - basic text statistics
674
+ * cohesion_stats.py - cohesion statistics
675
+ * lexical_stats.py - lexical sophistication statistics
676
+ * components.py - components of a spaCy pipeline
677
+ * corpus - measures of corpus linguistics: keywords, collocations, dispersion, concordance, stylometry, comparison of corpora
678
+ * datasets - datasets: Spanish-language literature, the frequency dictionary
679
+ * constants.py - constants of the Spanish language and of the metrics
680
+ * diversity_stats.py - lexical diversity metrics
681
+ * exceptions.py - library exceptions
682
+ * extractors.py - tools for object extraction from a text
683
+ * morph_stats.py - morphological statistics
684
+ * readability_stats.py - readability metrics
685
+ * syntax_stats.py - syntactic statistics
686
+ * syllables.py - syllabification and stress
687
+ * utils.py - helper tools
688
+ * visualizers - plots: Zipf's law, fingerprinting, word tree, corpus and stylometric plots, vocabulary growth, sentence lengths
689
+ * **scripts** - scripts that build the archives of the datasets
690
+ * **tests** - tests mirroring the package structure
691
+
692
+ </details>
693
+
694
+ ## Authors
695
+
696
+ * Sergey Shkarin (kouki.sergey@gmail.com)
697
+
698
+ ## License
699
+
700
+ [MIT](https://github.com/SergeyShk/esTS/blob/master/LICENSE.txt)
701
+
702
+ ## Citation
703
+
704
+ Please use the following BibTeX entry for citing **esTS** if you use it in your research or software. Citations are helpful for the continued development and maintenance of this library. The same metadata is in [CITATION.cff](https://github.com/SergeyShk/esTS/blob/master/CITATION.cff) - GitHub shows it under the "Cite this repository" button. The Concept DOI [10.5281/zenodo.22924655](https://doi.org/10.5281/zenodo.22924655) on Zenodo points at every version of the library; the DOI of a single version is on the page of its release.
705
+
706
+ ```bibtex
707
+ @software{esTS,
708
+ author = {Sergey Shkarin},
709
+ title = {{esTS, a library for statistics extraction from texts in Spanish}},
710
+ year = 2026,
711
+ doi = {10.5281/zenodo.22924655},
712
+ url = {https://github.com/SergeyShk/esTS}
713
+ }
714
+ ```