Last active
May 7, 2025 22:43
-
-
Save fernandoferreira-me/61e9e4a25060f95abbfdb8cb90aaaed9 to your computer and use it in GitHub Desktop.
Questionário - Projeto de Disciplina de Text Mining.ipynb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"nbformat": 4, | |
"nbformat_minor": 0, | |
"metadata": { | |
"colab": { | |
"name": "Questionário - Projeto de Disciplina de Text Mining.ipynb", | |
"provenance": [], | |
"collapsed_sections": [], | |
"include_colab_link": true | |
}, | |
"kernelspec": { | |
"name": "python3", | |
"display_name": "Python 3" | |
}, | |
"language_info": { | |
"name": "python" | |
} | |
}, | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": { | |
"id": "view-in-github", | |
"colab_type": "text" | |
}, | |
"source": [ | |
"<a href=\"https://colab.research.google.com/gist/fernandoferreira-me/61e9e4a25060f95abbfdb8cb90aaaed9/question-rio-projeto-de-disciplina-de-text-mining.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"# Projeto de Disciplina de Processamento de Linguagem Natural com Python\n", | |
"\n", | |
"Bem-vindo ao projeto de disciplina de **Processamento de Linguagem Natural com Python**. Ao longo das últimas aulas vimos uma série de aplicações que nos deram a amplitude de possibilidades em trabalhar com textos. Para tal, usamos diversas bibliotecas, onde as que mais se destacaram foram NLTK, SPACY e GENSIM.\n", | |
"\n", | |
"Esse notebook servirá de guia para a execução de uma análise de tópicos completa, usando o algoritmo de LDA e recursos para interpretação dos resultados. Utilizaremos notícias da seção \"Mercado\" extraídas da Folha de S. Paulo no ano de 2016. Complete a análise com os códigos que achar pertinente e responda as questões presentes no Moodle. Boa sorte!\n", | |
"\n", | |
"## O Notebook\n", | |
"\n", | |
"Nesse notebook, você será guiado pela análise de **Extração de Tópicos**. As seguintes tarefas serão realizadas\n", | |
"\n", | |
"\n", | |
"1. Download dos dados provenientes do kaggle \n", | |
"2. Seleção dos dados relevantes para a nossa análise\n", | |
"3. Instalação das principais ferramentas e importação de módulos\n", | |
"4. Pré-processamento usando NLTK\n", | |
"5. Pré-processamento usando Spacy\n", | |
"6. Análise de tópicos usando LDA\n", | |
"7. Análise de NER usando Spacy\n", | |
"8. Visualização dos tópicos usando tokens e entidades.\n", | |
"\n" | |
], | |
"metadata": { | |
"id": "s_NpsAsHItOr" | |
} | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Instruções para baixar os dados\n", | |
"\n", | |
"Para baixar os dados será necessário o uso do gerenciador de downloads da Kaggle. A Kaggle, uma subsidiária do grupo Alphabet (Google), é uma comunidade on-line de cientistas de dados e profissionais de aprendizado de máquina.\n", | |
"\n", | |
"Para utilizar o gerenciador, será necessário criar uma conta no site Kaggle.com.\n", | |
"Com a conta criada, obtenha um token de acesso, no formato kaggle.json\n", | |
"\n", | |
"" | |
], | |
"metadata": { | |
"id": "5J7ydpVZLGYp" | |
} | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Em posse do token (baixe para seu computador), execute a células da próxima seção para acessar os dados de interesse e baixá-los." | |
], | |
"metadata": { | |
"id": "8nAqC8_JMm54" | |
} | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"# Baixe os dados\n", | |
"\n", | |
"Instale o gerenciador kaggle no ambiente do Colab e faça o upload do arquivo kaggle.json " | |
], | |
"metadata": { | |
"id": "YW0KUuAVM7H3" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": { | |
"id": "pJ7IIZV8KBd-" | |
}, | |
"outputs": [], | |
"source": [ | |
"!pip install -q kaggle\n", | |
"!rm -rf kaggle.json\n", | |
"from google.colab import files\n", | |
"\n", | |
"files.upload()\n" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Crie a pasta .kaggle" | |
], | |
"metadata": { | |
"id": "LLlWEPPGNGbX" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"!rm -rf .kaggle\n", | |
"!mkdir .kaggle\n", | |
"!cp kaggle.json .kaggle/\n", | |
"!chmod 600 .kaggle/kaggle.json" | |
], | |
"metadata": { | |
"id": "LbDIrz7dAM_L" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"Baixe o dataset" | |
], | |
"metadata": { | |
"id": "ODCQbepdNKzw" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"!kaggle datasets download --force -d marlesson/news-of-the-site-folhauol" | |
], | |
"metadata": { | |
"id": "IQb28v1Q_odu" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Criar o DataFrame com os dados lidos diretamente da plataforma Kaggle" | |
], | |
"metadata": { | |
"id": "58h0gEezNNt3" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"import pandas as pd\n", | |
"from tqdm.auto import tqdm\n", | |
"tqdm.pandas()\n", | |
"\n", | |
"\n", | |
"df = pd.read_csv(\"news-of-the-site-folhauol.zip\")" | |
], | |
"metadata": { | |
"id": "t2jwPcZmACnN" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"# Atualizar o SPACY e instalar os modelos pt_core_news_lg" | |
], | |
"metadata": { | |
"id": "dSNTAT2nNU_M" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"# Escreva seu código aqui\n", | |
"# ...\n", | |
"\n", | |
"import spacy\n", | |
"from spacy.lang.pt.stop_words import STOP_WORDS" | |
], | |
"metadata": { | |
"id": "k_KUolFmPDXx" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Instalar os datasets `stopwords`, `punkt` e `rslp` do nltk" | |
], | |
"metadata": { | |
"id": "EH983kgENeUf" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"import nltk\n", | |
"\n", | |
"# Escreva seu código aqui" | |
], | |
"metadata": { | |
"id": "q6KKTeHgPF3b" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Carregar os módulos usados ao longo desse notebook" | |
], | |
"metadata": { | |
"id": "1vgbGyn0Nsg7" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"!pip install pyldavis &> /dev/null\n", | |
"\n", | |
"import warnings\n", | |
"warnings.filterwarnings('ignore')\n", | |
"\n", | |
"from sklearn.feature_extraction.text import TfidfVectorizer\n", | |
"from sklearn.decomposition import LatentDirichletAllocation as LDA\n", | |
"import numpy as np\n", | |
"\n", | |
"from wordcloud import WordCloud\n", | |
"\n", | |
"import seaborn as sns\n", | |
"import matplotlib.pyplot as plt\n", | |
"from itertools import chain\n", | |
"\n", | |
"from typing import List, Set, Any\n", | |
"\n", | |
"\n", | |
"SEED = 123" | |
], | |
"metadata": { | |
"id": "CkvbUB4woQFo" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"# Filtrando os dados para utilizar apenas as notícias do ano de 2016 e da categoria \"Mercado\"\n", | |
"\n", | |
"Filtre os dados do DataFrame df e crie um DataFrame news_2016 que contenha apenas notícias de **2016** e da categoria **mercado**." | |
], | |
"metadata": { | |
"id": "E02_RcvPNw5T" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"df['date'] = pd.to_datetime(df.date)\n", | |
"# Create a dataframe named news_2016\n", | |
"# news_2016 = " | |
], | |
"metadata": { | |
"id": "bce0QMD9Cd2N" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## NLTK Tokenizer and Stemmer\n", | |
"\n", | |
"Crie uma coluna no dataframe `news_2016` contendo os tokens para cada um dos textos. Os tokens devem estar representados pelo radical das palavras (stem). \n", | |
"Para tal, complete o conteúdo da função `tokenize`." | |
], | |
"metadata": { | |
"id": "vksOPQxsOXr6" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"\n", | |
"def tokenize(text: str) -> List:\n", | |
" \"\"\"\n", | |
" Function for tokenizing using `nltk.tokenize.word_tokenize`\n", | |
" \n", | |
" Returns:\n", | |
" - A list of stemmed tokens (`nltk.stem.RSLPStemmer`)\n", | |
" IMPORTANT: Only tokens with alphabetic\n", | |
" characters will be returned.\n", | |
" \"\"\"\n", | |
" #escreva seu código aqui\n", | |
" #return\n", | |
"\n", | |
"news_2016.loc[:, 'nltk_tokens'] = news_2016.text.progress_map(tokenize)" | |
], | |
"metadata": { | |
"id": "e0kNSRccMBy5" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Criar uma documento SPACY para cada texto do dataset\n", | |
"\n", | |
"Crie uma coluna `spacy_doc` que contenha os objetos spacy para cada texto do dataset de interesse. Para tal, carregue os modelos `pt_core_news_lg` e aplique em todos os textos (pode demorar alguns minutos...)" | |
], | |
"metadata": { | |
"id": "z2nFkMILRJ_K" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"# Escreva seu código aqui\n", | |
"# ...\n", | |
"# news_2016.loc[:, 'spacy_doc'] = ...(complete)" | |
], | |
"metadata": { | |
"id": "8XQvUL6WDiV5" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Realize a Lematização usando SPACY\n", | |
"\n", | |
"O modelo NLP do spacy oferece a possiblidade de lematizar textos em português (o que não acontece com a biblioteca NLTK). Iremos criar uma lista de tokens\n", | |
"lematizados para cada texto do nosso dataset. Para tal, iremos retirar as \n", | |
"stopwords, usando uma função que junta stopwords provenientes do NLTK e do Spacy. Essa lista completa, é retornada pela função stopwords (e você não precisa mexer).\n", | |
"\n", | |
"Já a função filter retorna True caso o token seja composto por caracters alfabéticos, não estiver dentro da lista de stopwords e o lemma resultante não estiver contido na lista `o\", \"em\", \"em o\", \"em a\" e \"ano\"`.\n", | |
"\n", | |
"Crie uma coluna chamada `spacy_lemma` para armazenar o resultado desse pré-processamento." | |
], | |
"metadata": { | |
"id": "oqmf7KU-R6qk" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"def stopwords() -> Set:\n", | |
" \"\"\"\n", | |
" Return complete list of stopwords\n", | |
" \"\"\"\n", | |
" return set(list(nltk.corpus.stopwords.words(\"portuguese\")) + list(STOP_WORDS))\n", | |
"\n", | |
"complete_stopwords = stopwords()\n", | |
"\n", | |
"def filter(w: spacy.lang.pt.Portuguese) -> bool:\n", | |
" \"\"\"\n", | |
" Filter stopwords and undesired tokens\n", | |
" \"\"\"\n", | |
" # Escreva seu código aqui\n", | |
"\n", | |
"\n", | |
"def lemma(doc: spacy.lang.pt.Portuguese) -> List[str]:\n", | |
" \"\"\"\n", | |
" Apply spacy lemmatization on the tokens of a text\n", | |
"\n", | |
" Returns:\n", | |
" - a list representing the standardized (with lemmatisation) vocabulary\n", | |
" \"\"\" \n", | |
" # Escreva seu cógigo aqui\n", | |
"\n", | |
"news_2016.loc[:, 'spacy_lemma'] = news_2016.spacy_doc.progress_map(lemma)" | |
], | |
"metadata": { | |
"id": "kiEdxHA7JKVO" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Reconhecimento de entidades nomeadas\n", | |
"\n", | |
"Crie uma coluna `spacy_ner` que armazene todas as organizações (APENAS organizações) que estão contidas no texto." | |
], | |
"metadata": { | |
"id": "RV6u3Fq1TJ-o" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"def NER(doc: spacy.lang.pt.Portuguese):\n", | |
" \"\"\"\n", | |
" Return the list of organizations for a SPACY document\n", | |
" \"\"\"\n", | |
" # Escreva seu código aqui\n", | |
"\n", | |
"news_2016.loc[:, 'spacy_ner'] = news_2016.spacy_doc.progress_map(NER)" | |
], | |
"metadata": { | |
"id": "HBkwT6fdkDaX" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Bag-of-Words\n", | |
"\n", | |
"Crie uma coluna `tfidf` no dataframe `news_2016`. Use a coluna `spacy_lemma` como base para cálculo do TFIDF. \n", | |
"O número máximo de features que iremos considerar é 5000. E o token, tem que ter aparecido pelo menos 10 vezes (`min_df`) nos documentos. " | |
], | |
"metadata": { | |
"id": "3KXw4Z27TedE" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"class Vectorizer:\n", | |
" def __init__(self, doc_tokens: List):\n", | |
" self.doc_tokens = doc_tokens\n", | |
" self.tfidf = None\n", | |
"\n", | |
" \n", | |
" def vectorizer(self):\n", | |
" \"\"\"\n", | |
" Convert a list of tokens to tfidf vector\n", | |
" Returns the tfidf vector and attribute it to self.tfidf\n", | |
" \"\"\"\n", | |
" # Escreva seu código aqui\n", | |
" #...\n", | |
" # return ...\n", | |
"\n", | |
" def __call__(self):\n", | |
" if self.tfidf is None:\n", | |
" self.vectorizer()\n", | |
" return self.tfidf\n", | |
"\n", | |
"doc_tokens = news_2016.spacy_lemma.values.tolist()\n", | |
"vectorizer = Vectorizer(doc_tokens)\n", | |
"\n", | |
"def tokens2tfidf(tokens):\n", | |
" tokens = ' '.join(tokens)\n", | |
" array = vectorizer().transform([tokens]).toarray()[0]\n", | |
" return array\n", | |
"\n", | |
"\n", | |
"news_2016.loc[:, 'tfidf'] = news_2016.spacy_lemma.progress_map(tokens2tfidf)" | |
], | |
"metadata": { | |
"id": "ucwxtHrBmAqu" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Extração de Tópicos\n", | |
"\n", | |
"Realize a extração de 9 tópicos usando a implementação do sklearn do algoritmo Latent Dirichlet Allocation. Como parâmetros, você irá usar o número máximo de iterações igual à 100 (pode demorar) e o `random_seed` igual a `SEED` que foi setado no início do notebook" | |
], | |
"metadata": { | |
"id": "Kw76w2wVZCJX" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"N_TOKENS = 9\n", | |
"\n", | |
"corpus = np.array(news_2016.tfidf.tolist())\n", | |
"#Escreva seu código aqui\n", | |
"#lda = ... (complete)" | |
], | |
"metadata": { | |
"id": "BKp7g1O3neG_" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"### Atribua a cada text, um (e apenas um) tópic. \n", | |
"\n", | |
"Crie uma coluna `topic` onde o valor é exatamente o tópico que melhor caracteriza o documento de acordo com o algoritmo de LDA." | |
], | |
"metadata": { | |
"id": "IR5dmpQ9Z_YR" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"def get_topic(tfidf: np.array):\n", | |
" \"\"\"\n", | |
" Get topic for a lda trained model\n", | |
" \"\"\"\n", | |
" # Escreva seu código aqui\n", | |
"\n", | |
"news_2016['topic'] = news_2016.tfidf.progress_map(get_topic)" | |
], | |
"metadata": { | |
"id": "RcXpEfM-3sgg" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Número de documentos vs tópicos \n", | |
"\n", | |
"Esse gráfico nos mostra quantos documentos foram caracterizados por cada tópico." | |
], | |
"metadata": { | |
"id": "9IHT0dDAaSs9" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"with sns.axes_style(\"ticks\"):\n", | |
" sns.set_context(\"talk\")\n", | |
" ax = news_2016['topic'].value_counts().sort_values().plot(kind = 'barh')\n", | |
" ax.yaxis.grid(True)\n", | |
" ax.set_ylabel(\"Tópico\")\n", | |
" ax.set_xlabel(\"Número de notícias (log)\")\n", | |
" sns.despine(offset = 10)\n", | |
" ax.set_xscale(\"log\")" | |
], | |
"metadata": { | |
"id": "M_qmqn2j6bCI" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Crie uma nuvem de palavra para cada tópico.\n", | |
"\n", | |
"Use as colunas `spacy_lemma` e `topic` para essa tarefa. " | |
], | |
"metadata": { | |
"id": "JP_cqIgoap32" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"def plot_wordcloud(text:str, ax:plt.Axes) -> plt.Axes:\n", | |
" \"\"\"\n", | |
" Plot the wordcloud for the text/\n", | |
" Arguments:\n", | |
" - text: string to be analised\n", | |
" - ax: plt subaxis\n", | |
" Returns:\n", | |
" - ax\n", | |
" \"\"\"\n", | |
" # Escreva seu código aqui\n", | |
" # return ax\n", | |
" \n", | |
"def plot_wordcloud_for_a_topic(topic:int, ax:plt.Axes) -> plt.Axes:\n", | |
" topic_news = news_2016[news_2016['topic'] == topic]\n", | |
" list_of_words = chain(*topic_news.spacy_lemma.values.tolist())\n", | |
" string_complete = ' '.join(list_of_words)\n", | |
" if not string_complete:\n", | |
" return None\n", | |
" return plot_wordcloud(string_complete, ax)\n", | |
"\n", | |
"fig, axis = plt.subplots(3, 3, figsize=(16, 12))\n", | |
"\n", | |
"axis_ = axis.flatten()\n", | |
"for idx, ax in enumerate(axis_):\n", | |
" ax_ = plot_wordcloud_for_a_topic(idx + 1, ax)\n", | |
" if ax_ is None:\n", | |
" plt.delaxes(ax)\n", | |
" continue\n", | |
" ax.set_title(f\"Tópico {idx + 1}\")\n", | |
"fig.tight_layout()" | |
], | |
"metadata": { | |
"id": "tLVrwjNr6r1c" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
}, | |
{ | |
"cell_type": "markdown", | |
"source": [ | |
"## Crie uma nuvem de entidades para cada tópico.\n", | |
"\n", | |
"Use as colunas `spacy_lemma` e `topic` para essa tarefa. " | |
], | |
"metadata": { | |
"id": "94p0NeZwa37S" | |
} | |
}, | |
{ | |
"cell_type": "code", | |
"source": [ | |
"def plot_wordcloud_entities_for_a_topic(topic:int, ax:plt.Axes) -> plt.Axes:\n", | |
" topic_news = news_2016[news_2016['topic'] == topic]\n", | |
" list_of_docs = topic_news.spacy_ner.apply(lambda l : [w.replace(\" \", \"_\") for w in l])\n", | |
" list_of_words = chain(*list_of_docs)\n", | |
" string_complete = ' '.join(list_of_words)\n", | |
" if not len(string_complete):\n", | |
" return None\n", | |
" return plot_wordcloud(string_complete, ax)\n", | |
"\n", | |
"fig, axis = plt.subplots(3, 3, figsize=(16, 12))\n", | |
"\n", | |
"axis_ = axis.flatten()\n", | |
"for idx, ax in enumerate(axis_):\n", | |
" ax_ = plot_wordcloud_entities_for_a_topic(idx + 1, ax)\n", | |
" if ax_ is None:\n", | |
" plt.delaxes(ax)\n", | |
" continue\n", | |
" ax.set_title(f\"Tópico {idx + 1}\")\n", | |
"fig.tight_layout()" | |
], | |
"metadata": { | |
"id": "IbaUfOtkCIaj" | |
}, | |
"execution_count": null, | |
"outputs": [] | |
} | |
] | |
} |
O exercicio com LDAVis foi removido. Essa é a versão correta em Maio de 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ver a versão com pyLDAvis pinada na versão 3.2.2 e pequeno fix na tokens2tfidf() em https://gist.github.com/felipefg/eb57200b25dc130e79201f7ecce22a2b