#!/usr/bin/env python
# coding: utf-8

# # Topic Modeling

# ## Introduccion

# Otra técnica para el análisis de textos, es el **Topic Modeling**. El objetivo del Top Modeling es encontrar los 'temas' presentes en el corpus.  Se puede utilizar en buscadores, automatización de atención al cliente, ...
# 
# Cada documento en el corpus estará formado por al menos un tema.  En este notebook, realizaremos el top modeling a través de **Latent Dirichlet Allocation (LDA)**.
# El LDA es un aprendizaje no supervisado a través de una nube de palabras.  A través de él podemos encontrar, temas ocultos y clasificar los documentos en base a los temas obtenidos entre otros.
# 
# https://es.wikipedia.org/wiki/Latent_Dirichlet_Allocation  
# https://towardsdatascience.com/latent-dirichlet-allocation-lda-9d1cd064ffa2
# 
# Para realizar un top modeling, necesitamos:
# * Document Term Matrix (corpus)
# * Los términos (topics) que queremos usar.
# 
# Una vez aplicada el top modeling, es necesario interpretar los resultados para ver si tienen sentido. En el caso de que no lo tengan, se pueden variar el número de temas, los términos en el document-term matrix, los parámetros del modelo o incluso probar un modelo diferente.

# ## Topic Modeling - Prueba #1 (Todo el texto)

# In[4]:


# Importar los módulos LDA con gensim
#!pip install gensim


# In[1]:


# Cargamos el document-term matrix generado previamente
import pandas as pd
import pickle

datos = pd.read_pickle('dtm_stop.pkl')
datos


# In[6]:


from gensim import matutils, models
import scipy.sparse


# In[7]:


# Uno de los requerimientos para el LDA es un term-document matrix transpuesto
tdm = datos.transpose()
tdm.head()


# In[8]:


# Cambiamos el formato de la matriz a 'gensim'
# Pasos necesarios df --> matriz dispersa --> corpus gensim
matriz_dispersa = scipy.sparse.csr_matrix(tdm)
corpus = matutils.Sparse2Corpus(matriz_dispersa)


# In[9]:


# Gensim necesita de un diccionario con todos los términos y su ubicación en el corpus.
# Recuperamos la matriz generada en el script 2
cv = pickle.load(open("cv_stop.pkl", "rb"))
id2word = dict((v, k) for k, v in cv.vocabulary_.items())


# Ya tenemos el corpus y el diccionario palabra:ubicación, necesitamos especificar otros 2 parámetros:
# - El total de temas y
# - El total de iteraciones en el entrenamiento. 
# 
# Probamos con 2 temas y veremos si el resultado tiene sentido.

# In[12]:


lda = models.LdaModel(corpus=corpus, id2word=id2word, num_topics=2, passes=40)
lda.print_topics()


# In[14]:


# LDA for num_topics = 3
lda = models.LdaModel(corpus=corpus, id2word=id2word, num_topics=3, passes=40)
lda.print_topics()


# In[16]:


# LDA for num_topics = 4
lda = models.LdaModel(corpus=corpus, id2word=id2word, num_topics=4, passes=40)
lda.print_topics()


# Lo que obtenemos es la probabilidad de que una palabra, aparezca en un tema.
# Pero los resultados son pobres.  Hemos probado a mejorarlo, modificando los parámetros, probemos ahora modificando los términos usados.

# ## Topic Modeling - Prueba #2 (Sólo sustantivos)

# Un truco habitual suele ser usar sólo sustantivos, sólo adjetivos, ...
# https://www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html. -> para comprobar la etiqueta para filtrar por sustantivos 

# In[17]:


# Creamos una función para extraer los sustantivos de un texto
from nltk import word_tokenize, pos_tag

def sustantivos(texto):
    '''Dada una cadena de texto, se tokeniza y devuelve sólo los sustantivos.'''
    # Aquí es donde nos quedamos sólo con los sustantivos.
    es_sustantivo = lambda pos: pos[:2] == 'NN'
    
    tokenizado = word_tokenize(texto)
    todo_sustantivos = [palabra for (palabra, pos) in pos_tag(tokenizado) if es_sustantivo(pos)] 
    return ' '.join(todo_sustantivos)


# In[20]:


# Leemos los datos limpios generados previamente
datos_limpios = pd.read_pickle('datos_limpios.pkl')
datos_limpios


# In[21]:


# Descargamos la librería para poder normalizar las palabras, según su contexto y análisis morfológico.
import nltk
nltk.download('averaged_perceptron_tagger')


# In[22]:


# Extraemos los sustantivos
datos_sustantivos = pd.DataFrame(datos_limpios ['transcripcion'].apply(sustantivos))
datos_sustantivos


# In[23]:


# Creamos un nuevo corpus sólo con los sustantivos
from sklearn.feature_extraction import text
from sklearn.feature_extraction.text import CountVectorizer

# Quitamos las stopwords, puesto que vamos a generar un nuevo corpus
add_stop_words = ['like', 'im', 'know', 'just', 'dont', 'thats', 'right', 'people',
                  'youre', 'got', 'gonna', 'time', 'think', 'yeah', 'said']
stop_words = text.ENGLISH_STOP_WORDS.union(add_stop_words)

# Corpus sólo con sustantivos
cvs = CountVectorizer(stop_words=stop_words)
datos_cvs = cvs.fit_transform(datos_sustantivos['transcripcion'])
datos_dtms = pd.DataFrame(datos_cvs.toarray(), columns=cvs.get_feature_names())
datos_dtms.index = datos_sustantivos.index
datos_dtms


# In[24]:


# Generar el corpus gensim
corpuss = matutils.Sparse2Corpus(scipy.sparse.csr_matrix(datos_dtms.transpose()))

# Generar el diccionario de vocabulario
id2words = dict((v, k) for k, v in cvs.vocabulary_.items())


# In[25]:


# Empezamos por 2 temas
ldas = models.LdaModel(corpus=corpuss, num_topics=2, id2word=id2words, passes=10)
ldas.print_topics()


# In[26]:


# topics = 3
ldas = models.LdaModel(corpus=corpuss, num_topics=3, id2word=id2words, passes=10)
ldas.print_topics()


# In[27]:


# topics = 4
ldas = models.LdaModel(corpus=corpuss, num_topics=4, id2word=id2words, passes=10)
ldas.print_topics()


# In[28]:


# topics = 5
ldas = models.LdaModel(corpus=corpuss, num_topics=5, id2word=id2words, passes=10)
ldas.print_topics()


# ## Topic Modeling - Prueba #3 (Sustantivos y Adjetivos)

# In[29]:


# Función para extraer los sustantivos y adjetivos
def sust_adj(texto):
    '''Dado un texto, lo tokeniza y devuelve sólo los sustantivos y adjetivos.'''
    es_sust_adj = lambda pos: pos[:2] == 'NN' or pos[:2] == 'JJ'
    tokenizado = word_tokenize(texto)
    todo_sust_adj = [palabra for (palabra, pos) in pos_tag(tokenizado) if es_sust_adj(pos)] 
    return ' '.join(todo_sust_adj)


# In[30]:


# Aplicamos la función a los datos limpios
datos_sust_adj = pd.DataFrame(datos_limpios['transcripcion'].apply(sust_adj))
datos_sust_adj


# In[31]:


# Creación del nuevo corpus, ahora sólo con sustantivos y adjetivos.  Además eliminamos las stop words con max_df superior a 0.8
cvna = CountVectorizer(stop_words=stop_words, max_df=.8)
datos_cvna = cvna.fit_transform(datos_sust_adj['transcripcion'])
datos_dtmna = pd.DataFrame(datos_cvna.toarray(), columns=cvna.get_feature_names())
datos_dtmna.index = datos_sust_adj.index
datos_dtmna


# In[32]:


# Creación del corpus gensim
corpusna = matutils.Sparse2Corpus(scipy.sparse.csr_matrix(datos_dtmna.transpose()))

# Diccionario de vocabulario
id2wordna = dict((v, k) for k, v in cvna.vocabulary_.items())


# In[33]:


# topics = 2
ldana = models.LdaModel(corpus=corpusna, num_topics=2, id2word=id2wordna, passes=10)
ldana.print_topics()


# In[34]:


# topics = 3
ldana = models.LdaModel(corpus=corpusna, num_topics=3, id2word=id2wordna, passes=10)
ldana.print_topics()


# In[35]:


# topics = 4
ldana = models.LdaModel(corpus=corpusna, num_topics=4, id2word=id2wordna, passes=10)
ldana.print_topics()


# ## Identificando los temas de cada documento

# De los 9 'topic models' que hemos extraido, el caso que parece tener más sentido es el 4º tema de la prueba con sustantivos y adjetivos.  Afinamos ahora el proceso a través de más iteraciones.

# In[37]:


# Modelo LDA final (de momento)
ldana = models.LdaModel(corpus=corpusna, num_topics=4, id2word=id2wordna, passes=180)
ldana.print_topics()


# Estos 4 temas parecen bastante 'decentes'
# * Tema 0: Familia
# * Tema 1: Marido
# * Tema 2: Negocios
# * Tema 3: Abuela, palabrotas

# In[38]:


# Comprobamos los temas que contiene cada transcripción
corpus_transformado = ldana[corpusna]
list(zip([a for [(a,b)] in corpus_transformado], datos_dtmna.index))


# ## Ejercicios

# 1. Prueba a modificar los parámetros para obtener unos mejores resultados.
# 2. Create a new topic model that includes terms from a different [part of speech](https://www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html) and see if you can get better topics.

# In[ ]:




