usurpadora elenco

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")

 

Rede de personagens 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!