Fase 3 — Visualização e comunicação | Pré-requisitos: Aulas 0 a 17 | Duração estimada: 85 minutos


Introdução

Todas as visualizações das aulas anteriores foram exploratórias: criadas rapidamente para ajudar você a pensar, descobrir padrões e formular perguntas. Elas cumpriram seu papel. Mas apresentar gráficos exploratórios para um stakeholder é como mostrar rascunhos em vez de um relatório final — a informação está lá, mas o trabalho de extrair significado ainda é do leitor.

Visualizações explicativas fazem o trabalho inverso: o analista extrai o significado e o gráfico o comunica diretamente. O leitor não precisa interpretar — precisa apenas confirmar o que o gráfico já declara.

Esta aula é sobre a transformação. Ela mostra sistematicamente como pegar um gráfico exploratório correto e refiná-lo até se tornar um gráfico explicativo eficaz. O processo envolve cinco dimensões: mensagem, hierarquia visual, anotações, limpeza de ruído e estilo consistente.


Configuração

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.ticker as ticker
import seaborn as sns
from scipy import stats

np.random.seed(42)
n = 800

df = pd.DataFrame({
    'mes': pd.date_range('2023-01-01', periods=24, freq='ME').repeat(n // 24 + 1)[:n],
    'regiao': np.random.choice(['Sul', 'Sudeste', 'Norte', 'Nordeste', 'Centro-Oeste'],
                                n, p=[0.20, 0.40, 0.10, 0.20, 0.10]),
    'categoria': np.random.choice(['Eletrônicos', 'Vestuário', 'Alimentos', 'Casa'],
                                   n, p=[0.25, 0.30, 0.25, 0.20]),
    'valor': np.random.lognormal(5.5, 0.8, n),
    'satisfacao': np.random.choice(range(1, 6), n, p=[0.05, 0.10, 0.25, 0.40, 0.20]),
    'devolvido': np.random.choice([0, 1], n, p=[0.88, 0.12])
})

# Tendência temporal e sazonalidade
df['valor'] = df['valor'] * (1 + df.index / n * 0.4)
df.loc[df['mes'].dt.month == 11, 'valor'] *= 1.8  # Black Friday
df.loc[df['mes'].dt.month == 12, 'valor'] *= 1.4

O processo de transformação

A transformação de exploratório para explicativo segue cinco passos em sequência. Cada passo remove um obstáculo entre o dado e a compreensão do leitor.

Passo 1 — Definir a mensagem: o que o gráfico deve comunicar em uma frase? Passo 2 — Criar hierarquia visual: o que deve ser visto primeiro, segundo, terceiro? Passo 3 — Anotar diretamente: substituir interpretação implícita por texto explícito no gráfico. Passo 4 — Remover ruído: eliminar tudo que não serve à mensagem. Passo 5 — Aplicar estilo consistente: garantir que o gráfico pertence ao mesmo conjunto visual dos outros gráficos do relatório.

Vamos aplicar esses cinco passos a um exemplo concreto.


Exemplo completo: de exploratório a explicativo

O problema: você analisou vendas mensais de 2023 a 2024 e descobriu que novembro de 2023 teve um pico expressivo por causa da Black Friday, e que a tendência geral é de crescimento. Você quer comunicar isso a um gestor.

Versão 1 — exploratória (ponto de partida)

# Preparar dados
vendas_mensais = (df.groupby('mes')['valor']
                  .sum()
                  .reset_index()
                  .rename(columns={'valor': 'total'}))

# Gráfico exploratório — rápido, sem polimento
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(vendas_mensais['mes'], vendas_mensais['total'])
ax.set_title('vendas mensais')
ax.set_ylabel('total')
plt.show()

Este gráfico responde a pergunta para quem já sabe o que está procurando. Para um gestor sem contexto, é opaco: o eixo Y não tem unidade, o título não diz o que importa, o pico não está explicado, a tendência não está destacada.

Versão 2 — com mensagem definida

O primeiro passo é escrever a mensagem numa frase antes de tocar no código:

"Vendas cresceram 40% em 2024 em relação a 2023, com pico em novembro 2023 devido à Black Friday."

Essa frase define o que precisa aparecer no gráfico: a tendência de crescimento e o pico com sua causa.

Versão 3 — explicativa (produto final)

fig, ax = plt.subplots(figsize=(13, 5))

# Linha principal
ax.plot(vendas_mensais['mes'], vendas_mensais['total'] / 1000,
        color='#2166AC', linewidth=2.5, zorder=3)

# Área preenchida para dar peso visual
ax.fill_between(vendas_mensais['mes'], vendas_mensais['total'] / 1000,
                alpha=0.08, color='#2166AC')

# Destaque do pico
pico_idx = vendas_mensais['total'].idxmax()
pico_mes = vendas_mensais.loc[pico_idx, 'mes']
pico_val = vendas_mensais.loc[pico_idx, 'total'] / 1000

ax.scatter([pico_mes], [pico_val],
           color='#D73027', s=80, zorder=5)

ax.annotate(
    f'Black Friday\nR$ {pico_val:.1f}k',
    xy=(pico_mes, pico_val),
    xytext=(pico_mes - pd.DateOffset(months=4), pico_val + 15),
    arrowprops=dict(arrowstyle='->', color='#D73027',
                    connectionstyle='arc3,rad=0.2'),
    fontsize=9, color='#D73027', fontweight='bold'
)

# Linha de tendência linear
x_num = np.arange(len(vendas_mensais))
slope, intercept, r, p, se = stats.linregress(
    x_num, vendas_mensais['total'] / 1000
)
ax.plot(vendas_mensais['mes'],
        slope * x_num + intercept,
        color='gray', linewidth=1.2, linestyle='--',
        alpha=0.7, label=f'Tendência linear (r = {r:.2f})')

# Linha separadora entre 2023 e 2024
divisor = pd.Timestamp('2024-01-01')
ax.axvline(divisor, color='gray', linestyle=':', linewidth=1, alpha=0.6)
ax.text(divisor + pd.DateOffset(days=5),
        ax.get_ylim()[0] + 5, '2024',
        fontsize=9, color='gray', va='bottom')
ax.text(divisor - pd.DateOffset(days=5),
        ax.get_ylim()[0] + 5, '2023',
        fontsize=9, color='gray', va='bottom', ha='right')

# Comparação ano a ano
vendas_2023 = vendas_mensais[vendas_mensais['mes'].dt.year == 2023]['total'].sum()
vendas_2024 = vendas_mensais[vendas_mensais['mes'].dt.year == 2024]['total'].sum()
crescimento = (vendas_2024 - vendas_2023) / vendas_2023 * 100

# Título como declaração — comunica a conclusão
ax.set_title(
    f'Vendas cresceram {crescimento:.0f}% em 2024, com pico na Black Friday de 2023',
    fontsize=13, pad=15, loc='left', fontweight='normal'
)

ax.set_ylabel('Vendas (R$ mil)', fontsize=11)
ax.set_xlabel('')

# Remover bordas desnecessárias
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

# Grade leve apenas no eixo Y
ax.grid(axis='y', alpha=0.3, linewidth=0.5)
ax.grid(axis='x', visible=False)

ax.legend(fontsize=9, framealpha=0)

# Formatar datas no eixo X
ax.xaxis.set_major_formatter(
    plt.matplotlib.dates.DateFormatter('%b\n%Y')
)

# Fonte dos dados
fig.text(0.99, 0.01, 'Fonte: Sistema de vendas interno',
         ha='right', fontsize=8, color='gray')

plt.tight_layout()
plt.show()

Compare as duas versões. A versão exploratória tem os dados corretos. A versão explicativa comunica a mensagem: o leitor vê imediatamente o crescimento, identifica o pico, entende a causa, e lê a conclusão no título antes de analisar qualquer detalhe.


Técnicas de hierarquia visual

Hierarquia visual é a ordem em que os elementos são percebidos. O elemento mais importante deve ser percebido primeiro. Três ferramentas criam hierarquia: contraste, tamanho e posição.

Contraste por cor

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

vendas_cat = (df.groupby('categoria')['valor']
              .sum()
              .sort_values(ascending=True)
              / 1000)

# Sem hierarquia — todas as barras iguais
axes[0].barh(vendas_cat.index, vendas_cat.values,
             color='steelblue', edgecolor='white', linewidth=0.5)
axes[0].set_title('Sem hierarquia — todas as cores iguais')
axes[0].set_xlabel('Vendas (R$ mil)')

# Com hierarquia — Eletrônicos em destaque
cores = ['#AAAAAA'] * len(vendas_cat)
idx_destaque = list(vendas_cat.index).index('Eletrônicos')
cores[idx_destaque] = '#2166AC'

axes[1].barh(vendas_cat.index, vendas_cat.values,
             color=cores, edgecolor='white', linewidth=0.5)

# Rótulo de valor apenas para o destaque
val_dest = vendas_cat['Eletrônicos']
axes[1].text(val_dest + 2, idx_destaque,
             f'R$ {val_dest:.0f}k',
             va='center', fontsize=10,
             color='#2166AC', fontweight='bold')

axes[1].set_title('Com hierarquia — Eletrônicos em destaque')
axes[1].set_xlabel('Vendas (R$ mil)')

for ax in axes:
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.grid(axis='x', alpha=0.3, linewidth=0.5)

plt.tight_layout()
plt.show()

Contraste por opacidade em linhas múltiplas

vendas_regiao = (df.groupby(['mes', 'regiao'])['valor']
                 .sum()
                 .reset_index()
                 .rename(columns={'valor': 'total'}))
vendas_regiao['total'] = vendas_regiao['total'] / 1000

fig, axes = plt.subplots(1, 2, figsize=(15, 5))

regioes = vendas_regiao['regiao'].unique()

# Sem hierarquia — todas as linhas com mesmo peso
paleta = sns.color_palette('Set2', len(regioes))
for regiao, cor in zip(regioes, paleta):
    dados = vendas_regiao[vendas_regiao['regiao'] == regiao]
    axes[0].plot(dados['mes'], dados['total'],
                 linewidth=2, label=regiao, color=cor)

axes[0].set_title('Sem hierarquia — todas as regiões iguais')
axes[0].set_ylabel('Vendas (R$ mil)')
axes[0].legend(fontsize=8)

# Com hierarquia — Sudeste em destaque
regiao_foco = 'Sudeste'
for regiao in regioes:
    dados = vendas_regiao[vendas_regiao['regiao'] == regiao]
    if regiao == regiao_foco:
        axes[1].plot(dados['mes'], dados['total'],
                     linewidth=2.5, color='#D73027',
                     zorder=5, label=regiao_foco)
        # Rótulo direto na linha
        ultimo = dados.iloc[-1]
        axes[1].text(ultimo['mes'] + pd.DateOffset(days=10),
                     ultimo['total'],
                     regiao_foco, fontsize=9,
                     color='#D73027', va='center')
    else:
        axes[1].plot(dados['mes'], dados['total'],
                     linewidth=1, color='#CCCCCC',
                     alpha=0.7, zorder=2)

axes[1].set_title('Com hierarquia — Sudeste em destaque')
axes[1].set_ylabel('Vendas (R$ mil)')

for ax in axes:
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.grid(axis='y', alpha=0.3, linewidth=0.5)
    ax.xaxis.set_major_formatter(
        plt.matplotlib.dates.DateFormatter('%b %Y')
    )
    ax.tick_params(axis='x', rotation=30)

plt.tight_layout()
plt.show()

Anotações que guiam a leitura

Anotações diretas — texto, setas, caixas — eliminam o trabalho do leitor de interpretar o que está vendo. Boas anotações são precisas, curtas e posicionadas sem cobrir dados importantes.

fig, ax = plt.subplots(figsize=(13, 5))

# Dados de satisfação por mês
satisf_mensal = (df.groupby('mes')['satisfacao']
                 .mean()
                 .reset_index())

ax.plot(satisf_mensal['mes'], satisf_mensal['satisfacao'],
        color='#4DAC26', linewidth=2.5)
ax.fill_between(satisf_mensal['mes'], satisf_mensal['satisfacao'],
                alpha=0.1, color='#4DAC26')

# Linha de meta
meta = 3.8
ax.axhline(meta, color='#D73027', linestyle='--',
           linewidth=1.5, alpha=0.8)
ax.text(satisf_mensal['mes'].iloc[-1] + pd.DateOffset(days=5),
        meta, f'Meta: {meta}',
        va='center', fontsize=9, color='#D73027')

# Identificar meses abaixo da meta
abaixo_meta = satisf_mensal[satisf_mensal['satisfacao'] < meta]
if len(abaixo_meta) > 0:
    ax.scatter(abaixo_meta['mes'], abaixo_meta['satisfacao'],
               color='#D73027', s=60, zorder=5)

# Anotação de tendência
ultimo_mes = satisf_mensal.iloc[-1]
primeiro_mes = satisf_mensal.iloc[0]
variacao = ultimo_mes['satisfacao'] - primeiro_mes['satisfacao']
sinal = '+' if variacao > 0 else ''
ax.annotate(
    f'{sinal}{variacao:.2f} pts no período',
    xy=(ultimo_mes['mes'], ultimo_mes['satisfacao']),
    xytext=(ultimo_mes['mes'] - pd.DateOffset(months=6),
            ultimo_mes['satisfacao'] + 0.15),
    arrowprops=dict(arrowstyle='->', color='gray'),
    fontsize=9, color='gray'
)

# Zona de conforto (acima da meta)
ax.axhspan(meta, 5.1, alpha=0.04, color='#4DAC26')
ax.text(satisf_mensal['mes'].iloc[0],
        4.9, 'Acima da meta',
        fontsize=8, color='#4DAC26', alpha=0.8)

ax.set_ylim(2.5, 5.1)
ax.set_ylabel('Satisfação média (1-5)', fontsize=11)
ax.set_title('Satisfação do cliente evoluiu acima da meta na maior parte do período',
             fontsize=12, loc='left', pad=15)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.grid(axis='y', alpha=0.3, linewidth=0.5)
ax.xaxis.set_major_formatter(
    plt.matplotlib.dates.DateFormatter('%b\n%Y')
)

fig.text(0.99, 0.01, 'Fonte: Sistema CRM interno',
         ha='right', fontsize=8, color='gray')

plt.tight_layout()
plt.show()

Títulos que comunicam conclusões

A diferença entre um título descritivo e um título conclusivo é a diferença entre um gráfico que informa e um gráfico que comunica.

Título descritivo: "Vendas por categoria em 2024" — descreve o que o gráfico contém. Título conclusivo: "Eletrônicos responderam por 38% das vendas em 2024, liderando pelo segundo ano consecutivo" — comunica o que o analista descobriu.

fig, axes = plt.subplots(2, 2, figsize=(14, 10))

vendas_cat = (df.groupby('categoria')['valor']
              .sum()
              .sort_values(ascending=False)
              / 1000)

paleta_base = ['#2166AC'] + ['#AAAAAA'] * (len(vendas_cat) - 1)

for ax, titulo in zip(axes.flat, [
    'Vendas por categoria',                                    # ruim
    'Distribuição das vendas por categoria no período',       # ruim
    'Eletrônicos lideram com R$ {:.0f}k em vendas'.format(   # bom
        vendas_cat.iloc[0]),
    'Eletrônicos concentram {:.0f}% das vendas,\n'           # melhor
    'quase o dobro de Vestuário'.format(
        vendas_cat.iloc[0] / vendas_cat.sum() * 100)
]):
    bars = ax.bar(vendas_cat.index, vendas_cat.values,
                  color=paleta_base, edgecolor='white', linewidth=0.5)
    ax.set_title(titulo, fontsize=10, pad=8, loc='left')
    ax.set_ylabel('Vendas (R$ mil)')
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.grid(axis='y', alpha=0.3, linewidth=0.5)
    ax.tick_params(axis='x', rotation=15)

axes[0, 0].text(0.02, 0.97, 'Título ruim:\nApenas descreve o conteúdo',
               transform=axes[0, 0].transAxes, fontsize=8,
               color='#D73027', va='top',
               bbox=dict(boxstyle='round', facecolor='#FFEEEE', alpha=0.8))

axes[1, 1].text(0.02, 0.97, 'Título bom:\nComunica a conclusão principal',
               transform=axes[1, 1].transAxes, fontsize=8,
               color='#4DAC26', va='top',
               bbox=dict(boxstyle='round', facecolor='#EEFFEE', alpha=0.8))

plt.suptitle('A importância do título: descrever vs. comunicar', fontsize=13, y=1.01)
plt.tight_layout()
plt.show()

Remoção sistemática de ruído

A remoção de elementos desnecessários é tão importante quanto adicionar elementos úteis. O processo é sistemático: pergunte sobre cada elemento visual "o que este elemento comunica que não estaria comunicado sem ele?"

def aplicar_estilo_publicacao(ax, titulo='', subtitulo='',
                               fonte='', xlabel='', ylabel=''):
    """
    Aplica estilo de publicação a um eixo matplotlib.
    Remove ruído e adiciona elementos de contexto essenciais.
    """
    # Remover bordas superior e direita
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)

    # Tornar bordas restantes mais sutis
    ax.spines['left'].set_linewidth(0.5)
    ax.spines['left'].set_color('#AAAAAA')
    ax.spines['bottom'].set_linewidth(0.5)
    ax.spines['bottom'].set_color('#AAAAAA')

    # Grade leve apenas no eixo principal
    ax.yaxis.grid(True, linewidth=0.4, color='#DDDDDD', zorder=0)
    ax.xaxis.grid(False)
    ax.set_axisbelow(True)

    # Ticks mais sutis
    ax.tick_params(axis='both', which='both',
                   length=0, labelsize=9, labelcolor='#555555')

    # Título e subtítulo
    if titulo:
        ax.set_title(titulo, fontsize=12, loc='left',
                     pad=15, color='#222222', fontweight='normal')
    if subtitulo:
        ax.text(0, 1.02, subtitulo,
               transform=ax.transAxes, fontsize=9,
               color='#888888', va='bottom')

    # Rótulos de eixo
    if xlabel:
        ax.set_xlabel(xlabel, fontsize=10, color='#555555', labelpad=8)
    if ylabel:
        ax.set_ylabel(ylabel, fontsize=10, color='#555555', labelpad=8)

    return ax

# Demonstração: antes e depois da limpeza
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

vendas_mes_2024 = (df[df['mes'].dt.year == 2024]
                   .groupby('mes')['valor']
                   .sum() / 1000)

# Antes — ruído visual padrão do matplotlib
axes[0].plot(vendas_mes_2024.index, vendas_mes_2024.values,
             linewidth=2, color='blue', marker='o', markersize=6)
axes[0].set_facecolor('#F5F5F5')
axes[0].set_title('VENDAS MENSAIS 2024', fontweight='bold', fontsize=14)
axes[0].set_ylabel('VALOR TOTAL DAS VENDAS EM R$ MIL', fontsize=9)
axes[0].set_xlabel('MÊS', fontsize=9)
for spine in axes[0].spines.values():
    spine.set_linewidth(1.5)
axes[0].grid(True, linewidth=1, color='white')
axes[0].tick_params(axis='x', rotation=45)

# Depois — estilo de publicação
axes[1].plot(vendas_mes_2024.index, vendas_mes_2024.values,
             linewidth=2.5, color='#2166AC', zorder=3)
axes[1].fill_between(vendas_mes_2024.index, vendas_mes_2024.values,
                     alpha=0.08, color='#2166AC')

aplicar_estilo_publicacao(
    axes[1],
    titulo='Vendas cresceram ao longo de 2024',
    subtitulo='Vendas mensais em R$ mil — jan. a dez. 2024',
    ylabel='R$ mil'
)

axes[1].xaxis.set_major_formatter(
    plt.matplotlib.dates.DateFormatter('%b')
)

for ax_i, rotulo in [(axes[0], 'ANTES'), (axes[1], 'DEPOIS')]:
    ax_i.text(0.5, -0.18, rotulo, transform=ax_i.transAxes,
              ha='center', fontsize=12, fontweight='bold',
              color='#888888' if rotulo == 'ANTES' else '#2166AC')

plt.tight_layout()
plt.show()

Exportação em alta resolução

Gráficos para relatórios e apresentações precisam de resolução adequada ao destino:

def salvar_grafico(fig, nome_base, formatos=None, dpi_raster=200):
    """
    Salva um gráfico nos formatos especificados.

    Parâmetros
    ----------
    fig : matplotlib Figure
    nome_base : str — nome do arquivo sem extensão
    formatos : list — lista de extensões ['png', 'pdf', 'svg']
    dpi_raster : int — resolução para PNG
    """
    if formatos is None:
        formatos = ['png', 'pdf']

    for fmt in formatos:
        caminho = f'{nome_base}.{fmt}'
        if fmt == 'png':
            fig.savefig(caminho, dpi=dpi_raster,
                       bbox_inches='tight',
                       facecolor='white')
        elif fmt == 'pdf':
            fig.savefig(caminho, bbox_inches='tight',
                       facecolor='white')
        elif fmt == 'svg':
            fig.savefig(caminho, bbox_inches='tight',
                       facecolor='white')
        print(f'Salvo: {caminho}')

# Uso:
# salvar_grafico(fig, 'vendas_2024', formatos=['png', 'pdf'])

Diretrizes de formato:

PNG com 150 a 200 dpi para relatórios digitais e apresentações em tela. PNG com 300 dpi para impressão. PDF ou SVG para documentos que serão editados ou escalados. SVG para web quando você precisa de escalabilidade sem perda de qualidade.


Sistema de estilos para um relatório completo

Quando você produz múltiplos gráficos para o mesmo relatório, um sistema de estilos garante consistência:

# Sistema de estilos para um relatório de vendas
RELATORIO = {
    # Paleta de cores
    'cor_primaria': '#2166AC',
    'cor_secundaria': '#D73027',
    'cor_destaque': '#F4A261',
    'cor_neutra': '#AAAAAA',
    'cor_sucesso': '#4DAC26',
    'cor_fundo': 'white',

    # Tipografia
    'fonte_titulo': 12,
    'fonte_subtitulo': 9,
    'fonte_rotulo': 10,
    'fonte_tick': 9,
    'fonte_anotacao': 8,
    'fonte_rodape': 8,

    # Layout
    'figsize_padrao': (11, 4.5),
    'figsize_largo': (14, 5),
    'figsize_quadrado': (7, 6),

    # Estilo de linha
    'linewidth_principal': 2.5,
    'linewidth_referencia': 1.2,
    'linewidth_grade': 0.4,
}

def grafico_base(figsize=None, titulo='', subtitulo='',
                  fonte='Fonte: dados internos'):
    """
    Cria figura e eixo com estilo padrão do relatório.
    Retorna (fig, ax) prontos para adicionar dados.
    """
    fs = figsize or RELATORIO['figsize_padrao']
    fig, ax = plt.subplots(figsize=fs)

    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.spines['left'].set_linewidth(0.5)
    ax.spines['left'].set_color('#CCCCCC')
    ax.spines['bottom'].set_linewidth(0.5)
    ax.spines['bottom'].set_color('#CCCCCC')

    ax.yaxis.grid(True, linewidth=RELATORIO['linewidth_grade'],
                  color='#EEEEEE', zorder=0)
    ax.xaxis.grid(False)
    ax.set_axisbelow(True)

    ax.tick_params(length=0, labelsize=RELATORIO['fonte_tick'],
                   labelcolor='#555555')

    if titulo:
        ax.set_title(titulo, fontsize=RELATORIO['fonte_titulo'],
                     loc='left', pad=18, color='#222222')
    if subtitulo:
        ax.text(0, 1.04, subtitulo, transform=ax.transAxes,
               fontsize=RELATORIO['fonte_subtitulo'],
               color='#888888', va='bottom')
    if fonte:
        fig.text(0.99, 0.01, fonte,
                ha='right', fontsize=RELATORIO['fonte_rodape'],
                color='#AAAAAA')

    return fig, ax

# Usar o sistema de estilos para criar gráficos consistentes
fig1, ax1 = grafico_base(
    titulo='Eletrônicos lideraram vendas em todos os trimestres de 2024',
    subtitulo='Vendas por categoria | Jan–Dez 2024'
)

vendas_cat_trim = (df[df['mes'].dt.year == 2024]
                   .assign(trimestre=df['mes'].dt.quarter)
                   .groupby(['trimestre', 'categoria'])['valor']
                   .sum()
                   .unstack()
                   / 1000)

categorias = vendas_cat_trim.columns
cores = [RELATORIO['cor_primaria']] + [RELATORIO['cor_neutra']] * (len(categorias) - 1)

x = np.arange(4)
largura = 0.2
for i, (cat, cor) in enumerate(zip(categorias, cores)):
    offset = (i - len(categorias) / 2 + 0.5) * largura
    ax1.bar(x + offset, vendas_cat_trim[cat],
            width=largura, color=cor,
            edgecolor='white', linewidth=0.5,
            label=cat, alpha=0.9 if cor == RELATORIO['cor_primaria'] else 0.5)

ax1.set_xticks(x)
ax1.set_xticklabels(['T1', 'T2', 'T3', 'T4'])
ax1.set_ylabel('Vendas (R$ mil)', fontsize=RELATORIO['fonte_rotulo'],
               color='#555555')
ax1.legend(title='Categoria', fontsize=8, framealpha=0,
           bbox_to_anchor=(1.01, 1), loc='upper left')

plt.tight_layout()
plt.show()

Checklist de revisão antes de publicar

Antes de incluir qualquer gráfico num relatório ou apresentação, passe por este checklist:

checklist_grafico = """
CHECKLIST DE REVISÃO — GRÁFICO EXPLICATIVO
==========================================

MENSAGEM
[ ] O título comunica uma conclusão (não apenas descreve o conteúdo)?
[ ] A mensagem principal é visualmente dominante?
[ ] Um leitor sem contexto entenderia em 5 segundos?

DADOS E ESCALA
[ ] O eixo Y começa em zero para gráficos de barras?
[ ] Escalas são consistentes com outros gráficos do mesmo relatório?
[ ] Unidades estão claramente indicadas nos rótulos dos eixos?
[ ] Datas e períodos estão claros?

HIERARQUIA VISUAL
[ ] O elemento mais importante está visualmente destacado?
[ ] Elementos secundários estão neutralizados (cinza ou transparência)?
[ ] A legenda é necessária ou os elementos podem ser rotulados diretamente?

ANOTAÇÕES E CONTEXTO
[ ] Picos, quedas e eventos importantes estão anotados?
[ ] A fonte dos dados está indicada?
[ ] Limitações relevantes estão mencionadas (se aplicável)?

LIMPEZA VISUAL
[ ] Bordas superior e direita foram removidas?
[ ] A grade é leve e não compete com os dados?
[ ] Não há elementos decorativos (3D, gradientes, sombras)?
[ ] Rótulos são legíveis no tamanho de exibição pretendido?

CONSISTÊNCIA
[ ] As cores são consistentes com os outros gráficos do relatório?
[ ] O tamanho da fonte é consistente?
[ ] O estilo é consistente com os outros gráficos do relatório?

EXPORTAÇÃO
[ ] O gráfico foi salvo com resolução adequada ao destino?
[ ] O fundo é branco (não transparente) se for inserido em apresentação?
"""

print(checklist_grafico)

Resumo

A transformação de gráfico exploratório para explicativo segue cinco passos: definir a mensagem em uma frase, criar hierarquia visual com contraste e destaque, anotar diretamente os elementos importantes, remover ruído sistemático e aplicar estilo consistente. Títulos conclusivos comunicam a descoberta; títulos descritivos apenas nomeiam o conteúdo. Hierarquia visual usa cor, opacidade e peso das linhas para guiar o olho ao mais importante. Anotações diretas — setas, rótulos, caixas — eliminam a necessidade de o leitor interpretar. A função aplicar_estilo_publicacao encapsula as remoções de ruído para uso consistente. Um sistema de estilos com cores, fontes e tamanhos padronizados garante que todos os gráficos de um relatório parecem pertencer ao mesmo conjunto. O checklist de revisão funciona como controle de qualidade antes de publicar.

Exercícios

  1. Qual a diferença fundamental entre um gráfico exploratório e um gráfico explicativo? Em que ponto do processo analítico cada um é produzido?

    ✓ Resposta:

    Um gráfico exploratório é criado para o analista. Seu objetivo é ajudar quem faz a análise a pensar — descobrir padrões, formular hipóteses, identificar anomalias. Velocidade de produção é mais importante que polimento. Um histograma sem título gerado em uma linha de código serve perfeitamente se responde a pergunta imediata do analista.

    Um gráfico explicativo é criado para o leitor. Seu objetivo é comunicar uma descoberta específica a alguém que não participou da análise e não conhece os dados. Ele assume que o leitor tem atenção limitada e não vai interpretar ativamente — o gráfico precisa trabalhar por conta própria. Polimento, hierarquia visual e anotações são essenciais.

    No processo analítico, gráficos exploratórios são produzidos durante as fases de wrangling e análise — você gera dezenas deles enquanto entende os dados. Gráficos explicativos são produzidos na fase de comunicação — você seleciona três ou quatro das descobertas mais importantes e os refina para o público-alvo. A proporção típica é algo como vinte gráficos exploratórios para cada gráfico explicativo que vai para um relatório.

    A habilidade de saber quais descobertas merecem ser transformadas em gráficos explicativos — e como fazer essa transformação — é o que distingue um analista que produz insight de um que produz outputs.

  2. Por que títulos conclusivos são mais eficazes do que títulos descritivos em visualizações explicativas? Dê dois exemplos de transformação de um título descritivo para um conclusivo.

    ✓ Resposta:

    Títulos descritivos transferem o trabalho de interpretação para o leitor. Quando o título diz "Vendas por região em 2024", o leitor precisa olhar para o gráfico, identificar qual região é maior, estimar as proporções e formular a conclusão por conta própria — tudo isso em poucos segundos de atenção disponível.

    Títulos conclusivos fazem esse trabalho pelo leitor. Quando o título diz "Sul e Sudeste concentraram 72% das vendas em 2024", o leitor confirma visualmente o que já leu. A carga cognitiva cai drasticamente. Além disso, títulos conclusivos forçam o analista a ser preciso sobre o que realmente descobriu — se você não consegue formular uma conclusão específica, provavelmente não terminou a análise.

    Transformação 1: Descritivo: "Taxa de devolução por categoria de produto" Conclusivo: "Eletrônicos têm taxa de devolução 3 vezes maior que a média — puxada por celulares importados"

    Transformação 2: Descritivo: "Satisfação dos clientes ao longo do tempo" Conclusivo: "Satisfação caiu abaixo da meta em agosto e setembro após mudança no prazo de entrega"

    Em ambos os casos, o título conclusivo não apenas nomeia o conteúdo mas comunica a descoberta mais importante. Um leitor que lê apenas o título já sabe o que o gráfico mostra.

  3. Descreva a técnica de destaque por neutralização e explique quando ela é preferível a usar múltiplas cores distintas.

    ✓ Resposta:

    A técnica de destaque por neutralização colore em cinza claro todos os elementos que não são o foco da mensagem e usa uma cor forte apenas para o elemento que você quer que o leitor note. O contraste entre cinza e cor forte cria uma hierarquia visual imediata: o olho vai diretamente para o elemento colorido.

    A diferença com múltiplas cores distintas é de intenção comunicativa. Múltiplas cores distintas dizem "todos esses elementos são igualmente importantes — compare-os". Destaque por neutralização diz "este elemento específico é o que importa — os outros são contexto".

    A técnica de neutralização é preferível quando: a mensagem central envolve um ou dois elementos específicos que se diferenciam dos demais (a categoria que lidera, a região que declinou, o mês do pico). Nesse caso, colorir todos com cores distintas distribui igualmente a atenção entre elementos de importância desigual.

    Múltiplas cores distintas são preferíveis quando: a mensagem é genuinamente sobre a comparação entre todos os grupos — não há um grupo que importa mais que os outros — e você quer que o leitor explore as diferenças entre todos. Por exemplo, num gráfico mostrando a paleta de produtos para ajudar o leitor a escolher entre opções equivalentes.

    Um teste simples: se você consegue completar a frase "este gráfico mostra que [grupo específico]...", use neutralização. Se a mensagem é "estes grupos diferem de várias formas interessantes", use múltiplas cores.

  4. Você está criando um relatório com oito gráficos sobre desempenho de vendas. Descreva como você criaria um sistema de estilos e quais elementos ele deveria padronizar.

    ✓ Resposta:

    Um sistema de estilos para um relatório é um conjunto de constantes e funções que garantem que todos os gráficos usam as mesmas escolhas visuais. O objetivo é que o leitor não precise reaprender a linguagem visual a cada novo gráfico.

    Os elementos a padronizar são os seguintes.

    Paleta de cores: definir no máximo cinco a seis cores com papéis claros. Uma cor primária para a série ou categoria principal de interesse, uma cor de destaque para alertas ou itens negativos, uma cor de sucesso para resultados positivos, e uma cor neutra para contexto e elementos secundários. Todas as cores devem funcionar em conjunto e ser distinguíveis por daltônicos.

    Tipografia: tamanhos fixos para cada tipo de elemento — títulos, subtítulos, rótulos de eixo, ticks, anotações e notas de rodapé. Dois ou três tamanhos apenas, sem variação livre. Cor do texto padronizada (nunca preto puro — um cinza escuro como #222222 é mais suave e moderno).

    Dimensões: tamanhos de figura padrão para tipos diferentes de gráfico — largo para séries temporais, quadrado para scatter, médio para barras. Isso garante que os gráficos sejam consistentes quando colocados lado a lado no relatório.

    Estilo de fundo e grade: sempre branco, grade leve em cinza claro apenas no eixo principal, bordas superior e direita removidas. Definido uma vez e aplicado por função.

    Elementos de contexto: formato padrão para indicação de fonte dos dados, posição e formato da legenda, formatação padrão dos eixos (moeda, porcentagem, datas).

    Implementar isso como um dicionário de constantes e uma função grafico_base() que cria a figura já com o estilo aplicado é muito mais eficiente do que replicar as configurações em cada gráfico. Quando você decidir mudar o estilo — um cliente prefere a cor primária em verde em vez de azul — você muda em um lugar e todos os gráficos atualizam.

  5. Você terminou uma análise de satisfação de clientes e produziu um gráfico de linha mostrando a evolução mensal. Antes de incluir no relatório, aplique o checklist de revisão e identifique pelo menos cinco elementos que você verificaria e como cada um afetaria a qualidade do gráfico.

    ✓ Resposta:

    Elemento 1 — título: verificar se o título comunica uma conclusão ou apenas descreve o conteúdo. "Satisfação mensal dos clientes" é descritivo e fraco. "Satisfação subiu 0.4 pontos após mudança na política de trocas em março" é conclusivo e imediatamente útil. Impacto: um título fraco obriga o leitor a interpretar o gráfico sozinho; um título forte direciona a atenção e reduz o trabalho cognitivo.

    Elemento 2 — escala do eixo Y: verificar se a escala é honesta e adequada. Para satisfação de 1 a 5, o eixo Y deve mostrar pelo menos o intervalo relevante com contexto suficiente. Um eixo truncado entre 3.5 e 4.5 faz variações de décimos parecerem enormes. Um eixo de 1 a 5 mostra a escala completa e contextualiza a variação real. Impacto: escala inadequada pode amplificar ou minimizar artificialmente a magnitude das mudanças.

    Elemento 3 — linha de meta ou referência: verificar se há uma referência que contextualiza os valores. Um valor de 4.1 é bom ou ruim? Sem saber a meta (digamos, 3.8) ou a média histórica, o leitor não tem como avaliar. Adicionar uma linha tracejada de referência converte um dado absoluto em relativo. Impacto: sem referência, o leitor não sabe se celebrar ou se preocupar.

    Elemento 4 — anotação de eventos importantes: verificar se picos, quedas ou mudanças abruptas estão explicados diretamente no gráfico. Uma queda em agosto sem explicação levanta dúvidas. Uma anotação "mudança de fornecedor" ou "greve logística" transforma um dado estranho num dado compreensível. Impacto: eventos não anotados geram perguntas que interrompem a leitura e desviam da mensagem principal.

    Elemento 5 — consistência com os outros gráficos do relatório: verificar se as cores, tamanhos de fonte e estilo geral são iguais aos dos outros sete gráficos. Se este usa azul e os outros usam verde, o leitor vai procurar um significado na diferença de cor que não existe. Impacto: inconsistência cria ruído cognitivo — o leitor gasta energia procurando padrões onde não há nenhum.

Referências

  • Knaflic, Cole Nussbaumer. Storytelling with Data. Wiley, 2015. O livro mais prático sobre a transformação de gráficos exploratórios para explicativos, com antes e depois de dezenas de exemplos reais. Leitura essencial para esta aula.
  • Tufte, Edward R. The Visual Display of Quantitative Information, 2ª edição. Graphics Press, 2001. A referência clássica sobre data-ink ratio e remoção de ruído visual.
  • Knaflic, Cole Nussbaumer. Storytelling with Data: Let's Practice! Wiley, 2019. Continuação do primeiro livro com exercícios práticos de revisão de gráficos.
  • Few, Stephen. Show Me the Numbers: Designing Tables and Graphs to Enlighten, 2ª edição. Analytics Press, 2012. Referência detalhada sobre design de gráficos para relatórios e dashboards corporativos.
  • Matplotlib — Annotations: matplotlib.org/stable/tutorials/text/annotations.html Referência completa para adicionar setas, caixas e texto diretamente nos gráficos.
  • Documentação do matplotlib — Saving figures: matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.savefig.html Referência para todos os parâmetros de exportação, incluindo dpi, formato e bounding box.