El análisis de diferencias: El método de Bland y Altman.
Primero, cargamos los datos y los visualizamos:
# Cargamos los datos
df <- read.csv('datos.csv')
# Visualizamos estructura del dataset
head(df, n = 8)
# Extraemos variables
x <- df$Method.A
y <- df$Method.B
# Graficamos los datos
plot(x, y, type = 'p',
main = 'Scatter plot of measurements',
col = 'blue', xlab = 'Method A',
ylab = 'Method B')
# Etiquetas de líneas.
legend("topleft", c("Data"), fill=c("blue"))
# Agregamos una cuadrícula.
grid()

Construimos una regresión lineal, para ver si desde ahí podemos observar si los métodos de medición están de acuerdo:
# Realizamos la regresion lineal
modelo <- lm(Method.B ~ Method.A, data = df)
# El resultado
summary(modelo)
Call:
lm(formula = Method.B ~ Method.A, data = df)
Residuals:
Min 1Q Median 3Q Max
-93.615 -11.908 0.052 12.314 55.504
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 12.0163 8.8667 1.355 0.186
Method.A 1.0416 0.0181 57.561 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 32.49 on 28 degrees of freedom
Multiple R-squared: 0.9916, Adjusted R-squared: 0.9913
F-statistic: 3313 on 1 and 28 DF, p-value: < 2.2e-16
Gráficamos este resultado de regresión con 99% de confianza:
# Definimos intervalo de interes
interval <- seq(min(x), max(x), length.out = 184)
# Calculamos las estimaciones para cada punto en el intervalo
mu <- predict(modelo, newdata = data.frame('Method A' = interval),
interval = "confidence", level = 0.99)
# Creamos gráfica
plot(x, y, type = 'p',
main = 'Confidence interval of 99% for linear regression',
col = 'blue', xlab = 'Method A',
ylab = 'Method B')
# Coloreamos zona de confianza
polygon(c(rev(interval), interval),
c(rev(mu[ ,3]), mu[ ,2]), col = 'grey',
border = NA)
# Graficamos los datos
lines(x, y, type = 'p', col = 'blue')
# Graficamos mejor linea
abline(modelo)
# Etiquetas de líneas.
legend("topleft", c("Data"), fill=c("blue"))
# Agregamos una cuadrícula.
grid()

Ahora vamos a agregar las columnas necesarias al dataframe para poder construir las gráficas A&B. Primero, la columna de las diferencias:
df$diff <- df$Method.A - df$Method.B
head(df, 10)
NA
En teoría, si dos métodos estuvieran completamente de acuerdo, podríamos esperar que la diferencia entre sus mediciones sean siempre cere, y estén siempre de acuerdo). El problema de hacerlo de esta manera, es que no se considera el error de medición que pudiera existir en cada método.
Para considerar esto, debemos obtener el promedio de las diferencias de ambos métodos, ya que si la diferencia de cada medición es únicamente al error del método, entonces este promedio debería ser cercano a 0:
mean <- mean(df$diff)
mean
[1] -27.16667
En nuestro caso, el promedio de las diferencias no es cero, por lo que, en promedio, el método B mide \(27.17\) unidades más que el método A, en cada meidición. A esto le consideramos sesgo.
Si estamos seguros de que uno de los métodos está correcto, entonces con este promedio de diferencias podemos “ajustar” el otro método, pero en caso de que no haya ninguno que funcione como referencia, debemos investigar mas al respecto, obteniendo el promedio de las mediciones.
df$mean <- (df$Method.A + df$Method.B)/2
head(df, 8)
A partir de aquí, podemos construir la gráfica A&B, que es simplemente las diferencias entre los métodos contra el promedio entre los métodos. Esto nos permite investigar cualquier relación posible entre el error de medición y el valor real.
# Extraemos variables
x <- df$mean
y <- df$diff
# Graficamos los datos
plot(x, y, type = 'p',
main = 'A&B plot',
col = 'blue', xlab = 'Mean of methods',
ylab = 'Difference of methods')
abline(h = mean, col = 'red')
# Etiquetas de líneas.
legend("top", c("Data", "Mean (Bias)"), fill=c("blue", "red"))
# Agregamos una cuadrícula.
grid()

La línea roja, que corresponde con el promedio de las diferencias, es el bias respecto a la no diferencia entre los métodos. Se puede observar que las mediciones arriba de 200 son las responsables de este bias negativo.
Podemos hacer otra regresión lineal pero esta vez considerando estos ejes y datos, para encontrar alguna relación entre la diferencia de las mediciones y el valor real (que estamos estimando con el promedio de los métodos):
# Realizamos la regresion lineal
modelo <- lm(diff ~ mean, data = df)
# El resultado
summary(modelo)
Call:
lm(formula = diff ~ mean, data = df)
Residuals:
Min 1Q Median 3Q Max
-54.536 -12.483 -1.058 10.294 94.298
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -10.14690 8.74881 -1.16 0.2559
mean -0.04505 0.01733 -2.60 0.0147 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 31.79 on 28 degrees of freedom
Multiple R-squared: 0.1945, Adjusted R-squared: 0.1657
F-statistic: 6.76 on 1 and 28 DF, p-value: 0.01472
Visualizamos esta refresión con el intervalo de confianza al 0.95:
# Definimos intervalo de interes
interval <- seq(0, 1000, length.out = 184)
# Calculamos las estimaciones para cada punto en el intervalo
mu <- predict(modelo, newdata = data.frame("mean" = interval),
interval = "confidence", level = 0.95)
# Creamos gráfica
plot(x, y, type = 'p',
main = 'Confidence interval of 95% for linear regression \nin A&B plot',
col = 'blue', xlab = 'Mean of methods',
ylab = 'Differences of methods')
# Coloreamos zona de confianza
polygon(c(rev(interval), interval),
c(rev(mu[ ,3]), mu[ ,2]), col = 'grey',
border = NA)
# Graficamos los datos
lines(x, y, type = 'p', col = 'blue')
# Graficamos mejor linea
abline(modelo)
# Gráfica de promedio (bias)
abline(h = mean, col = 'red')
# Etiquetas de líneas.
legend("top", c("Data", "Mean (Bias)"), fill=c("blue", "red"))
# Agregamos una cuadrícula.
grid()

Podemos ver que existe una tendencia negativa en la diferencia entre los métodos cuando el valor real estimado crece.
Nos interesa ahora obtener límites de acuerdo, esto es, un intervalo en el que podrémos decir que las diferencias entre las mediciones de ambos métodos se encuentran. Para esto, ya conocemos el promedio de las diferencias, obtenemos también la desviación estándar:
std <- sd(df$dif)
std
[1] 34.80595
Ahora consruimos un intervalo de confianza. Podríamos asumir que la distribución de la diferncia de los métodos es normal; sin embargo, debemos revisar que en efecto los datos sean normales. Para esto, se puede utilizar alguno de los múltiples test existentes. En este caso, mostraremos el histograma correspondiente y realizaremos una inspección visual y también con el test de Shapiro-Wilk:
differences <- df$diff
hist(differences)

shapiro.test(differences)
Shapiro-Wilk normality test
data: differences
W = 0.97956, p-value = 0.8137
Por lo tanto, podemos asumir que es normal (p = 0.814). Finalmente, construimos los intervalos de confianza al 95%:
up <- mean + 1.96*std
low <- mean - 1.96*std
print(paste(low, up))
[1] "-95.3863249384042 41.0529916050708"
Finalmente, nuestra gráfica A&B completa es:
# Extraemos variables
x <- df$mean
y <- df$diff
# Graficamos los datos
plot(x, y, type = 'p',
main = 'A&B plot',
col = 'blue', xlab = 'Mean of methods',
ylab = 'Difference of methods',
ylim = c(-110, 50))
abline(h = mean, col = 'red')
abline(h = up, col = 'green')
abline(h = low, col = 'green')
# Etiquetas de líneas.
legend("top", c("Data", "Mean (Bias", "Limit"), fill=c("blue", "red", "green"))
# Agregamos una cuadrícula.
grid()

Por ultimo, es importante mencionar que el sistema de gráfica A&B no dice si el acuerdo es suficente, únicamente cuantifica el sesgo del rango de acuerdo, donde el 95% de las dfierences entre los métodos se encontrará. Se pueden realizar pruebas de significancia sobre este sesgo.
LS0tDQp0aXRsZTogIlByb3llY3RvIDMgLSBFc3RhZMOtc3RpY2EgYXZhbnphZGEuIg0KYXV0aG9yOiAiR2FicmllbCBNaXNzYWVsIEJhcmNvLiBOVUE6IDQyNzA3MSB8IExhdXJhIGRlbCBDYXJtZW4gQ2FiYWwgUGFyYW1vLiBOVUE6IDM5MTU4MCINCmRhdGU6ICIxOCBkZSBOb3ZpZW1icmUgZGUgMjAyMSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCjxzdHlsZT4NCmJvZHkge3RleHQtYWxpZ246IGp1c3RpZnk7IGZvbnQtc2l6ZTogMTJwdH0NCjwvc3R5bGU+DQoNCjxjZW50ZXI+DQogICAgPGltZyB3aWR0aD0iNjAlIiBzcmM9Imh0dHBzOi8vaS5pbWd1ci5jb20vVWFvOUNJeS5wbmciPg0KPC9jZW50ZXI+DQoNCiMjIEludHJvZHVjY2nDs24uDQoNClBhcmEgbGEgcmVhbGl6YWNpw7NuIGRlIGVzdGUgcHJveWVjdG8sIHNlIHByZXRlbmRlIHJlcHJvZHVjaXIgZGUgbWFuZXJhIHBhcmNpYWwgKGRlIGFjdWVyZG8gYSBsb3MgZGF0b3MgZGlzcG9uaWJsZXMpIGxvcyByZXN1bHRhZG9zIGRlbCBhcnTDrWN1bG86DQoNCj4gKipHaWF2YXJpbmEgRC4qKiBfVW5kZXJzdGFuZGluZyBCbGFuZCBBbHRtYW4gYW5hbHlzaXNfLiBCaW9jaGVtIE1lZCAoWmFncmViKS4gMjAxNTsyNSgyKToxNDEtMTUxLiBQdWJsaXNoZWQgMjAxNSBKdW4gNS4gZG9pOjEwLjExNjEzL0JNLjIwMTUuMDE1DQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyMgRWwgYW7DoWxpc2lzIGRlIGRpZmVyZW5jaWFzOiBFbCBtw6l0b2RvIGRlIEJsYW5kIHkgQWx0bWFuLg0KDQpQcmltZXJvLCBjYXJnYW1vcyBsb3MgZGF0b3MgeSBsb3MgdmlzdWFsaXphbW9zOiANCg0KYGBge3J9DQoNCiMgQ2FyZ2Ftb3MgbG9zIGRhdG9zDQpkZiA8LSByZWFkLmNzdignZGF0b3MuY3N2JykNCg0KIyBWaXN1YWxpemFtb3MgZXN0cnVjdHVyYSBkZWwgZGF0YXNldA0KaGVhZChkZiwgbiA9IDgpDQoNCiMgRXh0cmFlbW9zIHZhcmlhYmxlcyANCnggPC0gZGYkTWV0aG9kLkENCnkgPC0gZGYkTWV0aG9kLkINCg0KIyBHcmFmaWNhbW9zIGxvcyBkYXRvcw0KcGxvdCh4LCB5LCB0eXBlID0gJ3AnLCANCiAgICAgbWFpbiA9ICdTY2F0dGVyIHBsb3Qgb2YgbWVhc3VyZW1lbnRzJywgDQogICAgIGNvbCA9ICdibHVlJywgeGxhYiA9ICdNZXRob2QgQScsDQogICAgIHlsYWIgPSAnTWV0aG9kIEInKQ0KDQojIEV0aXF1ZXRhcyBkZSBsw61uZWFzLg0KbGVnZW5kKCJ0b3BsZWZ0IiwgYygiRGF0YSIpLCBmaWxsPWMoImJsdWUiKSkNCg0KIyBBZ3JlZ2Ftb3MgdW5hIGN1YWRyw61jdWxhLg0KZ3JpZCgpDQoNCmBgYA0KDQpDb25zdHJ1aW1vcyB1bmEgcmVncmVzacOzbiBsaW5lYWwsIHBhcmEgdmVyIHNpIGRlc2RlIGFow60gcG9kZW1vcyBvYnNlcnZhciBzaSBsb3MgbcOpdG9kb3MgZGUgbWVkaWNpw7NuIGVzdMOhbiBkZSBhY3VlcmRvOiANCg0KYGBge3J9DQojIFJlYWxpemFtb3MgbGEgcmVncmVzaW9uIGxpbmVhbA0KbW9kZWxvIDwtIGxtKE1ldGhvZC5CIH4gTWV0aG9kLkEsIGRhdGEgPSBkZikNCg0KIyBFbCByZXN1bHRhZG8NCnN1bW1hcnkobW9kZWxvKQ0KYGBgDQoNCkdyw6FmaWNhbW9zIGVzdGUgcmVzdWx0YWRvIGRlIHJlZ3Jlc2nDs24gY29uIDk5JSBkZSBjb25maWFuemE6IA0KDQpgYGB7cn0NCiMgRGVmaW5pbW9zIGludGVydmFsbyBkZSBpbnRlcmVzDQppbnRlcnZhbCA8LSBzZXEobWluKHgpLCBtYXgoeCksIGxlbmd0aC5vdXQgPSAxODQpDQoNCiMgQ2FsY3VsYW1vcyBsYXMgZXN0aW1hY2lvbmVzIHBhcmEgY2FkYSBwdW50byBlbiBlbCBpbnRlcnZhbG8NCm11IDwtIHByZWRpY3QobW9kZWxvLCBuZXdkYXRhID0gZGF0YS5mcmFtZSgnTWV0aG9kIEEnID0gaW50ZXJ2YWwpLCANCiAgICAgICAgICAgICAgaW50ZXJ2YWwgPSAiY29uZmlkZW5jZSIsIGxldmVsID0gMC45OSkNCg0KIyBDcmVhbW9zIGdyw6FmaWNhDQpwbG90KHgsIHksIHR5cGUgPSAncCcsIA0KICAgICBtYWluID0gJ0NvbmZpZGVuY2UgaW50ZXJ2YWwgb2YgOTklIGZvciBsaW5lYXIgcmVncmVzc2lvbicsIA0KICAgICBjb2wgPSAnYmx1ZScsIHhsYWIgPSAnTWV0aG9kIEEnLA0KICAgICB5bGFiID0gJ01ldGhvZCBCJykNCg0KIyBDb2xvcmVhbW9zIHpvbmEgZGUgY29uZmlhbnphDQpwb2x5Z29uKGMocmV2KGludGVydmFsKSwgaW50ZXJ2YWwpLA0KICAgICAgICBjKHJldihtdVsgLDNdKSwgbXVbICwyXSksIGNvbCA9ICdncmV5JywNCiAgICAgICAgYm9yZGVyID0gTkEpDQoNCiMgR3JhZmljYW1vcyBsb3MgZGF0b3MNCmxpbmVzKHgsIHksIHR5cGUgPSAncCcsIGNvbCA9ICdibHVlJykNCg0KIyBHcmFmaWNhbW9zIG1lam9yIGxpbmVhDQphYmxpbmUobW9kZWxvKQ0KDQojIEV0aXF1ZXRhcyBkZSBsw61uZWFzLg0KbGVnZW5kKCJ0b3BsZWZ0IiwgYygiRGF0YSIpLCBmaWxsPWMoImJsdWUiKSkNCg0KIyBBZ3JlZ2Ftb3MgdW5hIGN1YWRyw61jdWxhLg0KZ3JpZCgpDQpgYGANCg0KQWhvcmEgdmFtb3MgYSBhZ3JlZ2FyIGxhcyBjb2x1bW5hcyBuZWNlc2FyaWFzIGFsIGRhdGFmcmFtZSBwYXJhIHBvZGVyIGNvbnN0cnVpciBsYXMgZ3LDoWZpY2FzIEEmQi4gUHJpbWVybywgbGEgY29sdW1uYSBkZSBsYXMgZGlmZXJlbmNpYXM6IA0KDQpgYGB7cn0NCmRmJGRpZmYgPC0gZGYkTWV0aG9kLkEgLSBkZiRNZXRob2QuQg0KaGVhZChkZiwgMTApDQoNCmBgYA0KDQpFbiB0ZW9yw61hLCBzaSBkb3MgbcOpdG9kb3MgZXN0dXZpZXJhbiBjb21wbGV0YW1lbnRlIGRlIGFjdWVyZG8sIHBvZHLDrWFtb3MgZXNwZXJhciBxdWUgbGEgZGlmZXJlbmNpYSBlbnRyZSBzdXMgbWVkaWNpb25lcyBzZWFuIHNpZW1wcmUgY2VyZSwgeSBlc3TDqW4gc2llbXByZSBkZSBhY3VlcmRvKS4gRWwgcHJvYmxlbWEgZGUgaGFjZXJsbyBkZSBlc3RhIG1hbmVyYSwgZXMgcXVlIG5vIHNlIGNvbnNpZGVyYSBlbCBlcnJvciBkZSBtZWRpY2nDs24gcXVlIHB1ZGllcmEgZXhpc3RpciBlbiBjYWRhIG3DqXRvZG8uIA0KDQpQYXJhIGNvbnNpZGVyYXIgZXN0bywgZGViZW1vcyBvYnRlbmVyIGVsIHByb21lZGlvIGRlIGxhcyBkaWZlcmVuY2lhcyBkZSBhbWJvcyBtw6l0b2RvcywgeWEgcXVlIHNpIGxhIGRpZmVyZW5jaWEgZGUgY2FkYSBtZWRpY2nDs24gZXMgw7puaWNhbWVudGUgYWwgZXJyb3IgZGVsIG3DqXRvZG8sIGVudG9uY2VzIGVzdGUgcHJvbWVkaW8gZGViZXLDrWEgc2VyIGNlcmNhbm8gYSAwOg0KDQoNCmBgYHtyfQ0KbWVhbiA8LSBtZWFuKGRmJGRpZmYpDQptZWFuDQpgYGANCkVuIG51ZXN0cm8gY2FzbywgZWwgcHJvbWVkaW8gZGUgbGFzIGRpZmVyZW5jaWFzIG5vIGVzIGNlcm8sIHBvciBsbyBxdWUsIGVuIHByb21lZGlvLCBlbCBtw6l0b2RvIEIgbWlkZSAkMjcuMTckIHVuaWRhZGVzIG3DoXMgcXVlIGVsIG3DqXRvZG8gQSwgZW4gY2FkYSBtZWlkaWNpw7NuLiBBIGVzdG8gbGUgY29uc2lkZXJhbW9zIF9zZXNnb18uDQoNClNpIGVzdGFtb3Mgc2VndXJvcyBkZSBxdWUgdW5vIGRlIGxvcyBtw6l0b2RvcyBlc3TDoSBjb3JyZWN0bywgZW50b25jZXMgY29uIGVzdGUgcHJvbWVkaW8gZGUgZGlmZXJlbmNpYXMgcG9kZW1vcyAiYWp1c3RhciIgZWwgb3RybyBtw6l0b2RvLCBwZXJvIGVuIGNhc28gZGUgcXVlIG5vIGhheWEgbmluZ3VubyBxdWUgZnVuY2lvbmUgY29tbyByZWZlcmVuY2lhLCBkZWJlbW9zIGludmVzdGlnYXIgbWFzIGFsIHJlc3BlY3RvLCBvYnRlbmllbmRvIGVsIHByb21lZGlvIGRlIGxhcyBtZWRpY2lvbmVzLg0KDQpgYGB7cn0NCmRmJG1lYW4gPC0gKGRmJE1ldGhvZC5BICsgZGYkTWV0aG9kLkIpLzINCmhlYWQoZGYsIDgpDQpgYGANCg0KQSBwYXJ0aXIgZGUgYXF1w60sIHBvZGVtb3MgY29uc3RydWlyIGxhIGdyw6FmaWNhIEEmQiwgcXVlIGVzIHNpbXBsZW1lbnRlIGxhcyBkaWZlcmVuY2lhcyBlbnRyZSBsb3MgbcOpdG9kb3MgY29udHJhIGVsIHByb21lZGlvIGVudHJlIGxvcyBtw6l0b2Rvcy4gRXN0byBub3MgcGVybWl0ZSBpbnZlc3RpZ2FyIGN1YWxxdWllciByZWxhY2nDs24gcG9zaWJsZSBlbnRyZSBlbCBlcnJvciBkZSBtZWRpY2nDs24geSBlbCB2YWxvciByZWFsLg0KDQpgYGB7cn0NCiMgRXh0cmFlbW9zIHZhcmlhYmxlcyANCnggPC0gZGYkbWVhbg0KeSA8LSBkZiRkaWZmDQoNCiMgR3JhZmljYW1vcyBsb3MgZGF0b3MNCnBsb3QoeCwgeSwgdHlwZSA9ICdwJywgDQogICAgIG1haW4gPSAnQSZCIHBsb3QnLCANCiAgICAgY29sID0gJ2JsdWUnLCB4bGFiID0gJ01lYW4gb2YgbWV0aG9kcycsDQogICAgIHlsYWIgPSAnRGlmZmVyZW5jZSBvZiBtZXRob2RzJykNCg0KYWJsaW5lKGggPSBtZWFuLCBjb2wgPSAncmVkJykNCg0KIyBFdGlxdWV0YXMgZGUgbMOtbmVhcy4NCmxlZ2VuZCgidG9wIiwgYygiRGF0YSIsICJNZWFuIChCaWFzKSIpLCBmaWxsPWMoImJsdWUiLCAicmVkIikpDQoNCiMgQWdyZWdhbW9zIHVuYSBjdWFkcsOtY3VsYS4NCmdyaWQoKQ0KYGBgDQpMYSBsw61uZWEgcm9qYSwgcXVlIGNvcnJlc3BvbmRlIGNvbiBlbCBwcm9tZWRpbyBkZSBsYXMgZGlmZXJlbmNpYXMsIGVzIGVsIGJpYXMgcmVzcGVjdG8gYSBsYSBubyBkaWZlcmVuY2lhIGVudHJlIGxvcyBtw6l0b2Rvcy4gU2UgcHVlZGUgb2JzZXJ2YXIgcXVlIGxhcyBtZWRpY2lvbmVzIGFycmliYSBkZSAyMDAgc29uIGxhcyByZXNwb25zYWJsZXMgZGUgZXN0ZSBiaWFzIG5lZ2F0aXZvLg0KDQpQb2RlbW9zIGhhY2VyIG90cmEgcmVncmVzacOzbiBsaW5lYWwgcGVybyBlc3RhIHZleiBjb25zaWRlcmFuZG8gZXN0b3MgZWplcyB5IGRhdG9zLCBwYXJhIGVuY29udHJhciBhbGd1bmEgcmVsYWNpw7NuIGVudHJlIGxhIGRpZmVyZW5jaWEgZGUgbGFzIG1lZGljaW9uZXMgeSBlbCB2YWxvciByZWFsIChxdWUgZXN0YW1vcyBlc3RpbWFuZG8gY29uIGVsIHByb21lZGlvIGRlIGxvcyBtw6l0b2Rvcyk6DQoNCmBgYHtyfQ0KIyBSZWFsaXphbW9zIGxhIHJlZ3Jlc2lvbiBsaW5lYWwNCm1vZGVsbyA8LSBsbShkaWZmIH4gbWVhbiwgZGF0YSA9IGRmKQ0KDQojIEVsIHJlc3VsdGFkbw0Kc3VtbWFyeShtb2RlbG8pDQpgYGANClZpc3VhbGl6YW1vcyBlc3RhIHJlZnJlc2nDs24gY29uIGVsIGludGVydmFsbyBkZSBjb25maWFuemEgYWwgMC45NToNCg0KYGBge3J9DQojIERlZmluaW1vcyBpbnRlcnZhbG8gZGUgaW50ZXJlcw0KaW50ZXJ2YWwgPC0gc2VxKDAsIDEwMDAsIGxlbmd0aC5vdXQgPSAxODQpDQoNCiMgQ2FsY3VsYW1vcyBsYXMgZXN0aW1hY2lvbmVzIHBhcmEgY2FkYSBwdW50byBlbiBlbCBpbnRlcnZhbG8NCm11IDwtIHByZWRpY3QobW9kZWxvLCBuZXdkYXRhID0gZGF0YS5mcmFtZSgibWVhbiIgPSBpbnRlcnZhbCksIA0KICAgICAgICAgICAgICBpbnRlcnZhbCA9ICJjb25maWRlbmNlIiwgbGV2ZWwgPSAwLjk1KQ0KDQojIENyZWFtb3MgZ3LDoWZpY2ENCnBsb3QoeCwgeSwgdHlwZSA9ICdwJywgDQogICAgIG1haW4gPSAnQ29uZmlkZW5jZSBpbnRlcnZhbCBvZiA5NSUgZm9yIGxpbmVhciByZWdyZXNzaW9uIFxuaW4gQSZCIHBsb3QnLCANCiAgICAgY29sID0gJ2JsdWUnLCB4bGFiID0gJ01lYW4gb2YgbWV0aG9kcycsDQogICAgIHlsYWIgPSAnRGlmZmVyZW5jZXMgb2YgbWV0aG9kcycpDQoNCiMgQ29sb3JlYW1vcyB6b25hIGRlIGNvbmZpYW56YQ0KcG9seWdvbihjKHJldihpbnRlcnZhbCksIGludGVydmFsKSwNCiAgICAgICAgYyhyZXYobXVbICwzXSksIG11WyAsMl0pLCBjb2wgPSAnZ3JleScsDQogICAgICAgIGJvcmRlciA9IE5BKQ0KDQojIEdyYWZpY2Ftb3MgbG9zIGRhdG9zDQpsaW5lcyh4LCB5LCB0eXBlID0gJ3AnLCBjb2wgPSAnYmx1ZScpDQoNCiMgR3JhZmljYW1vcyBtZWpvciBsaW5lYQ0KYWJsaW5lKG1vZGVsbykNCg0KIyBHcsOhZmljYSBkZSBwcm9tZWRpbyAoYmlhcykNCmFibGluZShoID0gbWVhbiwgY29sID0gJ3JlZCcpDQoNCiMgRXRpcXVldGFzIGRlIGzDrW5lYXMuDQpsZWdlbmQoInRvcCIsIGMoIkRhdGEiLCAiTWVhbiAoQmlhcykiKSwgZmlsbD1jKCJibHVlIiwgInJlZCIpKQ0KDQojIEFncmVnYW1vcyB1bmEgY3VhZHLDrWN1bGEuDQpncmlkKCkNCmBgYA0KUG9kZW1vcyB2ZXIgcXVlIGV4aXN0ZSB1bmEgdGVuZGVuY2lhIG5lZ2F0aXZhIGVuIGxhIGRpZmVyZW5jaWEgZW50cmUgbG9zIG3DqXRvZG9zIGN1YW5kbyBlbCB2YWxvciByZWFsIGVzdGltYWRvIGNyZWNlLg0KDQpOb3MgaW50ZXJlc2EgYWhvcmEgb2J0ZW5lciBsw61taXRlcyBkZSBhY3VlcmRvLCBlc3RvIGVzLCB1biBpbnRlcnZhbG8gZW4gZWwgcXVlIHBvZHLDqW1vcyBkZWNpciBxdWUgbGFzIGRpZmVyZW5jaWFzIGVudHJlIGxhcyBtZWRpY2lvbmVzIGRlIGFtYm9zIG3DqXRvZG9zIHNlIGVuY3VlbnRyYW4uIFBhcmEgZXN0bywgeWEgY29ub2NlbW9zIGVsIHByb21lZGlvIGRlIGxhcyBkaWZlcmVuY2lhcywgb2J0ZW5lbW9zIHRhbWJpw6luIGxhIGRlc3ZpYWNpw7NuIGVzdMOhbmRhcjogDQoNCmBgYHtyfQ0Kc3RkIDwtIHNkKGRmJGRpZikNCnN0ZA0KYGBgDQpBaG9yYSBjb25zcnVpbW9zIHVuIGludGVydmFsbyBkZSBjb25maWFuemEuIFBvZHLDrWFtb3MgYXN1bWlyIHF1ZSBsYSBkaXN0cmlidWNpw7NuIGRlIGxhIGRpZmVybmNpYSBkZSBsb3MgbcOpdG9kb3MgZXMgbm9ybWFsOyBzaW4gZW1iYXJnbywgZGViZW1vcyByZXZpc2FyIHF1ZSBlbiBlZmVjdG8gbG9zIGRhdG9zIHNlYW4gbm9ybWFsZXMuIFBhcmEgZXN0bywgc2UgcHVlZGUgdXRpbGl6YXIgYWxndW5vIGRlIGxvcyBtw7psdGlwbGVzIHRlc3QgZXhpc3RlbnRlcy4gRW4gZXN0ZSBjYXNvLCBtb3N0cmFyZW1vcyBlbCBoaXN0b2dyYW1hIGNvcnJlc3BvbmRpZW50ZSB5IHJlYWxpemFyZW1vcyB1bmEgaW5zcGVjY2nDs24gdmlzdWFsIHkgdGFtYmnDqW4gY29uIGVsIHRlc3QgZGUgU2hhcGlyby1XaWxrOiANCg0KYGBge3J9DQpkaWZmZXJlbmNlcyA8LSBkZiRkaWZmDQpoaXN0KGRpZmZlcmVuY2VzKQ0KDQpzaGFwaXJvLnRlc3QoZGlmZmVyZW5jZXMpDQpgYGANCg0KUG9yIGxvIHRhbnRvLCBwb2RlbW9zIGFzdW1pciBxdWUgZXMgbm9ybWFsIChwID0gMC44MTQpLiBGaW5hbG1lbnRlLCBjb25zdHJ1aW1vcyBsb3MgaW50ZXJ2YWxvcyBkZSBjb25maWFuemEgYWwgOTUlOg0KDQpgYGB7cn0NCnVwIDwtIG1lYW4gKyAxLjk2KnN0ZA0KbG93IDwtIG1lYW4gLSAxLjk2KnN0ZA0KDQpwcmludChwYXN0ZShsb3csIHVwKSkNCmBgYA0KRmluYWxtZW50ZSwgbnVlc3RyYSBncsOhZmljYSBBJkIgY29tcGxldGEgZXM6IA0KDQpgYGB7cn0NCiMgRXh0cmFlbW9zIHZhcmlhYmxlcyANCnggPC0gZGYkbWVhbg0KeSA8LSBkZiRkaWZmDQoNCiMgR3JhZmljYW1vcyBsb3MgZGF0b3MNCnBsb3QoeCwgeSwgdHlwZSA9ICdwJywgDQogICAgIG1haW4gPSAnQSZCIHBsb3QnLCANCiAgICAgY29sID0gJ2JsdWUnLCB4bGFiID0gJ01lYW4gb2YgbWV0aG9kcycsDQogICAgIHlsYWIgPSAnRGlmZmVyZW5jZSBvZiBtZXRob2RzJywNCiAgICAgeWxpbSA9IGMoLTExMCwgNTApKQ0KDQphYmxpbmUoaCA9IG1lYW4sIGNvbCA9ICdyZWQnKQ0KYWJsaW5lKGggPSB1cCwgY29sID0gJ2dyZWVuJykNCmFibGluZShoID0gbG93LCBjb2wgPSAnZ3JlZW4nKQ0KDQojIEV0aXF1ZXRhcyBkZSBsw61uZWFzLg0KbGVnZW5kKCJ0b3AiLCBjKCJEYXRhIiwgIk1lYW4gKEJpYXMiLCAiTGltaXQiKSwgZmlsbD1jKCJibHVlIiwgInJlZCIsICJncmVlbiIpKQ0KDQojIEFncmVnYW1vcyB1bmEgY3VhZHLDrWN1bGEuDQpncmlkKCkNCmBgYA0KDQpQb3IgdWx0aW1vLCBlcyBpbXBvcnRhbnRlIG1lbmNpb25hciBxdWUgZWwgc2lzdGVtYSBkZSBncsOhZmljYSBBJkIgbm8gZGljZSBzaSBlbCBhY3VlcmRvIGVzIHN1ZmljZW50ZSwgw7puaWNhbWVudGUgY3VhbnRpZmljYSBlbCBzZXNnbyBkZWwgcmFuZ28gZGUgYWN1ZXJkbywgZG9uZGUgZWwgOTUlIGRlIGxhcyBkZmllcmVuY2VzIGVudHJlIGxvcyBtw6l0b2RvcyBzZSBlbmNvbnRyYXLDoS4gU2UgcHVlZGVuIHJlYWxpemFyIHBydWViYXMgZGUgc2lnbmlmaWNhbmNpYSBzb2JyZSBlc3RlIHNlc2dvLg0KDQojIyBSZWZlcmVuY2lhcy4NCg0KMS4gKipHYWxsYWdoZXIgUiwgTWNLaW5sZXkgUywgRHJhY3VwIEsqKi4gKlByZWRpY3RvcnMgb2Ygd29tZW4ncyBhdHRlbmRhbmNlIGF0IGNhcmRpYWMgcmVoYWJpbGl0YXRpb24gcHJvZ3JhbXMqLiBQcm9nIENhcmRpb3Zhc2MgTnVycy4gMjAwMyBTdW1tZXI7MTgoMyk6MTIxLTYuIGRvaTogMTAuMTExMS9qLjA4ODktNzIwNC4yMDAzLjAyMTI5LnguIFBNSUQ6IDEyODkzOTczLg0KMi4gKipXYXluZSBXLiBEYW5pZWwgeSBDaGFkIEwuIENyb3NzKiosICpCaW9zdGF0aXN0aWNzOiBBIEZvdW5kYXRpb24gZm9yIEFuYWx5c2lzIGluIHRoZSBIZWFsdGggU2NpZW5jZXMqLCAxMMKwIGVkaXRpb24sIFdpbGV5Lg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMjIEFuZXhvcy4NCg0KIyMjIERpYXBvc2l0aXZhcyBkZSBwcmVzZW50YWNpw7NuIGRlIHByb3llY3RvLiANCg0KU2UgcHVlZGVuIGVuY29udHJhciBlbiBlc3RlIFtsaW5rXShodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9wcmVzZW50YXRpb24vZC9lLzJQQUNYLTF2UVVhbFV5RHg1UnlsRk1Yc0dqNXduQkhMUzBCOXRlRFJ0d01QVjhWZXpmTUpwQ19ZYjJkdmZmdHJuWUVIdEVHZ1plTEV1alNadGR0NENHL3B1Yj9zdGFydD1mYWxzZSZsb29wPWZhbHNlJmRlbGF5bXM9NjAwMDApLg0KDQo=