Creación con R de dendogramas semánticos y clústers de conversaciones en twitter

novembre 10, 2013 in Investigación, innovación y desarrollo de nuevas ideas

Elaborar un dendograma para una búsqueda en twitter es un complemento a la nube de palabras. Mientras que en la nube de tags buscamos que palabras son más utilizadas y las jerarquizamos según el número de veces que se han citado, no buscamos relaciones semánticas entre ellas.

El análisis clúster a través de un dendograma de las conversaciones que se producen en una búsqueda en twitter nos permite ver las relaciones semánticas entre palabras, y cómo se relacionan entre ellas. Es decir, nos permite ver “que conversaciones” se están produciendo y no sólo que palabras son más utilizadas.

Perdemos la capacidad de la nube de tags de jerarquizar el peso de cada palabra, por lo tanto este análisis puede ser un buen complemento al de la nube de tags.

Si te interesa entender mejor cómo funciona un análisis clúster.

El resultado que tendremos de este script es el siguiente, una gráfica donde veremos un “dendograma” donde las palabras más utilizadas estarán presentes, las palabras cuanto más asociadas a una misma frase estén tendrán una relación jerárquica más cercana. Algunas, como en este caso, aparecen juntas en numerosas frases y están enlazadas al mismo nivel, pero otras tienen mucha mayor distancia (comparten menos frases comunes). Con esta estructura de relación semántica podemos agrupar las conversaciones y obtener “clústers” o grupos de conversaciones.

dendograma

Un script que requiere más interpretación humana

La ejecución de este script tiene dos partes, una en la que introducimos los parámetros de búsqueda para que el script haga su trabajo, pero una segunda que nosotros tendremos que hacer el análisis de forma más intuitiva (pero aprovechando la capacidad de las librerías de R para hacer el análisis clúster). En esta parte interpretativa veremos el dendograma y decidiremos cuántos clústers queremos (entre 2 y 8), según el número de palabras que veamos y cómo intuitivamente veamos que podrían agruparse. Una vez el script elabora el número de clústers que creemos mejores, podremos etiquetarlos e identificarlos para crear una etiqueta que ayude a terceros a poder interpretar el dendograma.

dendograma.R

################################################
### Cargamos las librerías necesarias
## twitter para descargar datos
## tm (Text Mining)
## RColorBrewer para tener librerías de colores
## wordcloud para nubes de palabras
###############################################

library(twitteR)
require(tm)
library(wordcloud)
library(RColorBrewer)

## ATENCIÓN USUARIOS DE WINDOWS:
## Esta parte es necesaria para los usuarios de Windows para 
## superar problemas de permisos

library(RCurl)
options(RCurlOptions = list(cainfo = system.file("CurlSSL", "cacert.pem", package = "RCurl")))
u = "https://raw.github.com/tonybreyal/Blog-Reference-Functions/master/R/bingSearchXScraper/bingSearchXScraper."
x = getURL(u, cainfo = system.file("CurlSSL", "cacert.pem", package = "RCurl"))

## Autentificación con twitter, tienes que tener tu propio fichero .RData acreditado

load("rcredenciales.RData") ## El tener tu propio .RData autentificado es una parte compleja
registerTwitterOAuth(tw)

##################### Aclaración para conseguir tu .RData ######################
## Una explicación de cómo obtenerlo:
##
## 1.- Necesitas darte de alta como desarrollador 
## en dev.twitter.com
## 2.- Da de alta una app y consigue tu consumer key 
## y tu secret ke y.
## 3.- Sigue los primeros pasos hasta conseguir el .RData:
## http://aaronccrowley.wordpress.com/2013/05/27/extracting-data-from-twitter-with-r/
##
## Alerta: si eres usuario de windows en el script citado 
## tendrás que incluir las líneas 42 a 50 del actual Script al inicio del Script
## indicado en la web
####################################################

############## Definición de variables  ###########################

## k es un número
## k define el número de clústers
## k es preguntado una vez elaborado el dendograma

## terminobusqueda = String
## Es el nombre del usuario del que quiero buscar su nube de tags

terminobusqueda =  readline("¿Cuál es el término de la búsqueda? ") 
# pequeño código para evitar dejar vacío el término de búsqueda
while (terminobusqueda =="") terminobusqueda =  readline("Por favor, define un término de búsqueda ")

## TweetNumber = Number
## Es el número de twitts en los que quiero ahondar

TweetNumber =  readline("¿Cuantos tweets quieres analizar? ")
TweetNumber = as.numeric(unlist(strsplit(TweetNumber, ",")))

## geografico = string (number(latitud), number(longitud), number+km(radio geogr?fico)) o NULL
## Es una variable que permite acotar las búsquedas a twitts geolocalizados

print ("Ejemplos de datos geográficos, Barcelona, 150km a la redonda:41.378476,2.1701334,150km")

geografico =  readline("Ámbito geográfico, dejar vacio si no se quiere geolocalizar ")
if (geografico=="") geografico=NULL

## palabrasstop -> string
## String de palabras que no queremos que aparezcan en el dendograma

palabrasstop = readline("Define las palabras que no quieres que aparezcan (separadas por espacios y en minúsculas) ")
palabrasstop = unlist(strsplit(palabrasstop, split=" "))

############################################
## Ejecutamos la descarga de datos de twitter
############################################

mach_tweets = searchTwitter(terminobusqueda, n=TweetNumber, geocode=geografico)
mach_text = sapply(mach_tweets, function(x) x$getText())

########## Establezco el sistema local del idioma (solo para usuarios Windows)
Sys.setlocale("LC_CTYPE", "spanish")

## Función implícita no definida 
## Transforma una Lista() en otra Lista()
## Objetivo: Sustituye los carácteres mal descargados por
## carácteres latinos que los scripts puedan leer sin problemas

mach_text = tolower(mach_text)
mach_text = gsub("ã¡", "a", mach_text)
mach_text = gsub("ã©", "e", mach_text)
mach_text = gsub("ã³", "o", mach_text)
mach_text = gsub("ãº", "u", mach_text)
mach_text = gsub("ã±", "ñ", mach_text)
mach_text = gsub("ã¨", "e", mach_text)
mach_text = gsub("ã²", "o", mach_text)
mach_text = gsub("ã", "i", mach_text)

## Eliminamos los términos de b?squeda
## Transformamos una lista en otra lista

mach_text = gsub(terminobusqueda, "", mach_text)

## Se define la función clean.text
## Transforma una Lista() en otra Lista()
## Objetivo: Filtra palabras (evita URL, @usuarios, etc...)
##

clean.text = function(x)
{
  # tolower
  x = tolower(x)
  # remove rt
  x = gsub("rt", "", x)
  # remove at
  x = gsub("@\\w+", "", x)
  # remove punctuation
  x = gsub("[[:punct:]]", "", x)
  # remove numbers
  x = gsub("[[:digit:]]", "", x)
  # remove links http
  x = gsub("http\\w+", "", x)
  # remove tabs
  x = gsub("[ |\t]{2,}", "", x)
  # remove blank spaces at the beginning
  x = gsub("^ ", "", x)
  # remove blank spaces at the end
  x = gsub(" $", "", x)
  return(x)
}

### Hacemos la limpieza de texto

mach_text = clean.text(mach_text)

# Construimos un corpus
mydata.corpus = Corpus(VectorSource(mach_text))

# volvemos a asegurar que las palabras estarán en minúsculas
mydata.corpus = tm_map(mydata.corpus, tolower) 

# eliminamos la puntuación
mydata.corpus = tm_map(mydata.corpus, removePunctuation)

# elimina palabras comunes (del castellano y del catalán)
my_stopwords = c(stopwords('spanish'),stopwords('catalan'), palabrasstop)
mydata.corpus = tm_map(mydata.corpus, removeWords, my_stopwords)

# Transformamos elcurpus en una matriz
mydata.dtm = TermDocumentMatrix(mydata.corpus)

# inspeccionamos la matriz
mydata.dtm

# inspeccionamos las palabras más relevantes
findFreqTerms(mydata.dtm, lowfreq=30)

# Eliminamos los sparse terms para simplificar los clusters
# Si aumentamos el valor de sparse saldrán más palabras.
# Con la actual configuración tendremos entre 10 y 30 palabras
# Que es ideal para poder trabajar.
mydata.dtm2 = removeSparseTerms(mydata.dtm, sparse=0.95)
# Transformamos la matriz en un data frame
mydata.df = as.data.frame(inspect(mydata.dtm2))

# hacemos algunas comprobaciones sobre el dataframe
nrow(mydata.df)
ncol(mydata.df)
mydata.df.scale = scale(mydata.df)
d = dist(mydata.df.scale, method = "euclidean") # definimos la distancia
fit = hclust(d, method="ward")  # definimos el método para hacer clusters

############################
## Dibujamos el dendograma
############################

## Parte 1
# Generamos el dendograma

plot(fit, main="") 

# Ponemos un título al dendograma
titulo = c("Dendograma en twitter de ", terminobusqueda)
title(titulo)

## Parte 2
## Dibujamos los clusters
k =  readline("¿Cuántos clústers quieres dibujar? ") 
k = as.numeric(unlist(strsplit(k, ",")))
rect.hclust(fit, k, border=rainbow(k))

### Parte 3, interpretación del dendograma

## Pedimos al usuario que identifique
## cada cluster y le de una definición para
## poder escribir la leyenda

cluster1 = NULL
cluster2 = NULL
cluster3 = NULL
cluster4 = NULL
cluster5 = NULL
cluster6 = NULL
cluster7 = NULL
cluster8 = NULL

cluster1 =  readline("¿Cómo defines el primer clúster? ") 
cluster2 =  readline("¿Cómo defines el 2o clúster? ") 
if (k>2) cluster3 =  readline("¿Cómo defines el 3er clúster? ")  
if (k>3) cluster4 =  readline("¿Cómo defines el 4o clúster? ") 
if (k>4) cluster5 =  readline("¿Cómo defines el 5o clúster? ") 
if (k>5) cluster6 =  readline("¿Cómo defines el 6o clúster? ") 
if (k>6) cluster7 =  readline("¿Cómo defines el 7o clúster? ") 
if (k>7) cluster8 =  readline("¿Cómo defines el 8o clúster? ") 

## Dibujamos la leyenda segun los criterios del
## usuario.

labels.clusters = c(cluster1, cluster2, cluster3, cluster4, cluster5, cluster6, cluster7, cluster8)
legend("topright", legend = labels.clusters, fill = rainbow(k), title = "Clusters", box.col = "black", bg="transparent")