Introdução

Esse case trata-se de um desafio proposto pela RankMyApp, de criar um Relatório de Análise de Dados de um aplicativo do setor financeiro de um banco fictício chamado B4 Bank, cujo objetivo é avaliar a performance de seu app. Foram fornecidas duas base de dados (Retained_Bank e Reviews_Bank). Ambas as bases de dados podem ser encontradas no repositório do github: https://github.com/isaccarvalho/Case_RankMyApp.

Bibliotecas necessárias

library(tidyverse)
library(data.table)
library(tm)
library(SnowballC)
library(tidytext)
library(stringr)
library(stringi)
library(rvest)
library(readr)
library(gridExtra)

Carregando base de dados

Primeiramente vamos carregar nossa base de dados. Lembrando que minha base já se encontra em meu diretório.

Retained_Bank <- read.csv("Retained_Bank.csv", header=TRUE, sep=";")

Reviews_Bank <- read.csv("Reviews_Bank.csv", header=TRUE, sep=";")

Entendendo os dados - Base Retained_Bank

Primeiramente vamos trabalhar com a base Retained_Bank. A base de dados Retained_Bank traz os dados de instalações, visitas e canal de aquisição do usuário no aplicativo. Neste case foi dito que o KPI dever ser baseado nas métricas do Canal Orgânico. Portanto, irei extrair somente o que nos interessa antes de começar.

*Observação: Na base Retained_Bank havia uma data “31/09/2019”, o que evidentemente não existe. Acredito que o mês 10 foi trocado pelo mês 9, pois no mês 10 não tem a data “31/10/2019”. Não sei se a troca foi proposital, mas de qualquer forma a data mencionada ficará fora da análise.

Analisando Dados

###Analisando base Retained_Bank

#Extraindo Canal Orgânico e excluindo data inexistente
Retained_Bank_Org = Retained_Bank %>%
  filter(Acquisition.Channel == "Organic") %>%
  filter(Date != "31/09/2019") %>%                      
  mutate(Date = as.Date(Date, format = "%d/%m/%Y"))

#Criando coluna mês
Retained_Bank_Org = Retained_Bank_Org %>%
  mutate(MES = month(Date))

#Separando indicadores entre tipo quantidade e taxa

#Quantidade
Retained_Bank_Org_Qtd = Retained_Bank_Org %>%
  select(Date,MES,Package.Name:Installers,Installers.retained.for.1.day,Installers.retained.for.7.days,Installers.retained.for.15.days,Installers.retained.for.30.days)

#Taxa
Retained_Bank_Org_Tx = Retained_Bank_Org %>%
  select(Date,MES,Package.Name,Acquisition.Channel,Visitor.to.Installer.conversion.rate,Installer.to.1.day.retention.rate,Installer.to.7.days.retention.rate,Installer.to.15.days.retention.rate,Installer.to.30.days.retention.rate)

#Quantidade total de Instalações Retidas por mês
Retained_Bank_Org_Qtd_Mes = Retained_Bank_Org_Qtd %>%
  group_by(MES) %>%
  summarise(Visitas = sum(Store.Listing.Visitors),
  Instalacoes = sum(Installers),
  Retidas_1dia = sum(Installers.retained.for.1.day),
  Retidas_7dias = sum(Installers.retained.for.7.days),
  Retidas_15dias = sum(Installers.retained.for.15.days),
  Retidas_30dias = sum(Installers.retained.for.30.days))

#Exibindo base sumarizada - Quantidade total de Instalações Retidas por mês
print.data.frame(Retained_Bank_Org_Qtd_Mes)
##   MES Visitas Instalacoes Retidas_1dia Retidas_7dias Retidas_15dias
## 1   8   45180        9387         8255          7887           7699
## 2   9   65011       12378        11040         10587          10334
## 3  10   73384       11941        10619         10115           9824
##   Retidas_30dias
## 1           7490
## 2          10065
## 3           9539

Tabela 1 - Quantidade total de Instalações Retidas por mês

#Taxa média de Instalações Retidas por mês
Retained_Bank_Org_Tx_Mes = Retained_Bank_Org_Tx %>%
  group_by(MES) %>%
  summarise(Visitas_por_Intalacoes = round(mean(Visitor.to.Installer.conversion.rate),digits = 2),
  taxa_Retidas_1dia = round(mean(Installer.to.1.day.retention.rate),digits = 2),
  taxa_Retidas_7dias = round(mean(Installer.to.7.days.retention.rate),digits = 2),
  taxa_Retidas_15dias = round(mean(Installer.to.15.days.retention.rate),digits = 2),
  taxa_Retidas_30dia = round(mean(Installer.to.30.days.retention.rate),digits = 2))

#Exibindo base sumarizada - Taxa média de Instalações Retidas por mês
print.data.frame(Retained_Bank_Org_Tx_Mes)
##   MES Visitas_por_Intalacoes taxa_Retidas_1dia taxa_Retidas_7dias
## 1   8                   0.22              0.88               0.84
## 2   9                   0.21              0.89               0.85
## 3  10                   0.17              0.89               0.84
##   taxa_Retidas_15dias taxa_Retidas_30dia
## 1                0.82               0.79
## 2                0.83               0.81
## 3                0.82               0.80

Tabela 2 - Taxa média de Instalações Retidas por mês

Interpretando resultados - Base Retained_Bank

Analisando os resultados dos indicadores da Tabela 1, a coluna ‘Visitas’ obteve aumento siginificativo nestes últimos 3 meses, porém a quantidade de ‘Instalações’ não acompanhou esse aumento. Na Tabela 2 confirma essa hipótese ao verificar que a conversão de Visitas por Instalações vem diminuindo nos últimos 3 meses. Os indicadores de Instalações Retidas permaneceram dentro da normalidade, o que significa que nesses últimos 3 meses não ouveram grandes mudanças nas retenções. O que tudo indica, os usuários estão visitando mais, porém não estão instalando o aplicativo.

Entendendo os dados - Base Reviews_Bank

A base de dados Reviews_Bank traz os dados das avaliações do aplicativo, como classificação de estrelas, comentários e sentimentos do usuário.

Na variável ‘Sentiment’ possui 3 categorias (Positive, Negative e Neutral), porém ela está relacionada com a variável ‘Star Rating’ (Classificação por estrelas). Se o usuário classificar o aplicativo com 1 ou 2 estrelas, teremos a variável ‘Sentiment’ com a categoria ‘Negative’, mas se o usuário classificar o aplicativo com 4 ou 5 Estrelas, teremos a variável ‘Sentiment’ com a categoria ‘Positive’ e claro que se o usuário classificar o aplicativo com 3 estrelas, teremos a variável ‘Sentiment’ com a categoria ‘Neutral’.

O problema é que a categoria ‘Neutral’ oculta informações para análise, não conseguimos saber se essa fatia de usuários tiveram uma experiência Positiva ou Negativa no aplicativo, mas podemos a partir dos comentários dos próprios usuários (na variável ‘Review Text’), utilizar o algoritmo de Análise de Sentimentos para descobrir.

Analisando dados

###Analisando base Reviews_Bank

#Relacionando Classificação de Estrelas e Sentimentos
Reviews_Bank_Stars.vs.Sentiment = Reviews_Bank %>%
  group_by(Star.Rating,Sentiment) %>%
  summarise(Quantidade = n())

print.data.frame(Reviews_Bank_Stars.vs.Sentiment)
##    Star.Rating Sentiment Quantidade
## 1            1                  282
## 2            1  Negative         90
## 3            2                  124
## 4            2  Negative         39
## 5            3                  198
## 6            3   Neutral         63
## 7            4                  686
## 8            4  Positive        231
## 9            5                 2961
## 10           5  Positive       1005

Podemos ver na tabela acima, que na variável ‘Sentiment’ possui campos vazios, vamos completá-los usando o mesmo critério da base.

Quem for 1 ou 2 estrelas: ‘Negative’.

Quem for 3 estrelas: ‘Neutral’.

Quem for 4 ou 5 estrelas: ‘Positive’.

#Preenchendo o vazio com sentimentos
Reviews_Bank = Reviews_Bank %>%
  mutate(Sentiment = ifelse(Star.Rating <= 2,
    "Negative",
ifelse(Star.Rating == 3,
    "Neutral",
"Positive")))

#Relacionando Classificação de Estrelas e Sentimentos (novamente)
Reviews_Bank_Stars.vs.Sentiment = Reviews_Bank %>%
  group_by(Star.Rating,Sentiment) %>%
  summarise(Quantidade = n())

print.data.frame(Reviews_Bank_Stars.vs.Sentiment)
##   Star.Rating Sentiment Quantidade
## 1           1  Negative        372
## 2           2  Negative        163
## 3           3   Neutral        261
## 4           4  Positive        917
## 5           5  Positive       3966

Já que sabemos quantas avaliações temos em classificação por estrelas, vamos analisar as avaliações de posição neutra. Usaremos o algoritmo de Machine Learning (NLP), do pacote TM(Text Mining) para realizar uma Análise de Sentimentos.

#filtrando sentimento Neutro e avaliações comentadas
Reviews_Bank_Neutro = Reviews_Bank %>%
  filter(Sentiment == "Neutral") %>%
  filter(Review.Text !="")

#Limpando texto
Reviews_Bank_Neutro$Review.Text = chartr("áéíóúÁÉÍÓÚýÝàèìòùÀÈÌÒÙâêîôûÂÊÎÔÛãõÃÕñÑäëïöüÄËÏÖÜÿçÇ",
                 "aeiouaeiouyyaeiouaeiouaeiouaeiouaoaonnaeiouaeiouycc",Reviews_Bank_Neutro$Review.Text)

#Criando Corpus
Corpus = Corpus(VectorSource(Reviews_Bank_Neutro$Review.Text))
  
#Minerando Texto
Corpus = tm_map(Corpus, content_transformer(tolower))
Corpus = tm_map(Corpus, removePunctuation)
Corpus = tm_map(Corpus, removeNumbers)
Corpus = tm_map(Corpus, removeWords, stopwords("portuguese"))
Corpus = tm_map(Corpus, stripWhitespace)
Reviews_Bank_Neutro_Corpus = TermDocumentMatrix(Corpus)

Reviews_Bank_Neutro_Corpus = as.matrix(Reviews_Bank_Neutro_Corpus)

#Lematização do texto
lemma_dicio = read.delim(file = "https://raw.githubusercontent.com/michmech/lemmatization-lists/master/lemmatization-pt.txt", header = FALSE, stringsAsFactors = FALSE)
names(lemma_dicio) = c("stem", "term")

#Condicional Lematização - Tratando dados
Palavras = row.names(Reviews_Bank_Neutro_Corpus)

for (j in 1:length(Palavras)){
  comparacao = Palavras[j] == lemma_dicio$term
  if (sum(comparacao) == 1){
    Palavras[j] = as.character(lemma_dicio$stem[comparacao])
  } else {
    Palavras[j] = Palavras[j]
  }
}

Palavras_Corpus = Palavras

Reviews_Bank_Neutro_Corpus_df = as.data.frame(Reviews_Bank_Neutro_Corpus)
row.names(Reviews_Bank_Neutro_Corpus_df) = NULL
Reviews_Bank_Neutro_Corpus_df$Palavras = Palavras_Corpus

#Somando ocorrências
Reviews_Bank_Neutro_Corpus_df = Reviews_Bank_Neutro_Corpus_df %>%
  mutate(Ocorrencias = apply(Reviews_Bank_Neutro_Corpus_df[, 2:174],1, sum)) %>%
  select(Palavras,Ocorrencias)

#Exibindo as 10 palavras mais mencionadas (exceto app e aplicativo)
Palavras.mais.mencionadas.Neutro = Reviews_Bank_Neutro_Corpus_df %>%
  filter(Ocorrencias >= 13) %>%
  filter(Palavras != "app") %>%
  filter(Palavras != "aplicativo")
  
 print.data.frame(arrange(Palavras.mais.mencionadas.Neutro,desc(Ocorrencias)))
##     Palavras Ocorrencias
## 1        nao          75
## 2        bom          21
## 3     lentar          18
## 4    consigo          16
## 5      fazer          16
## 6      vezes          15
## 7        ser          14
## 8        pra          14
## 9     cartao          13
## 10 depositar          13

Tabela 3 - 10 Palavras mais mencionadas - Sentimento Neutro(Avaliação de 3 Estrelas)

Agora vamos fazer o mesmo com Sentimento Positivo e Sentimento Negativo, para saber quais os pontos positivos do aplicativo e os pontos a melhorar.

#filtrando sentimento Positivo e avaliações comentadas
Reviews_Bank_Positivo = Reviews_Bank %>%
  filter(Sentiment == "Positive") %>%
  filter(Review.Text !="")

#Limpando texto
Reviews_Bank_Positivo$Review.Text = chartr("áéíóúÁÉÍÓÚýÝàèìòùÀÈÌÒÙâêîôûÂÊÎÔÛãõÃÕñÑäëïöüÄËÏÖÜÿçÇ",
                 "aeiouaeiouyyaeiouaeiouaeiouaeiouaoaonnaeiouaeiouycc",Reviews_Bank_Positivo$Review.Text)

#Criando Corpus
Corpus = Corpus(VectorSource(Reviews_Bank_Positivo$Review.Text))
  
#Minerando Texto
Corpus = tm_map(Corpus, content_transformer(tolower))
Corpus = tm_map(Corpus, removePunctuation)
Corpus = tm_map(Corpus, removeNumbers)
Corpus = tm_map(Corpus, removeWords, stopwords("portuguese"))
Corpus = tm_map(Corpus, stripWhitespace)

Reviews_Bank_Positivo_Corpus = TermDocumentMatrix(Corpus)
Reviews_Bank_Positivo_Corpus = as.matrix(Reviews_Bank_Positivo_Corpus)

#Condicional Lematização - Tratando dados
Palavras = row.names(Reviews_Bank_Positivo_Corpus)

for (j in 1:length(Palavras)){
  comparacao = Palavras[j] == lemma_dicio$term
  if (sum(comparacao) == 1){
    Palavras[j] = as.character(lemma_dicio$stem[comparacao])
  } else {
    Palavras[j] = Palavras[j]
  }
}

Palavras_Corpus = Palavras

Reviews_Bank_Positivo_Corpus_df = as.data.frame(Reviews_Bank_Positivo_Corpus)
row.names(Reviews_Bank_Positivo_Corpus_df) = NULL
Reviews_Bank_Positivo_Corpus_df$Palavras = Palavras_Corpus

#Somando ocorrências
Reviews_Bank_Positivo_Corpus_df = Reviews_Bank_Positivo_Corpus_df %>%
  mutate(Ocorrencias = apply(Reviews_Bank_Positivo_Corpus_df[, 1:2144],1, sum)) %>%
  select(Palavras,Ocorrencias)

#Exibindo as 10 palavras mais mencionadas (exceto app e aplicativo)
Palavras.mais.mencionadas.Positivo = Reviews_Bank_Positivo_Corpus_df %>%
  filter(Ocorrencias >= 84) %>%
  filter(Palavras != "app") %>%
  filter(Palavras != "aplicativo")
  
 print.data.frame(arrange(Palavras.mais.mencionadas.Positivo,desc(Ocorrencias)))
##     Palavras Ocorrencias
## 1        bom         407
## 2   praticar         259
## 3      facil         225
## 4      otimo         203
## 5  excelente         191
## 6        nao         174
## 7        bom         125
## 8     rapido         120
## 9       usar          94
## 10       bem          84

Tabela 4 - 10 Palavras mais mencionadas - Sentimento Positivo(Avaliação de 4 e 5 Estrelas)

#filtrando sentimento Negativo e avaliações comentadas
Reviews_Bank_Negativo = Reviews_Bank %>%
  filter(Sentiment == "Negative") %>%
  filter(Review.Text !="")

#Limpando texto
Reviews_Bank_Negativo$Review.Text = chartr("áéíóúÁÉÍÓÚýÝàèìòùÀÈÌÒÙâêîôûÂÊÎÔÛãõÃÕñÑäëïöüÄËÏÖÜÿçÇ",
                 "aeiouaeiouyyaeiouaeiouaeiouaeiouaoaonnaeiouaeiouycc",Reviews_Bank_Negativo$Review.Text)

#Criando Corpus
Corpus = Corpus(VectorSource(Reviews_Bank_Negativo$Review.Text))
  
#Minerando Texto
Corpus = tm_map(Corpus, content_transformer(tolower))
Corpus = tm_map(Corpus, removePunctuation)
Corpus = tm_map(Corpus, removeNumbers)
Corpus = tm_map(Corpus, removeWords, stopwords("portuguese"))
Corpus = tm_map(Corpus, stripWhitespace)

Reviews_Bank_Negativo_Corpus = TermDocumentMatrix(Corpus)
Reviews_Bank_Negativo_Corpus = as.matrix(Reviews_Bank_Negativo_Corpus)

#Condicional Lematização - Tratando dados
Palavras = row.names(Reviews_Bank_Negativo_Corpus)

for (j in 1:length(Palavras)){
  comparacao = Palavras[j] == lemma_dicio$term
  if (sum(comparacao) == 1){
    Palavras[j] = as.character(lemma_dicio$stem[comparacao])
  } else {
    Palavras[j] = Palavras[j]
  }
}

Palavras_Corpus = Palavras

Reviews_Bank_Negativo_Corpus_df = as.data.frame(Reviews_Bank_Negativo_Corpus)
row.names(Reviews_Bank_Negativo_Corpus_df) = NULL
Reviews_Bank_Negativo_Corpus_df$Palavras = Palavras_Corpus

#Somando ocorrências
Reviews_Bank_Negativo_Corpus_df = Reviews_Bank_Negativo_Corpus_df %>%
  mutate(Ocorrencias = apply(Reviews_Bank_Negativo_Corpus_df[, 1:438],1, sum)) %>%
  select(Palavras,Ocorrencias)

#Exibindo as 10 palavras mais mencionadas (exceto app e aplicativo)
Palavras.mais.mencionadas.Negativo = Reviews_Bank_Negativo_Corpus_df %>%
  filter(Ocorrencias >= 37) %>%
  filter(Palavras != "app") %>%
  filter(Palavras != "aplicativo")
  
 print.data.frame(arrange(Palavras.mais.mencionadas.Negativo,desc(Ocorrencias)))
##       Palavras Ocorrencias
## 1          nao         292
## 2      consigo          56
## 3       lentar          56
## 4  atualizacao          55
## 5       cartao          52
## 6         bank          48
## 7        vezes          45
## 8       contar          42
## 9        fazer          41
## 10   funcionar          37

Tabela 5 - 10 Palavras mais mencionadas - Sentimento Negativo(Avaliação de 1 e 2 Estrelas)

Interpretando resultados - Base Reviews_Bank

Analisando os resultados da Tabela 3, podemos observar que as avaliações de posição Neutra possuem a palavra “não” como a que mais se destaca, mencionada quase 4 vezes mais do que a segunda palavra “bom”. A terceira palavra de maior impacto “lento” vem acompanhada de outras palavras de ação como “consigo” e “fazer”. O que tudo indica, os usuários de posição Neutra não estão satisfeitos com aplicativo, que apesar do aplicativo ser bom, não conseguem fazer o que necessitam.

Nos resultados da Tabela 4, observamos que as avaliações de posição Positiva tem destaque para as palavras como, “prático”, “fácil” e “rápido”, porém a 6ª palavra mais mencionada é “não”, o que liga um alerta para esses usuários, que apesar de gostar muito do aplicativo, podem estar com algum tipo de problema.

Os resultados da Tabela 5, tem comportamento muito parecido com as avaliações de posição Neutra. As avaliações de posição Negativa tem a palavra “não” como a mais mencionada, acompanhada de outras palavras de ação como “consigo”, “fazer” e “funciona”. Outras palavras destacadas também são “lento” e “atualização”, com quase a mesma quantidade, trazendo indícios que essas palavras estão relacionadas.

Conclusão

É evidente que o aplicativo B4 Bank é muito bem avaliado, com uma nota média de 4,4 em mais de 5 mil avaliações. Mas os indicadores de Visualizações e Instalações mostram que o aplicativo vem perdendo potenciais clientes neste período de 3 meses. Além de que as avaliações dos usuários mostram que o aplicativo tem dificuldades operacionais com alguns usuários, até mesmo com usuários que avaliaram muito bem o aplicativo. Sugiro verificar as atualizações do aplicativo que podem ter causado lentidão e entre outros problemas em alguns usuários.

As visualizações dos resultados obtidos neste projeto podem ser encontradas no Github: https://github.com/isaccarvalho/Case_RankMyApp.