Por Joaquín Aliaga, Pablo Torres y Juan Urrutia (Equipo 4)
Twitter presenta una gran cantidad de información; ¿Dónde nos encontramos? ¿Qué hacemos? ¿Qué opinamos?, entre muchas otras preguntas, incluso, en que invertimos. La vida, como hoy la conocemos nos muestra una clara dependencia a las redes sociales ya sea como medio de entretención, de información e incluso como mecanismo de control social.
En este hito se presenta la exploración de los datos mediante las técnicas aprendidas en los laboratorios para la clasificación de resultados mediante aprendizaje supervisado.
En un principio, los datos de Twitter fueron recolectados de personas al azar diariamente durante un mes, los cuales contenían principalmente los atributos: texto, fecha, cantidad de reTweets, etc. Estos datos luego fueron procesados de manera que se buscaban palabras claves que tenían que ver con la cryptomoneda escogida, en ese momento BitCoin.
Se creó un nuevo dataset que contenía datos de distintas fechas, con las columnas fecha, frecuencia y precio en dólares. La columna fecha se llenó con números partiendo de 1 correspondiente a la primera fecha en la que se tiene datos de Twitter hasta el último día en que se tienen datos de Twitter. La columna frecuencia se llenó sumando la cantidad de Tweets que contenían ciertas palabras clave de la cryptomoneda a analizar en cierta fecha (Si un Tweet contiene más de una, de igual manera vale como uno para la frecuencia). Finalmente la columna precio en dólares se llenó con el precio de la cryptomoneda en la fecha correspondiente. Finalmente, se creó la columna clase cuyo valor era 1 si el precio había subido ese día respecto al día anterior y 0 en caso contrario.
Este dataset no funcionó bien para clasificar dada la baja frecuencia, por lo que se realizaron los siguientes cambios:
Para este hito los datos de Bitcoin, Ripple, BitCash y DogeCoin se recolectaron de coinmetrics.io y los de Twitter se recolectaron mediante un programa para MacOS que automáticamente buscaba en Twitter los datos uno a uno.
Los datos de coinmetrics consisten de información del precio de cada moneda nombrada anteriormente desde el primero de enero del año 2016 tomados diariamente, sin embargo, hay monedas que no se encuentran desde esta fecha y por esto, para estas monedas se utilizaron las fechas de lanzamiento. Los datos de Twitter son registros desde el 01 de enero de 2016.
Para obtener los datos de Twitter, en MacOS se ejecuta el programa obtenido del repositorio https://github.com/bpb27/twitter_scraping. Con esto se obtiene un archivo CSV con los ID de cada tweet recolectado para cada influencer desde el 1 de enero de 2016 hasta el 1 de abril de 2018. Este CSV se procesa y se transforma en una lista de ids separados por '\n' en un archivo de texto, la cual se ingresa en el programa Hydrator obtenido en el siguiente repositorio https://github.com/DocNow/hydrator. Para procesar los datos de Twitter se cargan primero todas las librerías a utilizar y se definen las siguientes funciones:
import numpy as np
import pandas as pd
import re
def procesamientoInfluencers(df, keyWords):
# Listas para pasar la fecha a numeros a partir del 1
month2num = {'Jan': 0, 'Feb': 31, 'Mar': 59, 'Apr': 90, 'May': 120, 'Jun': 151, 'Jul': 181, 'Aug': 212, 'Sep': 243, 'Oct': 273, 'Nov': 304, 'Dec': 334}
month2num2016 = {'Jan': 0, 'Feb': 31, 'Mar': 60, 'Apr': 91, 'May': 121, 'Jun': 152, 'Jul': 182, 'Aug': 213, 'Sep': 244, 'Oct': 274, 'Nov': 305, 'Dec': 335}
d = {}
rt = {}
fv = {}
# Funcion que se utiliza como wrapper dentro del loop for, esto es para poder detener el loop apenas
# se encuentre una palabra de las que se buscan en keyWords
def loopAdentro(i, txt, d, rt, df, fv, keyWords):
date = df['created_at'][i].split()
dayNum = int(date[2])
monthNum = month2num[date[1]] if int(date[-1]) != 2016 else month2num2016[date[1]]
yearNum = (int(date[-1]) - 2016)*365
dateStr = (dayNum + monthNum + yearNum)
# Se itera cada palabra del texto de Twitter quitando el hashtag y sumando los retweets y favoritos
for word in txt.split():
if re.sub(r'[^\w]', ' ', word).strip().lower() in keyWords:
if dateStr in d.keys():
d[dateStr] += 1
rt[dateStr] += df['retweet_count'][i]
fv[dateStr] += df['favorite_count'][i]
return
else:
d[dateStr] = 1
rt[dateStr] = df['retweet_count'][i]
fv[dateStr] = df['favorite_count'][i]
return
else:
if dateStr not in d.keys():
d[dateStr] = 0
rt[dateStr] = 0
fv[dateStr] = 0
return
# Se itera sobre cada tweet
for i, txt in enumerate(df['text']):
loopAdentro(i, txt, d, rt, df, fv, keyWords)
# Se ordenan las fechas
sortedKeys = sorted(d.keys())
# Se crea un diccionario con todos los datos obtenidos anteriormente y se retorna este objeto
influencersProcessedDict = {}
for i in sortedKeys:
influencersProcessedDict[i] = {'frecuencia': d[i], 'retweets': rt[i], 'favoritos': fv[i]}
return influencersProcessedDict
Para ordenar el dataset de coinmetrics se utilizó el siguiente script en R:
## Cargar dataset de la criptomoneda, asignar nombres a columnas, limpiar columnas vacias y filtrar por fechas
# Leer datos
btc <- read.csv("~/CC5206/doge.csv", header=FALSE, na.strings="null")
# Asignar nombres a columnas
colnames(btc)[colnames(btc)=="V1"] <- "date"
colnames(btc)[colnames(btc)=="V2"] <- "txVolume(USD)"
colnames(btc)[colnames(btc)=="V3"] <- "txCount"
colnames(btc)[colnames(btc)=="V4"] <- "marketcap(USD)"
colnames(btc)[colnames(btc)=="V5"] <- "price(USD)"
colnames(btc)[colnames(btc)=="V6"] <- "exchangeVolume(USD)"
colnames(btc)[colnames(btc)=="V7"] <- "generatedCoins"
colnames(btc)[colnames(btc)=="V8"] <- "fees"
#Borrar primera fila (que contiene los nombres de las columnas)
btc1 <- btc[-c(1): ]
#Borrar ultima columna que no tiene datos
btc1 <- btc[ :-c(1)]
# Transformar columna "date" a datos tipo fecha
datos$fecha <- as.Date(datos$fecha,format="%Y-%m-%d")
# Filtrar por fecha
datos_filtrados <- subset(datos,fecha<="2013-05-30")
# Guardar datos como csv
write.csv(datos_filtrados,file="dogos.csv")
Teniendo los dos datasets cargados, se procede a cruzarlos y crear los nuevos datasets, uno para cada cryptomoneda de la manera en que se explicó anteriormente. Se define la siguiente función para esto:
def cryptoMasInfluencers(cryptoDf, ifProcessedDict, startPoint):
# Se definen los parametros que se utilizaran en los datos
diasAPromediar = 7
ultimosDiasInfluencers = 20
ultimosDiasFavs= 7
datosAEntrenar = {}
lastInfluencersDays = []
lastInfluencersDaysfv = []
# Se agregan la cantidad de dias pertinentes hacia adelante para el primer dia
btcPricesAround = [cryptoDf['price(USD)'][i] for i in range(int(diasAPromediar/2))]
lastPrice = 0
lastDayPrice = 0
# Se itera sobre las fechas
for dat in ifProcessedDict.keys():
if dat + startPoint < len(ifProcessedDict.keys()):
# Se verifica el tamaño de las listas que contienen la cantidad de retweets, precios y favoritos
if len(btcPricesAround) >= diasAPromediar:
btcPricesAround.pop(0)
if len(lastInfluencersDays) >= ultimosDiasInfluencers:
lastInfluencersDays.pop(0)
if len(lastInfluencersDaysfv) >= ultimosDiasFavs:
lastInfluencersDaysfv.pop(0)
if dat <= len(ifProcessedDict.keys()) - 2:
btcPricesAround.append(list(cryptoDf['price(USD)'])[(dat + startPoint - 1) + 2])
lastInfluencersDays.append(ifProcessedDict[dat + startPoint]['retweets'])
lastInfluencersDaysfv.append(ifProcessedDict[dat + startPoint]['favoritos'])
# Se determina la clase
if lastPrice <= np.mean(btcPricesAround):
clase = 1
else:
clase = 0
lastPrice = np.mean(btcPricesAround)
# Se determina la diferencia de precios para el dato agregado fallido
if dat != 0:
lastDayPrice = list(cryptoDf['price(USD)'])[dat + startPoint] - list(cryptoDf['price(USD)'])[dat + startPoint - 1]
# Se llena el dataset con los datos obtenidos
datosAEntrenar[dat + startPoint] = {'ultimos %i dias de retweets' % (ultimosDiasInfluencers) : sum(lastInfluencersDays), 'ultimos %i dias favoritos' % (ultimosDiasFavs) : sum(lastInfluencersDaysfv), 'Diferencia de precio': lastDayPrice , 'precio promedio %i dias' % (diasAPromediar) : lastPrice, 'clase' : clase}
#datosAEntrenar['favorites'].append(sum(lastInfluencersDaysfv))
datosAEntrenarDF = pd.DataFrame(data = datosAEntrenar)
# Se ordenan las categorias del dataset
datosAEntrenarDF = datosAEntrenarDF.transpose()[['ultimos %i dias de retweets' % (ultimosDiasInfluencers), 'ultimos %i dias favoritos' % (ultimosDiasFavs), 'Diferencia de precio', 'precio promedio %i dias' % (diasAPromediar), 'clase']]
return datosAEntrenarDF
Luego se ejecuta el siguiente script, importando los archivos csv correspondientes a los datasets de twitter y las cryptomonedas, y se crean las listas correspondientes a las key words para cada cryptomoneda, se elimina BtcCash debido a la poca cantidad de datos que contiene, se consideró que no aporta al proyecto.
df = pd.read_csv('D:/Docs universidad/Mineria de Datos/Proyecto/influencers_allcsvhito3.csv', encoding='latin1', low_memory = False)
btcdf = pd.read_csv('D:/Docs universidad/Mineria de Datos/Proyecto/btc_1enero2016_6julio2018.csv', encoding='latin1', low_memory = False)
dfDoge = pd.read_csv('D:/Docs universidad/Mineria de Datos/Proyecto/new cryptos/dogos3.csv', encoding='latin1', low_memory = False)
dfRipple = pd.read_csv('D:/Docs universidad/Mineria de Datos/Proyecto/new cryptos/Ripple3.csv', encoding='latin1', low_memory = False)
btcKeyWords = ['Bitcoin', 'bitcoin', 'blockchain', 'ICO', 'cryptocurrency', 'crypto', 'ethereum', 'btc', 'Blockchain']
dogeKeyWords = ['dogecoin', 'doge', 'DOGE']
rippleKeyWords = ['Ripple', 'xrp', 'XRP', 'ripple']
Después se generan los datasets agregados:
ifProcRipple = procesamientoInfluencers(df, rippleKeyWords)
datosAEntrenarDFRipple = cryptoMasInfluencers(dfRipple, ifProcRipple, 400)
ifProcBtc = procesamientoInfluencers(df, btcKeyWords)
datosAEntrenarDFBtc = cryptoMasInfluencers(btcdf, ifProcBtc, 400)
ifProcDoge = procesamientoInfluencers(df, dogeKeyWords)
datosAEntrenarDFDoge = cryptoMasInfluencers(dfDoge, ifProcDoge, 400)
Observando los datos, obtenemos los siguientes gráficos.
%matplotlib inline
datosAEntrenarDFBtc['ultimos 7 dias favoritos'].plot(legend=True, title='Precio del Bitcoin vs. reTweets de influencers')
datosAEntrenarDFBtc['ultimos 20 dias de retweets'].plot(legend=True, title='Retweets y tweets favoritos relacionados a Bitcoin')
%matplotlib inline
datosAEntrenarDFRipple['ultimos 7 dias favoritos'].plot(legend=True, title='Precio del Ripple vs. reTweets de influencers')
datosAEntrenarDFRipple['ultimos 20 dias de retweets'].plot(legend=True, title='Retweets y tweets favoritos relacionados a Ripple')
%matplotlib inline
datosAEntrenarDFDoge['ultimos 7 dias favoritos'].plot(legend=True, title='Precio del Dogecoin vs. reTweets de influencers')
datosAEntrenarDFDoge['ultimos 20 dias de retweets'].plot(legend=True, title='Retweets y tweets favoritos relacionados a Dogecoin')
Teniendo todos los datasets listos, se procede a definir funciones para clasificar, donde se incluye una red neuronal de 4 capas escondidas con 10 neuronas cada capa y función de activación ReLU
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score, recall_score, precision_score
from sklearn.dummy import DummyClassifier
from sklearn.svm import SVC # support vector machine classifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.naive_bayes import GaussianNB # naive bayes
from sklearn.neighbors import KNeighborsClassifier
from sklearn.neural_network import MLPClassifier
def run_classifier(clf, X, y, num_tests=100):
metrics = {'f1-score': [], 'precision': [], 'recall': []}
for _ in range(num_tests):
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.40, stratify=y)
clf.fit(X_train, y_train)
predictions = clf.predict(X_test)
metrics['f1-score'].append(f1_score(y_test, predictions)) # X_test y y_test deben ser definidos previamente
metrics['recall'].append(recall_score(y_test, predictions))
metrics['precision'].append(precision_score(y_test, predictions))
return metrics
def runAllClassifiers(X, y):
c0 = ("Base Dummy", DummyClassifier(strategy='stratified'))
c1 = ("Decision Tree", DecisionTreeClassifier())
c2 = ("Gaussian Naive Bayes", GaussianNB())
c3 = ("KNN", KNeighborsClassifier(n_neighbors=5))
c4 = ('nn', MLPClassifier(solver='lbfgs', alpha=1e-5, hidden_layer_sizes=(10, 10, 10, 10), random_state=1))
classifiers = [c0, c1, c2, c3, c4]
for name, clf in classifiers:
if name == 'nn':
X = (X - np.mean(X))/np.std(X)
metrics = run_classifier(clf, X, y) # hay que implementarla en el bloque anterior.
print("----------------")
print("Resultados para clasificador: ",name)
print("Precision promedio:",np.array(metrics['precision']).mean())
print("Recall promedio:",np.array(metrics['recall']).mean())
print("F1-score promedio:",np.array(metrics['f1-score']).mean())
print("----------------\n\n")
Para clasificar se seleccionan los datos a clasificar y las clases por separado (Se seleccionan todas las columnas para entrenar menos las últimas dos que corresponden al precio y a la clase y se selecciona la última columna para la clase):
X_orig_btc = datosAEntrenarDFBtc[datosAEntrenarDFBtc.columns[:2]]
y_orig_btc = datosAEntrenarDFBtc[datosAEntrenarDFBtc.columns[-1]]
X_orig_ripple = datosAEntrenarDFRipple[datosAEntrenarDFRipple.columns[:2]]
y_orig_ripple = datosAEntrenarDFRipple[datosAEntrenarDFRipple.columns[-1]]
X_orig_doge = datosAEntrenarDFDoge[datosAEntrenarDFDoge.columns[:2]]
y_orig_doge = datosAEntrenarDFDoge[datosAEntrenarDFDoge.columns[-1]]
Finalmente, se clasifican estos datos.
# Bitcoin
runAllClassifiers(X_orig_btc, y_orig_btc)
#Ripple
runAllClassifiers(X_orig_ripple, y_orig_ripple)
# Dogecoin
runAllClassifiers(X_orig_doge, y_orig_doge)
Acotando el tiempo de análisis se obtuvieron mejores resultados, abarcando los meses peak de las cryptomonedas; esto pues el mayor cambio en el valor de las criptomonedas se logra en el intervalo de tiempo en el cual se realizó el proyecto, luego tomar en el clasificador fechas donde la variación fue despreciable solo disminuía los valores de las métricas.
Los mejores resultados para las clasificaciones se obtuvieron del BitCoin; se espera que sea así pues es la moneda más popular y el índice de retweets se acerca más a la curva del precio (de acuerdo al promedio de éste establecido en los gráficos), además ésta cryptomoneda aparece en una mayor cantidad de Tweets, por lo que se tiene mayor información para entrenar.
La métrica F1-Score supera (en general) el 70%, lo que para el modelo propuesto es bueno (mejor que lanzar una moneda), sin embargo, esto puede significar una mayor cantidad de datos con el valor de la clase 0 debido a un recall alto, por lo tanto confiar plenamente en F1-Score no es recomendable y se prefiere observar las métricas en general.
Debido a que las clases estaban balanceadas no fue necesario realizar subsampling ni oversampling.
Para mejorar las métricas en los resultados de las criptomonedas se hace necesario buscar influencers y personas que twitteen más sobre esas criptomonedas (que la sigan más) ya sea porque invierten en ella o por interés, esto es porque se les llama influencers debido a que causan cierta influencia en la gente, y dado que las cryptomonedas tienen precios que dependen mucho de la gente, los influencers son la mejor opción para analizar.
El modelo propuesto: seguir el rastro de la variación de precios de una criptomoneda mediante el indicador retweet entrega buenos resultados para las criptomonedas que se testearon (aunque es mucho mejor en la moneda más popular: Bitcoin)
Se obtiene una tendencia cuando se empieza a hacer más relevante (mayor cantidad de retweets y favoritos) lo que dice un influencer a fechas cercanas a alzas y variaciones de precio.
Twitter tiene un grado de influencia en la variación de precio de las criptomonedas gracias a la acción de los influencers dentro de la red social.
Sería interesante un análisis de volatilidad (medida de la frecuencia e intensidad de los cambios del precio de un activo, en este caso de la criptomoneda) y de sentimientos de los twitter para obtener predicciones de acuerdo a estos indicadores y de esta manera incorporar un análisis de si cierto tweet afectará positiva o negativamente a la cryptomoneda analizada.