Utilizaremos un dataset que contiene las notas de los examens parciales del curso de matematicas impartido desde 2011 hasta 2016.

Intentaremos utilizar las tecnicas de clustering para identificar si existe alguna relación interesante en el dataset, posteriormente constuiremos dos modelos paara tratar de predecir la nota final de un alumno a partir de sus notas de examenes parcales, el año en el que recibío el curso y la variable de clustering que encontremos.

Cargamos librerias y dataset

library(readr)
library(caret)
Loading required package: lattice
Loading required package: ggplot2
library(tidyverse)
Loading tidyverse: tibble
Loading tidyverse: tidyr
Loading tidyverse: purrr
Loading tidyverse: dplyr
Conflicts with tidy packages ------------------------------------------
filter(): dplyr, stats
lag():    dplyr, stats
lift():   purrr, caret
dataset<-read.csv("Research2filePreng.csv")

1) Limpiez y acondicionamiento del dataset:

dataset.new<-dataset%>%
  #Eliminamos observaciones faltantes 
  filter(GESID01 != "NA") %>% 
  
  #Eliminamos columnas con    informacion de lis alumnos.
  select(-Estudiante09, -Carnet08, -GESID01) 
#Eliminamos observaciones restantes que contentan NA
dataset.new<-na.omit(dataset.new)
dataset.new
dim(dataset.new)
[1] 1739   11
colnames(dataset.new)
 [1] "Curso02"          "Seccion03"        "Carrera04"       
 [4] "Facultad05"       "Fecha06"          "Ciclo07"         
 [7] "Genero10"         "PrimerParcial11"  "SegundoParcial13"
[10] "ExamenFinal15"    "NotaFinal17"     

2) Creación de Datasets: Training y Test

#Mexclamos los datos para formar dos data sets: entrenamiento y prueba.
index<-1:nrow(dataset.new)
shuff_index<-sample(index)
dataset.new<-dataset.new[shuff_index,] #Data Frame desordenado.
#Generamos datos para entrenamiento y pruebas
#subdataset de entrenamiento.
train_dataset<-dataset.new[1:(0.7*nrow(dataset.new)),] 
#subdataset de prueba.
test_dataset<-dataset.new[(0.7*nrow(dataset.new)+1): nrow(dataset.new),]
dim(train_dataset)
[1] 1217   11

3) Custering

Ahora buscaremos la cantidad optima de clusters para clasificar a partir de las notas obtenidas por los alumnos:

wss <- 0
# Iteramos de 1 a 15 clusters
for (i in 1:15) {
  km.out <- kmeans(train_dataset[8:11], centers = i
                   , nstart=20, iter.max = 50)
  # Save total within sum of squares to wss variable
  wss[i] <- km.out$tot.withinss
}
#Graficamos
plot(1:15, wss, type = "b", 
     xlab = "Numero de Clusters", 
     ylab = "Suma de cuadrados entre grupos",main = "Scree Plot para el dataset de cancer de mamas", col="blue", lwd=2)
points(3,wss[3],col = "red",pch = 16)

#Creamos Dos Clusters ya que es el optimo.
km.out <- kmeans(train_dataset[8:11], centers = 3, nstart=20, iter.max = 50)

Plots de Notas de Exámenes vrs Nota Final

#Plot primer parcial y Nota Final
plot(train_dataset$PrimerParcial11, train_dataset$NotaFinal17, col=km.out$cluster, pch=19, xlab="Primer Parcial", ylab="Nota Final")

#Plot Segundo parial y Nota Final
plot(train_dataset$SegundoParcial13, train_dataset$NotaFinal17, col=km.out$cluster, pch=19, xlab="Segundo Parcial", ylab="Nota Final")

#Plot Nota Examen Final y Nota Final
plot(train_dataset$ExamenFinal15, train_dataset$NotaFinal17, col=km.out$cluster, pch=19, xlab="Examen Final", ylab="Nota Final")

Plots de relación entre Parciales y Examen Final

#Plot Nota Primer Parcial y Segundo Parcial
plot(train_dataset$PrimerParcial11, train_dataset$SegundoParcial13, col=km.out$cluster, pch=19, xlab="Primer Parcial", ylab="Segundo Parcial")

#Plot Nota Primer Parcial y Examen Final
plot(train_dataset$PrimerParcial11, train_dataset$ExamenFinal15, col=km.out$cluster, pch=19, xlab="Primer Parcial", ylab="Examen Final")

#Plot Nota Segundo Parcial y Examen Final
plot(train_dataset$SegundoParcial13, train_dataset$ExamenFinal15, col=km.out$cluster, pch=19, xlab="Segundo Parcial", ylab="Examen Final")

A partir de la información anterior podemos deducir que existe una estrecha realación con los alumnos que les va bien en los examens y terminan bien durante el curso.

Es decir que si a un alumno le va bien en el primer examen parcial, es muy probabable que le vaa bien en un examen su examen final y como consecuencia, bien durante todo el curso.

A partir del análisis de clusters hay un par de observaciones interesantes:

  • Aparentemente existe una relación positiva entre las notas cada examen y la nota final del curso.

  • El algortmo de clustering encontro tres clusters distintos que aparentemente están relacionados con el desempeño del alumno, podríamos decir que los clusters se refieren a la categoría del estudiante.

  • A partir de los clusters formados no existe una relación directa con alguna de las variables que conforman el dataset. Sin embargo podemos definir 3 categoría dentro de los estudiantes que inician en primer año.

  • Existen algunos puntos atipicos, que rerpesentan alumnos que iniciaron mal durante el curso, pero terminaron bien.

  • Existen varios puntos que se encuentran mezclados entre los clusters que representan una característica de cada uno de los estudiantes.

4) Modelo de Regresión.

A continuación mostraremos un modelo para predecir la nota final de un alumno dependiendo de 5 variables:

  • Nota del Primer Parcial.
  • Nota del Segundo Parcial.
  • Nota del Examen Final.
  • Carrera.
  • Genero.

Modelo de Regresión Lineal

#Modelo de regresión lineal
lm.fit1<-lm(NotaFinal17 ~ PrimerParcial11 + 
              SegundoParcial13 + ExamenFinal15 
            + Carrera04 + Genero10,
            data=train_dataset)
summary(lm.fit1)

Call:
lm(formula = NotaFinal17 ~ PrimerParcial11 + SegundoParcial13 + 
    ExamenFinal15 + Carrera04 + Genero10, data = train_dataset)

Residuals:
    Min      1Q  Median      3Q     Max 
-27.883  -5.035  -0.114   4.495  48.918 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)      10.340995   1.091440   9.475  < 2e-16 ***
PrimerParcial11   0.179333   0.009484  18.909  < 2e-16 ***
SegundoParcial13  0.303629   0.011250  26.988  < 2e-16 ***
ExamenFinal15     0.420446   0.009728  43.219  < 2e-16 ***
Carrera04IE      -2.769050   1.122315  -2.467  0.01375 *  
Carrera04II      -1.623830   0.764089  -2.125  0.03377 *  
Carrera04IME     -2.707497   0.885593  -3.057  0.00228 ** 
Carrera04ISE     -4.176924   1.297043  -3.220  0.00131 ** 
Carrera04ITR     -2.104896   0.959055  -2.195  0.02837 *  
Carrera04IU      -1.422686   0.734395  -1.937  0.05295 .  
Genero10          1.527838   0.637917   2.395  0.01677 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 7.663 on 1206 degrees of freedom
Multiple R-squared:  0.9254,    Adjusted R-squared:  0.9247 
F-statistic:  1495 on 10 and 1206 DF,  p-value: < 2.2e-16

A partir de la información anterior todas las variables seleccionadas son estadisticamente significativas y el modelo como tal es significativo.

Ahora construirmemos una tabla que nos permita hacer comparaciones de los valores Observados y Predichos.

#Predicción del prueba con modelo lineal
lm.pred1<-predict(lm.fit1, newdata=test_dataset)
#Tabla de salida para comparación.
Table1<-data.frame(test_dataset$NotaFinal17, round(lm.pred1, 0))
colnames(Table1)<-c("Observado", "Prediccion")
#Eliminamos el Nombre de la columan
row.names(Table1)<-NULL 
#Mostramos Tabla
Table1
save(lm.fit1, "nota_model.rda")
Error in save(lm.fit1, "nota_model.rda") : 
  object ‘nota_model.rda’ not found

#Prueba de Predicción para Shiny app
datatest<-data.frame(
      PrimerParcial11=57,
      SegundoParcial13=87,
      ExamenFinal15=60,
      Carrera04="IME",
      Genero10=2
    )
salida<-predict(lm.fit1, newdata=datatest)
salida
LS0tDQp0aXRsZTogIlByb3llY3RvIEZpbmFsIC0gQXByZW5kaXphamUgRXN0YWTtc3RpY28iDQphdXRob3I6ICJQcmVuZyBCaWJhIC0gMDkwMDAxODUiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdA0KLS0tDQojIyBVdGlsaXphcmVtb3MgdW4gZGF0YXNldCBxdWUgY29udGllbmUgbGFzIG5vdGFzIGRlIGxvcyBleGFtZW5zIHBhcmNpYWxlcyBkZWwgY3Vyc28gZGUgbWF0ZW1hdGljYXMgaW1wYXJ0aWRvIGRlc2RlIDIwMTEgaGFzdGEgMjAxNi4NCg0KIyMgSW50ZW50YXJlbW9zIHV0aWxpemFyIGxhcyB0ZWNuaWNhcyBkZSBjbHVzdGVyaW5nIHBhcmEgaWRlbnRpZmljYXIgc2kgZXhpc3RlIGFsZ3VuYSByZWxhY2nzbiBpbnRlcmVzYW50ZSBlbiBlbCBkYXRhc2V0LCBwb3N0ZXJpb3JtZW50ZSBjb25zdHVpcmVtb3MgZG9zIG1vZGVsb3MgcGFhcmEgdHJhdGFyIGRlIHByZWRlY2lyIGxhIG5vdGEgZmluYWwgZGUgdW4gYWx1bW5vIGEgcGFydGlyIGRlIHN1cyBub3RhcyBkZSBleGFtZW5lcyBwYXJjYWxlcywgZWwgYfFvIGVuIGVsIHF1ZSByZWNpYu1vIGVsIGN1cnNvIHkgbGEgdmFyaWFibGUgZGUgY2x1c3RlcmluZyBxdWUgZW5jb250cmVtb3MuDQoNCg0KIyMjQ2FyZ2Ftb3MgbGlicmVyaWFzIHkgZGF0YXNldA0KYGBge3J9DQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KZGF0YXNldDwtcmVhZC5jc3YoIlJlc2VhcmNoMmZpbGVQcmVuZy5jc3YiKQ0KYGBgDQoNCiMjIyAxKSBMaW1waWV6IHkgYWNvbmRpY2lvbmFtaWVudG8gZGVsIGRhdGFzZXQ6DQpgYGB7cn0NCmRhdGFzZXQubmV3PC1kYXRhc2V0JT4lDQogICNFbGltaW5hbW9zIG9ic2VydmFjaW9uZXMgZmFsdGFudGVzIA0KICBmaWx0ZXIoR0VTSUQwMSAhPSAiTkEiKSAlPiUgDQogIA0KICAjRWxpbWluYW1vcyBjb2x1bW5hcyBjb24gICAgaW5mb3JtYWNpb24gZGUgbGlzIGFsdW1ub3MuDQogIHNlbGVjdCgtRXN0dWRpYW50ZTA5LCAtQ2FybmV0MDgsIC1HRVNJRDAxKSANCg0KI0VsaW1pbmFtb3Mgb2JzZXJ2YWNpb25lcyByZXN0YW50ZXMgcXVlIGNvbnRlbnRhbiBOQQ0KZGF0YXNldC5uZXc8LW5hLm9taXQoZGF0YXNldC5uZXcpDQpkYXRhc2V0Lm5ldw0KYGBgDQpgYGB7cn0NCmRpbShkYXRhc2V0Lm5ldykNCmBgYA0KYGBge3J9DQpjb2xuYW1lcyhkYXRhc2V0Lm5ldykNCmBgYA0KDQojIDIpIENyZWFjafNuIGRlIERhdGFzZXRzOiBUcmFpbmluZyB5IFRlc3QNCmBgYHtyLCBlY2hvPVRSVUV9DQojTWV4Y2xhbW9zIGxvcyBkYXRvcyBwYXJhIGZvcm1hciBkb3MgZGF0YSBzZXRzOiBlbnRyZW5hbWllbnRvIHkgcHJ1ZWJhLg0KaW5kZXg8LTE6bnJvdyhkYXRhc2V0Lm5ldykNCnNodWZmX2luZGV4PC1zYW1wbGUoaW5kZXgpDQpkYXRhc2V0Lm5ldzwtZGF0YXNldC5uZXdbc2h1ZmZfaW5kZXgsXSAjRGF0YSBGcmFtZSBkZXNvcmRlbmFkby4NCmBgYA0KDQoNCmBgYHtyLCBlY2hvPVRSVUV9DQojR2VuZXJhbW9zIGRhdG9zIHBhcmEgZW50cmVuYW1pZW50byB5IHBydWViYXMNCg0KI3N1YmRhdGFzZXQgZGUgZW50cmVuYW1pZW50by4NCnRyYWluX2RhdGFzZXQ8LWRhdGFzZXQubmV3WzE6KDAuNypucm93KGRhdGFzZXQubmV3KSksXSANCg0KI3N1YmRhdGFzZXQgZGUgcHJ1ZWJhLg0KdGVzdF9kYXRhc2V0PC1kYXRhc2V0Lm5ld1soMC43Km5yb3coZGF0YXNldC5uZXcpKzEpOiBucm93KGRhdGFzZXQubmV3KSxdDQoNCmRpbSh0cmFpbl9kYXRhc2V0KQ0KYGBgDQoNCg0KIyAzKSBDdXN0ZXJpbmcNCg0KIyMjIEFob3JhIGJ1c2NhcmVtb3MgbGEgY2FudGlkYWQgb3B0aW1hIGRlIGNsdXN0ZXJzIHBhcmEgY2xhc2lmaWNhciBhIHBhcnRpciBkZSBsYXMgbm90YXMgb2J0ZW5pZGFzIHBvciBsb3MgYWx1bW5vczoNCg0KYGBge3IsIGVjaG89VFJVRX0NCndzcyA8LSAwDQojIEl0ZXJhbW9zIGRlIDEgYSAxNSBjbHVzdGVycw0KZm9yIChpIGluIDE6MTUpIHsNCiAga20ub3V0IDwtIGttZWFucyh0cmFpbl9kYXRhc2V0Wzg6MTFdLCBjZW50ZXJzID0gaQ0KICAgICAgICAgICAgICAgICAgICwgbnN0YXJ0PTIwLCBpdGVyLm1heCA9IDUwKQ0KICAjIFNhdmUgdG90YWwgd2l0aGluIHN1bSBvZiBzcXVhcmVzIHRvIHdzcyB2YXJpYWJsZQ0KICB3c3NbaV0gPC0ga20ub3V0JHRvdC53aXRoaW5zcw0KfQ0KDQojR3JhZmljYW1vcw0KcGxvdCgxOjE1LCB3c3MsIHR5cGUgPSAiYiIsIA0KICAgICB4bGFiID0gIk51bWVybyBkZSBDbHVzdGVycyIsIA0KICAgICB5bGFiID0gIlN1bWEgZGUgY3VhZHJhZG9zIGVudHJlIGdydXBvcyIsbWFpbiA9ICJTY3JlZSBQbG90IHBhcmEgZWwgZGF0YXNldCBkZSBjYW5jZXIgZGUgbWFtYXMiLCBjb2w9ImJsdWUiLCBsd2Q9MikNCnBvaW50cygzLHdzc1szXSxjb2wgPSAicmVkIixwY2ggPSAxNikNCmBgYA0KDQpgYGB7ciwgZWNobz1UUlVFfQ0KI0NyZWFtb3MgRG9zIENsdXN0ZXJzIHlhIHF1ZSBlcyBlbCBvcHRpbW8uDQprbS5vdXQgPC0ga21lYW5zKHRyYWluX2RhdGFzZXRbODoxMV0sIGNlbnRlcnMgPSAzLCBuc3RhcnQ9MjAsIGl0ZXIubWF4ID0gNTApDQpgYGANCg0KDQojIyMgUGxvdHMgZGUgTm90YXMgZGUgRXjhbWVuZXMgdnJzIE5vdGEgRmluYWwgDQpgYGB7ciwgZWNobz1UUlVFfQ0KI1Bsb3QgcHJpbWVyIHBhcmNpYWwgeSBOb3RhIEZpbmFsDQpwbG90KHRyYWluX2RhdGFzZXQkUHJpbWVyUGFyY2lhbDExLCB0cmFpbl9kYXRhc2V0JE5vdGFGaW5hbDE3LCBjb2w9a20ub3V0JGNsdXN0ZXIsIHBjaD0xOSwgeGxhYj0iUHJpbWVyIFBhcmNpYWwiLCB5bGFiPSJOb3RhIEZpbmFsIikNCg0KYGBgDQoNCmBgYHtyLCBlY2hvPVRSVUV9DQojUGxvdCBTZWd1bmRvIHBhcmlhbCB5IE5vdGEgRmluYWwNCnBsb3QodHJhaW5fZGF0YXNldCRTZWd1bmRvUGFyY2lhbDEzLCB0cmFpbl9kYXRhc2V0JE5vdGFGaW5hbDE3LCBjb2w9a20ub3V0JGNsdXN0ZXIsIHBjaD0xOSwgeGxhYj0iU2VndW5kbyBQYXJjaWFsIiwgeWxhYj0iTm90YSBGaW5hbCIpDQoNCmBgYA0KDQpgYGB7ciwgZWNobz1UUlVFfQ0KI1Bsb3QgTm90YSBFeGFtZW4gRmluYWwgeSBOb3RhIEZpbmFsDQpwbG90KHRyYWluX2RhdGFzZXQkRXhhbWVuRmluYWwxNSwgdHJhaW5fZGF0YXNldCROb3RhRmluYWwxNywgY29sPWttLm91dCRjbHVzdGVyLCBwY2g9MTksIHhsYWI9IkV4YW1lbiBGaW5hbCIsIHlsYWI9Ik5vdGEgRmluYWwiKQ0KYGBgDQoNCiMjIyBQbG90cyBkZSByZWxhY2nzbiBlbnRyZSBQYXJjaWFsZXMgeSBFeGFtZW4gRmluYWwNCmBgYHtyLCBlY2hvPVRSVUV9DQoNCiNQbG90IE5vdGEgUHJpbWVyIFBhcmNpYWwgeSBTZWd1bmRvIFBhcmNpYWwNCnBsb3QodHJhaW5fZGF0YXNldCRQcmltZXJQYXJjaWFsMTEsIHRyYWluX2RhdGFzZXQkU2VndW5kb1BhcmNpYWwxMywgY29sPWttLm91dCRjbHVzdGVyLCBwY2g9MTksIHhsYWI9IlByaW1lciBQYXJjaWFsIiwgeWxhYj0iU2VndW5kbyBQYXJjaWFsIikNCg0KYGBgDQoNCg0KYGBge3IsIGVjaG89VFJVRX0NCg0KI1Bsb3QgTm90YSBQcmltZXIgUGFyY2lhbCB5IEV4YW1lbiBGaW5hbA0KcGxvdCh0cmFpbl9kYXRhc2V0JFByaW1lclBhcmNpYWwxMSwgdHJhaW5fZGF0YXNldCRFeGFtZW5GaW5hbDE1LCBjb2w9a20ub3V0JGNsdXN0ZXIsIHBjaD0xOSwgeGxhYj0iUHJpbWVyIFBhcmNpYWwiLCB5bGFiPSJFeGFtZW4gRmluYWwiKQ0KDQpgYGANCg0KYGBge3IsIGVjaG89VFJVRX0NCg0KI1Bsb3QgTm90YSBTZWd1bmRvIFBhcmNpYWwgeSBFeGFtZW4gRmluYWwNCnBsb3QodHJhaW5fZGF0YXNldCRTZWd1bmRvUGFyY2lhbDEzLCB0cmFpbl9kYXRhc2V0JEV4YW1lbkZpbmFsMTUsIGNvbD1rbS5vdXQkY2x1c3RlciwgcGNoPTE5LCB4bGFiPSJTZWd1bmRvIFBhcmNpYWwiLCB5bGFiPSJFeGFtZW4gRmluYWwiKQ0KDQpgYGANCg0KIyMjIyBBIHBhcnRpciBkZSBsYSBpbmZvcm1hY2nzbiBhbnRlcmlvciBwb2RlbW9zIGRlZHVjaXIgcXVlIGV4aXN0ZSB1bmEgZXN0cmVjaGEgcmVhbGFjafNuIGNvbiBsb3MgYWx1bW5vcyBxdWUgbGVzIHZhIGJpZW4gZW4gbG9zIGV4YW1lbnMgeSB0ZXJtaW5hbiBiaWVuIGR1cmFudGUgZWwgY3Vyc28uDQoNCiMjIyMgRXMgZGVjaXIgcXVlIHNpIGEgdW4gYWx1bW5vIGxlIHZhIGJpZW4gZW4gZWwgcHJpbWVyIGV4YW1lbiBwYXJjaWFsLCBlcyBtdXkgcHJvYmFiYWJsZSBxdWUgbGUgdmFhIGJpZW4gZW4gdW4gZXhhbWVuIHN1IGV4YW1lbiBmaW5hbCB5IGNvbW8gY29uc2VjdWVuY2lhLCBiaWVuIGR1cmFudGUgdG9kbyBlbCBjdXJzby4NCg0KIyMjIyBBIHBhcnRpciBkZWwgYW7hbGlzaXMgZGUgY2x1c3RlcnMgaGF5IHVuIHBhciBkZSBvYnNlcnZhY2lvbmVzIGludGVyZXNhbnRlczogDQoNCiogQXBhcmVudGVtZW50ZSBleGlzdGUgdW5hIHJlbGFjafNuIHBvc2l0aXZhIGVudHJlIGxhcyBub3RhcyBjYWRhIGV4YW1lbiB5IGxhIG5vdGEgZmluYWwgZGVsIGN1cnNvLg0KDQoqIEVsIGFsZ29ydG1vIGRlIGNsdXN0ZXJpbmcgZW5jb250cm8gdHJlcyBjbHVzdGVycyBkaXN0aW50b3MgcXVlIGFwYXJlbnRlbWVudGUgZXN04W4gcmVsYWNpb25hZG9zIGNvbiBlbCBkZXNlbXBl8W8gZGVsIGFsdW1ubywgcG9kcu1hbW9zIGRlY2lyIHF1ZSBsb3MgY2x1c3RlcnMgc2UgcmVmaWVyZW4gYSBsYSBjYXRlZ29y7WEgZGVsIGVzdHVkaWFudGUuDQoNCiogQSBwYXJ0aXIgZGUgbG9zIGNsdXN0ZXJzIGZvcm1hZG9zIG5vIGV4aXN0ZSB1bmEgcmVsYWNp824gZGlyZWN0YSBjb24gYWxndW5hIGRlIGxhcyB2YXJpYWJsZXMgcXVlIGNvbmZvcm1hbiBlbCBkYXRhc2V0LiBTaW4gZW1iYXJnbyBwb2RlbW9zIGRlZmluaXIgMyBjYXRlZ29y7WEgZGVudHJvIGRlIGxvcyBlc3R1ZGlhbnRlcyBxdWUgaW5pY2lhbiBlbiBwcmltZXIgYfFvLg0KDQoqIEV4aXN0ZW4gYWxndW5vcyBwdW50b3MgYXRpcGljb3MsIHF1ZSByZXJwZXNlbnRhbiBhbHVtbm9zIHF1ZSBpbmljaWFyb24gbWFsIGR1cmFudGUgZWwgY3Vyc28sIHBlcm8gdGVybWluYXJvbiBiaWVuLiANCg0KKiBFeGlzdGVuIHZhcmlvcyBwdW50b3MgcXVlIHNlIGVuY3VlbnRyYW4gbWV6Y2xhZG9zIGVudHJlIGxvcyBjbHVzdGVycyBxdWUgcmVwcmVzZW50YW4gdW5hIGNhcmFjdGVy7XN0aWNhIGRlIGNhZGEgdW5vIGRlIGxvcyBlc3R1ZGlhbnRlcy4NCg0KIyA0KSBNb2RlbG8gZGUgUmVncmVzafNuLg0KDQojIyMgQSBjb250aW51YWNp824gbW9zdHJhcmVtb3MgdW4gbW9kZWxvIHBhcmEgcHJlZGVjaXIgbGEgbm90YSBmaW5hbCBkZSB1biBhbHVtbm8gZGVwZW5kaWVuZG8gZGUgNSB2YXJpYWJsZXM6DQoNCiogTm90YSBkZWwgUHJpbWVyIFBhcmNpYWwuDQoqIE5vdGEgZGVsIFNlZ3VuZG8gUGFyY2lhbC4NCiogTm90YSBkZWwgRXhhbWVuIEZpbmFsLg0KKiBDYXJyZXJhLg0KKiBHZW5lcm8uDQoNCiMjIyMgTW9kZWxvIGRlIFJlZ3Jlc2nzbiBMaW5lYWwNCmBgYHtyLCBlY2hvPVRSVUV9DQojTW9kZWxvIGRlIHJlZ3Jlc2nzbiBsaW5lYWwNCmxtLmZpdDE8LWxtKE5vdGFGaW5hbDE3IH4gUHJpbWVyUGFyY2lhbDExICsgDQogICAgICAgICAgICAgIFNlZ3VuZG9QYXJjaWFsMTMgKyBFeGFtZW5GaW5hbDE1IA0KICAgICAgICAgICAgKyBDYXJyZXJhMDQgKyBHZW5lcm8xMCwNCiAgICAgICAgICAgIGRhdGE9dHJhaW5fZGF0YXNldCkNCg0Kc3VtbWFyeShsbS5maXQxKQ0KDQpgYGANCg0KIyMjIEEgcGFydGlyIGRlIGxhIGluZm9ybWFjafNuIGFudGVyaW9yIHRvZGFzIGxhcyB2YXJpYWJsZXMgc2VsZWNjaW9uYWRhcyBzb24gZXN0YWRpc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhcyB5IGVsIG1vZGVsbyBjb21vIHRhbCBlcyBzaWduaWZpY2F0aXZvLg0KDQoNCiMjIyBBaG9yYSBjb25zdHJ1aXJtZW1vcyB1bmEgdGFibGEgcXVlIG5vcyBwZXJtaXRhIGhhY2VyIGNvbXBhcmFjaW9uZXMgZGUgbG9zIHZhbG9yZXMgT2JzZXJ2YWRvcyB5IFByZWRpY2hvcy4NCmBgYHtyLCBlY2hvPVRSVUV9DQoNCiNQcmVkaWNjafNuIGRlbCBwcnVlYmEgY29uIG1vZGVsbyBsaW5lYWwNCmxtLnByZWQxPC1wcmVkaWN0KGxtLmZpdDEsIG5ld2RhdGE9dGVzdF9kYXRhc2V0KQ0KDQojVGFibGEgZGUgc2FsaWRhIHBhcmEgY29tcGFyYWNp824uDQpUYWJsZTE8LWRhdGEuZnJhbWUodGVzdF9kYXRhc2V0JE5vdGFGaW5hbDE3LCByb3VuZChsbS5wcmVkMSwgMCkpDQpjb2xuYW1lcyhUYWJsZTEpPC1jKCJPYnNlcnZhZG8iLCAiUHJlZGljY2lvbiIpDQoNCiNFbGltaW5hbW9zIGVsIE5vbWJyZSBkZSBsYSBjb2x1bWFuDQpyb3cubmFtZXMoVGFibGUxKTwtTlVMTCANCg0KI01vc3RyYW1vcyBUYWJsYQ0KVGFibGUxDQpgYGANCmBgYHtyfQ0KI0V4cG9ydGFtb3MgbW9kZWxvIHBhcmEgc2hpbnkgYXBwDQpzYXZlKGxtLmZpdDEsIGZpbGU9Im5vdGFfbW9kZWwucmRhIikNCmBgYA0KDQoNCmBgYHtyLCB0cnVlfQ0KDQojUHJ1ZWJhIGRlIFByZWRpY2Np824gcGFyYSBTaGlueSBhcHANCmRhdGF0ZXN0PC1kYXRhLmZyYW1lKA0KICAgICAgUHJpbWVyUGFyY2lhbDExPTU3LA0KICAgICAgU2VndW5kb1BhcmNpYWwxMz04NywNCiAgICAgIEV4YW1lbkZpbmFsMTU9NjAsDQogICAgICBDYXJyZXJhMDQ9IklNRSIsDQogICAgICBHZW5lcm8xMD0yDQogICAgKQ0Kc2FsaWRhPC1wcmVkaWN0KGxtLmZpdDEsIG5ld2RhdGE9ZGF0YXRlc3QpDQpzYWxpZGENCg0KYGBgDQoNCg0K