#!/usr/bin/env python # coding: utf-8 #

Análisis del usuario @AlanGarciaPeru

Twitter y herramientas de BigData para explorar datos




# #
Roque Leal
#
DataScience

# ## Resumen # # El presente Jupyter Notebook correponde a una exploración de los datos en la red social Twitter asociados al usuario [@AlanGarciaPeru](https://twitter.com/AlanGarciaPeru), lamentando los hechos de los sucesos que desencadenaron en el fallecimiento del Ciudadano Ex-Presidente del Perú, se realiza el informe como una aproximación científica de diferentes métodos para colectar y analizar datos en Twitter basado en las herramientas disponibles de Python. # In[3]: import tweepy # Para consumir la API de Twitter import pandas as pd # Para manejo de datos import numpy as np # Para operaciones numéricas # Para ploteo y visualización: import seaborn as sns from IPython.display import display import matplotlib.pyplot as plt get_ipython().run_line_magic('matplotlib', 'inline') # ## App de Twitter # # Para poder extraer tweets de la cuenta @AlanGarciaPeru para un posterior análisis, creamos una aplicación basados en los servicios de Twitter. El sitio para poder hacer eso es [https://apps.twitter.com/](https://apps.twitter.com/). # # De esta app utilizaremos la siguiente información: # * Consumer Key (API Key) # * Consumer Secret (API Secret) # * Access Token # * Access Token Secret # # Ahora comenzaremos a a consumir el API de Twitter. Para ello, creamos una función que nos permita autenticar nuestras llaves de acceso y consumo. # In[4]: from credentials import * def twitter_config(): # Autenticar y acceder usando llaves: auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET) auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET) # Regresar acceso al API: api = tweepy.API(auth) return api # ## Extraemos tweets # # Ahora se construye un extractor de datos de Twitter para el usuario de @AlanGarciaPeru, usando el API Rest de Twitter para crear una ventana de búsqueda en el pasado de unos 60 días contados desde hoy 18 de abril del 2019 y utilizando la cuenta de ([@AlanGarciaPeru](https://twitter.com/AlanGarciaPeru)) como usuario a minar. A manera de un ejercicio práctico sólo analizaremos los últimos 200 tweets dentro de la ventana de tiempo que nos permite twitter # # # # In[5]: extractor = twitter_config() # Creamos una lista de tweets: tweets = extractor.user_timeline(screen_name="AlanGarciaPeru", count=200) print("Tweets obtenidos: {}.\n".format(len(tweets))) # Imprimimos los primeros 5 tweets: print("Los primeros 5 tweets:\n") for tweet in tweets[:5]: print(tweet.text) print() # ## Creamos un Dataframe # # Esto nos permite tener un ploteo más amigable usando el método head para visualizar sólo los primeros 10 tweets de la cuenta @AlanGarciaPeru (o el número que elementos que se le pasen como argumento). # In[6]: datos = pd.DataFrame(data=[tweet.text for tweet in tweets], columns=['Tweets']) display(datos.head(10)) # Vamos agregar nuevos elementos de los tweets para explorar las caracteristicas de cada tweet # In[7]: # Añadimos datos relevantes: datos['len'] = np.array([len(tweet.text) for tweet in tweets]) datos['ID'] = np.array([tweet.id for tweet in tweets]) datos['Creado'] = np.array([tweet.created_at for tweet in tweets]) datos['Fuente'] = np.array([tweet.source for tweet in tweets]) datos['Likes'] = np.array([tweet.favorite_count for tweet in tweets]) datos['RTs'] = np.array([tweet.retweet_count for tweet in tweets]) # In[8]: display(datos.head(10)) # ## Descripción básica # # En este apartado investigaremos sobre estadísticos básicos de la información minada, como la media de las longitudes de cada tweet, cuál fue el tweet con más favoritos, cuál fue el tweet más retweeteado, etc. # In[9]: # Extraemos el promedio: media = np.mean(datos['len']) print("El promedio de caracteres en tweets: {}".format(media)) # Extraemos el tweet con más FAVs y con más RTs: fav_max = np.max(datos['Likes']) rt_max = np.max(datos['RTs']) fav = datos[datos.Likes == fav_max].index[0] rt = datos[datos.RTs == rt_max].index[0] # Max FAVs: print("El tweet con más likes es: \n{}".format(datos['Tweets'][fav])) print("Número de likes: {}".format(fav_max)) print("{} caracteres.\n".format(datos['len'][fav])) # Max RTs: print("El tweet con más retweets es: \n{}".format(datos['Tweets'][rt])) print("Número de retweets: {}".format(rt_max)) print("{} caracteres.\n".format(datos['len'][rt])) # ## Series de tiempo # # Teniendo los tweets como vectores basado en las fechas de creación, podemos construir una serie de tiempo con respecto a longitudes de tweets, favoritos y retweets. # In[10]: # Creamos series de tiempo para datos: tlen = pd.Series(data=datos['len'].values, index=datos['Creado']) tfav = pd.Series(data=datos['Likes'].values, index=datos['Creado']) tret = pd.Series(data=datos['RTs'].values, index=datos['Creado']) tlen.plot(figsize=(16,4), color='r'); # En este caso observamos que la cuenta @AlanGarciaPeru mantiene un comportamiento constante aunque los días cercanos al 17 de abril su frecuencia disminuyó si comparamos con las semanas de marzo # In[11]: # Visualización de likes vs retweets: tfav.plot(figsize=(16,4), label="Likes", legend=True) tret.plot(figsize=(16,4), label="Retweets", legend=True); # Presentando una gráfica de las interacciones causadas por cada tweet de la cuenta @AlanGarciaPeru, observamos los likes y retweets originados, los cuales alcanzaron una audiencia de hasta 4000 cuentas que interactuaron # ## Pie charts de dispositivos # # Basados en los metadatos de la tweets de @AlanGarciaPeru es propicia la ocasión de conocer desde que dispositivo fue creado, en este caso la totalidad ha tenido por origen el dispositivo Iphone del usuario, como lo vemos en la siguiente gráfica. # In[12]: fuentes = [] for fuente in datos['Fuente']: if fuente not in fuentes: fuentes.append(fuente) percent = np.zeros(len(fuentes)) for fuente in datos['Fuente']: for index in range(len(fuentes)): if fuente == fuentes[index]: percent[index] += 1 pass percent /= 100 # Pie chart: pie_chart = pd.Series(percent, index=fuentes, name='Fuentes') pie_chart.plot.pie(fontsize=11, autopct='%.2f', figsize=(6, 6)); # ## Análisis de sentimientos # # Utilizando la libreria de TexBlob, se realiza el análisis de sentimientos para el usuario # In[13]: from textblob import TextBlob import re def limpia_tweet(tweet): return ' '.join(re.sub("(@[A-Za-z0-9]+)|([^0-9A-Za-z \t])|(\w+:\/\/\S+)", " ", tweet).split()) def analiza_sentimiento(tweet): analysis = TextBlob(limpia_tweet(tweet)) if analysis.sentiment.polarity > 0: return 1 elif analysis.sentiment.polarity == 0: return 0 else: return -1 datos['AdS'] = np.array([ analiza_sentimiento(tweet) for tweet in datos['Tweets'] ]) display(datos.head(10)) # Clasificamos los tweets y construimos una lista con todos los tweets clasificados, reportando los porcentajes del análisis realizado. # In[14]: tweets_positivos = [ tweet for index, tweet in enumerate(datos['Tweets']) if datos['AdS'][index] > 0] tweets_neutros = [ tweet for index, tweet in enumerate(datos['Tweets']) if datos['AdS'][index] == 0] tweets_negativos = [ tweet for index, tweet in enumerate(datos['Tweets']) if datos['AdS'][index] < 0] print("Porcentaje de tweets positivos: {}%".format(len(tweets_positivos)*100/len(datos['Tweets']))) print("Porcentaje de tweets neutros: {}%".format(len(tweets_neutros)*100/len(datos['Tweets']))) print("Porcentaje de tweets negativos: {}%".format(len(tweets_negativos)*100/len(datos['Tweets']))) # In[18]: # Data to plot labels = 'Positive Tweets', 'Neutral Tweets','Negative Tweets' sizes = [len(tweets_positivos)*100/len(datos['Tweets']), len(tweets_neutros)*100/len(datos['Tweets']), len(tweets_negativos)*100/len(datos['Tweets'])] colors = ['gold', 'blue', 'black'] explode = (0.07, 0.07, 0.07) # explode 1st slice # Plot plt.figure(figsize=(10,6)) plt.pie(sizes, explode=explode, labels=labels, colors=colors, autopct='%1.1f%%', shadow=True, startangle=140) plt.axis('equal') plt.show() # Basados en la totalidad de tweets publicados los últimos tres meses en la cuenta ([@AlanGarciaPeru](https://twitter.com/AlanGarciaPeru)) el procesador de lenguaje Texblob interpreta mayormente comentarios neutros en un 92%, precedidos en una menor proporción de comentarios positivos en un 6% y finalmente sólo 1% las publicaciones reflejan un sentimiento negativo por parte del autor. # ## Repercusiones # # Luego del lamentable acontecimiento del usuario ([@AlanGarciaPeru](https://twitter.com/AlanGarciaPeru)) el día de ayer [17 de abril del 2019](https://cnnespanol.cnn.com/2019/04/17/fallecio-el-expresidente-de-peru-alan-garcia-muere-suicidio/), exploremos las repercusiones por parte de otros usuarios en Twitter a manera de conocer las repercuciones en cuanto a los nodos de interacción utilizando un Grafo y por otra parte conociendo el contenido de estas interacciones con una nube de palabras # In[24]: import analisis_twitter as at # Extraemos el contenido de más de 4 Millones de tweets de diferente usuarios en la red social a manera de hacer un grafo de interacciones y una nube de palabras del contenido expresado en estos tweets # In[28]: at.main() # In[27]: texto = at.main() # In[29]: at.nube(texto) # ## Polaridad # # Realizado el análisis de sentimientos con Texblob se procede a utilizar tambien el modelo de vaderSentiment una libreria de análisis de sentimientos basada en reglas y léxicos que está específicamente en sintonía con los sentimientos expresados en las redes sociales y para este caso vamos a extraer los principales 1500 tweets relacionados a reacciones de los usuarios de Twitter con cinco cuentas: [@AlanGarciaPeru](https://twitter.com/AlanGarciaPeru), [@MartinVizcarraC](https://twitter.com/MartinVizcarraC) , [@IDL_R](https://twitter.com/IDL_R), [@KeikoFujimori](https://twitter.com/KeikoFujimori) y [@Ollanta_HumalaT](https://twitter.com/Ollanta_HumalaT) de esta manera calcularemos la polaridad de los sentimientos expresados para cada una de las 5 cuentas. # In[19]: import tweepy import matplotlib.pyplot as plt get_ipython().run_line_magic('matplotlib', 'inline') import seaborn as sns sns.set() import pandas as pd import numpy as np from datetime import datetime import math import json # In[20]: from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer analyzer = SentimentIntensityAnalyzer() # In[21]: from config import (consumer_key, consumer_secret, access_token, access_token_secret) # In[22]: auth = tweepy.OAuthHandler(consumer_key, consumer_secret) auth.set_access_token(access_token, access_token_secret) api = tweepy.API(auth, parser=tweepy.parsers.JSONParser()) # In[23]: target_terms = ["@AlanGarciaPeru", "@MartinVizcarraC","@KeikoFujimori","@IDL_R","@Ollanta_HumalaT"] # In[25]: sentiments = [] desired = 300 # In[26]: for target in target_terms: counter = 0 # Get desired number of tweets (desired = 100 in this case for each target) while (counter < desired): # if desired number of tweets don't get fetched, get more public_tweets = api.search(target, count=(desired-counter), result_type="recent") for tweet in public_tweets['statuses']: # Loop through all tweets results = analyzer.polarity_scores(tweet["text"]) # Run Vader Analysis on each tweet compound = results["compound"] pos = results["pos"] neu = results["neu"] neg = results["neg"] # convert datetime object to string and then strip out the 10 chars(date) Date = str(datetime.strptime(tweet["created_at"], "%a %b %d %H:%M:%S %z %Y")) Date = Date[:19] # extract date and time # Add sentiments for each tweet into an array sentiments.append({"Source": target, "Text": tweet["text"], "DateTime": Date, "Compound": compound, "Pos": pos, "Neg": neu, "Neu": neg, "Tweets Ago": counter+1}) # increment counter counter = counter + 1 sentiments_df = pd.DataFrame.from_dict(sentiments) # Convert sentiments[] to DataFrame sentiments_df # In[29]: markersize = 160 kws = dict(s=markersize, linewidth=.8, edgecolor="bk") sns.set(font_scale = 1.5) max_tweets = sentiments_df["Tweets Ago"].max() pyber_palette = ['#c6fcff','#1b919a','#ff0033','#000099','#ffff66'] # light Sky blue, Green, Red, blue, Yellow # rename column header to match solution example sentiments_df.rename(columns = {'Source':'Media Sources'}, inplace = True) Date = Date[:10] # extract only date from string sns.lmplot(x='Tweets Ago', y='Compound', data=sentiments_df, fit_reg=False, # No regression line should be displayed palette=pyber_palette, scatter_kws=kws, hue= 'Media Sources', size = 10, legend_out=True) #plt.text(8, 45, "Note:\nCircle size correlates with driver count per city", horizontalalignment='left',size='medium', color='green', weight='light') plt.title("Análisis de los Sentimientos ({})".format(Date)) plt.ylabel("Polaridad de Tweet") plt.xlabel("Tweets Ago") plt.xlim(max_tweets+5, -5.0) # margins so plot doesn't end at max values plt.ylim(-1, 1) # Save the figure plt.savefig("Sentiment Analysis of Media Tweets.png") plt.show() # In[28]: AlanGarciaPeru = sentiments_df.groupby('Media Sources')['Compound'].mean()['@AlanGarciaPeru'] MartinVizcarraC = sentiments_df.groupby('Media Sources')['Compound'].mean()['@MartinVizcarraC'] KeikoFujimori = sentiments_df.groupby('Media Sources')['Compound'].mean()['@KeikoFujimori'] IDL_R = sentiments_df.groupby('Media Sources')['Compound'].mean()['@IDL_R'] Ollanta_HumalaT = sentiments_df.groupby('Media Sources')['Compound'].mean()['@Ollanta_HumalaT'] print("@AlanGarciaPeru = " + str(AlanGarciaPeru) + ", @MartinVizcarraC = " + str(MartinVizcarraC) + ", @KeikoFujimori = " + str(KeikoFujimori) +", @IDL_R = " + str(IDL_R) +", @Ollanta_HumalaT = " + str(Ollanta_HumalaT)) x_labels = ['@AlanG','@MartinV','@KeikoF','@IDL_R','@Ollanta'] y_sentiments = [AlanGarciaPeru,MartinVizcarraC,KeikoFujimori,IDL_R,Ollanta_HumalaT] palette = ['#c6fcff','#1b919a','#ff0033','#000099','#ffff66'] # light Sky blue, Green, Red, blue, Yellow x_pos = [0,1,2,3,4] # positions for media sources on x axis plt.bar(x_pos, y_sentiments, color=sns.color_palette(palette,5), align='center', width = 1, edgecolor = 'bk', linewidth = .6) plt.xlim(-0.5, len(x_labels)-0.49) # 0.49 instead of 0.5 to show black edgeline of last bar plt.ylim(min(y_sentiments)-0.1, max(y_sentiments)+0.1) # margins of +/-0.1 beyond max/min values plt.xticks(x_pos, x_labels) plt.title("Overall Media Sentiment based on Twitter ({})".format(Date), fontsize=14) plt.ylabel("Tweet Polarity", fontsize=14) for a,b in zip(x_pos, y_sentiments): # show values of each bar in the plot if b <= 0: B = b-0.035 # position text below bar for negative bars. else: # Value of 'b' is preserved as its ploted (bar height) B = b+0.015 # position text above bar for positive bars plt.text(a-0.25, B, str(round(b, 2)), fontsize = 13) # round to 2 decimal places before plotting # Save the figure plt.savefig("Overall Media Sentiment based on Twitter.png") plt.show() # En esta oportunidad basado en muestra de 1500 tweets y usando las cinco cuentas para la comparación se tiene la cuenta de @MartinVizcarraC como la principal que se asocia con sentimientos negativos por parte de los seguidores de Twitter, precedida por la cuenta @IDL_R, @AlanGarciaPeru, @Ollanta_HumalaT y finalmente la cuenta @KeikoFujimori. Todas ellas polarizadas a sentimientos negativos aunque parcialmente se expresan sentimientos positivos a las cuentas de @AlanGarciaPeru , @Ollanta_HumalaT y @MartinVizcarraC # ## Conclusiones # # Los resultados presentan diferentes soluciones en cuanto al análisis de contenido en twitter a partir de diferentes librerias de Python, la interpretación de ellos es a juicio de los lectores y espera ser una aproximación inicial para que permita involucrarlos en la exploración de los datos en redes sociales como parte del estudio sobre el comportamiento de las audiencias y los fenómenos sociales. # # Referencias # # 1. [Documentación oficial de Tweepy](http://tweepy.readthedocs.io/en/v3.5.0/). # 2. [Documentación oficial de NumPy](https://docs.scipy.org/doc/numpy-dev/index.html). # 3. [Documentación oficial de Pandas](https://pandas.pydata.org/pandas-docs/stable/index.html). # 4. [Documentación oficial de Matplotlib](http://matplotlib.org/index.html). # 5. [Sitio oficial de Seaborn](https://seaborn.pydata.org/). # 6. [Documentación oficial de TextBlob](https://textblob.readthedocs.io/en/dev/). # 7. [Creación de clasificadores con TextBlob](https://textblob.readthedocs.io/en/dev/classifiers.html). # In[ ]: