💬 Unidad 4. Análisis de Sentimientos
El Análisis de Sentimientos (Sentiment Analysis), también conocido como minería de opiniones, es una de las aplicaciones más populares del NLP. Consiste en determinar la actitud, emoción u opinión expresada en un texto.
4.1. ¿Qué es el Análisis de Sentimientos?
Es el proceso computacional de identificar y categorizar opiniones expresadas en texto para determinar si la actitud del escritor hacia un tema es:
- Positiva: "Me encanta este producto, es increíble"
- Negativa: "Terrible servicio, muy decepcionado"
- Neutral: "El producto llegó ayer"
Niveles de Análisis
| Nivel | Descripción | Ejemplo |
|---|---|---|
| Documento | Sentimiento global del texto | Reseña completa de un producto |
| Oración | Sentimiento de cada oración | Cada frase de una reseña |
| Aspecto | Sentimiento sobre aspectos específicos | "La batería es excelente, pero la cámara es mala" |
| Entidad | Sentimiento hacia entidades específicas | Opiniones sobre diferentes marcas en un texto |
Tipos de Salida
- Binaria: Positivo / Negativo
- Ternaria: Positivo / Neutral / Negativo
- Escala: Rating numérico (1-5 estrellas)
- Emociones: Alegría, Tristeza, Ira, Miedo, Sorpresa, Disgusto
4.2. Enfoques para Análisis de Sentimientos
Enfoque Basado en Léxico
Utiliza diccionarios de palabras con polaridad predefinida.
Ventajas:
- No requiere datos de entrenamiento.
- Interpretable y transparente.
- Funciona bien en dominios generales.
Desventajas:
- No captura contexto ni negaciones bien.
- Depende de la calidad del léxico.
- Dificultad con sarcasmo e ironía.
# Ejemplo simple de enfoque léxico
lexico_positivo = {'bueno', 'excelente', 'genial', 'increíble', 'mejor', 'feliz', 'encanta'}
lexico_negativo = {'malo', 'terrible', 'horrible', 'peor', 'triste', 'odio', 'decepcionante'}
def analisis_lexico(texto):
tokens = texto.lower().split()
score = 0
for token in tokens:
if token in lexico_positivo:
score += 1
elif token in lexico_negativo:
score -= 1
if score > 0:
return "Positivo"
elif score < 0:
return "Negativo"
else:
return "Neutral"
# Ejemplos
print(analisis_lexico("Este producto es excelente y genial")) # Positivo
print(analisis_lexico("Terrible servicio, muy malo")) # Negativo
print(analisis_lexico("El paquete llegó ayer")) # Neutral
Enfoque Basado en Machine Learning
Entrena clasificadores supervisados con datos etiquetados.
Proceso típico:
- Recolectar datos etiquetados (reseñas con ratings).
- Preprocesar texto (tokenización, limpieza).
- Vectorizar (BoW, TF-IDF, embeddings).
- Entrenar clasificador (Naive Bayes, SVM, Logistic Regression).
- Evaluar y ajustar.
Ventajas:
- Captura patrones complejos.
- Adaptable a dominios específicos.
- Mejor rendimiento general.
Desventajas:
- Requiere datos etiquetados.
- Puede no generalizar a otros dominios.
4.3. Implementación con Machine Learning
Dataset: IMDB Movie Reviews
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
from sklearn.metrics import classification_report, accuracy_score
# Cargar datos (ejemplo simplificado)
# En la práctica, usar datasets como IMDB de Keras o Hugging Face
reseñas = [
("me encanta esta película, actuaciones increíbles", 1),
("película brillante, la recomiendo totalmente", 1),
("una historia hermosa y conmovedora", 1),
("excelente dirección y guión", 1),
("muy entretenida, la mejor del año", 1),
("qué película tan aburrida y larga", 0),
("terrible actuación, muy decepcionante", 0),
("perdí mi tiempo viendo esto", 0),
("la peor película que he visto", 0),
("no la recomiendo para nada, muy mala", 0),
]
textos = [r[0] for r in reseñas]
etiquetas = [r[1] for r in reseñas]
# División train/test
X_train, X_test, y_train, y_test = train_test_split(
textos, etiquetas, test_size=0.3, random_state=42
)
# Vectorización TF-IDF
vectorizer = TfidfVectorizer(max_features=5000, ngram_range=(1, 2))
X_train_tfidf = vectorizer.fit_transform(X_train)
X_test_tfidf = vectorizer.transform(X_test)
# Entrenar múltiples modelos
modelos = {
"Naive Bayes": MultinomialNB(),
"Logistic Regression": LogisticRegression(max_iter=1000),
"SVM": LinearSVC()
}
for nombre, modelo in modelos.items():
modelo.fit(X_train_tfidf, y_train)
y_pred = modelo.predict(X_test_tfidf)
acc = accuracy_score(y_test, y_pred)
print(f"{nombre}: Accuracy = {acc:.4f}")
4.4. Análisis de Sentimientos con Bibliotecas
TextBlob (Rápido y Simple)
from textblob import TextBlob
def analizar_sentimiento_textblob(texto):
blob = TextBlob(texto)
# Polaridad: -1 (negativo) a 1 (positivo)
# Subjetividad: 0 (objetivo) a 1 (subjetivo)
return {
'texto': texto,
'polaridad': blob.sentiment.polarity,
'subjetividad': blob.sentiment.subjectivity,
'sentimiento': 'Positivo' if blob.sentiment.polarity > 0
else 'Negativo' if blob.sentiment.polarity < 0
else 'Neutral'
}
# Ejemplos
textos = [
"I love this product, it's amazing!",
"Terrible experience, very disappointed",
"The package arrived yesterday"
]
for texto in textos:
resultado = analizar_sentimiento_textblob(texto)
print(f"{resultado['texto']}")
print(f" → {resultado['sentimiento']} (pol: {resultado['polaridad']:.2f})")
print()
VADER (Especializado en Redes Sociales)
VADER (Valence Aware Dictionary for Sentiment Reasoning) está específicamente diseñado para texto de redes sociales, manejando emojis, mayúsculas, puntuación, etc.
from nltk.sentiment import SentimentIntensityAnalyzer
import nltk
nltk.download('vader_lexicon')
sia = SentimentIntensityAnalyzer()
textos = [
"I LOVE this!!! 😍",
"This is okay, nothing special",
"Worst product EVER!!! 😡😡😡",
"The movie was good, but the ending was bad"
]
for texto in textos:
scores = sia.polarity_scores(texto)
print(f"'{texto}'")
print(f" Scores: {scores}")
print(f" Compound: {scores['compound']:.3f}")
print()
Output de VADER:
neg: Proporción negativaneu: Proporción neutralpos: Proporción positivacompound: Score normalizado entre -1 y 1
4.5. Análisis de Sentimientos con Transformers
Los modelos basados en Transformers (BERT, RoBERTa) ofrecen el mejor rendimiento actual.
Usando Hugging Face
from transformers import pipeline
# Cargar pipeline de análisis de sentimientos
sentiment_pipeline = pipeline("sentiment-analysis")
# Análisis simple
textos = [
"I love this movie, it's fantastic!",
"This is the worst product I've ever bought",
"The weather is nice today"
]
for texto in textos:
resultado = sentiment_pipeline(texto)[0]
print(f"'{texto}'")
print(f" → {resultado['label']} (score: {resultado['score']:.4f})")
print()
# Para español, usar modelo específico
sentiment_es = pipeline(
"sentiment-analysis",
model="nlptown/bert-base-multilingual-uncased-sentiment"
)
texto_es = "Esta película es absolutamente maravillosa"
resultado_es = sentiment_es(texto_es)
print(f"Español: '{texto_es}' → {resultado_es}")
Modelo Específico para Español
from transformers import pipeline
# Modelo entrenado específicamente para español
classifier = pipeline(
"text-classification",
model="pysentimiento/robertuito-sentiment-analysis"
)
textos_es = [
"Me encanta este restaurante, la comida es deliciosa",
"El servicio fue terrible y tardaron mucho",
"El pedido llegó a tiempo"
]
for texto in textos_es:
resultado = classifier(texto)[0]
print(f"'{texto}'")
print(f" → {resultado['label']} ({resultado['score']:.3f})")
4.6. Análisis de Sentimientos por Aspectos
El Aspect-Based Sentiment Analysis (ABSA) identifica sentimientos hacia aspectos específicos de un producto o servicio.
"La batería del teléfono dura mucho, pero la cámara es terrible"
Aspectos:
- batería → Positivo
- cámara → Negativo
Implementación Simple
import spacy
nlp = spacy.load('es_core_news_sm')
aspectos_positivos = {
'batería': ['dura', 'excelente', 'increíble', 'buena'],
'cámara': ['nítida', 'clara', 'buena', 'increíble'],
'pantalla': ['brillante', 'clara', 'grande', 'hermosa'],
'diseño': ['elegante', 'bonito', 'moderno', 'hermoso']
}
aspectos_negativos = {
'batería': ['corta', 'mala', 'terrible', 'poco'],
'cámara': ['borrosa', 'mala', 'terrible', 'pésima'],
'pantalla': ['pequeña', 'oscura', 'mala'],
'diseño': ['feo', 'malo', 'anticuado']
}
def absa_simple(texto):
"""Análisis de sentimiento por aspectos simple."""
texto_lower = texto.lower()
resultados = {}
# Buscar aspectos y palabras de sentimiento cercanas
for aspecto in aspectos_positivos.keys():
if aspecto in texto_lower:
sentimiento = "Neutral"
# Buscar palabras positivas
for palabra in aspectos_positivos[aspecto]:
if palabra in texto_lower:
sentimiento = "Positivo"
break
# Buscar palabras negativas
for palabra in aspectos_negativos.get(aspecto, []):
if palabra in texto_lower:
sentimiento = "Negativo"
break
resultados[aspecto] = sentimiento
return resultados
# Ejemplo
texto = "La batería es excelente y dura todo el día, pero la cámara es terrible y borrosa"
print(absa_simple(texto))
# {'batería': 'Positivo', 'cámara': 'Negativo'}
4.7. Aplicaciones Reales
Monitoreo de Redes Sociales
# Ejemplo: Analizar sentimiento de menciones de una marca
menciones = [
"@MarcaX Gran servicio al cliente, muy satisfecho!",
"@MarcaX El producto llegó dañado, muy decepcionado",
"@MarcaX ¿Cuál es el horario de atención?",
"@MarcaX Increíble calidad, lo recomiendo totalmente",
"@MarcaX El peor servicio que he recibido, jamás volveré"
]
from collections import Counter
def monitorear_marca(menciones, classifier):
"""Analiza el sentimiento de menciones de una marca."""
resultados = []
for mencion in menciones:
analisis = classifier(mencion)[0]
resultados.append(analisis['label'])
# Resumen
conteo = Counter(resultados)
total = len(resultados)
print("=== Resumen de Sentimiento ===")
for label, count in conteo.most_common():
porcentaje = (count / total) * 100
print(f"{label}: {count} ({porcentaje:.1f}%)")
return resultados
# monitorear_marca(menciones, sentiment_pipeline)
Análisis de Reseñas de Productos
def analizar_reseñas_producto(reseñas):
"""
Analiza reseñas de un producto y genera un resumen.
"""
positivas = []
negativas = []
for reseña in reseñas:
# Aquí iría el clasificador
# resultado = classifier(reseña)
# Por simplicidad, usamos longitud como proxy
if len(reseña) > 50 and "excelente" in reseña.lower():
positivas.append(reseña)
elif "malo" in reseña.lower() or "terrible" in reseña.lower():
negativas.append(reseña)
return {
'total': len(reseñas),
'positivas': len(positivas),
'negativas': len(negativas),
'score': len(positivas) / max(len(reseñas), 1)
}
4.8. Desafíos y Consideraciones
Desafíos Comunes
-
Sarcasmo e Ironía: "Oh genial, otro producto que no funciona" → Detectado erróneamente como positivo.
-
Negaciones: "No me gustó nada" → Palabras positivas ("gustó") en contexto negativo.
-
Comparaciones: "Mejor que la competencia pero aún malo" → Mezcla de sentimientos.
-
Contexto del Dominio: "La película es una bomba" → Puede ser muy buena (éxito) o muy mala (fracaso).
-
Multilingüe: Modelos entrenados en inglés no funcionan bien en español sin adaptación.
Métricas de Evaluación
| Métrica | Uso |
|---|---|
| Accuracy | Proporción de predicciones correctas |
| Precision | De los predichos positivos, cuántos son correctos |
| Recall | De los positivos reales, cuántos detectamos |
| F1-Score | Media armónica de Precision y Recall |
| Macro F1 | Promedio de F1 por clase (útil con clases desbalanceadas) |
📅 Fecha de creación: Enero 2026
✍️ Autor: Fran García