Neste post abordaremos a última palavra em modelos estatísticos para dados relacionais os exponential random graph models, ou modelos p* (pê-estrela). Iniciaremos com um post mais técnico focado nos comandos e possíveis parâmetros do pacote statnet. No próximo post, abordaremos questões mais conceituais relacionadas aos modelos bem como sua interpretação.
Para ver uma versão deste post organizado num belo e eficiente layout chamado Tufte Handout inspirado nos livros e comunicações do prof. Edward Tufte, acesse aqui.
Os modelos p* foram desenvolvidos para dar conta de dados que assumem interdependência das observações. Ora, os modelos econométricos clássicos (regressão linear, regressão logística, etc.) assumem independência das observações e portanto não devem ser aplicados a dados relacionais. Em outras palavras, se A conhece C, a existência de um laço entre B e C altera a probabilidade de existência de um laço entre B e A[1].
O modelo p* pode ser definido por
onde Y é o grafo teórico simulado, y é o grafo observado, ΣA é a soma de todas as configurações A, ηA é o parâmetro estimado correpondente à configuração A, gA(y) é a estatística da configuração A observada no grafo y e k é uma constante que assegura a distribuição de probabilidades[2].
Para estimar um modelo p* no R, usaremos os comandos da suite statnet e o pacote igraph para auxílio com a importação dos dados quando for o caso. Vamos ao primeiro exemplo.
ERGM - Florentine Families
Vamos tentar modelar a rede casamentos de famílias de Florença do famoso trabalho de Padgett (1994)[3]. Primeiro, investiguemos a rede.
library(statnet)
library(sand)
library(intergraph)
data(florentine)
flomarriage
## Network attributes:
## vertices = 16
## directed = FALSE
## hyper = FALSE
## loops = FALSE
## multiple = FALSE
## bipartite = FALSE
## total edges= 20
## missing edges= 0
## non-missing edges= 20
##
## Vertex attribute names:
## priorates totalties vertex.names wealth
##
## No edge attributes
plot(flomarriage, displaylabels=T,
label.cex=0.8, main="Padgett Florentine Families")
Investiguemos agora os atributos. Como acho mais fácil fazer isso usando o pacote igraph, vamos a ele.
marriage <- asIgraph(flomarriage)
V(marriage)$priorates
## [1] 53 65 0 12 22 0 21 0 53 0 42 0 38 35 74 0
V(marriage)$totalties
## [1] 2 3 14 9 18 9 14 14 54 7 32 1 4 5 29 7
V(marriage)$wealth
## [1] 10 36 55 44 20 32 8 42 103 48 49 3 27 10 146 48
Podemos plotar a rede representando algum atributo. Vamos representar a riqueza com o tamanho dos vértices. Como o pacote igraph lê os valores absolutos e o grafo fica muito poluído, vamos dividir o vetor de valores de riqueza por 4 para uma visualização mais limpa.
plot(marriage, vertex.label.cex=0.9,
vertex.label=V(marriage)$vertex.names,
vertex.size=V(marriage)$wealth/4,
vertex.color="lightblue",
vertex.label.color="black",
main = "Padgett Florentine Families",
xlab="Size = Wealth")
Agora vamos à modelagem. Primeiro, é comum estimarmos o modelo nulo[4], ou seja, um modelo apenas com o parâmetro edges que corresponde ao intercepto dos modelos econométricos comuns. Este modelo toma a díade por unidade de análise e estima a “propensão dos atores a escolherem outros atores, serem escolhidos por outros, a fazer escolhas recíprocas e a tendência média (parâmetro de densidade) a interagir com os outros (Lazega & Higgins, 2014, p. 81)[5].
fit1 <- ergm(flomarriage~edges)
summary(fit1)
##
## ==========================
## Summary of model fit
## ==========================
##
## Formula: flomarriage ~ edges
##
## Iterations: 5 out of 20
##
## Monte Carlo MLE Results:
## Estimate Std. Error MCMC % p-value
## edges -1.6094 0.2449 0 <1e-04 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Null Deviance: 166.4 on 120 degrees of freedom
## Residual Deviance: 108.1 on 119 degrees of freedom
##
## AIC: 110.1 BIC: 112.9 (Smaller is better.)
O parâmetro negativo e significativo nos mostra que esta rede se desenvolve com menos laços entre seus atores do que esperaríamos numa rede aleatória[6]. Em seguida, vamos estimar um modelo controlando por triângulos e por distribuição de laços compartilhados geometricamente pesada[7] (gwesp). Vamos usar os comandos formula e summary.statistics para contar as estatísticas existentes na rede original.
model2 <- formula(flomarriage~edges+triangle+
gwesp(1,fixed=T))
summary.statistics(model2)
## edges triangle gwesp.fixed.1
## 20.000000 3.000000 8.632121
Em seguida, “rodamos” o modelo.
fit2 <- ergm(model2)
summary(fit2)
##
## ==========================
## Summary of model fit
## ==========================
##
## Formula: flomarriage ~ edges + triangle + gwesp(1, fixed = T)
##
## Iterations: 2 out of 20
##
## Monte Carlo MLE Results:
## Estimate Std. Error MCMC % p-value
## edges -1.7424 0.3733 0 <1e-04 ***
## triangle -4.6371 7.8862 0 0.558
## gwesp.fixed.1 1.8157 2.8884 0 0.531
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Null Deviance: 166.4 on 120 degrees of freedom
## Residual Deviance: 107.6 on 117 degrees of freedom
##
## AIC: 113.6 BIC: 122 (Smaller is better.)
Às vezes é útil avaliar o processo de amostragem por Monte Carlo Markov Chain (MCMC). Para isso, usamos o comando plot()
.
plot(fit2)
## Sample statistics summary:
##
## Iterations = 16384:4209664
## Thinning interval = 1024
## Number of chains = 1
## Sample size per chain = 4096
##
## 1. Empirical mean and standard deviation for each variable,
## plus standard error of the mean:
##
## Mean SD Naive SE Time-series SE
## edges -0.02808 4.011 0.06266 0.06266
## triangle -0.03198 1.950 0.03047 0.03047
## gwesp.fixed.1 -0.12463 5.376 0.08400 0.08400
##
## 2. Quantiles for each variable:
##
## 2.5% 25% 50% 75% 97.5%
## edges -8.000 -3 0.000e+00 3 8.00
## triangle -3.000 -1 0.000e+00 1 4.00
## gwesp.fixed.1 -8.632 -3 2.409e-13 3 11.63
##
##
## Sample statistics cross-correlations:
## edges triangle gwesp.fixed.1
## edges 1.0000000 0.7350670 0.7410482
## triangle 0.7350670 1.0000000 0.9978614
## gwesp.fixed.1 0.7410482 0.9978614 1.0000000
##
## Sample statistics auto-correlation:
## Chain 1
## edges triangle gwesp.fixed.1
## Lag 0 1.000000000 1.000000000 1.000000000
## Lag 1024 0.009105098 0.006407971 0.006171425
## Lag 2048 -0.025615341 -0.023582057 -0.023856444
## Lag 3072 0.012069051 0.023109858 0.022171381
## Lag 4096 0.001542952 -0.009649964 -0.010962329
## Lag 5120 -0.004242831 -0.009134193 -0.008909237
##
## Sample statistics burn-in diagnostic (Geweke):
## Chain 1
##
## Fraction in 1st window = 0.1
## Fraction in 2nd window = 0.5
##
## edges triangle gwesp.fixed.1
## 1.0923 0.1873 0.1368
##
## Individual P-values (lower = worse):
## edges triangle gwesp.fixed.1
## 0.2747163 0.8514111 0.8911700
## Joint P-value (lower = worse): 0.3727567 .
## Loading required namespace: latticeExtra
Para verificar o ajuste e a convergência do modelo, usamos o comando gof()
.
gof2 <- gof(fit2)
par(mfrow=c(1,3))
plot(gof2)
O modelo parece se ajustar bem aos dados. Este modelo convergiu e podemos confiar nos parâmetros estimados para explicar a emergência de nossa rede observada. Neste caso, observando os resultados, apesar de triângulos e distribuição de laços compartilhados terem estimadores respectivamente negativo e postivo, eles não são estatisticamente significativos indicando que essas configurações não são importantes para entendermos o surgimento da rede.
Agora, como outro exemplo, vamos trabalhar com os dados dos advogados de Lazega.
ERGM - Lazega’s lawyers
Estes dados estão no pacote sand. Precisamos transformá-los num objeto de classe network para realizar a estimação do p*.
lazega <- upgrade_graph(lazega)
lawyers <- asNetwork(lazega)
fit1 <- ergm(lawyers~edges)
summary(fit1)
##
## ==========================
## Summary of model fit
## ==========================
##
## Formula: lawyers ~ edges
##
## Iterations: 5 out of 20
##
## Monte Carlo MLE Results:
## Estimate Std. Error MCMC % p-value
## edges -1.4992 0.1031 0 <1e-04 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Null Deviance: 873.4 on 630 degrees of freedom
## Residual Deviance: 598.8 on 629 degrees of freedom
##
## AIC: 600.8 BIC: 605.2 (Smaller is better.)
Depois de estimar o modelo P1, vamos estimar um modelo com triângulos e distribuição de laços compartilhados.
model2 <- formula(lawyers~edges+triangle+
gwesp(1,fixed=T))
summary.statistics(model2)
## edges triangle gwesp.fixed.1
## 115.0000 120.0000 213.1753
fit2 <- ergm(model2)
summary(fit2)
##
## ==========================
## Summary of model fit
## ==========================
##
## Formula: lawyers ~ edges + triangle + gwesp(1, fixed = T)
##
## Iterations: 2 out of 20
##
## Monte Carlo MLE Results:
## Estimate Std. Error MCMC % p-value
## edges -3.6696 0.3120 0 <1e-04 ***
## triangle -0.2200 0.2129 0 0.302
## gwesp.fixed.1 0.9804 0.2463 0 <1e-04 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Null Deviance: 873.4 on 630 degrees of freedom
## Residual Deviance: 519.8 on 627 degrees of freedom
##
## AIC: 525.8 BIC: 539.1 (Smaller is better.)
Vamos ver o ajuste desse modelo aos dados e sua convergência.
gof2 <- gof(fit2)
par(mfrow=c(1,3))
plot(gof2)
Vamos tentar elaborar um modelo um pouco mais complexo usando algumas estatísticas a mais além dos atributos dos atores. Dessa vez vamos modelar ainda a distribuição de grau e os atributos gênero, escritório que pertence, escola de origem, idade e senioridade. Atributos qualitativos são inseridos no modelo por semelhança. Atributos quantitativos são inseridos, neste caso, somando-se os valores de díades para todos os atores na rede. A lista de estatísticas possíveis é longa e podemos investigá-la com o comando help("ergm-terms")
. Vamos ao modelo:
model3 <- formula(lawyers~edges+triangle+
gwesp(1,fixed=T)+gwdegree(1,fixed=T)+
nodematch("Gender")+nodematch("Office")+
nodematch("School")+nodecov("Age")+
nodecov("Seniority"))
summary.statistics(model3)
## edges triangle gwesp.fixed.1 gwdegree
## 115.0000 120.0000 213.1753 79.2000
## nodematch.Gender nodematch.Office nodematch.School nodecov.Age
## 99.0000 85.0000 36.0000 10526.0000
## nodecov.Seniority
## 4687.0000
fit3 <- ergm(model3)
summary(fit3)
##
## ==========================
## Summary of model fit
## ==========================
##
## Formula: lawyers ~ edges + triangle + gwesp(1, fixed = T) + gwdegree(1,
## fixed = T) + nodematch("Gender") + nodematch("Office") +
## nodematch("School") + nodecov("Age") + nodecov("Seniority")
##
## Iterations: 3 out of 20
##
## Monte Carlo MLE Results:
## Estimate Std. Error MCMC % p-value
## edges 1.348214 2.393001 0 0.573367
## triangle -0.142962 0.216670 0 0.509616
## gwesp.fixed.1 0.936593 0.275091 0 0.000705 ***
## gwdegree 0.462105 0.632732 0 0.465462
## nodematch.Gender 0.339332 0.214801 0 0.114673
## nodematch.Office 0.990756 0.179510 0 < 1e-04 ***
## nodematch.School -0.004922 0.253659 0 0.984524
## nodecov.Age -0.053593 0.018657 0 0.004211 **
## nodecov.Seniority -0.025914 0.015030 0 0.085180 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Null Deviance: 873.4 on 630 degrees of freedom
## Residual Deviance: 475.5 on 621 degrees of freedom
##
## AIC: 493.5 BIC: 533.6 (Smaller is better.)
Para exemplos de interpretação, veja Lusher, Koskinen & Robins (2012) e Salej Higgins et al (2014)[8]
Vamos agora investigar o ajuste do modelo aos dados e sua convergência.
gof3 <- gof(fit3)
par(mfrow=c(1,3))
plot(gof3)
Quando trabalhamos com modelos p*, as possibilidades são enormes. Devemos testar várias configurações de estatísticas modeladas de acordo com o que pretendemos investigar na rede em questão e com o ajuste do modelo aos dados.
Algoritmos
O algoritmo default pelo qual o pacote statnet realiza a estimação dos modelos p* é o Geyer-Thompson (1992) (MCMLE)[9]. Este algoritmo costuma retornar os melhores resultados mas, por ser extremamente exigente (os modelos devem convergir 2 vezes, do contrário, o comando dá erro e para) pode não rodar para algumas redes mais extensas. O pacote statnet permite o uso de mais três algoritmos além do MCMLE: o algoritmo Robbins-Monro (usado pela família de softwares PNET), Metropolis-Hastings (aproximação estocástica) e Stepping. Vamos realizar a estimação do modelo 3 para os dados de Lazega com os outros 3 algoritmos e vamos compará-los.
fit3.rb <- ergm(model3,
control=control.ergm(main.method = "Robbins-Monro"))
fit3.sa <- ergm(model3,
control=control.ergm(main.method = "Stochastic-Approximation"))
fit3.step <- ergm(model3,
control=control.ergm(main.method = "Stepping"))
library(texreg)
htmlreg(list(fit3, fit3.rb, fit3.sa, fit3.step),
single.row=T, caption.above = T,
custom.model.names=c("Geyer-Thompson", "Robbins-Monro","Metropolis-Hastings","Stepping"))
Geyer-Thompson | Robbins-Monro | Metropolis-Hastings | Stepping | ||
---|---|---|---|---|---|
edges | 1.35 (2.39) | 0.90 | -0.17 (2.61) | 0.88 (2.37) | |
triangle | -0.14 (0.22) | 0.12 | -0.00 (0.22) | -0.10 (0.20) | |
gwesp.fixed.1 | 0.94 (0.28)*** | 0.69 | 0.79 (0.31)* | 0.88 (0.25)*** | |
gwdegree | 0.46 (0.63) | 0.90 | 1.16 (0.72) | 0.37 (0.60) | |
nodematch.Gender | 0.34 (0.21) | 0.35 | 0.32 (0.23) | 0.34 (0.22) | |
nodematch.Office | 0.99 (0.18)*** | 1.33 | 0.70 (0.21)** | 0.97 (0.19)*** | |
nodematch.School | -0.00 (0.25) | -0.36 | 0.06 (0.23) | -0.02 (0.26) | |
nodecov.Age | -0.05 (0.02)** | -0.05 | -0.04 (0.02)* | -0.05 (0.02)** | |
nodecov.Seniority | -0.03 (0.02) | -0.01 | -0.02 (0.02) | -0.02 (0.02) | |
AIC | 493.54 | 507.18 | 511.53 | 494.43 | |
BIC | 533.55 | 547.19 | 551.54 | 534.44 | |
Log Likelihood | -237.77 | -244.59 | -246.76 | -238.21 | |
***p < 0.001, **p < 0.01, *p < 0.05 |
Vamos verificar o ajuste dos modelos estimados com os outros algoritmos.
gof3.rb <- gof(fit3.rb)
gof3.sa <- gof(fit3.sa)
gof3.step <- gof(fit3.step)
par(mfrow=c(1,3))
plot(gof3.rb,
main = "Goodness-of-fit 'Robbins-Monro'")
plot(gof3.sa,
main = "Goodness-of-fit 'Metropolis-Hastings'")
plot(gof3.step,
main="Goodness-of-fit 'Stepping'")
É importante lembrar que…
A estimação de um modelo p* pode ser um processo longo e cansativo. Não há testes ou métodos para escolha prévia de um modelo a não ser o bom e velho “tentativa e erro”. A partir do seu problema de pesquisa, escolha os parâmetros que deseja estimar e vá melhorando o modelo escolhendo outros parâmetros. Muita atenção: o modelo deve convergir e os parâmetros estimados devem estar dentro dos limites dos plots de Goodness-of-fit. O algoritmo Geyer-Thompson tem a vantagem (ou não) de não rodar quando o modelo não converge. Os outros algoritmos rodam de qualquer modo e checamos se o modelo está degenerado pelo GOF. É importantíssimo salientar que o modelo deve convergir completamente. Um modelo que não converge em algum de seus parâmetros não quer dizer absolutamente nada (Lusher, Koskinem & Robins, 2012)[10].
[1] Granovetter, M. S. (1973). The strength of weak ties. American journal of sociology, 1360-1380.
[2] Robins, Garry et al. An introduction to exponential random graph (p*) models for social networks. Social networks, Elsevier, v. 29, n. 2, p. 173–191, 2007.
[3] Padgett, John F. (1994) Marriage and Elite Structure in Renaissance Florence, 1282-1500. Paper delivered to the Social Science History Association.
[4] Este modelo é também conhecido como P1 e foi desenvolvido por Holland e Leinhardt.
[5] Lazega, E., & Higgins, S. S. (2014). Redes sociais e estruturas relacionais. Belo Horizonte, MG: Fino Traço.
[6] No próximo post pretendo explicar melhor o conceito teórico de rede aleatória e como ele é mobilizado na interpretação dos ERGMs.
[7] Geometrically weighted edgewise shared partner distribution.
[8] Salej Higgins, S., Ribeiro, A., Botrel de Vasconcellos, M., & Estevão Barbosa, J. (2014). L’émergence d’une structure cœur-périphérie dans un réseau brésilien de copublications en sciences comptables. Communiquer. Revue de communication sociale et publique, (12), 43-60.
[9] Lusher, D., Koskinen, J., & Robins, G. (2012). Exponential random graph models for social networks: Theory, methods, and applications. Cambridge University Press.
[10] Lusher, D., Koskinen, J., & Robins, G. (2012). Exponential random graph models for social networks: Theory, methods, and applications. Cambridge University Press.