<jupyter_start><jupyter_text>1.5 Gerando matrizes e visualizando word frequencies 1.5.1 RetomandoNos guias anteriores, vimos como são feitas as matrizes de sentenças. Cada um dos tweets tinha três parâmetros correspondentes, o bias, a soma das frequências positivas e a soma das frequências negativas (exemplo: [1, 4, 2]).Exemplificando, a matriz de uma lista de sentenças então ficaria de modo semelhante a esse: [[1, 3, 5],[1, 0, 2],[1, 6, 5]]Cada linha corresponde aos três features de cada sentença, porém o que separa as linhas é a vírgula. Sendo assim, é apensa uma questão de organização visual a linearização da matriz.Resumindo, o que vamos ver agora depende da função build_freqs e da função process_tweet, ambas se encontram no arquivo **utils.py**.```freqs = build_freqs(tweets,labels)  Cria o dicionário de frequências usando a função build_freqs()X = np.zeros((m,3))  Matriz "x" inicia zerada e possui três parâmetros [0, 0, 0]for i in range(m): Para cada tweet...    p_tweet = process_tweet(tweets[i])  Pré-processar (tokenização, stop words, stem, etc)    x[i,:] = extract_features(p_tweet,freqs)  Extrai as features somando as frequências positivas e negativas```Vamos importar as funções do utils.py para nos auxiliar no cálculo das **word frequencies**, e então visualizar isso no _dataset_. 1.5.2 Carregando bibliotecas, funções e _dataset_<jupyter_code>import nltk                                  # Biblioteca de NLP
from nltk.corpus import twitter_samples      # Corpus de tweets
import matplotlib.pyplot as plt              # Visualização gráfica
import numpy as np                           # Biblioteca de Ciência da Computação e operações de matriz
nltk.download('stopwords') # Para a função process_tweet

# import our convenience functions
from utils import process_tweet, build_freqs # O utils.py, bem como suas funções, está dentro dessa mesma pasta. 

# select the lists of positive and negative tweets
all_positive_tweets = twitter_samples.strings('positive_tweets.json')
all_negative_tweets = twitter_samples.strings('negative_tweets.json')

# concatenate the lists, 1st part is the positive tweets followed by the negative
tweets = all_positive_tweets + all_negative_tweets

# let's see how many tweets we have
print("Number of tweets: ", len(tweets))<jupyter_output>[nltk_data] Downloading package stopwords to
[nltk_data]     C:\Users\Dante\AppData\Roaming\nltk_data...
[nltk_data]   Package stopwords is already up-to-date!<jupyter_text>Por fim, teremos um array etiquetado. Ele pode ser entendido como uma lista comum, porém é otimizado para atividades computacionais e manipulação.Nossas 10.000 sentenças são dividas em 5.000 positivas e 5.000 negativas, que são preenchidas respectivamentes por '1' e '0'. Algumas operações úteis da biblioteca **numpy** nos auxiliam a realizar tais alterações.- ``p.ones()`` cria um array '1';- ``np.zeros()`` cria um array '0';- ``np.append()`` concatena arrays.<jupyter_code># array NumPy representando o número de cada label dos tweets
labels = np.append(np.ones((len(all_positive_tweets))), np.zeros((len(all_negative_tweets))))<jupyter_output><empty_output><jupyter_text>1.5.3 DicionáriosPodemos criar **dicionários** em Python. Eles são coleções mutáveis e indexadas, e itens como pares de valor-chave. Para que consultas sejam feitas em tempo constante, os dicionários utilizam o _data structure_ **hash table** (https://en.wikipedia.org/wiki/Hash_table).Os dicionários são muito utilizados no NLP exatamente por conta do resgate rápido de itens. Vamos definir um dicionário utilizando chaves (brackets):<jupyter_code>dicionario = {'chave1': 1, 'chave2' : 2}<jupyter_output><empty_output><jupyter_text>A variável **dicionario** agora está relacionada a um dicionário com duas entradas. Cada entrada possui uma **chave** e um **valor**. Podemos utilizar quaisquer tipos de valor nas entradas (float, int, tuple, string).Para **adicionar e modificar entradas**, chamamos a variável e utilizamos formato de colchete para indicar a chave a ser adicionada/alterada.<jupyter_code>dicionario['chave3'] = 3
dicionario['chave1'] = 4
print(dicionario)<jupyter_output>{'chave1': 4, 'chave2': 2, 'chave3': 3}<jupyter_text>Para **acessar valores e chaves**, podemos utilizar dois métodos:1. Utilizando o colchete (square bracket) para valores que existem;2. Utilizando o método **.get()**.<jupyter_code>print(dicionario['chave3'])
print(dicionario['chave4']) # A chave não existe, então retornará 'KeyError'<jupyter_output><empty_output><jupyter_text>Utilizando o **if** e **else** ou o método **.get()**, já não existe erro pois apresentamos um valor padrão se a chave não for encontrada.<jupyter_code># Nesses dois casos, a chave será encontrada
if 'chave1' in dicionario:
    print('Achei!', dicionario['chave1'])
else:
    print('Essa chave ainda não foi definida')

print('Item encontrado!:', dicionario.get('chave1', 'não existe'))


# Nesse caso, retornou 'não existe' pois não foi encontrado 
print('Pesquisando item que não existe:', dicionario.get('chave4', 'não existe'))<jupyter_output><empty_output><jupyter_text>1.5.4 Dicionário de word frequencyAgora podemos olhar atentamente para a função **build_freqs** (que se encontra no utils.py).<jupyter_code>def build_freqs(tweets, ys):
    """Build frequencies.
    Input:
        tweets: a list of tweets
        ys: an m x 1 array with the sentiment label of each tweet
            (either 0 or 1)
    Output:
        freqs: a dictionary mapping each (word, sentiment) pair to its
        frequency
    """
    # Converta o array np em lista, pois o zip precisa de um iterável.
    # O .squeeze é necessário ou a lista termina com um elemento apenas.
    # Observe também que este é apenas um NOP se ys já for uma lista.
    yslist = np.squeeze(ys).tolist()

    # Comece com um dicionário vazio e preencha-o realizando um looping em todos os tweets
    # Um segundo looping também será feito em todas as palavras processadas de cada tweet
    freqs = {}
    for y, tweet in zip(yslist, tweets):
        for word in process_tweet(tweet):
            pair = (word, y)
            if pair in freqs:
                freqs[pair] += 1
            else:
                freqs[pair] = 1    
    return freqs

    # Este é apenas um outro modo de realizar os mesmos loops, substituindo o if e else pelo método .get, como já visto.
    for y, tweet in zip(yslist, tweets):
        for word in process_tweet(tweet):
            pair = (word, y)
            freqs[pair] = freqs.get(pair, 0) + 1<jupyter_output><empty_output><jupyter_text>O par **(word, y)** dirá respeito a uma **chave**, sendo **word** um elemento do tweet processado e **y** um integer, '1' para positivo e '0' para negativo. No dicionário, a **chave** (key) dirá respeito ao elemento word, e seu **valor** (value) corresponderá ao número de aparecimentos no corpus inteiro. Ao longo dos loopings, mais tweets e seus elementos vão sendo analisados e as frequências vão aumentando. Vamos observar alguns exemplos para entender melhor o resultado final:``` "folowfriday" aparece "25 vezes" nos tweets "positivos"('followfriday', 1.0): 25 "shame" aparece "19 vezes" nos tweets "negativos"('shame', 0.0): 19``` Agora que já compreendemos melhor como realizar o cálculo de frequências e como guardar em um dicionário, vamos avançar mais um passo!Como já temos os dicionários e seus elementos (**word**, **y** e **frequência**), agora basta **alimentar a nossa lista de tweets e etiquetas**.<jupyter_code>freqs = build_freqs(tweets, labels) # Vamos utilizar a função para criar os dicionários em 'freqs'
print(f'type(freqs) = {type(freqs)}') # Vamos checar qual o _data type_ da variável 'freqs'
print(f'len(freqs) = {len(freqs)}') # Vamos checar o tamanho do dicionário
print(freqs) # E agora um print de todo o dicionário<jupyter_output><empty_output><jupyter_text>1.5.5 Tabela de contagem de palavrasVamos escolher algumas palavras que gostaríamos de visualizar. Primeiro vamos indicá-las em uma lista. Criamos uma segunda lista que será a **representação de nossa tabela de contagem de palavras**.<jupyter_code>keys = ['happi', 'merri', 'nice', 'good', 'bad', 'sad', 'mad', 'best', 'pretti',
        '❤', ':)', ':(', '😒', '😬', '😄', '😍', '♛',
        'song', 'idea', 'power', 'play', 'magnific']

# lista representando nossa tabela de contagem de palavras
data = [] # cada elemento consistirá de uma sub-lista com esse padrão: [<word>, <positive_count>, <negative_count>]


for word in keys: # para cada elemento em nossa lista de palavras

    positivas = 0
    negativas = 0
    
    if (word, 1) in freqs: # se word for "1" no dicionário de frequências
        positivas = freqs[(word, 1)]
        
    # retrieve number of negative counts
    if (word, 0) in freqs: # se word for "0" no dicionário de frequências
        negativas = freqs[(word, 0)]
        
    # append the word counts to the table
    data.append([word, positivas, negativas])
    
data<jupyter_output><empty_output><jupyter_text>Percebeu como a palavra "happi" (_lemma_ de _happy_) apareceu muito mais em sentenças positivas do que em negativas? Vamos utilizar um gráfico de dispersão (_scatter plot_) para visualizar a tabela que criamos. Nesse caso, a contagem não será crua (_raw_) mas sim indicada em escala logarítmica (para um entendimento mais palpável das medidas). A linha vermelha marca a fronteira entre positivo e negativo. As palavras mais **marginais** são as mais **positivas/negativas** e as **centrais** são **neutras**.<jupyter_code>fig, ax = plt.subplots(figsize = (8, 8))

x = np.log([x[1] + 1 for x in data]) # converte os dados crus de "x" em escala logarítmica e +1 para não ocasionar log(0)
y = np.log([x[2] + 1 for x in data]) # igualmente para "y"

ax.scatter(x, y) # plota um pontinho para cada palavra

plt.xlabel("Log Positive count") # axis labels
plt.ylabel("Log Negative count")

for i in range(0, len(data)): # Adiciona as palavras na mesma posição dos pontinhos
    ax.annotate(data[i][0], (x[i], y[i]), fontsize=14)

ax.plot([0, 9], [0, 9], color = 'red') # Plota a linha vermelha que vai de "0" a "9"
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10081878267732071, 'fraction_numerical': 0.011837821840781295, 'mean_word_length': 4.4098185699039485, 'pattern_counts': {'":': 0, '<': 42, '<?xml version=': 0, '>': 42, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '666740', 'license': 'MIT', 'n_tokens_mistral': 3603, 'n_tokens_neox': 3452, 'n_words': 1244, 'path': 'Inteligência Artificial/Natural Language Processing (NLP)/1. Introdução ao NLP e Análise de Sentimento/1.5 Gerando matrizes e visualizando word frequencies.ipynb', 'repo_name': 'dantebarross/vamos-programar', 'size': '10137'}