Feature Engineering

Implementação completa e consolidação de variáveis derivadas

Author

Marlon

Published

May 7, 2026

Introdução

Objetivos

Este notebook tem como objetivos:

  1. Documentar todas as variáveis existentes no dataset
  2. Consolidar features criadas nos notebooks de EDA (03-06)
  3. Limpar dados para análise semântica (URLs, outliers)
  4. Criar features adicionais para modelagem
  5. Gerar dataset final otimizado para clustering/tópicos
  6. Comparar abordagem metodológica vs exploratória anterior

Fluxo de Trabalho

flowchart TD
    A[Dataset Base] --> B[Dicionário de Variáveis]
    B --> C[Consolidação de Features]
    C --> D[Limpeza para Semântica]
    D --> E[Novas Features]
    E --> F[Feature Selection]
    F --> G[Dataset Final]
    G --> H[Comparação Metodológica]


Setup e Configuração

Configuração

Parâmetro Valor Path
Entrada messages.parquet ~/whatsapp-interaction-analysis/data/processed/export_2024-10_2025-10
Saída messages_enriched.parquet ~/whatsapp-interaction-analysis/data/processed/export_2024-10_2025-10

Carregamento dos Dados

Métrica Valor
Mensagens 91,924
Colunas 8
Período 19/10/2024 → 20/10/2025
Coluna Tipo Descrição
timestamp datetime Data e hora da mensagem
remetente category Participante (P1 ou P2)
tipo_mensagem category Tipo específico - 21 possíveis (text_pure, audio_omitted, video_attached, etc.)
grupo_mensagem category Categoria agrupada: TEXT, AUDIO, VID, IMG, STICKER, GIF, DOC, CONTACT, FILE, SYSTEM, CALL
conteudo text Conteúdo enriquecido: transcrição (se mídia transcrita) ou texto original
arquivo text Nome do arquivo de mídia anexado (ex: 00001-AUDIO-2024-10-21.opus), se aplicável
transcricao bool Indica se a mensagem possui transcrição de áudio/vídeo
date_match bool Indica se é mensagem órfã/sintética (transcrição sem match de timestamp)
timestamp remetente tipo_mensagem grupo_mensagem conteudo arquivo transcricao date_match
0 2024-10-19 13:52:51 P1 text_pure TEXT Le? NaN False False
1 2024-10-19 13:53:25 P2 text_with_link TEXT https://www.localiza.com/brasil/pt-br/reservas... NaN False False
2 2024-10-19 13:53:25 P1 image_omitted IMG NaN NaN False False
3 2024-10-19 13:53:41 P2 sticker_omitted STICKER NaN NaN False False
4 2024-10-19 13:53:43 P2 text_pure TEXT Oi NaN False False

Dicionário de Variáveis

Variáveis do Dataset Original

NoteVariáveis Base (do processamento inicial)

Estas variáveis foram criadas no processo de ETL inicial (parsing do WhatsApp + enriquecimento contextual).

Identificação e Temporal:

Variável Tipo Descrição Exemplo
timestamp datetime64 Data/hora da mensagem 2024-12-16 19:23:45
remetente object Quem enviou (P1 ou P2) P1
ano int64 Ano da mensagem 2024
mes int64 Mês da mensagem 12
dia int64 Dia do mês 16

Conteúdo:

Variável Tipo Descrição Exemplo
tipo_mensagem object Tipo específico do WhatsApp text, audio, video, image, sticker
grupo_mensagem object Agrupamento por categoria TEXT, AUDIO, VID, IMG
conteudo object Texto da mensagem ou transcrição “Oi amor, tudo bem?”
midia_path object Caminho do arquivo de mídia (se houver) /media/audio_123.opus

Contexto:

Variável Tipo Descrição Exemplo
em_encontro bool Se estavam juntos fisicamente True
encontro_id float64 ID do encontro (se em_encontro=True) 5.0
fase_relacionamento object Fase da relação Relacionamento

Estatísticas do Dataset Base:

Resumo das variáveis base:

Tipos de mensagem: 21 únicos
tipo_mensagem
text_pure             62885
video_note_omitted     9168
audio_omitted          8040
text_with_emoji        3858
image_omitted          3287
sticker_omitted        1869
message_edited          658
text_with_link          554
audio_attached          512
image_attached          298
sticker_attached        248
video_omitted           248
video_attached          186
document_omitted         38
gif_omitted              28
message_deleted          22
missed_call              11
contact_attached          9
system_message            2
voice_call                2
file_attached             1
Name: count, dtype: int64

Grupos de mensagem: 11 únicos
grupo_mensagem
TEXT       67297
VID         9602
AUDIO       8552
IMG         3585
STICKER     2117
SYSTEM       682
DOC           38
GIF           28
CALL          13
CONTACT        9
FILE           1
Name: count, dtype: int64

Distribuição remetente:
remetente
P1    46948
P2    44976
Name: count, dtype: int64

Variáveis Criadas nos EDAs

ImportantFeatures Criadas em 03-overview.qmd

Não houve criação de features específicas - apenas análise descritiva das variáveis base.

Descobertas principais: - 97.2% Relacionamento vs 2.8% Amizade - 85.4% Separados vs 14.6% Juntos - Redução de -68.3% no volume quando juntos

ImportantFeatures Criadas em 04-eda-temporal.qmd

Features temporais derivadas:

Variável Tipo Descrição Valores Criação
hora int64 Hora do dia (0-23) 0, 1, …, 23 df['timestamp'].dt.hour
dia_semana object Dia da semana Segunda, Terça, …, Domingo df['timestamp'].dt.day_name()
mes int64 Mês (1-12) 1, 2, …, 12 df['timestamp'].dt.month
ano_mes object Ano-Mês para agregação 2024-12, 2025-01 df['timestamp'].dt.to_period('M')
fase_temporal object Fase evolutiva (4 fases) Início, Experimentação, Transição, Consolidação Regras temporais

Regras de fase_temporal:

# Início: Dezembro/2024
# Experimentação: Janeiro-Maio/2025
# Transição: Junho-Julho/2025
# Consolidação: Agosto-Outubro/2025

Descobertas: - Padrões horários: Separados nocturno (19h), Juntos diurno (12h) - Padrões semanais: Monday paradox (27.1% quando juntos) - Evolução: TEXT +8.3pp, AUDIO -56%, VID pico em Jan

ImportantFeatures Criadas em 05.2-eda-interacao.qmd

Features de interação e sequenciamento:

Variável Tipo Descrição Exemplo Criação
tempo_desde_ultima float64 Minutos desde mensagem anterior 0.12, 5.3, 120.0 Diff de timestamps
sequencia int64 Contador de msgs consecutivas do mesmo remetente 1, 2, 3, …, 54 Incremental por remetente
turno_id int64 ID da sessão de conversa (gap >1h) 0, 1, 2, …, 1125 Grupos por gap temporal

Features de contexto temporal (relacionamento com encontros):

Variável Tipo Descrição Exemplo Criação
dias_desde_ultimo float64 Dias desde último encontro 0.5, 5.0, 45.0 Cálculo baseado em datas de encontros
dias_ate_proximo float64 Dias até próximo encontro 0.5, 7.0, 50.0 Cálculo baseado em datas de encontros

Descobertas: - 81% respostas <60s (ultra-rápidas) - Sequências: Separados 3.45 msgs vs Juntos 2.73 msgs - Turnos: Separados 92.5 msgs vs Juntos 43.7 msgs (-53%) - Padrão “U invertido”: Volume pico pós-encontro e pré-encontro

ImportantFeatures Criadas em 06-eda-conteudo.qmd

Features de tamanho textual:

Variável Tipo Descrição Exemplo Criação
tamanho_chars int64 Número de caracteres 17, 183, 1164 df['conteudo'].str.len()
tamanho_palavras int64 Número de palavras 3, 36, 250 df['conteudo'].str.split().str.len()

Features de origem do texto:

Variável Tipo Descrição Valores Criação
origem object Fonte do conteúdo textual TEXT_escrito, TRANSCRITO_falado Baseado em grupo_mensagem
# Lógica:
# 'TEXT_escrito' se grupo_mensagem == 'TEXT'
# 'TRANSCRITO_falado' se grupo_mensagem in ['AUDIO', 'VID']

Features de URLs:

Variável Tipo Descrição Exemplo Criação
tem_url bool Se contém URL True, False Regex em conteudo
num_urls int64 Quantidade de URLs 0, 1, 2 Contagem via regex

Features de Emojis:

Variável Tipo Descrição Exemplo Criação
tem_emoji bool Se contém emoji True, False Biblioteca emoji
num_emojis int64 Quantidade de emojis 0, 1, 3 Contagem via emoji
emojis object (list) Lista de emojis da mensagem [‘💚’, ‘😍’] Extração via emoji

Descobertas: - Mediana: 3 palavras (ultra-curto) - TEXT vs TRANSCRITO: 3 vs 14 palavras (4.7x diferença!) - URLs: 0.8% (raro), P1 67% mais que P2 - Emojis: 7.5%, P1 frequente/pontual vs P2 raro/explosivo - 💚 é 15.2% de todos emojis (símbolo da relação)

Resumo de Features Disponíveis

Colunas Reais do Dataset

COLUNAS PRESENTES no dataset base:

Total: 8 colunas

   • timestamp                 | datetime64[us]  | 91,924 (100.0%)
   • remetente                 | category        | 91,924 (100.0%)
   • tipo_mensagem             | category        | 91,924 (100.0%)
   • grupo_mensagem            | category        | 91,924 (100.0%)
   • conteudo                  | str             | 68,686 (74.7%)
   • arquivo                   | str             | 1,254 (1.4%)
   • transcricao               | bool            | 91,924 (100.0%)
   • date_match                | bool            | 91,924 (100.0%)

======================================================================

Categorização das Colunas Existentes

CATEGORIZAÇÃO DAS COLUNAS EXISTENTES:

TEMPORAIS (1):
   timestamp

CONTEXTUAIS (0):

CONTEÚDO (3):
   tipo_mensagem
   grupo_mensagem
   conteudo

INTERAÇÃO (1):
   remetente

OUTRAS (3):
   • arquivo
   • transcricao
   • date_match

Features a Serem Criadas

FEATURES A SEREM CRIADAS:

TEMPORAIS:
   hora
   dia_semana
   mes
   ano_mes
   fase_temporal
INTERAÇÃO:
   tempo_desde_ultima
   sequencia
   turno_id
   dias_desde_ultimo
   dias_ate_proximo
CONTEÚDO:
   tamanho_chars
   tamanho_palavras
   origem
ELEMENTOS:
   tem_url
   num_urls
   tem_emoji
   num_emojis
   emojis

Total a criar: 18 features
NoteEntendimento do Dataset Base

Dataset base contém informação PRIMÁRIA: - Dados brutos do WhatsApp (timestamp, remetente, conteudo) - Enriquecimento contextual (em_encontro, fase_relacionamento)

Features DERIVADAS serão criadas aqui: - Extrações temporais (hora, dia_semana de timestamp) - Métricas de conteúdo (tamanho de conteudo) - Padrões de interação (sequências, turnos) - Elementos linguísticos (emojis, URLs)

Estas features não precisam estar no dataset base - são calculadas conforme necessário para análise.


Mapeamento de Nomenclatura

MAPEAMENTO - Nomenclatura Existente:

Features Realmente Faltantes


FEATURES QUE REALMENTE FALTAM:

   mes (int) - Mês do ano (1-12)
      -> Será extraído de timestamp

======================================================================

RESUMO:
   • Total de colunas: 8
   • Features temporais: COMPLETAS
   • Features interação: COMPLETAS
   • Features conteúdo: COMPLETAS
   • Features elementos: COMPLETAS
   • Faltando: apenas 'mes' (extração simples)
TipDataset Já Consolidado!

Surpresa agradável: O dataset messages_with_context.parquet JÁ CONTÉM praticamente todas as features necessárias!

Features presentes: - Temporais: hora, dia_semana, ano_mes, fase_temporal - Interação: tempo_desde_ultima, sequencia, turno_id - Conteúdo: tamanho_chars, tamanho_palavras, origem - Elementos: tem_url, num_urls, tem_emoji, num_emojis, emojis - Contexto encontros: dias_desde_ultimo_encontro, dias_ate_proximo_encontro

Diferenças de nomenclatura: - dias_desde_ultimo_encontro (não dias_desde_ultimo) - dias_ate_proximo_encontro (não dias_ate_proximo)

Faltando: - Apenas mes (mês como int 1-12)

Implicação para este notebook: O feature engineering será mais focado em: 1. Documentação das features existentes 2. Criação de mes (simples) 3. Limpeza para análise semântica (URLs, outliers) 4. Validação de qualidade 5. Comparação metodológica (se houver dataset anterior)


Features Temporais

Variáveis derivadas do timestamp.

Feature Tipo Definição Exemplo Uso
ano int Ano extraído do timestamp 2024 Comparar períodos anuais, evolução de longo prazo
trimestre category Trimestre do ano (Q1, Q2, Q3, Q4) Q3 Análise de tendências em blocos trimestrais
mes int Mês numérico (1=Jan, 12=Dez) 10 Sazonalidade mensal, comparações entre meses
data date Data sem componente de hora (YYYY-MM-DD) 2024-10-15 Agregações diárias, contar mensagens por dia
dia_semana category (ordered) Dia da semana textual (Segunda...Domingo) Sexta Padrões por dia da semana, comparações
fim_de_semana bool True se Sábado ou Domingo False Comparar comportamento weekday vs weekend
hora int Hora do dia (0-23) 14 Identificar horários de pico, padrões de atividade
periodo_dia category (ordered) Madrugada (00-05h), Manhã (06-11h), Tarde (12-17h), Noite (18-23h) Tarde Análise de padrões por turno do dia
horario_comercial bool True se Seg-Sex entre 08:00-18:00 True Diferenciar msgs em horário de trabalho vs lazer

Ano

# Extrai ano
df['ano'] = df['timestamp'].dt.year

Trimestre

# Extrai trimestre (Q1, Q2, Q3, Q4)
df['trimestre'] = 'Q' + df['timestamp'].dt.quarter.astype(str)
df['trimestre'] = df['trimestre'].astype('category')

Mês

# Extrai mês (1-12)
df['mes'] = df['timestamp'].dt.month

Data (sem hora)

# Extrai data sem componente de hora
df['data'] = df['timestamp'].dt.date

Dia da Semana

# Extrai dia da semana textual (Segunda-Domingo)
dias_semana = ['Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado', 'Domingo']
df['dia_semana'] = pd.Categorical(
    df['timestamp'].dt.day_name(locale='pt_BR.UTF-8'),
    categories=dias_semana,
    ordered=True
)

Fim de Semana

# Identifica se é final de semana (Sábado ou Domingo)
df['fim_de_semana'] = df['timestamp'].dt.dayofweek >= 5

Hora

# Extrai hora do dia (0-23)
df['hora'] = df['timestamp'].dt.hour

Período do Dia

# Define período do dia baseado na hora
def classificar_periodo(hora):
    if 0 <= hora <= 5:
        return 'Madrugada'
    elif 6 <= hora <= 11:
        return 'Manhã'
    elif 12 <= hora <= 17:
        return 'Tarde'
    else:
        return 'Noite'

df['periodo_dia'] = df['hora'].apply(classificar_periodo)
df['periodo_dia'] = pd.Categorical(
    df['periodo_dia'],
    categories=['Madrugada', 'Manhã', 'Tarde', 'Noite'],
    ordered=True
)

Horário Comercial

# Identifica se mensagem foi enviada em horário comercial
# Seg-Sex, 08:00-18:00
eh_dia_util = df['timestamp'].dt.dayofweek < 5  # 0=Segunda, 4=Sexta
eh_horario_comercial = (df['hora'] >= 8) & (df['hora'] < 18)
df['horario_comercial'] = eh_dia_util & eh_horario_comercial

Validação

timestamp ano trimestre mes data dia_semana fim_de_semana hora periodo_dia horario_comercial
0 2024-10-19 13:52:51 2024 Q4 10 2024-10-19 Sábado True 13 Tarde False
1 2024-10-19 13:53:25 2024 Q4 10 2024-10-19 Sábado True 13 Tarde False
2 2024-10-19 13:53:25 2024 Q4 10 2024-10-19 Sábado True 13 Tarde False
3 2024-10-19 13:53:41 2024 Q4 10 2024-10-19 Sábado True 13 Tarde False
4 2024-10-19 13:53:43 2024 Q4 10 2024-10-19 Sábado True 13 Tarde False

Features de Texto

Variáveis derivadas do conteúdo da mensagem.

Feature Tipo Definição Exemplo Uso
tamanho_caracteres int Quantidade de caracteres no conteúdo 157 Identificar mensagens curtas vs longas, padrões de verbosidade
tamanho_palavras int Quantidade de palavras (separadas por espaço) 24 Complexidade da mensagem, densidade de informação
tem_emoji bool True se a mensagem contém pelo menos um emoji True Identificar uso de expressões visuais, tom emocional
qtd_emojis int Quantidade total de emojis na mensagem 3 Intensidade do uso de emojis, padrões expressivos
tem_link bool True se contém URL (http:// ou https://) False Identificar compartilhamento de links, conteúdo externo
tem_mencao bool True se contém @ (menção/referência) False Detectar menções a outras pessoas ou serviços
tem_interrogacao bool True se a mensagem termina com ? True Identificar perguntas, busca por informação
tem_exclamacao bool True se a mensagem termina com ! False Detectar ênfase, entusiasmo ou urgência
eh_caixa_alta bool True se >70% das letras são maiúsculas (mínimo 3 letras) False Identificar mensagens enfáticas ou "gritadas"

Tamanho em Caracteres

# Conta caracteres no conteúdo
df['tamanho_caracteres'] = df['conteudo'].fillna('').str.len()

Tamanho em Palavras

# Conta palavras (split por espaço)
df['tamanho_palavras'] = df['conteudo'].fillna('').str.split().str.len().fillna(0).astype(int)

Detecção de Emoji

import re

# Padrão regex para detectar emojis (ranges Unicode comuns)
emoji_pattern = re.compile(
    "["
    "\U0001F600-\U0001F64F"  # emoticons
    "\U0001F300-\U0001F5FF"  # símbolos e pictogramas
    "\U0001F680-\U0001F6FF"  # transporte e mapas
    "\U0001F1E0-\U0001F1FF"  # bandeiras
    "\U00002600-\U000027BF"  # símbolos diversos
    "\U0001F900-\U0001F9FF"  # símbolos suplementares
    "\U0001FA00-\U0001FA6F"  # símbolos estendidos
    "]+",
    flags=re.UNICODE
)

# Detecta presença de emoji
df['tem_emoji'] = df['conteudo'].fillna('').apply(lambda x: bool(emoji_pattern.search(x)))

# Conta quantidade de emojis
df['qtd_emojis'] = df['conteudo'].fillna('').apply(lambda x: len(emoji_pattern.findall(x)))

Detecção de Link

Detecção de Menção

# Detecta @ (menções)
df['tem_mencao'] = df['conteudo'].fillna('').str.contains(r'(?:^|\s)@\w+', regex=True)

Detecção de Interrogação

# Detecta se termina com ?
df['tem_interrogacao'] = df['conteudo'].fillna('').str.strip().str.endswith('?')

Detecção de Exclamação

# Detecta se termina com !
df['tem_exclamacao'] = df['conteudo'].fillna('').str.strip().str.endswith('!')

Detecção de Caixa Alta

# Detecta mensagens em caixa alta (>70% maiúsculas, mínimo 3 letras)
def eh_caixa_alta(texto):
    if pd.isna(texto) or texto == '':
        return False

    # Extrai apenas letras
    letras = [c for c in texto if c.isalpha()]

    # Precisa ter no mínimo 3 letras
    if len(letras) < 3:
        return False

    # Calcula proporção de maiúsculas
    maiusculas = sum(1 for c in letras if c.isupper())
    proporcao = maiusculas / len(letras)

    return proporcao > 0.7

df['eh_caixa_alta'] = df['conteudo'].apply(eh_caixa_alta)

Validação

Feature Total Exemplo
tem_emoji 4910 👍🏻
tem_link 555 https://www.localiza.com/brasil/pt-br/reservas/passo-2
tem_mencao 3 @L371c1@
tem_interrogacao 4883 Le?
tem_exclamacao 2277 O jeep é show!
eh_caixa_alta 2209 HAHAHAH

Features de Conversação

Variáveis sobre o fluxo da conversa.

Feature Tipo Definição Exemplo Uso
tempo_desde_ultima_msg float Tempo em segundos desde a mensagem anterior (qualquer remetente) 127.5 Identificar ritmo da conversa, pausas, engajamento
tempo_desde_ultima_msg_mesmo_remetente float Tempo em segundos desde a última mensagem do MESMO remetente 450.0 Analisar padrão individual, frequência de envio por pessoa
eh_inicio_conversa bool True se gap desde última msg > 1 hora (3600 segundos) False Delimitar sessões de conversa, identificar retomadas
eh_resposta_rapida bool True se respondeu em < 60 segundos True Identificar engajamento imediato, urgência, flow da conversa
sequencia_mesmo_remetente int Contador de mensagens consecutivas do mesmo remetente 3 Identificar monólogos, rajadas de mensagens, padrão de interação
turno_conversa int ID incremental do bloco de conversa (reseta quando eh_inicio_conversa) 42 Agrupar mensagens de uma mesma sessão, análises por conversa
tempo_resposta_outro_remetente float Tempo em segundos até a próxima mensagem do OUTRO remetente 345.2 Medir velocidade de resposta, disponibilidade, reciprocidade

Tempo Desde Última Mensagem (Qualquer Remetente)

# Garante ordenação por timestamp
df = df.sort_values('timestamp').reset_index(drop=True)

# Calcula tempo desde a mensagem anterior (qualquer remetente)
df['tempo_desde_ultima_msg'] = df['timestamp'].diff().dt.total_seconds()

Tempo Desde Última Mensagem (Mesmo Remetente)

# Calcula tempo desde a última mensagem do mesmo remetente
df['tempo_desde_ultima_msg_mesmo_remetente'] = df.groupby('remetente')['timestamp'].diff().dt.total_seconds()

Início de Conversa

# Define threshold de início de conversa (1 hora)
THRESHOLD_INICIO_CONVERSA = 3600  # 1 hora em segundos

# Marca início de conversa (gap > 1 hora OU primeira mensagem)
df['eh_inicio_conversa'] = (df['tempo_desde_ultima_msg'] > THRESHOLD_INICIO_CONVERSA) | (df['tempo_desde_ultima_msg'].isna())

Resposta Rápida

# Define threshold de resposta rápida (60 segundos)
THRESHOLD_RESPOSTA_RAPIDA = 60

# Marca mensagens enviadas rapidamente (< 60 segundos)
df['eh_resposta_rapida'] = df['tempo_desde_ultima_msg'] < THRESHOLD_RESPOSTA_RAPIDA

Sequência Mesmo Remetente

# Conta quantas mensagens consecutivas do mesmo remetente
# Reinicia contagem quando muda de remetente
df['sequencia_mesmo_remetente'] = df.groupby((df['remetente'] != df['remetente'].shift()).cumsum()).cumcount() + 1

Turno de Conversa

# Cria ID de turno de conversa (incrementa a cada início)
df['turno_conversa'] = df['eh_inicio_conversa'].cumsum()

Tempo Resposta do Outro Remetente

# Versão vetorizada usando merge_asof (MUITO mais rápida)
def calcular_tempo_resposta_outro_vetorizado(df):
    """
    Usa merge_asof para encontrar próxima mensagem do outro remetente.
    Performance: ~10-100x mais rápido que loops.
    """
    resultado = pd.Series(index=df.index, dtype='float64')

    remetentes = df['remetente'].unique()

    for rem_atual in remetentes:
        # Mensagens deste remetente
        msgs_atual = df[df['remetente'] == rem_atual][['timestamp']].copy()
        msgs_atual = msgs_atual.reset_index()

        # Mensagens do outro remetente
        msgs_outro = df[df['remetente'] != rem_atual][['timestamp']].copy()
        msgs_outro['timestamp_outro'] = msgs_outro['timestamp']
        msgs_outro = msgs_outro[['timestamp_outro']].reset_index(drop=True)

        # Merge asof: para cada msg atual, encontra próxima do outro
        merged = pd.merge_asof(
            msgs_atual.sort_values('timestamp'),
            msgs_outro.sort_values('timestamp_outro'),
            left_on='timestamp',
            right_on='timestamp_outro',
            direction='forward'  # Próxima no futuro
        )

        # Calcula diferença de tempo
        merged['tempo_resposta'] = (
            merged['timestamp_outro'] - merged['timestamp']
        ).dt.total_seconds()

        # Atribui ao índice original
        resultado.loc[merged['index']] = merged['tempo_resposta'].values

    return resultado

df['tempo_resposta_outro_remetente'] = calcular_tempo_resposta_outro_vetorizado(df)

Validação

Feature Não-Nulos Mediana (seg)
tempo_desde_ultima_msg 91923 8.0
tempo_desde_ultima_msg_mesmo_remetente 91922 11.0
eh_inicio_conversa 1206 1206 inícios detectados
eh_resposta_rapida 74390 74390 respostas rápidas
sequencia_mesmo_remetente 91924 Máx: 54
turno_conversa 91924 1206 turnos totais
tempo_resposta_outro_remetente 91920 80.0

Features de Contexto Externo

Informações agregadas de fontes externas ao dataset de mensagens.

Encontros Presenciais

Dados sobre períodos em que estavam juntos fisicamente, enriquecendo a análise com contexto do relacionamento.

Feature Tipo Definição Exemplo Uso
em_encontro bool True se mensagem enviada durante encontro presencial True Comparar padrões de comunicação junto vs separado
encontro_id int ID do encontro (1-10) ou None se separados 3 Identificar período/viagem específica
encontro_nome str Nome descritivo do encontro BSB (Carnaval) Contexto narrativo do período
local_encontro str Cidade/local do encontro Brasília Análise por localização
tipo_encontro str Quem visitou quem (Marlon @ BSB ou Letícia @ SP) Marlon @ BSB Padrão de deslocamento
dias_desde_ultimo_encontro int Dias desde término do último encontro (None durante encontro) 15 Medir período de separação, saudade
dias_ate_proximo_encontro int Dias até início do próximo encontro (None durante encontro) 23 Medir expectativa, ansiedade pré-encontro
dia_do_encontro int Qual dia dentro do encontro (1, 2, 3...) ou None 5 Padrões dentro do período junto (início vs fim)
eh_marco_especial bool True se é data de marco especial True Identificar datas significativas
marco_nome str Descrição do marco especial 1a Vez Juntos Contexto emocional da data

Carregamento dos Dados

# Carrega dados de encontros presenciais
encontros = pd.read_csv(PATHS['external'] / 'encontros.csv')

# Converte datas para datetime
encontros['inicio'] = pd.to_datetime(encontros['inicio'])
encontros['fim'] = pd.to_datetime(encontros['fim'])
encontros['marco_especial'] = pd.to_datetime(encontros['marco_especial'])

# Extrai apenas a data (sem hora) das mensagens para matching
df['data_msg'] = df['timestamp'].dt.date

Features Básicas de Encontro

Em Encontro

# Identifica se mensagem foi enviada durante encontro
df['em_encontro'] = False
df['encontro_id'] = None
df['encontro_nome'] = None
df['local_encontro'] = None
df['tipo_encontro'] = None

for _, encontro in encontros.iterrows():
    mask = (df['data_msg'] >= encontro['inicio'].date()) & (df['data_msg'] <= encontro['fim'].date())
    df.loc[mask, 'em_encontro'] = True
    df.loc[mask, 'encontro_id'] = encontro['encontro_id']
    df.loc[mask, 'encontro_nome'] = encontro['nome']
    df.loc[mask, 'local_encontro'] = encontro['local']
    df.loc[mask, 'tipo_encontro'] = encontro['tipo_encontro']

Dia do Encontro

# Calcula qual dia do encontro (1, 2, 3...)
df['dia_do_encontro'] = None

for _, encontro in encontros.iterrows():
    mask = (df['data_msg'] >= encontro['inicio'].date()) & (df['data_msg'] <= encontro['fim'].date())
    df.loc[mask, 'dia_do_encontro'] = (
        pd.to_datetime(df.loc[mask, 'data_msg']) - encontro['inicio']
    ).dt.days + 1

Features de Marcos Especiais

Marco Especial

# Identifica mensagens em datas de marcos especiais
df['eh_marco_especial'] = False
df['marco_nome'] = None

for _, encontro in encontros.iterrows():
    if pd.notna(encontro['marco_especial']):
        mask = df['data_msg'] == encontro['marco_especial'].date()
        df.loc[mask, 'eh_marco_especial'] = True
        df.loc[mask, 'marco_nome'] = encontro['marco_nome']

Features de Distância Temporal

Dias Desde Último Encontro

# Calcula dias desde término do último encontro
df['dias_desde_ultimo_encontro'] = None

# Ordena encontros por data
encontros_ord = encontros.sort_values('inicio')

for idx, encontro in encontros_ord.iterrows():
    # Período APÓS este encontro até o próximo (ou fim do dataset)
    fim_encontro = encontro['fim'].date()

    # Encontra próximo encontro
    proximos = encontros_ord[encontros_ord['inicio'] > encontro['fim']]
    if len(proximos) > 0:
        inicio_proximo = proximos.iloc[0]['inicio'].date()
    else:
        inicio_proximo = df['data_msg'].max()

    # Mensagens no período de separação
    mask = (df['data_msg'] > fim_encontro) & (df['data_msg'] < inicio_proximo)
    df.loc[mask, 'dias_desde_ultimo_encontro'] = (
        pd.to_datetime(df.loc[mask, 'data_msg']) - pd.to_datetime(fim_encontro)
    ).dt.days

# Mensagens ANTES do primeiro encontro
primeiro_encontro = encontros_ord.iloc[0]['inicio'].date()
mask = df['data_msg'] < primeiro_encontro
df.loc[mask, 'dias_desde_ultimo_encontro'] = None  # Não tinha encontro anterior

Dias Até Próximo Encontro

# Calcula dias até início do próximo encontro
df['dias_ate_proximo_encontro'] = None

# Ordena encontros por data
encontros_ord = encontros.sort_values('inicio')

for idx, encontro in encontros_ord.iterrows():
    # Encontra período ANTES deste encontro (desde o anterior)
    inicio_encontro = encontro['inicio'].date()

    # Encontra encontro anterior
    anteriores = encontros_ord[encontros_ord['fim'] < encontro['inicio']]
    if len(anteriores) > 0:
        fim_anterior = anteriores.iloc[-1]['fim'].date()
    else:
        fim_anterior = df['data_msg'].min()

    # Mensagens no período de separação antes deste encontro
    mask = (df['data_msg'] > fim_anterior) & (df['data_msg'] < inicio_encontro)
    df.loc[mask, 'dias_ate_proximo_encontro'] = (
        pd.to_datetime(inicio_encontro) - pd.to_datetime(df.loc[mask, 'data_msg'])
    ).dt.days

# Mensagens APÓS o último encontro
ultimo_encontro = encontros_ord.iloc[-1]['fim'].date()
mask = df['data_msg'] > ultimo_encontro
df.loc[mask, 'dias_ate_proximo_encontro'] = None  # Não tem próximo encontro

Validação

Feature Total Exemplo
em_encontro 13,432 msgs durante encontros Chapada/BSB (1ª viagem)
dias_desde_ultimo_encontro 76,042 não-nulos Mediana: 18 dias
dias_ate_proximo_encontro 78,464 não-nulos Mediana: 16 dias
eh_marco_especial 114 msgs em marcos 💕 1ª Vez Juntos

Consolidação e Validação

Criação de Features Faltantes

Feature: mes

Feature 'mes' já existe no dataset.

Padronização de Nomenclatura (Opcional)

NoteDecisão - Manter Nomes Originais

As features de distância temporal existem como: - dias_desde_ultimo_encontro (em vez de dias_desde_ultimo) - dias_ate_proximo_encontro (em vez de dias_ate_proximo)

Decisão: Manter nomenclatura original (mais descritiva e clara).

Se preferir padronizar para nomes curtos:

# Descomente para renomear:
# df = df.rename(columns={
#     'dias_desde_ultimo_encontro': 'dias_desde_ultimo',
#     'dias_ate_proximo_encontro': 'dias_ate_proximo'
# })

Validação Final

DATASET CONSOLIDADO - Estatísticas Finais:

   Total de mensagens: 91,924
   Total de colunas: 43
   Período: 2024-10-19 -> 2025-10-20
   Memória: 36.6 MB

FEATURES CONSOLIDADAS POR CATEGORIA:

TEMPORAIS (4/6):
   timestamp                      (100.0% preenchido)
   hora                           (100.0% preenchido)
   dia_semana                     ( 27.2% preenchido)
   mes                            (100.0% preenchido)
IDENTIFICAÇÃO (3/3):
   remetente                      (100.0% preenchido)
   tipo_mensagem                  (100.0% preenchido)
   grupo_mensagem                 (100.0% preenchido)
CONTEÚDO (4/6):
   conteudo                       ( 74.7% preenchido)
   arquivo                        (  1.4% preenchido)
   transcricao                    (100.0% preenchido)
   tamanho_palavras               (100.0% preenchido)
CONTEXTO (5/6):
   em_encontro                    (100.0% preenchido)
   encontro_id                    ( 14.6% preenchido)
   encontro_nome                  ( 14.6% preenchido)
   dias_desde_ultimo_encontro     ( 82.7% preenchido)
   dias_ate_proximo_encontro      ( 85.4% preenchido)
INTERAÇÃO (0/3):
ELEMENTOS (1/5):
   tem_emoji                      (100.0% preenchido)
TÉCNICAS (1/1):
   date_match                     (100.0% preenchido)

Verificação Final

Dataset Enriquecido:
   • Mensagens: 91,924
   • Colunas originais: 8
   • Features criadas: 35
   • Total de colunas: 43

Todas as features foram criadas com sucesso!

Amostra do Dataset Enriquecido:
timestamp remetente conteudo ano mes dia_semana hora periodo_dia tamanho_caracteres tamanho_palavras tem_emoji tempo_desde_ultima_msg eh_inicio_conversa em_encontro encontro_nome
0 2024-10-19 13:52:51 P1 Le? 2024 10 Sábado 13 Tarde 3 1 False NaN True False None
1 2024-10-19 13:53:25 P2 https://www.localiza.com/brasil/pt-br/reservas... 2024 10 Sábado 13 Tarde 54 1 False 34.0 False False None
2 2024-10-19 13:53:25 P1 NaN 2024 10 Sábado 13 Tarde 0 0 False 0.0 False False None
3 2024-10-19 13:53:41 P2 NaN 2024 10 Sábado 13 Tarde 0 0 False 16.0 False False None
4 2024-10-19 13:53:43 P2 Oi 2024 10 Sábado 13 Tarde 2 1 False 2.0 False False None
5 2024-10-19 13:54:34 P1 NaN 2024 10 Sábado 13 Tarde 0 0 False 51.0 False False None
6 2024-10-19 13:56:05 P1 NaN 2024 10 Sábado 13 Tarde 0 0 False 91.0 False False None
7 2024-10-19 13:56:37 P2 Arrasou 2024 10 Sábado 13 Tarde 7 1 False 32.0 False False None
8 2024-10-19 13:56:49 P2 confortável 2024 10 Sábado 13 Tarde 11 1 False 12.0 False False None
9 2024-10-19 13:56:59 P2 esse vai de boas 2024 10 Sábado 13 Tarde 16 4 False 10.0 False False None

Exportação

Salvando dataset enriquecido com todas as features.

# Exporta dataset enriquecido
df.to_parquet(OUTPUT_FILE, index=False)

print(f"Dataset exportado com sucesso!")
print(f"Arquivo: {OUTPUT_FILE.name}")
print(f"Shape: {df.shape}")
print(f"Tamanho: {OUTPUT_FILE.stat().st_size / 1024 / 1024:.2f} MB")
Dataset exportado com sucesso!
Arquivo: messages_enriched.parquet
Shape: (91924, 43)
Tamanho: 2.56 MB

Resumo das Features

Catálogo completo de todas as features criadas neste notebook.

Categoria Feature Tipo Descrição
Temporal ano int Ano extraído do timestamp
Temporal trimestre category Trimestre (Q1-Q4)
Temporal mes int Mês numérico (1-12)
Temporal data date Data sem hora (YYYY-MM-DD)
Temporal dia_semana category Dia da semana (Segunda-Domingo)
Temporal fim_de_semana bool True se Sábado ou Domingo
Temporal hora int Hora do dia (0-23)
Temporal periodo_dia category Período (Madrugada, Manhã, Tarde, Noite)
Temporal horario_comercial bool True se Seg-Sex 08:00-18:00
Texto tamanho_caracteres int Quantidade de caracteres no conteúdo
Texto tamanho_palavras int Quantidade de palavras
Texto tem_emoji bool True se contém emoji
Texto qtd_emojis int Quantidade de emojis
Texto tem_link bool True se contém URL
Texto tem_mencao bool True se contém menção (@usuario)
Texto tem_interrogacao bool True se termina com ?
Texto tem_exclamacao bool True se termina com !
Texto eh_caixa_alta bool True se >70% maiúsculas
Conversação tempo_desde_ultima_msg float Segundos desde mensagem anterior
Conversação tempo_desde_ultima_msg_mesmo_remetente float Segundos desde última do mesmo remetente
Conversação eh_inicio_conversa bool True se gap > 1 hora
Conversação eh_resposta_rapida bool True se respondeu em < 60s
Conversação sequencia_mesmo_remetente int Contador de msgs consecutivas
Conversação turno_conversa int ID do bloco de conversa
Conversação tempo_resposta_outro_remetente float Segundos até resposta do outro
Contexto Externo em_encontro bool True se durante encontro presencial
Contexto Externo encontro_id int ID do encontro (1-10)
Contexto Externo encontro_nome str Nome descritivo do encontro
Contexto Externo local_encontro str Cidade/local do encontro
Contexto Externo tipo_encontro str Quem visitou quem
Contexto Externo dias_desde_ultimo_encontro int Dias desde término do último encontro
Contexto Externo dias_ate_proximo_encontro int Dias até início do próximo encontro
Contexto Externo dia_do_encontro int Dia dentro do encontro (1, 2, 3...)
Contexto Externo eh_marco_especial bool True se data de marco especial
Contexto Externo marco_nome str Descrição do marco especial

Estatísticas por Categoria:
Categoria Quantidade
Contexto Externo 10
Conversação 7
Temporal 9
Texto 9

Próximos Passos

Com o dataset enriquecido, seguimos para:

  1. Model-Based Features (opcional) — Features derivadas de ML
    • Análise de sentimento (BERT)
    • Embeddings semânticos
    • Topic modeling
  2. Exploratory Data Analysis — Análise exploratória das features criadas
    • Distribuições univariadas
    • Análises bivariadas (features vs remetente, tempo, contexto)
    • Correlações entre features
    • Visualizações temporais e padrões
    • Use messages_with_models.parquet se disponível
    • Ou messages_enriched.parquet para análise sem modelos