Una vez que hemos preprocesado los datos con los que vamos a trabajar, estamos listos para iniciar nuestro análisis. Cuando ejecutamos análisis de datos en inteligencia de negocios tenemos que considerar dos formas importantes de llevarlo a cabo:

En este capítulo nos concentraremos en la primera forma de análisis, para lo cual empecemos importando nuestras librerías de trabajo tidyverse y lubridate:

library(tidyverse)
library(lubridate)

A continuación, importemos el conjunto de datos con el que vamos a trabajar y guardémoslo en el objeto df_sesiones. Éste se encuentra en el link https://raw.githubusercontent.com/jsaraujo5081/clasesBI/main/sesiones_web.csv.

df_sesiones <- read_csv(file = "https://raw.githubusercontent.com/jsaraujo5081/clasesBI/main/sesiones_web.csv",
                          col_names = TRUE)

-- Column specification ---------------------------------------------------------
cols(
  Usuario_IP = col_character(),
  Fecha = col_date(format = ""),
  DiaSemana = col_character(),
  Paginas = col_double(),
  Duracion_Min = col_double(),
  Num_Sesion = col_double(),
  Tipo_Dispositivo = col_character(),
  Dispositivo = col_character(),
  Navegador = col_character(),
  TipoFuente_Analisis = col_character(),
  Hora = col_double(),
  HoraEC = col_double()
)

Demos una mirada a este set de datos y a su estructura:

View(df_sesiones01)
glimpse(df_sesiones01)
Rows: 174,313
Columns: 12
$ Usuario_IP          <chr> "1.0.149.137", "1.0.197.59", "1.0.197.59", "1.0.216~
$ Fecha               <date> 2020-06-15, 2020-04-27, 2020-04-27, 2020-02-23, 20~
$ DiaSemana           <chr> "1.Lu", "1.Lu", "1.Lu", "7.Do", "3.Mi", "2.Ma", "2.~
$ Paginas             <dbl> 2, 1, 1, 1, 1, 1, 1, 1, 1, 4, 2, 1, 2, 1, 1, 1, 2, ~
$ Duracion_Min        <dbl> 0.003888900, 0.119444450, 0.119444450, 1.277129633,~
$ Num_Sesion          <dbl> 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 3, 1, 1, 1, 1, ~
$ Tipo_Dispositivo    <chr> "Escritorio", "Movil", "Movil", "Movil", "Escritori~
$ Dispositivo         <chr> "PC", "Android", "Android", "iPhone", "PC", "Mac", ~
$ Navegador           <chr> "Google Chrome", "Google Chrome", "Google Chrome", ~
$ TipoFuente_Analisis <chr> "SearchEngine", "SearchEngine", "SearchEngine", "Se~
$ Hora                <dbl> 8, 7, 7, 15, 12, 21, 17, 14, 18, 6, 19, 20, 20, 16,~
$ HoraEC              <dbl> 20, 19, 19, 3, 0, 9, 3, 0, 3, 15, 5, 5, 6, 1, 1, 4,~

Vale señalar que los datos que hemos cargado corresponden al detalle de sesiones web que recibió un dominio de una empresa de turismo receptivo entre el 2019 y el 2020, y que se utilizan para desarrollar estrategias de mercadeo digital.

Una vez cargados estos datos, empecemos su análisis. Es importante que consideremos en este punto que todo análisis que hagamos debe intentar responder una pregunta de negocio, de forma que con los resultados generados, se transformen los datos que tenemos en información para tomar decisiones.

1. Estadística descriptiva

La estadística descriptiva hace referencia a la capacidad de resumir datos mediante el uso de medidas representativas. Como nos daremos cuenta, gran parte de estas medidas ya las hemos venido utilizado.

1.1 Tendencia central

La primera pregunta que quisiéramos responder con estos datos sería conocer cuántas páginas en promedio se visitan en cada sesión. Entonces, usando la función summarise vamos a responder a estas preguntas, y vamos a calcular las medidas de media y mediana en las variables que correspondan.

df_sesiones %>%
  summarise(media_pags = mean(Paginas),
            mediana_pags = median(Paginas))

Viendo estos resultados, se puede concluir que en cada sesión en promedio se visitan entre 1.41 páginas distintas. Además que es frecuente que los usurios ingresen en una sola página por sesión.

Siguiendo con esta línea de ideas, la siguiente pregunta planteada es saber cuál es el tiempo promedio de estadía en cada sesión web. Aplicando la misma estrategia se obtienen los siguientes resultados:

df_sesiones %>%
  summarise(media_segs = mean(Duracion_Min)*60,
            mediana_segs = median(Duracion_Min)*60)

En consecuencia, se tiene que evidencia que en promedio las sesiones tienen una duración de aproximadamente 25 segundos. Adicionalmente, que en el 50% de los casos, los usuarios navegan durante 8 segundos o menos.

1.2 Posición

Siguiendo con la duración de las sesiones, se plantea la pregunta de saber un rango de tiempo referencial que duran las sesiones, diferenciando por tipo de dispositivo utilizado. Igualmente, usemos la función summarise, pero en esta ocasión agrupemos por el tipo de dispositivo utilizado (group_by) y calculemos el mínimo y el máximo de tiempo como medidas.

df_sesiones %>%
  group_by(Tipo_Dispositivo) %>%
  summarise(min_segs = min(Duracion_Min)*60,
            max_segs = max(Duracion_Min)*60) %>%
  ungroup()

Los resultados alcanzados dan cuenta que en móvil el rango de tiempo de las sesiones parece ser menor, sin embargo los umbrales observados no nos están dando una información totalmente asertiva ni concluyente. Usemos entonces otras medidas un poco más pertinentes: los cuartiles. La función que permite obtener cuartiles de una variable es quantile, donde se especifica el “percentil” que deseamos obtener (0.25 para el cuartil 1 y 0.75 para el cuartil 3.

df_sesiones %>%
  group_by(Tipo_Dispositivo) %>%
  summarise(cuartil1_segs = quantile(Duracion_Min, 0.25)*60,
            cuartil3_segs = quantile(Duracion_Min, 0.75)*60) %>%
  ungroup()

Resulta interesante observar que al usar cuartiles, los rangos de tiempo no parecen ser tan diferentes y tienen mayor sentido. Existiría una leve evidencia de que en móvil las sesiones tienden a durar más incluso.

El siguiente cuestionamiento que surge es saber cuál es el máximo de sesiones que un mismo usuario puede mantener en el dominio analizado, a fin de conocer el nivel de reingreso.

df_sesiones %>%
  summarise(max_sesiones = max(Num_Sesion))

Nuevamente, este resultado parece extraño pues es dificil que un usuario ingrese a un mismo dominio más de 5000 veces. Seguramente, este usuario es un bot. Para dar una medida más pertienente, usemos nuevamente la función quantile para conocer el 3er cuartil.

df_sesiones %>%
  summarise(max_sesiones = quantile(Num_Sesion,0.75))

Esto resulta más coherente. Se puede afirmar que como máximo un usuario ingresará hasta 14 veces al dominio.

1.3 Dispersión y Correlación

Ahora bien, interesa conocer que nivel de variabilidad tiene la cantidad de páginas visitadas en cada sesión, para saber si es generalizable el hecho de que una sesión corresponde a 1.41 páginas visitadas.

Sigamos con summarise, pero ahora usemos como medidas la desviación estándar y el coeficiente de variación.

df_sesiones %>%
  summarise(desv_pags = sd(Paginas),
            cv_pags = sd(Paginas)/mean(Paginas))

EStos resultados son interesantes pues quedaría evidenciado que hay mucha dispersión. Si bien en promedio cada usuario visita 1.4 páginas por sesión, un amplio porcentaje de casos registran sesiones de hasta 8 páginas.

La siguiente pregunta que nos planteamos es saber si la cantidad de páginas visitadas incide en la duración de las sesiones. En principio esto parece evidente, pero evaluemos esta hipótesis a la luz de los datos. Usemos la medida de correlación, y apliquemos filtros para solamente ver las sesiones con duraciones mayores a 0, y solamente aquellos casos que representen hasta la sesión 14 para un mismo usuario.

df_sesiones %>%
  filter(Duracion_Min > 0) %>%
  filter(Num_Sesion <= 14) %>%
  summarise(corr_pags_duracion = cor(Paginas,Duracion_Min))

Otra vez un resultado interesando, los datos arrojan que lo que parecía evidente, no lo es. En otras palabras, el tiempo que un usuario pase en el dominio no tiene tanta relación con las páginas visitadas, sino que se podría asociar con el contenido de las páginas (si por ejemplo, lo que aparece en la página es interesante o no para las personas).

1.4 Frecuencias

Hasta el momento hemos analizado variables numéricas, obteniendo infromación valiosa para tomar decisiones. Planteemos ahora inquietudes asociadas a las variables no numéricas.

Primeramente, sería util saber cómo se comporta el tráfico web por día de la semana. Usemos las funciones count y arrange.

df_sesiones %>%
  count(DiaSemana) %>%
  arrange(-n)

Los resultados nos evidencia que los días martes y lunes son los de mayor tráfico, mientras que los sábados y domingos son los de menos.

Complementemos esto, viendo en los 3 días de mayor tráfico, el comportamiento por horas.

df_sesiones %>%
  filter(DiaSemana == "1.Lu" | DiaSemana == "2.Ma" | DiaSemana == "3.Mi") %>%
  count(Hora) %>%
  arrange(-n)

El mayor tráfico en estos días claramente se da en el horario de “almuerzo”, es decir entre las 11 am y las 16 pm. Por su parte el menos, está en las madrugadas, entendiendo que gran parte de las personas estan dormidas. Es lógico.

Se pregunta ahora algo similar, cuál es el comportamiento del tráfico por fuente de acceso, pero solamente para las primeras sesiones de los usuarios.

df_sesiones %>%
  filter(Num_Sesion == 1) %>%
  count(TipoFuente_Analisis) %>%
  arrange(-n)

Considerando la realidad del marketing digital actual, estos resultados son bastante coherentes: los ingresos por buscadores web dominan, mientras que los ingresos por páginas referidas son las de menor tráfico.

2. Estadística inferencial

La estadística inferencial hace referencia a la capacidad de utilizar métodos matemáticos para caracterizar el comportamiento de los datos. En este curso vamos a concentrarnos en dos aspectos aspectos relevantes de la mencioada inferencia.

2.1 Pruebas de hipótesis

Hemos visto que los resultados arrojan que en promedio existen 1.4 páginas visitadas por sesión. Validemos estadísticamente esto, mediante una prueba de hipótesis, donde nuestra hipótesis es

\[ H_0: \mu_{pags} = 1.4 \]

t.test(df_sesiones$Paginas,
       mu = 1.4)

    One Sample t-test

data:  df_sesiones$Paginas
t = 1.5629, df = 174312, p-value = 0.1181
alternative hypothesis: true mean is not equal to 1.4
95 percent confidence interval:
 1.396852 1.427929
sample estimates:
mean of x 
  1.41239 

Con una significancia del 10%, podemos afirmar que efectivamente el promedio de páginas visitadas sería de 1.4. Tenemos la evidencia estadística del caso mirando el valor p obtenido.

Por otra parte, dijimos también que la duración de sesiones entre tipos de dispositivos son prácticamente iguales. Validemos esto con una nueva prueba de hipótesis donde

\[ H_0: \mu_{segs,escritorio} = \mu_{segs,movil}\] Antes de aplicar la función t.test creemos unos data frames de apoyo para los tipos de dispositivo

df_sesiones_esc <- df_sesiones %>%
  filter(Tipo_Dispositivo == "Escritorio")
df_sesiones_mov <- df_sesiones %>%
  filter(Tipo_Dispositivo == "Movil")
t.test(df_sesiones_esc$Duracion_Min,
       df_sesiones_mov$Duracion_Min)

    Welch Two Sample t-test

data:  df_sesiones_esc$Duracion_Min and df_sesiones_mov$Duracion_Min
t = 19.736, df = 171296, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 0.08753822 0.10684171
sample estimates:
mean of x mean of y 
0.4634422 0.3662523 

Aplicando métodos de inferencia, llegamos a la conclusión de que parece que sí existen diferencias en cuanto al tiempo de navegación por tipo de dispositivo. Aspecto que no pudimos evidenciar usando solamente estadística descriptiva.

2.2 Intervalos de confianza

Dado que vimos que mediante métodos de inferencia sí hay diferencias por tipo de dispositivo en los tiempos de visita. Aclaremos esto utilizando intervalos de confianza y la función quantile pero ahora apliquemos referencias de 0.975 y 0.025 para generar intervalos al 95% de confianza.

df_sesiones_esc %>%
  summarise(lim_inf = quantile(Duracion_Min, 0.025)*60,
            lim_sup = quantile(Duracion_Min, 0.975)*60)
df_sesiones_mov %>%
  summarise(lim_inf = quantile(Duracion_Min, 0.025)*60,
            lim_sup = quantile(Duracion_Min, 0.975)*60)

Los intervalos obtenidos muestran que en efecto existiría una diferencia a ser tomada en cuenta, los móviles tienen una menor duración de navegación.

LS0tDQp0aXRsZTogIkVzdGFkw61zdGljYSBkZXNjcmlwdGl2YSBlIGluZmVyZW5jaWFsIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KVW5hIHZleiBxdWUgaGVtb3MgcHJlcHJvY2VzYWRvIGxvcyBkYXRvcyBjb24gbG9zIHF1ZSB2YW1vcyBhIHRyYWJhamFyLCBlc3RhbW9zIGxpc3RvcyBwYXJhIGluaWNpYXIgbnVlc3RybyBhbsOhbGlzaXMuIEN1YW5kbyBlamVjdXRhbW9zIGFuw6FsaXNpcyBkZSBkYXRvcyBlbiBpbnRlbGlnZW5jaWEgZGUgbmVnb2Npb3MgdGVuZW1vcyBxdWUgY29uc2lkZXJhciBkb3MgZm9ybWFzIGltcG9ydGFudGVzIGRlIGxsZXZhcmxvIGEgY2FibzoNCg0KKiBFc3RhZMOtc3RpY2EgZGVzY3JpcHRpdmEgZSBpbmZlcmVuY2lhbA0KKiBWaXN1YWxpemFjacOzbiBtZWRpYW50ZSBncsOhZmljb3MNCg0KRW4gZXN0ZSBjYXDDrXR1bG8gbm9zIGNvbmNlbnRyYXJlbW9zIGVuIGxhIHByaW1lcmEgZm9ybWEgZGUgYW7DoWxpc2lzLCBwYXJhIGxvIGN1YWwgZW1wZWNlbW9zIGltcG9ydGFuZG8gbnVlc3RyYXMgbGlicmVyw61hcyBkZSB0cmFiYWpvICoqdGlkeXZlcnNlKiogeSAqKmx1YnJpZGF0ZSoqOg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobHVicmlkYXRlKQ0KYGBgDQoNCkEgY29udGludWFjacOzbiwgaW1wb3J0ZW1vcyBlbCBjb25qdW50byBkZSBkYXRvcyBjb24gZWwgcXVlIHZhbW9zIGEgdHJhYmFqYXIgeSBndWFyZMOpbW9zbG8gZW4gZWwgb2JqZXRvICpkZl9zZXNpb25lcyouIMOJc3RlIHNlIGVuY3VlbnRyYSBlbiBlbCBsaW5rIGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qc2FyYXVqbzUwODEvY2xhc2VzQkkvbWFpbi9zZXNpb25lc193ZWIuY3N2Lg0KYGBge3J9DQpkZl9zZXNpb25lcyA8LSByZWFkX2NzdihmaWxlID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qc2FyYXVqbzUwODEvY2xhc2VzQkkvbWFpbi9zZXNpb25lc193ZWIuY3N2IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sX25hbWVzID0gVFJVRSkNCmBgYA0KRGVtb3MgdW5hIG1pcmFkYSBhIGVzdGUgc2V0IGRlIGRhdG9zIHkgYSBzdSBlc3RydWN0dXJhOg0KYGBge3J9DQpWaWV3KGRmX3Nlc2lvbmVzKQ0KYGBgDQpgYGB7cn0NCmdsaW1wc2UoZGZfc2VzaW9uZXMpDQpgYGANClZhbGUgc2XDsWFsYXIgcXVlIGxvcyBkYXRvcyBxdWUgaGVtb3MgY2FyZ2FkbyBjb3JyZXNwb25kZW4gYWwgZGV0YWxsZSBkZSBzZXNpb25lcyB3ZWIgcXVlIHJlY2liacOzIHVuIGRvbWluaW8gZGUgdW5hIGVtcHJlc2EgZGUgdHVyaXNtbyByZWNlcHRpdm8gZW50cmUgZWwgMjAxOSB5IGVsIDIwMjAsIHkgcXVlIHNlIHV0aWxpemFuIHBhcmEgZGVzYXJyb2xsYXIgZXN0cmF0ZWdpYXMgZGUgbWVyY2FkZW8gZGlnaXRhbC4NCg0KVW5hIHZleiBjYXJnYWRvcyBlc3RvcyBkYXRvcywgZW1wZWNlbW9zIHN1IGFuw6FsaXNpcy4gRXMgaW1wb3J0YW50ZSBxdWUgY29uc2lkZXJlbW9zIGVuIGVzdGUgcHVudG8gcXVlIHRvZG8gYW7DoWxpc2lzIHF1ZSBoYWdhbW9zIGRlYmUgaW50ZW50YXIgcmVzcG9uZGVyIHVuYSBwcmVndW50YSBkZSBuZWdvY2lvLCBkZSBmb3JtYSBxdWUgY29uIGxvcyByZXN1bHRhZG9zIGdlbmVyYWRvcywgc2UgdHJhbnNmb3JtZW4gbG9zIGRhdG9zIHF1ZSB0ZW5lbW9zIGVuIGluZm9ybWFjacOzbiBwYXJhIHRvbWFyIGRlY2lzaW9uZXMuDQoNCiMjIDEuIEVzdGFkw61zdGljYSBkZXNjcmlwdGl2YQ0KDQpMYSBlc3RhZMOtc3RpY2EgZGVzY3JpcHRpdmEgaGFjZSByZWZlcmVuY2lhIGEgbGEgY2FwYWNpZGFkIGRlIHJlc3VtaXIgZGF0b3MgbWVkaWFudGUgZWwgdXNvIGRlIG1lZGlkYXMgcmVwcmVzZW50YXRpdmFzLiBDb21vIG5vcyBkYXJlbW9zIGN1ZW50YSwgZ3JhbiBwYXJ0ZSBkZSBlc3RhcyBtZWRpZGFzIHlhIGxhcyBoZW1vcyB2ZW5pZG8gdXRpbGl6YWRvLg0KDQojIyMgMS4xIFRlbmRlbmNpYSBjZW50cmFsDQoNCkxhIHByaW1lcmEgcHJlZ3VudGEgcXVlIHF1aXNpw6lyYW1vcyByZXNwb25kZXIgY29uIGVzdG9zIGRhdG9zIHNlcsOtYSBjb25vY2VyIGN1w6FudGFzIHDDoWdpbmFzIGVuIHByb21lZGlvIHNlIHZpc2l0YW4gZW4gY2FkYSBzZXNpw7NuLiBFbnRvbmNlcywgdXNhbmRvIGxhIGZ1bmNpw7NuICpzdW1tYXJpc2UqIHZhbW9zIGEgcmVzcG9uZGVyIGEgZXN0YXMgcHJlZ3VudGFzLCB5IHZhbW9zIGEgY2FsY3VsYXIgbGFzIG1lZGlkYXMgZGUgbWVkaWEgeSBtZWRpYW5hIGVuIGxhcyB2YXJpYWJsZXMgcXVlIGNvcnJlc3BvbmRhbi4NCmBgYHtyfQ0KZGZfc2VzaW9uZXMgJT4lDQogIHN1bW1hcmlzZShtZWRpYV9wYWdzID0gbWVhbihQYWdpbmFzKSwNCiAgICAgICAgICAgIG1lZGlhbmFfcGFncyA9IG1lZGlhbihQYWdpbmFzKSkNCmBgYA0KVmllbmRvIGVzdG9zIHJlc3VsdGFkb3MsIHNlIHB1ZWRlIGNvbmNsdWlyIHF1ZSBlbiBjYWRhIHNlc2nDs24gZW4gcHJvbWVkaW8gc2UgdmlzaXRhbiBlbnRyZSAxLjQxIHDDoWdpbmFzIGRpc3RpbnRhcy4gQWRlbcOhcyBxdWUgZXMgZnJlY3VlbnRlIHF1ZSBsb3MgdXN1cmlvcyBpbmdyZXNlbiBlbiB1bmEgc29sYSBww6FnaW5hIHBvciBzZXNpw7NuLg0KDQpTaWd1aWVuZG8gY29uIGVzdGEgbMOtbmVhIGRlIGlkZWFzLCBsYSBzaWd1aWVudGUgcHJlZ3VudGEgcGxhbnRlYWRhIGVzIHNhYmVyIGN1w6FsIGVzIGVsIHRpZW1wbyBwcm9tZWRpbyBkZSBlc3RhZMOtYSBlbiBjYWRhIHNlc2nDs24gd2ViLiBBcGxpY2FuZG8gbGEgbWlzbWEgZXN0cmF0ZWdpYSBzZSBvYnRpZW5lbiBsb3Mgc2lndWllbnRlcyByZXN1bHRhZG9zOg0KYGBge3J9DQpkZl9zZXNpb25lcyAlPiUNCiAgc3VtbWFyaXNlKG1lZGlhX3NlZ3MgPSBtZWFuKER1cmFjaW9uX01pbikqNjAsDQogICAgICAgICAgICBtZWRpYW5hX3NlZ3MgPSBtZWRpYW4oRHVyYWNpb25fTWluKSo2MCkNCmBgYA0KRW4gY29uc2VjdWVuY2lhLCBzZSB0aWVuZSBxdWUgZXZpZGVuY2lhIHF1ZSBlbiBwcm9tZWRpbyBsYXMgc2VzaW9uZXMgdGllbmVuIHVuYSBkdXJhY2nDs24gZGUgYXByb3hpbWFkYW1lbnRlIDI1IHNlZ3VuZG9zLiBBZGljaW9uYWxtZW50ZSwgcXVlIGVuIGVsIDUwJSBkZSBsb3MgY2Fzb3MsIGxvcyB1c3VhcmlvcyBuYXZlZ2FuIGR1cmFudGUgOCBzZWd1bmRvcyBvIG1lbm9zLg0KDQojIyMgMS4yIFBvc2ljacOzbg0KDQpTaWd1aWVuZG8gY29uIGxhIGR1cmFjacOzbiBkZSBsYXMgc2VzaW9uZXMsIHNlIHBsYW50ZWEgbGEgcHJlZ3VudGEgZGUgc2FiZXIgdW4gcmFuZ28gZGUgdGllbXBvIHJlZmVyZW5jaWFsIHF1ZSBkdXJhbiBsYXMgc2VzaW9uZXMsIGRpZmVyZW5jaWFuZG8gcG9yIHRpcG8gZGUgZGlzcG9zaXRpdm8gdXRpbGl6YWRvLiBJZ3VhbG1lbnRlLCB1c2Vtb3MgbGEgZnVuY2nDs24gKnN1bW1hcmlzZSosIHBlcm8gZW4gZXN0YSBvY2FzacOzbiBhZ3J1cGVtb3MgcG9yIGVsIHRpcG8gZGUgZGlzcG9zaXRpdm8gdXRpbGl6YWRvICgqZ3JvdXBfYnkqKSB5IGNhbGN1bGVtb3MgZWwgbcOtbmltbyB5IGVsIG3DoXhpbW8gZGUgdGllbXBvIGNvbW8gbWVkaWRhcy4NCmBgYHtyfQ0KZGZfc2VzaW9uZXMgJT4lDQogIGdyb3VwX2J5KFRpcG9fRGlzcG9zaXRpdm8pICU+JQ0KICBzdW1tYXJpc2UobWluX3NlZ3MgPSBtaW4oRHVyYWNpb25fTWluKSo2MCwNCiAgICAgICAgICAgIG1heF9zZWdzID0gbWF4KER1cmFjaW9uX01pbikqNjApICU+JQ0KICB1bmdyb3VwKCkNCmBgYA0KTG9zIHJlc3VsdGFkb3MgYWxjYW56YWRvcyBkYW4gY3VlbnRhIHF1ZSBlbiBtw7N2aWwgZWwgcmFuZ28gZGUgdGllbXBvIGRlIGxhcyBzZXNpb25lcyBwYXJlY2Ugc2VyIG1lbm9yLCBzaW4gZW1iYXJnbyBsb3MgdW1icmFsZXMgb2JzZXJ2YWRvcyBubyBub3MgZXN0w6FuIGRhbmRvIHVuYSBpbmZvcm1hY2nDs24gdG90YWxtZW50ZSBhc2VydGl2YSBuaSBjb25jbHV5ZW50ZS4gVXNlbW9zIGVudG9uY2VzIG90cmFzIG1lZGlkYXMgdW4gcG9jbyBtw6FzIHBlcnRpbmVudGVzOiBsb3MgY3VhcnRpbGVzLiBMYSBmdW5jacOzbiBxdWUgcGVybWl0ZSBvYnRlbmVyIGN1YXJ0aWxlcyBkZSB1bmEgdmFyaWFibGUgZXMgKnF1YW50aWxlKiwgZG9uZGUgc2UgZXNwZWNpZmljYSBlbCAicGVyY2VudGlsIiBxdWUgZGVzZWFtb3Mgb2J0ZW5lciAoMC4yNSBwYXJhIGVsIGN1YXJ0aWwgMSB5IDAuNzUgcGFyYSBlbCBjdWFydGlsIDMuDQpgYGB7cn0NCmRmX3Nlc2lvbmVzICU+JQ0KICBncm91cF9ieShUaXBvX0Rpc3Bvc2l0aXZvKSAlPiUNCiAgc3VtbWFyaXNlKGN1YXJ0aWwxX3NlZ3MgPSBxdWFudGlsZShEdXJhY2lvbl9NaW4sIDAuMjUpKjYwLA0KICAgICAgICAgICAgY3VhcnRpbDNfc2VncyA9IHF1YW50aWxlKER1cmFjaW9uX01pbiwgMC43NSkqNjApICU+JQ0KICB1bmdyb3VwKCkNCmBgYA0KUmVzdWx0YSBpbnRlcmVzYW50ZSBvYnNlcnZhciBxdWUgYWwgdXNhciBjdWFydGlsZXMsIGxvcyByYW5nb3MgZGUgdGllbXBvIG5vIHBhcmVjZW4gc2VyIHRhbiBkaWZlcmVudGVzIHkgdGllbmVuIG1heW9yIHNlbnRpZG8uIEV4aXN0aXLDrWEgdW5hIGxldmUgZXZpZGVuY2lhIGRlIHF1ZSBlbiBtw7N2aWwgbGFzIHNlc2lvbmVzIHRpZW5kZW4gYSBkdXJhciBtw6FzIGluY2x1c28uICANCg0KRWwgc2lndWllbnRlIGN1ZXN0aW9uYW1pZW50byBxdWUgc3VyZ2UgZXMgc2FiZXIgY3XDoWwgZXMgZWwgbcOheGltbyBkZSBzZXNpb25lcyBxdWUgdW4gbWlzbW8gdXN1YXJpbyBwdWVkZSBtYW50ZW5lciBlbiBlbCBkb21pbmlvIGFuYWxpemFkbywgYSBmaW4gZGUgY29ub2NlciBlbCBuaXZlbCBkZSByZWluZ3Jlc28uDQpgYGB7cn0NCmRmX3Nlc2lvbmVzICU+JQ0KICBzdW1tYXJpc2UobWF4X3Nlc2lvbmVzID0gbWF4KE51bV9TZXNpb24pKQ0KYGBgDQpOdWV2YW1lbnRlLCBlc3RlIHJlc3VsdGFkbyBwYXJlY2UgZXh0cmHDsW8gcHVlcyBlcyBkaWZpY2lsIHF1ZSB1biB1c3VhcmlvIGluZ3Jlc2UgYSB1biBtaXNtbyBkb21pbmlvIG3DoXMgZGUgNTAwMCB2ZWNlcy4gU2VndXJhbWVudGUsIGVzdGUgdXN1YXJpbyBlcyB1biBib3QuIFBhcmEgZGFyIHVuYSBtZWRpZGEgbcOhcyBwZXJ0aWVuZW50ZSwgdXNlbW9zIG51ZXZhbWVudGUgbGEgZnVuY2nDs24gKnF1YW50aWxlKiBwYXJhIGNvbm9jZXIgZWwgM2VyIGN1YXJ0aWwuDQpgYGB7cn0NCmRmX3Nlc2lvbmVzICU+JQ0KICBzdW1tYXJpc2UobWF4X3Nlc2lvbmVzID0gcXVhbnRpbGUoTnVtX1Nlc2lvbiwwLjc1KSkNCmBgYA0KRXN0byByZXN1bHRhIG3DoXMgY29oZXJlbnRlLiBTZSBwdWVkZSBhZmlybWFyIHF1ZSBjb21vIG3DoXhpbW8gdW4gdXN1YXJpbyBpbmdyZXNhcsOhIGhhc3RhIDE0IHZlY2VzIGFsIGRvbWluaW8uDQoNCiMjIyAxLjMgRGlzcGVyc2nDs24geSBDb3JyZWxhY2nDs24NCg0KQWhvcmEgYmllbiwgaW50ZXJlc2EgY29ub2NlciBxdWUgbml2ZWwgZGUgdmFyaWFiaWxpZGFkIHRpZW5lIGxhIGNhbnRpZGFkIGRlIHDDoWdpbmFzIHZpc2l0YWRhcyBlbiBjYWRhIHNlc2nDs24sIHBhcmEgc2FiZXIgc2kgZXMgZ2VuZXJhbGl6YWJsZSBlbCBoZWNobyBkZSBxdWUgdW5hIHNlc2nDs24gY29ycmVzcG9uZGUgYSAxLjQxIHDDoWdpbmFzIHZpc2l0YWRhcy4gDQoNClNpZ2Ftb3MgY29uICpzdW1tYXJpc2UqLCBwZXJvIGFob3JhIHVzZW1vcyBjb21vIG1lZGlkYXMgbGEgZGVzdmlhY2nDs24gZXN0w6FuZGFyIHkgZWwgY29lZmljaWVudGUgZGUgdmFyaWFjacOzbi4NCmBgYHtyfQ0KZGZfc2VzaW9uZXMgJT4lDQogIHN1bW1hcmlzZShkZXN2X3BhZ3MgPSBzZChQYWdpbmFzKSwNCiAgICAgICAgICAgIGN2X3BhZ3MgPSBzZChQYWdpbmFzKS9tZWFuKFBhZ2luYXMpKQ0KYGBgDQpFU3RvcyByZXN1bHRhZG9zIHNvbiBpbnRlcmVzYW50ZXMgcHVlcyBxdWVkYXLDrWEgZXZpZGVuY2lhZG8gcXVlIGhheSBtdWNoYSBkaXNwZXJzacOzbi4gU2kgYmllbiBlbiBwcm9tZWRpbyBjYWRhIHVzdWFyaW8gdmlzaXRhIDEuNCBww6FnaW5hcyBwb3Igc2VzacOzbiwgdW4gYW1wbGlvIHBvcmNlbnRhamUgZGUgY2Fzb3MgcmVnaXN0cmFuIHNlc2lvbmVzIGRlIGhhc3RhIDggcMOhZ2luYXMuDQoNCkxhIHNpZ3VpZW50ZSBwcmVndW50YSBxdWUgbm9zIHBsYW50ZWFtb3MgZXMgc2FiZXIgc2kgbGEgY2FudGlkYWQgZGUgcMOhZ2luYXMgdmlzaXRhZGFzIGluY2lkZSBlbiBsYSBkdXJhY2nDs24gZGUgbGFzIHNlc2lvbmVzLiBFbiBwcmluY2lwaW8gZXN0byBwYXJlY2UgZXZpZGVudGUsIHBlcm8gZXZhbHVlbW9zIGVzdGEgaGlww7N0ZXNpcyBhIGxhIGx1eiBkZSBsb3MgZGF0b3MuIFVzZW1vcyBsYSBtZWRpZGEgZGUgY29ycmVsYWNpw7NuLCB5IGFwbGlxdWVtb3MgZmlsdHJvcyBwYXJhIHNvbGFtZW50ZSB2ZXIgbGFzIHNlc2lvbmVzIGNvbiBkdXJhY2lvbmVzIG1heW9yZXMgYSAwLCB5IHNvbGFtZW50ZSBhcXVlbGxvcyBjYXNvcyBxdWUgcmVwcmVzZW50ZW4gaGFzdGEgbGEgc2VzacOzbiAxNCBwYXJhIHVuIG1pc21vIHVzdWFyaW8uDQoNCmBgYHtyfQ0KZGZfc2VzaW9uZXMgJT4lDQogIGZpbHRlcihEdXJhY2lvbl9NaW4gPiAwKSAlPiUNCiAgZmlsdGVyKE51bV9TZXNpb24gPD0gMTQpICU+JQ0KICBzdW1tYXJpc2UoY29ycl9wYWdzX2R1cmFjaW9uID0gY29yKFBhZ2luYXMsRHVyYWNpb25fTWluKSkNCmBgYA0KT3RyYSB2ZXogdW4gcmVzdWx0YWRvIGludGVyZXNhbmRvLCBsb3MgZGF0b3MgYXJyb2phbiBxdWUgbG8gcXVlIHBhcmVjw61hIGV2aWRlbnRlLCBubyBsbyBlcy4gRW4gb3RyYXMgcGFsYWJyYXMsIGVsIHRpZW1wbyBxdWUgdW4gdXN1YXJpbyBwYXNlIGVuIGVsIGRvbWluaW8gbm8gdGllbmUgdGFudGEgcmVsYWNpw7NuIGNvbiBsYXMgcMOhZ2luYXMgdmlzaXRhZGFzLCBzaW5vIHF1ZSBzZSBwb2Ryw61hIGFzb2NpYXIgY29uIGVsIGNvbnRlbmlkbyBkZSBsYXMgcMOhZ2luYXMgKHNpIHBvciBlamVtcGxvLCBsbyBxdWUgYXBhcmVjZSBlbiBsYSBww6FnaW5hIGVzIGludGVyZXNhbnRlIG8gbm8gcGFyYSBsYXMgcGVyc29uYXMpLg0KDQojIyMgMS40IEZyZWN1ZW5jaWFzDQoNCkhhc3RhIGVsIG1vbWVudG8gaGVtb3MgYW5hbGl6YWRvIHZhcmlhYmxlcyBudW3DqXJpY2FzLCBvYnRlbmllbmRvIGluZnJvbWFjacOzbiB2YWxpb3NhIHBhcmEgdG9tYXIgZGVjaXNpb25lcy4gUGxhbnRlZW1vcyBhaG9yYSBpbnF1aWV0dWRlcyBhc29jaWFkYXMgYSBsYXMgdmFyaWFibGVzIG5vIG51bcOpcmljYXMuDQoNClByaW1lcmFtZW50ZSwgc2Vyw61hIHV0aWwgc2FiZXIgY8OzbW8gc2UgY29tcG9ydGEgZWwgdHLDoWZpY28gd2ViIHBvciBkw61hIGRlIGxhIHNlbWFuYS4gVXNlbW9zIGxhcyBmdW5jaW9uZXMgKmNvdW50KiB5ICphcnJhbmdlKi4NCmBgYHtyfQ0KZGZfc2VzaW9uZXMgJT4lDQogIGNvdW50KERpYVNlbWFuYSkgJT4lDQogIGFycmFuZ2UoLW4pDQpgYGANCkxvcyByZXN1bHRhZG9zIG5vcyBldmlkZW5jaWEgcXVlIGxvcyBkw61hcyBtYXJ0ZXMgeSBsdW5lcyBzb24gbG9zIGRlIG1heW9yIHRyw6FmaWNvLCBtaWVudHJhcyBxdWUgbG9zIHPDoWJhZG9zIHkgZG9taW5nb3Mgc29uIGxvcyBkZSBtZW5vcy4NCg0KQ29tcGxlbWVudGVtb3MgZXN0bywgdmllbmRvIGVuIGxvcyAzIGTDrWFzIGRlIG1heW9yIHRyw6FmaWNvLCBlbCBjb21wb3J0YW1pZW50byBwb3IgaG9yYXMuDQpgYGB7cn0NCmRmX3Nlc2lvbmVzICU+JQ0KICBmaWx0ZXIoRGlhU2VtYW5hID09ICIxLkx1IiB8IERpYVNlbWFuYSA9PSAiMi5NYSIgfCBEaWFTZW1hbmEgPT0gIjMuTWkiKSAlPiUNCiAgY291bnQoSG9yYSkgJT4lDQogIGFycmFuZ2UoLW4pDQpgYGANCkVsIG1heW9yIHRyw6FmaWNvIGVuIGVzdG9zIGTDrWFzIGNsYXJhbWVudGUgc2UgZGEgZW4gZWwgaG9yYXJpbyBkZSAiYWxtdWVyem8iLCBlcyBkZWNpciBlbnRyZSBsYXMgMTEgYW0geSBsYXMgMTYgcG0uIFBvciBzdSBwYXJ0ZSBlbCBtZW5vcywgZXN0w6EgZW4gbGFzIG1hZHJ1Z2FkYXMsIGVudGVuZGllbmRvIHF1ZSBncmFuIHBhcnRlIGRlIGxhcyBwZXJzb25hcyBlc3RhbiBkb3JtaWRhcy4gRXMgbMOzZ2ljby4NCg0KU2UgcHJlZ3VudGEgYWhvcmEgYWxnbyBzaW1pbGFyLCBjdcOhbCBlcyBlbCBjb21wb3J0YW1pZW50byBkZWwgdHLDoWZpY28gcG9yIGZ1ZW50ZSBkZSBhY2Nlc28sIHBlcm8gc29sYW1lbnRlIHBhcmEgbGFzIHByaW1lcmFzIHNlc2lvbmVzIGRlIGxvcyB1c3Vhcmlvcy4NCmBgYHtyfQ0KZGZfc2VzaW9uZXMgJT4lDQogIGZpbHRlcihOdW1fU2VzaW9uID09IDEpICU+JQ0KICBjb3VudChUaXBvRnVlbnRlX0FuYWxpc2lzKSAlPiUNCiAgYXJyYW5nZSgtbikNCmBgYA0KQ29uc2lkZXJhbmRvIGxhIHJlYWxpZGFkIGRlbCBtYXJrZXRpbmcgZGlnaXRhbCBhY3R1YWwsIGVzdG9zIHJlc3VsdGFkb3Mgc29uIGJhc3RhbnRlIGNvaGVyZW50ZXM6IGxvcyBpbmdyZXNvcyBwb3IgYnVzY2Fkb3JlcyB3ZWIgZG9taW5hbiwgbWllbnRyYXMgcXVlIGxvcyBpbmdyZXNvcyBwb3IgcMOhZ2luYXMgcmVmZXJpZGFzIHNvbiBsYXMgZGUgbWVub3IgdHLDoWZpY28uDQoNCiMjIDIuIEVzdGFkw61zdGljYSBpbmZlcmVuY2lhbA0KDQpMYSBlc3RhZMOtc3RpY2EgaW5mZXJlbmNpYWwgaGFjZSByZWZlcmVuY2lhIGEgbGEgY2FwYWNpZGFkIGRlIHV0aWxpemFyIG3DqXRvZG9zIG1hdGVtw6F0aWNvcyBwYXJhIGNhcmFjdGVyaXphciBlbCBjb21wb3J0YW1pZW50byBkZSBsb3MgZGF0b3MuIEVuIGVzdGUgY3Vyc28gdmFtb3MgYSBjb25jZW50cmFybm9zIGVuIGRvcyBhc3BlY3RvcyBhc3BlY3RvcyByZWxldmFudGVzIGRlIGxhIG1lbmNpb2FkYSBpbmZlcmVuY2lhLg0KDQoqIFBydWViYXMgZGUgaGlww7N0ZXNpcw0KKiBJbnRlcnZhbG9zIGRlIGNvbmZpYW56YQ0KDQojIyMgMi4xIFBydWViYXMgZGUgaGlww7N0ZXNpcw0KDQpIZW1vcyB2aXN0byBxdWUgbG9zIHJlc3VsdGFkb3MgYXJyb2phbiBxdWUgZW4gcHJvbWVkaW8gZXhpc3RlbiAxLjQgcMOhZ2luYXMgdmlzaXRhZGFzIHBvciBzZXNpw7NuLiBWYWxpZGVtb3MgZXN0YWTDrXN0aWNhbWVudGUgZXN0bywgbWVkaWFudGUgdW5hIHBydWViYSBkZSBoaXDDs3Rlc2lzLCBkb25kZSBudWVzdHJhIGhpcMOzdGVzaXMgZXMgDQoNCiQkIEhfMDogXG11X3twYWdzfSA9IDEuNCAkJCANCmBgYHtyfQ0KdC50ZXN0KGRmX3Nlc2lvbmVzJFBhZ2luYXMsDQogICAgICAgbXUgPSAxLjQpDQpgYGANCkNvbiB1bmEgc2lnbmlmaWNhbmNpYSBkZWwgMTAlLCBwb2RlbW9zIGFmaXJtYXIgcXVlIGVmZWN0aXZhbWVudGUgZWwgcHJvbWVkaW8gZGUgcMOhZ2luYXMgdmlzaXRhZGFzIHNlcsOtYSBkZSAxLjQuIFRlbmVtb3MgbGEgZXZpZGVuY2lhIGVzdGFkw61zdGljYSBkZWwgY2FzbyBtaXJhbmRvIGVsICp2YWxvciBwKiBvYnRlbmlkby4NCg0KUG9yIG90cmEgcGFydGUsIGRpamltb3MgdGFtYmnDqW4gcXVlIGxhIGR1cmFjacOzbiBkZSBzZXNpb25lcyBlbnRyZSB0aXBvcyBkZSBkaXNwb3NpdGl2b3Mgc29uIHByw6FjdGljYW1lbnRlIGlndWFsZXMuIFZhbGlkZW1vcyBlc3RvIGNvbiB1bmEgbnVldmEgcHJ1ZWJhIGRlIGhpcMOzdGVzaXMgZG9uZGUNCg0KJCQgSF8wOiBcbXVfe3NlZ3MsZXNjcml0b3Jpb30gPSBcbXVfe3NlZ3MsbW92aWx9JCQNCkFudGVzIGRlIGFwbGljYXIgbGEgZnVuY2nDs24gKnQudGVzdCogY3JlZW1vcyB1bm9zIGRhdGEgZnJhbWVzIGRlIGFwb3lvIHBhcmEgbG9zIHRpcG9zIGRlIGRpc3Bvc2l0aXZvDQpgYGB7cn0NCmRmX3Nlc2lvbmVzX2VzYyA8LSBkZl9zZXNpb25lcyAlPiUNCiAgZmlsdGVyKFRpcG9fRGlzcG9zaXRpdm8gPT0gIkVzY3JpdG9yaW8iKQ0KZGZfc2VzaW9uZXNfbW92IDwtIGRmX3Nlc2lvbmVzICU+JQ0KICBmaWx0ZXIoVGlwb19EaXNwb3NpdGl2byA9PSAiTW92aWwiKQ0KdC50ZXN0KGRmX3Nlc2lvbmVzX2VzYyREdXJhY2lvbl9NaW4sDQogICAgICAgZGZfc2VzaW9uZXNfbW92JER1cmFjaW9uX01pbikNCmBgYA0KQXBsaWNhbmRvIG3DqXRvZG9zIGRlIGluZmVyZW5jaWEsIGxsZWdhbW9zIGEgbGEgY29uY2x1c2nDs24gZGUgcXVlIHBhcmVjZSBxdWUgc8OtIGV4aXN0ZW4gZGlmZXJlbmNpYXMgZW4gY3VhbnRvIGFsIHRpZW1wbyBkZSBuYXZlZ2FjacOzbiBwb3IgdGlwbyBkZSBkaXNwb3NpdGl2by4gQXNwZWN0byBxdWUgbm8gcHVkaW1vcyBldmlkZW5jaWFyIHVzYW5kbyBzb2xhbWVudGUgZXN0YWTDrXN0aWNhIGRlc2NyaXB0aXZhLg0KDQojIyMgMi4yIEludGVydmFsb3MgZGUgY29uZmlhbnphDQoNCkRhZG8gcXVlIHZpbW9zIHF1ZSBtZWRpYW50ZSBtw6l0b2RvcyBkZSBpbmZlcmVuY2lhIHPDrSBoYXkgZGlmZXJlbmNpYXMgcG9yIHRpcG8gZGUgZGlzcG9zaXRpdm8gZW4gbG9zIHRpZW1wb3MgZGUgdmlzaXRhLiBBY2xhcmVtb3MgZXN0byB1dGlsaXphbmRvIGludGVydmFsb3MgZGUgY29uZmlhbnphIHkgbGEgZnVuY2nDs24gKnF1YW50aWxlKiBwZXJvIGFob3JhIGFwbGlxdWVtb3MgcmVmZXJlbmNpYXMgZGUgMC45NzUgeSAwLjAyNSBwYXJhIGdlbmVyYXIgaW50ZXJ2YWxvcyBhbCA5NSUgZGUgY29uZmlhbnphLg0KDQpgYGB7cn0NCmRmX3Nlc2lvbmVzX2VzYyAlPiUNCiAgc3VtbWFyaXNlKGxpbV9pbmYgPSBxdWFudGlsZShEdXJhY2lvbl9NaW4sIDAuMDI1KSo2MCwNCiAgICAgICAgICAgIGxpbV9zdXAgPSBxdWFudGlsZShEdXJhY2lvbl9NaW4sIDAuOTc1KSo2MCkNCmBgYA0KYGBge3J9DQpkZl9zZXNpb25lc19tb3YgJT4lDQogIHN1bW1hcmlzZShsaW1faW5mID0gcXVhbnRpbGUoRHVyYWNpb25fTWluLCAwLjAyNSkqNjAsDQogICAgICAgICAgICBsaW1fc3VwID0gcXVhbnRpbGUoRHVyYWNpb25fTWluLCAwLjk3NSkqNjApDQpgYGANCkxvcyBpbnRlcnZhbG9zIG9idGVuaWRvcyBtdWVzdHJhbiBxdWUgZW4gZWZlY3RvIGV4aXN0aXLDrWEgdW5hIGRpZmVyZW5jaWEgYSBzZXIgdG9tYWRhIGVuIGN1ZW50YSwgbG9zIG3Ds3ZpbGVzIHRpZW5lbiB1bmEgbWVub3IgZHVyYWNpw7NuIGRlIG5hdmVnYWNpw7NuLg0KDQo=