"Dados não são números frios. São histórias, padrões, erros e oportunidades. Aprenda a ouvi-los."
Porque aqui é onde a teoria se torna prática.
Na Lição 2 você aprendeu o mapa. Agora, você vai percorrer o caminho.
Você irá:
⚠️ Aviso amigável: Esta lição tem mais código que as anteriores. Mas não tenha medo. Faremos passo a passo, com explicações detalhadas, erros comuns e dicas de especialistas. Você não estará sozinho.
Ao final, você será capaz de:
✅ Carregar um conjunto de dados de uma URL ou arquivo local usando Pandas.
✅ Explorar sua estrutura, conteúdo e possíveis problemas (nulos, duplicados, valores estranhos).
✅ Criar visualizações simples para entender padrões.
✅ Preparar os dados para o modelo: codificar rótulos, dividir em treino/teste, vetorizar texto.
✅ Entender por que cada etapa de preparação é necessária.
✅ Sentir-se confortável manipulando dados… sua nova matéria-prima!
💡 Se você ainda não fez isso, abra o Google Colab agora: https://colab.research.google.com
Crie um novo notebook e vamos começar!
Usaremos o conjunto de dados SMS Spam Collection. É pequeno, limpo e perfeito para começar.
# Sempre comece importando o que você precisa
import pandas as pd
📌 O que é Pandas?
É uma biblioteca Python para manipular e analisar dados. Pense nela como o Excel, mas mais poderosa e programável.
# URL do conjunto de dados (hospedado no GitHub)
url = "https://raw.githubusercontent.com/justmarkham/DAT8/master/data/sms.tsv"
# Carregar com pandas
# O arquivo é separado por tabulações (\t), e não tem cabeçalho
data = pd.read_csv(url, sep='\t', names=['label', 'message'])
# Mostrar as primeiras 5 linhas
print(data.head())
📌 Saída esperada:
label message
0 ham Go until jurong point, crazy.. Available only ...
1 ham Ok lar... Joking wif u oni...
2 spam Free entry in 2 a wkly comp to win FA Cup fina...
3 ham U dun say so early hor... U c already then say...
4 ham Nah I don't think he goes to usf, he lives aro...
✅ Dados carregados! Agora você tem um DataFrame do Pandas.
# Quantas linhas e colunas?
print(f"Formato do conjunto de dados: {data.shape}") # (5572, 2)
# Nomes das colunas
print(f"Colunas: {data.columns.tolist()}") # ['label', 'message']
# Tipos de dados
print(data.dtypes)
📌 Saída:
label object
message object
dtype: object
→ Ambas as colunas são do tipo object (texto no Pandas).
# Resumo estatístico (apenas para colunas numéricas, mas útil para ver se há nulos)
print(data.describe(include='all'))
📌 Saída chave:
label message
count 5572 5572
unique 2 5169
top ham Sorry, I'll call later
freq 4825 30
→ unique=2 no rótulo: há apenas dois valores: 'ham' e 'spam'.
→ top=ham: o valor mais frequente é 'ham'.
→ freq=4825: 'ham' aparece 4825 vezes.
Agora, vamos aprofundar. Não assuma nada. Explore tudo.
print(data['label'].value_counts())
📌 Saída:
ham 4825
spam 747
Name: label, dtype: int64
→ Temos um conjunto de dados desbalanceado! Há 6,5 vezes mais ham que spam.
→ Isso é normal na detecção de spam… mas afetará como avaliamos o modelo. Veremos isso na Lição 6!
print(data.isnull().sum())
📌 Saída:
label 0
message 0
dtype: int64
→ Perfeito! Não há valores nulos. Na vida real, isso é raro. Sempre verifique isso.
# Criar uma nova coluna: comprimento da mensagem
data['length'] = data['message'].apply(len)
# Estatísticas descritivas
print(data['length'].describe())
📌 Saída:
count 5572.000000
mean 80.489052
std 59.942492
min 2.000000
25% 36.000000
50% 61.000000
75% 111.000000
max 910.000000
Name: length, dtype: float64
→ Há mensagens de até 910 caracteres! Serão spam? Serão normais?
import matplotlib.pyplot as plt
import seaborn as sns
# Definir estilo
sns.set_style("whitegrid")
# Histograma dos comprimentos, colorido por rótulo
plt.figure(figsize=(12, 6))
sns.histplot(data=data, x='length', hue='label', bins=50, kde=False)
plt.title("Distribuição do comprimento das mensagens por tipo (Spam vs Ham)", fontsize=16)
plt.xlabel("Comprimento da mensagem (caracteres)", fontsize=12)
plt.ylabel("Frequência", fontsize=12)
plt.legend(title='Tipo', labels=['Spam', 'Ham'])
plt.show()
📌 O que você vê?
Vamos fazer uma análise de texto muito básica.
# Filtrar apenas spam
spam_messages = data[data['label'] == 'spam']['message']
# Converter para minúsculas e dividir em palavras
words = ' '.join(spam_messages).lower().split()
# Contar frequência das palavras
from collections import Counter
word_freq = Counter(words)
# Mostrar as 20 palavras mais comuns em spam
print("Palavras mais frequentes em SPAM:")
for word, freq in word_freq.most_common(20):
print(f"{word}: {freq}")
📌 Saída típica:
free: 167
to: 137
you: 117
call: 90
txt: 89
now: 87
...
→ Claro! Palavras como "free", "call", "now" são muito comuns em spam.
→ Isso confirma que o modelo conseguirá aprender com essas pistas.
Agora, prepararemos os dados para o modelo. Lembre-se: modelos entendem números, não texto.
Converteremos 'ham' e 'spam' em 0 e 1.
# Criar um mapeamento
label_map = {'ham': 0, 'spam': 1}
# Aplicar o mapeamento
data['label_encoded'] = data['label'].map(label_map)
# Verificar
print(data[['label', 'label_encoded']].head())
📌 Saída:
label label_encoded
0 ham 0
1 ham 0
2 spam 1
3 ham 0
4 ham 0
→ Pronto! Agora o rótulo é numérico.
Nunca treine e avalie com os mesmos dados!
from sklearn.model_selection import train_test_split
# Características (X) = mensagens
# Rótulo (y) = label_encoded
X = data['message']
y = data['label_encoded']
# Dividir: 80% treino, 20% teste
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42, # Para reprodutibilidade
stratify=y # Mantém a proporção de spam/ham no treino e teste!
)
print(f"Tamanho do treino: {len(X_train)} mensagens")
print(f"Tamanho do teste: {len(X_test)} mensagens")
print(f"Proporção de spam no treino: {y_train.mean():.2%}")
print(f"Proporção de spam no teste: {y_test.mean():.2%}")
📌 Saída:
Tamanho do treino: 4457 mensagens
Tamanho do teste: 1115 mensagens
Proporção de spam no treino: 13.42%
Proporção de spam no teste: 13.41%
→ Perfeito! A proporção é mantida graças a stratify=y.
Usaremos o CountVectorizer do Scikit-learn.
from sklearn.feature_extraction.text import CountVectorizer
# Criar o vetorizador
vectorizer = CountVectorizer()
# Aprender o vocabulário e transformar X_train
X_train_vec = vectorizer.fit_transform(X_train)
# Apenas transformar X_test (não aprender com ele!)
X_test_vec = vectorizer.transform(X_test)
# Ver o tamanho
print(f"Vocabulário: {len(vectorizer.vocabulary_)} palavras únicas")
print(f"Formato de X_train_vec: {X_train_vec.shape}") # (4457, 7358)
print(f"Formato de X_test_vec: {X_test_vec.shape}") # (1115, 7358)
📌 O que significa (4457, 7358)?
→ Cada mensagem é agora um vetor de 7358 números (a maioria é 0, porque nem todas as palavras aparecem em todas as mensagens).
Quer ver que palavras o vetorizador aprendeu?
# Obter as primeiras 20 palavras do vocabulário
vocab = vectorizer.get_feature_names_out()
print("Primeiras 20 palavras no vocabulário:")
print(vocab[:20])
📌 Saída:
['00', '000', '0000', '00000', '000000', '00001', '0001', '00011', '00012', '00015', '0002', '0003', '0004', '0005', '0006', '0007', '00080', '0009', '001', '0010']
→ Ops! Há muitos números. Por quê? Porque o CountVectorizer por padrão pega tudo como palavra, incluindo números e pontuação.
💡 Dica profissional: Mais tarde, você pode melhorar isso com:
stop_words='english' → remover palavras comuns ("the", "and", "is").lowercase=True → converter para minúsculas (já faz isso por padrão).token_pattern=r'\b[a-zA-Z]{2,}\b' → apenas palavras de 2+ letras, sem números ou sinais.Mas por agora, está bom! Estamos aprendendo.
Agora, é sua vez de explorar.
# Encontrar o índice da mensagem mais longa
idx_max = data['length'].idxmax()
longest_message = data.loc[idx_max]
print(f"Comprimento: {longest_message['length']} caracteres")
print(f"Tipo: {longest_message['label']}")
print(f"Mensagem: {longest_message['message']}")
📌 Saída típica:
Comprimento: 910 caracteres
Tipo: spam
Mensagem: "I HAVE A DATE ON SUNDAY WITH WILL!!..." (Um spam MUITO longo!)
# Filtrar mensagens longas
long_messages = data[data['length'] > 200]
total_long = len(long_messages)
spam_long = long_messages[long_messages['label'] == 'spam'].shape[0]
print(f"Mensagens > 200 caracteres: {total_long}")
print(f"Dessas, spam: {spam_long} ({spam_long/total_long:.1%})")
📌 Saída típica:
Mensagens > 200 caracteres: 45
Dessas, spam: 43 (95.6%)
→ Quase todas as mensagens longas são spam! Isso confirma nossa hipótese visual.
Veja algumas mensagens aleatórias. Você vê sinais de pontuação, letras maiúsculas, números, erros de ortografia?
# Mostrar 5 mensagens aleatórias
sample = data.sample(5, random_state=1)
for i, row in sample.iterrows():
print(f"[{row['label']}] {row['message'][:100]}...") # Apenas os primeiros 100 caracteres
→ Você verá coisas como:
💡 Reflexão: Você acha que isso afetará o modelo? Como você poderia melhorá-lo? (Dica: limpeza de texto, lematização, etc. — veremos isso em cursos avançados).
stratify em train_test_split → Desbalanceia treino/teste.fit_transform no teste → Vazamento de dados. Apenas transform!y_train e y_test como variáveis separadas → Depois você não consegue treinar ou avaliar.☐ Carregar um conjunto de dados de uma URL com Pandas.
☐ Explorar sua estrutura, valores únicos, nulos e estatísticas.
☐ Criar visualizações para entender padrões (comprimento, palavras frequentes).
☐ Codificar rótulos de texto em números.
☐ Dividir dados em treino/teste mantendo proporções (stratify).
☐ Vetorizar texto com CountVectorizer.
☐ Entender a forma das matrizes resultantes.
☐ Fazer perguntas exploratórias e respondê-las com código.
☐ Sentir-se confortável com a manipulação básica de dados.
"Antes de treinar um modelo, treine seus olhos. Aprenda a ver o que os dados estão dizendo."
← Anterior: Lição 2: O Mapa do Tesouro | Próximo: Lição 4: Treine Seu Primeiro Modelo →
Course: AI-course0
Language: PT
Lesson: 3 data exploration