Análise de dados: média ou visualizar a distribuição?

Na nossa plataforma de cursos online queremos mostrar para nossos potenciais e atuais alunos e alunas o quanto as pessoas estão saindo satisfeitas após estudarem um curso. Para isso cada estudante dá uma nota entre 0 e 10, e podemos tirar a média. Por exemplo, um curso com 4 alunos que deram notas [6, 8, 9, 10] teria a média 8.25.

Ótimo, vamos calcular todas as médias de nossos cursos. Para isso usarei o Pandas em Python, e uma conexão a um banco MySQL:

sql ='''select avg(nota) as media from Registros r
where ...
group by r.curso;'''
medias = pd.read_sql(sql, conexao)

Imprimimos a média, moda e mediana:

print(f'Média: {medias.mean()}')      # Média: 9.03
print(f'Moda: {medias.mode()}')       # Moda: 9
print(f'Mediana: {medias.median()}')  # Mediana: 9.06

Que maravilha, a nota média de nossos cursos é aproximadamente 9, assim como a nota que mais aparece (a moda, que parece não fazer sentido nenhum nessa análise) é 9, e metade dos nossos cursos (mediana) estão acima de 9.06 e metade abaixo. Mas o quanto isso diz pra mim sobre meus cursos? Ainda não muito. O desvio padrão pode nos ajudar:

print(f'Desvio padrão: {medias.std()}')   # Desvio padrão: 0.33

A e o desvio padrão indicam que 95% dos nossos cursos estão entre 8.37 e 9.69. Ótimo! Sucesso para a empresa.

Mas números são difíceis de “visualizar”

Ao invés de resumir todas as informações em um único número, vamos tentar visualizar essas médias?

Usando a biblioteca seaborn (importada como `sns`) podemos plotar um “tique” para cada nota, um rugplot:

sns.rugplot(medias)

Mas como temos muitos “tiques” próximos, não conseguimos ver direito a informação. Que tal se acumularmos os tiques? Isto é, se dois cursos tem nota entre 9 e 9.1, faço uma barra de altura 2 ali. Se cinco cursos tem nota média entre 9.1 e 9.2, faço uma barra de altura 5 ali. Esse é o básico de um histograma: dividir o espaço de notas em diversos pequenos trechos e desenhar barras de acordo:

sns.distplot(medias, hist=True, kde=False)

Agora sim conseguimos ver que nenhum curso tem nota média menor do que 7, e que o grande bolo de cursos está lá com notas boas entre 8.5 e 9.5. Mas podemos melhorar essa visualização, repara que dividimos em muitos pedaços pequenos, o próprio seaborn tenta adivinhar uma divisão adequada (existem algoritmos para isso), mas podemos em alguns casos que achamos interessantes forçar, como aqui quero forçar 10 espaços (bins):

sns.distplot(medias, hist=True, kde=False, bins=10)

Mas repara que nossas notas médias de cada curso podem ir de 0 a 10, então podemos definir o valor mínimo e máximo, que fica ótimo para o ego, todas as notas médias acumuladas lá na direita:

Mas não ajuda a entender a distribuição das notas né? portanto voltamos atrás e tentamos somente com o eixo x que a própria biblioteca sugere, além de 20 espaços agora:

sns.distplot(medias, hist=True, kde=False, bins=20)

Existem tentativas de fórmula mágica para tentar descobrir um número ótimo de bins, e o padrão do seaborn tenta nos ajudar, mas repara que vai sempre depender da distribuição dos seus pontos e do que você deseja focar. No nosso caso, 20 pareceu o mais interessante.

Por mais que as barras do histograma deêm um sentido aos nossos dados, seria interessante tentar traçar uma função `f(media)` que descreva os nossos dados. A maneira mais tradicional de aproximar o histograma a uma função é através do método chamado de `kde`:

sns.distplot(medias, hist=True, kde=True, bins=20)

Essa função poderia ser usada para tentarmos prever a distribuição para pontos que não possuimos informação. Repara como o comportamento da distribuição dessa função se assemelha muito a uma distribuição normal (gamma):

sns.distplot(medias, fit=stats.gamma, hist=True, kde=True)

Quando seus dados se assemelham a uma normal de verdade (sem outliers, caudas tendendo a zero, assimétricas etc) aquela medida de média lá do começo e o desvio padrão podem fazer sentido, mas vamos tentar explorar um pouco mais o que conseguimos. Em geral, mais interessante que o histograma, é entender a área embaixo do histograma.

O que podemos fazer? Ao invés de plotarmos a função da distribuição – no ponto 8, médias iguais a 8, no ponto 9 médias igual a 9 –  vamos acumular os valores? Isto é, no ponto 8, colocamos todas as notas menores que 8, no ponto 9, as menores do que 9. Acumulando:

sns.kdeplot(medias['media'], cumulative=True)

O gráfico cumulativo é muito importante. Ele mostra por exemplo, que se pegarmos a nota ~8.5, temos que menos de 8% das médias são abaixo dela. Assim como somente ~10% das médias estão acima de 9.5.

Aproveito para descrever como fazer a anotação que fiz no gráfico, plotando um pequeno `x` e duas linhas:

a4_dims = (8, 6)
fig, ax = pyplot.subplots(figsize=a4_dims)

sns.kdeplot(medias['media'], cumulative=True, ax=ax)
ax.set_xlabel('Nota média')
ax.set_ylabel('Porcentagem de cursos')

plt.plot(8.5, 0.075, "x")
plt.plot([8.5, 8.5], [0, 0.075], 'r--', lw=1)
plt.plot([6.7, 8.5], [0.075, 0.075], 'r--', lw=1)

Três visualizações conclusivas

Mas, o mais interessante para nós será invertermos a linha de pensamento. Se rotacionarmos nosso gráfico, trocando o eixo x e y poderemos ver as porcentagens acumuladas e as notas:

sns.kdeplot(medias['media'], cumulative=True, vertical=True)

Nessa visualização rotacionada fica bem fácil de escolher uma porcentagem, por exemplo, 20% e perceber que só 20% das notas estão abaixo de 8.8.

Desafio para o/a leitor/a: qual o motivo de, a longo prazo, ser muito difícil (quase impossível) manter média máxima (10) numa avaliação?

Por fim, sem rotacionar o nosso gráfico podemos plotar o histograma cumulativo junto ao kde:

sns.distplot(medias, hist=True, kde=True, color='y',
             hist_kws={'cumulative': True, 'color':'g'},
             kde_kws={'cumulative': True})

Nossos dois últimos gráficos são ferramentas ótimas para nós, como empresa, professores/as e educadores, pois indicam que os alunos em média terminam os cursos de maneira satisfatória, com notas positivas.

Mas a média não parece ajudar um ponto do aluno e da aluna. Se os top 10 cursos tem nota próximo a 10 e os bottom 10 tem nota próxima a 8, não é tão claro a diferença entre eles. E quem estuda conosco gostaria de diferenciar tais cursos claramente, para saber o que esperar de um curso com nota 8. Por isso a medida de média que estamos usando para nossos gráficos e que usávamos para mostrar a nota de nossos cursos parece não ajudar.

Visando ajudar o dia a dia de nossos alunos preferimos mudar a nota visualizada para uma outra fórmula baseada no NPS (mas não exatamente o NPS) para que a distribuição das notas ficasse mais “esticada” e nossos alunos e alunas entendam melhor que apesar de um curso ter um alto número de alunos e alunas satisfeitos, pode ou não estar no nosso top cursos. Com essa nova fórmula a distribuição é:

Gosto muito do resumo que o Paulo Silveira fez sobre essa decisão:

“O NPS gera números mais baixos. Pode parecer estranho, mas optamos por mostrar como média o ‘NPS normalizado’ por acreditar que esses números são mais coerentes à expectativa do aluno, com valores mais baixos e diferenciados em cursos que houve mais notas baixas. Caso contrário, a média simples puxava o valor para cima com facilidade, dada a esmagadora quantidade de alunos satisfeitos.”.

Recursivamente, dentro da própria Alura temos um curso que ensina a trabalhar com Python e Pandas, além de diversos outros cursos para analisar nossos dados e visualizações, dando o primeiro passo na sua carreira de analista de dados.

2 Comentários

  1. Junior Santos 23/08/2018 at 19:24 #

    Ótimo artigo !! sempre aproveito esses posts pra descobrir novos cursos.

Deixe uma resposta