NOTA: DOCUMENTO EM ELABORAÇÃO


0.1 Introdução

Este documento é um registro do meu aprendizado de redes sociais e sua implementação no R. Para esse objetivo farei uso, inicialmente, do pacote igraph do R embora à medida que eu for avançando no aprendizado de redes sociais não descarte a possibilidade de explorar outros pacotes disponíveis com igual finalidade a exemplo do sand, visNetwork, statnet ou mesmo outros aplicativos como o Gephi.

Para obter ajuda sobre o pacote igraph recomendo consultar o site igraph.org/r/.

O conjunto de dados utilizado neste documento está disponível para download no GitHub.


0.2 Conceitos básicos

Inicia-se por tentar responder à seguinte questão: o que é uma rede social?

Uma rede social é qualquer estrutura constituída de um conjunto de itens (pessoas, objetos, etc) que possuem algum tipo de relação (vínculo) entre si.

Essa estrutura é representada por objetos matemáticos denominados grafos nos quais os itens são representados por vértices e o vínculo existente entre os itens por arestas.

Um grafo é um objeto matemático formado por dois conjuntos. O primeiro deles é o conjunto dos vértices (V) e o segundo é o conjunto das relações existentes entre os vértices denominado arestas (E).

Os grafos podem ser direcionados ou não direcionados. Nos grafos direcionados a relação entre os itens possuem uma direção. Por exemplo, a pessoa A emprestou dinheiro à pessoa B. Esta relação pode ser representada no grafo com uma aresta que consiste numa “seta”" apontando de A para B. Nos grafos não direcionados a relação não tem um direcionamento. Exemplo: a pessoa A conhece a pessoa B e vice-versa.

Os dois grafos a seguir ilustram o que foi comentado.


0.2.1 Homofilia

Na análise de redes sociais um conceito importante é o de homofilia. Este é um conceito emprestado da sociologia e significa que as pessoas tem uma forte tendência a associar-se a outras com quem elas percebam como sendo semelhantes a elas de alguma forma. O ditado popular “diga-me com quem andas que te direi quem és” expressa muito bem este conceito.

Uma rede social é homofílica se os vértices com o atributo X estão em sua maioria associados com outros vértices que também possuam o atributo X. Não será objeto de deste documento, mas existem medidas para avaliar se uma rede é homofílica.


0.2.2 Matrizes de adjacência e de incidência

Dois outros conceitos relevantes são o conceito de matriz de adjacência e matriz de incidência.

Uma matriz de adjacência é uma matriz quadrada onde o valor 1 indica a existência de vínculo entre dois vértices e 0 indica a ausência de vínculo. Um exemplo é a matriz a seguir:

##   A B C D E
## A 0 1 1 0 0
## B 0 0 1 1 0
## C 0 1 0 0 0
## D 0 0 0 0 0
## E 0 0 1 0 0

Já uma matriz de incidência indica como se dá o vínculo entre dois conjuntos de vértices com características distintas. Por exemplo, considere um conjunto de \(m\) estudantes que pertençam a \(n\) turmas. A matriz de incidência é então uma matriz retangular \(m \times n\), onde o valor 1 indica que determinado estudante pertence a determinada turma e 0 caso não pertença. Exemplo:

##    T1 T2 T3 T4
## E1  1  0  0  1
## E2  0  0  1  1
## E3  0  1  1  0
## E4  0  1  0  0
## E5  1  1  0  0
## E6  1  0  0  1


0.3 Importação dos dados

Embora o pacote igraph ofereça algumas opções para a criação de grafos, uma opção bastante conveniente é a criação do grafo a partir de um data frame.

Neste documento será utilizado um conjunto de dados relativo aos investimentos feitos por RPPS fluminenses em diversos ativos incluindo fundos de investimento. Os dados estão contidos no arquivo dados_dair_DtGer_12-12-2017.rds criado a partir da extração de dados de arquivos xml do DAIR de alguns dos já mencionados RPPS.

Os comandos a seguir ilustram a importação dos dados e retenção dos registros relativos aos investimentos em fundos de investimento:

setwd("I:\\Redes Sociais e RPPS")

# Importação dos dados
dados <- readRDS("dados_dair_DtGer_12-12-2017.rds")

# Retenção dos registros relativos a aplicações em Fundos de Investimentos
dados <- subset(dados, !is.na(cnpj_fi))
str(dados)
## 'data.frame':    6163 obs. of  13 variables:
##  $ ente                            : chr  "Pinheiral" "Pinheiral" "Pinheiral" "Pinheiral" ...
##  $ ano                             : chr  "2017" "2017" "2017" "2017" ...
##  $ mes                             : chr  "1" "1" "1" "1" ...
##  $ identificacaoDoAtivo            : chr  "14.386.926/0001-71 - CAIXA BRASIL IDKA 2 A TITULOS PUBLICOS FI RENDA FIXA LP" "18.598.288/0001-03 - CAIXA BRASIL2024I TITULOS PUBLICOS  FI RENDA FIXA" "11.060.913/0001-10 - CAIXA BRASIL IMA B 5 TITULOS PUBLICOS FI RENDA FIXA LP" "10.740.658/0001-93 - CAIXA BRASIL IMA B TITULOS PUBLICOS FI RENDA FIXA LP" ...
##  $ nome                            : chr  "FI 100% títulos TN" "FI 100% títulos TN" "FI 100% títulos TN" "FI 100% títulos TN" ...
##  $ textoFundamentoLegal            : chr  "Art. 7º, I, \"b\"" "Art. 7º, I, \"b\"" "Art. 7º, I, \"b\"" "Art. 7º, I, \"b\"" ...
##  $ quantidade                      : chr  "2996314.7144273080" "300000.0000000000" "1793117.7799846900" "4552179.1893383010" ...
##  $ valorAtualAtivo                 : chr  "1.6425240000" "1.3566020000" "2.1790120000" "2.2783610000" ...
##  $ valorTotalAtual                 : chr  "4921518.83" "406980.60" "3907225.16" "10371507.53" ...
##  $ pctTotalRecursosSPPS            : chr  "14.79" "1.22" "11.74" "31.16" ...
##  $ valorAtualPatrimonioLiquidoFundo: chr  "3664210527.36" "362551374.08" "5503989684.97" "5218065279.62" ...
##  $ pctPatrimonioLiquidoFundo       : chr  "0.13" "0.11" "0.07" "0.20" ...
##  $ cnpj_fi                         : chr  "14.386.926/0001-71" "18.598.288/0001-03" "11.060.913/0001-10" "10.740.658/0001-93" ...

Feita a importação dos dados é sempre uma boa ideia explorar um pouco o conjunto de dados. Os dados se referem aos DAIR do exercício de 2017. Mas quais RPPS estão na base e quais meses? O código abaixo busca responder a esta questão:

table(dados$ente, ordered(dados$mes, levels=1:12))
##                            
##                              1  2  3  4  5  6  7  8  9 10 11 12
##   Angra dos Reis            31 31 31 31 31 31 31 31  0  0  0  0
##   Areal                     16 16 18 18 18 18 18 18  0  0  0  0
##   Armação dos Búzios         8  8  8  8  9  9 10 10  0  0  0  0
##   Barra Mansa                2  2  2  2  2  2  2  2  0  0  0  0
##   Belford Roxo              18 18 17 17 17 17 17 17  0  0  0  0
##   Bom Jardim                10 10 10 10 10 10 10 10  0  0  0  0
##   Cabo Frio                  5  5  5  5  5  5  5  5  0  0  0  0
##   Cambuci                    7  7 10 10 11 10 10 10  0  0  0  0
##   Cantagalo                 10 12 12 12 12 13 14 15  0  0  0  0
##   Cardoso Moreira            8  8  9  9  9  9  9  9  0  0  0  0
##   Carmo                     11 11 11 11 11 11 11 11  0  0  0  0
##   Casimiro de Abreu         33 33 33 33 33 34 33 33 34  0  0  0
##   Comendador Levy Gasparian  8  8  8  9  9  9  9  9  0  0  0  0
##   Conceição de Macabu       15 15 15 15 15 15 15 15 15  0  0  0
##   Duas Barras               17 17 17 17 17 17 19 19  0  0  0  0
##   Italva                    17 17 17 17 17 17 17 17  0  0  0  0
##   Itaocara                  10 10 10 10 10 11 11 11  0  0  0  0
##   Itaperuna                  7  7  6  6  6  6  6  6  0  0  0  0
##   Itatiaia                  29 29 28 30 30 31 31 31  0  0  0  0
##   Japeri                    15 16 15 16 16 16 16 16  0  0  0  0
##   Macaé                     25 24 24 24 24 24 24 24  0  0  0  0
##   Maricá                     9  9  9  9  9  9  9  9  0  0  0  0
##   Mesquita                  13 15 15 15 15 15 15 17  0  0  0  0
##   Miguel Pereira            17 17 18 18 18 20 20 20  0  0  0  0
##   Natividade                12 12 12 12 12 12 12 12  0  0  0  0
##   Nilópolis                  1  1  2  2  2  2  2  3  0  0  0  0
##   Niterói                   11 15 15 15 15 15 13 14 14 14  0  0
##   Nova Friburgo              9 10  9  9  9  9 10 12  0  0  0  0
##   Nova Iguaçu                5  6  6  6  6  5  5  5  0  0  0  0
##   Paty do Alferes           18 18 17 17 18 18 18 18 18 19  0  0
##   Petrópolis                 3  3  4  3  3  3  3  3  0  0  0  0
##   Pinheiral                 14 14 14 14 14 28  0 14  0  0  0  0
##   Piraí                     13 13 13 13 13 13 13 13 13 13  0  0
##   Porciúncula               20 20 20 20 23 21 21 19  0  0  0  0
##   Quatis                     9  9  9  9  9  9 13 13  0  0  0  0
##   Queimados                 28 26 24 24 24 24 24 24  0  0  0  0
##   Resende                   28 29 29 29 29 29 29 29 29 29  0  0
##   Rio Claro                 11 11 11 11 12 12 11 11  0  0  0  0
##   Rio das Ostras            34 41 38 40 40 40 37 38  0  0  0  0
##   Rio de Janeiro            28 28 28 28 29 29 29 33  0  0  0  0
##   São Gonçalo                7  7  7  7  7  7  6  6  0  0  0  0
##   São João da Barra          3  3  3  4  4  4  4  4  0  0  0  0
##   São Pedro da Aldeia       14 17 19 19 19 19 20 20  0  0  0  0
##   São Sebastião do Alto      4  4  4  4  4  4  4  6  0  0  0  0
##   Sapucaia                  18 18 18 18 18 18 18 18  0  0  0  0
##   Saquarema                 10 10 11 11 11 11 11 11  0  0  0  0
##   Silva Jardim              10 11 11 11 12 13 13 16  0  0  0  0
##   Sumidouro                 22 22 23 23 23 23 23 23  0  0  0  0
##   Trajano de Moraes         15 15 15 15 15 16  0  0  0  0  0  0
##   Valença                    0 12  0  0  0 12 12 13  0  0  0  0
##   Varre-Sai                 10 10 11 11 11 11 11  8  0  0  0  0
##   Vassouras                 14 14 14 14 14 14 14 14  0  0  0  0

Os valores nesta tabela indicam, para cada ente e mês, em quantos fundos de investimento o RPPS mantinha recursos aplicados.

A base de dados possui registros relativos aos meses de janeiro a outubro de 2017 (apenas alguns RPPS). Como a maioria dos RPPS forneceu os dados até agosto de 2017, será utilizado os dados deste mês para a elaboração do grafo.

# Obtenção dos registros relativos ao mês de agosto
dados_ago <- subset(dados, mes == 8)


0.4 Construção do grafo

Feita a importação dos dados o passo seguinte é construir o grafo desejado. O objetivo inicial é verificar como os RPPS e os fundos de investimento se relacionam. A ideia é verificar se esta abordagem (análise de redes sociais) pode ser utilizada para ajudar na detecção de fraudes. Por ora o objetivo é só aprender alguma teoria de redes sociais e realizar análises usando o R.

Deve ser observado que o interesse está no relacionamento de duas entidades: os RPPS e os fundos de investimento. O interesse poderia estar em criar um grafo só com os fundos de investimento considerando que dois fundos tenham um vínculo, por exemplo, se possuirem o mesmo gestor ou o mesmo administrador.

Redes que apresentam o relacionamento entre dois tipos de objeto são chamadas bipartites. Neste tipo de rede os vínculos ocorrem apenas entre entidades de tipos diferentes. No caso em análise os vínculos ocorrem apenas entre RPPS e fundos de investimento. Não existem vínculos entre RPPS ou entre fundos de investimento.

O pacote igraph possui a função graph_from_data_frame() que possibilita a construção de um grafo a partir de um data frame no qual as duas primeiras colunas contenham a identificação dos itens (vértices) cuja relação se deseja examinar.

No conjunto de dados utilizado como exemplo, as duas primeiras colunas contém o nome do ente a que o RPPS pertence e na segunda coluna o CNPJ do fundo de investimento no qual o RPPS possuia recursos aplicados em agosto de 2017. As demais colunas referem-se a outras variáveis existentes no conjunto de dados. Assim, cada linha representa um vínculo (aresta) existente entre RPPS e fundo de investimento.

O código a seguir ilustra como construir o grafo a partir da base de dados. A primeira coisa a fazer é carregar o pacote igraph:

library(igraph)

O grafo pode ser elaborado da seguinte forma:

grafo <- graph_from_data_frame(dados_ago[,c("ente", "cnpj_fi", "textoFundamentoLegal", "pctTotalRecursosSPPS")],  directed = FALSE)

Deve ser notado que foram utilizadas apenas algumas colunas da base de dados e não toda a base. Isto porque as demais colunas, além das duas primeiras, serão consideradas como atributos das arestas. Estas variáveis também podem ser utilizadas na elaboração de visualizações dos grafos como será mostrado mais adiante.

A visualização do grafo pode ser obtida com a função plot().

plot(grafo)

Como pode ser visto, o resultado não é nada interessante. É possível melhorar sensivelmente o gráfico alterando os valores de alguns argumentos da função:

plot(grafo,
     vertex.size=7,
     vertex.label=NA)

Uma rápida inspeção do grafo revela um ponto interessante. Alguns RPPS apresentam vínculos com um punhado de fundos de investimentos que não tem relação com mais nenhum outro RPPS (pelo menos em relação aos RPPS que constam da base de dados).

Mais adiante será visto como diferenciar, no grafo, os RPPS dos fundos de investimento.


0.5 Medidas descritivas em grafos

A partir de um grafo é possível obter algumas medidas descritivas tais como o tamanho do grafo, sua densidade e o seu diâmetro.

Além dessas medidas, também é útil obter a relação dos vértices e arestas de um grafo. Esta seção ilustra como obter estas informações.

Com as funções V() e E() obtém-se a relação dos vértices e das arestas.

V(grafo)
## + 229/229 vertices, named, from 865641f:
##   [1] Pinheiral                 Armação dos Búzios       
##   [3] Mesquita                  Niterói                  
##   [5] Cabo Frio                 Bom Jardim               
##   [7] Duas Barras               Nova Friburgo            
##   [9] Itaocara                  São Gonçalo              
##  [11] São Sebastião do Alto     Cantagalo                
##  [13] Barra Mansa               Silva Jardim             
##  [15] São Pedro da Aldeia       Itaperuna                
##  [17] Natividade                Porciúncula              
##  [19] Rio Claro                 Valença                  
## + ... omitted several vertices
E(grafo)
## + 765/765 edges from 865641f (vertex names):
##  [1] Pinheiral         --14.386.926/0001-71
##  [2] Pinheiral         --18.598.288/0001-03
##  [3] Pinheiral         --11.060.913/0001-10
##  [4] Pinheiral         --10.740.658/0001-93
##  [5] Pinheiral         --10.740.670/0001-06
##  [6] Pinheiral         --06.018.364/0001-85
##  [7] Pinheiral         --07.861.554/0001-22
##  [8] Pinheiral         --05.164.356/0001-84
##  [9] Pinheiral         --13.077.415/0001-05
## [10] Pinheiral         --13.077.418/0001-49
## + ... omitted several edges

As medidas descritivas anteriormente mencionadas (tamanho, densidade e diâmetro) são obtidas, respectivamente, com as funções gorder(), edge_density() e diameter().

Tamanho: é a quantidade de vértices que uma rede possui. No conjunto de dados em análise tem-se 229 vértices entre fundos de investimento e RPPS.

gorder(grafo)
## [1] 229

A quantidade de arestas pode ser obtida com a função gsize().

gsize(grafo)
## [1] 765


Densidade: a densidade da rede é a proporção de arestas existente na rede em relação a todas as arestas possíveis entre os vértices. Sendo uma proporção, esta quantidade varia entre 0 e 1, sendo que quanto mais próxima de 1 mais densa é rede.

edge_density(grafo)
## [1] 0.02930361


Diâmetro: o diâmetro de uma rede é o maior caminho dentre todos os caminhos mais curtos entre dois pares quaisquer de vértices. Um caminho é uma série de passos necessários para ir de um vértice A até um vértice B.

O diâmetro de uma rede é uma medida bastante útil de sua compacticidade.

diameter(grafo)
## [1] 6


Coesão:

vertex_connectivity(grafo)
## [1] 1


0.6 Operações básicas com grafos

Além de obter algumas medidas descritivas dos grafos que se está a examinar, algumas operações sobre grafos são bem corriqueiras. Exemplos de tais operações são: consultar e definir atributos de vértices e arestas, filtrar grafos, adicionar ou excluir vértices e arestas ao grafo ou ainda combinar múltiplos grafos.


0.6.1 Consultar e definir atributos de vértices e arestas

Atributos dos vértices, das arestas ou mesmo dos grafos podem ser obtidos, respectivamente com as funções vertex_attr(), edge_attr() e graph_attr().

Para identificar os atributos existentes em um grafo pode-se utilizar a função summary().

summary(grafo)
## IGRAPH 865641f UN-- 229 765 -- 
## + attr: name (v/c), textoFundamentoLegal (e/c),
## | pctTotalRecursosSPPS (e/c)

O resultado fornece algumas inforamções interessantes: primeiro a string UN-- 229 765 -- indica tratar-se de um grafo não direcionado (U), com vértices nomeados (N) possuindo 229 vértices e 765 arestas. Outras opções possíveis são (D) para indicar grafos direcionados e (W) para indicar grafos com pesos atribuídos às arestas.

A string name (v/c), textoFundamentoLegal (e/c), pctTotalRecursosSPPS (e/c) indica os atributos existentes no grafo, a que componentes (vértice ou aresta) pertencem e o tipo do atributo (textual, numérico). Exemplo: name (v/c) indica a existência de atributo de nome name atribuído aos vértices sendo um atributo textual (v/c); textoFundamentoLegal (e/c) indica a existência de um atributo de nome textoFundamentoLegal atribuídos as arestas sendo também um atributo textual. O mesmo para o último atributo (pctTotalRecursosSPPS).

Agora que se conhece os atributos exístentes no grafo, pode-se consultar seu conteúdo da seguinte forma:

# Os 10 primeiros
vertex_attr(grafo, name="name")[1:10]
##  [1] "Pinheiral"          "Armação dos Búzios" "Mesquita"          
##  [4] "Niterói"            "Cabo Frio"          "Bom Jardim"        
##  [7] "Duas Barras"        "Nova Friburgo"      "Itaocara"          
## [10] "São Gonçalo"
# Os 10 primeiros
edge_attr(grafo, "textoFundamentoLegal")[1:10]
##  [1] "Art. 7º, I, \"b\""   "Art. 7º, I, \"b\""   "Art. 7º, I, \"b\""  
##  [4] "Art. 7º, I, \"b\""   "Art. 7º, I, \"b\""   "Art. 7º, VII, \"a\""
##  [7] "Art. 7º, III, \"a\"" "Art. 7º, IV, \"a\""  "Art. 7º, IV, \"a\"" 
## [10] "Art. 7º, IV, \"a\""

Visto como consultar os atributos de vértices e arestas, será visto agora como definir novos atributos para vértices. A função set_vertex_attr() pode ser utilizada com esta finalidade.

Será criado um novo atributo denominado fundo_vedado que irá indicar se o fundo de investimento é vedado para aplicações do RPPS ou não. Para isso será necessário importar a base de dados de fundos vedados divulgada pelo Ministério da Fazenda.

library(readxl)
setwd("I:\\Melhores Praticas TCE e Atricon\\ATRICON\\3. Parte Pratica - Laboratorio\\extracao-dados-pdf-fundos-vedados")
fundos_vedados <- read_excel("fundos_vedados_28-08-2018.xlsx")[,1:2]
head(fundos_vedados)
## # A tibble: 6 x 2
##   `CNPJ DO FUNDO`    `NOME DO FUNDO`                                      
##   <chr>              <chr>                                                
## 1 00.828.035/0001-13 FATOR MAX CORPORATIVO FUNDO DE INVESTIMENTO DE RENDA~
## 2 01.107.772/0001-90 CONCÓRDIA EXTRA FUNDO DE INVESTIMENTO RENDA FIXA CRÉ~
## 3 01.375.954/0001-41 PLANNER FUNDO DE INVESTIMENTO MULTIMERCADO           
## 4 01.653.201/0001-50 GRAU SAVANA FUNDO DE INVESTIMENTO MULTIMERCADO       
## 5 03.362.624/0001-47 GWI CLASSIC FUNDO DE INVESTIMENTO EM AÇÕES           
## 6 04.877.280/0001-71 SANTOS CREDIT YIELD FUNDO DE INVESTIMENTO RENDA FIXA~

A definição do novo atributo é feita a seguir:

grafo <- set_vertex_attr(grafo,
                         name="fundos_vedados",
                         value=ifelse(V(grafo)$name %in% fundos_vedados$`CNPJ DO FUNDO`, TRUE, FALSE))

summary(grafo)
## IGRAPH 865641f UN-- 229 765 -- 
## + attr: name (v/c), fundos_vedados (v/l), textoFundamentoLegal
## | (e/c), pctTotalRecursosSPPS (e/c)

O resumo indica a criação do novo atributo para os vértices com o nome fundos_vedados do tipo lógico. Mas erá que existe algum valor verdadeiro? Ou seja, algum RPPS tinha aplicações em fundos de investimento vedados?

any(vertex_attr(grafo, name="fundos_vedados"))
## [1] TRUE

O resultado indica que sim. Este atributo poderá ser utilizado para identificar os RPPS que investiram em fundos vedados.

Para remover atributos dos vértices, das arestas ou do grafo pode-se utilizar, respectivamente, as funções delete_vertex_attr(), delete_edge_attr() e delete_graph_attr().


0.6.2 Filtrar grafos

Uma outra tarefa comum na análise de redes sociais é a filtragem de grafos com base nos atributos dos vértices ou arestas. O comando a seguir mostra como filtrar os vértices que tenham o atributo fundos_vedados igual a TRUE.

grafo_filtrado <- subgraph.edges(grafo, E(grafo)[inc(V(grafo)[V(grafo)$fundos_vedados])])
plot(grafo_filtrado,
     vertex.size=7)


0.7 Medidas clássicas de centralidade

Centralidade é uma medida de quão importante um vértice é no contexto de toda a rede. Trata-se de conceito bem amplo e diversas medidas de centralidade estão disponíveis que buscam capturar as diferentes noções de importância de um vértice.


0.7.1 Grau de um vértice

A medida de centralidade mais simples é o grau do vértice. Esta medida atribui a cada vértice a quantidade de arestas ao qual está conectado, considerando dessa forma que são importantes os vértices que mais conexões tém com outros vértices.

O grau de um vértice indica a quantos outros vértices o vértice em exame está conectado. Esta medida é calculada com a função degree()

Assim, são considerados mais centrais os vértices com maior quantidade de arestas, de forma que quando a rede é plotada, quanto maior o grau de um vétice, mais ao centro da rede ele estará. O códig a seguir mostra os cinco vértices com maior grau:

sort(degree(grafo), decreasing = TRUE)[1:5]
## 10.740.670/0001-06     Rio das Ostras  Casimiro de Abreu 
##                 39                 38                 33 
##     Rio de Janeiro 11.328.882/0001-35 
##                 33                 32

Para obter o grau de um vértice em particular, por exemplo, o vértice que representa o RPPS de Japeri, podemos fazer:

degree(grafo)["Japeri"]
## Japeri 
##     16

Quais vertices apresentam grau maior que 30?

degree(grafo)[degree(grafo) > 30]
##  Casimiro de Abreu     Angra dos Reis           Itatiaia 
##                 33                 31                 31 
##     Rio das Ostras     Rio de Janeiro 10.740.670/0001-06 
##                 38                 33                 39 
## 11.328.882/0001-35 
##                 32

Quais fundos estão vinculados a um único RPPS :

degree(grafo)[degree(grafo) == 1]
## 05.100.221/0001-55 02.998.164/0001-85 08.692.888/0001-82 
##                  1                  1                  1 
## 23.957.101/0001-50 11.898.280/0001-13 05.073.656/0001-58 
##                  1                  1                  1 
## 16.599.968/0001-16 10.309.539/0001-80 07.899.238/0001-40 
##                  1                  1                  1 
## 04.288.966/0001-27 15.154.220/0001-47 03.394.711/0001-86 
##                  1                  1                  1 
## 17.517.779/0001-10 30.822.936/0001-69 15.486.093/0001-83 
##                  1                  1                  1 
## 18.800.239/0001-01 12.330.846/0001-79 07.539.298/0001-51 
##                  1                  1                  1 
## 09.630.188/0001-26 01.525.057/0001-77 04.877.280/0001-71 
##                  1                  1                  1 
## 21.347.528/0001-01 18.598.088/0001-50 14.507.699/0001-95 
##                  1                  1                  1 
## 17.073.556/0001-00 17.116.227/0001-08 18.270.783/0001-99 
##                  1                  1                  1 
## 15.188.380/0001-07 02.887.290/0001-62 06.916.384/0001-73 
##                  1                  1                  1 
## 03.737.211/0001-08 02.228.453/0001-03 19.303.794/0001-90 
##                  1                  1                  1 
## 10.418.362/0001-50 23.176.675/0001-91 19.542.287/0001-00 
##                  1                  1                  1 
## 00.840.011/0001-80 14.171.644/0001-57 21.098.129/0001-54 
##                  1                  1                  1 
## 19.391.026/0001-36 13.594.673/0001-69 17.213.849/0001-46 
##                  1                  1                  1 
## 11.198.684/0001-02 13.344.834/0001-66 07.187.570/0001-81 
##                  1                  1                  1 
## 11.357.758/0001-06 17.412.812/0001-47 59.285.411/0001-13 
##                  1                  1                  1 
## 13.593.438/0001-72 07.972.299/0001-95 11.351.413/0001-37 
##                  1                  1                  1 
## 12.053.694/0001-04 15.153.656/0001-11 13.651.947/0001-04 
##                  1                  1                  1 
## 15.769.621/0001-01 14.655.180/0001-54 12.360.621/0001-65 
##                  1                  1                  1 
## 18.373.362/0001-93 12.312.767/0001-35 14.631.148/0001-39 
##                  1                  1                  1 
## 10.896.292/0001-46 19.523.306/0001-50 21.918.953/0001-03 
##                  1                  1                  1 
## 20.139.342/0001-02 22.791.074/0001-26 20.139.534/0001-00 
##                  1                  1                  1 
## 24.022.566/0001-82 10.787.647/0001-69 00.975.480/0001-06 
##                  1                  1                  1 
## 11.046.645/0001-81 02.296.928/0001-90 09.215.250/0001-13 
##                  1                  1                  1 
## 03.069.104/0001-40 
##                  1

Eventualmente pode ser de interesse calcular o grau médio de uma rede. Isso pode ser feito da seguinte forma:

# grau médio
mean(degree(grafo))
## [1] 6.681223
# plotar vértices mais centrais, ou seja, os que possuem maior grau
plot(grafo,
     vertex.label=ifelse(V(grafo)$name %in% c("10.740.670/0001-06", "Rio das Ostras"), V(grafo)$name, NA),
     vertex.size=7,
     vertex.color=ifelse(V(grafo)$name %in% c("10.740.670/0001-06", "Rio das Ostras"), "red", NA))

No grafo acima, foram plotados os labels e coloridos de vermelho apenas 2 vértices identificados como os de maior grau.

Também pode ser útil verificar como se distribui o grau dos vértices. Um histograma ou um gráfico de pontos pode ser usados com esta finalidade

par(mfrow=c(1,2), bty="n")
hist(degree(grafo), col="lightblue", main = "")
stripchart(degree(grafo), method = "stack", pch=16, cex=1.2, at=0, col="lightblue")


0.7.2 Closeness

Esta medida de centralidade tenta expressar a importância de um vértice pelo fato dele estar próximo a muitos outros vértices. Os cinco vértices com maior valor desta medida é apresentada a seguir:

sort(closeness(grafo), decreasing = TRUE)[1:5]
## 10.740.670/0001-06 10.740.658/0001-93 11.328.882/0001-35 
##        0.002036660        0.001869159        0.001821494 
## 13.322.205/0001-35 13.077.418/0001-49 
##        0.001808318        0.001795332


0.7.3 Betweenness

Esta medida de centralidade reconhece como mais importantes os vértices que se localizam na rede de tal forma se localizem entre outros pares de vértices.

sort(betweenness(grafo), decreasing = TRUE)[1:5]
## 10.740.670/0001-06     Rio de Janeiro     Rio das Ostras 
##           4431.757           3157.816           2833.575 
##          Queimados     Angra dos Reis 
##           2449.070           2358.505


0.7.4 Centralidade de autovetor

Uma outra medida de centralidade é a eingenvector centrality que atribui maiores pesos aos vértices que estão conectados a outros vértices também importantes.

eigen_centrality(grafo)$vector[1:5]
##          Pinheiral Armação dos Búzios           Mesquita 
##          0.3342700          0.4325819          0.3347853 
##            Niterói          Cabo Frio 
##          0.4311459          0.1528061


0.8 Vizinhança

Os vizinhos de um vértice (vértices relacionados ao vértice em análise) podem ser identificados com as funções neighbors() ou adjacent_vertices(). Por exemplo, se fosse necessário identificar os vértices adjacentes ao RPPS de Japeri (fundos em que o RPPS de Japeri investe), pode-se utilizar o seguinte comando:

neighbors(grafo, "Japeri")
## + 16/229 vertices, named, from 865641f:
##  [1] 10.740.670/0001-06 05.164.356/0001-84 13.555.918/0001-49
##  [4] 07.111.384/0001-69 09.315.625/0001-17 20.886.575/0001-60
##  [7] 19.391.026/0001-36 13.594.673/0001-69 17.213.849/0001-46
## [10] 12.845.801/0001-37 11.198.684/0001-02 13.344.834/0001-66
## [13] 07.187.570/0001-81 11.357.758/0001-06 17.412.812/0001-47
## [16] 59.285.411/0001-13

Também é possível identificar a “vizinhança” de um determinado vértice. A função neighborhood() implementa essa funcionalidade. Para identificar a “vizinhança”" do Fundo de Investimento “05.100.221/0001-55” a duas arestas de distância, isto é, identificar os RPPS que investem no referido Fundo e todos os demais Fundos nos quais tais RPPS investem.

neighborhood(grafo, order = 2, "05.100.221/0001-55")
## [[1]]
## + 15/229 vertices, named, from 865641f:
##  [1] 05.100.221/0001-55 Pinheiral          14.386.926/0001-71
##  [4] 18.598.288/0001-03 11.060.913/0001-10 10.740.658/0001-93
##  [7] 10.740.670/0001-06 06.018.364/0001-85 07.861.554/0001-22
## [10] 05.164.356/0001-84 13.077.415/0001-05 13.077.418/0001-49
## [13] 11.898.349/0001-09 08.973.942/0001-68 02.998.164/0001-85

Uma ego net(rede egocêntrica) é um subgrafo consistindo de um vértice \(v\) e seus vizinhos imediatos.


0.9 Subgrafos

Para a definição de subgrafos énecessário apenas que se identifique quais vértices deseja-se que o subgrafo contenha. Estes vértices podem ser definidos ad hoc pelo analista.

A identificação de comunidades, por outro lado, consiste em achar subgrafos cujos vértices serão definidos em razão da coesão existente entre eles.

Um subgrafo pode ser criado conforme mostrado a seguir, onde apenas os vértices relativos aos RPPS de Belford Roxo e Japeri comporão o subgrafo.

A função subgraph.edges() cuidará de identificar as arestas associadas a estes vértices que, no caso em análise, são os fundos de investimento.

grafo_sub <- subgraph.edges(grafo, E(grafo)[inc(c("Belford Roxo", "Japeri"))])

plot(grafo_sub, vertex.color="lightgreen", vertex.size=7)

O subgrafo acima foi elaborado simplesmente escolhendo-se alguns vértices cujos nomes foram passados à função como um vetor.

Outros subgrafos podem ser obtidos levando-se em consideração padrões existentes nas arestas que definam subgrafos com características particulares. Exemplos desses tipos de subgrafo são os cliques e os k-cores.


0.9.1 Cliques

Um “clique” consiste em um subgrafo no qual todos os vértices estão conectados entre si. Na figura a seguir, é possível identificar dois cliques: ABCD e EFG

Para determinar o tamanho (quantidade de vértices) do maior clique em um grafo pode-se utilizar a função clique.number(). No conjunto de dados em análise:

clique.number(grafo)
## [1] 2

Considerando a natureza do nosso conjunto de dados, onde cada RPPS se vincula a fundos de investimento e, por sua vez, cada fundo se vincula a RPPSs que não se vinculam entre si, o mesmo valendo para os fundos de investimento, não seria possível esperar cliques maiores que 2. Na prática apenas cliques maiores que 3 são relevantes.

Outras funções úteis quando se deseja analisar cliques são: cliques(), largest_cliques(), maximal.cliques() e max_cliques(). Recomenda-se consultar a ajuda das funções para mais informação.


0.9.2 k-Cores

Um k-core é um subgrafo com a seguinte característica: máximo subgrafo no qual cada vértice está conectado a pelo menos k outros vértices no subgrafo.

Este tipo de subgrafo consiste num relaxamento quanto a exigência existente no clique de que todos os vértices estejam interconectados entre si, fato que faz com que na prática os cliques de maiores tamanhos sejam raros mesmo em redes com grande quantidade de vértices.

A identificação de subgrafos k-core é realizada com o uso da função graph.coreness()

grafo_kcores <- graph.coreness(grafo)
table(grafo_kcores)
## grafo_kcores
##  1  2  3  4  5  6  7  8 
## 73 31 25 18  9 22  6 45

O resultado mostra que a rede possui cores de tamanho variando de 1 a 8, sendo que 45 vértices constituem um 8-core.


0.10 Comunidades

A detecção de comunidades engloba um conjunto de algoritmos que tem por objetivo identificar conjuntos de vértices que, de alguma forma, são coesos entre si e não tão associados com outros grupos ou vértices.


0.10.1 Modularidade

Uma característica importante das redes e que é usada com frequência na detecção de comunidades é expressa pelo conceito de modularidade. A modularidade é uma medida da estrutura da rede. Espeficicamente, a extenção na qual os vértices parecem estar agrupados de forma a exibir uma maior densidade dentro dos grupos e uma menor densidade entre os grupos.

A modularidade é uma estatística que varia de -1/2 a 1, sendo que quanto mais próxima de 1 mais agrupados estarão os vértices.

O cálculo da modularidade de uma rede dá-se de duas formas: (1) de maneira exploratória, na qual utiliza-se um algoritmo que busque classificar os vértices de forma a maximizar o valor da modularidade. (2) de forma descritiva, onde a modularidade é calculada para algum atributo dos vértices com o objetivo de se verificar se o referido atributo define um bom agrupamento dos vértices.

Existem diversos algoritmos para detecção de comunidades. O comando abaixo utiliza o algoritmo edge-betweenness implementado pela função cluster_edge_betweeness()

cm <- cluster_edge_betweenness(grafo)
modularity(cm)
## [1] 0.1575753

A tabela a seguir elenca os algoritmos de detecção de comunidades e as correspondentes funções no pacote igraph que as implementam.

Algoritmo Função
Edge-betweenness cluster_edge_betweenness()
Leading eigenvector cluster_leading_eigen()
Fast-greedy cluster_fast_greedy()
Louvain cluster_louvain()
Walktrap cluster_walktrap()
Label propagation cluster_label_prop()
InfoMAP cluster_infomap()
Spinglass cluster_spinglass()
Optimal cluster_optimal()

Fonte: A User’s Guide to Network Analysis in R


Para visualizar os grupos identificados pelo algoritmo, utiliza-se a função plot() conforme mostrado a seguir:

plot(cm, grafo, vertex.label=NA, vertex.size=6)

Embora a visualização dê uma boa indicação dos grupos detectados pelo algoritmo, pode ser necessário obter a identificação dos vértices pertencentes a cada grupo. Isso pode ser feito com a função membership() conforme mostrado a seguir:

mbp <- membership(cm)
table(mbp)
## mbp
##   1   2   3   4   5   6   7   8 
## 156   8   5   4  15  12  13  16

Foram identificados 8 subgrafos, sendo que no grupo 1 foram classificados 156 vertices, no grupo 2, oito, e assim por diante. Para identificar quais vértices estão em cada grupo é só fazer:

# Vertices pertencentes ao grupo 8
mbp[mbp == 8]
##     Rio de Janeiro 25.078.994/0001-90 24.117.278/0001-01 
##                  8                  8                  8 
## 18.687.230/0001-36 19.523.306/0001-50 21.918.953/0001-03 
##                  8                  8                  8 
## 20.139.342/0001-02 22.791.074/0001-26 20.139.534/0001-00 
##                  8                  8                  8 
## 24.022.566/0001-82 10.787.647/0001-69 00.975.480/0001-06 
##                  8                  8                  8 
## 11.046.645/0001-81 02.296.928/0001-90 09.215.250/0001-13 
##                  8                  8                  8 
## 03.069.104/0001-40 
##                  8
# Vertices pertencentes ao grupo 4
mbp[mbp == 4]
##        São Gonçalo 17.517.779/0001-10 09.613.226/0001-32 
##                  4                  4                  4 
## 06.175.696/0001-73 
##                  4

Para identicar no gráfico cada vértice com o número do grupo a que pertencem, o código a seguir pode ser utilizado.

plot(cm, grafo, 
     vertex.size=7,
     vertex.label=as.character(mbp))


0.11 Visualização de redes

Neste tópico serão apresentados alguns recursos adicionais para a visualização de grafos além dos que já foram apresentados anteriormente.

O pacote igraph oferece um amplo conjunto de possibilidades com esta finalidade. Elenca-se a seguir alguns parâmetros que podem ser usados na função plot() com o objetivo de dar uma maior flexibilidade à visualização das redes.

Os principais parâmetros gráficos são apresentados a seguir:

Parâmetros relacionados aos vértices:

Parâmetro Descrição
vertex.color define a cor do vértice
vextex.frame.color define a cor da borda do vértice
vertex.shape define o formato do vértice
vertex.size define o tamanho do vértice. O tamanho “default” é 15
vertex.size2 define um segundo tamanho para o vértice
vextex.label define os nomes (labels) dos vértices
vertex.label.family define a fonte a para os nomes dos vértices (ex:“Times”, “Helvetica”)
vertex.label.font define o tipo da fonte: 1-plano, 2-negrito, 3-italico, 4-negrito itálico, 5-symbol
vertex.label.cex define o tamanho da fonte
vertex.label.dist define a distância entre o label e o vértice
vertex.label.degree define a posição do label em relação ao vértice: ‘0’-direita, ‘pi’-esquerda, ‘pi/2’-abaixo, ‘-pi/2’- acima


Parâmetros relacionados às arestas:

Parâmetro Descrição
edge.color define a cor das arestas
edge.width define a espessura das arestas. O valor padrão é 1
edge.arrow.size define o tamanho da seta. O valor padrão é 1
edge.arrow.width define a espessura da seta. O valor padrão é 1
edge.lty define o tipo da linha. Valores possíveis: 0 ou “blank”, 1 ou “solid”, 2 ou " dashed“, 3 ou”dotted“, 4 ou”dotdash“, 5 ou”longdash“, 6 ou”twodash“
edge.label vetor de caracteres para dar nomes às arestas
edge.label.family define a fonte a para os nomes das arestas (ex: “Times”, “Helvetica”)
edge.label.font define o tipo da fonte: 1-plano, 2-negrito, 3-italico, 4-negrito itálico, 5-symbol
edge.label.cex define o tamanho da fonte
edge.curved define a curvatura da aresta. Valores entre 0 e 1 (FALSE equivale a 0 e TRUE a 0.5)


Outros parâmetros

Parâmetro Descrição
margin define o tamanho das margens ao redor do gráfico
frame se TRUE desenha uma borda ao redor do gráfico
main adiciona título ao gráfico
sub adiciona subtítulo ao gráfico
asp valor numérico que define a relação altura/largura do gráfico
palette paleta de cor a ser usada para colorir os vértices
rescale valor lógico utilizado para indicar se as coordenadas devem ser reescaladas para [-1, 1]. A opção padrão é TRUE


0.11.1 Layout da rede

Os layouts da rede referem-se aos diveros algoritmos utilizados para definir as coordenadas dos vértices em um gráfico da rede.

Existem diversos layouts disponíveis para representar as redes. Alguns serão mais adequados que outros em determinadas situações. Existe a possibilidade de passar o layout diretamente como um valor do parâmetro layout= da função plot() ou utilizar uma função para o cálculo das coordenadas.

Alguns layouts disponíveis no pacote igraph são:

  • layout_in_circle()
  • layout_randomly()
  • layout_on_sphere()
  • layout_with_fr()
  • layout_with_kk()
  • layout_with_lgl()
  • layout_with_mds()
  • layout.fruchterman.reingold()
  • layout.kamada.kawai()
  • layout.reingold.tilford()
windowsFonts(JP1 = windowsFont("MS Mincho"),
             JP2 = windowsFont("MS Gothic"),
             JP3 = windowsFont("Arial Unicode MS"))

lout <- layout.fruchterman.reingold(grafo_sub)

plot(grafo_sub, layout=lout,
     vertex.label.family="JP2",
     edge.curved=0.2,
     vertex.label.cex=0.8,
     vertex.label.font=2,
     vertex.color="lightgreen")


0.12 Grafos ponderados

Um grafo ponderado apresenta pesos associados a cada uma de suas arestas. Estes pesos podem ser alguma medida da “força” do vínculo existente entre os vértices.

No exemplo em análise, será criada uma rede usando a variável pctTotalRecursosSPPS como peso. Essa variável informa o percentual dos recursos do RPPS aplicado em determinado fundo de investimento. Para ilustrar o procedimento será utilizado o subgrafo grafo_sub construído anteriormente.

# Definição do atributo width
E(grafo_sub)$width <- as.numeric(E(grafo_sub)$pctTotalRecursosSPPS)

# Criação do gráfico
plot(grafo_sub, vertex.size=9)


0.13 Grafos bipartite

Um grafo bipartite é um grafo no qual estão representadas as relações entre dois tipos de objeto. No grafo em análise estão representados tanto os RPPS quanto os fundos de investimentos. Para criar um gráfo bipartite é necessário alterar o atributo type dos vértices de forma a indicar o tipo de objeto de cada um deles. Isso pode ser feito da seguinte forma:

# Testa para verificar se o grafo é bipartite
is_bipartite(grafo)
## [1] FALSE
# Criar o grafo bipartite
V(grafo)$type <- grepl("^\\d", V(grafo)$name)
# Verificando 
is_bipartite(grafo)
## [1] TRUE

No código acima foram utilizados os nomes dos vértices para indicar se o vértice se refere ao RPPS ou ao fundo de investimento. Especificamente se o nome começa com um dígito foi atribuido o valor TRUE e FALSE caso contrário. Com isso foi associado o valor TRUE aos fundos de investimento e FALSE aos RPPS.

Para atribuir cores diferentes a cada um dos tipos de vértices, define-se o atributo color com as cores desejadas. No caso em análise será utilizado o atributo type para auxiliar na definição das cores.

V(grafo)$color <- ifelse(V(grafo)$type, "blue", "red")

Em azul estarão o fundos de investimento e em vermelho os RPPS.

plot(grafo,
     vertex.size=7,
     vertex.label=NA)

legend("topright",
       legend = c("RPPS", "FUNDOS"),
        fill = c("red", "blue"), bty="n")


0.13.1 Projeções

Em gráficos bipartite é possível obter dois subgrafos denominados projeções. No exemplo em análise, um subgrafo contendo os vértices relativos aos RPPS e outro subgrafo com os vértices relativos aos fundos de investimento.

No subgrafo dos RPPS estes estarão conectados se investem em fundos de investimento em comum. Da mesma forma, no subgrafo dos fundos de investimento estes estarão conectados caso tenham recebido investimentos de ao menos um RPPS comum.

Percebe-se assim que, diferentemente dos grafos onde o vínculo é direto, nas projeções o vínculo entre os vértices é índireto.

grafo.proj <- bipartite.projection(grafo)

Projeção dos RPPS

plot(grafo.proj$proj1,
     vertex.size=9,
     edge.color="lightgrey",
     edge.lty=3,
     vertex.label.color="black",
     vertex.label.font=2,
     vertex.color="lightgreen")

Projeção dos Fundos de Investimentos

plot(grafo.proj$proj2,
     vertex.size=9,
     edge.color="lightgrey",
     edge.lty=3,
     vertex.label.color="black",
     vertex.label.font=2,
     vertex.color="lightgreen")


0.14 Referências

Statistical Analysis of Network Data with R - Eric D. Kolaczyk; Gàbor Csàrdi
A User’s Guide to Network Analytis in R - Douglas A. Luke
Network visualization with R - Katherine Ognyanova www.kateto.net
Network anlysis and visualization with R and igraph - Katherine Ognyanova www.kateto.net
Fraud Analytics - Bart Baesens, Véronique van Vlasselaer, Wouter Verbeke