Fase 1 — Fundamentos | Pré-requisitos: Aulas 0 a 5 | Duração estimada: 80 minutos


Introdução

Você aprendeu a carregar dados, inspecioná-los, manipulá-los e operá-los numericamente. Agora chegou o momento de extrair significado e comunicar o que você encontrou. Essa é a etapa que transforma números em informação útil.

Esta aula cobre dois territórios interligados. O primeiro é estatística descritiva: as medidas numéricas que resumem distribuições, centralidade, dispersão e relações entre variáveis. O segundo é visualização básica com pandas: gráficos gerados diretamente de DataFrames com uma linha de código, adequados para exploração rápida durante a análise.

A Fase 3 do curso aprofundará visualização com matplotlib e seaborn para fins explicativos — gráficos polidos para apresentar a outros. Aqui o foco é no uso exploratório: gráficos rápidos que te ajudam a pensar, não necessariamente que você vai mostrar para alguém.


Estatística descritiva: conceitos fundamentais

Estatística descritiva é o conjunto de medidas que resumem as características de um conjunto de dados sem fazer inferências sobre populações maiores. Ela responde perguntas como: qual é o valor típico? Quão espalhados são os dados? A distribuição é simétrica? Há valores extremos?

Medidas de tendência central

Tendência central descreve o "valor típico" de uma distribuição. Há três medidas principais.

A média aritmética é a soma de todos os valores dividida pelo número de observações. É a medida mais usada e mais sensível a outliers. Um único valor extremo pode puxar a média para longe do centro real dos dados.

A mediana é o valor que divide a distribuição ao meio quando os dados estão ordenados. Cinquenta por cento dos valores ficam abaixo dela e cinquenta por cento acima. É robusta a outliers — um valor extremo não a afeta enquanto não mover mais de metade dos dados.

A moda é o valor mais frequente. Útil para dados categóricos e para distribuições com picos claros. Uma distribuição pode ter múltiplas modas.

import pandas as pd
import numpy as np

df = pd.read_csv('dados.csv')

# Média
df['salario'].mean()

# Mediana
df['salario'].median()

# Moda (retorna uma Series porque pode haver múltiplas modas)
df['salario'].mode()

# Média truncada (ignora os extremos — compromisso entre média e mediana)
from scipy import stats
stats.trim_mean(df['salario'].dropna(), proportiontocut=0.05)
# ignora os 5% menores e 5% maiores antes de calcular a média

Quando média e mediana divergem significativamente, isso é um sinal de assimetria na distribuição. Se a média é maior que a mediana, a distribuição tem uma cauda à direita — alguns valores muito altos puxam a média para cima. Salários, preços de imóveis e receitas de empresas são exemplos clássicos de distribuições com essa característica.

Medidas de dispersão

Dispersão descreve o quanto os valores se espalharam em torno do centro.

A amplitude é simplesmente o máximo menos o mínimo. É fácil de calcular mas muito sensível a outliers — um único valor extremo inflaciona a amplitude.

A variância é a média dos quadrados dos desvios em relação à média. Elevar ao quadrado tem dois efeitos: torna todos os desvios positivos e amplifica desvios grandes em relação a pequenos. A unidade da variância é o quadrado da unidade original — salários em reais teriam variância em reais².

O desvio padrão é a raiz quadrada da variância. Ele está na mesma unidade dos dados originais, o que o torna interpretável: em uma distribuição aproximadamente normal, cerca de 68% dos valores ficam dentro de um desvio padrão da média, e 95% dentro de dois desvios padrão.

O intervalo interquartil (IQR) é a diferença entre o percentil 75 e o percentil 25. Ele descreve a dispersão dos 50% centrais dos dados, ignorando os extremos. É robusto a outliers e muito útil para detectá-los.

# Desvio padrão
df['salario'].std()

# Variância
df['salario'].var()

# Amplitude
df['salario'].max() - df['salario'].min()

# Intervalo interquartil
q75 = df['salario'].quantile(0.75)
q25 = df['salario'].quantile(0.25)
iqr = q75 - q25

# Percentis arbitrários
df['salario'].quantile([0.10, 0.25, 0.50, 0.75, 0.90])

# describe() resume tudo de uma vez
df['salario'].describe()

Detecção de outliers com IQR

O método mais comum de detecção de outliers usa o IQR para definir limites:

q25 = df['salario'].quantile(0.25)
q75 = df['salario'].quantile(0.75)
iqr = q75 - q25

limite_inferior = q25 - 1.5 * iqr
limite_superior = q75 + 1.5 * iqr

outliers = df[(df['salario'] < limite_inferior) | (df['salario'] > limite_superior)]
print(f"Outliers detectados: {len(outliers)}")
print(outliers[['nome', 'salario']])

O fator 1.5 é convencional e foi proposto por John Tukey. Para outliers extremos, usa-se 3.0. Esses valores não são leis da natureza — são heurísticas. Sempre interprete os outliers identificados antes de decidir o que fazer com eles.

Medidas de forma

Assimetria (skewness) mede a simetria da distribuição. Zero indica distribuição simétrica. Valores positivos indicam cauda à direita (a maioria dos valores é baixa mas há alguns muito altos). Valores negativos indicam cauda à esquerda.

Curtose (kurtosis) mede a "altura" dos picos e a "peso" das caudas em relação a uma distribuição normal. Curtose alta indica distribuição com pico pronunciado e caudas pesadas. Curtose baixa indica distribuição achatada.

# Assimetria
df['salario'].skew()

# Curtose
df['salario'].kurt()

Correlação

Correlação mede a força e a direção da relação linear entre duas variáveis numéricas. O coeficiente de correlação de Pearson varia de -1 a 1.

Valor próximo de 1: forte relação positiva — quando uma variável aumenta, a outra tende a aumentar. Valor próximo de -1: forte relação negativa — quando uma aumenta, a outra tende a diminuir. Valor próximo de 0: pouca ou nenhuma relação linear.

# Correlação entre duas colunas
df['idade'].corr(df['salario'])

# Matriz de correlação entre todas as colunas numéricas
df.corr(numeric_only=True)

# Correlação de Spearman (baseada em rankings, mais robusta a outliers)
df['idade'].corr(df['salario'], method='spearman')

Correlação não implica causalidade. Isso não é apenas um aviso retórico — é uma armadilha que analistas experientes também caem. Duas variáveis podem ser altamente correlacionadas porque uma causa a outra, porque ambas são causadas por uma terceira variável, ou por pura coincidência estatística em amostras pequenas. Nunca interprete uma correlação como causalidade sem uma teoria sólida e, idealmente, um desenho experimental ou quase-experimental.


Visualizações básicas com pandas

pandas tem um sistema de visualização embutido que é um wrapper sobre matplotlib. Ele não foi projetado para gráficos publicáveis — foi projetado para exploração rápida durante a análise. O método central é .plot(), com o parâmetro kind definindo o tipo de gráfico.

Antes de usar, garanta que o matplotlib está instalado e que, se estiver no Jupyter, os gráficos aparecem inline:

import pandas as pd
import matplotlib.pyplot as plt

# No Jupyter Notebook (versões antigas podem precisar disso)
# %matplotlib inline

Histograma

O histograma é o gráfico mais importante para entender a distribuição de uma variável numérica. Ele divide o intervalo de valores em bins (faixas) e conta quantas observações caem em cada faixa.

# Histograma simples
df['salario'].plot(kind='hist', bins=20)
plt.title('Distribuição de salários')
plt.xlabel('Salário (R$)')
plt.ylabel('Frequência')
plt.show()

# Histograma de múltiplas colunas sobrepostas
df[['salario', 'bonus']].plot(kind='hist', bins=20, alpha=0.5)
plt.show()

# Forma alternativa diretamente com hist()
df['salario'].hist(bins=30, edgecolor='white')
plt.show()

A escolha do número de bins afeta muito o que você vê. Poucos bins escondem estrutura na distribuição. Muitos bins criam ruído que parece estrutura. Uma regra prática é começar com a raiz quadrada do número de observações e ajustar visualmente.

Gráfico de caixa (boxplot)

O boxplot é a representação visual do resumo de cinco números: mínimo, Q1, mediana, Q3 e máximo. Os "bigodes" se estendem até 1.5×IQR além dos quartis, e pontos além dos bigodes são marcados como outliers.

# Boxplot de uma coluna
df['salario'].plot(kind='box')
plt.show()

# Boxplot comparando grupos
df.boxplot(column='salario', by='departamento')
plt.suptitle('')  # remove o título automático gerado pelo pandas
plt.title('Salários por departamento')
plt.show()

O boxplot é excelente para comparar distribuições entre grupos. Ele mostra de uma vez a mediana, a dispersão dos 50% centrais e os outliers.

Gráfico de barras

Adequado para comparar valores entre categorias.

# Contagem por categoria
df['regiao'].value_counts().plot(kind='bar')
plt.title('Vendas por região')
plt.xlabel('Região')
plt.ylabel('Número de vendas')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

# Barras horizontais (mais fácil de ler rótulos longos)
df['regiao'].value_counts().plot(kind='barh')
plt.show()

# Barras agrupadas a partir de um DataFrame com múltiplas colunas
df.groupby('regiao')[['valor_2023', 'valor_2024']].sum().plot(kind='bar')
plt.show()

# Barras empilhadas
df.groupby('regiao')[['valor_2023', 'valor_2024']].sum().plot(kind='bar', stacked=True)
plt.show()

Gráfico de linha

Adequado para dados com ordem natural — especialmente séries temporais.

# Série temporal simples
df.set_index('data')['valor'].plot(kind='line')
plt.title('Evolução das vendas')
plt.xlabel('Data')
plt.ylabel('Valor (R$)')
plt.show()

# Múltiplas linhas
df.set_index('data')[['vendas_sp', 'vendas_rj', 'vendas_mg']].plot(kind='line')
plt.show()

# Com reamostragem mensal (se o índice for datetime)
df.set_index('data').resample('ME')['valor'].sum().plot(kind='line', marker='o')
plt.show()

Gráfico de dispersão (scatter)

Adequado para visualizar a relação entre duas variáveis numéricas.

# Scatter básico
df.plot(kind='scatter', x='idade', y='salario', alpha=0.5)
plt.title('Relação entre idade e salário')
plt.show()

# Scatter com cor representando uma terceira variável
# (requer matplotlib diretamente para usar colormap)
scatter = plt.scatter(df['idade'], df['salario'],
                      c=df['anos_experiencia'],
                      cmap='viridis',
                      alpha=0.6)
plt.colorbar(scatter, label='Anos de experiência')
plt.xlabel('Idade')
plt.ylabel('Salário')
plt.show()

Gráfico de pizza

Adequado apenas para mostrar proporções de um todo com poucas categorias. Para mais de cinco ou seis categorias, um gráfico de barras é sempre mais legível.

df['regiao'].value_counts().plot(kind='pie', autopct='%1.1f%%')
plt.title('Distribuição de vendas por região')
plt.ylabel('')  # remove o rótulo 'regiao' do eixo y
plt.show()

Matriz de dispersão

Para explorar relações entre múltiplas variáveis numéricas de uma vez:

pd.plotting.scatter_matrix(
    df[['idade', 'salario', 'anos_experiencia', 'bonus']],
    figsize=(10, 10),
    diagonal='hist',
    alpha=0.5
)
plt.show()

A diagonal mostra o histograma de cada variável. Os outros painéis mostram o scatter entre cada par. É uma inspeção rápida mas poderosa para detectar correlações e distribuições de uma vez.


Personalizando gráficos básicos do pandas

Mesmo nos gráficos exploratórios, algumas personalizações básicas melhoram a legibilidade:

df['salario'].plot(
    kind='hist',
    bins=25,
    color='steelblue',
    edgecolor='white',
    figsize=(10, 5),     # largura x altura em polegadas
    title='Distribuição de salários',
    grid=True,
    alpha=0.8
)
plt.xlabel('Salário (R$)')
plt.ylabel('Frequência')
plt.tight_layout()       # ajusta margens para evitar cortes
plt.show()

Parâmetros úteis do .plot():

figsize — tupla (largura, altura) em polegadas. Padrão é pequeno; quase sempre vale aumentar. title — título do gráfico. color ou c — cor ou lista de cores. alpha — transparência (0 = invisível, 1 = opaco). Útil para sobreposições. grid — mostra grade de fundo. legend — mostra ou esconde a legenda. fontsize — tamanho da fonte dos rótulos dos eixos. rot — rotação dos rótulos do eixo x.


Salvando gráficos

df['salario'].plot(kind='hist', bins=25)
plt.title('Distribuição de salários')
plt.tight_layout()

# Salvar em diferentes formatos
plt.savefig('histograma_salarios.png', dpi=150)   # PNG para apresentações
plt.savefig('histograma_salarios.pdf')            # PDF para documentos
plt.savefig('histograma_salarios.svg')            # SVG para edição vetorial

plt.show()

dpi (dots per inch) controla a resolução. 72 dpi é adequado para tela. 150 a 300 dpi para impressão ou apresentações de alta qualidade.


Um fluxo exploratório completo

Juntando estatísticas descritivas e visualizações, este é um fluxo típico de exploração de uma coluna numérica:

import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('funcionarios.csv')
col = 'salario'

# 1. Estatísticas descritivas
print(f"=== Estatísticas de '{col}' ===")
print(df[col].describe())
print(f"\nAssimetria: {df[col].skew():.3f}")
print(f"Curtose: {df[col].kurt():.3f}")

# 2. Valores ausentes
n_ausentes = df[col].isnull().sum()
print(f"Valores ausentes: {n_ausentes} ({n_ausentes/len(df)*100:.1f}%)")

# 3. Outliers pelo método IQR
q25, q75 = df[col].quantile([0.25, 0.75])
iqr = q75 - q25
lim_inf = q25 - 1.5 * iqr
lim_sup = q75 + 1.5 * iqr
n_outliers = ((df[col] < lim_inf) | (df[col] > lim_sup)).sum()
print(f"Outliers (método IQR): {n_outliers}")

# 4. Visualização
fig, axes = plt.subplots(1, 2, figsize=(12, 4))

df[col].plot(kind='hist', bins=30, ax=axes[0],
             color='steelblue', edgecolor='white', alpha=0.8)
axes[0].set_title(f'Distribuição de {col}')
axes[0].axvline(df[col].mean(), color='red', linestyle='--', label='Média')
axes[0].axvline(df[col].median(), color='green', linestyle='--', label='Mediana')
axes[0].legend()

df[col].plot(kind='box', ax=axes[1])
axes[1].set_title(f'Boxplot de {col}')

plt.tight_layout()
plt.show()

Neste fluxo, o histograma com as linhas verticais de média e mediana imediatamente revela se a distribuição é simétrica ou assimétrica. O boxplot ao lado mostra os quartis e os outliers. Juntos, os dois gráficos dão uma leitura completa da distribuição em segundos.


Resumo

Estatística descritiva usa medidas de tendência central (média, mediana, moda), dispersão (desvio padrão, IQR, variância) e forma (assimetria, curtose) para resumir distribuições. Quando média e mediana divergem, há assimetria. O método IQR com fator 1.5 detecta outliers candidatos. Correlação de Pearson mede relação linear entre duas variáveis e varia de -1 a 1 — mas correlação não é causalidade. pandas tem visualização embutida via .plot(kind=...) adequada para exploração rápida: histogramas para distribuições, boxplots para comparações, barras para categorias, linhas para séries temporais e scatter para relações entre variáveis. Gráficos exploratórios são para você pensar; gráficos explicativos são para comunicar a outros — essa distinção guia a Fase 3.

Exercícios

  1. Em quais situações a mediana é preferível à média como medida de tendência central? Dê dois exemplos de variáveis do mundo real onde isso se aplica.

    ✓ Resposta:

    A mediana é preferível quando a distribuição é assimétrica ou contém outliers significativos, porque ela não é afetada por valores extremos enquanto a média é puxada na direção dos outliers.

    Exemplo 1: renda domiciliar. A renda no Brasil tem distribuição fortemente assimétrica à direita — a maioria das famílias tem renda baixa ou média, mas um pequeno número de famílias muito ricas eleva a média significativamente acima do valor típico. Se a renda mediana domiciliar é R$ 2.800 e a média é R$ 5.400, a mediana representa melhor a experiência da família típica.

    Exemplo 2: tempo de resposta de um sistema web. A maioria das requisições é respondida em poucos milissegundos, mas ocasionalmente uma requisição leva segundos por um problema de timeout ou cache miss. A média do tempo de resposta seria inflada por esses casos raros. O percentil 95 ou 99 do tempo de resposta — medidas relacionadas à mediana — é o que engenheiros de performance monitoram.

  2. O coeficiente de correlação de Pearson entre horas estudadas e nota final é 0.72. Um gestor conclui que "aumentar as horas de estudo causa melhora nas notas". Identifique o erro nessa conclusão e explique como ele poderia ser corrigido ou ao menos mitigado.

    ✓ Resposta:

    O erro é a confusão entre correlação e causalidade. Uma correlação de 0.72 mostra que as duas variáveis tendem a aumentar juntas, mas não diz nada sobre qual causa qual, nem se há uma causa comum.

    Nesse caso específico, existem pelo menos três explicações alternativas. Primeira: alunos com maior capacidade de concentração e disciplina estudam mais e também tiram notas melhores — a variável de confusão é a disciplina, não as horas de estudo. Segunda: alunos com mais motivação pela matéria estudam mais e se engajam mais nas aulas, o que melhora as notas — a motivação é a causa de ambos. Terceira: a relação pode ser inversa em parte — alunos que percebem que estão mal saem estudando mais na véspera, o que significa que notas piores também causam mais horas de estudo.

    Para estabelecer causalidade, seria necessário um experimento controlado: dividir alunos aleatoriamente em grupos com diferentes cargas de estudo mandatórias e medir as notas. A aleatorização elimina variáveis de confusão. Sem aleatorização, o melhor que se pode fazer é controlar estatisticamente por variáveis confundidoras conhecidas — mas isso nunca elimina a possibilidade de confundidores não medidos.

  3. Por que o número de bins de um histograma importa? O que acontece com a interpretação quando você usa bins demais ou de menos?

    ✓ Resposta:

    O número de bins define a resolução com que o histograma representa a distribuição. É um parâmetro de suavização: mais bins significa mais detalhe, menos bins significa mais simplificação.

    Com bins de menos — por exemplo, 3 ou 4 bins para mil observações — você perde estrutura real da distribuição. Uma distribuição bimodal (com dois picos) pode parecer unimodal se os bins forem largos o suficiente para cobrir os dois picos. Você acaba com uma representação simplificada demais que esconde padrões importantes.

    Com bins demais — por exemplo, 500 bins para cem observações — cada bin tem um ou zero elementos, e o histograma se torna uma série de barras esparsas que reflete ruído estatístico, não estrutura da distribuição. Você pode ver padrões que são apenas variação aleatória da amostra.

    A escolha ideal depende do tamanho da amostra e do nível de detalhe que faz sentido para a análise. Regras automáticas como a de Sturges (bins ≈ log₂(n) + 1) ou a de Scott (baseada no desvio padrão e tamanho da amostra) oferecem um ponto de partida, mas sempre vale ajustar visualmente. A prática recomendada é experimentar com pelo menos três valores de bins antes de escolher.

  4. Dado o código abaixo, descreva o que cada linha faz e qual o resultado esperado no gráfico final:

    fig, axes = plt.subplots(1, 2, figsize=(12, 4))
    
    df['idade'].plot(kind='hist', bins=20, ax=axes[0], color='steelblue', edgecolor='white')
    axes[0].axvline(df['idade'].mean(), color='red', linestyle='--', label='Média')
    axes[0].axvline(df['idade'].median(), color='green', linestyle='--', label='Mediana')
    axes[0].legend()
    axes[0].set_title('Distribuição de idades')
    
    df.boxplot(column='idade', by='departamento', ax=axes[1])
    axes[1].set_title('Idade por departamento')
    plt.suptitle('')
    
    plt.tight_layout()
    plt.show()
    

    ✓ Resposta:

    fig, axes = plt.subplots(1, 2, figsize=(12, 4)) — cria uma figura com dois painéis lado a lado (1 linha, 2 colunas), com 12 polegadas de largura e 4 de altura. axes é um array com os dois objetos de eixo.

    df['idade'].plot(kind='hist', ...) — plota o histograma da coluna idade com 20 bins no primeiro painel (axes[0]), com barras azul-aço e bordas brancas.

    As duas linhas axvline adicionam linhas verticais tracejadas no primeiro painel: uma vermelha na média e uma verde na mediana. Isso permite visualizar imediatamente se a distribuição é simétrica (as linhas se sobrepõem) ou assimétrica (as linhas estão separadas, com a média sendo puxada na direção da cauda mais longa).

    axes[0].legend() — adiciona a legenda que identifica as linhas de média e mediana.

    df.boxplot(column='idade', by='departamento', ax=axes[1]) — no segundo painel, plota um boxplot da coluna idade separado por grupo da coluna departamento. Cada departamento terá sua própria caixa, permitindo comparar as distribuições de idade entre eles.

    plt.suptitle('') — remove o título automático gerado pelo pandas quando se usa boxplot com o parâmetro by, que costuma ser redundante.

    plt.tight_layout() — ajusta automaticamente as margens e espaçamentos para evitar que rótulos e títulos sejam cortados.

    O resultado é uma figura com dois painéis: à esquerda, a distribuição geral de idades com as referências de média e mediana; à direita, boxplots comparando a distribuição de idades em cada departamento.

  5. Escreva o código completo para um fluxo exploratório de uma coluna categórica — por exemplo, a coluna departamento de um DataFrame de funcionários. O fluxo deve incluir: contagem de valores únicos, frequência absoluta e relativa de cada categoria, detecção de possíveis inconsistências de nomenclatura, e um gráfico de barras horizontais ordenado da categoria mais frequente para a menos frequente.

    ✓ Resposta:
    import pandas as pd
    import matplotlib.pyplot as plt
    
    # Supondo que df já foi carregado
    col = 'departamento'
    
    # 1. Número de categorias únicas
    n_unicos = df[col].nunique()
    print(f"Categorias únicas: {n_unicos}")
    
    # 2. Valores ausentes
    n_ausentes = df[col].isnull().sum()
    print(f"Valores ausentes: {n_ausentes} ({n_ausentes/len(df)*100:.1f}%)")
    
    # 3. Frequência absoluta e relativa
    freq = df[col].value_counts()
    freq_rel = df[col].value_counts(normalize=True).round(3) * 100
    
    tabela = pd.DataFrame({
        'contagem': freq,
        'percentual': freq_rel
    })
    print("\nFrequências:")
    print(tabela.to_string())
    
    # 4. Detecção de possíveis inconsistências de nomenclatura
    # Verificar variações de capitalização e espaços em branco
    print("\nVerificação de possíveis inconsistências:")
    valores_normalizados = df[col].str.lower().str.strip()
    n_unicos_original = df[col].nunique()
    n_unicos_normalizado = valores_normalizados.nunique()
    
    if n_unicos_original > n_unicos_normalizado:
        diferenca = n_unicos_original - n_unicos_normalizado
        print(f"Atenção: há {diferenca} categoria(s) que podem ser duplicatas")
        print("(variações de capitalização ou espaços em branco detectadas)")
        print("\nValores únicos no original:")
        print(sorted(df[col].dropna().unique()))
    else:
        print("Nenhuma inconsistência de capitalização ou espaços detectada.")
    
    # 5. Gráfico de barras horizontais ordenado
    freq_ordenada = df[col].value_counts().sort_values(ascending=True)
    
    fig, ax = plt.subplots(figsize=(10, max(4, len(freq_ordenada) * 0.4)))
    freq_ordenada.plot(kind='barh', ax=ax, color='steelblue', edgecolor='white')
    
    ax.set_title(f'Frequência por {col}')
    ax.set_xlabel('Contagem')
    ax.set_ylabel('')
    
    # Adicionar rótulos com os valores nas barras
    for i, v in enumerate(freq_ordenada):
        ax.text(v + 0.1, i, str(v), va='center', fontsize=10)
    
    plt.tight_layout()
    plt.show()
    

    O figsize com altura dinâmica max(4, len(freq_ordenada) * 0.4) evita que barras fiquem comprimidas quando há muitas categorias. O loop que adiciona os rótulos numéricos ao final de cada barra torna o gráfico mais informativo sem exigir que o leitor estime os valores visualmente.

Referências

  • Tukey, John W. Exploratory Data Analysis. Addison-Wesley, 1977. O livro fundacional da análise exploratória, que introduziu o boxplot e a filosofia de deixar os dados falarem antes de testar hipóteses.
  • Field, Andy. Discovering Statistics Using IBM SPSS Statistics, 5ª edição. SAGE, 2017. Excelente introdução a estatística descritiva e inferencial com foco aplicado, acessível para não-matemáticos.
  • Documentação do pandas — Chart Visualization: pandas.pydata.org/docs/user_guide/visualization.html Referência completa do sistema de visualização embutido do pandas com exemplos de todos os tipos de gráfico.
  • Documentação do matplotlib — pyplot: matplotlib.org/stable/api/pyplot_summary.html Referência das funções do pyplot usadas para personalizar gráficos.
  • Wheelan, Charles. Naked Statistics: Stripping the Dread from the Data. W. W. Norton, 2013. Introdução intuitiva e bem-humorada à estatística descritiva e inferencial para não especialistas.
  • Vigen, Tyler. Spurious Correlations. spuriouscorrelations.com. Coleção de correlações estatisticamente significativas mas obviamente sem sentido causal — uma forma divertida de entender por que correlação não implica causalidade.