import pandas as pd
from plotnine import *
4 Visualizações com plotnine
= pd.read_csv('https://github.com/jtrecenti/main-cdad2/releases/download/data/camaras.csv') camaras
4.1 Introdução
Nessa parte, nosso interesse é trabalhar na escolha das melhores visualizações para nossos dados, e como implementar isso usando a biblioteca plotnine.
Por que isso é importante? Além de realizar análises de dados, precisamos nos preparar para comunicar resultados. Boas visualizações são essenciais para isso. A necessidade de comunicar dados pode acontecer dentro do dos seus estudos de direito, mas também em situações de negócios, como apresentações para clientes e visual law.
4.1.1 O que é?
Visualização de dados é a representação de dados em gráficos, tabelas e diagramas que podem ser interpretados por pessoas. Trata-se de uma área interdisciplinar, misturando estatística, arte e comunicação. É uma parte da área de data storytelling, que envolve organizar todos os resultados de uma análise de dados em uma ordem lógica para comunicar de forma efetiva com a audiência.
4.1.2 Por que fazer?
Visualizações estão presentes na grande maioria dos projetos de ciência de dados. Além disso, é a parte mais acessível da ciência de dados do ponto de vista de quem lê. Mostrar uma visualização costuma ser mais efetivo do que a saída de um modelo ou uma fórmula. Finalmente, é uma das partes mais difíceis de automatizar da ciência de dados. Uma carreira em dataviz dificilmente ficará obsoleta.
4.1.3 Para que servem?
Uma base de dados contém toda a informação que precisamos. No entanto, não somos capazes de tirar conclusões apenas olhando essas bases. Por isso, é necessário resumir esses dados em estatísticas, como vimos na apostila sobre medidas de posição e variabilidade. Nem sempre as estatísticas (os números) são úteis para uma comunicação efetiva… Por isso, faz sentido mostrá-las usando formas, cores e outros elementos que facilitam a absorção da informação pelas pessoas.
4.1.4 Em que momento utilizamos?
Abaixo, temos o ciclo da ciência de dados. Esse diagrama foi adaptado do livro R para ciência de dados, que é uma referência para quem quer aprender ciência de dados com R.
Esse ciclo resume a maioria das tarefas que precisamos executar ao longo de um projeto de ciência de dados. Começamos pela importação, que envolve a leitura de dados de diferentes fontes. Em seguida, limpamos e transformamos esses dados para que possam ser usados em análises. A etapa de análise é um ciclo em si, envolvendo transformação de dados (criação de colunas e agregações), a visualização dos dados e a aplicação de modelos estatísticos / de machine learning para entender os dados. Finalmente, precisamos comunicar os resultados ou automatizar nosso produto de dados.
Note que a visualização de dados aparece em duas partes principais: Visualizar e Comunicar.
Na parte de visualização, estamos fazendo uma análise exploratória dos dados. Isso significa que estamos tentando entender os dados, e não necessariamente comunicar resultados. É um trabalho de investigação, que precisa ser rápido de fazer. O objetivo principal é aprender.
Na parte de comunicação, estamos fazendo um trabalho de otimização visual. Agora, nosso objetivo é comunicar os resultados com outras pessoas. Isso significa que precisamos de gráficos mais bonitos, mais explicativos e mais fáceis de entender. O trabalho deve, inclusive, ser encaixado em um fluxo de storytelling. O objetivo principal é comunicar.
O plotnine
é uma biblioteca muito efetiva para fazer gráficos de análise exploratória e para otimização visual de gráficos estáticos. É possível fazer gráficos muito bonitos com ela, e é uma biblioteca muito fácil de usar. Por isso, é uma ótima escolha para começar a aprender visualização de dados. Ela é baseada em uma ferramenta muito popular e robusta da linguagem de programação R: o ggplot2
. Atualmente, o plotnine
é a biblioteca mais próxima do ggplot2
na linguagem Python e, mesmo não sendo tão completa quanto o ggplot2
, já é suficiente para nossas aplicações.
A partir de agora, vamos retomar os nossos conceitos de tipos de variáveis e descrever as melhores visualizações para cada combinação de tipos de variáveis.
Uma referência legal nesse sentido é o site From Data to Viz, que é um guia para escolher a melhor visualização para os seus dados.
Vamos tratar dos seguintes exemplos:
- Visualizações para variáveis categóricas
- Gráficos univariados
- Gráficos bivariados
- Com outra categórica
- Com variável numérica
- Visualizações para variáveis numéricas
- Gráficos univariados
- Gráficos bivariados
- Com variável categórica
- Com outra numérica
Primeiro vamos ver gráficos exploratórios, depois vamos dar um exemplo de otimização visual.
4.2 Mapeamento estético
O plotnine
é baseado na gramática dos gráficos (grammar of graphics), que é uma forma de pensar em gráficos de forma estruturada. A ideia central é que um gráfico é uma representação visual de dados. Assim, um gráfico é composto por:
- Dados: a base de dados que queremos representar
- Mapeamento estético: a relação entre as variáveis (colunas) da base de dados e os elementos visuais do gráfico (posição, cor, tamanho, forma)
- Geometria: os elementos visuais que compõem o gráfico (pontos, linhas, barras, caixas, etc)
O gráfico também pode ter outros componentes, como facetas, temas, rótulos, etc. No entanto, os três componentes acima são os mais importantes.
4.3 Visualizações para variáveis categóricas
Os gráficos univariados de variáveis categóricas são os mais comuns em ciência de dados no direito, já que o tipo de variável mais comum é a categórica.
4.3.1 Univariada
A função mais comum para visualizar variáveis categóricas é o countplot
. Esse gráfico é uma versão do barplot
do matplotlib
, mas com a contagem de cada categoria no eixo y.
(+ # dados
ggplot(camaras) ='decisao') + # mapeamento estético
aes(x# geometria
geom_bar() )
Outra forma de visualizar é rotacionando o eixo:
(+ # dados
ggplot(camaras) ='decisao') + # mapeamento estético
aes(x+ # geometria
geom_bar()
coord_flip() )
Se nosso interesse é mostrar a proporção e não os valores absolutos, podemos calcular as proporções e depois utilizar um gráfico de barras com geom_col()
:
= camaras.value_counts('decisao').reset_index(name='n')
dados_agregados 'prop'] = dados_agregados['n'] / dados_agregados['n'].sum()
dados_agregados[
(+
ggplot(dados_agregados) ='decisao', y='prop') +
aes(x+
geom_col()
coord_flip() )
Também podemos ordenar as barras para facilitar a leitura. Duas formas de fazer isso são:
- Usando a função
reorder()
(mais fácil). - Transformando a variável categórica em um tipo
category
do pandas, e ordenando as categorias (mais robusto).
# usando reorder
= camaras.value_counts('decisao').reset_index(name='n')
dados_agregados 'prop'] = dados_agregados['n'] / dados_agregados['n'].sum()
dados_agregados[
(+
ggplot(dados_agregados) ='reorder(decisao, n)', y='n') +
aes(x+
geom_col()
coord_flip() )
# usando categoria ordenada
= camaras.value_counts('decisao').reset_index(name='n')
dados_agregados 'prop'] = dados_agregados['n'] / dados_agregados['n'].sum()
dados_agregados[
'decisao'] = pd.Categorical(
dados_agregados['decisao'],
dados_agregados[=dados_agregados.sort_values('n')['decisao']
categories
)
(+
ggplot(dados_agregados) ='decisao', y='n') +
aes(x+
geom_col()
coord_flip() )
4.3.2 Bivariada: explicativa categórica
Quando temos duas variáveis categóricas, temos várias escolhas de visualizações possíveis para os gráficos de barras. Os mais comuns são i) separar por cores e colocar as barras lado a lado; ii) separar por cores e empilhar as barras; e iii) criar sub-gráficos para cada categoria.
Barras lado a lado
= (
ct_mag
camaras'polo_mp == "Passivo"')
.query('rel_tipo_magistrado', 'decisao'])
.value_counts([='n')
.reset_index(name
)
'prop'] = ct_mag['n'] / ct_mag.groupby('rel_tipo_magistrado')['n'].transform('sum')
ct_mag[
ct_mag
rel_tipo_magistrado | decisao | n | prop | |
---|---|---|---|---|
0 | desembargador | Negaram | 4259 | 0.533041 |
1 | desembargador | Parcialmente | 3098 | 0.387735 |
2 | juiz substituto 2º grau | Negaram | 656 | 0.547123 |
3 | juiz substituto 2º grau | Parcialmente | 445 | 0.371143 |
4 | desembargador | Provido | 370 | 0.046308 |
5 | desembargador | Punibilidade Extinta | 205 | 0.025657 |
6 | juiz substituto 2º grau | Provido | 58 | 0.048374 |
7 | desembargador | Outros | 45 | 0.005632 |
8 | juiz substituto 2º grau | Punibilidade Extinta | 28 | 0.023353 |
9 | desembargador | Não conhecido | 13 | 0.001627 |
10 | juiz substituto 2º grau | Outros | 10 | 0.008340 |
11 | juiz substituto 2º grau | Não conhecido | 2 | 0.001668 |
(+
ggplot(ct_mag) ='decisao', y='prop', fill='rel_tipo_magistrado') +
aes(x='dodge') +
geom_col(position
coord_flip() )
Barras empilhadas
'extraord'] = camaras['camara'].str.contains('Extra')
camaras[
= (
ct_cam
camaras'polo_mp == "Passivo"')
.query('extraord == False')
.query('camara', 'unanimidade'])
.value_counts([='n')
.reset_index(name
)
'prop'] = ct_cam['n'] / ct_cam.groupby('camara')['n'].transform('sum')
ct_cam[
10) ct_cam.head(
camara | unanimidade | n | prop | |
---|---|---|---|---|
0 | 06ª Câmara de Direito Criminal | Unânime | 661 | 0.995482 |
1 | 08ª Câmara de Direito Criminal | Unânime | 621 | 0.977953 |
2 | 07ª Câmara de Direito Criminal | Unânime | 587 | 0.979967 |
3 | 16ª Câmara de Direito Criminal | Unânime | 579 | 0.983022 |
4 | 13ª Câmara de Direito Criminal | Unânime | 575 | 0.984589 |
5 | 14ª Câmara de Direito Criminal | Unânime | 567 | 0.986087 |
6 | 09ª Câmara de Direito Criminal | Unânime | 551 | 0.985689 |
7 | 15ª Câmara de Direito Criminal | Unânime | 538 | 0.952212 |
8 | 11ª Câmara de Direito Criminal | Unânime | 531 | 0.960217 |
9 | 04ª Câmara de Direito Criminal | Unânime | 525 | 0.983146 |
(+
ggplot(ct_cam) ='camara', y='prop', fill = 'unanimidade') +
aes(x+
geom_col()
coord_flip() )
Sub-gráficos (facets)
Para facer sub-gráficos com o plotnine, podemos usar a função facet_wrap()
. Ela cria um sub-gráfico para cada categoria de uma variável categórica.
= ['SAO PAULO', 'SAO JOSE DOS CAMPOS', 'SANTOS', 'CAMPINAS']
comarcas
= (
ct_comarca
camaras'polo_mp == "Passivo"')
.query('comarca in @comarcas')
.query('comarca', 'decisao'])
.value_counts([
.reset_index()
)
'prop'] = ct_comarca['count'] / ct_comarca.groupby('comarca')['count'].transform('sum')
ct_comarca[
ct_comarca
comarca | decisao | count | prop | |
---|---|---|---|---|
0 | SAO PAULO | Negaram | 1061 | 0.579465 |
1 | SAO PAULO | Parcialmente | 657 | 0.358820 |
2 | CAMPINAS | Negaram | 127 | 0.566964 |
3 | CAMPINAS | Parcialmente | 79 | 0.352679 |
4 | SAO JOSE DOS CAMPOS | Negaram | 70 | 0.588235 |
5 | SAO PAULO | Provido | 69 | 0.037684 |
6 | SANTOS | Negaram | 63 | 0.636364 |
7 | SAO JOSE DOS CAMPOS | Parcialmente | 42 | 0.352941 |
8 | SAO PAULO | Punibilidade Extinta | 36 | 0.019661 |
9 | SANTOS | Parcialmente | 30 | 0.303030 |
10 | CAMPINAS | Provido | 17 | 0.075893 |
11 | SAO PAULO | Outros | 7 | 0.003823 |
12 | SAO JOSE DOS CAMPOS | Provido | 5 | 0.042017 |
13 | SANTOS | Punibilidade Extinta | 3 | 0.030303 |
14 | SANTOS | Provido | 3 | 0.030303 |
15 | SAO JOSE DOS CAMPOS | Punibilidade Extinta | 2 | 0.016807 |
16 | CAMPINAS | Punibilidade Extinta | 1 | 0.004464 |
17 | SAO PAULO | Não conhecido | 1 | 0.000546 |
(+
ggplot(ct_comarca) ='decisao', y='prop') +
aes(x+
geom_col() '~comarca') +
facet_wrap(
coord_flip() )
4.3.3 Bivariada: explicativa numérica
Quando temos a variável de interesse categórica e a explicativa numérica, é um pouco difícil de criar visualizações, porque no fundo o que queremos entender é como o aumento/diminuição dessa variável numérica afeta a probabilidade de um evento relacionado à variável categórica acontecer. Isso geralmente é feito através de modelos estatísticos como a regressão logística, que veremos mais para frente na disciplina.
Algumas alternativas são: i) categorizar a variável numérica – nesse caso, voltamos ao que já vimos anteriormente; ii) analisar a distribuição da variável numérica para cada categoria – nesse caso, é como se estivéssemos invertendo qual é a variável de interesse e qual é a variável explicativa, logo isso faz parte da seção de variáveis numéricas, que veremos ainda nessa apostila, mas mais para frente.
4.4 Visualização para variáveis numéricas
4.4.1 Univariada
A análise univariada de uma variável de interesse numérica geralmente busca entender a distribuição dos dados.
Existem três principais visualizações aqui: histograma, densidade e boxplot. A densidade é simplesmente uma versão suavizada do histograma, e é possível mostrar os dois ao mesmo tempo.
(+
ggplot(camaras) ='tempo') +
aes(x=1)
geom_histogram(binwidth )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:293: PlotnineWarning: stat_bin : Removed 1 rows containing non-finite values.
Podemos mudar o número de barras com os parâmetros bins
(quantidade de barras) ou binwidth
(largura da barra).
(+
ggplot(camaras) ='tempo') +
aes(x=40)
geom_histogram(bins )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:293: PlotnineWarning: stat_bin : Removed 1 rows containing non-finite values.
Densidade:
(+
ggplot(camaras) ='tempo') +
aes(x
geom_density() )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:293: PlotnineWarning: stat_density : Removed 1 rows containing non-finite values.
Veja que, nesse caso, o eixo y representa a densidade, e não as contagens. A densidade tem relação com o conceito de distribuição de probabilidades. Na verdade, essa densidade é uma estimativa da função densidade de probabilidades da variável estudada (ou seja, é um modelo estatístico!). A área total do gráfico é 1.
Abaixo, juntamos histograma e densidade
(+
ggplot(camaras) ='tempo') +
aes(x=1) +
geom_histogram(binwidth=after_stat('count')), alpha=0.2, fill = 'red')
geom_density(aes(y )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:293: PlotnineWarning: stat_bin : Removed 1 rows containing non-finite values.
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:293: PlotnineWarning: stat_density : Removed 1 rows containing non-finite values.
Finalmente, temos o boxplot. O boxplot pode ser usado no caso univariado, mas é mais comum quando temos uma variável explicativa categórica.
(='tempo')) +
ggplot(camaras, aes(y+
geom_boxplot()
coord_flip() )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:293: PlotnineWarning: stat_boxplot : Removed 1 rows containing non-finite values.
4.4.2 Bivariada: explicativa categórica
A ideia aqui é simplesmente repetir os gráficos acima para a variável categórica de interesse.
Começamos pelo histograma / densidade
(+
ggplot(camaras) ='tempo') +
aes(x=30) +
geom_histogram(bins'~decisao')
facet_wrap( )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:293: PlotnineWarning: stat_bin : Removed 1 rows containing non-finite values.
Note que aqui, a escala pode afetar o gráfico e dificultar a comparação.
(+
ggplot(camaras) ='tempo') +
aes(x=30) +
geom_histogram(bins'~decisao', scales='free_y')
facet_wrap( )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:293: PlotnineWarning: stat_bin : Removed 1 rows containing non-finite values.
Quando temos muitas categorias, o gráfico fica difícil de interpretar. Nesse caso, o boxplot é uma alternativa melhor.
= camaras[~camaras['extraord']].sort_values('camara')
camaras_sem_extraord
(+
ggplot(camaras_sem_extraord) ='camara', y='tempo') +
aes(x+
geom_boxplot()
coord_flip() )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:293: PlotnineWarning: stat_boxplot : Removed 1 rows containing non-finite values.
# alternativa ao boxplot: violinplot
# trata-se basicamente de um gráfico de densidade duplicado. Fica bonitinho.
(+
ggplot(camaras_sem_extraord) ='camara', y='tempo') +
aes(x=0.5) +
geom_violin(draw_quantiles
coord_flip() )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:293: PlotnineWarning: stat_ydensity : Removed 1 rows containing non-finite values.
4.4.3 Bivariada: explicativa numérica
Quando temos duas variáveis numéricas, usualmente fazemos um gráfico de dispersão. Nesse caso, usamos o geom_point()
.
(+
ggplot(camaras) ='rel_tempo_magistratura', y='tempo') +
aes(x
geom_point() )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:372: PlotnineWarning: geom_point : Removed 2733 rows containing missing values.
Podemos até adicionar mais variáveis aqui, como, por exemplo, nas cores
(+
ggplot(camaras) ='rel_tempo_magistratura', y='tempo', colour='rel_quinto') +
aes(x
geom_point() )
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\layer.py:372: PlotnineWarning: geom_point : Removed 2733 rows containing missing values.
Um tipo especial de análise bivariada é quando o eixo x é uma data. Nesse caso, temos uma série de tempo, e representsaos os dados com um gráfico de linhas.
# vamos calcular a proporção de negados ao longo do tempo
# criando uma coluna que pega a data de publicação e arredonda o trimestre
# o pandas é bem burocrático para fazer essa tarefa simples
'periodo'] = pd.to_datetime(camaras['dt_publicacao']).dt.to_period('Q').dt.to_timestamp()
camaras[
= (
prop_negados_mes
camaras'polo_mp == "Passivo"')
.query('periodo')
.groupby(= ('decisao', lambda x: (x == 'Negaram').mean()))
.agg(prop_negados
.reset_index()
)
(+
ggplot(prop_negados_mes) ='periodo', y='prop_negados') +
aes(x
geom_line() )
4.5 Otimização
A otimização visual é um assunto extenso e muito baseado em tentativa e erro. Afinal, o que queremos aqui é adaptar nosso gráfico para um fim específico, e isso pode variar muito. Nessas situações, o chatGPT e ferramentas similares podem ser muito úteis!
Vamos colocar alguns exemplos de otimização aqui:
- Colocar o % nos eixos que são porcentagens
- Mudar o título dos eixos
- Mudar as cores das barras
- Mudar a cor de fundo
Colocar o % nos eixos que são porcentagens
Vamos voltar para o gráfico que vimos anteriormente
from mizani.labels import percent_format
(+
ggplot(ct_mag) ='reorder(decisao, prop)', y='prop', fill='rel_tipo_magistrado') +
aes(x='dodge') +
geom_col(position+
coord_flip() =percent_format())
scale_y_continuous(labels )
Veja que precisamos voltar a usar uma função mais ‘baixo nível’, a barplot
Mudar o título dos eixos
(+
ggplot(ct_mag) ='reorder(decisao, prop)', y='prop', fill='rel_tipo_magistrado') +
aes(x='dodge') +
geom_col(position+
coord_flip() =percent_format()) +
scale_y_continuous(labels
labs(='Decisões em recursos por tipo de magistrado',
title='Decisão',
x='Proporção',
y='Tipo de magistrado'
fill
) )
Cores
(+
ggplot(ct_mag) ='reorder(decisao, prop)', y='prop', fill='rel_tipo_magistrado') +
aes(x='dodge') +
geom_col(position+
coord_flip() =percent_format()) +
scale_y_continuous(labels
labs(='Decisões em recursos por tipo de magistrado',
title='Decisão',
x='Proporção',
y='Tipo de magistrado',
fill='Fonte: Dados extraídos do site do TJSP'
caption+
) +
theme_minimal() type='qual', palette='Set1')
scale_fill_brewer( )
= (
grafico_otimizado +
ggplot(ct_mag) ='reorder(decisao, prop)', y='prop', fill='rel_tipo_magistrado') +
aes(x='dodge') +
geom_col(position+
coord_flip() =percent_format()) +
scale_y_continuous(labels
labs(='Decisões em recursos por tipo de magistrado',
title='Decisão',
x='Proporção',
y='Tipo',
fill='Fonte: Dados extraídos do site do TJSP'
caption+
) +
theme_minimal() type='qual', palette='Set2') +
scale_fill_brewer(
theme(=element_text(hjust=0),
plot_title='top'
legend_position
) )
Salvando um gráfico em arquivo:
='grafico_otimizado.png', dpi=300, width=8, height=6) ggsave(grafico_otimizado, filename
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\ggplot.py:630: PlotnineWarning: Saving 8 x 6 in image.
c:\Users\julio\OneDrive\Documentos\insper\cdad-book\.venv\Lib\site-packages\plotnine\ggplot.py:631: PlotnineWarning: Filename: grafico_otimizado.png