El presente documento se corresponde al Projecto Final de Experto del postgrado en Bussiness Inteligence del Centro Universitario de Tecnología y Arte Digital, U-TAD. Tiene como objeto el análisis de redes de usuarios de youtube dentro de los canales de tendencia política en Youtube España.
La motivación de la presente investigación es el creciente número de cuentas falsas, trolls o usuarios que sirven los intereses de partidos políticos.
El uso de las redes sociales en política no es ninguna novedad. El caso más flagrante quizás sea en las elecciones del 2016 cuando Donald Trump consiguió sentarse en el despacho Oval contra todo pronóstico. Continúa en tela de juicio la manipulación de la campaña por parte del gobierno ruso:
“Una investigación realizada por The New York Times, y los análisis de la firma de ciberseguridad FireEye, revelan algunos de los mecanismos que supuestos operadores rusos utilizaron por medio de Twitter y Facebook para difundir mensajes en contra de Clinton y promover el material hackeado” (NYtimes, septiembre del 2017).
Se dice que la inserción masiva de tweets, usuarios falsos, perfiles de Facebook falsos y memes durante la campaña hizo inclinarse la balanza hacia el lado republicano. Todo esto habría sido orquestado desde el número 55 de calle Sávushkina en San Petesburgo, lugar donde se ubica la Agencia de Investigación de Internet o IRA.
Sede del IRA
Hoy en día todos estos rumores han sido constatados gracias al Data Science. Numerosos data scientist han aportado sus análisis y se ha constatado la influencia de esta oleada de desinformación en las elecciones del 2016. (Añadir que la práctica de Sentiment Análisis con el profesor Pedro Concejero lo hicimos sobre este caso.)
Desde el 2016 se ha acusado de las mismas manipulaciones en distintas elecciones alrededor del mundo: Francia, Ucrania y por supuesto, España. De hecho, en la victoria de Mariano Rajoy también en Junio de 2016 se le dió gran al papel jugado por las redes sociales.
Había nacido una nueva arma electoral… y a su vez la era de la “sobredesinformación”.
Con todo lo expuesto anteriormente es sobradamente conocido (y cada día aparecen nuevos casos y situaciones) el uso de fake accounts en Twitter. Por eso mismo es motivo de este documento el análisis en otras redes sociales. Entre las más usadadas como Reddit o Instagram, se ha decidido centrar la investigación en Youtube.
Es decir, esta tesis pretende analizar los canales de Youtube España y mostrar el uso o no de fake accounts o de usuarios que sean cibervoluntarios de partidos políticos.
El primer paso es tomar la decisión de que canales vamos a analizar. Canales de partidos políticos, periódicos y en general canales de influencia política, a ser posible, relacionados entre sí. ¿Qué quiere decir esto? Que sean canales que te haya recomendado un canal original. Ejemplo: Podemos -> La Tuerka.
En primer lugar vamos a importar nuestro archivo .txt con la lista de canales seleccionados usando la opción file -> import dataset -> from text (readr).
Lista = read_csv("Lista.txt")
## Parsed with column specification:
## cols(
## `CANALES:` = col_character()
## )
## Warning: 2 parsing failures.
## row col expected actual file
## 22 -- 1 columns 2 columns 'Lista.txt'
## 30 -- 1 columns 2 columns 'Lista.txt'
Lista
## # A tibble: 31 x 1
## `CANALES:`
## <chr>
## 1 1. VOX España
## 2 2. OK Diario
## 3 3. La Vanguardia
## 4 4. NTN24
## 5 5. El Diario (sugiere bastantes canales)
## 6 6. Caso Aislado ELIMINAR
## 7 7. El Mundo
## 8 8. El País
## 9 9. La Razón.
## 10 10. La Tuerka
## # ... with 21 more rows
htmlTable::htmlTable(Lista,)
| CANALES: | |
|---|---|
| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 | LacontraTV |
| 16 | Inocente duke |
| 17 | El teatro de will |
| 18 | Spanish revolution |
| 19 | Canales de izquierda: |
| 20 | Quetzal |
| 21 | Cuellilargo |
| 22 | En la frontera |
| 23 | Fort Apache |
| 24 | NTMEP No te metas en Política (programa de humor de izquierdas) |
| 25 | Polonia (TV3) |
| 26 | Canales de derecha/liberales: |
| 27 | Wall Street Wolverine |
| 28 | InfoVlogger |
| 29 | UTBH |
| 30 | VisualPolitik (a pesar de que hacen buenos videos y aportan mucha información en determinados temas |
| 31 | El club de los Viernes (canal libertario) |
Se ha tomado la decisión de analizar los 14 primeros canales. En parte debido a la gran cantidad de información que vamos a manejar.
En este punto comezamos a entrar en materia. Lo que vamos a hacer es obtener las estadísticas de cada canal; para tener una valoración general, a simple vista y ordenada en un dataset de todos los canales que vamos a analizar.
Vamos a mostrar distintas formas de análisis y explicar por qué se han usado unas y otras.
Para esto vamos a cargar tres librerías fundamentales para este análisis.
library(devtools) #herramientas de desarrollador
## Loading required package: usethis
library(tuber) #youtube en R
library(vosonSML) #Sentiment Media Laboratory.
La librería devtools es una librería necesaria para descargar librerías hechas por desarrolladores. Tuber y VosonSML son dos librerías que descargan datos de Youtube y, por extensión, de Google. Es por esta razón por la que debemos darnos de alta como desarrolladores en ambos servicios y crear una API en casa uno.
Google ofrece muchos servicios de desarrolladores:
Conjunto de APIS de google.
Nosotos necesitamos darnos de alta dentro de Youtube. En el siguiente link se encuentra el YouTube Data API Overview, la introducción a la API de Youtube. Darse de alta es algo confuso pero paso a paso puede realizarse perfectamente. https://developers.google.com/youtube/v3/getting-started
Para darnos de alta necesitaremos unas credenciales. Esto es importante porque son las que luego necesitamos incluír en R para poder usar las librerías. Estas credenciales son:
- Client ID
- Cliente secret.
- ApiKey.
Estas son para Google; en caso de querer utilizar una API de Google relacionada con otros servicios de Google (como por ejemplo Google Maps para hacer ruteos) podemos usar la misma.
Una vez que se cubren todos los datos necesarios; tenemos una pantalla general con nuestra API como esta:
API en GoogleDevs
Una vez que tenemos nuestro proyecto" (vease en la imagen como el nombre de la API es “Projecto U-TAD”) ya podemos comenzar a obtener nuestros datos de los canales.
El primer paso es identificarnos en Youtube con nuestra key:
apikey <- "AIzaSyBh8VFmf7FaA6d8PIADsTi8vVrVGEbE4Xg"
key <- Authenticate("youtube",
apiKey=apikey)
client_id <- "412086871179-ivmhh6p61ke87du52jco6clff9fbqpc8.apps.googleusercontent.com"
client_secret <- "6Qcf8Z0wF_-BYI-veMM-0hdo"
# use the youtube oauth
youtubeAuth = yt_oauth(app_id = client_id,
app_secret = client_secret,
token = '')
Al identificarnos aparecerán tres archivos dentros del directorio que estemos usando:
-.httr-oauth
-.gitignore
-.Rhistory
Antes de volver a identificarnos en otra sesión se deben de eliminar los antiguos o habrá un error. Si la identificación ha sido correcta se abrirá una pestaña en el navegador indicándolo.
Ahora descargamos las estadísticas de cada canal, podemos ir uno a uno o generar una variable con todos los channel ID (pero deben de ser vectores).
Vamos a mostrar el ejemplo usando un único canal. Se ha elegido el de VOX debido a su alto contenido en redes sociales. En este estudio nos centraremos en el de VOX y en el de PODEMOS.
NOTA: Esto no se pone como un chuck debido al límite de peticiones de la API de YOUTUBE
#Crear un dataset con el nombre del canal, usando s channel id. (Se encuentra al final de la URL del canal o, si la tienen escondida, puede encontrarse en el código fuente de la página.)
VOX = list_channel_resources(filter = c(channel_id = "UCRvpumrJs0qY1xLzeU0Ss1Q"), part="contentDetails")
#Playlists del canal:
playlist_id = VOX$items[[1]]$contentDetails$relatedPlaylists$uploads
#Obtener los videos de la playlist
vids = get_playlist_items(filter= c(playlist_id=playlist_id))
#Lista con los ids de cada video.
vid_ids = as.vector(vids$contentDetails.videoId)
#Scrapping:
get_all_stats <- function(id) {
get_stats(id)
}
#Ontenemos las estadísticas y generamos el dataframe.
res = lapply(vid_ids, get_all_stats)
res_df = do.call(rbind, lapply(res, data.frame))
head(res_df)
API en GoogleDevs
Conseguir esta información nos resulta muy útil para conocer datos macro de cada canal y a su vez, saber en que videos debemos centrarnos a la hora del análisis individual. Con este dataset podemos comenzar a sacar nuestras primeras conclusiones. El único problema es que no saca el nombre del video, solo el id.
Disponemos de otra opción; aunque menos ordenada en la que se trata de buscar directamente en Youtube con la función yt_search:
#Buscar videos
Vox = yt_search("Vox")
head(Vox)
Debemos de repetir este procedimiento para sacar las estadísticas de todos los canales que deseemos. Posteriormente habrá que guardar el dataset en un archivo.
Aunque nos ofrece bastante información, como digo, está bastante desordenada.
Para realizar este análisis lo que realmente necesitamos obtener son los comentarios de cada usuario.
Conocer el número de comentarios de un usuario, el tipo de comentarios que hace, en qué videos postea… es la forma de identificar si es un usuario normal o si es un “cibervoluntario” de un partido.
Para obtener los comentarios de cada canal vamos a usar la función Collect de tubeR:
# Podemos utilizar esta función para crear una lista con los ids de uno o varios videos.
video_id = GetYoutubeVideoIDs(c("https://www.youtube.com/watch?v=qZc5ozdiBCE"))
# o bien podemos indicarlos de forma manual:
video_id2 = c("qZc5ozdiBCE")
# Este pipe usara nuestra key de youtube y recabará toda la información; incluyendo los comentarios.
comentarios_vox = key %>% Collect(video_id2)
head(comentarios_vox)
Introducimos la librería Kable que nos permite mostrar tablas visualmente más atractivas.
library(kableExtra)
kable(comentarios_vox) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))
Aunque no lo cargamos y lo mostramos como imagen porque ralentiza mucho R.
De esta forma tenemos un dataset muy interesante con el que empezar a trabajar:
Por último guardamos el dataset en un archivo .csv
write.csv(comentarios_vox, "Vox_Espana.csv")
La idea es repetir este procedimiento para obtener todos los comentarios de los 14 canales que estamos estudiando. Esto ya nos resulta sencillo y al terminar obtendremos algo más o menos así:
El siguiente paso es unir todos los archivos en un único dataset. Esto lo resolvemos, a mi parecer, de una forma bastante elegante:
setwd("C:/Users/zapic/Documents/U-Tad/Business Analytics/Proyecto/Canales/comentarios")
getwd()
files = list.files(path= "C:/Users/zapic/Documents/U-Tad/Business Analytics/Proyecto/Canales/comentarios")
tmp = lapply(files, read.csv, header = TRUE)
comentarios = do.call(rbind, tmp)
write.csv(comentarios, "comentarios.csv")
Eliminamos los 16 primeros comentarios ya que son en inglés y no aportan nada a nuestro análisis:
comentarios = comentarios[-c(1:16), ]
head(comentarios)
El siguiente paso es comenzar a limpiar el dataset. Hay columnas como el número de likes o el momento en el que se escribió el comentario que, a pesar de dar gran información; no nos resultan útiles para nuetro análisis.
comentarios = select(comentarios, 3,4,8,12,13)
comentarios
De esta forma nos quedamos con:
- El comentario que nos proporcionará información sobre los sentimientos de cada ususario.
- El nombre de usuario.
- El número de replies, que indica cuánto influencia ha tenido el comentario.
- El id del comentario.
- El parentID. Si el parentid es 0, es que es un comentario original y no un replie.
write.csv(comentarios, "comentarios_mejor.csv")
Se puede seguir indagando en este dataset, por ejemplo, podemos filtar por comentarios que hayan tenido al menos 10 respuestas; es decir un alto grado de interacciones:
comentarios_min_10_reply = comentarios[comentarios$ReplyCount>=10,]
write.csv(comentarios_min_10_reply, "comentarios_min_10_reply.csv")
Lo ideal es revisar este dataset para ver que información contiene:
summary(comentarios_min_10_reply)
Podemos ver que hemos pasado de un dataset de casi 64000 lines a tan solo 850. E incluso podemos observar que un comentario ha tenido 418 respuestas.
Vamos a comenzar realizando una identificación manual, filtrando el dataset:
sapply(comentarios,class) %>% data.frame()
summary(comentarios)
Además si hacemos un summary siendo las clases factores obtenemos la siguiente información:
Vemos que dentro de los 64000 comentarios hay una serie de usuarios que comentan mucho más que los demás. Es así como poemos empezar a identificar a nuestros sospechosos. Comprobamos:
comentarios %>% count(comentarios$AuthorDisplayName, sort = TRUE)
Incluso vamos a filtrar por más de 50 comentarios. Estos serían los llamados comentaristas “profesionales”. O nuestra target, cibervoluntarios.
cp = comentarios %>% count(comentarios$AuthorDisplayName, sort = TRUE)
cp = subset(cp, n >= 50,)
cp
Vamos a crear un dataset con los comentarios del usuario que más comentarios tiene.
Raul_Sanchez = subset(comentarios, AuthorDisplayName == "Raul Sanchez", select = c("CommentID","ParentID"))
write.csv(Raul_Sanchez, "Raul Sanchez.csv")
Así hemos empezado a indagar en nuestros usuarios, a sacar algunas conclusiones y a ponernos algunos objetivos. Y, aunque tenemos algunos targets interesantes como “Kike” o “Raul Sanchez”, no tienen porqué ser “cibervoluntarios”.
Lo que estamos buscando son Usuarios clave y sus relaciones entre ellos.
Para ver estas relaciones vamos a seguir usando el dataframe “comentarios”, y usando la columna ParentID vamos a ver las relaciones entre los usuarios usando un JOIN:
#Para no trabajar dobre el df comentarios vamos a crear uno gemelo:
comsSI_NA = comentarios
comsSI_NA
#Unimos
join = merge(x = comsSI_NA, y = comsNo_NA, by = "ParentID")
head(join)
write.csv(join,"join.csv")
Nos quedamos con las dos columnas que nos interesan: AuthorDisplayName.x y AuthorDisplayName.y
join2 = select(join, 3, 7)
head(join2)
write.csv(join2, "relaciones_usuarios.csv")
Ejemplo de funcionalidad. Estas son todas las interacciones del usuario Kike con otros usuarios:
join3 = join2[join2$AuthorDisplayName.x == "Kike",]
join3
En este momento vamos a empezar a necesitar un programa especializado en relaciones e interacciones entre usuarios pero su uso lo aplazamos para el punto 6.
VosonSML (Antes llamada SocialMediaLab) es una libreria para el análisis de social media. Es muy virtuosa y puede recopilar datos de: Instagram, Reddit y Youtube. En este punto vamos a recopilar los usuarios las interacciones entre los mismos usando esta librería.
VosonSML es una librería que ha sido desarrollada en GITHUB, por esta misma razón usareremos la librería gh, que es la API de github. VosonSML ya había sido cargada antes.
library(gh)
Este método continúa descargando datos de youtube; por lo que es importante asegurarse que seguimos con la autentificación con la API de youtube activa.
apikey <- "AIzaSyBh8VFmf7FaA6d8PIADsTi8vVrVGEbE4Xg"
key <- Authenticate("youtube",
apiKey=apikey)
client_id <- "412086871179-ivmhh6p61ke87du52jco6clff9fbqpc8.apps.googleusercontent.com"
client_secret <- "6Qcf8Z0wF_-BYI-veMM-0hdo"
# use the youtube oauth
youtubeAuth = yt_oauth(app_id = client_id,
app_secret = client_secret,
token = '')
El proceso es similar al anterior, comenzamos incluyendo el id de un canal:
channel = list_channel_resources(filter = c(channel_id = "UC0oux2kiDINhX76StpGpA8g"), part="contentDetails")
# Playlists:
playlist_id = channel$items[[1]]$contentDetails$relatedPlaylists$uploads
# Pone los videos de las playlists
vids = get_playlist_items(filter= c(playlist_id=playlist_id))
# Video ids
vid_ids = as.vector(vids$contentDetails.videoId)
# Función para scrapear las estadísticas de los videos:
get_all_stats = function(id) {
get_stats(id)
}
# Toma las estadísticas y crea un dataframe
res <- lapply(vid_ids, get_all_stats)
res_df <- do.call(rbind, lapply(res, data.frame))
head(res_df)
Recordar que hace la recopilación de los últimos 50 videos.
ids = res_df[1]
ids
ids = as.data.frame(t(ids))
ids
Recortamos el df a 45 por una razón que explicamos más adelante.
ids = ids[1:45]
ids
videos = GetYoutubeVideoIDs(ids)
En esta función es importante indicar que nos estamos autenticando en la red social “Youtube”:
youtubeAuth = Authenticate("youtube", apiKey = "AIzaSyBh8VFmf7FaA6d8PIADsTi8vVrVGEbE4Xg")
Y la siguiente es la función importante, Collect, es parecida a la que usamos antes pero no hace lo mismo. En esta función crea un dataset y guarda este objeto en nuestro directorio. Para crear el dataset scrapea los últimos 50 videos de un canal. Para scrapear toda esta información nuestro scrip hace peticiones “queries” a la API de Youtube (recordemos que forma parte del enviroment de APIS de google). Esta API de desarrollador “novel” es gratuíta por lo tanto tenemos una limitación de 10.000 queries por día. Al llegar a este límite no podemos scrapear más.
El problema de usar este método que aunque es muy útil, se llega rápidamente a este límite, por es reducimos de 50 a 45 aunque en canales como el de Podemos con mucha interacción, tan solo se pueden scrapear unos 30 videos. La solución es pedir un aumento del límite, cosa que hice para realizar este documento, sin éxito, o comprar la versión de desarrollador profesional. Las estadísticas de tus peticiones puedes verlas en la página de tu projecto, que se mostró al inicio del documento.
youtubeData <- youtubeAuth %>%
Collect(videoIDs = videos, writeToFile = TRUE, verbose = FALSE, maxComments = 1e+10)
Al finalizar escribe en nuestor directorio un archivo .rds
Este archivo es el que vamos a usar para generar un grafo de nodos. En esta librería lo llaman red de actores y con la funcion Create se usa “actor” para crear la relación entre los actores o nodos del archivo .rds que hemos generado. También contempla la opción de crear activity, semantic o twomode.
Una vez hecho esto vamos a generar el archivo del grafo. Este es el archivo que vamos a utilizar en gephi.
Tambien podemos generar grafos es con la librería igraph, creada especialmente para generar este tipo de visualizaciones. No es tan efectiva como gephi pero te da una idea general.
library(igraph)
table(E(actorGraph)$edge_type)
Ejemplos con el canal “La Tuerka”:
V(actorGraph)$color <- ifelse(V(actorGraph)$node_type=="video", "red", "grey")
png("LaTuerka_actor.png", width=600, height=600)
plot(actorGraph, vertex.label="", vertex.size=4, edge.arrow.size=0.5)
dev.off()
Los puntos rojos son videos y los puntos grises son usuarios que interactúan en ese vídeo y las interacciones entre usuarios.
g2 <- delete.edges(actorGraph, which(E(actorGraph)$edge_type!="reply-comment"))
#By deleting edges other than "reply-comment", we now have 417 isolates
#> length(which(degree(g2)==0))
#[1] 417
g2 <- delete.vertices(g2, which(degree(g2)==0)) #remove the isolates
V(g2)$color <- "grey"
ind <- tail_of(actorGraph,grep("arson|backburn|climate change",tolower(E(g2)$vosonTxt_comment)))
V(g2)$color[ind] <- "red"
png("La_Tuerka_actor_reply.png", width=600, height=600)
plot(g2, vertex.label="", vertex.size=4, edge.arrow.size=0.5)
dev.off()
Este grafo representa todos los replies entre usuarios del canal.
Gephi es una herramienta open-source desarrollada en Java para visualizer y analizar grandes gráficos de red. Usa un motor de renderizado 3D para mostrar gráficos en tiempo real y permite explorer, analizar, filtrar, clusterizar, manipular y exporter diversos tipos de gráficos.
Con esta herramienta podemos abrir tanto .csv como archivos con formato .graphtml que han sigo generados en el punto anterior con la función Create(“actor”) de la librería vosonSML.
Cuando abrimos gephi tenemos un menú como este:
Debemos cargar el archivo que queramos analizar. En el menu de arriba, en laboratorio de datos; podemos ver cómo gephi ha cargado los datos en formato “excel”. A la derecha tenemos un menú con opciones de medición, como el grado medio, la densidad de la red o la modularizad que digamos que mide la clusterización de un grafo. Esta opción es muy útil y la usaremos en nuestro análisis.
Gephi es una herramienta que, a pesar de ser poco intuitiva y que requiere cierta práctica para su manejo, puede llegar a ser muy útil tanto de forma analítica como visual.
Como habíamos comentado antes; vamos a crear los grafos en gephi usando los archivos generados por los dos anteriores métodos.
Para usar este método vamos a generar otro data frame aparte del que ya tenemos con un código más elegante:
library(dplyr)
df = as_tibble(read.csv("comentarios_mejor.csv"))
df = df %>% select(-ParentID) %>% inner_join(df %>%
filter(!is.na(ParentID)) %>%
select(ParentID,ReplyAuthor=AuthorDisplayName),
by=c('CommentID'='ParentID'))
df = df[df$AuthorDisplayName %in% c("Kike", "Raul Sanchez","ANGEL FERNADEZ CHAPERO COMO SU PADRE AJJAJAJAJA", "Victor Jiménez Ledesma","Rene Rf", "Diccionario VOX","dinos algo porfa","santiago garcia toribio", "Miguel Rubiños","luis ranera Higuera", "Moon Light","pruebas py", "Agustín Gómez", "Spanish Patriot", "Gabi Marton","Jondalar Wilt","Montse Montse","De todo sabe,de nada entiende.", "aurelio mac augits", "TheXaoMustGoOn","daniprott","S H","VOX ES FACISMO VOX ES FASCISMO", "Alan the leopard", "Diego Canto", "Ismael Ardilla", "Monarquía Española", "Mr. Teflon", "Laly", "Pina Garofalo", "Alfredo Cañizares", "Maria Cuntz", "Candelas Vaquerizo", "Alex BP", "Eugenio Aguado", "Ana B", "PEDRO LOPEZ","Leonidas Marronidas", "Roberto Casabán - Finanzas - Crecimiento Personal ", "el vengador justo", "Sergio González González ", "Oswald Mosley", "Tic tac Galapagar","Samuel Gallop","Noviembre","César Domínguez", "Naji Handor","Víctor", "Alejandro Franco","Jose Ab", "jaime leon", "Estrella *", "Rosana Fernández", "tiger elchapero", "El profesor Moreno CEIP La señora del Rosario", "jjj h", "Francisco Javier Vila Pizzarro", "Cri Cri", "Rambo España", "Escuadrón Rapador Bo bobo", "no me manipules educación ya", "VIGILANTE", "LIBERTAD ESPAÑOLA", "Robert Jarni", "libertad", "El cazador de TROLLS"),]
df_2=df %>%
group_by(AuthorDisplayName) %>%
count() %>%
filter(n>10) %>%
select(AuthorDisplayName) %>%
inner_join(df,by='AuthorDisplayName')
df_3 = df_2 %>%
group_by(ReplyAuthor) %>%
count() %>%
filter(n>10) %>%
select(ReplyAuthor) %>%
inner_join(df_2)
df_3 %>% select(Source=AuthorDisplayName,Target=ReplyAuthor) %>% group_by(Source,Target) %>% count() %>% write.csv('df_4.csv',row.names = F)
df_4 =read.csv("for_gephi_filtered.csv")
El csv generado lo cargamos en gephi:
Como se decía Gephi no es un programa muy intuitivo y siempre es bueno probar las distintas opciones. Para este proyecto en concreto debemos de usar en el menú importa como la opción de Lista de Adyacencia.
Una vez hecho esto, vemos una pantalla que nos presenta el informe de importación que nos dice el número de nodos y de aristas que se van a generar. Esto es extremadamente importante ya que nos ofrece una primera idea para saber si el grafo será útil o no ya que podemos tener demasiadas pocas uniones o si hay demasiados nodos gephi directamente no será capaz de cargarlos ya que en ese sentido es un programa limitado.
Una vez importando debemos “jugar” con las configuraciones. En este caso he ejecutado el grado medio y la modularidad, que serviran para clasificar el grafo.
En el menú superior izquierda, Apariencia, en la paleta de color utilizaremos la partición “modularity class” (por eso ejecutamos antes modularidad) y el tamaño de los nodos los clasificaresmos por el grado de entrada. Esta opción es muy importante ya que nos permite visualizar fácilmente que nodos (usuarios/actores) tienen más interacciones. Por último, en el menú inferior de distribución, elegimos el tipo de distribución del grafo. En este caso he usado Force Atlas 2 porque es de las más comunes pero más adelante podremos ver otras.
Al ejecutar obtendremos algo más o menos así:
El siguiente paso sería previsualizar el grafo. Esto es un tipo de “render” que muestra el grafo creado con un aspecto más atractivo y con las etiquetas de los nodos. Podemos jugar con las opciones de visualización en la pestaña configuración y modificar tamaños, colores, vectores, etc.
Si lo hacemos bien obtenemos el siguiente grafo:
En este grafo salen con nombre los usuarios con más interacciones (los actores principales) y representados con un número los usuarios más irrelevantes.
Aunque desde lejos marea, es debido a la disposición de las aristas. Este es un grafo con buen aspecto, una buena clusterización y que si ampliamos y analizamos cluster a cluster y las uniones entre ellos podemos ir viendo como interaccionan los usuarios de los que hemos scrapeado su información.
De todas formas este grafo tiene muchos nodos y se puede generar algo más concreto transformando el dataframe original. Filtrando y concretando más podemos obtener el siguiente grafo:
E incluso podemos llegar a este nivel de afinamiento que muestra muy bien las interacciones entre los principales actores:
Aquí vemos a todos los usuarios con los que nos habíamos encontrado en el punto 3. Muestra sin ninguna duda el grado de importancia de cada usuario mucho mejor que con una lista y las relaciones entre ellos y su clusterización.
Podemos ver que Raul Sanchez tiene una gran importancias, tiene un nodo grande y está en el centro, en cambio “Kike” que es el segundo con más comentarios, tienen poca repercursión. Mucha menos que Victor, MontseMontse o “dinos algo porf” que aunque tengas menos comentarios consiguen mas replies.
Ahora vamos a utilizar el archivo .graphtml generado por la libreria vosonSML en Gephi, como habíamos dicho antes teníamos dos métodos.
En el punto anterior se explicó el uso de gephi, no varía nada por lo que no me voy a detener en los pormenores.
Para no repetir el grafo del canal La Tuerka, hemos optado en este caso por mostar un grafo no de un canal, si no de un video. Ya que tambien podemos hacer el scrapping de un solo video. Tenemos menos información pero al ser menos es más clara.
El video en question está en el canal de Vox y se titula “10 medidas para Galicia #GaliciaEsVerde”. Tiene como id: -kF8-ZraXvY&t=2s
Una vez importado y ejecutado esa es la forma del grafo; a lo que ahora hay que añadir su previsualizacion:
Podemos ver que con tanta etiqueta es muy complicado visualizar el grafo completo; por lo tanto lo mostramos sin etiquetas:
Hemos podido ver que gephi es el último paso y el complemento perfecto para la librería vosonSML.
Por último dentro de curiosidad, mostramos como se verían tanto el canal de Vox como el de Podemos sin tocar nada del dataframe formado por sus últimos videos. Parece que en el fondo no son tan distintos.
En este punto vamos a generar disintos wordclous que nos permitan analizar las palabras más comentados. Es el paso previo al análisis de sentimientos, ya que de alguna manera es un sentiment analysis a grosso modo.
La definición de Wordcloud es: “Una nube de palabras o nube de etiquetas es una representación visual de las palabras que conforman un texto, en donde el tamaño es mayor para las palabras que aparecen con más frecuencia.”
El primer paso es cargar las librerías que vamos a usar para este proceso:
library(tidyverse)
library(tidytext)
library(quanteda)
library(NLP)
library(tm)
library(SnowballC)
library(wordcloud)
library(wordcloud2)
library(RColorBrewer)
Para representar los wordclouds lo que se ha hecho es realizar un proceso aplicable a todos los dataframe que hemos descargado usando el método mostrado en el apartado #3.2.
Vamos a utilizar de ejemplo los comentarios scrapeados del canal de Podemos.
text = read.csv("Podemos comments.csv")
Y nos quedamos únicamente con la columna que tiene los comentarios, en este caso no nos interesan usuarios ni replies.
text = select(text, 2)
head(text)
El siguiente paso es convertir el dataset en un corpus. Podemos decir que un corpus es generar un dataset en formato lista.
corpus = Corpus(VectorSource(text)) # formato de texto
# Checkeamos:
summary(corpus,
n=2)
Los siguientes pasos consisten en la limpieza de texto. Esto consiste en poner todo el texto del dataset de fomra uniforme, minúsculas, sin elementos extraños, sin puntuación… etc.
# lleva a minúsculas
d = tm_map(corpus, tolower)
# quita espacios en blanco
d = tm_map(d, stripWhitespace)
# quita la puntuación
d = tm_map(d, removePunctuation)
# quita los números
d = tm_map(d, removeNumbers)
Un punto importante son las stopwords. Son palabras que no deseamos que entren dentro del estudio. Artículos, adverbios…palabras comunes que no aportan datos significativos. Para esto existen diccionarios creados previamente o podremos cargar un archivo .txt con nuestras propias stopwords.
stopwords("spanish")
Creamos la llamada matriz de términos. Matriz que compara todos los tokens. Este proceso puede tardar un tiempo.
d = tm_map(d, removeWords, stopwords("spanish"))
# crea matriz de términos
tdm = TermDocumentMatrix(d)
findFreqTerms(tdm, lowfreq=20)
m = as.matrix(tdm) #lo vuelve una matriz
v = sort(rowSums(m),decreasing=TRUE) #lo ordena y suma
df = data.frame(word = names(v),freq=v) # lo nombra y le da formato de data.frame
Antes de mostrar el wordcloud podemos aprovechar un mostrar un gráfico de las palábras más frecuentes dentro del canal de Podemos.
### TRAZAR FRECUENCIA DE PALABRAS
barplot(df[1:10,]$freq, las = 2, names.arg = df[1:10,]$word,
col ="lightblue", main ="PALABRAS MÁS FRECUENTES", ylab = "Frecuencia de palabras")
wordcloud(words = df$word, freq = df$freq, min.freq = 6,
max.words=100, random.order=FALSE, rot.per=0.35,
colors=brewer.pal(8, "Dark2"))
Y lo exportamos como una imagen:
png("wordcloud_podemos.png",
width=2400,height=1620,units="px",
pointsize=24,bg="white",res=300)
wordcloud(words = df$word, freq = df$freq, min.freq = 6,
max.words=100, random.order=FALSE, rot.per=0.35,
colors=brewer.pal(8, "Dark2"))
dev.off()
Como hemos podido ver, no es difícil crear un wordcloud. Existe otra librería, wordcloud2 con la que podemos hacer nubes de palabras más dinámicos.
wordcloud2(df, size=0.8)
o en forma elíptica:
wordcloud2(df, size = 0.5, shape = "eliptic")
png("wordcloud_podemos3.png",
width=2400,height=1620,units="px",
pointsize=24,bg="white",res=300)
wordcloud2(df, size=1.2, ellipticity = 0.5)
dev.off()
A modo complementario vamos a añadir otros wordclouds que hemos obtenido:
Estos son obtenidos con el dataset con 64000 comentarios, es decir, es el wordcloud de los 14 canales. Información muy buena:
Y estos serían los wordclouds del canal de VOX:
Por último, y a modo de curiosidad vamos a incluír las palabras más frecuentes del canal de VOX:
Podríamos sacar ya una conclusión, la influencia de VOX en las redes sociales y en concreto en Youtube es enorme. Llegamos a esta conclusión viendo el WC de todos los canales donde la palabra VOX es de las principales.
El análisis de sentimientos es la interpretación y clasificación de las emociones (positivas, negativas y neutrales) dentro de los datos de texto utilizando técnicas de análisis de texto. Las herramientas de análisis de opiniones permiten a las empresas identificar la opinión de los clientes hacia productos, marcas o servicios en los comentarios en línea.
Lo hemos estudiado con el profesor Pedro Concejero estudiando discursos de Obama y Trump. En R son los ejemplo habituales y, si bien hay bastantes ejemplo en inglés; en español no hay tantos. Estos es debido a que aún hay necesidad de mejorar los diccionarios en otros idiomas.
if (!require("pacman")) install.packages("pacman")
pacman::p_load(sentimentr, dplyr, magrittr)
Utilizamos la librería sentimenr:
library(sentimentr)
En este caso vamos a cargar un dataset ya creado explícitamente para este apartado que solo contiene los comentarios de los 14 canales de los que vamos a hacer el análisis. De todas formas vamos a elegir una muestra aleatoria de 1000 comentarios porque no es necesario analizar los casi 64000 comentarios y será aleatoria para no escoger comentarios de un solo canal.
mytext = read.csv("comments.csv")
mytext
mytext = mytext[sample(nrow(mytext), 1000), ]
head(mytext)
Muesta aleatoria de 1000:
mytext = select(mytext, Comment)
head(mytext)
df_sent (dataframe sentiment) Es lo que queremos; es el dataframe con la columna que identifica si el comentario es positivo, negtivo o neutro:
mytext = get_sentences(mytext)
df_sent = sentiment(mytext)
head(df_sent)
Creamos un data frame solo con el valor del sentimiento.
sentimiento = select(df_sent, sentiment)
Lo transformamos a vector:
v_sent = dplyr::pull(sentimiento, sentiment)
Y calculamos la media:
Podemos ver que los comentarios son ligeramente negativos.
sent_todos = read.csv("comments.csv")
sent_todos = select(sent_todos, Comment)
sent_todos = get_sentences(mytext)
df_sent_todos = sentiment(sent_todos)
df_sent_todos_valores = select(df_sent_todos, sentiment)
v_sent_todos = dplyr::pull(df_sent_todos_valores, sentiment)
mean(v_sent_todos)
Para comprobar, hemos calculado el sentimiento con el dataset completo y nos da el mismo valor, por lo que damos la anterior muestra de 1000 como válida.
Este último procedimiento que se ha mostrado es de una forma muy sencilla. Para que realmente tuviese efecto el procedimiento completo sería:
- Segmentar los canales por tendencias políticas "izquierda" y "derecha".
- Pasar el sentimentR por cada grupo de canales. Así, si analizamos los canales de izquierda; podemos tomar los comentarios positivos como usuarios de izquierda y los negativos como de derecha. En los canales de tendencia de derecha, sería al revés.
- De esta forma podríamos segmentar y localizar la tendencia política de CADA USUARIO. wow!!
- Posteriormente podemos generar un grafo con gephi para ver las relaciones entre ellos y clusterizar por tendencias.
- De esta forma habremos hecho un grafo de forma correcta.
Con este análisis lo que se ha desarrollado es un procedimiento para analizar relaciones y sentimientos entre usuarios de Youtube España.
Este proceso es algo común dentro de Twitter, pero se ha querido dar un paso más dentro de la analítica de datos de la social media y aplicarla en Youtube.
Hay que añadir que Gephi es un programa muy útil para este tipo de aplicaciones pero es necesario un ordenador muy potente para mover una cantidad de nodos alta.
La principal conclusión que se puede sacar es que, a pesar de que existen interacciones entre usuarios que han quedado demostradas, por el momento no hay una cantidad masiva de usuarios que generen tantos comentarios como para sacar conclusiones. Es decir, a priori los partidos políticos no tienen cibervoluntarios que trabajen de manera exhaustiva dentro de la red social de youtube.
Otra conclusión que se puede sacar es algo que más o menos se sabía: el gran ascenso de los partidos Podemos y VOX se debe a su gran uso de las redes sociales, subiendo videos y generando actividad constantemente. De hecho, se puede ver que dentro de las 10 palabras más repetidas en el canal de Podemos es la palabra “Vox”.
Por último destacar que a futuro el uso de Youtube por parte de los partidos polítics irá en aumento y estre procedimiento se puede usar para ir comprando como crece el uso de estos canales y como evolucionan las relaciones entre usuarios y por supuesto, comprobar si finalmente los partidos utilizan cibervoluntarios en Youtube, al igual que lo hacen en Instagram o Twitter.