Tópico 8 – Um pouco Mais de Visualização de Dados 📈
Aprender um pouco sobre transformação de dados e alguns exemplos a mais.
Resultados Esperados
- Aprender como transformar dados
- Aprender qual a melhor forma de ter dados sobre postos
Material Adaptado do DSC10 (UCSD)
#In:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use('ggplot')
Agenda
- Distribuições.
- Histogramas de densidade.
- Parcelas sobrepostas.
Revisão: tipos de visualizações
O tipo de visualização que criamos depende dos tipos de variáveis que estamos visualizando.
- Gráfico de dispersão: numérico versus numérico.
- Exemplo: peso x altura.
- Gráfico de linhas: numérico sequencial (tempo) vs.
- Exemplo: altura vs. tempo.
- Gráfico de barras: categórico vs. numérico.
- Exemplo: alturas de diferentes membros da família.
- Histograma: distribuição numérica.
Observação: Podemos trocar as palavras “plot”, “chart” e “graph”; todos eles significam a mesma coisa.
Algumas visualizações ruins
Distribuições
Qual é a distribuição de uma variável?
- A distribuição de uma variável consiste em todos os valores da variável que ocorrem nos dados, juntamente com suas frequências.
- As distribuições ajudam você a entender:
- Com que frequência uma variável assume um determinado valor?_
- Ambas as variáveis categóricas e numéricas têm distribuições.
Variáveis categóricas
A distribuição de uma variável categórica pode ser exibida como uma tabela ou gráfico de barras, entre outras formas! Por exemplo, vejamos qual o tipo de ensino médio dos alunos de FCD.
#In:
tipo_medio = pd.DataFrame().assign(
TipoEscola=['Privado', 'Público (Estadual)', 'Público (Municipal)', 'Público (Federal)'],
NumDiscentes=[15, 8, 3, 7]
)
tipo_medio
TipoEscola | NumDiscentes | |
---|---|---|
0 | Privado | 15 |
1 | Público (Estadual) | 8 |
2 | Público (Municipal) | 3 |
3 | Público (Federal) | 7 |
#In:
tipo_medio.plot(kind='barh', x='TipoEscola', y='NumDiscentes');
#In:
tipo_medio.plot(kind='bar', x='TipoEscola', y='NumDiscentes');
Variáveis numéricas
A distribuição de uma variável numérica nem sempre pode ser representada com precisão por um gráfico de barras. Por exemplo, vejamos o número de streams de cada uma das 200 músicas mais populares no Spotify nos EUA. 🎵
#In:
charts = pd.read_csv('https://raw.githubusercontent.com/flaviovdf/fcd/master/assets/07-DataViz/data/regional-us-daily-2023-01-21.csv')
charts = (charts.set_index('rank')
.assign(million_streams = np.round(charts.get('streams')/1000000, 2))
.get(['track_name', 'artist_names', 'streams', 'million_streams'])
)
charts
track_name | artist_names | streams | million_streams | |
---|---|---|---|---|
rank | ||||
1 | Flowers | Miley Cyrus | 3356361 | 2.48 |
2 | Kill Bill | SZA | 2479445 | 1.34 |
3 | Creepin' (with The Weeknd & 21 Savage) | Metro Boomin, The Weeknd, 21 Savage | 1337320 | 1.24 |
4 | Superhero (Heroes & Villains) [with Future & C... | Metro Boomin, Future, Chris Brown | 1235285 | 1.11 |
5 | Rich Flex | Drake, 21 Savage | 1109704 | 1.05 |
... | ... | ... | ... | ... |
196 | Burn, Burn, Burn | Zach Bryan | 267772 | 0.27 |
197 | LET GO | Central Cee | 267401 | 0.27 |
198 | Major Distribution | Drake, 21 Savage | 266986 | 0.27 |
199 | Sun to Me | Zach Bryan | 266968 | 0.27 |
200 | The Real Slim Shady | Eminem | 266698 | NaN |
200 rows × 4 columns
Para ver a distribuição do número de streams, precisamos agrupar pela coluna 'million_streams'
.
#In:
stream_counts = charts.groupby('million_streams').count()
stream_counts = stream_counts.assign(Count=stream_counts.get('track_name')).drop(columns=['track_name', 'artist_names', 'streams'])
stream_counts
Count | |
---|---|
million_streams | |
0.27 | 17 |
0.28 | 20 |
0.29 | 19 |
0.30 | 8 |
0.31 | 14 |
0.32 | 7 |
0.33 | 14 |
0.34 | 7 |
0.35 | 10 |
0.36 | 8 |
0.37 | 1 |
0.38 | 7 |
0.39 | 2 |
0.40 | 5 |
0.41 | 3 |
0.42 | 2 |
0.43 | 4 |
0.44 | 1 |
0.45 | 2 |
0.46 | 1 |
0.47 | 4 |
0.48 | 2 |
0.49 | 1 |
0.50 | 1 |
0.52 | 5 |
0.53 | 1 |
0.54 | 2 |
0.55 | 2 |
0.56 | 1 |
0.57 | 1 |
0.58 | 2 |
0.61 | 1 |
0.64 | 1 |
0.66 | 1 |
0.67 | 1 |
0.69 | 1 |
0.74 | 2 |
0.75 | 1 |
0.76 | 1 |
0.79 | 5 |
0.83 | 1 |
0.86 | 2 |
0.87 | 1 |
0.94 | 1 |
1.00 | 1 |
1.05 | 1 |
1.11 | 1 |
1.24 | 1 |
1.34 | 1 |
2.48 | 1 |
#In:
stream_counts.plot(kind='bar', y='Count', figsize=(15,5));
Isso obscurece o fato de que as duas músicas principais são atípicas, com muito mais streams do que as outras músicas.
O eixo horizontal deve ser numérico (como uma reta numérica), não categórico. Deve haver mais espaço entre certas barras do que outras.
Histogramas
Histogramas mostram a distribuição de variáveis numéricas sem normalizar
Em vez de um gráfico de barras, visualizaremos a distribuição de uma variável numérica com um histograma. Vamos ver como é um histograma de densidade para 'million_streams'
. O que você percebe nessa visualização?
#In:
# Ignore the code for right now.
charts.plot(
kind='hist',
y='million_streams',
bins=np.arange(0, 4, 0.5),
ec='w' # borda branca
);
Primeira ideia chave por trás dos histogramas: binning 🗑️
- Binning é o ato de contar o número de valores numéricos que se enquadram nos intervalos definidos por dois pontos finais. Esses intervalos são chamados de “caixas”.
- Um valor cai em uma caixa se for maior ou igual ao ponto final esquerdo e menor que o ponto final direito.
- [a, b): a está incluído, b não.
- A largura de uma caixa é sua extremidade direita menos sua extremidade esquerda.
#In:
from IPython.display import display, IFrame
def binning_animation():
src="https://docs.google.com/presentation/d/e/2PACX-1vTnRGwEnKP2V-Z82DlxW1b1nMb2F0zWyrXIzFSpQx_8Wd3MFaf56y2_u3JrLwZ5SjWmfapL5BJLfsDG/embed?start=false&loop=false&delayms=60000"
width=900
height=307
display(IFrame(src, width, height))
binning_animation()
Personalizando as caixas
- Por padrão, o Python agrupará seus dados em 10 compartimentos de tamanhos iguais.
- Você pode especificar outro número de compartimentos de tamanhos iguais definindo o argumento opcional
bins
igual a algum outro valor inteiro. - Você também pode especificar o início e os pontos finais do compartimento personalizado definindo
bins
iguais a uma sequência de pontos finais do compartimento. - Pode ser um array
list
ounumpy
.
#In:
charts.plot(
kind='hist',
y='million_streams',
bins=20,
ec='w'
);
#In:
charts.plot(
kind='hist', y='million_streams', density=True,
bins=[0, 1, 2, 3, 4, 5],
ec='w'
);
Nos dois histogramas acima, o que é diferente e o que é igual?
Observações
- A forma geral de todos os dois histogramas é a mesma, independentemente dos compartimentos. Esta forma é chamada de inclinada à direita.
- Mais compartimentos fornecem uma imagem mais precisa e granular da distribuição da variável
'million_streams'
. - Os valores do eixo $y$ parecem mudar muito quando mudamos os compartimentos. Agarre-se a esse pensamento; veremos o porquê em breve.
Histogramas de densidade mostram a distribuição de variáveis numéricas
Podemos também criar histogramas normalizados ou histogramas de densidade.
Ideia chave por trás dos histogramas de densidade: a área total é 1
- Em um histograma de densidade, o eixo $y$ pode ser difícil de interpretar, mas foi projetado para dar ao histograma uma propriedade muito boa: \(\textbf{As barras de um histograma de densidade }\) \(\textbf{têm uma área total combinada de 1.}\)
- Isso significa que a área de uma barra é igual à proporção de todos os pontos de dados que caem nessa caixa.
- Proporções e percentagens representam a mesma coisa.
- Uma proporção é um decimal entre 0 e 1, uma porcentagem está entre 0\% e 100\%.
- A proporção 0,34 significa 34\%.
Cálculo de exemplo
#In:
charts.plot(
kind='hist',
y='million_streams',
density=True,
bins=[0, 0.5, 1, 1.5, 2.5, 4],
ec='w'
);
Com base neste histograma, que proporção das 200 músicas mais populares teve menos de meio milhão de streams?
Exemplo de cálculo
- A altura da barra [0, 0.5) parece ser apenas 1,6.
A largura da caixa é 0,5 - 0 = 0,5.
- Portanto, usando a fórmula da área de um retângulo,
- Como as áreas representam proporções, isso significa que a proporção das 200 músicas mais populares com menos de 0,5 milhão de streams foi de aproximadamente 0,8 (ou 80\%).
Verifique a matemática
#In:
first_bin = charts[charts.get('million_streams') < 0.5].shape[0]
first_bin
159
#In:
first_bin/200
0.795
Isso corresponde ao resultado que obtivemos. (Não exatamente, já que fizemos uma estimativa para a altura.)
Como calcular alturas em um histograma de densidade
Como a barra de um histograma é um retângulo, sua área é dada por
\[\text{Área} = \text{Altura} \times \text{Largura}\]Que significa
\[\text{Altura} = \frac{\text{Área}}{\text{Largura}} = \frac{\text{Proporção (ou Porcentagem)}}{\text{Largura}}\]Isso implica que as unidades de altura são “proporção por (unidade $x$-eixo)”. O eixo $y$ representa uma espécie de densidade, e é por isso que o chamamos de histograma de densidade.
#In:
charts.plot(kind='hist', y='million_streams', density=True,
bins=[0, 0.5, 1, 1.5, 2.5, 4],
ec='w');
plt.ylabel('Densidade')
Text(0, 0.5, 'Densidade')
As unidades do eixo $y$ aqui são “proporção por milhão de fluxos”, já que o eixo $x$ representa milhões de fluxos.
- Infelizmente, as unidades do eixo $y$ no histograma sempre são exibidas como “Frequência”. Isto está errado!
- Podemos corrigir com
plt.ylabel(...)
Verificação de conceito ✅
Suponha que criamos um histograma de densidade dos tamanhos dos sapatos das pessoas. 👟 Abaixo estão as caixas que escolhemos junto com suas alturas.
Caixa | Altura da barra |
---|---|
[3, 7) | 0,05 |
[7, 10) | 0,1 |
[10, 12) | 0,15 |
[12, 16] | $X$ |
Qual deve ser o valor de $X$ para que este seja um histograma válido?
A. 0,02 B. 0,05 C. 0,2 D. 0,5 E. 0.7
Gráficos de barras vs. histogramas
Gráfico de barras | Histograma |
---|---|
Mostra a distribuição de uma variável categórica | Mostra a distribuição de uma variável numérica |
1 eixo categórico, 1 eixo numérico | 2 eixos numéricos |
As barras têm larguras e espaçamentos arbitrários, mas iguais | O eixo horizontal é numérico e está em escala |
Os comprimentos das barras são proporcionais à quantidade numérica de interesse | A altura mede a densidade; as áreas são proporcionais à proporção (porcentagem) de indivíduos |
🌟 Importante 🌟
Nesta aula, “histograma” sempre significará um “histograma de densidade”. Usaremos apenas histogramas de densidade.
Nota: É possível criar o que é chamado de histograma de frequência onde o eixo $y$ simplesmente representa uma contagem do número de valores em cada compartimento. Embora sejam mais fáceis de interpretar, os histogramas de frequência não têm a importante propriedade de que a área total é 1, portanto não podem ser conectados à probabilidade da mesma forma que os histogramas de densidade. Isso os torna muito menos úteis para cientistas de dados.
Gráficos sobrepostos
Novo conjunto de dados: populações de San Diego e San Jose ao longo do tempo
Os dados para ambas as cidades vêm de macrotrends.net.
#In:
population = pd.read_csv('https://raw.githubusercontent.com/flaviovdf/fcd/master/assets/08-MaisViz/data/sd-sj-2022.csv').set_index('date')
population
Pop SD | Growth SD | Pop SJ | Growth SJ | |
---|---|---|---|---|
date | ||||
1970 | 1209000 | 3.69 | 1009000 | 4.34 |
1971 | 1252000 | 3.56 | 1027000 | 1.78 |
1972 | 1297000 | 3.59 | 1046000 | 1.85 |
1973 | 1344000 | 3.62 | 1064000 | 1.72 |
1974 | 1392000 | 3.57 | 1084000 | 1.88 |
1975 | 1442000 | 3.59 | 1103000 | 1.75 |
1976 | 1493000 | 3.54 | 1123000 | 1.81 |
1977 | 1547000 | 3.62 | 1143000 | 1.78 |
1978 | 1602000 | 3.56 | 1163000 | 1.75 |
1979 | 1660000 | 3.62 | 1184000 | 1.81 |
1980 | 1718000 | 3.49 | 1204000 | 1.69 |
1981 | 1774000 | 3.26 | 1221000 | 1.41 |
1982 | 1832000 | 3.27 | 1237000 | 1.31 |
1983 | 1891000 | 3.22 | 1254000 | 1.37 |
1984 | 1953000 | 3.28 | 1271000 | 1.36 |
1985 | 2017000 | 3.28 | 1288000 | 1.34 |
1986 | 2082000 | 3.22 | 1305000 | 1.32 |
1987 | 2150000 | 3.27 | 1323000 | 1.38 |
1988 | 2220000 | 3.26 | 1341000 | 1.36 |
1989 | 2293000 | 3.29 | 1359000 | 1.34 |
1990 | 2356000 | 2.75 | 1376000 | 1.25 |
1991 | 2387000 | 1.32 | 1392000 | 1.16 |
1992 | 2418000 | 1.30 | 1408000 | 1.15 |
1993 | 2450000 | 1.32 | 1424000 | 1.14 |
1994 | 2482000 | 1.31 | 1441000 | 1.19 |
1995 | 2514000 | 1.29 | 1457000 | 1.11 |
1996 | 2547000 | 1.31 | 1474000 | 1.17 |
1997 | 2580000 | 1.30 | 1491000 | 1.15 |
1998 | 2614000 | 1.32 | 1508000 | 1.14 |
1999 | 2648000 | 1.30 | 1525000 | 1.13 |
2000 | 2681000 | 1.25 | 1541000 | 1.05 |
2001 | 2708000 | 1.01 | 1554000 | 0.84 |
2002 | 2735000 | 1.00 | 1566000 | 0.77 |
2003 | 2763000 | 1.02 | 1578000 | 0.77 |
2004 | 2791000 | 1.01 | 1591000 | 0.82 |
2005 | 2819000 | 1.00 | 1603000 | 0.75 |
2006 | 2848000 | 1.03 | 1616000 | 0.81 |
2007 | 2876000 | 0.98 | 1629000 | 0.80 |
2008 | 2905000 | 1.01 | 1642000 | 0.80 |
2009 | 2935000 | 1.03 | 1655000 | 0.79 |
2010 | 2964000 | 0.99 | 1668000 | 0.79 |
2011 | 2994000 | 1.01 | 1681000 | 0.78 |
2012 | 3024000 | 1.00 | 1694000 | 0.77 |
2013 | 3055000 | 1.03 | 1708000 | 0.83 |
2014 | 3086000 | 1.01 | 1721000 | 0.76 |
2015 | 3117000 | 1.00 | 1735000 | 0.81 |
2016 | 3148000 | 0.99 | 1749000 | 0.81 |
2017 | 3180000 | 1.02 | 1762000 | 0.74 |
2018 | 3212000 | 1.01 | 1776000 | 0.79 |
2019 | 3231000 | 0.59 | 1783000 | 0.39 |
2020 | 3251000 | 0.62 | 1791000 | 0.45 |
2021 | 3272000 | 0.65 | 1799000 | 0.45 |
2022 | 3295000 | 0.70 | 1809000 | 0.56 |
2023 | 3319000 | 0.73 | 1821000 | 0.66 |
Lembre-se: gráficos de linha
#In:
population.plot(kind='line', y='Growth SD',
title='San Diego population growth rate', legend=False);
#In:
population.plot(kind='line', y='Growth SJ',
title='San Jose population growth rate', legend=False);
Observe os argumentos opcionais title
e legend
. Alguns outros argumentos opcionais úteis são figsize
, xlabel
e ylabel
. Existem many optional arguments.
Gráficos sobrepostos
Se y=column_name
for omitido, todas colunas serão plotadas!
#In:
population.plot(kind='line');
Por que existem apenas três linhas mostradas, mas quatro na legenda? 🤔
Selecionando várias colunas de uma vez
- Para selecionar várias colunas, use
.get([column_1, ..., column_k])
. - Passar uma
lista
de rótulos de colunas para.get
retorna um DataFrame. .get([column_name])
retornará um DataFrame com apenas uma coluna!
#In:
growths = population.get(['Growth SD', 'Growth SJ'])
growths
Growth SD | Growth SJ | |
---|---|---|
date | ||
1970 | 3.69 | 4.34 |
1971 | 3.56 | 1.78 |
1972 | 3.59 | 1.85 |
1973 | 3.62 | 1.72 |
1974 | 3.57 | 1.88 |
1975 | 3.59 | 1.75 |
1976 | 3.54 | 1.81 |
1977 | 3.62 | 1.78 |
1978 | 3.56 | 1.75 |
1979 | 3.62 | 1.81 |
1980 | 3.49 | 1.69 |
1981 | 3.26 | 1.41 |
1982 | 3.27 | 1.31 |
1983 | 3.22 | 1.37 |
1984 | 3.28 | 1.36 |
1985 | 3.28 | 1.34 |
1986 | 3.22 | 1.32 |
1987 | 3.27 | 1.38 |
1988 | 3.26 | 1.36 |
1989 | 3.29 | 1.34 |
1990 | 2.75 | 1.25 |
1991 | 1.32 | 1.16 |
1992 | 1.30 | 1.15 |
1993 | 1.32 | 1.14 |
1994 | 1.31 | 1.19 |
1995 | 1.29 | 1.11 |
1996 | 1.31 | 1.17 |
1997 | 1.30 | 1.15 |
1998 | 1.32 | 1.14 |
1999 | 1.30 | 1.13 |
2000 | 1.25 | 1.05 |
2001 | 1.01 | 0.84 |
2002 | 1.00 | 0.77 |
2003 | 1.02 | 0.77 |
2004 | 1.01 | 0.82 |
2005 | 1.00 | 0.75 |
2006 | 1.03 | 0.81 |
2007 | 0.98 | 0.80 |
2008 | 1.01 | 0.80 |
2009 | 1.03 | 0.79 |
2010 | 0.99 | 0.79 |
2011 | 1.01 | 0.78 |
2012 | 1.00 | 0.77 |
2013 | 1.03 | 0.83 |
2014 | 1.01 | 0.76 |
2015 | 1.00 | 0.81 |
2016 | 0.99 | 0.81 |
2017 | 1.02 | 0.74 |
2018 | 1.01 | 0.79 |
2019 | 0.59 | 0.39 |
2020 | 0.62 | 0.45 |
2021 | 0.65 | 0.45 |
2022 | 0.70 | 0.56 |
2023 | 0.73 | 0.66 |
#In:
growths.plot(kind='line');
Para traçar vários gráficos de uma vez:
.get
apenas as colunas que contêm informações relevantes para o seu gráfico.- Equivalentemente,
.drop
todas as colunas estranhas. - Especifique a coluna para o eixo $x$ (se não for o índice) em
.plot(x=column_name)
. - Omita o argumento
y
. Então todas as outras colunas serão plotadas em um eixo $y$ compartilhado.
A mesma coisa funciona para 'barh'
, 'bar'
e 'hist'
, mas não para 'scatter'
.
Novo conjunto de dados: alturas das crianças e de seus pais 👪 📏
- Os dados abaixo foram coletados no final de 1800 por Francis Galton.
- Ele era eugenista e defensor do racismo científico, por isso coletou esses dados.
- Hoje entendemos que a eugenia é imoral e que não há evidências científicas ou qualquer outra justificativa para o racismo.
- Revisitaremos esse conjunto de dados posteriormente no curso.
- Por enquanto, precisaremos apenas das colunas
'mother'
e'childHeight'
.
#In:
mother_child = pd.read_csv('https://raw.githubusercontent.com/flaviovdf/fcd/master/assets/08-MaisViz/data/galton.csv').get(['mother', 'childHeight'])
mother_child
mother | childHeight | |
---|---|---|
0 | 67.0 | 73.2 |
1 | 67.0 | 69.2 |
2 | 67.0 | 69.0 |
3 | 67.0 | 69.0 |
4 | 66.5 | 73.5 |
... | ... | ... |
929 | 66.0 | 64.0 |
930 | 66.0 | 62.0 |
931 | 66.0 | 61.0 |
932 | 63.0 | 66.5 |
933 | 63.0 | 57.0 |
934 rows × 2 columns
Plotando histogramas sobrepostos
alpha
controla o quão transparentes as barras são (alpha=1
é opaco, alpha=0
é transparente).
#In:
height_bins = np.arange(55, 80, 2.5)
mother_child.plot(kind='hist', density=True, ec='w',
alpha=0.65, bins=height_bins);
Por que as crianças parecem muito mais altas que as mães?
Prática Extra
Tente responder a estas perguntas com base no histograma sobreposto.
Qual proporção de crianças tinha entre 70 e 75 polegadas de altura?
Qual proporção de mães tinha entre 60 e 63 polegadas de altura?
Respostas
Clique aqui para mostrar.
Pergunta 1 A altura da barra $[70, 72.5)$ é em torno de $0,08$, o que significa que $0,08 \cdot 2,5 = 0,2$ de crianças tinham altura nesse intervalo. A altura da barra $[70, 72.5)$ é em torno de $0,02$, o que significa $0,02 \cdot 2,5 = 0,05$ de crianças tinham altura nesse intervalo. Assim, a proporção geral de crianças que tinham entre $70$ e $75$ polegadas de altura era de cerca de $0,20 + 0,05 = 0,25$, ou $25\%$. Para verificar nossa resposta, podemos executarheights[(heights.get('childHeight') >= 70) & (heights.get('childHeight') < 75)].shape[0] / heights.shape[0]
Pergunta 2 Não podemos dizer. Poderíamos tentar dividir na proporção de mães em $[60, 62.5)$ e $[60, 62.5)$, mas não sabemos o último. Na ausência de qualquer informação adicional, não podemos inferir sobre a distribuição de valores dentro de um compartimento. Por exemplo, pode ser que todos no intervalo $[60, 62.5)$ realmente caiam no intervalo $[60, 62.5)$ - ou pode ser que ninguém caia! Resumo, da próxima vez
Resumo
- Histogramas (não gráficos de barras!) são usados para exibir a distribuição de uma variável numérica.
- Sempre usaremos histogramas de densidade.
- Em histogramas de densidade, a área de uma barra representa a proporção (porcentagem) de valores dentro de seu compartimento.
- A área total de todas as barras é 1 (100%).
- Podemos sobrepor vários gráficos de linhas, gráficos de barras e histogramas uns sobre os outros para observar vários relacionamentos ou distribuições.
Próxima vez
- Escrevendo nossas próprias funções.
- Aplicar funções aos dados num DataFrame.