Teoría

La regresión lineal es un método que permite analizar la relación entre una variable que queremos predecir y diferentes variables que pueden explicarla.

En R se utiliza la función lm() para crear este tipo de modelos.

Una medida importante para evaluar el modelo es la R-Cuadrada, que toma valores entre 0 y 1. Entre más cercano sea el valor a 1, mayor cantidad de la variación de la variable objetivo está explicando el modelo.

Contexto

Una inmobiliaria cuenta con información de diferentes casas que ya fueron vendidas. La base incluye características como tamaño, ubicación, estacionamiento y distancia a distintos servicios.

El objetivo es generar un modelo de regresión lineal que permita estimar el precio de una casa a partir de sus características.

Instalar paquetes y llamar librerías

# install.packages("corrplot")

library(corrplot)

Importar la base de datos

df <- read.csv("/Users/kamilahchaidez/Downloads/HousePriceData.csv")

Entender la base de datos

# Resumen general de los datos
summary(df)
##   Observation      Dist_Taxi      Dist_Market    Dist_Hospital  
##  Min.   :  1.0   Min.   :  146   Min.   : 1666   Min.   : 3227  
##  1st Qu.:237.0   1st Qu.: 6477   1st Qu.: 9367   1st Qu.:11302  
##  Median :469.0   Median : 8228   Median :11149   Median :13189  
##  Mean   :468.4   Mean   : 8235   Mean   :11022   Mean   :13091  
##  3rd Qu.:700.0   3rd Qu.: 9939   3rd Qu.:12675   3rd Qu.:14855  
##  Max.   :932.0   Max.   :20662   Max.   :20945   Max.   :23294  
##                                                                 
##      Carpet         Builtup           Parking      City_Category
##  Min.   :  775   Min.   :  932   Length   :905   Length   :905  
##  1st Qu.: 1317   1st Qu.: 1579   N.unique :  4   N.unique :  3  
##  Median : 1478   Median : 1774   N.blank  :  0   N.blank  :  0  
##  Mean   : 1511   Mean   : 1794   Min.nchar:  4   Min.nchar:  5  
##  3rd Qu.: 1654   3rd Qu.: 1985   Max.nchar: 12   Max.nchar:  5  
##  Max.   :24300   Max.   :12730                                  
##  NAs    :7                                                      
##     Rainfall       House_Price       
##  Min.   :-110.0   Min.   :  1492000  
##  1st Qu.: 600.0   1st Qu.:  4623000  
##  Median : 780.0   Median :  5860000  
##  Mean   : 786.9   Mean   :  6083992  
##  3rd Qu.: 970.0   3rd Qu.:  7200000  
##  Max.   :1560.0   Max.   :150000000  
## 
# Tipo de variables
str(df)
## 'data.frame':    905 obs. of  10 variables:
##  $ Observation  : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ Dist_Taxi    : int  9796 8294 11001 8301 10510 6665 13153 5882 7495 8233 ...
##  $ Dist_Market  : int  5250 8186 14399 11188 12629 5142 11869 9948 11589 7067 ...
##  $ Dist_Hospital: int  10703 12694 16991 12289 13921 9972 17811 13315 13370 11400 ...
##  $ Carpet       : int  1659 1461 1340 1451 1770 1442 1542 1261 1090 1030 ...
##  $ Builtup      : int  1961 1752 1609 1748 2111 1733 1858 1507 1321 1235 ...
##  $ Parking      : chr  "Open" "Not Provided" "Not Provided" "Covered" ...
##  $ City_Category: chr  "CAT B" "CAT B" "CAT A" "CAT B" ...
##  $ Rainfall     : int  530 210 720 620 450 760 1030 1020 680 1130 ...
##  $ House_Price  : int  6649000 3982000 5401000 5373000 4662000 4526000 7224000 3772000 4631000 4415000 ...
# Primeras filas
head(df)
##   Observation Dist_Taxi Dist_Market Dist_Hospital Carpet Builtup      Parking
## 1           1      9796        5250         10703   1659    1961         Open
## 2           2      8294        8186         12694   1461    1752 Not Provided
## 3           3     11001       14399         16991   1340    1609 Not Provided
## 4           4      8301       11188         12289   1451    1748      Covered
## 5           5     10510       12629         13921   1770    2111 Not Provided
## 6           6      6665        5142          9972   1442    1733         Open
##   City_Category Rainfall House_Price
## 1         CAT B      530     6649000
## 2         CAT B      210     3982000
## 3         CAT A      720     5401000
## 4         CAT B      620     5373000
## 5         CAT B      450     4662000
## 6         CAT B      760     4526000
# Últimas filas
tail(df)
##     Observation Dist_Taxi Dist_Market Dist_Hospital Carpet Builtup      Parking
## 900         927     10915       17486         15964   1549    1851 Not Provided
## 901         928     12176        8518         15673   1582    1910      Covered
## 902         929      7214        8717         10553   1387    1663         Open
## 903         930      7423       11708         13220   1200    1436         Open
## 904         931     15082       14700         19617   1299    1560         Open
## 905         932      9297       12537         14418   1174    1429      Covered
##     City_Category Rainfall House_Price
## 900         CAT C     1220     7062000
## 901         CAT C     1080     6639000
## 902         CAT A      850     8208000
## 903         CAT A     1060     7644000
## 904         CAT B      770     9661000
## 905         CAT C     1110     5434000

La variable que queremos predecir es House_Price, mientras que las demás variables contienen diferentes características de las casas.

Algunas variables son numéricas y otras son categóricas, como Parking y City_Category.

Analizar correlaciones

# Seleccionar únicamente las columnas numéricas
numericas <- df[sapply(df, is.numeric)]

# Crear matriz de correlaciones
correlacion <- cor(
  numericas,
  use = "complete.obs"
)

# Graficar correlaciones
corrplot(correlacion)

La gráfica permite identificar cuáles variables presentan una relación más fuerte.

Los círculos más grandes representan correlaciones más altas. En este caso, las variables relacionadas con el tamaño de la casa, como Carpet y Builtup, presentan una relación importante entre ellas y también con el precio.

Por otro lado, algunas variables de distancia presentan una relación mucho menor con House_Price.

Limpiar la base de datos

# Revisar valores faltantes por columna
colSums(is.na(df))
##   Observation     Dist_Taxi   Dist_Market Dist_Hospital        Carpet 
##             0             0             0             0             7 
##       Builtup       Parking City_Category      Rainfall   House_Price 
##             0             0             0             0             0
# Eliminar filas con datos faltantes
df <- na.omit(df)

Se eliminan los registros que contienen valores faltantes para trabajar con una base completa antes de generar el modelo.

Como la cantidad de registros eliminados es pequeña comparada con el total de observaciones, se puede continuar con el análisis sin perder una cantidad importante de información.

Generar el modelo

# Crear modelo utilizando todas las variables
regresion <- lm(
  House_Price ~ .,
  data = df
)

summary(regresion)
## 
## Call:
## lm(formula = House_Price ~ ., data = df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -3694478  -799483   -54242   780191  4539531 
## 
## Coefficients:
##                       Estimate Std. Error t value Pr(>|t|)    
## (Intercept)          5.331e+06  3.781e+05  14.097  < 2e-16 ***
## Observation          4.070e+02  1.524e+02   2.670  0.00773 ** 
## Dist_Taxi            2.777e+01  2.685e+01   1.034  0.30127    
## Dist_Market          1.444e+01  2.083e+01   0.693  0.48839    
## Dist_Hospital        4.921e+01  3.011e+01   1.634  0.10257    
## Carpet               9.894e+03  1.423e+02  69.508  < 2e-16 ***
## Builtup             -7.544e+03  2.407e+02 -31.344  < 2e-16 ***
## ParkingNo Parking   -6.156e+05  1.388e+05  -4.435 1.04e-05 ***
## ParkingNot Provided -4.975e+05  1.236e+05  -4.027 6.15e-05 ***
## ParkingOpen         -2.575e+05  1.127e+05  -2.285  0.02253 *  
## City_CategoryCAT B  -1.874e+06  9.613e+04 -19.494  < 2e-16 ***
## City_CategoryCAT C  -2.897e+06  1.059e+05 -27.367  < 2e-16 ***
## Rainfall            -9.559e+01  1.543e+02  -0.620  0.53564    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1224000 on 885 degrees of freedom
## Multiple R-squared:  0.9433, Adjusted R-squared:  0.9426 
## F-statistic:  1228 on 12 and 885 DF,  p-value: < 2.2e-16

En los resultados del modelo se puede observar qué variables presentan una mayor relación con el precio.

Las variables con mayor significancia son principalmente:

  • Carpet
  • Builtup
  • Parking
  • City_Category

Mientras que variables como las distancias al mercado, hospital o taxi presentan menor importancia en el modelo.

La R-Cuadrada del modelo es aproximadamente 0.9433, lo que significa que el modelo explica cerca del 94% de la variación del precio de las casas.

La variable Observation no representa una característica real de una casa, ya que funciona solamente como un identificador de los registros, por lo que no es recomendable utilizarla para realizar predicciones.

Ajustar el modelo

# Crear un segundo modelo únicamente con las variables más relevantes

regresion2 <- lm(
  House_Price ~ Carpet + Builtup + Parking + City_Category,
  data = df
)

summary(regresion2)
## 
## Call:
## lm(formula = House_Price ~ Carpet + Builtup + Parking + City_Category, 
##     data = df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -3430967  -802513   -46915   775070  4263090 
## 
## Coefficients:
##                       Estimate Std. Error t value Pr(>|t|)    
## (Intercept)          6477702.2   272077.2  23.808  < 2e-16 ***
## Carpet                  9968.8      143.3  69.584  < 2e-16 ***
## Builtup                -7611.6      243.3 -31.279  < 2e-16 ***
## ParkingNo Parking    -536301.8   139943.6  -3.832 0.000136 ***
## ParkingNot Provided  -457380.4   125020.1  -3.658 0.000269 ***
## ParkingOpen          -236576.3   114356.8  -2.069 0.038857 *  
## City_CategoryCAT B  -1918518.1    97007.4 -19.777  < 2e-16 ***
## City_CategoryCAT C  -2908756.6   107359.6 -27.094  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1243000 on 890 degrees of freedom
## Multiple R-squared:  0.9412, Adjusted R-squared:  0.9407 
## F-statistic:  2034 on 7 and 890 DF,  p-value: < 2.2e-16

En este segundo modelo se eliminaron las variables que tenían menor importancia.

La R-Cuadrada se mantiene aproximadamente en 0.9412, por lo que disminuye muy poco comparada con el modelo anterior.

Esto indica que se puede utilizar un modelo más sencillo con menos variables y obtener prácticamente el mismo nivel de explicación.

Interpretación de variables

Los resultados del modelo permiten observar cómo las diferentes características modifican el precio estimado de una casa.

  • Carpet: al aumentar el tamaño de la propiedad, también aumenta el precio estimado.

  • Builtup: se encuentra muy relacionada con Carpet, por lo que ambas variables pueden influir entre sí dentro del modelo.

  • City_Category: la ubicación tiene un efecto importante en el precio. Las casas ubicadas en diferentes categorías de ciudad presentan diferencias considerables en su valor.

  • Parking: contar o no con determinado tipo de estacionamiento también modifica el precio esperado de la propiedad.

Generar Predicciones

# Crear ejemplos de casas nuevas para estimar su precio

datos_nuevos <- data.frame(
  Carpet = 1500,
  Builtup = 1800,
  Parking = "Covered",
  City_Category = c(
    "CAT A",
    "CAT B",
    "CAT C"
  )
)

predict(
  regresion2,
  newdata = datos_nuevos
)
##       1       2       3 
## 7729938 5811420 4821181

En este ejemplo se utiliza una casa con las mismas características de superficie y estacionamiento, pero se modifica la categoría de la ciudad.

De esta forma podemos observar cómo la ubicación puede cambiar considerablemente el precio estimado, aunque el resto de las características de la propiedad permanezcan iguales.

Conclusiones

A partir del modelo de regresión lineal se observa que las variables que tienen una mayor relación con el precio de una casa son principalmente su tamaño, la ubicación y el estacionamiento.

El primer modelo explicó aproximadamente el 94% de la variación del precio, pero al eliminar variables poco relevantes el resultado prácticamente no cambió.

Por esta razón, el segundo modelo es más conveniente, ya que utiliza menos variables y mantiene un nivel de explicación muy similar.

Finalmente, las predicciones muestran que una casa con las mismas características puede tener precios diferentes dependiendo principalmente de la categoría de la ciudad donde se encuentre.

LS0tCnRpdGxlOiAiUmVncmVzacOzbiBMaW5lYWwgLSBQcmVjaW8gZGUgQ2FzYXMiCmF1dGhvcjogIkthbWlsYWggQ2hhaWRleiAtIEEwMTc0MTk0MyIKZGF0ZTogIjIwMjYtMDgtMjAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBjb3NtbwotLS0KCiFbXShodHRwczovL3d3dy5naWZzcy5jb20vYXJxdWl0ZWN0dXJhL2Nhc2FzL2ltYWdlcy9jYXNhLTI2LmdpZikKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmRhcmsiPlRlb3LDrWE8L3NwYW4+CgpMYSAqKnJlZ3Jlc2nDs24gbGluZWFsKiogZXMgdW4gbcOpdG9kbyBxdWUgcGVybWl0ZSBhbmFsaXphciBsYSByZWxhY2nDs24gZW50cmUgdW5hIHZhcmlhYmxlIHF1ZSBxdWVyZW1vcyBwcmVkZWNpciB5IGRpZmVyZW50ZXMgdmFyaWFibGVzIHF1ZSBwdWVkZW4gZXhwbGljYXJsYS4KCkVuIFIgc2UgdXRpbGl6YSBsYSBmdW5jacOzbiAqKmxtKCkqKiBwYXJhIGNyZWFyIGVzdGUgdGlwbyBkZSBtb2RlbG9zLgoKVW5hIG1lZGlkYSBpbXBvcnRhbnRlIHBhcmEgZXZhbHVhciBlbCBtb2RlbG8gZXMgbGEgKipSLUN1YWRyYWRhKiosIHF1ZSB0b21hIHZhbG9yZXMgZW50cmUgMCB5IDEuIEVudHJlIG3DoXMgY2VyY2FubyBzZWEgZWwgdmFsb3IgYSAxLCBtYXlvciBjYW50aWRhZCBkZSBsYSB2YXJpYWNpw7NuIGRlIGxhIHZhcmlhYmxlIG9iamV0aXZvIGVzdMOhIGV4cGxpY2FuZG8gZWwgbW9kZWxvLgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6ZGFyayI+Q29udGV4dG88L3NwYW4+CgpVbmEgaW5tb2JpbGlhcmlhIGN1ZW50YSBjb24gaW5mb3JtYWNpw7NuIGRlIGRpZmVyZW50ZXMgY2FzYXMgcXVlIHlhIGZ1ZXJvbiB2ZW5kaWRhcy4gTGEgYmFzZSBpbmNsdXllIGNhcmFjdGVyw61zdGljYXMgY29tbyB0YW1hw7FvLCB1YmljYWNpw7NuLCBlc3RhY2lvbmFtaWVudG8geSBkaXN0YW5jaWEgYSBkaXN0aW50b3Mgc2VydmljaW9zLgoKRWwgb2JqZXRpdm8gZXMgZ2VuZXJhciB1biBtb2RlbG8gZGUgKipyZWdyZXNpw7NuIGxpbmVhbCoqIHF1ZSBwZXJtaXRhIGVzdGltYXIgZWwgcHJlY2lvIGRlIHVuYSBjYXNhIGEgcGFydGlyIGRlIHN1cyBjYXJhY3RlcsOtc3RpY2FzLgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6ZGFyayI+SW5zdGFsYXIgcGFxdWV0ZXMgeSBsbGFtYXIgbGlicmVyw61hczwvc3Bhbj4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIGluc3RhbGwucGFja2FnZXMoImNvcnJwbG90IikKCmxpYnJhcnkoY29ycnBsb3QpCmBgYAoKIyA8c3BhbiBzdHlsZT0iY29sb3I6ZGFyayI+SW1wb3J0YXIgbGEgYmFzZSBkZSBkYXRvczwvc3Bhbj4KCmBgYHtyfQpkZiA8LSByZWFkLmNzdigiL1VzZXJzL2thbWlsYWhjaGFpZGV6L0Rvd25sb2Fkcy9Ib3VzZVByaWNlRGF0YS5jc3YiKQpgYGAKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmRhcmsiPkVudGVuZGVyIGxhIGJhc2UgZGUgZGF0b3M8L3NwYW4+CgpgYGB7cn0KIyBSZXN1bWVuIGdlbmVyYWwgZGUgbG9zIGRhdG9zCnN1bW1hcnkoZGYpCgojIFRpcG8gZGUgdmFyaWFibGVzCnN0cihkZikKCiMgUHJpbWVyYXMgZmlsYXMKaGVhZChkZikKCiMgw5psdGltYXMgZmlsYXMKdGFpbChkZikKYGBgCgpMYSB2YXJpYWJsZSBxdWUgcXVlcmVtb3MgcHJlZGVjaXIgZXMgKipIb3VzZV9QcmljZSoqLCBtaWVudHJhcyBxdWUgbGFzIGRlbcOhcyB2YXJpYWJsZXMgY29udGllbmVuIGRpZmVyZW50ZXMgY2FyYWN0ZXLDrXN0aWNhcyBkZSBsYXMgY2FzYXMuCgpBbGd1bmFzIHZhcmlhYmxlcyBzb24gbnVtw6lyaWNhcyB5IG90cmFzIHNvbiBjYXRlZ8OzcmljYXMsIGNvbW8gYFBhcmtpbmdgIHkgYENpdHlfQ2F0ZWdvcnlgLgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6ZGFyayI+QW5hbGl6YXIgY29ycmVsYWNpb25lczwvc3Bhbj4KCmBgYHtyfQojIFNlbGVjY2lvbmFyIMO6bmljYW1lbnRlIGxhcyBjb2x1bW5hcyBudW3DqXJpY2FzCm51bWVyaWNhcyA8LSBkZltzYXBwbHkoZGYsIGlzLm51bWVyaWMpXQoKIyBDcmVhciBtYXRyaXogZGUgY29ycmVsYWNpb25lcwpjb3JyZWxhY2lvbiA8LSBjb3IoCiAgbnVtZXJpY2FzLAogIHVzZSA9ICJjb21wbGV0ZS5vYnMiCikKCiMgR3JhZmljYXIgY29ycmVsYWNpb25lcwpjb3JycGxvdChjb3JyZWxhY2lvbikKYGBgCgpMYSBncsOhZmljYSBwZXJtaXRlIGlkZW50aWZpY2FyIGN1w6FsZXMgdmFyaWFibGVzIHByZXNlbnRhbiB1bmEgcmVsYWNpw7NuIG3DoXMgZnVlcnRlLgoKTG9zIGPDrXJjdWxvcyBtw6FzIGdyYW5kZXMgcmVwcmVzZW50YW4gY29ycmVsYWNpb25lcyBtw6FzIGFsdGFzLiBFbiBlc3RlIGNhc28sIGxhcyB2YXJpYWJsZXMgcmVsYWNpb25hZGFzIGNvbiBlbCAqKnRhbWHDsW8gZGUgbGEgY2FzYSoqLCBjb21vIGBDYXJwZXRgIHkgYEJ1aWx0dXBgLCBwcmVzZW50YW4gdW5hIHJlbGFjacOzbiBpbXBvcnRhbnRlIGVudHJlIGVsbGFzIHkgdGFtYmnDqW4gY29uIGVsIHByZWNpby4KClBvciBvdHJvIGxhZG8sIGFsZ3VuYXMgdmFyaWFibGVzIGRlIGRpc3RhbmNpYSBwcmVzZW50YW4gdW5hIHJlbGFjacOzbiBtdWNobyBtZW5vciBjb24gYEhvdXNlX1ByaWNlYC4KCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmRhcmsiPkxpbXBpYXIgbGEgYmFzZSBkZSBkYXRvczwvc3Bhbj4KCmBgYHtyfQojIFJldmlzYXIgdmFsb3JlcyBmYWx0YW50ZXMgcG9yIGNvbHVtbmEKY29sU3Vtcyhpcy5uYShkZikpCgojIEVsaW1pbmFyIGZpbGFzIGNvbiBkYXRvcyBmYWx0YW50ZXMKZGYgPC0gbmEub21pdChkZikKYGBgCgpTZSBlbGltaW5hbiBsb3MgcmVnaXN0cm9zIHF1ZSBjb250aWVuZW4gdmFsb3JlcyBmYWx0YW50ZXMgcGFyYSB0cmFiYWphciBjb24gdW5hIGJhc2UgY29tcGxldGEgYW50ZXMgZGUgZ2VuZXJhciBlbCBtb2RlbG8uCgpDb21vIGxhIGNhbnRpZGFkIGRlIHJlZ2lzdHJvcyBlbGltaW5hZG9zIGVzIHBlcXVlw7FhIGNvbXBhcmFkYSBjb24gZWwgdG90YWwgZGUgb2JzZXJ2YWNpb25lcywgc2UgcHVlZGUgY29udGludWFyIGNvbiBlbCBhbsOhbGlzaXMgc2luIHBlcmRlciB1bmEgY2FudGlkYWQgaW1wb3J0YW50ZSBkZSBpbmZvcm1hY2nDs24uCgojIDxzcGFuIHN0eWxlPSJjb2xvcjpkYXJrIj5HZW5lcmFyIGVsIG1vZGVsbzwvc3Bhbj4KCmBgYHtyfQojIENyZWFyIG1vZGVsbyB1dGlsaXphbmRvIHRvZGFzIGxhcyB2YXJpYWJsZXMKcmVncmVzaW9uIDwtIGxtKAogIEhvdXNlX1ByaWNlIH4gLiwKICBkYXRhID0gZGYKKQoKc3VtbWFyeShyZWdyZXNpb24pCmBgYAoKRW4gbG9zIHJlc3VsdGFkb3MgZGVsIG1vZGVsbyBzZSBwdWVkZSBvYnNlcnZhciBxdcOpIHZhcmlhYmxlcyBwcmVzZW50YW4gdW5hIG1heW9yIHJlbGFjacOzbiBjb24gZWwgcHJlY2lvLgoKTGFzIHZhcmlhYmxlcyBjb24gbWF5b3Igc2lnbmlmaWNhbmNpYSBzb24gcHJpbmNpcGFsbWVudGU6CgoqIGBDYXJwZXRgCiogYEJ1aWx0dXBgCiogYFBhcmtpbmdgCiogYENpdHlfQ2F0ZWdvcnlgCgpNaWVudHJhcyBxdWUgdmFyaWFibGVzIGNvbW8gbGFzIGRpc3RhbmNpYXMgYWwgbWVyY2FkbywgaG9zcGl0YWwgbyB0YXhpIHByZXNlbnRhbiBtZW5vciBpbXBvcnRhbmNpYSBlbiBlbCBtb2RlbG8uCgpMYSAqKlItQ3VhZHJhZGEqKiBkZWwgbW9kZWxvIGVzIGFwcm94aW1hZGFtZW50ZSAqKjAuOTQzMyoqLCBsbyBxdWUgc2lnbmlmaWNhIHF1ZSBlbCBtb2RlbG8gZXhwbGljYSBjZXJjYSBkZWwgKio5NCUgZGUgbGEgdmFyaWFjacOzbiBkZWwgcHJlY2lvIGRlIGxhcyBjYXNhcyoqLgoKTGEgdmFyaWFibGUgYE9ic2VydmF0aW9uYCBubyByZXByZXNlbnRhIHVuYSBjYXJhY3RlcsOtc3RpY2EgcmVhbCBkZSB1bmEgY2FzYSwgeWEgcXVlIGZ1bmNpb25hIHNvbGFtZW50ZSBjb21vIHVuIGlkZW50aWZpY2Fkb3IgZGUgbG9zIHJlZ2lzdHJvcywgcG9yIGxvIHF1ZSBubyBlcyByZWNvbWVuZGFibGUgdXRpbGl6YXJsYSBwYXJhIHJlYWxpemFyIHByZWRpY2Npb25lcy4KCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmRhcmsiPkFqdXN0YXIgZWwgbW9kZWxvPC9zcGFuPgoKYGBge3J9CiMgQ3JlYXIgdW4gc2VndW5kbyBtb2RlbG8gw7puaWNhbWVudGUgY29uIGxhcyB2YXJpYWJsZXMgbcOhcyByZWxldmFudGVzCgpyZWdyZXNpb24yIDwtIGxtKAogIEhvdXNlX1ByaWNlIH4gQ2FycGV0ICsgQnVpbHR1cCArIFBhcmtpbmcgKyBDaXR5X0NhdGVnb3J5LAogIGRhdGEgPSBkZgopCgpzdW1tYXJ5KHJlZ3Jlc2lvbjIpCmBgYAoKRW4gZXN0ZSBzZWd1bmRvIG1vZGVsbyBzZSBlbGltaW5hcm9uIGxhcyB2YXJpYWJsZXMgcXVlIHRlbsOtYW4gbWVub3IgaW1wb3J0YW5jaWEuCgpMYSBSLUN1YWRyYWRhIHNlIG1hbnRpZW5lIGFwcm94aW1hZGFtZW50ZSBlbiAqKjAuOTQxMioqLCBwb3IgbG8gcXVlIGRpc21pbnV5ZSBtdXkgcG9jbyBjb21wYXJhZGEgY29uIGVsIG1vZGVsbyBhbnRlcmlvci4KCkVzdG8gaW5kaWNhIHF1ZSBzZSBwdWVkZSB1dGlsaXphciB1biBtb2RlbG8gbcOhcyBzZW5jaWxsbyBjb24gbWVub3MgdmFyaWFibGVzIHkgb2J0ZW5lciBwcsOhY3RpY2FtZW50ZSBlbCBtaXNtbyBuaXZlbCBkZSBleHBsaWNhY2nDs24uCgojIDxzcGFuIHN0eWxlPSJjb2xvcjpkYXJrIj5JbnRlcnByZXRhY2nDs24gZGUgdmFyaWFibGVzPC9zcGFuPgoKTG9zIHJlc3VsdGFkb3MgZGVsIG1vZGVsbyBwZXJtaXRlbiBvYnNlcnZhciBjw7NtbyBsYXMgZGlmZXJlbnRlcyBjYXJhY3RlcsOtc3RpY2FzIG1vZGlmaWNhbiBlbCBwcmVjaW8gZXN0aW1hZG8gZGUgdW5hIGNhc2EuCgoqICoqQ2FycGV0OioqIGFsIGF1bWVudGFyIGVsIHRhbWHDsW8gZGUgbGEgcHJvcGllZGFkLCB0YW1iacOpbiBhdW1lbnRhIGVsIHByZWNpbyBlc3RpbWFkby4KCiogKipCdWlsdHVwOioqIHNlIGVuY3VlbnRyYSBtdXkgcmVsYWNpb25hZGEgY29uIGBDYXJwZXRgLCBwb3IgbG8gcXVlIGFtYmFzIHZhcmlhYmxlcyBwdWVkZW4gaW5mbHVpciBlbnRyZSBzw60gZGVudHJvIGRlbCBtb2RlbG8uCgoqICoqQ2l0eV9DYXRlZ29yeToqKiBsYSB1YmljYWNpw7NuIHRpZW5lIHVuIGVmZWN0byBpbXBvcnRhbnRlIGVuIGVsIHByZWNpby4gTGFzIGNhc2FzIHViaWNhZGFzIGVuIGRpZmVyZW50ZXMgY2F0ZWdvcsOtYXMgZGUgY2l1ZGFkIHByZXNlbnRhbiBkaWZlcmVuY2lhcyBjb25zaWRlcmFibGVzIGVuIHN1IHZhbG9yLgoKKiAqKlBhcmtpbmc6KiogY29udGFyIG8gbm8gY29uIGRldGVybWluYWRvIHRpcG8gZGUgZXN0YWNpb25hbWllbnRvIHRhbWJpw6luIG1vZGlmaWNhIGVsIHByZWNpbyBlc3BlcmFkbyBkZSBsYSBwcm9waWVkYWQuCgojIDxzcGFuIHN0eWxlPSJjb2xvcjpkYXJrIj5HZW5lcmFyIFByZWRpY2Npb25lczwvc3Bhbj4KCmBgYHtyfQojIENyZWFyIGVqZW1wbG9zIGRlIGNhc2FzIG51ZXZhcyBwYXJhIGVzdGltYXIgc3UgcHJlY2lvCgpkYXRvc19udWV2b3MgPC0gZGF0YS5mcmFtZSgKICBDYXJwZXQgPSAxNTAwLAogIEJ1aWx0dXAgPSAxODAwLAogIFBhcmtpbmcgPSAiQ292ZXJlZCIsCiAgQ2l0eV9DYXRlZ29yeSA9IGMoCiAgICAiQ0FUIEEiLAogICAgIkNBVCBCIiwKICAgICJDQVQgQyIKICApCikKCnByZWRpY3QoCiAgcmVncmVzaW9uMiwKICBuZXdkYXRhID0gZGF0b3NfbnVldm9zCikKYGBgCgpFbiBlc3RlIGVqZW1wbG8gc2UgdXRpbGl6YSB1bmEgY2FzYSBjb24gbGFzIG1pc21hcyBjYXJhY3RlcsOtc3RpY2FzIGRlIHN1cGVyZmljaWUgeSBlc3RhY2lvbmFtaWVudG8sIHBlcm8gc2UgbW9kaWZpY2EgbGEgY2F0ZWdvcsOtYSBkZSBsYSBjaXVkYWQuCgpEZSBlc3RhIGZvcm1hIHBvZGVtb3Mgb2JzZXJ2YXIgY8OzbW8gKipsYSB1YmljYWNpw7NuIHB1ZWRlIGNhbWJpYXIgY29uc2lkZXJhYmxlbWVudGUgZWwgcHJlY2lvIGVzdGltYWRvKiosIGF1bnF1ZSBlbCByZXN0byBkZSBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZSBsYSBwcm9waWVkYWQgcGVybWFuZXpjYW4gaWd1YWxlcy4KCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmRhcmsiPkNvbmNsdXNpb25lczwvc3Bhbj4KCkEgcGFydGlyIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgc2Ugb2JzZXJ2YSBxdWUgbGFzIHZhcmlhYmxlcyBxdWUgdGllbmVuIHVuYSBtYXlvciByZWxhY2nDs24gY29uIGVsIHByZWNpbyBkZSB1bmEgY2FzYSBzb24gcHJpbmNpcGFsbWVudGUgKipzdSB0YW1hw7FvLCBsYSB1YmljYWNpw7NuIHkgZWwgZXN0YWNpb25hbWllbnRvKiouCgpFbCBwcmltZXIgbW9kZWxvIGV4cGxpY8OzIGFwcm94aW1hZGFtZW50ZSBlbCAqKjk0JSBkZSBsYSB2YXJpYWNpw7NuIGRlbCBwcmVjaW8qKiwgcGVybyBhbCBlbGltaW5hciB2YXJpYWJsZXMgcG9jbyByZWxldmFudGVzIGVsIHJlc3VsdGFkbyBwcsOhY3RpY2FtZW50ZSBubyBjYW1iacOzLgoKUG9yIGVzdGEgcmF6w7NuLCBlbCBzZWd1bmRvIG1vZGVsbyBlcyBtw6FzIGNvbnZlbmllbnRlLCB5YSBxdWUgdXRpbGl6YSBtZW5vcyB2YXJpYWJsZXMgeSBtYW50aWVuZSB1biBuaXZlbCBkZSBleHBsaWNhY2nDs24gbXV5IHNpbWlsYXIuCgpGaW5hbG1lbnRlLCBsYXMgcHJlZGljY2lvbmVzIG11ZXN0cmFuIHF1ZSB1bmEgY2FzYSBjb24gbGFzIG1pc21hcyBjYXJhY3RlcsOtc3RpY2FzIHB1ZWRlIHRlbmVyIHByZWNpb3MgZGlmZXJlbnRlcyBkZXBlbmRpZW5kbyBwcmluY2lwYWxtZW50ZSBkZSBsYSAqKmNhdGVnb3LDrWEgZGUgbGEgY2l1ZGFkIGRvbmRlIHNlIGVuY3VlbnRyZSoqLg==