¿Qué es el Análisis de Sentimiento?
El análisis de sentimiento (sentiment analysis) o minería de opiniones es una técnica de NLP (Natural Language Processing) que identifica y extrae información subjetiva de textos, clasificándolos típicamente como:
- Positivo: "Este producto es increíble, lo recomiendo 100%"
- Negativo: "Pésima experiencia, nunca más compro aquí"
- Neutral: "Llegó el pedido hoy"
Más Allá de Positivo/Negativo
El análisis moderno va más profundo:
Análisis de emociones específicas:
- Alegría, tristeza, enojo, miedo, sorpresa
- "Estoy frustrado con el servicio al cliente" → Enojo
- "Wow, no esperaba que funcionara tan bien" → Sorpresa positiva
Análisis de aspectos:
"La comida era deliciosa pero el servicio muy lento"
Aspectos identificados:
- Comida: Positivo
- Servicio: Negativo
Detección de intención:
- Queja
- Pregunta
- Recomendación
- Solicitud de ayuda
Por Qué es Importante para Redes Sociales
Escala Imposible de Manejar Manualmente
Ejemplo real:
Marca global promedio:
- 50,000 menciones/mes
- Para leer todas: 416 horas/mes (10.4 semanas de trabajo)
- Con sentiment analysis: 2 horas de revisión de casos prioritarios
Insights Accionables
Caso: Lanzamiento de producto
Análisis manual: "Parece que les gusta"
Análisis de sentimiento:
{
'positivo': 72%,
'negativo': 18%,
'neutral': 10%,
'emociones_top': ['excitement': 45%, 'surprise': 22%, 'skepticism': 15%],
'aspectos_negativos': [
'precio': 40%,
'disponibilidad': 35%,
'compatibilidad': 25%
]
}
Acción: Campaña explicando valor vs precio, expandir disponibilidad
Técnicas de Análisis de Sentimiento
1. Basado en Lexicon (Dictionary-Based)
Usa diccionarios de palabras con polaridad pre-asignada.
VADER (Valence Aware Dictionary and sEntiment Reasoner)
Optimizado para redes sociales, entiende:
- Mayúsculas ("BUENO" más positivo que "bueno")
- Emojis ("😍" = muy positivo)
- Puntuación ("!!!" aumenta intensidad)
- Negaciones ("no es malo" ≠ "malo")
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
analyzer = SentimentIntensityAnalyzer()
# Ejemplos
textos = [
"Me encanta este producto!!! 😍",
"No está mal",
"ODIO este servicio",
"El envío fue rápido, pero el producto llegó dañado"
]
for texto in textos:
scores = analyzer.polarity_scores(texto)
print(f"\nTexto: {texto}")
print(f"Compound score: {scores['compound']}")
print(f"Pos: {scores['pos']}, Neg: {scores['neg']}, Neu: {scores['neu']}")
# Clasificación
if scores['compound'] >= 0.05:
sentimiento = "POSITIVO"
elif scores['compound'] <= -0.05:
sentimiento = "NEGATIVO"
else:
sentimiento = "NEUTRAL"
print(f"Sentimiento: {sentimiento}")
Output:
Texto: Me encanta este producto!!! 😍
Compound score: 0.8807
Pos: 0.574, Neg: 0.0, Neu: 0.426
Sentimiento: POSITIVO
Texto: No está mal
Compound score: 0.2263
Pos: 0.385, Neg: 0.0, Neu: 0.615
Sentimiento: POSITIVO
Texto: ODIO este servicio
Compound score: -0.7096
Pos: 0.0, Neg: 0.662, Neu: 0.338
Sentimiento: NEGATIVO
Ventaja: Rápido, sin necesidad de entrenamiento, interpretable Desventaja: No captura contexto complejo, sarcasmo limitado
2. Machine Learning Tradicional
Entrenar clasificadores con features extraídos de texto.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import pandas as pd
# 1. Cargar datos etiquetados
# Formato: texto, sentimiento (positivo/negativo/neutral)
data = pd.read_csv('tweets_etiquetados.csv')
X = data['texto']
y = data['sentimiento']
# 2. Split datos
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 3. Vectorización TF-IDF
vectorizer = TfidfVectorizer(
max_features=5000,
ngram_range=(1, 2), # Unigramas y bigramas
min_df=2,
max_df=0.8
)
X_train_vec = vectorizer.fit_transform(X_train)
X_test_vec = vectorizer.transform(X_test)
# 4. Entrenar modelo
modelo = LogisticRegression(
max_iter=1000,
class_weight='balanced', # Para clases desbalanceadas
random_state=42
)
modelo.fit(X_train_vec, y_train)
# 5. Evaluar
y_pred = modelo.predict(X_test_vec)
print(classification_report(y_test, y_pred))
# 6. Función de predicción
def predecir_sentimiento(texto):
texto_vec = vectorizer.transform([texto])
sentimiento = modelo.predict(texto_vec)[0]
probabilidades = modelo.predict_proba(texto_vec)[0]
return {
'sentimiento': sentimiento,
'confianza': max(probabilidades),
'probabilidades': dict(zip(modelo.classes_, probabilidades))
}
# Ejemplo
resultado = predecir_sentimiento("Este producto superó mis expectativas")
print(resultado)
# {'sentimiento': 'positivo', 'confianza': 0.89, 'probabilidades': {...}}
3. Deep Learning (Transformers)
Modelos pre-entrenados usando arquitecturas transformer.
from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
import torch
# Modelo en español optimizado para redes sociales
modelo_nombre = "finiteautomata/beto-sentiment-analysis"
tokenizer = AutoTokenizer.from_pretrained(modelo_nombre)
modelo = AutoModelForSequenceClassification.from_pretrained(modelo_nombre)
sentiment_pipeline = pipeline(
"sentiment-analysis",
model=modelo,
tokenizer=tokenizer
)
# Analizar tweets
tweets = [
"Acabo de probar la nueva app y está brutal! 🔥",
"Llevo 2 horas esperando soporte, esto es inaceptable",
"Hoy salió el sol"
]
for tweet in tweets:
resultado = sentiment_pipeline(tweet)[0]
print(f"\nTweet: {tweet}")
print(f"Sentimiento: {resultado['label']}")
print(f"Confianza: {resultado['score']:.2%}")
Modelos recomendados:
| Idioma | Modelo | Casos de uso |
|---|---|---|
| Español | finiteautomata/beto-sentiment-analysis |
Redes sociales generales |
| Inglés | cardiffnlp/twitter-roberta-base-sentiment |
Twitter específicamente |
| Multilingüe | nlptown/bert-base-multilingual-uncased-sentiment |
Múltiples idiomas |
Ventaja: Mayor precisión, entiende contexto complejo Desventaja: Requiere más recursos computacionales
4. Análisis con LLMs (GPT-4, Claude)
import openai
def analizar_sentimiento_llm(texto, analizar_aspectos=True):
prompt = f"""Analiza el sentimiento del siguiente texto de redes sociales.
Texto: "{texto}"
Proporciona:
1. Sentimiento general (Positivo/Negativo/Neutral)
2. Intensidad (1-10)
3. Emoción principal
4. Aspectos mencionados y su sentimiento (si aplica)
Formato JSON:
```json
{{
"sentimiento_general": "",
"intensidad": 0,
"emocion_principal": "",
"aspectos": []
}}
```"""
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[
{"role": "system", "content": "Eres un experto en análisis de sentimiento para redes sociales."},
{"role": "user", "content": prompt}
],
temperature=0
)
import json
resultado = json.loads(response.choices[0].message.content)
return resultado
# Ejemplo
texto_complejo = """La comida del restaurante es deliciosa y el ambiente perfecto,
pero el servicio es lentísimo. Llevamos 40 minutos esperando la cuenta.
No vuelvo aunque la comida sea buena."""
analisis = analizar_sentimiento_llm(texto_complejo)
print(json.dumps(analisis, indent=2, ensure_ascii=False))
Output esperado:
{
"sentimiento_general": "Negativo",
"intensidad": 7,
"emocion_principal": "Frustración",
"aspectos": [
{"aspecto": "comida", "sentimiento": "Positivo", "intensidad": 9},
{"aspecto": "ambiente", "sentimiento": "Positivo", "intensidad": 8},
{"aspecto": "servicio", "sentimiento": "Negativo", "intensidad": 9}
]
}
Pipeline Completo de Análisis
Sistema de Producción
import tweepy
import pandas as pd
from datetime import datetime, timedelta
import schedule
import time
class SentimentAnalysisPipeline:
def __init__(self, keywords, sentiment_analyzer):
self.keywords = keywords
self.sentiment_analyzer = sentiment_analyzer
self.resultados = []
# Configurar API de Twitter
self.twitter_api = self.setup_twitter_api()
def setup_twitter_api(self):
"""Configurar Tweepy"""
auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
return tweepy.API(auth)
def recolectar_tweets(self, dias=1):
"""Recolectar tweets recientes"""
fecha_inicio = datetime.now() - timedelta(days=dias)
tweets = []
for keyword in self.keywords:
try:
resultados = tweepy.Cursor(
self.twitter_api.search_tweets,
q=keyword,
lang="es",
since=fecha_inicio.strftime("%Y-%m-%d"),
tweet_mode="extended"
).items(1000)
for tweet in resultados:
tweets.append({
'id': tweet.id,
'texto': tweet.full_text,
'autor': tweet.user.screen_name,
'fecha': tweet.created_at,
'retweets': tweet.retweet_count,
'likes': tweet.favorite_count,
'keyword': keyword
})
except Exception as e:
print(f"Error recolectando {keyword}: {e}")
return pd.DataFrame(tweets)
def preprocesar_texto(self, texto):
"""Limpieza de texto"""
# Remover URLs
texto = re.sub(r'http\S+|www\S+|https\S+', '', texto, flags=re.MULTILINE)
# Remover menciones
texto = re.sub(r'@\w+', '', texto)
# Remover hashtags (opcional, depende del análisis)
# texto = re.sub(r'#\w+', '', texto)
# Remover caracteres especiales excesivos
texto = re.sub(r'[^\w\s#@!?.,]', '', texto)
return texto.strip()
def analizar_sentimientos(self, df):
"""Aplicar análisis de sentimiento"""
df['texto_limpio'] = df['texto'].apply(self.preprocesar_texto)
sentimientos = []
for texto in df['texto_limpio']:
sent = self.sentiment_analyzer(texto)
sentimientos.append(sent)
df['sentimiento'] = [s['sentimiento'] for s in sentimientos]
df['confianza'] = [s['confianza'] for s in sentimientos]
return df
def calcular_metricas(self, df):
"""Calcular métricas agregadas"""
metricas = {
'total_menciones': len(df),
'distribucion_sentimiento': df['sentimiento'].value_counts().to_dict(),
'sentimiento_porcentaje': (df['sentimiento'].value_counts(normalize=True) * 100).to_dict(),
'net_sentiment': (
df[df['sentimiento'] == 'positivo'].shape[0] -
df[df['sentimiento'] == 'negativo'].shape[0]
) / len(df) * 100 if len(df) > 0 else 0,
'engagement_promedio': {
'retweets': df['retweets'].mean(),
'likes': df['likes'].mean()
},
'top_autores': df.groupby('autor').size().nlargest(10).to_dict(),
'fecha_analisis': datetime.now().isoformat()
}
# Sentimiento por keyword
metricas['sentimiento_por_keyword'] = {}
for keyword in self.keywords:
df_kw = df[df['keyword'] == keyword]
if len(df_kw) > 0:
metricas['sentimiento_por_keyword'][keyword] = {
'positivo': (df_kw['sentimiento'] == 'positivo').sum(),
'negativo': (df_kw['sentimiento'] == 'negativo').sum(),
'neutral': (df_kw['sentimiento'] == 'neutral').sum()
}
return metricas
def detectar_alertas(self, df):
"""Detectar situaciones que requieren atención"""
alertas = []
# 1. Pico de sentimiento negativo
pct_negativo = (df['sentimiento'] == 'negativo').mean() * 100
if pct_negativo > 30:
alertas.append({
'tipo': 'sentimiento_negativo_alto',
'severidad': 'alta' if pct_negativo > 50 else 'media',
'mensaje': f'{pct_negativo:.1f}% de menciones son negativas',
'tweets_muestra': df[df['sentimiento'] == 'negativo'].head(5).to_dict('records')
})
# 2. Influencer negativo
df_negativo = df[df['sentimiento'] == 'negativo']
if not df_negativo.empty:
autor_top_negativo = df_negativo.groupby('autor')['retweets'].sum().idxmax()
retweets_negativos = df_negativo.groupby('autor')['retweets'].sum().max()
if retweets_negativos > 100:
alertas.append({
'tipo': 'influencer_negativo',
'severidad': 'alta',
'mensaje': f'@{autor_top_negativo} con {retweets_negativos} RTs negativos',
'autor': autor_top_negativo
})
# 3. Cambio súbito en volumen
# (requiere histórico para comparar)
return alertas
def generar_reporte(self, df, metricas, alertas):
"""Generar reporte HTML"""
html = f"""
<html>
<head><title>Reporte de Sentimiento</title></head>
<body>
<h1>Reporte de Análisis de Sentimiento</h1>
<p>Fecha: {metricas['fecha_analisis']}</p>
<h2>Resumen</h2>
<ul>
<li>Total menciones: {metricas['total_menciones']}</li>
<li>Positivo: {metricas['sentimiento_porcentaje'].get('positivo', 0):.1f}%</li>
<li>Negativo: {metricas['sentimiento_porcentaje'].get('negativo', 0):.1f}%</li>
<li>Neutral: {metricas['sentimiento_porcentaje'].get('neutral', 0):.1f}%</li>
<li>Net Sentiment: {metricas['net_sentiment']:.1f}</li>
</ul>
<h2>Alertas ({len(alertas)})</h2>
<ul>
{"".join([f"<li><strong>{a['severidad'].upper()}</strong>: {a['mensaje']}</li>" for a in alertas])}
</ul>
<h2>Top 10 Tweets Negativos</h2>
<ul>
{"".join([f"<li>@{t['autor']}: {t['texto'][:100]}... ({t['likes']} likes, {t['retweets']} RTs)</li>"
for t in df[df['sentimiento'] == 'negativo'].nlargest(10, 'retweets').to_dict('records')])}
</ul>
</body>
</html>
"""
with open(f"reporte_sentimiento_{datetime.now().strftime('%Y%m%d')}.html", "w") as f:
f.write(html)
def ejecutar_analisis(self):
"""Pipeline completo"""
print(f"[{datetime.now()}] Iniciando análisis...")
# 1. Recolectar
df = self.recolectar_tweets(dias=1)
print(f"Recolectados {len(df)} tweets")
if len(df) == 0:
print("No se encontraron tweets")
return
# 2. Analizar
df = self.analizar_sentimientos(df)
# 3. Calcular métricas
metricas = self.calcular_metricas(df)
# 4. Detectar alertas
alertas = self.detectar_alertas(df)
# 5. Generar reporte
self.generar_reporte(df, metricas, alertas)
# 6. Enviar alertas si hay
if alertas:
self.enviar_notificaciones(alertas)
print(f"[{datetime.now()}] Análisis completado")
return {'metricas': metricas, 'alertas': alertas}
# Uso
keywords = ["@MiMarca", "MiProducto", "#MiHashtag"]
pipeline = SentimentAnalysisPipeline(keywords, sentiment_analyzer=predecir_sentimiento)
# Ejecutar una vez
resultado = pipeline.ejecutar_analisis()
# O programar ejecución diaria
schedule.every().day.at("09:00").do(pipeline.ejecutar_analisis)
while True:
schedule.run_pending()
time.sleep(60)
Casos de Estudio Reales
Caso 1: Lanzamiento de Producto
Empresa: Startup de fitness tech Situación: Lanzan smartwatch, quieren medir recepción
Análisis:
# Periodo: 7 días post-lanzamiento
# Total menciones: 8,450
Sentimiento general:
- Positivo: 68%
- Neutral: 22%
- Negativo: 10%
Top emociones positivas:
- Excitement: 45%
- Satisfaction: 35%
- Trust: 20%
Aspectos negativos más mencionados:
- "batería dura poco" (35% de negativos)
- "app tiene bugs" (28%)
- "precio alto" (22%)
Acciones tomadas:
- Update de software para optimizar batería (semana 2)
- Campaña explicando valor vs precio
- Priorizar fixes de app en roadmap
Resultado: Sentimiento negativo bajó a 6% en mes 2
Caso 2: Crisis de PR
Empresa: Cadena de restaurantes Situación: Video viral de condiciones insalubres
Timeline con sentiment analysis:
Día 1, 10:00 AM - Video publicado
Menciones: 50
Sentimiento: 90% negativo
Día 1, 2:00 PM - Sin respuesta oficial
Menciones: 2,500
Sentimiento: 95% negativo
Trending topic #2 nacional
Día 1, 4:00 PM - Comunicado oficial
Menciones: 5,000
Sentimiento: 85% negativo (mejora leve)
Día 2 - Acciones concretas compartidas
Menciones: 3,200
Sentimiento: 70% negativo
Día 7 - Follow-up con medidas implementadas
Menciones: 800
Sentimiento: 55% negativo
Lección: Análisis de sentimiento en tiempo real permitió medir efectividad de cada acción de comunicación.
Herramientas Especializadas
Plataformas con Sentiment Analysis
Brandwatch
- IA propietaria para 27 idiomas
- Análisis de emociones (8 categorías)
- Detección de sarcasmo
- Precio: Enterprise ($$$)
Sprout Social
- Sentiment analysis en todos los planes
- Integrado con CRM
- Reportes automatizados
- Precio: Desde $249/mes
MonkeyLearn
- Plataforma de ML no-code
- Entrenar modelos custom
- API para integración
- Precio: Desde $299/mes
Hugging Face
- Modelos open-source
- Gratis (self-hosted)
- Requiere conocimientos técnicos
Limitaciones y Desafíos
1. Sarcasmo e Ironía
"Genial, justo lo que necesitaba, que el servicio fuera peor"
→ Palabras positivas, sentimiento negativo
Solución: Modelos avanzados (transformers) + contexto
2. Idiomas y Dialectos
Español de México: "Está bien chido"
Español de España: "Está guay"
Español de Argentina: "Está piola"
Mismo significado, vocabulario diferente
Solución: Modelos específicos por región o multilingües
3. Contexto Temporal
"Este producto es como los de hace 20 años"
Podría ser: Nostálgico-positivo o Anticuado-negativo
Solución: Análisis de aspectos + emociones
4. Neutralidad Ambigua
"El producto llegó"
¿Es neutral satisfecho o neutral decepcionado?
Solución: Complementar con engagement metrics (likes, shares)
Próximos Pasos
Esta Semana
- Prueba VADER con 50 tweets de tu marca
- Compara con clasificación manual
- Identifica patrones que el modelo malinterpreta
- Documenta casos edge para mejorar
Próximos 30 Días
- Implementa pipeline automatizado básico
- Establece baseline de sentimiento
- Crea alertas para sentimiento negativo >30%
- Genera primer reporte mensual
Recursos Adicionales
- Datasets: Kaggle Sentiment140, TASS (español)
- Cursos: "Natural Language Processing" en Coursera
- Papers: "Attention is All You Need" (Transformers)
Continúa Aprendiendo
- Complementa con: "Machine Learning Aplicado al Marketing Digital" para integrar con otros modelos
- Profundiza en: "Crisis Management en Redes Sociales" para usar sentiment analysis en situaciones críticas
Recuerda: El análisis de sentimiento es una herramienta, no una verdad absoluta. Siempre combina con análisis cualitativo y contexto de negocio.