Data & Analytics

Vamos a utilizar el Dataset “World cities” para explorar los datos con la siguiente hipótesis:

“Dado que el Reino Unido (UK) fue uno de los principales países que colonizaron los Estados Unidos (USA), y UK se encuentra en el lado este de USA, entonces hay más ciudades/poblados con nombres de ciudades de UK en la costa este de USA en comparación a la costa oeste”

datos <- "https://raw.githubusercontent.com/duberc/R-Python-SQL-en-R-Markdown/main/wordlcities.csv"
datos = read.csv(datos,header = TRUE,sep = ",")

estados <- "https://raw.githubusercontent.com/duberc/R-Python-SQL-en-R-Markdown/main/estadosUS.csv"
estados = read.csv(estados,header = TRUE,sep = ",")
# revisamos los nombres y tipos de datos
summary(datos)
   Country              City            AccentCity           Region            Population         Latitude       Longitude       
 Length:5846        Length:5846        Length:5846        Length:5846        Min.   :    216   Min.   :17.97   Min.   :-165.406  
 Class :character   Class :character   Class :character   Class :character   1st Qu.:  11725   1st Qu.:36.20   1st Qu.: -92.399  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :  19742   Median :40.85   Median : -80.084  
                                                                             Mean   :  44974   Mean   :41.24   Mean   : -64.978  
                                                                             3rd Qu.:  37844   3rd Qu.:44.80   3rd Qu.:  -5.049  
                                                                             Max.   :8107916   Max.   :71.29   Max.   :  36.000  
#resumen con estadistica descriptiva
glimpse(datos)
Rows: 5,846
Columns: 7
$ Country    <chr> "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es", "es...
$ City       <chr> "abaran", "a coruna", "adeje", "adra", "aguilar de la frontera", "aguilas", "aguimes", "albacete", "albal", "albolote", "al...
$ AccentCity <chr> "Abarán", "A Coruña", "Adeje", "Adra", "Aguilar de la Frontera", "Ã\u0081guilas", "Agüimes", "Albacete", "Albal", "Albol...
$ Region     <chr> "31", "58", "53", "51", "51", "31", "53", "54", "60", "51", "60", "51", "51", "29", "51", "52", "31", "51", "54", "29", "29...
$ Population <dbl> 13850, 236010, 22245, 23988, 13557, 30739, 21908, 155083, 13507, 14355, 19431, 10688, 58934, 195152, 21377, 13884, 37969, 1...
$ Latitude   <dbl> 38.20551, 43.36661, 28.11984, 36.74740, 37.51445, 37.40598, 27.90505, 38.99585, 39.40000, 37.23006, 39.50000, 37.38856, 37....
$ Longitude  <dbl> -1.399072, -8.406812, -16.725581, -3.015989, -4.656173, -1.585300, -15.445395, -1.857773, -0.416667, -3.655454, -0.350000, ...
# inspeccion rapida de la data
introduce(datos)
# revisamos como esta compuesto el dataset y vemos que no existen N/As
plot_intro(datos)

# lo comprobamos
plot_missing(datos)

Python

# Pasar de R Dataframe a Python
df = r.datos

# eliminar NA's
df = df.dropna()

# eliminar duplicados
df = df.drop_duplicates()

R

# recuperar dataframe de python y pasarlo a R
datos = py$df

# comprobamos que eliminamos lo NA's
plot_missing(datos)

# Pasar un dataframe a BD para lectura SQL
copy_to(db, datos, overwrite = TRUE)

SQL

-- crear una salida SQL en el reporte para encontrar el nombre de paises segun ciudades de interes
select distinct Country, City
from datos where (City like "%Manchester%" or City like "%Texas%")
and Population > 0

R

# Creamos una columna nueva validando que el nombre de la ciudad en US exista en GB.
us$Comparar <- as.factor(ifelse(us$City %in% gb$City, 'yes','no'))
# Pasar un dataframe a BD para lectura SQL
copy_to(db, us,verwrite = TRUE)
copy_to(db, gb,verwrite = TRUE)
# Listado de tablas en la BD
dbListTables(db)
[1] "datos"        "gb"           "sqlite_stat1" "sqlite_stat4" "us"          
# crear un parámetro para filtro en SQL
pop = 0

# pasar el nombre de una columna como parametro
filtro <- glue::glue_sql("Region", .con = db)

# crear 2 o mas parámetros para el uso de "IN"
ciudades <- c("New York", "Los Angeles", "Miami", "Anchorage")

# pasar la variable ciudades a class SQL
ciudades = glue::glue_sql("{ciudades*}", .con = db)

# Veamos
ciudades
<SQL> 'New York', 'Los Angeles', 'Miami', 'Anchorage'

SQL

-- ejecutar sentencia para usar los 3 par攼㸱metros
select distinct * from us where ?filtro = "FL" and AccentCity in (?ciudades) and Population >?pop  order by Population desc limit 10
select * from datos where Country = "es" and AccentCity = "Adeje"
-- ejemplo delete
Delete from datos where Country = "es" and AccentCity = "Adeje"
-- ejemplo inser into
insert into datos values ("es","adeje","Adeje","53","22245","28.11984","-16.72558")
-- validar e insert
select * from datos where Country = "es" and AccentCity = "Adeje"
-- buscar todas la ciudades de us que tengan nombres de ciudades de gb
select distinct * from us where City in (select City from gb) and Population >?pop  order by Population desc
-- Validar en US si una Ciudad - Region se repite
select count(*) as cantidad, df1.City, df1.Region, df1.Population, df1.Country
from us as df1  where df1.city in (select City from gb) and df1.Population >0
group by df1.City, df1.Region, df1.Population having cantidad>1 order by df1.Population desc 
-- Ciudades mas importante Costa Este que no tengan nombre exacto en gb
select Region, City, Population
from us where City not in (select Distinct City from gb) and Location = 'Costa Este'
order by Population desc limit 10
-- Ciudades mas importante Costa Oeste que no tengan nombre exacto en gb
select Region, City, Population
from us where City not in (select Distinct City from gb) and Location = 'Costa Oeste'
order by Population desc limit 10
-- Ciudades mas importante de ambas costas que no existen explicitamente en gb
select City, Population, Location, Comparar
from us where Comparar ='no' and Location like "Costa%"
order by Population desc limit 20
-- Ciudades mas importante de ambas costas que no existen explicitamente en gb
select City, Population, Location, Comparar
from us where Comparar = 'no' and Location like "Costa%"
order by Population desc limit 20
# agregar el df test a la bd
copy_to(db, test, overwrite = TRUE)
-- validar la tabla
select * from test
-- hacer un split de las ciudades sin resultados en gb
select SUBSTRING(City, CHARINDEX(' ', City) + 1, length(City) - CHARINDEX(' ', City)) as City
from test 
union
select SUBSTRING(City, 1, CHARINDEX(' ', City) - 1) as City
from test
-- guardar como table
select SUBSTRING(City, CHARINDEX(' ', City) + 1, length(City) - CHARINDEX(' ', City)) as City
from test
union
select SUBSTRING(City, 1, CHARINDEX(' ', City) - 1) as City
from test
# agregar df test1 a la tabla
copy_to(db, test1)
-- buscar por string en gb para ver coincidencias
select City,Population from gb where City in (select City from test1) order by Population
-- Dado que New York viene de "York" en GB actualizamos el registro
update us set Comparar = "yes"
where City = "new york"
-- validamos
select * from us
where City = "new york"
-- crear una salida SQL como dataframe R usando "output.var"
SELECT * FROM us where Comparar = "yes" order by Population desc

R

Tabla US
Lugar Ctd
Costa Este 103
Costa Oeste 12
Medio Oeste 33
Noroeste 1
Oeste 6
Sureste 13
Suroeste 9

Python

Ver el df en Python

    Region Country        City  ...                  Name     Location  Comparar
0       NY      us    new york  ...              New York   Costa Este       yes
1       TX      us     houston  ...                 Texas     Suroeste       yes
2       MA      us      boston  ...         Massachusetts   Costa Este       yes
3       DC      us  washington  ...  District of Columbia   Costa Este       yes
4       NE      us     lincoln  ...              Nebraska  Medio Oeste       yes
..     ...     ...         ...  ...                   ...          ...       ...
172     NH      us     chester  ...         New Hampshire   Costa Este       yes
173     VT      us     newport  ...               Vermont   Costa Este       yes
174     ID      us     preston  ...                 Idaho     Noroeste       yes
175     NH      us   wakefield  ...         New Hampshire   Costa Este       yes
176     VT      us  manchester  ...               Vermont   Costa Este       yes

[177 rows x 10 columns]

describe() nos permite conocer datos generales del dataframe

         Population    Latitude   Longitude
count  1.770000e+02  177.000000  177.000000
mean   9.617597e+04   40.142351  -83.318123
std    6.276584e+05    3.686483   14.688758
min    2.160000e+02   26.244167 -124.052222
25%    1.169900e+04   38.533889  -89.519167
50%    1.943200e+04   41.159444  -77.441944
75%    3.534900e+04   42.595000  -71.688611
max    8.107916e+06   46.975556  -68.505556

Ejemplo de grafico en Python

[<matplotlib.lines.Line2D object at 0x000002749A4963C8>]

R

summary(py$df)
    Region            Country              City            AccentCity          Population         Latitude       Longitude      
 Length:177         Length:177         Length:177         Length:177         Min.   :    216   Min.   :26.24   Min.   :-124.05  
 Class :character   Class :character   Class :character   Class :character   1st Qu.:  11699   1st Qu.:38.53   1st Qu.: -89.52  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :  19432   Median :41.16   Median : -77.44  
                                                                             Mean   :  96176   Mean   :40.14   Mean   : -83.32  
                                                                             3rd Qu.:  35349   3rd Qu.:42.59   3rd Qu.: -71.69  
                                                                             Max.   :8107916   Max.   :46.98   Max.   : -68.51  
     Name             Location           Comparar        
 Length:177         Length:177         Length:177        
 Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character  
                                                         
                                                         
                                                         

Ver las ciudades en un mapa

# ver las ciudades en un mapa
us %>%
  filter(Comparar=="yes") %>%
  select(AccentCity,Latitude, Longitude) %>%
  leaflet( width = 900) %>%
  addTiles()%>%
  addMarkers(clusterOptions = markerClusterOptions(),popup = ~htmlEscape(AccentCity))

NA
LS0tDQp0aXRsZTogIlIgKyBQeXRob24gKyBTUUwiDQphdXRob3I6ICJEdWJlciBDYWRyYXpjbyINCmRhdGU6ICIyMS8wNi8yMDIxIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIExpYnJlcmlhcw0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KERhdGFFeHBsb3JlcikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShyZXRpY3VsYXRlKQ0KbGlicmFyeShEQkkpDQpsaWJyYXJ5KGxlYWZsZXQpDQpsaWJyYXJ5KGh0bWx0b29scykNCiMgZGVjbGFyYXIgbGEgREIgU1FMIGVuIG1lbW9yaWENCmRiID0gZGJDb25uZWN0KFJTUUxpdGU6OlNRTGl0ZSgpLCBkYm5hbWUgPSAic3FsLnNxbGl0ZSIpDQpgYGANCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQoNCiMgaW5pY2lhbGl6YXIgUHl0aG9uDQprbml0cjo6a25pdF9lbmdpbmVzJHNldChweXRob24gPSByZXRpY3VsYXRlOjplbmdfcHl0aG9uKQ0KDQojIGluaWNpYWxpemFyIFNRTCB5IGxhIGNvbmV4acOzbiBhIGxhIEJEDQprbml0cjo6b3B0c19jaHVuayRzZXQoY29ubmVjdGlvbiA9ICJkYiIpDQoNCmBgYA0KDQojIERhdGEgJiBBbmFseXRpY3MNCg0KDQpWYW1vcyBhIHV0aWxpemFyIGVsIERhdGFzZXQgIldvcmxkIGNpdGllcyIgcGFyYSBleHBsb3JhciBsb3MgZGF0b3MgY29uIGxhIHNpZ3VpZW50ZSBoaXDDs3Rlc2lzOg0KDQrigJxEYWRvIHF1ZSBlbCBSZWlubyBVbmlkbyAoVUspIGZ1ZSB1bm8gZGUgbG9zIHByaW5jaXBhbGVzIHBhw61zZXMgcXVlIGNvbG9uaXphcm9uIGxvcyBFc3RhZG9zIFVuaWRvcyAoVVNBKSwgeSBVSyBzZSBlbmN1ZW50cmEgZW4gZWwgbGFkbyBlc3RlIGRlIFVTQSwgZW50b25jZXMgaGF5IG3DoXMgY2l1ZGFkZXMvcG9ibGFkb3MgY29uIG5vbWJyZXMgZGUgY2l1ZGFkZXMgZGUgVUsgZW4gbGEgY29zdGEgZXN0ZSBkZSBVU0EgZW4gY29tcGFyYWNpw7NuIGEgbGEgY29zdGEgb2VzdGXigJ0NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkYXRvcyA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2R1YmVyYy9SLVB5dGhvbi1TUUwtZW4tUi1NYXJrZG93bi9tYWluL3dvcmRsY2l0aWVzLmNzdiINCmRhdG9zID0gcmVhZC5jc3YoZGF0b3MsaGVhZGVyID0gVFJVRSxzZXAgPSAiLCIpDQoNCmVzdGFkb3MgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9kdWJlcmMvUi1QeXRob24tU1FMLWVuLVItTWFya2Rvd24vbWFpbi9lc3RhZG9zVVMuY3N2Ig0KZXN0YWRvcyA9IHJlYWQuY3N2KGVzdGFkb3MsaGVhZGVyID0gVFJVRSxzZXAgPSAiLCIpDQpgYGANCg0KYGBge3IgfQ0KIyByZXZpc2Ftb3MgbG9zIG5vbWJyZXMgeSB0aXBvcyBkZSBkYXRvcw0Kc3VtbWFyeShkYXRvcykNCiNyZXN1bWVuIGNvbiBlc3RhZGlzdGljYSBkZXNjcmlwdGl2YQ0KZ2xpbXBzZShkYXRvcykNCiMgaW5zcGVjY2lvbiByYXBpZGEgZGUgbGEgZGF0YQ0KaW50cm9kdWNlKGRhdG9zKQ0KIyByZXZpc2Ftb3MgY29tbyBlc3RhIGNvbXB1ZXN0byBlbCBkYXRhc2V0IHkgdmVtb3MgcXVlIG5vIGV4aXN0ZW4gTi9Bcw0KcGxvdF9pbnRybyhkYXRvcykNCiMgbG8gY29tcHJvYmFtb3MNCnBsb3RfbWlzc2luZyhkYXRvcykNCmBgYA0KIyBQeXRob24NCmBgYHtweXRob24gd2FybmluZz1GQUxTRX0NCiMgUGFzYXIgZGUgUiBEYXRhZnJhbWUgYSBQeXRob24NCmRmID0gci5kYXRvcw0KDQojIGVsaW1pbmFyIE5BJ3MNCmRmID0gZGYuZHJvcG5hKCkNCg0KIyBlbGltaW5hciBkdXBsaWNhZG9zDQpkZiA9IGRmLmRyb3BfZHVwbGljYXRlcygpDQpgYGANCiMgUg0KYGBge3J9DQojIHJlY3VwZXJhciBkYXRhZnJhbWUgZGUgcHl0aG9uIHkgcGFzYXJsbyBhIFINCmRhdG9zID0gcHkkZGYNCg0KIyBjb21wcm9iYW1vcyBxdWUgZWxpbWluYW1vcyBsbyBOQSdzDQpwbG90X21pc3NpbmcoZGF0b3MpDQpgYGANCg0KYGBge3J9DQojIFBhc2FyIHVuIGRhdGFmcmFtZSBhIEJEIHBhcmEgbGVjdHVyYSBTUUwNCmNvcHlfdG8oZGIsIGRhdG9zLCBvdmVyd3JpdGUgPSBUUlVFKQ0KYGBgDQoNCiMgU1FMDQpgYGB7c3FsfQ0KLS0gY3JlYXIgdW5hIHNhbGlkYSBTUUwgZW4gZWwgcmVwb3J0ZSBwYXJhIGVuY29udHJhciBlbCBub21icmUgZGUgcGFpc2VzIHNlZ3VuIGNpdWRhZGVzIGRlIGludGVyZXMNCnNlbGVjdCBkaXN0aW5jdCBDb3VudHJ5LCBDaXR5DQpmcm9tIGRhdG9zIHdoZXJlIChDaXR5IGxpa2UgIiVNYW5jaGVzdGVyJSIgb3IgQ2l0eSBsaWtlICIlVGV4YXMlIikNCmFuZCBQb3B1bGF0aW9uID4gMA0KYGBgDQoNCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsZmlnLmFsaWduPSJjZW50ZXIiLGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTV9DQoNCiMgZmlsdHJhbW9zIFVTIHkgR0INCnVzID0gZmlsdGVyKGRhdG9zLCBDb3VudHJ5ID09ICd1cycpDQpnYiA9IGZpbHRlcihkYXRvcywgQ291bnRyeSA9PSAnZ2InKQ0KDQojIGdlbmVyYW1vcyBMb2NhdGlvbg0KdXMgPSBtZXJnZSh1cywgZXN0YWRvcywgYnkgPSBjKCJSZWdpb24iKSkNCg0KI3dyaXRlLmNzdihkYXRvcywgZmlsZT0iZGF0b3MuY3N2IikNCiN3cml0ZS5jc3YoZ2IsIGZpbGU9ImdiLmNzdiIpDQpgYGANCg0KIyBSDQpgYGB7cn0NCiMgQ3JlYW1vcyB1bmEgY29sdW1uYSBudWV2YSB2YWxpZGFuZG8gcXVlIGVsIG5vbWJyZSBkZSBsYSBjaXVkYWQgZW4gVVMgZXhpc3RhIGVuIEdCLg0KdXMkQ29tcGFyYXIgPC0gYXMuZmFjdG9yKGlmZWxzZSh1cyRDaXR5ICVpbiUgZ2IkQ2l0eSwgJ3llcycsJ25vJykpDQpgYGANCg0KYGBge3J9DQojIFBhc2FyIHVuIGRhdGFmcmFtZSBhIEJEIHBhcmEgbGVjdHVyYSBTUUwNCmNvcHlfdG8oZGIsIHVzLHZlcndyaXRlID0gVFJVRSkNCmNvcHlfdG8oZGIsIGdiLHZlcndyaXRlID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCiMgTGlzdGFkbyBkZSB0YWJsYXMgZW4gbGEgQkQNCmRiTGlzdFRhYmxlcyhkYikNCmBgYA0KDQpgYGB7cn0NCiMgY3JlYXIgdW4gcGFyw6FtZXRybyBwYXJhIGZpbHRybyBlbiBTUUwNCnBvcCA9IDANCg0KIyBwYXNhciBlbCBub21icmUgZGUgdW5hIGNvbHVtbmEgY29tbyBwYXJhbWV0cm8NCmZpbHRybyA8LSBnbHVlOjpnbHVlX3NxbCgiUmVnaW9uIiwgLmNvbiA9IGRiKQ0KDQojIGNyZWFyIDIgbyBtYXMgcGFyw6FtZXRyb3MgcGFyYSBlbCB1c28gZGUgIklOIg0KY2l1ZGFkZXMgPC0gYygiTmV3IFlvcmsiLCAiTG9zIEFuZ2VsZXMiLCAiTWlhbWkiLCAiQW5jaG9yYWdlIikNCg0KIyBwYXNhciBsYSB2YXJpYWJsZSBjaXVkYWRlcyBhIGNsYXNzIFNRTA0KY2l1ZGFkZXMgPSBnbHVlOjpnbHVlX3NxbCgie2NpdWRhZGVzKn0iLCAuY29uID0gZGIpDQoNCiMgVmVhbW9zDQpjaXVkYWRlcw0KYGBgDQoNCiMgU1FMDQpgYGB7c3FsfQ0KLS0gZWplY3V0YXIgc2VudGVuY2lhIHBhcmEgdXNhciBsb3MgMyBwYXLDoW1ldHJvcw0Kc2VsZWN0IGRpc3RpbmN0ICogZnJvbSB1cyB3aGVyZSA/ZmlsdHJvID0gIkZMIiBhbmQgQWNjZW50Q2l0eSBpbiAoP2NpdWRhZGVzKSBhbmQgUG9wdWxhdGlvbiA+P3BvcCAgb3JkZXIgYnkgUG9wdWxhdGlvbiBkZXNjIGxpbWl0IDEwDQpgYGANCmBgYHtzcWx9DQpzZWxlY3QgKiBmcm9tIGRhdG9zIHdoZXJlIENvdW50cnkgPSAiZXMiIGFuZCBBY2NlbnRDaXR5ID0gIkFkZWplIg0KYGBgDQpgYGB7c3FsfQ0KLS0gZWplbXBsbyBkZWxldGUNCkRlbGV0ZSBmcm9tIGRhdG9zIHdoZXJlIENvdW50cnkgPSAiZXMiIGFuZCBBY2NlbnRDaXR5ID0gIkFkZWplIg0KYGBgDQoNCmBgYHtzcWx9DQotLSBlamVtcGxvIGluc2VyIGludG8NCmluc2VydCBpbnRvIGRhdG9zIHZhbHVlcyAoImVzIiwiYWRlamUiLCJBZGVqZSIsIjUzIiwiMjIyNDUiLCIyOC4xMTk4NCIsIi0xNi43MjU1OCIpDQpgYGANCg0KYGBge3NxbH0NCi0tIHZhbGlkYXIgZSBpbnNlcnQNCnNlbGVjdCAqIGZyb20gZGF0b3Mgd2hlcmUgQ291bnRyeSA9ICJlcyIgYW5kIEFjY2VudENpdHkgPSAiQWRlamUiDQpgYGANCg0KYGBge3NxbH0NCi0tIGJ1c2NhciB0b2RhcyBsYSBjaXVkYWRlcyBkZSB1cyBxdWUgdGVuZ2FuIG5vbWJyZXMgZGUgY2l1ZGFkZXMgZGUgZ2INCnNlbGVjdCBkaXN0aW5jdCAqIGZyb20gdXMgd2hlcmUgQ2l0eSBpbiAoc2VsZWN0IENpdHkgZnJvbSBnYikgYW5kIFBvcHVsYXRpb24gPj9wb3AgIG9yZGVyIGJ5IFBvcHVsYXRpb24gZGVzYw0KYGBgDQoNCmBgYHtzcWx9DQotLSBWYWxpZGFyIGVuIFVTIHNpIHVuYSBDaXVkYWQgLSBSZWdpb24gc2UgcmVwaXRlDQpzZWxlY3QgY291bnQoKikgYXMgY2FudGlkYWQsIGRmMS5DaXR5LCBkZjEuUmVnaW9uLCBkZjEuUG9wdWxhdGlvbiwgZGYxLkNvdW50cnkNCmZyb20gdXMgYXMgZGYxICB3aGVyZSBkZjEuY2l0eSBpbiAoc2VsZWN0IENpdHkgZnJvbSBnYikgYW5kIGRmMS5Qb3B1bGF0aW9uID4wDQpncm91cCBieSBkZjEuQ2l0eSwgZGYxLlJlZ2lvbiwgZGYxLlBvcHVsYXRpb24gaGF2aW5nIGNhbnRpZGFkPjEgb3JkZXIgYnkgZGYxLlBvcHVsYXRpb24gZGVzYyANCmBgYA0KDQpgYGB7c3FsfQ0KLS0gQ2l1ZGFkZXMgbWFzIGltcG9ydGFudGUgQ29zdGEgRXN0ZSBxdWUgbm8gdGVuZ2FuIG5vbWJyZSBleGFjdG8gZW4gZ2INCnNlbGVjdCBSZWdpb24sIENpdHksIFBvcHVsYXRpb24NCmZyb20gdXMgd2hlcmUgQ2l0eSBub3QgaW4gKHNlbGVjdCBEaXN0aW5jdCBDaXR5IGZyb20gZ2IpIGFuZCBMb2NhdGlvbiA9ICdDb3N0YSBFc3RlJw0Kb3JkZXIgYnkgUG9wdWxhdGlvbiBkZXNjIGxpbWl0IDEwDQpgYGANCg0KYGBge3NxbH0NCi0tIENpdWRhZGVzIG1hcyBpbXBvcnRhbnRlIENvc3RhIE9lc3RlIHF1ZSBubyB0ZW5nYW4gbm9tYnJlIGV4YWN0byBlbiBnYg0Kc2VsZWN0IFJlZ2lvbiwgQ2l0eSwgUG9wdWxhdGlvbg0KZnJvbSB1cyB3aGVyZSBDaXR5IG5vdCBpbiAoc2VsZWN0IERpc3RpbmN0IENpdHkgZnJvbSBnYikgYW5kIExvY2F0aW9uID0gJ0Nvc3RhIE9lc3RlJw0Kb3JkZXIgYnkgUG9wdWxhdGlvbiBkZXNjIGxpbWl0IDEwDQpgYGANCmBgYHtzcWx9DQotLSBDaXVkYWRlcyBtYXMgaW1wb3J0YW50ZSBkZSBhbWJhcyBjb3N0YXMgcXVlIG5vIGV4aXN0ZW4gZXhwbGljaXRhbWVudGUgZW4gZ2INCnNlbGVjdCBDaXR5LCBQb3B1bGF0aW9uLCBMb2NhdGlvbiwgQ29tcGFyYXINCmZyb20gdXMgd2hlcmUgQ29tcGFyYXIgPSdubycgYW5kIExvY2F0aW9uIGxpa2UgIkNvc3RhJSINCm9yZGVyIGJ5IFBvcHVsYXRpb24gZGVzYyBsaW1pdCAyMA0KYGBgDQoNCmBgYHtzcWwgb3V0cHV0LnZhcj0idGVzdCJ9DQotLSBDaXVkYWRlcyBtYXMgaW1wb3J0YW50ZSBkZSBhbWJhcyBjb3N0YXMgcXVlIG5vIGV4aXN0ZW4gZXhwbGljaXRhbWVudGUgZW4gZ2INCnNlbGVjdCBDaXR5LCBQb3B1bGF0aW9uLCBMb2NhdGlvbiwgQ29tcGFyYXINCmZyb20gdXMgd2hlcmUgQ29tcGFyYXIgPSAnbm8nIGFuZCBMb2NhdGlvbiBsaWtlICJDb3N0YSUiDQpvcmRlciBieSBQb3B1bGF0aW9uIGRlc2MgbGltaXQgMjANCmBgYA0KDQpgYGB7cn0NCiMgYWdyZWdhciBlbCBkZiB0ZXN0IGEgbGEgYmQNCmNvcHlfdG8oZGIsIHRlc3QsIG92ZXJ3cml0ZSA9IFRSVUUpDQpgYGANCg0KDQpgYGB7c3FsIH0NCi0tIHZhbGlkYXIgbGEgdGFibGENCnNlbGVjdCAqIGZyb20gdGVzdA0KYGBgDQoNCg0KYGBge3NxbH0NCi0tIGhhY2VyIHVuIHNwbGl0IGRlIGxhcyBjaXVkYWRlcyBzaW4gcmVzdWx0YWRvcyBlbiBnYg0Kc2VsZWN0IFNVQlNUUklORyhDaXR5LCBDSEFSSU5ERVgoJyAnLCBDaXR5KSArIDEsIGxlbmd0aChDaXR5KSAtIENIQVJJTkRFWCgnICcsIENpdHkpKSBhcyBDaXR5DQpmcm9tIHRlc3QgDQp1bmlvbg0Kc2VsZWN0IFNVQlNUUklORyhDaXR5LCAxLCBDSEFSSU5ERVgoJyAnLCBDaXR5KSAtIDEpIGFzIENpdHkNCmZyb20gdGVzdA0KYGBgDQoNCmBgYHtzcWwgb3V0cHV0LnZhcj0idGVzdDEifQ0KLS0gZ3VhcmRhciBjb21vIHRhYmxlDQpzZWxlY3QgU1VCU1RSSU5HKENpdHksIENIQVJJTkRFWCgnICcsIENpdHkpICsgMSwgbGVuZ3RoKENpdHkpIC0gQ0hBUklOREVYKCcgJywgQ2l0eSkpIGFzIENpdHkNCmZyb20gdGVzdA0KdW5pb24NCnNlbGVjdCBTVUJTVFJJTkcoQ2l0eSwgMSwgQ0hBUklOREVYKCcgJywgQ2l0eSkgLSAxKSBhcyBDaXR5DQpmcm9tIHRlc3QNCmBgYA0KDQoNCmBgYHtyfQ0KIyBhZ3JlZ2FyIGRmIHRlc3QxIGEgbGEgdGFibGENCmNvcHlfdG8oZGIsIHRlc3QxKQ0KYGBgDQoNCmBgYHtzcWx9DQotLSBidXNjYXIgcG9yIHN0cmluZyBlbiBnYiBwYXJhIHZlciBjb2luY2lkZW5jaWFzDQpzZWxlY3QgQ2l0eSxQb3B1bGF0aW9uIGZyb20gZ2Igd2hlcmUgQ2l0eSBpbiAoc2VsZWN0IENpdHkgZnJvbSB0ZXN0MSkgb3JkZXIgYnkgUG9wdWxhdGlvbg0KYGBgDQpgYGB7c3FsfQ0KLS0gRGFkbyBxdWUgTmV3IFlvcmsgdmllbmUgZGUgIllvcmsiIGVuIEdCIGFjdHVhbGl6YW1vcyBlbCByZWdpc3Rybw0KdXBkYXRlIHVzIHNldCBDb21wYXJhciA9ICJ5ZXMiDQp3aGVyZSBDaXR5ID0gIm5ldyB5b3JrIg0KDQpgYGANCmBgYHtzcWx9DQotLSB2YWxpZGFtb3MNCnNlbGVjdCAqIGZyb20gdXMNCndoZXJlIENpdHkgPSAibmV3IHlvcmsiDQpgYGANCg0KYGBge3NxbCBvdXRwdXQudmFyPSJmaW5hbCJ9DQotLSBjcmVhciB1bmEgc2FsaWRhIFNRTCBjb21vIGRhdGFmcmFtZSBSIHVzYW5kbyAib3V0cHV0LnZhciINClNFTEVDVCAqIEZST00gdXMgd2hlcmUgQ29tcGFyYXIgPSAieWVzIiBvcmRlciBieSBQb3B1bGF0aW9uIGRlc2MNCmBgYA0KIyBSDQpgYGB7ciBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyB2ZXIgbGEgc2FsaWRhIGRlIFNRTCBjb21vIGRhdGFmcmFtZQ0KZmluYWwNCmBgYA0KDQpgYGB7ciBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Ka25pdHI6OmthYmxlKA0KICB0YWJsZShmaW5hbCRMb2NhdGlvbiksIA0KICBjYXB0aW9uID0gIlRhYmxhIFVTIiwNCiAgY29sLm5hbWVzPSBjKCJMdWdhciIsIkN0ZCIpKQ0KYGBgDQoNCmBgYHtweXRob24gZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSxmaWcuYWxpZ249ImNlbnRlciIsZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0NCiMgTGliZXJpYQ0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0DQoNCiMgY2FyZ2FyIHZhcmlhYmxlcyBkZSBSIGVuIGVudG9ybm8gUHl0aG9uDQojci51cyAjIGxsYW1hbW9zIGVsIGRhdGFmcmFtZSB1cw0KDQojIGNhcmdhciB2YXJpYWJsZXMgZGUgUHl0aG9uIGVuIGVudG9ybm8gUg0KIyBweSRkZiBsbGFtYW1vcyBlbCBkYXRhZnJhbWUgZGYNCmBgYA0KDQpgYGB7ciBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBJbnN0YWxhciBsaWJyZXJpYXMgZGUgcHl0aG9uIGVuIFItcmV0aWN1bGF0ZQ0KI3B5X2luc3RhbGwoIm1hdHBsb3RsaWIiKQ0KDQpgYGANCiMgUHl0aG9uDQpWZXIgZWwgZGYgZW4gUHl0aG9uDQpgYGB7cHl0aG9ufQ0KIyBQYXNhciBkZSBSIERhdGFmcmFtZSBhIFB5dGhvbg0KZGYgPSByLmZpbmFsDQpkZg0KYGBgDQpkZXNjcmliZSgpIG5vcyBwZXJtaXRlIGNvbm9jZXIgZGF0b3MgZ2VuZXJhbGVzIGRlbCBkYXRhZnJhbWUNCmBgYHtweXRob259DQojIGRlc2NyaWJlKCkgbm9zIHBlcm1pdGUgY29ub2NlciBkYXRvcyBnZW5lcmFsZXMgZGVsIGRhdGFmcmFtZQ0KcHJpbnQoZGYuZGVzY3JpYmUoKSkNCg0KYGBgDQoNCkVqZW1wbG8gZGUgZ3JhZmljbyBlbiBQeXRob24NCmBgYHtweXRob259DQp4ID0gbnAuYXJhbmdlKDEsIDExKQ0KeSA9IG5wLnJhbmRvbS5yYW5kaW50KDEwLCBzaXplPTEwKQ0KcGx0LnBsb3QoeCwgeSkNCnBsdC5zaG93KCkNCmBgYA0KIyBSDQoNCmBgYHtyfQ0Kc3VtbWFyeShweSRkZikNCmBgYA0KIyBWZXIgbGFzIGNpdWRhZGVzIGVuIHVuIG1hcGENCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp1cyAlPiUNCiAgZmlsdGVyKENvbXBhcmFyPT0ieWVzIikgJT4lDQogIHNlbGVjdChBY2NlbnRDaXR5LExhdGl0dWRlLCBMb25naXR1ZGUpICU+JQ0KICBsZWFmbGV0KCB3aWR0aCA9IDkwMCkgJT4lDQogIGFkZFRpbGVzKCklPiUNCiAgYWRkTWFya2VycyhjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkscG9wdXAgPSB+aHRtbEVzY2FwZShBY2NlbnRDaXR5KSkNCmBgYA==