En primer lugar, cargamos/instalmos las librerías que vamos a usar y leemos la base de datos en la variable “df”, nos quedamos solo con los datos completos, tiramos los niveles que no se usan, renombramos el identimicador de la encuesta de V3 a ID y exploramos las primeras líneas:

if (!require("pacman")) install.packages("pacman")
pacman::p_load("MASS","tidyverse","haven","stringr", "FactoMineR","factoextra","corrplot","gplots","ggrepel")
df <- read_sav("WV6_Data_Spain_2011_Spss_v20180912.sav") %>%
#  select (V3,V39, V46, V218_ESMA, V217_ESMA, V219_ESMA, V220_ESMA, V221_ESMA, V222_ESMA, V223_ESMA) %>%
#  rename(ID=V3) %>%
  as_factor() %>%
  filter(complete.cases(.)) %>% 
  droplevels()

df %>% head()

Es posible que nos interese simplificar ideologia en pocas categorias:


df2 = df %>% mutate(Ideol=as.numeric(Ideol)) %>%
  mutate(Ideol=ifelse(Ideol<=5,                 1,
                      ifelse(Ideol<=10,          2,
                                                3))) %>%
  mutate(CatLab=ifelse(CatLab %in% c("CASA","AUTON","DESEMP","OTROS","TParc","ESTUD"),"PrecLab",as.character(CatLab)))%>%
  mutate(PriorNac=ifelse(PriorNac %in% c("PriorINDEF",  "PriorNO"),"PriorNO",as.character(PriorNac))) %>%
  mutate(ConfExtr=ifelse(ConfExtr %in% c("ALGO",    "COMPLETAMENTE"),"ConfSI", "ConfNo"#as.character(ConfExtr)
                           )) %>%
    mutate(ClaseSoc=ifelse(ClaseSoc %in% c("ALTA",  "MEDIA ALTA","MEDIA BAJA"),"ClMedia","ClBaja")) %>%
  
select(-OrgNac,-TV)

En formato “tidy”:

df3 <- df2 %>% gather("Variable", "Valor",-ID)
attributes are not identical across measure variables;
they will be dropped
df3 %>% arrange(ID) %>% head()

vamos a explorar visualmente qué pintan tienen las respuestas. Empezamos por un tabla simple de recuentos:

tabla=xtabs (~ Variable+Valor,data=df3)
tabla %>% knitr::kable()
1 2 ClBaja ClMedia ConfNo ConfSI EmailNO EmailSI InmNO InmSI InternetNO InternetSI JUB MovilNO MovilSI PeriodicoNO PeriodicoSI PrecLab PriorNO PriorSI RadioNO RadioSI RevistaNO RevistaSI TCompl
CatLab 0 0 0 0 0 0 0 0 0 0 0 0 171 0 0 0 0 408 0 0 0 0 0 0 271
ClaseSoc 0 0 236 614 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ConfExtr 0 0 0 0 431 419 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Email 0 0 0 0 0 0 612 238 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Ideol 605 245 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Internet 0 0 0 0 0 0 0 0 0 0 523 327 0 0 0 0 0 0 0 0 0 0 0 0 0
Movil 0 0 0 0 0 0 0 0 0 0 0 0 0 503 347 0 0 0 0 0 0 0 0 0 0
Periodico 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 302 548 0 0 0 0 0 0 0 0
PriorNac 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 388 462 0 0 0 0 0
Radio 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 431 419 0 0 0
Revista 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 580 270 0
VecInm 0 0 0 0 0 0 0 0 68 782 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Escalamiento múltiple

El modelo que se ha hecho en SPSS tenía esta pinta si lo he visto bien:

fit <- MCA(df2 %>% select(-ID)%>%mutate_all(as.factor), ncp = 2, graph = TRUE)

Los autovalores son:

get_eigenvalue(fit)
       eigenvalue variance.percent cumulative.variance.percent
Dim.1  0.24064005        22.212928                    22.21293
Dim.2  0.12253258        11.310700                    33.52363
Dim.3  0.10388916         9.589769                    43.11340
Dim.4  0.08408853         7.762018                    50.87541
Dim.5  0.08022838         7.405696                    58.28111
Dim.6  0.07596893         7.012516                    65.29363
Dim.7  0.06973865         6.437414                    71.73104
Dim.8  0.06799284         6.276262                    78.00730
Dim.9  0.06653336         6.141541                    84.14884
Dim.10 0.05746678         5.304626                    89.45347
Dim.11 0.05341440         4.930560                    94.38403
Dim.12 0.04110216         3.794046                    98.17808
Dim.13 0.01973751         1.821924                   100.00000
fviz_screeplot(fit, addlabels = TRUE)

Realmente si nos quedamos solo con 2 dimensiones no vamos a ver mucho, pues queda mucha información fuera de ellas, pero vamos a echar un vistazo e todos modos:

fviz_mca_var(fit, choice = "mca.cor", col.var="black",
            repel = TRUE, 
            ggtheme = theme_minimal())

fviz_mca_var(fit, 
             repel = TRUE, col.var="black",
             ggtheme = theme_minimal())

Ya vimos que 2 dimensiones se quedaban cortas para representar las variables. Vamos a poner de color rojo las que estén mejor representadas en un plano, y de otros colores conforme vayamos perdiendo esta propiedad

fviz_mca_var(fit, col.var = "cos2",
             gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"), 
             repel = TRUE, # Avoid text overlapping
             ggtheme = theme_minimal())

Una forma de ver quien está mejor o peor representado también sería asociando, en lugar de color rojo, un grado de transparencia al símbolo que lo representa:

fviz_mca_var(fit, alpha.var="cos2", col.var="black",
             repel = TRUE,
             ggtheme = theme_minimal()) 

Ordenemos de mejor a peor lo bien que se representan cada valor de las variables en un plano:

fviz_cos2(fit, choice = "var", axes = 1:2)

Aparentemente, en la clasificación sobre la cuestión de los inmigrantes y TV, la cuestión de dónde se sitúa las respuestas de la gente no se puede asociar muy bien con las dimensiones del plano donde lo hemos representado.

Estudiando el grado de independencia entre variables

Hasta aquí era MDS estándar. Vamos a explorar un poco más por nuesra cuenta. Empezamos por el grado de independencia que hay entre cualquier par de variables que haya, para poder establecer un grado de disimilaridad.

Variables=df3$Variable %>% unique() %>% setdiff("ID")
tablaParejas=expand.grid(V1=Variables, V2=Variables)
tablaParejas %>% head(20) %>% knitr::kable()
V1 V2
VecInm VecInm
PriorNac VecInm
Ideol VecInm
ConfExtr VecInm
Revista VecInm
Periodico VecInm
Radio VecInm
Movil VecInm
Email VecInm
Internet VecInm
CatLab VecInm
ClaseSoc VecInm
VecInm PriorNac
PriorNac PriorNac
Ideol PriorNac
ConfExtr PriorNac
Revista PriorNac
Periodico PriorNac
Radio PriorNac
Movil PriorNac

Vamos a añadir una medida de distancia para cada combinación. En principio tomo esta que hace que dos variables sean menos distantes cuanto menos independiente sean.

#Esto es solo un truco para hacer pruebas chi2 entre la misma variable, cosa que no tiene interés.
df2b=df2
names(df2b)=str_c(names(df2),"b")
df2TRUCO=cbind(df2,df2b)

Voy a ver qué pinta tienen los “p” de independencia entre parejas de variables:

tablaParejas$p=map2_dbl(tablaParejas[,1],tablaParejas[,2], ~ (xtabs(formula(str_c("~",.x,"+",.y,"b")),data=df2TRUCO) %>% chisq.test() %>% .[["p.value"]]  ))
matrizP <- matrix(round(tablaParejas$p,5), nrow = length(Variables), dimnames=list(Variables,Variables))
matrizP
           VecInm PriorNac   Ideol ConfExtr Revista Periodico   Radio   Movil   Email Internet  CatLab ClaseSoc
VecInm    0.00000  0.00003 0.00003  0.00000 0.76515   0.92843 0.99596 0.56101 0.31886  0.04653 0.00210  0.64742
PriorNac  0.00003  0.00000 0.00329  0.00084 0.04202   0.62939 0.07880 0.12690 0.29276  0.14748 0.03456  0.29768
Ideol     0.00003  0.00329 0.00000  0.00127 0.21180   0.38012 0.24169 0.59159 0.18273  0.72653 0.65984  0.03420
ConfExtr  0.00000  0.00084 0.00127  0.00000 0.00032   0.70598 0.01373 0.53456 0.00547  0.00419 0.00027  0.00991
Revista   0.76515  0.04202 0.21180  0.00032 0.00000   0.00000 0.00000 0.00000 0.00000  0.00000 0.00007  0.00001
Periodico 0.92843  0.62939 0.38012  0.70598 0.00000   0.00000 0.00000 0.04440 0.00000  0.00000 0.00000  0.00771
Radio     0.99596  0.07880 0.24169  0.01373 0.00000   0.00000 0.00000 0.00000 0.00000  0.00000 0.00091  0.00629
Movil     0.56101  0.12690 0.59159  0.53456 0.00000   0.04440 0.00000 0.00000 0.00000  0.00000 0.00003  0.00000
Email     0.31886  0.29276 0.18273  0.00547 0.00000   0.00000 0.00000 0.00000 0.00000  0.00000 0.00000  0.00000
Internet  0.04653  0.14748 0.72653  0.00419 0.00000   0.00000 0.00000 0.00000 0.00000  0.00000 0.00000  0.00000
CatLab    0.00210  0.03456 0.65984  0.00027 0.00007   0.00000 0.00091 0.00003 0.00000  0.00000 0.00000  0.00038
ClaseSoc  0.64742  0.29768 0.03420  0.00991 0.00001   0.00771 0.00629 0.00000 0.00000  0.00000 0.00038  0.00000

Con dibujitos lo veo mejor:

corrplot(matrizP,  is.corr = FALSE) 

Vamos a hacer algo similar, creando una distancias entre variables según una medida del grado de independencia

tablaParejas$distancia=map2_dbl(tablaParejas[,1],tablaParejas[,2], ~ (xtabs(formula(str_c("~",.x,"+",.y,"b")),data=df2TRUCO) %>% chisq.test() %>% (function(chi2) (1/(1+abs(chi2[["statistic"]]))/chi2[["parameter"]]))(.)
                                                               
                                                                        ))

Con ella contruyo la matriz de distancias, asegurándome de que la diagonal la pongo en cero

matrizDistancias <- matrix(round(tablaParejas$distancia,4), nrow = length(Variables), dimnames=list(Variables,Variables))
for (i in 1:nrow(matrizDistancias))matrizDistancias[i,i]=0L
matrizDistancias
          VecInm PriorNac  Ideol ConfExtr Revista Periodico  Radio  Movil  Email Internet CatLab ClaseSoc
VecInm    0.0000   0.0537 0.0547   0.0414  0.9181    0.9920 1.0000 0.7474 0.5016   0.2015 0.0375   0.8270
PriorNac  0.0537   0.0000 0.1037   0.0823  0.1948    0.8111 0.2445 0.3003 0.4746   0.3228 0.0647   0.4797
Ideol     0.0547   0.1037 0.0000   0.0879  0.3908    0.5649 0.4218 0.7765 0.3603   0.8910 0.2730   0.1823
ConfExtr  0.0414   0.0823 0.0879   0.0000  0.0718    0.8754 0.1414 0.7216 0.1147   0.1087 0.0287   0.1307
Revista   0.9181   0.1948 0.3908   0.0718  0.0000    0.0122 0.0113 0.0117 0.0113   0.0214 0.0248   0.0501
Periodico 0.9920   0.8111 0.5649   0.8754  0.0122    0.0000 0.0162 0.1984 0.0327   0.0226 0.0079   0.1235
Radio     1.0000   0.2445 0.4218   0.1414  0.0113    0.0162 0.0000 0.0319 0.0225   0.0300 0.0333   0.1181
Movil     0.7474   0.3003 0.7765   0.7216  0.0117    0.1984 0.0319 0.0000 0.0042   0.0077 0.0228   0.0311
Email     0.5016   0.4746 0.3603   0.1147  0.0113    0.0327 0.0225 0.0042 0.0000   0.0022 0.0071   0.0250
Internet  0.2015   0.3228 0.8910   0.1087  0.0214    0.0226 0.0300 0.0077 0.0022   0.0000 0.0054   0.0185
CatLab    0.0375   0.0647 0.2730   0.0287  0.0248    0.0079 0.0333 0.0228 0.0071   0.0054 0.0000   0.0299
ClaseSoc  0.8270   0.4797 0.1823   0.1307  0.0501    0.1235 0.1181 0.0311 0.0250   0.0185 0.0299   0.0000
tablaParejas$distancia=map2_dbl(tablaParejas[,1],tablaParejas[,2], ~ (xtabs(formula(str_c("~",.x,"+",.y,"b")),data=df2TRUCO) %>% chisq.test() %>% (function(chi2)chi2[["statistic"]]/chi2[["parameter"]])(.) %>%  (function(x)1/(1+sqrt(abs(x))) )(.)))
tablaParejas %>% head(20)

Con ella contruyo la matriz de distancias, asegurándome de que la diagonal la pongo en cero


matrizDistancias <- matrix(round(tablaParejas$distancia,4), nrow = length(Variables), dimnames=list(Variables,Variables))
for (i in 1:nrow(matrizDistancias))matrizDistancias[i,i]=0L
matrizDistancias
          VecInm PriorNac  Ideol ConfExtr Revista Periodico  Radio  Movil  Email Internet CatLab ClaseSoc
VecInm    0.0000   0.1924 0.1938   0.1721  0.7700    0.9176 0.9950 0.6324 0.5008   0.3344 0.2871   0.6862
PriorNac  0.1924   0.0000 0.2538   0.2305  0.3297    0.6745 0.3626 0.3958 0.4873   0.4084 0.3528   0.4899
Ideol     0.1938   0.2538 0.0000   0.2369  0.4447    0.5326 0.4607 0.6508 0.4287   0.7409 0.6080   0.3208
ConfExtr  0.1721   0.2305 0.2369   0.0000  0.2176    0.7261 0.2887 0.6169 0.2647   0.2588 0.2587   0.2794
Revista   0.7700   0.3297 0.4447   0.2176  0.0000    0.1001 0.0967 0.0982 0.0967   0.1289 0.2442   0.1868
Periodico 0.9176   0.6745 0.5326   0.7261  0.1001    0.0000 0.1137 0.3322 0.1554   0.1319 0.1519   0.2729
Radio     0.9950   0.3626 0.4607   0.2887  0.0967    0.1137 0.0000 0.1537 0.1316   0.1496 0.2743   0.2679
Movil     0.6324   0.3958 0.6508   0.6169  0.0982    0.3322 0.1537 0.0000 0.0608   0.0811 0.2361   0.1519
Email     0.5008   0.4873 0.4287   0.2647  0.0967    0.1554 0.1316 0.0608 0.0000   0.0448 0.1454   0.1381
Internet  0.3344   0.4084 0.7409   0.2588  0.1289    0.1319 0.1496 0.0811 0.0448   0.0000 0.1288   0.1207
CatLab    0.2871   0.3528 0.6080   0.2587  0.2442    0.1519 0.2743 0.2361 0.1454   0.1288 0.0000   0.2629
ClaseSoc  0.6862   0.4899 0.3208   0.2794  0.1868    0.2729 0.2679 0.1519 0.1381   0.1207 0.2629   0.0000

Vamos a visualizar esas distancias

corrplot(matrizDistancias,  is.corr = FALSE) 

Vamos a intentar representar esas distancias como si de distancias entre ciudades se tratase usando escalamiento dimensional:

Podemos hacerlo

fit <- cmdscale(matrizDistancias,eig=TRUE,k=2) 
fit
$points
                   [,1]         [,2]
VecInm    -0.5852622238  0.179064849
PriorNac  -0.2302196247 -0.075967082
Ideol     -0.2477231106 -0.354561210
ConfExtr  -0.2355390852 -0.100291092
Revista    0.1762511274 -0.073625632
Periodico  0.3771678016  0.005211178
Radio      0.3204803496 -0.170238285
Movil      0.1717794473  0.157853408
Email      0.0728919652  0.045474403
Internet   0.0679933218  0.265194838
CatLab    -0.0005076661  0.183511292
ClaseSoc   0.1126876974 -0.061626666

$eig
 [1]  8.405493e-01  3.428243e-01  1.789290e-01  1.253356e-01  5.868912e-02  9.340754e-03  4.297083e-03 -1.110223e-16 -1.340308e-02 -1.107221e-01 -2.090123e-01 -3.821644e-01

$x
NULL

$ac
[1] 0

$GOF
[1] 0.5201032 0.7585898
Coordenadas=fit$points%>% as_tibble() %>% mutate(Variable=dimnames(fit$points)[[1]])


ggplot(Coordenadas,aes(x=V1,y=V2,label=Variable)) + geom_point() +geom_text_repel(alpha=0.5)+
  coord_cartesian(xlim=c(-1,1) ,ylim=c(-0.5,0.5))+theme_minimal()

No me hace gracia ver autovalores negativos, así que cambio a otro método:

fit <- isoMDS(matrizDistancias, k=2) 
initial  value 22.914048 
iter   5 value 16.143230
iter  10 value 15.314215
iter  15 value 14.823088
final  value 14.236163 
converged
fit
$points
                 [,1]        [,2]
VecInm    -0.94856770 -0.02424811
PriorNac  -0.71870231 -0.39872698
Ideol     -0.48407458 -0.68807952
ConfExtr  -0.38688095 -0.07385496
Revista    0.37342093  0.11479621
Periodico  0.64960110 -0.18053381
Radio      0.58121305  0.06477342
Movil      0.29099449  0.52300367
Email      0.24991008  0.25405642
Internet   0.21171970  0.25031406
CatLab    -0.06217582  0.34546959
ClaseSoc   0.24354201 -0.18696999

$stress
[1] 14.23616
Coordenadas=fit$points%>% as_tibble() %>% mutate(Variable=dimnames(fit$points)[[1]])


ggplot(Coordenadas,aes(x=V1,y=V2,label=Variable)) + geom_point() +geom_text_repel(alpha=0.5)+
  coord_cartesian(xlim=c(-1.25,1.25) ,ylim=c(-1,1))+theme_minimal()

Bueno, con eso nos hacemos una idea de qué variables son las más independientes entre sí. Por ejemplo:

LS0tCnRpdGxlOiAiQW5hbGlzaXMgZGUgY29ycmVzcG9uZGVuY2lhcyIKYXV0aG9yOiAiQW5hIE1hcsOtYSBMw7NwZXogTmFyYm9uYSIKZGF0ZTogIjIwMTktMDYtMDYiCm91dHB1dDogaHRtbF9ub3RlYm9vawoKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGRwaT0zMDAsZmlnLndpZHRoPTEyLGZpZy5oZWlnaHQ9MTApCmBgYAoKRW4gcHJpbWVyIGx1Z2FyLCBjYXJnYW1vcy9pbnN0YWxtb3MgbGFzIGxpYnJlcsOtYXMgcXVlIHZhbW9zIGEgdXNhciB5IGxlZW1vcyBsYSBiYXNlIGRlIGRhdG9zIGVuIGxhIHZhcmlhYmxlICJkZiIsIG5vcyBxdWVkYW1vcyBzb2xvIGNvbiBsb3MgZGF0b3MgY29tcGxldG9zLCB0aXJhbW9zIGxvcyBuaXZlbGVzIHF1ZSBubyBzZSB1c2FuLCByZW5vbWJyYW1vcyBlbCBpZGVudGltaWNhZG9yIGRlIGxhIGVuY3Vlc3RhIGRlIFYzIGEgSUQgeSBleHBsb3JhbW9zIGxhcyBwcmltZXJhcyBsw61uZWFzOgoKYGBge3J9CmlmICghcmVxdWlyZSgicGFjbWFuIikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpCnBhY21hbjo6cF9sb2FkKCJNQVNTIiwidGlkeXZlcnNlIiwiaGF2ZW4iLCJzdHJpbmdyIiwgIkZhY3RvTWluZVIiLCJmYWN0b2V4dHJhIiwiY29ycnBsb3QiLCJncGxvdHMiLCJnZ3JlcGVsIikKYGBgCgoKYGBge3J9CmRmIDwtIHJlYWRfc2F2KCJXVjZfRGF0YV9TcGFpbl8yMDExX1Nwc3NfdjIwMTgwOTEyLnNhdiIpICU+JQojICBzZWxlY3QgKFYzLFYzOSwgVjQ2LCBWMjE4X0VTTUEsIFYyMTdfRVNNQSwgVjIxOV9FU01BLCBWMjIwX0VTTUEsIFYyMjFfRVNNQSwgVjIyMl9FU01BLCBWMjIzX0VTTUEpICU+JQojICByZW5hbWUoSUQ9VjMpICU+JQogIGFzX2ZhY3RvcigpICU+JQogIGZpbHRlcihjb21wbGV0ZS5jYXNlcyguKSkgJT4lIAogIGRyb3BsZXZlbHMoKQoKZGYgJT4lIGhlYWQoKQpgYGAKCgpFcyBwb3NpYmxlIHF1ZSBub3MgaW50ZXJlc2Ugc2ltcGxpZmljYXIgaWRlb2xvZ2lhIGVuIHBvY2FzIGNhdGVnb3JpYXM6CgpgYGB7cn0KCmRmMiA9IGRmICU+JSBtdXRhdGUoSWRlb2w9YXMubnVtZXJpYyhJZGVvbCkpICU+JQogIG11dGF0ZShJZGVvbD1pZmVsc2UoSWRlb2w8PTUsICAgICAgICAgICAgICAgICAxLAogICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKElkZW9sPD0xMCwgICAgICAgICAgMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMykpKSAlPiUKICBtdXRhdGUoQ2F0TGFiPWlmZWxzZShDYXRMYWIgJWluJSBjKCJDQVNBIiwiQVVUT04iLCJERVNFTVAiLCJPVFJPUyIsIlRQYXJjIiwiRVNUVUQiKSwiUHJlY0xhYiIsYXMuY2hhcmFjdGVyKENhdExhYikpKSU+JQogIG11dGF0ZShQcmlvck5hYz1pZmVsc2UoUHJpb3JOYWMgJWluJSBjKCJQcmlvcklOREVGIiwJIlByaW9yTk8iKSwiUHJpb3JOTyIsYXMuY2hhcmFjdGVyKFByaW9yTmFjKSkpICU+JQogIG11dGF0ZShDb25mRXh0cj1pZmVsc2UoQ29uZkV4dHIgJWluJSBjKCJBTEdPIiwJIkNPTVBMRVRBTUVOVEUiKSwiQ29uZlNJIiwgIkNvbmZObyIjYXMuY2hhcmFjdGVyKENvbmZFeHRyKQogICAgICAgICAgICAgICAgICAgICAgICAgICApKSAlPiUKICAgIG11dGF0ZShDbGFzZVNvYz1pZmVsc2UoQ2xhc2VTb2MgJWluJSBjKCJBTFRBIiwJIk1FRElBIEFMVEEiLCJNRURJQSBCQUpBIiksIkNsTWVkaWEiLCJDbEJhamEiKSkgJT4lCiAgCnNlbGVjdCgtT3JnTmFjLC1UVikKCmBgYAoKCgoKCkVuIGZvcm1hdG8gInRpZHkiOgoKYGBge3J9CmRmMyA8LSBkZjIgJT4lIGdhdGhlcigiVmFyaWFibGUiLCAiVmFsb3IiLC1JRCkKZGYzICU+JSBhcnJhbmdlKElEKSAlPiUgaGVhZCgpCmBgYAoKCnZhbW9zIGEgZXhwbG9yYXIgdmlzdWFsbWVudGUgcXXDqSBwaW50YW4gdGllbmVuIGxhcyByZXNwdWVzdGFzLiBFbXBlemFtb3MgcG9yIHVuIHRhYmxhIHNpbXBsZSBkZSByZWN1ZW50b3M6CgpgYGB7cn0KdGFibGE9eHRhYnMgKH4gVmFyaWFibGUrVmFsb3IsZGF0YT1kZjMpCnRhYmxhICU+JSBrbml0cjo6a2FibGUoKQpgYGAKCgoKCgoKIyBFc2NhbGFtaWVudG8gbcO6bHRpcGxlCkVsIG1vZGVsbyBxdWUgc2UgaGEgaGVjaG8gZW4gU1BTUyB0ZW7DrWEgZXN0YSBwaW50YSBzaSBsbyBoZSB2aXN0byBiaWVuOgoKYGBge3J9CmZpdCA8LSBNQ0EoZGYyICU+JSBzZWxlY3QoLUlEKSU+JW11dGF0ZV9hbGwoYXMuZmFjdG9yKSwgbmNwID0gMiwgZ3JhcGggPSBUUlVFKQpgYGAKCkxvcyBhdXRvdmFsb3JlcyBzb246CmBgYHtyfQpnZXRfZWlnZW52YWx1ZShmaXQpCmBgYAoKYGBge3J9CmZ2aXpfc2NyZWVwbG90KGZpdCwgYWRkbGFiZWxzID0gVFJVRSkKYGBgCgpSZWFsbWVudGUgc2kgbm9zIHF1ZWRhbW9zIHNvbG8gY29uIDIgZGltZW5zaW9uZXMgbm8gdmFtb3MgYSB2ZXIgbXVjaG8sIHB1ZXMgcXVlZGEgbXVjaGEgaW5mb3JtYWNpw7NuIGZ1ZXJhIGRlIGVsbGFzLCBwZXJvIHZhbW9zIGEgZWNoYXIgdW4gdmlzdGF6byBlIHRvZG9zIG1vZG9zOgoKCgpgYGB7cn0KZnZpel9tY2FfdmFyKGZpdCwgY2hvaWNlID0gIm1jYS5jb3IiLCBjb2wudmFyPSJibGFjayIsCiAgICAgICAgICAgIHJlcGVsID0gVFJVRSwgCiAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkpCmBgYAoKCgoKCgoKYGBge3J9CmZ2aXpfbWNhX3ZhcihmaXQsIAogICAgICAgICAgICAgcmVwZWwgPSBUUlVFLCBjb2wudmFyPSJibGFjayIsCiAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpKQpgYGAKCllhIHZpbW9zIHF1ZSAyIGRpbWVuc2lvbmVzIHNlIHF1ZWRhYmFuIGNvcnRhcyBwYXJhIHJlcHJlc2VudGFyIGxhcyB2YXJpYWJsZXMuIFZhbW9zIGEgcG9uZXIgZGUgY29sb3Igcm9qbyBsYXMgcXVlIGVzdMOpbiBtZWpvciByZXByZXNlbnRhZGFzIGVuIHVuIHBsYW5vLCB5IGRlIG90cm9zIGNvbG9yZXMgY29uZm9ybWUgdmF5YW1vcyBwZXJkaWVuZG8gZXN0YSBwcm9waWVkYWQKCmBgYHtyICBkcGk9NjAwLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OX0KZnZpel9tY2FfdmFyKGZpdCwgY29sLnZhciA9ICJjb3MyIiwKICAgICAgICAgICAgIGdyYWRpZW50LmNvbHMgPSBjKCIjMDBBRkJCIiwgIiNFN0I4MDAiLCAiI0ZDNEUwNyIpLCAKICAgICAgICAgICAgIHJlcGVsID0gVFJVRSwgIyBBdm9pZCB0ZXh0IG92ZXJsYXBwaW5nCiAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpKQpgYGAKCgoKClVuYSBmb3JtYSBkZSB2ZXIgcXVpZW4gZXN0w6EgbWVqb3IgbyBwZW9yIHJlcHJlc2VudGFkbyB0YW1iacOpbiBzZXLDrWEgYXNvY2lhbmRvLCBlbiBsdWdhciBkZSBjb2xvciByb2pvLCB1biBncmFkbyBkZSB0cmFuc3BhcmVuY2lhIGFsIHPDrW1ib2xvIHF1ZSBsbyByZXByZXNlbnRhOgoKYGBge3J9CmZ2aXpfbWNhX3ZhcihmaXQsIGFscGhhLnZhcj0iY29zMiIsIGNvbC52YXI9ImJsYWNrIiwKICAgICAgICAgICAgIHJlcGVsID0gVFJVRSwKICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkpIApgYGAKCk9yZGVuZW1vcyBkZSBtZWpvciBhIHBlb3IgbG8gYmllbiBxdWUgc2UgcmVwcmVzZW50YW4gY2FkYSB2YWxvciBkZSBsYXMgdmFyaWFibGVzIGVuIHVuIHBsYW5vOgoKYGBge3J9CmZ2aXpfY29zMihmaXQsIGNob2ljZSA9ICJ2YXIiLCBheGVzID0gMToyKQpgYGAKCkFwYXJlbnRlbWVudGUsIGVuIGxhIGNsYXNpZmljYWNpw7NuIHNvYnJlIGxhIGN1ZXN0acOzbiBkZSBsb3MgaW5taWdyYW50ZXMgeSBUViwgbGEgY3Vlc3Rpw7NuIGRlIGTDs25kZSBzZSBzaXTDumEgbGFzIHJlc3B1ZXN0YXMgZGUgbGEgZ2VudGUgbm8gc2UgcHVlZGUgYXNvY2lhciBtdXkgYmllbiBjb24gbGFzIGRpbWVuc2lvbmVzIGRlbCBwbGFubyBkb25kZSBsbyBoZW1vcyByZXByZXNlbnRhZG8uIAoKCgoKCgoKCgojIEVzdHVkaWFuZG8gZWwgZ3JhZG8gZGUgaW5kZXBlbmRlbmNpYSBlbnRyZSB2YXJpYWJsZXMKSGFzdGEgYXF1w60gZXJhIE1EUyBlc3TDoW5kYXIuIFZhbW9zIGEgZXhwbG9yYXIgdW4gcG9jbyBtw6FzIHBvciBudWVzcmEgY3VlbnRhLiBFbXBlemFtb3MgcG9yIGVsIGdyYWRvIGRlIGluZGVwZW5kZW5jaWEgcXVlIGhheSBlbnRyZSBjdWFscXVpZXIgcGFyIGRlIHZhcmlhYmxlcyBxdWUgaGF5YSwgcGFyYSBwb2RlciBlc3RhYmxlY2VyIHVuIGdyYWRvIGRlIGRpc2ltaWxhcmlkYWQuIAoKCgpgYGB7cn0KVmFyaWFibGVzPWRmMyRWYXJpYWJsZSAlPiUgdW5pcXVlKCkgJT4lIHNldGRpZmYoIklEIikKdGFibGFQYXJlamFzPWV4cGFuZC5ncmlkKFYxPVZhcmlhYmxlcywgVjI9VmFyaWFibGVzKQp0YWJsYVBhcmVqYXMgJT4lIGhlYWQoMjApICU+JSBrbml0cjo6a2FibGUoKQpgYGAKCgpWYW1vcyBhIGHDsWFkaXIgdW5hIG1lZGlkYSBkZSBkaXN0YW5jaWEgcGFyYSBjYWRhIGNvbWJpbmFjacOzbi4gRW4gcHJpbmNpcGlvIHRvbW8gZXN0YSBxdWUgaGFjZSBxdWUgZG9zIHZhcmlhYmxlcyBzZWFuIG1lbm9zIGRpc3RhbnRlcyBjdWFudG8gbWVub3MgaW5kZXBlbmRpZW50ZSBzZWFuLiAKCgpgYGB7cn0KI0VzdG8gZXMgc29sbyB1biB0cnVjbyBwYXJhIGhhY2VyIHBydWViYXMgY2hpMiBlbnRyZSBsYSBtaXNtYSB2YXJpYWJsZSwgY29zYSBxdWUgbm8gdGllbmUgaW50ZXLDqXMuCmRmMmI9ZGYyCm5hbWVzKGRmMmIpPXN0cl9jKG5hbWVzKGRmMiksImIiKQpkZjJUUlVDTz1jYmluZChkZjIsZGYyYikKYGBgCgoKVm95IGEgdmVyIHF1w6kgcGludGEgdGllbmVuIGxvcyAicCIgZGUgaW5kZXBlbmRlbmNpYSBlbnRyZSBwYXJlamFzIGRlIHZhcmlhYmxlczoKCmBgYHtyfQp0YWJsYVBhcmVqYXMkcD1tYXAyX2RibCh0YWJsYVBhcmVqYXNbLDFdLHRhYmxhUGFyZWphc1ssMl0sIH4gKHh0YWJzKGZvcm11bGEoc3RyX2MoIn4iLC54LCIrIiwueSwiYiIpKSxkYXRhPWRmMlRSVUNPKSAlPiUgY2hpc3EudGVzdCgpICU+JSAuW1sicC52YWx1ZSJdXSAgKSkKYGBgCgpgYGB7cn0KbWF0cml6UCA8LSBtYXRyaXgocm91bmQodGFibGFQYXJlamFzJHAsNSksIG5yb3cgPSBsZW5ndGgoVmFyaWFibGVzKSwgZGltbmFtZXM9bGlzdChWYXJpYWJsZXMsVmFyaWFibGVzKSkKbWF0cml6UApgYGAKCkNvbiBkaWJ1aml0b3MgbG8gdmVvIG1lam9yOgpgYGB7cn0KY29ycnBsb3QobWF0cml6UCwgIGlzLmNvcnIgPSBGQUxTRSkgCmBgYAoKCgoKVmFtb3MgYSBoYWNlciBhbGdvIHNpbWlsYXIsIGNyZWFuZG8gdW5hIGRpc3RhbmNpYXMgZW50cmUgdmFyaWFibGVzIHNlZ8O6biB1bmEgbWVkaWRhIGRlbCBncmFkbyBkZSBpbmRlcGVuZGVuY2lhCgpgYGB7cn0KdGFibGFQYXJlamFzJGRpc3RhbmNpYT1tYXAyX2RibCh0YWJsYVBhcmVqYXNbLDFdLHRhYmxhUGFyZWphc1ssMl0sIH4gKHh0YWJzKGZvcm11bGEoc3RyX2MoIn4iLC54LCIrIiwueSwiYiIpKSxkYXRhPWRmMlRSVUNPKSAlPiUgY2hpc3EudGVzdCgpICU+JSAoZnVuY3Rpb24oY2hpMikgKDEvKDErYWJzKGNoaTJbWyJzdGF0aXN0aWMiXV0pKS9jaGkyW1sicGFyYW1ldGVyIl1dKSkoLikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpCmBgYAoKQ29uIGVsbGEgY29udHJ1eW8gbGEgbWF0cml6IGRlIGRpc3RhbmNpYXMsIGFzZWd1csOhbmRvbWUgZGUgcXVlIGxhIGRpYWdvbmFsIGxhIHBvbmdvIGVuIGNlcm8KCmBgYHtyfQptYXRyaXpEaXN0YW5jaWFzIDwtIG1hdHJpeChyb3VuZCh0YWJsYVBhcmVqYXMkZGlzdGFuY2lhLDQpLCBucm93ID0gbGVuZ3RoKFZhcmlhYmxlcyksIGRpbW5hbWVzPWxpc3QoVmFyaWFibGVzLFZhcmlhYmxlcykpCmZvciAoaSBpbiAxOm5yb3cobWF0cml6RGlzdGFuY2lhcykpbWF0cml6RGlzdGFuY2lhc1tpLGldPTBMCm1hdHJpekRpc3RhbmNpYXMKYGBgCgoKCgoKYGBge3J9CnRhYmxhUGFyZWphcyRkaXN0YW5jaWE9bWFwMl9kYmwodGFibGFQYXJlamFzWywxXSx0YWJsYVBhcmVqYXNbLDJdLCB+ICh4dGFicyhmb3JtdWxhKHN0cl9jKCJ+IiwueCwiKyIsLnksImIiKSksZGF0YT1kZjJUUlVDTykgJT4lIGNoaXNxLnRlc3QoKSAlPiUgKGZ1bmN0aW9uKGNoaTIpY2hpMltbInN0YXRpc3RpYyJdXS9jaGkyW1sicGFyYW1ldGVyIl1dKSguKSAlPiUgIChmdW5jdGlvbih4KTEvKDErc3FydChhYnMoeCkpKSApKC4pKSkKdGFibGFQYXJlamFzICU+JSBoZWFkKDIwKQpgYGAKCgpDb24gZWxsYSBjb250cnV5byBsYSBtYXRyaXogZGUgZGlzdGFuY2lhcywgYXNlZ3Vyw6FuZG9tZSBkZSBxdWUgbGEgZGlhZ29uYWwgbGEgcG9uZ28gZW4gY2VybwoKYGBge3J9CgptYXRyaXpEaXN0YW5jaWFzIDwtIG1hdHJpeChyb3VuZCh0YWJsYVBhcmVqYXMkZGlzdGFuY2lhLDQpLCBucm93ID0gbGVuZ3RoKFZhcmlhYmxlcyksIGRpbW5hbWVzPWxpc3QoVmFyaWFibGVzLFZhcmlhYmxlcykpCmZvciAoaSBpbiAxOm5yb3cobWF0cml6RGlzdGFuY2lhcykpbWF0cml6RGlzdGFuY2lhc1tpLGldPTBMCm1hdHJpekRpc3RhbmNpYXMKYGBgCgoKClZhbW9zIGEgdmlzdWFsaXphciBlc2FzIGRpc3RhbmNpYXMKYGBge3J9CmNvcnJwbG90KG1hdHJpekRpc3RhbmNpYXMsICBpcy5jb3JyID0gRkFMU0UpIApgYGAKClZhbW9zIGEgaW50ZW50YXIgcmVwcmVzZW50YXIgZXNhcyBkaXN0YW5jaWFzIGNvbW8gc2kgZGUgZGlzdGFuY2lhcyBlbnRyZSBjaXVkYWRlcyBzZSB0cmF0YXNlIHVzYW5kbyBlc2NhbGFtaWVudG8gZGltZW5zaW9uYWw6CgpQb2RlbW9zIGhhY2VybG8gCgpgYGB7cn0KZml0IDwtIGNtZHNjYWxlKG1hdHJpekRpc3RhbmNpYXMsZWlnPVRSVUUsaz0yKSAKZml0CmBgYAoKYGBge3J9CkNvb3JkZW5hZGFzPWZpdCRwb2ludHMlPiUgYXNfdGliYmxlKCkgJT4lIG11dGF0ZShWYXJpYWJsZT1kaW1uYW1lcyhmaXQkcG9pbnRzKVtbMV1dKQoKCmdncGxvdChDb29yZGVuYWRhcyxhZXMoeD1WMSx5PVYyLGxhYmVsPVZhcmlhYmxlKSkgKyBnZW9tX3BvaW50KCkgK2dlb21fdGV4dF9yZXBlbChhbHBoYT0wLjUpKwogIGNvb3JkX2NhcnRlc2lhbih4bGltPWMoLTEsMSkgLHlsaW09YygtMC41LDAuNSkpK3RoZW1lX21pbmltYWwoKQpgYGAKCk5vIG1lIGhhY2UgZ3JhY2lhIHZlciBhdXRvdmFsb3JlcyBuZWdhdGl2b3MsIGFzw60gcXVlIGNhbWJpbyBhIG90cm8gbcOpdG9kbzoKCgpgYGB7cn0KZml0IDwtIGlzb01EUyhtYXRyaXpEaXN0YW5jaWFzLCBrPTIpIApmaXQKYGBgCgpgYGB7cn0KQ29vcmRlbmFkYXM9Zml0JHBvaW50cyU+JSBhc190aWJibGUoKSAlPiUgbXV0YXRlKFZhcmlhYmxlPWRpbW5hbWVzKGZpdCRwb2ludHMpW1sxXV0pCgoKZ2dwbG90KENvb3JkZW5hZGFzLGFlcyh4PVYxLHk9VjIsbGFiZWw9VmFyaWFibGUpKSArIGdlb21fcG9pbnQoKSArZ2VvbV90ZXh0X3JlcGVsKGFscGhhPTAuNSkrCiAgY29vcmRfY2FydGVzaWFuKHhsaW09YygtMS4yNSwxLjI1KSAseWxpbT1jKC0xLDEpKSt0aGVtZV9taW5pbWFsKCkKYGBgCgoKCkJ1ZW5vLCBjb24gZXNvIG5vcyBoYWNlbW9zIHVuYSBpZGVhIGRlIHF1w6kgdmFyaWFibGVzIHNvbiBsYXMgbcOhcyBpbmRlcGVuZGllbnRlcyBlbnRyZSBzw60uIFBvciBlamVtcGxvOgoKKiBJbnRlcm5ldCwgZW1haWwgeSBtb3ZpbCBzb24gcG9jbyBpbmRlcGVuZGllbnRlcyBlbnRyZSBzw60KKiByYWRpbyB5IHBlcmnDs2RpY28gc29uIHBvY28gaW5kZXBlbmRpZW50ZXMgZW50cmUgc8OtCiogVFYgZXMgaW5kZXBlbmRpZW50ZSBkZSB0b2RvLCBhc8OtIGNvbW8gVmVjaW5vSW5taWdyYW50ZSwgYXVucXVlIGVzdGEgw7psdGltYSBzw60gcXVlIG5vIGVzIGRlbCB0b2RvIGluZGVwZW5kaWVudGUgZGUgSW5taWdyYW50ZXMuCgoKCgoKCgo=