Volver a artículos
Redes Sociales

Análisis de Sentimiento en Redes Sociales

Aprende técnicas de NLP para analizar sentimiento en redes sociales: desde modelos pre-entrenados hasta casos reales con Python y herramientas especializadas.

Academia Tooldata
30 de agosto de 2026
5 min de lectura
sentiment-analysisnlppythonsocial-media

¿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:

  1. Update de software para optimizar batería (semana 2)
  2. Campaña explicando valor vs precio
  3. 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

  1. Prueba VADER con 50 tweets de tu marca
  2. Compara con clasificación manual
  3. Identifica patrones que el modelo malinterpreta
  4. Documenta casos edge para mejorar

Próximos 30 Días

  1. Implementa pipeline automatizado básico
  2. Establece baseline de sentimiento
  3. Crea alertas para sentimiento negativo >30%
  4. 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.

¿Te gustó este artículo?

Explora más contenido educativo en nuestras categorías

Explorar más contenido