---
title: "Análise Exploratória Completa (EDA)"
subtitle: "Volume, tipos, contexto e sentimento integrados"
author: "Marlon"
date: today
format:
html:
toc: true
toc-depth: 3
theme: cosmo
code-fold: show
code-tools: true
execute:
warning: false
echo: true
freeze: true
---
# Introdução
Este notebook apresenta a **visão geral do dataset** com contexto externo integrado.
O dataset foi enriquecido com informações de encontros presenciais, permitindo análises que consideram a diferença entre comunicação quando **juntos** vs **separados**.
**Objetivo:** Visão panorâmica descritiva do dataset completo.
**Estrutura:**
- Estatísticas gerais
- Volume diário (com contexto)
- Distribuição por remetente
- Tipos de mensagem
- Análise de sentimento (DistilBERT vs RoBERTa)
------------------------------------------------------------------------
# Setup
## Bibliotecas
```{python}
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from pathlib import Path
import warnings
warnings.filterwarnings('ignore')
# Configurações de visualização
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', 100)
```
## Caminhos
```{python}
# Estrutura de diretórios
PROJECT_ROOT = Path.home() / 'Desktop' / 'whatsapp-interaction-analysis'
DATA_DIR = PROJECT_ROOT / 'data' / 'processed' / 'export_2024-10_2025-10'
# Arquivo de entrada (COM CONTEXTO INTEGRADO - do notebook 02)
INPUT_FILE = DATA_DIR / 'messages_with_context.parquet'
# Cores padronizadas
COLORS = {
'P1': '#636EFA',
'P2': '#EF553B',
'separados': '#636EFA',
'juntos': '#00CC96',
'TEXT': '#636EFA',
'AUDIO': '#EF553B',
'VID': '#00CC96',
'IMG': '#AB63FA'
}
print(f"📁 Diretório de dados: {DATA_DIR}")
print(f"📂 Arquivo de entrada: {INPUT_FILE.name}")
print(f"✅ Arquivo existe: {INPUT_FILE.exists()}")
```
------------------------------------------------------------------------
# Carregamento dos Dados
```{python}
# Carrega dataset do wrangling
df = pd.read_parquet(INPUT_FILE)
print(f"📊 Shape: {df.shape}")
print(f"📅 Período: {df['timestamp'].min().date()} → {df['timestamp'].max().date()}")
print(f"👥 Remetentes: {df['remetente'].unique().tolist()}")
```
## Estrutura do Dataset
```{python}
# Exibe informações sobre as colunas
df.info()
```
------------------------------------------------------------------------
# Preview dos Dados
```{python}
#| label: compute-preview-stats
#| output: false
# Estatísticas para o preview e insights
total_msgs = len(df)
# Período
periodo_inicio = df['timestamp'].min()
periodo_fim = df['timestamp'].max()
dias_total = (periodo_fim - periodo_inicio).days
msgs_por_dia_media = total_msgs / dias_total if dias_total > 0 else 0
# Participantes
participantes = None
p1_count = 0
p2_count = 0
p1_pct = 0
p2_pct = 0
mais_ativo = 'N/A'
mais_ativo_pct = 0
if 'remetente' in df.columns:
participantes = df['remetente'].value_counts()
p1_count = participantes.iloc[0] if len(participantes) > 0 else 0
p2_count = participantes.iloc[1] if len(participantes) > 1 else 0
p1_name = participantes.index[0] if len(participantes) > 0 else 'P1'
p2_name = participantes.index[1] if len(participantes) > 1 else 'P2'
p1_pct = (p1_count / total_msgs * 100) if total_msgs > 0 else 0
p2_pct = (p2_count / total_msgs * 100) if total_msgs > 0 else 0
mais_ativo = p1_name if p1_count > p2_count else p2_name
mais_ativo_pct = max(p1_pct, p2_pct)
# Tipo mais comum
tipo_mais_comum = 0
tipo_nome = 'N/A'
tipo_pct = 0
if 'tipo_mensagem' in df.columns:
tipo_mais_comum = df['tipo_mensagem'].value_counts().iloc[0]
tipo_nome = df['tipo_mensagem'].value_counts().index[0]
tipo_pct = (tipo_mais_comum / total_msgs * 100)
# Transcrições
com_transcricao = 0
taxa_transcricao = 0
if 'tem_transcricao' in df.columns:
com_transcricao = df['tem_transcricao'].sum()
taxa_transcricao = (com_transcricao / total_msgs * 100) if total_msgs > 0 else 0
```
**Resumo do dataset:**
- 📊 Total: `{python} f"{total_msgs:,}"` mensagens
- 📅 Período: `{python} periodo_inicio.strftime('%d/%m/%Y')` até `{python} periodo_fim.strftime('%d/%m/%Y')` (`{python} dias_total` dias)
- 📋 Colunas: `{python} len(df.columns)`
- 📈 Volume médio: `{python} f"{msgs_por_dia_media:.1f}"` mensagens/dia
- 💬 Tipo predominante: `{python} tipo_nome` (`{python} f"{tipo_pct:.1f}"`% das mensagens)
- 🎙️ Taxa de transcrição: `{python} f"{taxa_transcricao:.2f}"`% (`{python} f"{com_transcricao:,}"` de `{python} f"{total_msgs:,}"`)
**Primeiras mensagens:**
```{python}
#| label: preview-data
# Seleciona colunas para preview
preview_cols = ['timestamp', 'remetente', 'tipo_mensagem']
if 'conteudo_enriquecido' in df.columns:
preview_cols.append('conteudo_enriquecido')
elif 'conteudo' in df.columns:
preview_cols.append('conteudo')
df[preview_cols].head(10)
```
```{python}
#| label: compute-transcribed
#| output: false
df_trans = None
trans_count = 0
if 'tem_transcricao' in df.columns:
df_trans = df[df['tem_transcricao'] == True]
trans_count = len(df_trans)
```
```{python}
#| label: show-transcribed-header
#| output: asis
#| echo: false
if trans_count > 0:
print(f"🎙️ Exemplos de mensagens transcritas ({trans_count} total):")
```
```{python}
#| label: preview-transcribed
#| echo: false
if df_trans is not None and len(df_trans) > 0:
trans_preview_cols = ['timestamp', 'remetente']
if 'tipo_arquivo' in df_trans.columns:
trans_preview_cols.append('tipo_arquivo')
if 'conteudo_enriquecido' in df_trans.columns:
trans_preview_cols.append('conteudo_enriquecido')
display(df_trans[trans_preview_cols].head(5))
```
### Distribuição por Participante
```{python}
#| label: show-participant-insights
#| echo: false
if participantes is not None:
participantes_data = []
for rem, count in participantes.items():
pct = (count / total_msgs * 100)
participantes_data.append({
'Participante': rem,
'Mensagens': f"{count:,}",
'%': f"{pct:.1f}%"
})
df_part = pd.DataFrame(participantes_data)
display(df_part.style.hide(axis='index'))
```
👤 **Mais ativo:** `{python} mais_ativo` (`{python} f"{mais_ativo_pct:.1f}"`% das mensagens)
## Estatísticas Descritivas
```{python}
# Resumo estatístico
df.describe(include='all')
```
::: {.callout-warning}
### 💡 Observação
68.686 mensagens têm conteúdo textual (74.7%), o resto são mídias sem transcrição. Isso indica que análises de texto serão aplicáveis à maioria dos dados.
:::
------------------------------------------------------------------------
# Visão Geral — Análise de Volume
## Volume de Mensagens
```{python}
# Total de mensagens
total_msgs = len(df)
print(f"📊 Total de mensagens: {total_msgs:,}")
# Por fase
print(f"\n📊 Por fase do relacionamento:")
msgs_por_fase = df['fase_relacionamento'].value_counts()
for fase, count in msgs_por_fase.items():
pct = (count / total_msgs) * 100
print(f" {fase}: {count:,} ({pct:.1f}%)")
# Por remetente
print(f"\n👥 Por remetente:")
msgs_por_remetente = df['remetente'].value_counts()
for remetente, count in msgs_por_remetente.items():
pct = (count / total_msgs) * 100
print(f" {remetente}: {count:,} ({pct:.1f}%)")
# Por contexto
print(f"\n🏷️ Por contexto:")
msgs_por_contexto = df['em_encontro'].value_counts()
print(f" Separados: {msgs_por_contexto[False]:,} ({msgs_por_contexto[False]/total_msgs*100:.1f}%)")
print(f" Juntos: {msgs_por_contexto[True]:,} ({msgs_por_contexto[True]/total_msgs*100:.1f}%)")
```
## Distribuição por Remetente
```{python}
#| label: fig-msgs-remetente
#| fig-cap: "Distribuição de mensagens por remetente"
# Visualização
fig = px.bar(
x=msgs_por_remetente.index,
y=msgs_por_remetente.values,
labels={'x': 'Remetente', 'y': 'Quantidade de Mensagens'},
title='Volume de Mensagens por Remetente',
color=msgs_por_remetente.index,
color_discrete_map={'P1': COLORS['P1'], 'P2': COLORS['P2']},
text=msgs_por_remetente.values
)
fig.update_traces(texttemplate='%{text:,}', textposition='outside')
fig.update_layout(showlegend=False, height=400)
fig.show()
```
::: {.callout-note}
### 💡 Insight - Visão Geral do Dataset
**Período completo:** 367 dias (Out/2024 - Out/2025)
**Composição:**
- **97.2% Relacionamento** (89.336 msgs) vs 2.8% Amizade
- **85.4% Separados** (78.492 msgs) vs 14.6% Juntos (13.432 msgs)
- **51.1% P1** vs 48.9% P2 (quase simétrico)
**Descobertas:**
1. **Relacionamento domina:** Apenas 2.8% são de amizade - período pré-16/dez é marginal
2. **Comunicação é majoritariamente à distância:** 85.4% das mensagens acontecem quando separados, mesmo estando juntos 33% do tempo (122 dias)
3. **Simetria P1 × P2:** Diferença de apenas 2.2pp - participação equilibrada, baixo poder discriminatório para modelos
:::
## Volume Diário - Série Temporal
```{python}
#| label: fig-volume-diario
#| fig-cap: "Volume de mensagens ao longo do tempo"
# Agrega por dia
msgs_por_dia = df.groupby(df['timestamp'].dt.date).size().reset_index(name='count')
msgs_por_dia.columns = ['data', 'mensagens']
# Visualização
fig = px.line(
msgs_por_dia,
x='data',
y='mensagens',
title='Volume Diário de Mensagens',
labels={'data': 'Data', 'mensagens': 'Mensagens'}
)
fig.update_layout(height=400, hovermode='x')
fig.show()
# Estatísticas
print(f"\n📊 Estatísticas do volume diário:")
print(f" Média: {msgs_por_dia['mensagens'].mean():.1f} msgs/dia")
print(f" Mediana: {msgs_por_dia['mensagens'].median():.0f} msgs/dia")
print(f" Máximo: {msgs_por_dia['mensagens'].max():,} msgs/dia")
print(f" Mínimo: {msgs_por_dia['mensagens'].min()} msgs/dia")
```
::: {.callout-note}
### 💡 Insight - Volume Diário
**Padrão identificado no gráfico:**
1. **Vale em Setembro = Encontro de 2 meses** (BSB, Ago-Out/2025)
- Volume cai drasticamente (quase zero)
- Confirma visualmente: presença física = menos WhatsApp
2. **Picos em Jun-Jul = Separação máxima**
- Máximo: **1.288 msgs/dia** (4.5x acima da média!)
- Período de maior distância temporal entre encontros
3. **Crescimento Nov-Dez 2024 = Início do relacionamento**
- Volume cresce de ~200 para ~700 msgs/dia
- Marca transição amizade → relacionamento
4. **Variabilidade extrema:**
- Amplitude: 1 a 1.288 msgs/dia (1.288x!)
- Média (283) > Mediana (216) = distribuição assimétrica
- Picos puxam média para cima
**Conclusão:** Volume diário é **altamente variável** e modulado por contexto externo (encontros). Não há padrão temporal intrínseco - padrão é REATIVO à distância física.
:::
## Volume por Contexto
```{python}
#| label: fig-volume-contexto
#| fig-cap: "Volume médio: junto vs separado"
# Calcula médias por contexto
volume_contexto = df.groupby([df['timestamp'].dt.date, 'em_encontro']).size().reset_index(name='count')
media_por_contexto = volume_contexto.groupby('em_encontro')['count'].agg(['mean', 'median', 'std']).round(1)
media_por_contexto.index = ['Separados', 'Juntos']
print(f"\n📊 Volume por contexto:")
print(media_por_contexto)
# Visualização
fig = px.bar(
x=['Separados', 'Juntos'],
y=[media_por_contexto.loc['Separados', 'mean'], media_por_contexto.loc['Juntos', 'mean']],
labels={'x': 'Contexto', 'y': 'Média de Mensagens/Dia'},
title='Volume Médio Diário: Junto vs Separado',
color=['Separados', 'Juntos'],
color_discrete_map={'Separados': COLORS['separados'], 'Juntos': COLORS['juntos']},
text=[f"{media_por_contexto.loc['Separados', 'mean']:.0f}",
f"{media_por_contexto.loc['Juntos', 'mean']:.0f}"]
)
fig.update_traces(textposition='outside')
fig.update_layout(showlegend=False, height=400)
fig.show()
```
::: {.callout-important}
### 💡 Insight - Contexto Domina Volume
**Diferença DRAMÁTICA:**
- **Separados:** 372 msgs/dia (média)
- **Juntos:** 118 msgs/dia (média)
- **Redução:** -68.3% quando juntos
**Validação do notebook 02:**
- Confirma visualmente a queda de -68% (p<0.001)
- Presença física = WhatsApp vira coordenação (não conexão)
**Variabilidade:**
- Separados: Desvio 244 (alta oscilação - saudade varia)
- Juntos: Desvio 135 (menor oscilação - convívio estável)
- Mediana separados (346) < Média (372) = picos de saudade puxam média
**Conclusão:** `em_encontro` é a feature mais importante do dataset - explica 68% da variância no volume.
:::
---
## Síntese - Volume de Mensagens
::: {.callout-tip}
### 🎯 Conclusão - Padrões de Volume
**Dataset caracterizado por:**
- 91.924 mensagens em 367 dias (Out/2024 - Out/2025)
- Participação equilibrada: P1 (51.1%) vs P2 (48.9%)
- Dominância do relacionamento: 97.2% das mensagens (pós 16/dez)
**Descoberta CRÍTICA - Contexto domina tudo:**
| Métrica | Separados | Juntos | Diferença |
|---------|-----------|--------|-----------|
| **Volume médio** | 372 msgs/dia | 118 msgs/dia | **-68.3%** 🔥 |
| **Variabilidade** | Alta (σ=244) | Moderada (σ=135) | Mais estável juntos |
| **% do tempo** | 66.8% | 33.2% | Paradoxo: juntos 1/3 do tempo, mas apenas 14.6% msgs |
**Padrões temporais identificados:**
- ✅ Crescimento Nov-Dez/2024 = Início relacionamento (200→700 msgs/dia)
- ✅ Pico Jun-Jul/2025 = Separação máxima (1.288 msgs/dia)
- ✅ Vale Ago-Set/2025 = Encontro 2 meses (volume quase zero)
**Implicação para análises seguintes:**
> Volume NÃO é temporal intrínseco - é **REATIVO** à distância física. Feature `em_encontro` explica 68% da variância. Modelos sem contexto externo terão poder limitado.
:::
-----------------------------------------------------------------
# Distribuição de Tipos de Mensagem
## Por Tipo Específico
```{python}
# Contagem por tipo específico
tipo_counts = df['tipo_mensagem'].value_counts()
print(f"📋 Tipos de mensagem identificados: {len(tipo_counts)}")
print(f"\nTipos mais frequentes:")
print(tipo_counts)
```
```{python}
#| label: fig-tipos-especificos
#| fig-cap: "Tipos de mensagem mais frequentes"
# Visualização - Top 10
top_10_tipos = tipo_counts
fig = px.bar(
x=top_10_tipos.values,
y=top_10_tipos.index,
orientation='h',
labels={'x': 'Quantidade', 'y': 'Tipo de Mensagem'},
title='Top 10 Tipos de Mensagem',
text=top_10_tipos.values
)
fig.update_traces(texttemplate='%{text:,}', textposition='outside')
fig.update_layout(height=500, yaxis={'categoryorder': 'total ascending'})
fig.show()
```
---
## Por Grupo (Categoria)
```{python}
# Contagem por grupo
grupo_counts = df['grupo_mensagem'].value_counts()
print(f"📦 Grupos de mensagem: {len(grupo_counts)}")
print(f"\nDistribuição por grupo:")
for grupo, count in grupo_counts.items():
pct = (count / total_msgs) * 100
print(f" {grupo}: {count:,} ({pct:.1f}%)")
```
```{python}
#| label: fig-grupos-mensagem
#| fig-cap: "Distribuição por grupo de mensagem"
# Cores padronizadas por grupo
cores_grupos = [COLORS.get(grupo, '#95a5a6') for grupo in grupo_counts.index]
fig = px.pie(
names=grupo_counts.index,
values=grupo_counts.values,
title='Distribuição por Grupo de Mensagem',
color=grupo_counts.index,
color_discrete_map=COLORS,
hole=0.4
)
fig.update_traces(
textposition='inside',
textinfo='label+percent',
hovertemplate='<b>%{label}</b><br>Quantidade: %{value:,}<br>Percentual: %{percent}<extra></extra>'
)
fig.update_layout(height=500)
fig.show()
```
---
## Por Grupo × Remetente
```{python}
# Distribuição por grupo e remetente
grupo_remetente = df.groupby(['grupo_mensagem', 'remetente']).size().unstack(fill_value=0)
print(f"📊 Distribuição por grupo e remetente:\n")
print(grupo_remetente)
# Percentuais
grupo_remetente_pct = grupo_remetente.div(grupo_remetente.sum(axis=1), axis=0) * 100
print(f"\n📊 Percentuais (por grupo):\n")
print(grupo_remetente_pct.round(1))
```
```{python}
#| label: fig-grupos-remetente
#| fig-cap: "Distribuição de grupos por remetente"
# Prepara dados
dados_plot = grupo_remetente.reset_index().melt(
id_vars='grupo_mensagem',
var_name='remetente',
value_name='count'
)
fig = px.bar(
dados_plot,
x='grupo_mensagem',
y='count',
color='remetente',
barmode='group',
labels={'grupo_mensagem': 'Grupo', 'count': 'Quantidade', 'remetente': 'Remetente'},
title='Distribuição de Grupos por Remetente',
color_discrete_map={'P1': COLORS['P1'], 'P2': COLORS['P2']},
text='count'
)
fig.update_traces(texttemplate='%{text:,}', textposition='outside')
fig.update_layout(height=400)
fig.show()
```
---
## Por Grupo × Contexto
```{python}
# Distribuição por grupo e contexto
grupo_contexto = df.groupby(['grupo_mensagem', 'em_encontro']).size().unstack(fill_value=0)
grupo_contexto.columns = ['Separados', 'Juntos']
print(f"📊 Distribuição por grupo e contexto:\n")
print(grupo_contexto)
# Percentuais (dentro de cada grupo)
grupo_contexto_pct = grupo_contexto.div(grupo_contexto.sum(axis=1), axis=0) * 100
print(f"\n📊 Percentuais (por grupo):\n")
print(grupo_contexto_pct.round(1))
```
```{python}
#| label: fig-grupos-contexto
#| fig-cap: "Distribuição de grupos por contexto"
# Prepara dados
dados_plot = grupo_contexto.reset_index().melt(
id_vars='grupo_mensagem',
var_name='contexto',
value_name='count'
)
fig = px.bar(
dados_plot,
x='grupo_mensagem',
y='count',
color='contexto',
barmode='group',
labels={'grupo_mensagem': 'Grupo', 'count': 'Quantidade', 'contexto': 'Contexto'},
title='Distribuição de Grupos por Contexto (Junto vs Separado)',
color_discrete_map={'Separados': COLORS['separados'], 'Juntos': COLORS['juntos']},
text='count'
)
fig.update_traces(texttemplate='%{text:,}', textposition='outside')
fig.update_layout(height=400)
fig.show()
```
---
## Síntese - Tipos de Mensagem
::: {.callout-note}
#### 💡 Insight - Tipos de Mensagem
**Composição geral (dataset completo):**
- **TEXT domina:** 73.2% (67.297 msgs) - comunicação objetiva e rápida
- **VID:** 10.4% (9.602 msgs) - mídia rica
- **AUDIO:** 9.3% (8.552 msgs) - expressividade e afeto
- **IMG:** 3.9% (3.585 msgs) - compartilhamento visual
- **Outros:** 3.2% (stickers, calls, docs, etc)
**Concentração:** Top 3 tipos (TEXT, VID, AUDIO) = 92.9% das mensagens
---
**Por remetente - Assimetrias identificadas:**
| Grupo | P1 | P2 | Diferença | Interpretação |
|-------|----|----|-----------|---------------|
| **TEXT** | 49.4% | 50.6% | +1.2pp | Equilibrado |
| **AUDIO** | **62.4%** | 37.6% | **+24.8pp** 🔥 | P1 prefere áudio! |
| **VID** | 51.5% | 48.5% | +3.0pp | Equilibrado |
| **IMG** | 54.8% | 45.2% | +9.6pp | P1 levemente mais visual |
**Descoberta:** P1 usa **62.4%** de todos os áudios (quase 2/3!) - preferência clara por voz vs P2 que prefere texto.
---
**Por contexto - MUDANÇA DRAMÁTICA:**
| Grupo | Separados | Juntos | Interpretação |
|-------|-----------|--------|---------------|
| **VID** | **97.2%** | **2.8%** | **VID some quando juntos!** 🔥 |
| **AUDIO** | 88.7% | 11.3% | Cai drasticamente |
| **TEXT** | 83.6% | 16.4% | Mais equilibrado |
| **IMG** | 81.7% | 18.3% | Cai moderadamente |
**Descobertas CRÍTICAS:**
1. **VID é mídia de SEPARAÇÃO pura:**
- 97.2% quando separados (compensa distância visual)
- Apenas 2.8% quando juntos (presença física > vídeo)
- **Redução de 34.6x no uso!**
2. **AUDIO também é ferramenta de distância:**
- 88.7% quando separados (ouvir a voz conecta)
- 11.3% quando juntos (convívio substitui)
3. **TEXT é mais estável:**
- Proporção mais equilibrada (83.6% vs 16.4%)
- Funciona em ambos contextos (coordenação universal)
**Conclusão:** Presença física não só reduz VOLUME (-68%), mas também muda **TIPO** de mídia. Relacionamento tem dois "idiomas": mídia rica quando separados (VID/AUDIO para conexão) vs texto quando juntos (coordenação rápida).
:::
------------------------------------------------------------------------
# Análise de Sentimento
## A Subjetividade do Sentimento
Testamos dois modelos de IA para classificar o sentimento das mensagens. O resultado? **Eles concordam em apenas 37% dos casos!**
Isso revela algo importante: **não existe interpretação "objetiva" de sentimento.**
- Um modelo vê neutralidade onde o outro vê positividade
- Mensagens ambíguas podem ser interpretadas de formas opostas
- Assim como pessoas diferentes interpretam conversas diferentemente
**Qual usar?** Depende da pergunta:
- "Como estavam as conversas objetivamente?" → RoBERTa
- "Qual o tom emocional predominante?" → DistilBERT
## Análise Comparativa: DistilBERT vs RoBERTa
Distribuições completamente diferentes:
| Métrica | RoBERTa | DistilBERT | Diferença |
|---------|---------|------------|-----------|
| Positive | 28.3% | 62.1% | +33.8% 🟢⬆️ |
| Neutral | 48.9% | 5.0% | -43.9% ⚪⬇️ |
| Negative | 22.8% | 32.9% | +10.1% 🔴⬆️ |
**Concordância: APENAS 37%!** Isso significa que em 63% das mensagens, os modelos discordam!
## O Que Está Acontecendo?
### 1. Viés dos Modelos Muito Diferente
**RoBERTa: Conservador e cauteloso**
- Quando não tem certeza → classifica como neutral
- 49% das mensagens são neutras
- Confiança média: 0.672 (alta)
**DistilBERT: Polarizador e ousado**
- Quando não tem certeza → escolhe positivo ou negativo
- Apenas 5% neutras (quase ignora essa classe!)
- Confiança média: 0.544 (mais baixa)
### 2. Por Que Isso Acontece?
**Treinamento Diferente:**
- RoBERTa: Tweets (ambiente neutro/informativo comum)
- DistilBERT: Destilado de BERT (pode ter viés do dataset)
**Filosofia:**
- RoBERTa: "Se não tenho certeza, é neutro"
- DistilBERT: "Se não tenho certeza, escolho um lado"
### 3. Qual Está "Certo"?
Nenhum e ambos! Depende do que você quer:
**Se quer análise conservadora:** ✅ Use RoBERTa
- Mais confiança
- Não "inventa" sentimento onde não tem
- Bom para análise objetiva
**Se quer análise polarizada:** ✅ Use DistilBERT
- Captura nuances sutis
- Força uma interpretação
- Bom para identificar tendências
### Exemplos Reveladores
Exemplos do DistilBERT classificados como positivo:
- "https://www.localiza.com/brasil..." → positive (0.540)
- "Arrasou" → positive (0.551)
- "confortável" → positive (0.915) ← Alta confiança!
RoBERTa provavelmente classificou URL como neutral (mais sensato).
E como neutral no DistilBERT:
- "Total tá, não por pessoa" → neutral (0.507)
- "Ai não rola kkkkkk" → neutral (0.569)
RoBERTa pode ter visto "kkkkkk" como positivo!
## Código para Análise de Discordância
```{python}
#| label: sentiment-discordance
#| echo: true
# Mensagens onde modelos discordam
if 'sentimento_roberta_label' in df.columns and 'sentimento_distilbert_label' in df.columns:
df_discordantes = df[
(df['sentimento_roberta_label'] != df['sentimento_distilbert_label']) &
df['sentimento_roberta_label'].notna() &
df['sentimento_distilbert_label'].notna()
]
print(f"Mensagens discordantes: {len(df_discordantes):,} ({len(df_discordantes)/len(df)*100:.1f}%)")
# Padrões de discordância
from collections import Counter
padroes = Counter(
zip(df_discordantes['sentimento_roberta_label'], df_discordantes['sentimento_distilbert_label'])
)
print("\nPadrões de discordância:")
for (roberta, distilbert), count in padroes.most_common():
print(f" RoBERTa: {roberta:8s} → DistilBERT: {distilbert:8s} : {count:,} casos")
else:
print("⚠️ Colunas de sentimento não encontradas no dataset.")
print(" Execute o pipeline de sentimento antes desta análise.")
```
## Recomendação: RoBERTa 🏆
**Por quê:**
- ✅ Maior confiança (0.67 vs 0.54)
- ✅ Mais equilibrado (não ignora neutral)
- ✅ Treinado em texto curto (tweets = WhatsApp)
- ✅ Resultados mais "crível" (62% positivo parece alto demais)
**Mas mantenha DistilBERT para:**
- Comparações e discussões
- Mostrar a subjetividade
- Identificar mensagens polarizadas
✅ No EDA, use `sentimento_roberta_label` como principal.
✅ Crie uma seção de comparação mostrando a discordância.
------------------------------------------------------------------------
# Síntese e Conclusões
## Resumo - Contexto Externo (Notebook 02)
::: {.callout-note}
### 📌 Descobertas do Notebook Anterior
**Contexto externo integrado:**
- 10 encontros presenciais (122 dias juntos = 33.2% do tempo)
- 2 períodos distintos: Amizade (2.8%) vs Relacionamento (97.2%)
- Zero dias sem falar quando separados (100% gaps = encontros)
**Impacto do contexto validado:**
- Volume: -68.3% quando juntos (372 → 118 msgs/dia)
- Timing: Noturno (19h) → Diurno (12h)
- Semanal: Uniforme → Segunda explode (26.8%), fim de semana some (3%)
**Conclusão:** `em_encontro` é switch de protocolo - muda volume, timing e comportamento.
:::
---
## Síntese Final - Overview do Dataset
::: {.callout-important}
### 🎯 Conclusão Geral - Características do Dataset
**Estrutura do dataset:**
- 91.924 mensagens em 367 dias
- Participação equilibrada: P1 (51.1%) vs P2 (48.9%)
- Dominância do relacionamento: 97.2% pós 16/dez
---
**VOLUME - Reativo ao contexto:**
- Separados: 372 msgs/dia (ALTO, canal principal)
- Juntos: 118 msgs/dia (BAIXO, -68%, coordenação)
- Padrão temporal: picos = separação, vales = encontros
---
**TIPOS - Dois "idiomas" de comunicação:**
**Quando SEPARADOS (mídia rica para conexão):**
- VID: 97.2% de uso (compensa distância visual)
- AUDIO: 88.7% de uso (ouvir a voz conecta)
- Preferência: P1 usa 62.4% dos áudios (voz > texto)
**Quando JUNTOS (texto para coordenação):**
- TEXT: Mais estável (16.4% das mensagens)
- VID: Redução de 34.6x (2.8% apenas!)
- AUDIO: Cai para 11.3%
---
**SENTIMENTO - Subjetividade revelada:**
- RoBERTa vs DistilBERT concordam em apenas 37%
- RoBERTa recomendado (conservador, mais confiável)
- Sentimento é complementar, não substituto de análise contextual
---
**Descoberta central:**
> Relacionamento à distância opera em **DOIS MODOS DISTINTOS:**
>
> 1. **SEPARADOS:** WhatsApp = conexão emocional (volume alto, mídia rica, noturno)
> 2. **JUNTOS:** WhatsApp = coordenação prática (volume baixo, texto, diurno)
:::
------------------------------------------------------------------------
# Próximos Passos
Com a exploração inicial completa, seguimos para:
1. [**Análise Avançada**](06-advanced-analysis.qmd) — Técnicas avançadas
- Clustering usando embeddings
- Análise temporal de sentimento
- Cluster, PCA, MCA
- Modelagem preditiva