Já está disponível no IBPAD o curso online de Análise de Redes com R. O objetivo do curso é fazer com que interessados em Análise de Redes em Mídias Sociais aprendam e pratiquem como fazer tais análises utilizando linguagem R. O curso combina um breve conteúdo teórico, apresentando textos com conceitos importantes e com aulas gravadas onde comento o código usado para aplicar tais conceitos na prática.
O módulo 04 do curso aborda Simulação de redes e Detecção de comunidades por meio de Machine Learning. Para mostrar exemplos de situações em que você pode praticar esses conceitos, vou apresentar nesse post uma aplicação meio “pop”: a detecção de núcleos de personagens da novela mexicana A Usurpadora – uma das mais populares já transmitidas no Brasil e que vez ou outra ainda é reprisada no SBT – por meio de um algoritmo automático. Para quem não lembra do enredo ou nunca viu a novela, esta página da Wikipedia traz um resumo dos acontecimentos da obra.
Neste post, usaremos os seguintes pacotes:
library(tidyverse)
library(rvest) # web scraping
library(lubridate)
library(igraph)
library(xml2)
Modelagem da novela como uma rede social
Para analisar A Usurpadora como uma novela, iremos tratar seus personagens como vértices do grafo e as cenas entre todos os pares possíveis dos personagens que aparecem na cena como arestas. Ou seja, se em uma cena estão presentes os personagens Paola, Paulina e Carlos Daniel, então existem 3 diferentes pares: Paola – Paulina, Paola – Carlos Daniel e Paulina – Carlos Daniel.
Se não existe nenhuma aresta entre um par de vértices (personagens), significa que eles nunca estiveram presentes em uma mesma cena, de acordo com a metodologia da coleta dos dados.
Coleta dos dados
Felizmente, existe este site que traz o roteiro resumido dos capítulos da novela. Com isso, podemos extrair e limpar os textos no R:
url_roteiros <- “https://ndenovela.wordpress.com/a-usurpadora/”
# ler codigo fonte da pagina cod_fonte_scripts <- url_roteiros %>% xml2::read_html() # extrair texto nos paragrafos roteiro <- cod_fonte_scripts %>% html_nodes("p") %>% html_text() # remover espaços desnecessários roteiro <- str_squish(roteiro) # coletar elementos do vetor cujo tamanho é maior que 1 roteiro <- roteiro[nchar(roteiro) > 1] # amostra dos dados print(str_trunc(roteiro[1:4], 180, side = "right")) ## [1] "Capítulo 01 – exibido em 03/07/2000" ## [2] "Paulina é uma moça pobre, que vive com a mãe numa pequena choupana à beira mar e trabalha como arrumadeira num elegante clube de Cancun. Paulina vive um drama pessoal com a doen..." ## [3] "Capítulo 02 – exibido em 04/07/2000" ## [4] "Enquanto Paola se diverte no litoral ao lado de Luciano, seu amante, o marido Carlos Daniel e toda a família acreditam que ela está nos Estados Unidos submetendo-se a um tratame..."
O objeto roteiro
é um vetor de characters extraído do site. Precisamos então o transformar em um dataframe, isto é, uma tabela onde a primeira coluna corresponde ao índice do capítulo (ex.: elementos 1 e 3 do output acima) e a segunda coluna ao texto do capítulo (elementos 2 e 4).
Como os dados foram extraídos por meio de web scraping, nem sempre os elementos dos índices ímpares do vetor roteiro
serão cabeçalhos de capítulos e os dos índices pares os textos. Por isso, vou extrair as posições dos elementos que contêm a palavra Capítulo e as posições seguintes serão os textos. No teste que fiz, essa heurística funcionou.
# detectar posições no vetor em que o elemento possui a palavra Capítulo ind_capitulo <- which(str_detect(roteiro, "Capítulo ")) # montar dataframe com cabeçalho de capitulo e texto df_usurp <- data.frame( capitulo = roteiro[ind_capitulo], texto = roteiro[ind_capitulo + 1], stringsAsFactors = FALSE )
Após a construção do dataframe, fazemos algumas limpezas:
# por erro do site, o cap 56 veio duplicado df_usurp <- df_usurp[-56,] # corrigir rownames (56 deixa de existir, cria-se um buraco) rownames(df_usurp) <- seq(1, nrow(df_usurp), 1) # substituir Dr. por Dr df_usurp$texto <- str_replace_all(df_usurp$texto, "Dr\\. ", "Dr") # remover espaços desnecessarios df_usurp$texto <- str_squish(df_usurp$texto) # montar dataframe separando os textos dos capítulos em frases df_usurp_frase <- df_usurp %>% as.tibble() %>% mutate(frase = str_split(texto, "\\. ")) %>% select(-texto) %>% unnest() knitr::kable(head(df_usurp_frase))
capitulo | frase |
---|---|
Capítulo 01 – exibido em 03/07/2000 | Paulina é uma moça pobre, que vive com a mãe numa pequena choupana à beira mar e trabalha como arrumadeira num elegante clube de Cancun |
Capítulo 01 – exibido em 03/07/2000 | Paulina vive um drama pessoal com a doença de sua mãe |
Capítulo 01 – exibido em 03/07/2000 | Desenganada pelos médicos, Dona Paula se preocupa com o futuro da filha |
Capítulo 01 – exibido em 03/07/2000 | Por isso, para tranqüiliza-la, Paulina pede a Osvaldo, seu namorado, que se casem o mais rápido possível |
Capítulo 01 – exibido em 03/07/2000 | Mas, o que ela desconhece são os planos de Osvaldo… Paola, por sua vez, é uma jovem senhora da alta sociedade |
Capítulo 01 – exibido em 03/07/2000 | Mulher fria, calculista, aventureira e de muitos amantes |
A tabela acima é o nosso dataframe já formatado, em que cada linha corresponde a uma frase extraída do capítulo indicado na primeira coluna. Veja que nem sempre existe um par de personagens (ou nem mesmo um personagem só), como na primeira linha acima.
Outra observação a ser feita é que uma frase pode contar dois personagens mesmo que não represente uma cena em que ambos estejam presentes, como na quinta linha, em que são mencionados Osvaldo e Paola, sem que eles estejam contracenando. Para simplificar esta análise, vamos ignorar esse tipo de situação.
A tarefa agora é identificar os personagens que são mencionados em cada frase. Para isso, faço novamente um web scraping a partir da página da Wikipedia da novela para extrair a tabela de personagens:
# conjunto de personagens de a usurpadora usurpadora_wiki <- read_html("https://pt.wikipedia.org/wiki/La_usurpadora") # ler tabela tb <- usurpadora_wiki %>% html_table(fill = TRUE) # selecionar quinta tabela da pagina personagens <- tb[[5]] personagens <- as.character(personagens[,2]) # corrigir doutor personagens <- str_replace_all(personagens, "Dr\\. ", "Dr") # remover virgulas personagens <- str_replace_all(personagens, ",", "") personagens <- word(personagens, 1) # corrigir vovo piedade personagens[4] <- "Piedade" # remover personagens sem importancia personagens <- personagens[-c(42, 43, 64, 66, 69)] extrair_mencionados <- function(x){ unique(personagens[str_detect(x, personagens)]) } gerar_pares <- function(x){ if (length(x) < 2) return(NULL) x <- sort(unique(x)) x <- x %>% combn(m = 2, simplify = TRUE) %>% t() x <- as.tibble(x) colnames(x) <- c("P1", "P2") x } df_pares_personagens <- df_usurp_frase %>% mutate(par = frase %>% map(extrair_mencionados) %>% map(gerar_pares)) %>% select(capitulo, frase, par) df_pares_personagens <- df_pares_personagens %>% filter(map_dbl(par, length) > 0) %>% unnest() # construir rede df_pares_personagens <- df_pares_personagens %>% count(P1, P2, sort = TRUE) knitr::kable(head(df_pares_personagens))
P1 P2 n
Carlos – Paulina – 173
Paola – Paulina – 134
Carlos – Paola – 113
Carlos – Leda – 46
Paulina – Piedade – 44
Paulina – Rodrigo – 43
Sendo assim, temos um dataframe pronto para construir a rede: cada linha do dataframe acima corresponde a uma interação entre personagens, onde a coluna n
mostra o número de cenas entre ambos.
Análise da rede
Podemos partir então para a análise do grafo de A Usurpadora. No vídeo referente a esta análise no módulo 04 do vídeo, eu abordo dois temas: análise de centralidade por meio de simulação de redes aleatórias e detecção de núcleos de personagem. Neste post, vou comentar apenas o último. Existem diversos algoritmos de detecção de comunidades em redes e cada um possui suas propriedades e características.
Neste post, eu mostro o exemplo do algoritmo greedy optimization of modularity, que busca encontrar subgrafos em um grafo que otimizem a métrica de modularidade, que por sua vez corresponde à fração de arestas que pertencem a uma comunidade menos a probabilidade de que uma aresta criada aleatoriamente pertenceria a uma comunidade. Em outras palavras, deseja-se encontrar subgrupos de vértices que sejam coesos ou densos entre si mas esparsos com vértices de outros grupos.
Aplicar esse algoritmo no R é muito fácil:
# criar objeto da rede g <- graph_from_data_frame(df_pares_personagens, directed = FALSE) # usando n como peso das arestas cl1 <- cluster_fast_greedy(g, weights = E(g)$n/sum(E(g)$n)) par(mar=c(1,1,1,1)) l <- layout_nicely(g) set.seed(123) plot(cl1, g, layout = l, mark.groups = NULL, asp = 0, main = "Rede de personagens de A Usurpadora")
Como o gráfico acima mostra, foram identificados 4 subgrafos diferentes, separados por cores únicas. Estes seriam subgrupos de personagens fortes entre si mas pouco conectados com vértices de outros, de acordo com o algoritmo.
Estes núcleos fazem sentido para você? Pessoalmente, não me lembro suficiente bem da novela para confirmar ou não. Temos algum noveleiro aqui para julgar os resultados? 😀
Obrigado e nos encontramos no curso!