Analisando Sentimento em notas de app (Python) – Parte 1
Fala pessoal, tudo bem?
A motivação desse meu artigo é falarmos sobre Apps. Principalmente no que toca “o que o cliente pensa” do meu app, quais são as coisas que ele está pontuando como bom ou ruim?
Além disso, nos grupos de Whatsapp que participo sobre Data, vira e mexe alguém quer saber como extrair dados das lojas de aplicativo (Google Play e App Store) para analisar os dados.
Nessa parte 1, resolvi focar na extração e visualização dos dados e adicionar algumas outras coisas para deixar interessante.
Para a parte 2, como continuação, focarei em mostrar um modelo básico de Machine Learning para que, a partir dos comentários em texto, possamos criar uma ferramenta de predição; ou seja, a partir do comentário, adivinhar qual será a nota correspondente.
Busquei construir o código da forma mais agnóstica possível, ou seja, se você fizer algumas pequenas alterações poderá replicar para o app da sua preferência de análise.
Então vamos lá!
Parte 1.1: Coletando os dados.
O primeiro passo é abrir o Google Colab e criar um novo notebook. Se você ainda não tem familiaridade, pode buscar aqui que eu explico os primeiros passos e como fazer uma análise básica.
Importante: Scrape (Iscrêipi) é diferente de Scrap (Iscrép). Scrape é “raspar”, Scrap é “descartar”. Estamos usando um wébiscrêiper, um raspador de dados, não um webiscréper, um descartador de dados. A grafia inclusive seria webscrapper, e não é.
Voltando:
O primeiro passo é instalar o scraper que vamos usar, chamado google-play-scraper. Tem um pra App Store também, se não me engano chama app-store-scraper.
pip install google-play-scraper
Uma vez instalado, vamos para as configs que queremos.
#Importando o comando app da library google-play-scraper from google_play_scraper import app
Agora eu preciso do “nome” do app que quero analisar:
No caso desse artigo é o ‘com.rappi.storekeeper’, também conhecido como Rappi para Entregador.
Legal! Onde você achou esse “nome” para o app? Demorei pra captar quando fui ler a documentação do google-play-scraper, que usa como exemplo o Pokemon Go em Inglês.
Vamos lá.
Passo 1: Entre na Google Play Store
Passo 2: Busque o App que você quer analisar
Passo 3: Veja qual a url do app.
No meu caso, ficou assim:
https://play.google.com/store/apps/details?id=com.rappi.storekeeper
O nome depois do id= é o nome que você precisa usar no scraper.
result = app('com.rappi.storekeeper', lang = 'pt', country = 'br') result
o parâmetro lang é o idioma (português) e o country o país (Brasil).
Quando eu der result, vai sair isso aqui no notebook:
{'adSupported': None, 'androidVersion': '4.4', 'androidVersionText': '4.4 ou superior', 'appId': 'com.rappi.storekeeper', 'comments': [], 'containsAds': False, 'contentRating': 'Classificação 14 anos', 'contentRatingDescription': 'Drogas Ilícitas', 'currency': 'USD', 'description': 'Ganhe dinheiro por cada entrega, sem limite de lucro!\r\n\r\n\r\nFizemos este app pensando em você! Tenha toda a informação que você precisa e coordene os seus pedidos com a palma da sua mão. \r\n\r\n<b>Novidades:</b>\r\n\r\n- Seus pedidos agora terão um passo-a-passo, permitindo maior controle do processo \r\n- Histórico com todas as informações das suas dispersões e pagamentos \r\n- Sistema para reservar e verificar suas vagas\r\n- Canal de suporte para receber ajuda no que você precisar\r\n- Opção de ajuda para encontrar informações e novidades da Rappi\r\n- Mapa com zonas da cidade para que você conheça os lugares e as horas com maior demanda\r\n- Botão de emergência\r\n- Promoções\r\n\r\nBaixe o app, complete o registro, assista a nossa primeira sessão informativa e pronto! \r\n\r\nGere dinheiro quando precisar e conecte-se quando quiser. Sem chefes e sem horários. Entregue a qualquer momento, graças a nossa grande rede de aliados.\r\n\r\nNão deixe de fazer o que você gosta. Seja estudar, trabalhar ou compartilhar seu tempo com a sua família, cumpra seus objetivos com Rappi. \r\n \r\nNão cobramos comissões: todo o lucro que aparece no app é seu. \r\nRappi tem muitos aliados, pedidos e possibilidades de lucros.\r\n\r\nEstamos em 9 países, 87 cidades e continuamos crescendo a cada dia, com entregas em bicicleta, moto, carro e até caminhando! **\r\n\r\n<b>Conheça mais em:</b>\r\n\r\nsoyrappi.com/faq\r\nhttps://blogbra.soyrappi.com\r\nhttps://bit.ly/2Uaoo3G\r\n\r\n*Segundo disponibilidade do paí/cidade', 'descriptionHTML': 'Ganhe dinheiro por cada entrega, sem limite de lucro!<br><br><br>Fizemos este app pensando em você! Tenha toda a informação que você precisa e coordene os seus pedidos com a palma da sua mão. <br><br><b>Novidades:</b><br><br>- Seus pedidos agora terão um passo-a-passo, permitindo maior controle do processo <br>- Histórico com todas as informações das suas dispersões e pagamentos <br>- Sistema para reservar e verificar suas vagas<br>- Canal de suporte para receber ajuda no que você precisar<br>- Opção de ajuda para encontrar informações e novidades da Rappi<br>- Mapa com zonas da cidade para que você conheça os lugares e as horas com maior demanda<br>- Botão de emergência<br>- Promoções<br><br>Baixe o app, complete o registro, assista a nossa primeira sessão informativa e pronto! <br><br>Gere dinheiro quando precisar e conecte-se quando quiser. Sem chefes e sem horários. Entregue a qualquer momento, graças a nossa grande rede de aliados.<br><br>Não deixe de fazer o que você gosta. Seja estudar, trabalhar ou compartilhar seu tempo com a sua família, cumpra seus objetivos com Rappi. <br> <br>Não cobramos comissões: todo o lucro que aparece no app é seu. <br>Rappi tem muitos aliados, pedidos e possibilidades de lucros.<br><br>Estamos em 9 países, 87 cidades e continuamos crescendo a cada dia, com entregas em bicicleta, moto, carro e até caminhando! **<br><br><b>Conheça mais em:</b><br><br>soyrappi.com/faq<br>https://blogbra.soyrappi.com<br>https://bit.ly/2Uaoo3G<br><br>*Segundo disponibilidade do paí/cidade', 'developer': 'Rappi, Inc - Delivery', 'developerAddress': None, 'developerEmail': '[email protected]', 'developerId': 'Rappi,+Inc+-+Delivery', 'developerInternalID': '5624915113975274778', 'developerWebsite': 'http://www.rappi.com', 'free': True, 'genre': 'Corporativo', 'genreId': 'BUSINESS', 'headerImage': 'https://lh3.googleusercontent.com/aamdTUDw51odx6ubwkF25HT9sLk7ksdT9ezRGfyTQclK_iS_mFjKgAzT63geQH_mnRWj', 'histogram': [28436, 6715, 13082, 25278, 166545], 'icon': 'https://lh3.googleusercontent.com/8uxa1rl11waVZQ-86950pZJzMFHRKPO-E5vo_dUjNShi8ksWWvPBTf0f08gv2yMCOR8', 'inAppProductPrice': None, 'installs': '5.000.000+', 'minInstalls': 5000000, 'offersIAP': False, 'originalPrice': None, 'price': 0, 'privacyPolicy': 'http://grability.rappi.com/api/termsconditions', 'ratings': 240056, 'recentChanges': None, 'recentChangesHTML': None, 'released': '3 de jun. de 2017', 'reviews': 135656, 'sale': False, 'saleText': None, 'saleTime': None, 'score': 4.22795, 'screenshots': ['https://lh3.googleusercontent.com/FlIHDs89XHL7OsWPzhJ1qagbNQyjWboNbxkYfgdC8FFua5Mv1OwqDS6D2i63iUNJNYGN', 'https://lh3.googleusercontent.com/C7G5Lctms7iPvHB0cFCNX3JESAf4MvHGyoOYwH1CZJu7v3WdFongRdZkzHe1egwDuZ0', 'https://lh3.googleusercontent.com/5_ZiJMBy58HG46LJV0PO5poUyzkI1ufdkfqG5lQ_eD8HSNTnd916jfhQ3zmXE3nflA', 'https://lh3.googleusercontent.com/BNFrnPdanaO8iyw9pPR5B6U93F44JrKSq2cccOGAsMSeTGxDFR0ck3NaYR1OyFmNp4CP'], 'size': '33M', 'summary': 'O App para ser entregador - Ganhe dinheiro com cada pedido', 'summaryHTML': 'O App para ser entregador - Ganhe dinheiro com cada pedido', 'title': 'App para entregadores - RappiEntregador', 'updated': 1601866167, 'url': 'https://play.google.com/store/apps/details?id=com.rappi.storekeeper&hl=pt&gl=br', 'version': '6.31.1', 'video': None, ' videoImage': None}
Parece que tá tudo certo!
App desejado? Check.
Idioma correto? Check.
Nota de rodapé: A Google Play menciona que a classificação dos comentários é 14 anos, pois menciona drogas ilícitas. Não confunda com o app que não tem nada a ver!
Bora baixar todas as reviews!
Bom, agora esse é o código que você precisa para baixar todas as reviews do seu app de escolha.
Muito parecido com o código anterior, mas aqui vamos usar a função reviews_all, o app, idioma e país igual.
Mas vamos usar também o parâmetro sort, onde ele apresentará primeiro os comentários mais relevantes (que tiveram mais “likes”) e um comando pra ele não “dormir” em caso de demorar muito.
#Baixando todos os reviews do app. from google_play_scraper import Sort, reviews_all #Comando para todas as reviews Reviews = reviews_all( 'com.rappi.storekeeper', lang = 'pt', country = 'br', sort = Sort.MOST_RELEVANT, sleep_milliseconds = 0)
Esse código leva uns 5 minutinhos para gerar o resultado.
Então é uma boa hora para levantar e tomar um café.
Voltou?
Ótimo, bora pra mais um pouco.
A primeira coisa que vamos fazer é transformar esse Reviews que criamos em um DataFrame, para podermos usá-lo de forma proveitosa.
Então:
#Importando pandas. import pandas as pd #Transformando os dados em um DataFrame para trabalharmos as análises. Reviews_Rappi = pd.DataFrame(Reviews) #Verificando a serie de dados. Reviews_Rappi
E você verá algo mais ou menos assim, mas incluídos também os nomes dos revisores (não vou replicar aqui):
Revisar o sentimento pelo emoji…. Será que vai dar?
1.2 Criando a tokenização das palavras e normalizando as palavras
Essa é uma das primeira etapas para o Processamento de Linguagem Natural. O que vamos fazer aqui é “fazer o Python entender” o que é uma palavra, ou seja, uma lista de símbolos (letras) que juntas criam um sentido. Sim, o Python não sabe isso de cara.
Então vamos lá.
#Importando o nltk e salvando os corpus necessários import nltk nltk.download('wordnet') nltk.download('punkt') #Aplicando uma função para tokenizar por palavra Reviews_Rappi['content'] = Reviews_Rappi.apply(lambda row: nltk.word_tokenize(row['content']), axis=1) # Tokenização dos dados
Agora, vamos trabalhar em normalizar as palavras para que tenhamos menos “sujeira” disponível.
Vamos, por exemplo:
- Remover palavras conectoras, as stopwords (conjunções, verbos de ligação, etc), para deixar nosso DataSet mais limpinho;
- Deixar todo o texto em caixa baixa, ou lowercase (sem maiúsculas) para não termos a mesma palavra em duas grafias;
- Remover a pontuação, pelos mesmos motivos.
Poderíamos fazer outras coisas, como remover acentuação. Mas não farei porque mais pra frente vou introduzir um dicionário de polaridade, então quero manter essa parte sem mexer por enquanto.
Pense como um MVP, por enquanto vamos assim, depois melhoramos.
Vamos usar uma lista pré carregada de stopwords (as tais palavras conectoras):
import re from nltk.corpus import stopwords nltk.download('stopwords') language = 'portuguese' #Criando a lista de stopwords stopwords = stopwords.words(language) stopwords = list(set(stopwords))
Agora, vamos criar uma função para cada um dos pontos 1, 2 e 3 que citei.
Ponto 1: Stopwords
def remove_stopwords(words): """Remover as Stopwords das palavras tokenizadas""" new_words = [] for word in words: if word not in stopwords: new_words.append(word) return new_words
Ponto 2: lowercase
def to_lowercase(words): """converter todos os caracteres para lowercase""" new_words = [] for word in words: new_word = word.lower() new_words.append(new_word) return new_words
Ponto 3: remover pontuação
def remove_punctuation(words): """remover pontuação""" new_words = [] for word in words: new_word = re.sub(r'[^\w\s]', '', word) if new_word != '': new_words.append(new_word) return new_words
Agora juntar tudo numa função para normalizar as palavras, ou seja, rodar todas as funções acima em uma.
def normalize(words): words = to_lowercase(words) words = remove_punctuation(words) words = remove_stopwords(words) return ' '.join(words)
E agora aplicar aos reviews:
Reviews_Rappi['content'] = Reviews_Rappi.apply(lambda row: normalize(row['content']), axis=1)
Pronto! Barba, cabelo e bigode!
1.3 Importando o SentiLex-PT02
O SentiLex-PT02 é um dicionário de sentimentos.
Que?
Sim, é nada mais que um arquivo em texto que possui a palavra e indica o sentimento, tipo:
Bom: palavra positiva, então +1
Ruim: Palavra negativa, então -1
Normal: Palavra neutra, então 0
Para baixar, siga os passos aqui
Depois disso, simplesmente arraste para seu ambiente no Google Colab.
#Importando o Léxico de Palavras com polaridades sentilexpt = open('SentiLex-lem-PT02.txt') #Criando um dicionário de palavras com a respectiva polaridade. dic_palavra_polaridade = {} for i in sentilexpt.readlines(): pos_ponto = i.find('.') palavra = (i[:pos_ponto]) pol_pos = i.find('POL') polaridade = (i[pol_pos+7:pol_pos+9]).replace(';', '') dic_palavra_polaridade[palavra] = polaridade #Verificando o dicionário dic_palavra_polaridade
A partir desses passos, você verá algo assim:
Agora vamos criar uma função para aplicar o SentiLex nos reviews e colocar o score numa nova coluna:
#Criando uma função chamada "Score de Sentimento" para determinar os #sentimentos associados def Score_sentimento(frase): frase = frase.lower() l_sentimento = [] for p in frase.split(): l_sentimento.append(int(dic_palavra_polaridade.get(p, 0))) score = sum(l_sentimento) if score > 0: return 'Pos {} '.format(score) elif score == 0: return 'Neu {} '.format(score) else: return 'Neg {}'.format(score)
Nessa função o que eu fiz? “Leia a frase, calcule a soma de palavras e diga se o final é positivo ou negativo”.
Coisas por exemplo: Bom, mas pode melhorar, deve sair neutro. (+1-1 = 0).
Bora aplicar isso para todas as linhas do Dataset.
#Criando uma função para aplicar um score de sentimento para cada um dos comentários, a partir das palavras positivas e negativas. Reviews_Rappi['sentimento'] = Reviews_Rappi.apply(lambda row: Score_sentimento(row['content']), axis=1)
Depois que montei essa função, percebi que ficaria melhor se eu colocasse o score numérico separado das Palavras POS, NEU e NEG.
#Reorganizando o resultado em colunas para posteriormente lançar no modelo Reviews_Rappi['Score_Sentimento'] = Reviews_Rappi['sentimento'].str.slice(-2) Reviews_Rappi['Score_Sentimento'] =Reviews_Rappi['Score_Sentimento'].astype(int) Reviews_Rappi['Sent'] = Reviews_Rappi['sentimento'].str.slice(0,-3)
Agora posso olhar como ficou o Dataset com as notas.
#Verificando como ficou a distribuição de comentários a partir do Score de Sentimento Criado. Reviews_Rappi.groupby('Score_Sentimento').count()
Ficou assim.
1.4 Wordclouds.
Pra finalizar essa parte 1 que já ficou bem longa, vamos olhar para as nuvens de palavras.
Para fazer a nuvem de palavras, precisamos juntar o texto de todas as linhas, como se fosse um livro de review.
Vamos lá.
Primeiro um novo objeto usando somente a coluna de comentários.
#criando um objeto somente com os comentários content = Reviews_Rappi['content']
Agora juntando tudo:
#juntando todos eles para construir a wordcloud - ela tem que estar todo contido numa string all_content = "".join(c for c in content)
Importando tudo e montando a wordcloud:
#importando as libraries necessárias para o wordcloud from wordcloud import WordCloud, ImageColorGenerator, STOPWORDS #montando um novo dicionário de stopwords stopwords = set(STOPWORDS)
Aqui eu fui tentando e errando: adicionei algumas palavras que vi na cloud que eram “sujeira” e fui limpando.
Ou seja: montei o gráfico, vi que tinha coisas “sujas” e fui anotando. Depois incluí nessa lista para o gráfico ficar mais limpo.
#Após review das palavras, adicionando alguns termos "sujeira" encontrados nas nuvens stopwords.update(["pra", "app", "aplicativo", "vc", "pra", "to", "os", "rappi", "vcs", "nao", "pq", "mim", "ai", "ta", "ja", "ter", "fazer", "lá", "deu", "dado", "então", "vou", "vai", "veze", "ficar", "tá", "apena"])
Agora, vamos criar finalmente a wordcloud:
#Criando o objeto wordcloud com as configs necessárias. Cor escolhida = preta, origem dos dados = all_content wordcloud = WordCloud(stopwords=stopwords, background_color='black', width=1600, height=800).generate(all_content) #configurando forma de apresentação do gráfico e apresentando no notebook. fig, ax = plt.subplots(figsize=(16,8)) ax.imshow(wordcloud, interpolation='bilinear') ax.set_axis_off() plt.imshow(wordcloud)
Pronto!
Se quiser montar um pandas-profiling e analisar também como ficaram as classificações, pode fazer seguindo os mesmos passos que ensinei aqui:
Conclusão, até agora:
- Fizemos um scraping (iscrêiping) do Google Play e puxamos a base de dados de avaliações do Google Play. Tem sites famosos como o App Annie que pra fazer isso só pagando.
- Transformamos os dados em um Dataset para ficar bem bonito.
- Normalizamos os dados, para que eles fiquem mais limpos. Importante: se você lendo percebeu que dava para limpar muito mais, a resposta é: eu sei. Em algum momento podemos voltar a isso para melhorar a performance do modelo.
- Inserimos um dicionário de sentimentos, o SentiLex-PT02 e calculamos scores para as frases.
- Criamos uma nuvem de palavras para o DataSet Limpo.
Parte 2
- Demonstrarei os tratamentos para fazer um modelo de Machine Learning usando somente o texto (coluna ‘content’) para prever a nota.
- Vamos rodar o modelo e ver os resultados.
Parte 3
- Vamos tentar evoluir, usando uma modelagem mais complexa, incluindo os demais fatores para a previsão (O Score do SentiLex, o número de “likes” nos comentários, etc)
Por enquanto, espero que tenham gostado!
Recomendo que testem com outros apps!
Um abraço!
Observações:
Bom, brincadeiras que começam assim (como brincadeiras) e vão evoluindo! Escrevi esse artigo para compartilhar minha jornada no NLP em Português, que começou agora. Nesse primeiro artigo, eu mostro:
– Como baixar todas as avaliações de um App na Google Play e transformá-lo no formato Dataset;
– Como transformar as palavras em tokens para que o Python compreenda palavras;
– Como normalizar os dados;
– Como usar um Dicionário de Sentimentos e Calcular o Score numa Frase;
– Como fazer uma WordCloud. A idéia é mostrar em detalhe como rodar isso num Machine Learning simples, e em seguida num mais parrudo. Vamos ver o que vai dar!
Tag:#machinelearning, #nlp, #python