Natural language processing (NLP): is a discipline where computer science, artificial intelligence and cognitive logic are intercepted, with the objective that machines can read and understand our language for decision making [1].
spaCy: features fast statistical NER as well as an open-source named-entity visualizer [2].
# Load Python libraries
import io
import random
from collections import Counter
# Load NLP libraries from spacy
import spacy
# Verify installed spacy version
spacy.__version__
'3.0.6'
# Util function to read a plain text file
def read_text_file(file_path, encoding='ISO-8859-1'):
text = ""
with open(file_path, 'r', encoding=encoding) as f:
text = f.read()
return text
# Get text sample
file_path = "../data/es/El Grillo del Hogar - Charles Dickens.txt"
book_text = read_text_file(file_path)
# Show first 1000 raw characters of document
book_text[:1000]
'El grillo del hogar\n(The Cricket of the Heard)\nde\n\nCharles Dickens\nEste libro electrónico es cortesía de\n\nhttp://www.dominiopublico.es\nPrimer grito\nCapítulo I\nCapítulo II\nCapítulo III\nCapítulo IV\nCapítulo V\nCapítulo VI\nSegundo grito\nCapítulo I\nCapítulo II\nCapítulo III\nCapítulo IV\nCapítulo V\nCapítulo VI\nTercer grito\nCapítulo I\nCapítulo II\nCapítulo III\nCapítulo IV\nCapítulo V\nCapítulo VI\n\nPrimer grito\n- I -\nEmpezó el puchero. No necesito que me contéis lo que la señora Peerybingle dijera; yo me entiendo. Dejad que la señora Peerybingle se pase hasta la consumación de los siglos asegurando la imposibilidad de decidir cuál empezó: yo digo que fue el puchero. Tengo motivos para saberlo. El puchero empezó cinco minutos antes que el grillo, según el relojito holandés de cuadrante barnizado situado en el rincón.\n¡Como si el reloj no hubiese cesado de tocar! ¡Como si el segadorcido de movimientos convulsivos y bruscos que lo remata, paseando la hoz de derecha a izquierda y luego de izquierda a d'
# Create NLP model for spanish language
nlp = spacy.load('es_core_news_sm')
doc_es = nlp(book_text)
- Vocabulary: unique words of the document.
# Get vocabulary
vocabulary_es = set(str(token).lower() for token in doc_es if not token.is_stop and token.is_alpha)
len(vocabulary_es)
5613
# Show 100 random words of the vocabulary
print(random.sample(vocabulary_es, 100))
['rastrojos', 'esperad', 'dicha', 'reconozco', 'ajeno', 'particularmente', 'ocasiones', 'volveré', 'reproducir', 'peatones', 'guiarme', 'imagen', 'pendía', 'delgado', 'bastantes', 'llenando', 'sinónimo', 'caballos', 'atravesaban', 'ocultó', 'cuero', 'extemporáneo', 'alta', 'infinidad', 'inconcebible', 'consoladoras', 'siglo', 'simpatía', 'consolarse', 'hogar', 'pensionado', 'punta', 'levantarse', 'vigoroso', 'agradable', 'salió', 'perfecta', 'carcajada', 'cabecera', 'apasionado', 'estrambótica', 'demuestra', 'encontrarse', 'presente', 'examinarle', 'helaría', 'abramos', 'empeño', 'acudió', 'iniciales', 'enmohecía', 'cancioncilla', 'permitieseis', 'diversión', 'envía', 'cogerle', 'consejos', 'trabajaban', 'acá', 'incidentes', 'enlazadas', 'radiante', 'objetar', 'parado', 'preparativos', 'parándose', 'seguida', 'perfeccionamiento', 'bendito', 'vasija', 'preciso', 'sospecha', 'hayáis', 'reconocerlas', 'público', 'comparación', 'calmarla', 'quince', 'siquiera', 'exceda', 'expuesta', 'acaso', 'directa', 'a', 'tratado', 'cadena', 'intencionado', 'dirección', 'retiró', 'alejé', 'interrumpió', 'herida', 'caballero', 'descubrir', 'atareado', 'desdichada', 'areópago', 'regordetillas', 'entornado', 'rechazado']
- Stopwords: refers to the most common words in a language, which do not significantly affect the meaning of the text.
# Get unique stop-words
stop_words_es = set(str(token).lower() for token in doc_es if token.is_stop)
len(stop_words_es)
416
# Show unique stop-words
print(stop_words_es)
{'mi', 'su', 'ella', 'nunca', 'mayor', 'puedo', 'ésa', 'manera', 'quiere', 'pronto', 'fui', 'nosotras', 'tercera', 'como', 'sois', 'podrían', 'trata', 'ver', 'estar', 'cuándo', 'creo', 'uno', 'varias', 'así', 'primero', 'vuestra', 'algunas', 'excepto', 'días', 'entre', 'los', 'aquella', 'cuál', 'respecto', 'cosas', 'hace', 'tal', 'sé', 'de', 'usted', 'algo', 'final', 'eso', 'está', 'estaban', 'suyo', 'nos', 'algunos', 'trabajo', 'dijeron', 'haya', 'demás', 'tenía', 'tuyos', 'anterior', 'ningún', 'seis', 'conmigo', 'yo', 'según', 'al', 'alguna', 'quién', 'unos', 'pasado', 'tan', 'hicieron', 'mí', 'antes', 'señaló', 'aún', 'estoy', 'se', 'nadie', 'aquí', 'pudo', 'vuestras', 'dan', 'verdad', 'tiempo', 'quienes', 'breve', 'hacer', 'mas', 'manifestó', 'grandes', 'sola', 'tenido', 'en', 'tener', 'sino', 'estuvo', 'dicen', 'temprano', 'debe', 'cierto', 'están', 'ese', 'hemos', 'será', 'nuestras', 'dio', 'ésta', 'vuestro', 'cinco', 'toda', 'mismo', 'todos', 'quedó', 'tarde', 'cuando', 'he', 'realizar', 'encuentra', 'somos', 'que', 'dar', 'nuestra', 'suyas', 'sea', 'consigo', 'dice', 'adelante', 'te', 'pero', 'indicó', 'cuenta', 'tengo', 'todas', 'mediante', 'añadió', 'ambos', 'uso', 'ni', 'propias', 'encima', 'mejor', 'es', 'vais', 'diferente', 'verdadero', 'estamos', 'solo', 'buena', 'estas', 'esta', 'ellos', 'siendo', 'voy', 'saber', 'poner', 'gran', 'demasiado', 'llevar', 'hizo', 'siempre', 'vuestros', 'ello', 'fin', 'casi', 'por', 'último', 'debajo', 'pocas', 'tus', 'tampoco', 'cuántos', 'han', 'sabemos', 'éste', 'tu', 'ser', 'lo', 'deben', 'veces', 'incluso', 'un', 'aunque', 'nuevos', 'nuevas', 'para', 'momento', 'claro', 'ahora', 'vaya', 'hacerlo', 'había', 'mucha', 'podemos', 'éstos', 'propio', 'apenas', 'dicho', 'peor', 'le', 'dos', 'largo', 'nueva', 'sus', 'nada', 'eran', 'delante', 'va', 'otras', 'una', 'otros', 'luego', 'ningunos', 'haciendo', 'os', 'buenas', 'existe', 'explicó', 'qué', 'día', 'cual', 'pocos', 'desde', 'aquél', 'ha', 'son', 'poca', 'todo', 'mientras', 'dado', 'habrá', 'otra', 'ya', 'les', 'quiénes', 'mucho', 'cuanto', 'mismas', 'van', 'cuales', 'cuánto', 'cómo', 'buen', 'pasada', 'me', 'mal', 'donde', 'puede', 'soy', 'segundo', 'partir', 'verdadera', 'cuatro', 'medio', 'sin', 'tiene', 'menudo', 'ahí', 'detrás', 'estaba', 'ante', 'porque', 'cada', 'esa', 'algún', 'muchas', 'lejos', 'mismos', 'buenos', 'hubo', 'después', 'usar', 'parte', 'esos', 'arriba', 'bueno', 'lugar', 'misma', 'tuvo', 'pueda', 'última', 'era', 'propios', 'fuera', 'quien', 'igual', 'estará', 'sería', 'nuevo', 'cualquier', 'habla', 'aun', 'dijo', 'ninguna', 'con', 'menos', 'principalmente', 'suya', 'él', 'tenemos', 'ciertas', 'hacemos', 'existen', 'estos', 'da', 'ti', 'pesar', 'también', 'tanto', 'sido', 'hecho', 'tres', 'segunda', 'debido', 'allí', 'bastante', 'hacia', 'general', 'ejemplo', 'esas', 'pueden', 'próximos', 'más', 'nuestro', 'poco', 'ellas', 'ayer', 'trabajan', 'vez', 'podrá', 'cuantos', 'ciertos', 'expresó', 'sobre', 'valor', 'mío', 'alguno', 'siguiente', 'la', 'realizado', 'tienen', 'todavía', 'alrededor', 'poder', 'vosotros', 'propia', 'mis', 'mía', 'no', 'lado', 'primer', 'través', 'próximo', 'saben', 'decir', 'muy', 'tendrá', 'conocer', 'modo', 'aquel', 'solas', 'el', 'sigue', 'aquélla', 'cierta', 'tenga', 'últimas', 'cerca', 'podría', 'esto', 'realizó', 'muchos', 'habían', 'cuantas', 'vamos', 'contra', 'horas', 'sean', 'tras', 'sabe', 'acuerdo', 'fue', 'llegó', 'solamente', 'dentro', 'junto', 'hoy', 'ir', 'además', 'solos', 'dieron', 'entonces', 'aquellos', 'dónde', 'bajo', 'hay', 'estado', 'trabajar', 'pues', 'raras', 'las', 'posible', 'quizá', 'bien', 'hasta', 'si', 'primera', 'dejó', 'haber', 'parece', 'fueron', 'ustedes', 'sí', 'míos', 'sólo', 'este', 'nosotros', 'unas', 'otro', 'del', 'durante'}
- Entity: can be any word or series of words that consistently refers to the same thing.
# Returns a text with data quality
def text_quality(text):
new_text = text.replace('\n', '')
return new_text.strip('\r\n')
# Print out named first 50 entities
for ix in range(50):
ent = doc_es.ents[ix]
ent_text = text_quality(ent.text)
if len(ent_text) > 3:
print((ix + 1), '- Entity:', ent_text, ', Label:', ent.label_)
1 - Entity: The Cricket of the Heard , Label: MISC 2 - Entity: Charles Dickens , Label: PER 3 - Entity: Este libro electrónico , Label: MISC 4 - Entity: Primer grito , Label: MISC 5 - Entity: Capítulo I , Label: PER 6 - Entity: Capítulo II , Label: PER 7 - Entity: Capítulo III , Label: PER 8 - Entity: Capítulo IV , Label: PER 9 - Entity: Capítulo V , Label: PER 10 - Entity: Capítulo VI , Label: PER 11 - Entity: Segundo grito , Label: MISC 12 - Entity: Capítulo I , Label: PER 13 - Entity: Capítulo II , Label: PER 14 - Entity: Capítulo III , Label: PER 15 - Entity: Capítulo IV , Label: PER 16 - Entity: Capítulo V , Label: PER 17 - Entity: Capítulo VI , Label: PER 18 - Entity: Tercer , Label: MISC 19 - Entity: Capítulo I , Label: PER 20 - Entity: Capítulo II , Label: PER 21 - Entity: Capítulo III , Label: PER 22 - Entity: Capítulo IV , Label: PER 23 - Entity: Capítulo V , Label: PER 24 - Entity: Capítulo VI , Label: PER 25 - Entity: Primer , Label: MISC 27 - Entity: Empezó , Label: PER 28 - Entity: No necesito que me contéis , Label: MISC 29 - Entity: Peerybingle , Label: ORG 30 - Entity: Dejad , Label: PER 31 - Entity: Peerybingle , Label: ORG 32 - Entity: Tengo , Label: PER 33 - Entity: El puchero empezó , Label: MISC 34 - Entity: A decir verdad , Label: MISC 35 - Entity: Por nada del mundo opondría mi opinión personal , Label: MISC 36 - Entity: Peerybingle , Label: ORG 37 - Entity: Dejarme contar el caso tal como ocurrió , Label: MISC 38 - Entity: ¿cómo queréis que empiece por el principio , Label: MISC 39 - Entity: Parecía , Label: PER 40 - Entity: Una lucha musical , Label: MISC 41 - Entity: Vais , Label: PER 42 - Entity: La señora Peerybingle , Label: MISC 43 - Entity: Euclides , Label: PER 44 - Entity: La señora Peerybingle , Label: MISC 45 - Entity: De vuelta ya , Label: MISC 46 - Entity: Peerybingle muy pequeña- , Label: MISC 47 - Entity: Peerybingle , Label: ORG 48 - Entity: Además , Label: PER 49 - Entity: No quería dejarse acomodar sobre la barra superior de la rejilla , Label: MISC 50 - Entity: Peerybingle , Label: ORG
- POS: the parts of speech explain how a word is used in a sentence.
# Part of speech (POS) used in this document
set(token.pos_ for token in doc_es)
{'ADJ', 'ADP', 'ADV', 'AUX', 'CCONJ', 'DET', 'INTJ', 'NOUN', 'NUM', 'PART', 'PRON', 'PROPN', 'PUNCT', 'SCONJ', 'SPACE', 'SYM', 'VERB'}
- Sentences: a set of words that is complete in itself and typically containing a subject and predicate.
# How many sentences are in this text?
sentences = [s for s in doc_es.sents]
len(sentences)
1699
# Show first 10 sentences
sentences[1:11]
[Capítulo IV, , Capítulo V Capítulo VI, Segundo grito Capítulo I Capítulo II Capítulo III Capítulo IV, Capítulo V Capítulo VI, Tercer grito Capítulo I Capítulo II Capítulo III Capítulo IV, Capítulo V Capítulo VI Primer, grito - I - Empezó el puchero., No necesito que me contéis lo que la señora Peerybingle dijera; yo me entiendo., Dejad que la señora Peerybingle se pase hasta la consumación de los siglos asegurando la imposibilidad de decidir cuál empezó: yo digo que fue el puchero.]
# Get the sentences in which the 'grillo' appears
pattern = 'grillo'
cricket_sent = [sent for sent in doc_es.sents if pattern in sent.text]
len(cricket_sent)
49
# Show the first 10 sentences in which the 'grillo' appears
for sent in cricket_sent[1:11]:
print('-', sent)
- El puchero empezó cinco minutos antes que el grillo, según el relojito holandés de cuadrante barnizado situado en el rincón. - ¡Como si el reloj no hubiese cesado de tocar! ¡Como si el segadorcido de movimientos convulsivos y bruscos que lo remata, paseando la hoz de derecha a izquierda y luego de izquierda a derecha ante la fachada de su palacio morisco, no hubiese segado medio acre de césped imaginario antes que el grillo hubiese hecho notar su presencia! A decir verdad, no fui nunca terco, como todo el mundo sabe. - Pero se trata de una cuestión de hecho, y el hecho es que el puchero empezó por lo menos cinco minutos antes que el grillo hubiese dado señal de vida. - Es lo que hubiera hecho desde la primera frase a no considerar que si cuento una historia debo empezar por el principio, y ¿cómo queréis que empiece por el principio si no empiezo por la vasija? Parecía que la vasija y el grillo luchaban. - Aquí, precisamente en este punto, fue cuando el grillo entró en escena con un crrri, crrri, crrri, de magnífica potencia a coro con el puchero; pero con una voz tan asombradamente desproporcionada a su estatura -¡su estatura!, era casi invisible-, sobre todo comparándole con el puchero, que si por desgracia hubiese reventado como un cañón excesivamente cargado, cayendo, víctima de su celo, su cuerpecito roto en mil fragmentos, no hubiera parecido sino la consecuencia natural y perseguida con su trabajo afanoso. - Perseveró con ardor constante; pero el grillo se erigió en concertino y se mantuvo en su supremacía. - No obstante, marchaban muy bien unidos el grillo y el puchero. - Cuando volvió a sentarse en su sitio, el grillo y el puchero se esmeraban todavía en el canto con cierta rivalidad furiosa, siendo indudablemente el lado flaco del puchero la presunción de vencer constantemente. - El grillo logra una milla de delantera. - ¡Crrri, crrri, crrri!..., el grillo dobla la esquina.
- NER: Named Entity Recognition.
# Returns the most common entities and their quantity
def find_entities(doc, ent_type, n):
entities = Counter()
for ent in doc.ents:
if ent.label_ == ent_type:
ent_name = text_quality(ent.lemma_)
entities[ent_name] += 1
return entities.most_common(n)
# Show entities of type PERSON
find_entities(doc_es, 'PER', 20)
[('John', 153), ('Tackleton', 68), ('Caleb', 56), ('Berta', 43), ('May', 38), ('John-', 16), ('John Peerybingle', 15), ('Tilly', 14), ('¿', 12), ('después', 10), ('Tilly Slowboy', 8), ('Dot', 7), ('Eduardo', 7), ('May Fielding', 6), ('Sol', 5), ('aquí', 5), ('Peerybingle', 5), ('ir', 5), ('además', 4), ('¡ Crrri', 4)]
# Returns persons adjectives
def get_person_adj(doc, person):
adjectives = []
for ent in doc.ents:
if ent.lemma_ == person:
for token in ent.subtree:
if token.pos_ == 'ADJ': # Adjective
adjectives.append(token.lemma_)
for ent in doc.ents:
if ent.lemma_ == person:
if ent.root.dep_ == 'nsubj': # Nominal subject
for child in ent.root.head.children:
if child.dep_ == 'acomp': # Adjectival complement
adjectives.append(child.lemma_)
return set(adjectives)
# Show the adjectives used for John (most common entity)
curr_person = 'John'
print(get_person_adj(doc_es, curr_person))
{'distraído', 'venturoso', 'cabizbajo', 'diligente', 'eficazmente', 'santo', 'turbada-', 'pensativo', 'amado', 'extrañado'}
# Returns the people who use a certain verb
def verb_persons(doc, verb, n):
verb_count = Counter()
for ent in doc.ents:
if ent.label_ == 'PER' and ent.root.head.lemma_ == verb:
verb_count[ent.text] += 1
return verb_count.most_common(n)
# Show the people who use a certain verb
curr_verb = 'hacer'
verb_persons(doc_es, curr_verb, 10)
[('John', 3), ('Pronto', 1), ('Tiempo', 1), ('Hacía', 1), ('Tackleton', 1), ('Hizo', 1), ('Tilly', 1), ('¡Cuánto', 1), ('May', 1)]
# Get ADJ type labels
adj_tokens = set(str(token.orth_).lower() for token in doc_es if token.pos_ == 'ADJ')
len(adj_tokens)
1340
# Show 50 random ADJ type labels
print(random.sample(adj_tokens, 50))
['confiada', 'viejo', 'abierta', 'turbada-', 'privado', 'levantadas', 'patético', 'única', 'sorprendido', 'supremo', 'aires', 'sordo', 'echado', 'codicioso', 'vivísima', 'vivarachos', 'ensimismado', 'justísimo', 'alegro', 'convulsiva', 'ingenua', 'cruzadas', 'gentil', 'riguroso', 'verde', 'ocupadísima', 'holandés', 'bolso', 'tetera', 'insensible', 'franca', 'rígido', 'creciente', 'guantes', 'doméstico', 'insaciable', 'sano', 'vivo', 'verdaderos', 'indiferentes', 'útil', 'musical', 'próximos', 'generoso', 'egoísta', 'tiernas', 'hundida', 'arrebatadora', 'consabida', 'crema']
# Get PROPN type labels
propn_tokens = set(str(token.orth_).lower() for token in doc_es if token.pos_ == 'PROPN')
len(adj_tokens)
1340
# Show 50 random PROPN type labels
print(random.sample(propn_tokens, 50))
['cruel', 'madre', 'vivía', 'tackleton', 'soltarme', 'vi', '-era', '-¡el', 'padre', 'experiencia-', 'leed', '-o', '-murmuró', 'juro', 'cuán', 'verdad', 'error', '-¡berta', '-siguió', 'n.', 'vedle', 'tac', 'sepa', 'cámara', 'peerybingle', 'royal-george', 'mía', 'pobre', 'era-', 'shem', 'habríais', '-¡oh', 'crrri', 'sansón', 'regocijado-', '-(n.', 'peerybingle-', 'tic', 'fuerzas-', 'invariablemente', 'indudablemente', '-john', 'eduardo', 'dímelo', 'cuánto', 'hija', 'mecía', 'apiadaos', 'berta-', 'gruff']