Curso Introducción a R para RRHH

Bienvenidas y bienvenidos al curso Introducción a R para RRHH 👩‍💻. R es un lenguaje open source, es decir que es abierto, gratuito, y que además la comunidad de usuarias y usuarios contribuye a su mantenimiento.

Crear proyectos en RStudio

Trabajar con proyectos en RStudio hace que todo el trabajo sea más sencillo. Los proyectos crean una carpeta en nuestra PC en donde se almacenarán los archivos, tablas, scripts, y hace que todo sea más organizado.

Para crear un proyecto tenés podés entrar en: * File * New project

Y luego poner el nombre de la carpeta.

Se abre una ventana, hacemos click en New Project, luego en Ok y finalmente en la tercera ventana podemos darle un nombre al proyecto, y asignar la carpeta en la que queremos guardarlo con el botón Browse.

Objetos

Por definición, R es un lenguaje orientado a objetos. Esto significa que a una tabla, un valor, una variable, etc. le voy a poner un nombre, y esa cosa que ahora tiene nombre se llama objeto.

Esto nos simplifica un montón la vida. La asignación la hacemos con el símbolo <- que simula una flecha hacia la izquierda. También se puede hacer con el símbolo = pero como muchas veces lo utilizamos para configurar un parámetro de una función usamos “la flechita”.

Valores

# Esto es un comentario

objeto_1 <- 1979
objeto_2 <- 2021

# Para ver el resultado del objeto uso las teclas Ctrl + Enter
# También puedo usar el ícono  "Run"
objeto_1
## [1] 1979
# Podemos hacer operaciones con objetos
objeto_2 - objeto_1
## [1] 42

Vectores

Uno de los objetos más utilizados son los vectores que son conjuntos de valores, numéricos, de texto, o combinados. Para crear un vector necesitamos usar la función c() que significa combine.

Los vectores pueden tener texto o número.

# Vector de Texto
nombre <- c("Gustavo", "Charly", "Zeta") # Las variables de texto van siempre con comillas.

# Ver el contenido
nombre
## [1] "Gustavo" "Charly"  "Zeta"
# Seleccionar un elemento del vector 
nombre[2]
## [1] "Charly"
# Vector numérico
anio_nacimiento <- c(1959, 1963, 1958)

Los vectores son muy utilizados para filtrar elementos, cambiar nombres de tablas, etc..

Data frames

Los data frames son tables que contienen filas (observaciones) y columnas (variables).

Podemos crear un data frame usando vectores que tengan la misma cantidad de elementos usando la función data.frame(). Dentro de la función podemos poner los nombres

# Crear el data frame soda_stereo usando los vectores nombre y anio_nacimiento
soda_stereo <- data.frame(nombre, anio_nacimiento)

# Seleccionar elementos de un vector usando la sintaxis: nombre_dataframe[fila, columna]
soda_stereo[2,1]
## [1] "Charly"

Normalmente los data frames los cargamos de algún archivo. Por ejemplo, carguemos el archivo rotacion.csv usando la función read.csv.

# Crear un objeto llamado rotacion leyendo el archivo rotacion.csv
rotacion <- read.csv("rotacion.csv",
                     sep = ";")

# Ver el contenido del data frame
rotacion # Ctrl + Enter
##    Periodo Movimientos Cantidades
## 1     2010    Ingresos          9
## 2     2010     Egresos          2
## 3     2011    Ingresos         84
## 4     2011     Egresos         14
## 5     2012    Ingresos         44
## 6     2012     Egresos         17
## 7     2013    Ingresos         44
## 8     2013     Egresos         15
## 9     2014    Ingresos         60
## 10    2014     Egresos         14
## 11    2015    Ingresos         36
## 12    2015     Egresos         27
## 13    2016    Ingresos         14
## 14    2016     Egresos         14

Notas sobre el nombre de los objetos

Si bien a los objetos podemos nombrarlos de cualquier manera, hay buenas prácticas recomendadas. Por ejemplo:

  • Usar nombres cortos, y descriptivos
  • Los nombres tienen que empezar con una letra.
  • Los símbolos válidos que se pueden usar son el guión bajo (_) o el punto (.).
  • Se pueden usar mayúsculas, pero es más cómodo que siempre usar minúsculas.
  • Recuerden que R es case sensitive, o sea que hay que prestar atención a mayúsculas y mínusculas
# Nombres válidos de objetos
## Mejores opciones
respuestas_clima

respuestas.clima

# Esto funciona pero es menos recomendable
RespuestasClima

Por otro lado:

  • Los objetos no pueden comenzar con números.
  • No se pueden usar acentos, ñ, u otros caracteres especiales.
  • No se recomiendan usar letras o nombres de funciones.

Paquetes

Los paquetes o librerías son extensiones desarrolladas por la comunidad o por empresas que facilitan el uso de R y expanden sus capacidades. En este encuentro vamos a usar los siguientes paquetes:

  • readxl: Desarrollado por Hadley Wickham and Jennifer Bryan. Link a la web.
  • openxlsx: Desarrollado por Philipp Schauberger and Alexander Walker. Link a la web.
  • tidyverse: Desarrollado por Hadley Wickham y muchos más. Link a la web.

Este último paquete, tidyverse es una colección de paquetes que permiten realizar muchas tareas de exploración, limpieza y transformación de datos.

Para utilizar un paquete, lo primero que tenemos que hacer es instalarlos. Eso lo hacemos con la función install.packages() y dentro del paréntesis tenemos que poner el nombre del paquete. Tengan en cuenta que:

  • R es un lenguaje case sensitive o sea que hay que prestar atención a mayúsculas y minúsculas.

  • Para instalar los paquetes hay que usar comillas

Este es un paso que hacemos una sola vez por computadora.

Para correr el código se tienen que parar en la línea de código que quieren usar y apretar las teclas Ctrl + Enter o bien el triángulo verde (como si fuera un ícono de “Play” ▶️)

# Instalar los paquetes readxl y tidyverse
install.packages("tidyverse")
install.packages("readxl")

Esto lo que hace es instalar paquetes desde CRAN, que es un repositorio donde se publican los paquetes, asegurando un estándar de calidad y de documentación que hace que trabajar con cualquier paquete de CRAN sea seguro.

Para usar las funciones de los paquetes que instalamos, ahora tenemos que cargarlos. Esto lo que hace es de alguna manera “activar” el paquete y que podamos empezar sus funciones.

Para cargar un paquete tenemos que usar la función library(). Recuerden prestar atención a las mayúsculas y minúsculas. Ahora no son necesarias las comillas.

# Cargar los paquetes readxl y tidyverse
library(readxl)
library(tidyverse)

Una de las formas en las que nos damos cuenta que el paquete está instalado es cuando empezamos a escribir su nombre y nos aparece el nombre del paquete para autocompletar. Esta es una de las ventajas de trabajar en RStudio.

Recuerden:

La instalación de los paquetes se hace una sola vez por computadora.

La carga de los paquete se se hace cada vez que se abre un script.

Instalar y cargar introR4hr

Para este curso desarrollamos un paquete, introR4hr que consiste en una serie de tutoriales interactivos que iremos compartiendo a lo largo del curso.

introR4hr no es un paquete publicado en CRAN por lo cual hay que descargar la versión de desarrollo desde el repositorio de GitHub y seguir los siguientes pasos:

# Instalar los paquetes remotes y learnr
install.packages("remotes")
install.packages("learnr")

# Instalar el paquete introR4hr desde GitHub
remotes::install_github("chechoid/introR4hr")

La notación remotes::install_github() reemplaza parcialmente a la función library() ya que no carga el paquete (ahorrando memoria) y sólo utiliza la función que está invocando.

Por eso luego usamos la siguiente sintaxis para cargar un tutorial interactivo.

learnr::run_tutorial("sesion0", "introR4hr")

Donde:

  • sesion0 es el nombre del tutorial
  • introR4hr es el nombre del paquete

Carga de datos

Como gran parte de los datos que vamos a usar en la vida son archivos de Excel vamos a usar la función read_excel() del paquete readxls para levantar dos archivos: el archivo maestro.xlsx que contiene un listado de empleados de una empresa, y el archivo salarios.xlsx que contiene el sueldo base de algunos de los empleados del listado.

# Cargar el archivo maestro.xlsx
maestro <- read_excel("maestro.xlsx")

# Cargar el archivo salarios.xlsx
salarios <- read_excel("salarios.xlsx")

Los archivos no necesariamente tienen que estar en una computadora. También se pueden cargar archivos desde internet, por ejemplo, desde repositorios de GitHub:

# Cargar un archivo desde GitHub
encuesta <- read.csv("https://raw.githubusercontent.com/r4hr/kiwi2020/main/rh_ar.csv", 
                     sep = ";",
                     encoding = "UTF-8")

Otra alternativa para leer archivos .csv cuando el delimitador entre campos no es una coma (,) es usar la función read_delim() del paquete readr que viene en tidyverse.

encuesta <- read_delim("https://raw.githubusercontent.com/r4hr/kiwi2020/main/rh_ar.csv",
                       delim = ";") # Indicamos el delimitador de campos

Una función que nos permite ver el contenido de estos data frames es la función View().

# Ver el contenido de los data frames maestro, salarios y encuesta
View(maestro)
View(salarios)
View(encuesta)

Otra función interesante para explorar el contenido de un data frame es la función glimpse() del paquete dplyr que forma parte del paquete tidyverse.

# Explorar los dataframes maestro, salarios y encuesta
glimpse(maestro)
## Rows: 522
## Columns: 8
## $ ID           <dbl> 180, 242, 260, 277, 278, 304, 314, 315, 345, 362, 375, 38…
## $ ANTIGUEDAD   <dbl> 38, 37, 36, 36, 36, 35, 35, 35, 40, 33, 32, 32, 32, 32, 3…
## $ EDAD         <dbl> 63, 64, 61, 64, 59, 57, 54, 56, 59, 59, 55, 62, 52, 52, 6…
## $ ESTADO_CIVIL <chr> "C", "C", "C", "S", "C", "K", "C", "K", "C", "S", "S", "S…
## $ HIJOS        <dbl> 1, 2, 2, 4, 0, 1, 4, 3, 2, 1, 5, 0, 4, 2, 3, 2, 2, 0, 4, …
## $ AREA         <chr> "PRODUCCION 1", "PRODUCCION 1", "PRODUCCION 1", "PRODUCCI…
## $ ID_CAT       <chr> "5", "5", "5", "5", "4", "4", "4", "4", "5", "4", "5", "5…
## $ N_CATEG      <chr> "OP. CAT. 5", "OP. CAT. 5", "OP. CAT. 5", "OP. CAT. 5", "…
glimpse(salarios)
## Rows: 135
## Columns: 3
## $ ID     <dbl> 597, 789, 791, 804, 816, 820, 851, 864, 867, 872, 892, 898, 918…
## $ PUESTO <chr> "TEC. MANTENIMIENTO", "VOLANTE", "JEFE", "LIDER DE EQUIPO", "LI…
## $ SUELDO <dbl> 25129, 24474, 44344, 34235, 26401, 26682, 23851, 25811, 25877, …
str(encuesta)
## spec_tbl_df [528 × 45] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ Marca.temporal            : POSIXct[1:528], format: "2020-09-30 20:29:06" "2020-09-30 21:45:26" ...
##  $ genero                    : chr [1:528] "Femenino" "Masculino" "Femenino" "Femenino" ...
##  $ genero_diverso            : chr [1:528] "No" "Si" "No" "Si" ...
##  $ edad                      : num [1:528] 35 33 24 32 32 26 29 30 34 41 ...
##  $ discapacidad              : chr [1:528] "No tengo ninguna discapacidad" "No tengo ninguna discapacidad" "No tengo ninguna discapacidad" "No tengo ninguna discapacidad" ...
##  $ nivel_formacion           : chr [1:528] "Universitario completo" "Universitario en curso" "Universitario en curso" "Secundario completo" ...
##  $ carrera_grado             : chr [1:528] "RRHH / RRLL / RRTT" "RRHH / RRLL / RRTT" "RRHH / RRLL / RRTT" "Contador Público/Lic. En Letras" ...
##  $ tipo_universidad          : chr [1:528] "Universidad Privada" "Universidad Pública" "Universidad Privada" "Universidad Pública" ...
##  $ pais                      : chr [1:528] "Argentina" "Argentina" "Argentina" "Argentina" ...
##  $ provincia                 : chr [1:528] "Buenos Aires" "Chaco" "Buenos Aires" "Buenos Aires" ...
##  $ trabajo                   : chr [1:528] "Relación de Dependencia" "Relación de Dependencia" "Relación de Dependencia" "Relación de Dependencia" ...
##  $ rubro                     : chr [1:528] "Comercio" "Otros" "Tecnología" "Hotelería, restauración, turismo" ...
##  $ dotacion                  : num [1:528] 1433 6000 270 700 7500 ...
##  $ origen_capital            : chr [1:528] "Nacional" "Multinacional" "Nacional" "Nacional" ...
##  $ dotacion_rh               : num [1:528] 10 55 7 4 25 3 9 2 10 1 ...
##  $ puesto                    : chr [1:528] "Analista" "HRBP" "Analista" "Gerente" ...
##  $ tipo_contratacion         : chr [1:528] "Full time" "Full time" "Full time" "Full time" ...
##  $ funcion_rh                : chr [1:528] "Administración de personal" "Relaciones laborales" "Reclutamiento y selección" "Generalista" ...
##  $ personas_a_cargo          : num [1:528] 0 8 0 3 0 0 2 1 0 0 ...
##  $ anios_en_empresa          : num [1:528] 0 9 1 10 2 3 5 7 2 0 ...
##  $ anios_en_puesto           : num [1:528] 0 3 1 3 2 3 3 4 2 0 ...
##  $ anios_experiencia         : num [1:528] 7 8 2 10 14 5 5 4 12 10 ...
##  $ sueldo_bruto              : num [1:528] 55700 55000 47000 86747 85500 ...
##  $ beneficios                : chr [1:528] "Tarjeta de descuento" "Medicina prepaga / Plan de salud, Horarios flexibles, Abono de celular, Seguro de vida (adicionales a los de le"| __truncated__ "Medicina prepaga / Plan de salud, Seguro de vida (adicionales a los de ley), Idiomas" "Horarios flexibles, Frutas y snacks, Abono de celular, Reintegros en capacitación, Home office, Comedor / Almue"| __truncated__ ...
##  $ bono                      : chr [1:528] "Menos de un sueldo" "No recibo bono" "No recibo bono" "No recibo bono" ...
##  $ ajuste                    : chr [1:528] "1 solo" "2 ajustes" "1 solo" "1 solo" ...
##  $ ajuste_porcentaje         : num [1:528] 20 50 25 50 10 15 17 25 30 0 ...
##  $ ajuste_mes                : chr [1:528] "Marzo" "Septiembre" "Septiembre" "Enero" ...
##  $ otros_proyectos           : chr [1:528] "No" "No" "No" "Si, en emprendimiento relacionado con RRHH" ...
##  $ erp                       : chr [1:528] "Enlatado propio" "SAP / SuccessFactors" "No tenemos sistema de gestión" "Tango" ...
##  $ nombre_area               : chr [1:528] "Recursos Humanos" "Recursos Humanos" "Recursos Humanos" "Recursos Humanos" ...
##  $ mate                      : chr [1:528] "Si, está permitido" "Si, está permitido" "Si, está permitido" "Si, está permitido" ...
##  $ idioma_exigencia          : chr [1:528] "No" "No" "No" "No" ...
##  $ idioma_porcentaje         : num [1:528] 0 0 0.2 0 0.2 0 0.1 NA 0 0 ...
##  $ contactos_linkedin        : num [1:528] 500 100 3000 1474 9240 ...
##  $ satisfaccion              : num [1:528] 3 5 3 5 2 3 4 NA 2 5 ...
##  $ busqueda                  : chr [1:528] "No,  pero escucho ofertas" "No,  pero escucho ofertas" "No,  pero escucho ofertas" "No estoy buscando cambiar" ...
##  $ beneficios_expectativa    : chr [1:528] "Vacaciones extendidas, almuerzo,medicina prepaga" "Bono semestral" "mas home, bonificaciones, comedor" "Idiomas" ...
##  $ rh_una_palabra            : chr [1:528] "Servicio" "Indispensable" "Empatia" "Valor" ...
##  $ pregunta_bizarra          : chr [1:528] "Si pensaba tener hijos en los próximos meses" "No recuerdo ninguna" "cual es tu signo?" NA ...
##  $ teletrabajo               : chr [1:528] "Voy rotando entre la oficina y el trabajo" "Si" "Voy rotando entre la oficina y el trabajo" "Voy rotando entre la oficina y el trabajo" ...
##  $ elementos                 : chr [1:528] "Computadora / Laptop" "Computadora / Laptop, Silla ergonómica, Abono de celular, Abono de internet" "Computadora / Laptop" "Computadora / Laptop" ...
##  $ valoracion_gestion_empresa: num [1:528] 3 5 3 3 2 4 4 NA NA NA ...
##  $ multiplicador             : num [1:528] 1 1 1 1 1 1 1 1 1 1 ...
##  $ sueldo_ft                 : num [1:528] 55700 55000 47000 86747 85500 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   Marca.temporal = col_datetime(format = ""),
##   ..   genero = col_character(),
##   ..   genero_diverso = col_character(),
##   ..   edad = col_double(),
##   ..   discapacidad = col_character(),
##   ..   nivel_formacion = col_character(),
##   ..   carrera_grado = col_character(),
##   ..   tipo_universidad = col_character(),
##   ..   pais = col_character(),
##   ..   provincia = col_character(),
##   ..   trabajo = col_character(),
##   ..   rubro = col_character(),
##   ..   dotacion = col_double(),
##   ..   origen_capital = col_character(),
##   ..   dotacion_rh = col_double(),
##   ..   puesto = col_character(),
##   ..   tipo_contratacion = col_character(),
##   ..   funcion_rh = col_character(),
##   ..   personas_a_cargo = col_double(),
##   ..   anios_en_empresa = col_double(),
##   ..   anios_en_puesto = col_double(),
##   ..   anios_experiencia = col_double(),
##   ..   sueldo_bruto = col_double(),
##   ..   beneficios = col_character(),
##   ..   bono = col_character(),
##   ..   ajuste = col_character(),
##   ..   ajuste_porcentaje = col_double(),
##   ..   ajuste_mes = col_character(),
##   ..   otros_proyectos = col_character(),
##   ..   erp = col_character(),
##   ..   nombre_area = col_character(),
##   ..   mate = col_character(),
##   ..   idioma_exigencia = col_character(),
##   ..   idioma_porcentaje = col_double(),
##   ..   contactos_linkedin = col_double(),
##   ..   satisfaccion = col_double(),
##   ..   busqueda = col_character(),
##   ..   beneficios_expectativa = col_character(),
##   ..   rh_una_palabra = col_character(),
##   ..   pregunta_bizarra = col_character(),
##   ..   teletrabajo = col_character(),
##   ..   elementos = col_character(),
##   ..   valoracion_gestion_empresa = col_double(),
##   ..   multiplicador = col_double(),
##   ..   sueldo_ft = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>

Esto que estamos haciendo se llama Análisis Exploratorio de Datos, pueden ver la sesión que hicimos en R4HR Club de R para RRHH en YouTube.

Tidyverse

RStudio además de desarrollar la IDE para usar R, es también uno de los principales desarrolladores de paquetes.

Uno de sus paquetes, que engloba varios paquetes, se convirtió en un estándar de la limpieza y transformación de datos, que es tidyverse. A este estándar de limpieza de datos se lo llama tidy data, en donde entre sus principios dice:

Cada variable forma una columna.

Cada observación tiene que estar en una fila.

Como decíamos, tidyverse es un paquete que engloba varios paquetes, todos muy utilizados, a veces declarativamente, otras veces R lo usa detrás de escenas. En esta sesión vamos a usar principalmente los paquetes dplyr.

Una de las razones por las cuales tidyverse se hizo tan popular es que la lógica de las funciones imita el razonamiento que haríamos verbalmente.

Por ejemplo, ¿cómo harían para calcular la edad promedio por área?

maestro %>% 
  select(AREA, EDAD) %>% # Selecciona las columnas
  group_by(AREA) %>%     # Agrupa por la variable AREA
  summarise(edad_promedio = mean(EDAD)) %>% # Crea una variable con la edad promedio
  arrange(-edad_promedio) # Ordena descendentemente los resultados por la variable que pasemos.
## # A tibble: 12 × 2
##    AREA             edad_promedio
##    <chr>                    <dbl>
##  1 COMPRAS                   47.3
##  2 GERENCIA GENERAL          45.5
##  3 ALMACEN                   42.4
##  4 DESARROLLO                40.5
##  5 PRODUCCION 1              38.4
##  6 LOGISTICA                 36.7
##  7 PRODUCCION 2              36.0
##  8 CALIDAD                   35.6
##  9 RRHH                      35.4
## 10 MASS                      30  
## 11 COMERCIAL                 29.8
## 12 FINANZAS                  29.5

dplyr

¿Vieron este símbolo %>% en la slide anterior? En la jerga lo conocemos como “pipe” (atajo de teclado Ctrl + Mayús + M). Lo que nos permite este “tubo” es ordenar el código en secuencias, haciéndolo más comprensible a la lectura.

Una de las ventajas del pipe es que no necesitamos invocar al dataframe en cada función que encadenamos en el pipe.

Por ejemplo, queremos ver los empledos que tienen hijos por cada área. Los pasos son:

  1. Elegir las variables AREA, ID, HIJOS.
  2. Filtrar de la variable HIJOS los casos en que sí tengan hijos.
  3. Ordenar los resultados por área.
# Versión sin pipe
arrange(filter(select(maestro, AREA,ID, HIJOS), HIJOS > 0), AREA)
## # A tibble: 377 × 3
##    AREA       ID HIJOS
##    <chr>   <dbl> <dbl>
##  1 ALMACEN   653     2
##  2 ALMACEN   741     4
##  3 ALMACEN   980     2
##  4 ALMACEN  1085     2
##  5 ALMACEN  1138     4
##  6 ALMACEN  1436     1
##  7 ALMACEN  3117     2
##  8 CALIDAD   404     2
##  9 CALIDAD   596     3
## 10 CALIDAD   789     2
## # … with 367 more rows

Esto mismo usando el pipe de dplyr lo hacemos así:

maestro %>%
  select(AREA, ID, HIJOS) %>%
  filter(HIJOS>0) %>%
  arrange(AREA) 
## # A tibble: 377 × 3
##    AREA       ID HIJOS
##    <chr>   <dbl> <dbl>
##  1 ALMACEN   653     2
##  2 ALMACEN   741     4
##  3 ALMACEN   980     2
##  4 ALMACEN  1085     2
##  5 ALMACEN  1138     4
##  6 ALMACEN  1436     1
##  7 ALMACEN  3117     2
##  8 CALIDAD   404     2
##  9 CALIDAD   596     3
## 10 CALIDAD   789     2
## # … with 367 more rows

Cheatsheets

Habitualmente a las funciones del paquete dplyr las llamamos verbos. Para muchos paquetes desarrollados por RStudio existe un cheat sheet o guía de referencia para ayudarnos a recordar las funciones más utilizadas y se encuentran en este link de su página.

En la sección Translations podemos encontrar muchos cheat sheets en español. Acá pueden descargar el de transformación de datos que veremos hoy.

left_join()

La función left_join() es un equivalente a la función de Excel BUSCARV o VLOOKUP. Esto nos permite trabajar con múltiples archivos sin necesidad de pasarlos manualmente uno por uno a un archivo que contenga a todos, sino traer la información que necesitemos.

Hay muchos tipos de join() distintos, por ejemplo right_join(), inner_join(), o anti_join() por ejemplo que no serán tema de este curso.

Vamos a crear un data frame combinando las tablas maestro y salarios.

# Hacer un left_join de maestro y salarios y guardarlo en un data frame llamado maestro_full
maestro_full <- left_join(maestro, salarios, by = "ID")

# Usar la función glimpse() en el nuevo data frame
glimpse(maestro_full)
## Rows: 522
## Columns: 10
## $ ID           <dbl> 180, 242, 260, 277, 278, 304, 314, 315, 345, 362, 375, 38…
## $ ANTIGUEDAD   <dbl> 38, 37, 36, 36, 36, 35, 35, 35, 40, 33, 32, 32, 32, 32, 3…
## $ EDAD         <dbl> 63, 64, 61, 64, 59, 57, 54, 56, 59, 59, 55, 62, 52, 52, 6…
## $ ESTADO_CIVIL <chr> "C", "C", "C", "S", "C", "K", "C", "K", "C", "S", "S", "S…
## $ HIJOS        <dbl> 1, 2, 2, 4, 0, 1, 4, 3, 2, 1, 5, 0, 4, 2, 3, 2, 2, 0, 4, …
## $ AREA         <chr> "PRODUCCION 1", "PRODUCCION 1", "PRODUCCION 1", "PRODUCCI…
## $ ID_CAT       <chr> "5", "5", "5", "5", "4", "4", "4", "4", "5", "4", "5", "5…
## $ N_CATEG      <chr> "OP. CAT. 5", "OP. CAT. 5", "OP. CAT. 5", "OP. CAT. 5", "…
## $ PUESTO       <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
## $ SUELDO       <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…

Ahora contamos con los datos del listado de personal y también de los salarios de un segmento de la gente de la compañía. Con lo cual podremos hacer un análisis de los salarios de la organización.

select()

Con la función select() lo que hacemos es elegir con qué columnas (variables queremos trabajar). Por ejemplo, elijamos del data frame encuesta las variables genero, puesto, tipo_universidad y sueldo_bruto.

# Seleccionar del data frame encuesta las variables genero y sueldo_bruto
encuesta %>%      # el atajo del pipe es Ctrl + Mayús + M
  select(genero, puesto, tipo_universidad, sueldo_bruto)
## # A tibble: 528 × 4
##    genero    puesto      tipo_universidad             sueldo_bruto
##    <chr>     <chr>       <chr>                               <dbl>
##  1 Femenino  Analista    Universidad Privada                 55700
##  2 Masculino HRBP        Universidad Pública                 55000
##  3 Femenino  Analista    Universidad Privada                 47000
##  4 Femenino  Gerente     Universidad Pública                 86747
##  5 Masculino HRBP        Universidad Privada                 85500
##  6 Femenino  Analista    Universidad Pública                 73600
##  7 Masculino HRBP        Universidad Pública                 80000
##  8 Femenino  Responsable Universidad Pública                 98948
##  9 Femenino  HRBP        Universidad Privada                 85000
## 10 Masculino Analista    No estudié en la Universidad        60241
## # … with 518 more rows

select() nos permite también elegir el orden en que queremos las columnas.

# Seleccionar las mismas variables en orden diferente
encuesta %>%
  select(tipo_universidad, sueldo_bruto, puesto, genero)
## # A tibble: 528 × 4
##    tipo_universidad             sueldo_bruto puesto      genero   
##    <chr>                               <dbl> <chr>       <chr>    
##  1 Universidad Privada                 55700 Analista    Femenino 
##  2 Universidad Pública                 55000 HRBP        Masculino
##  3 Universidad Privada                 47000 Analista    Femenino 
##  4 Universidad Pública                 86747 Gerente     Femenino 
##  5 Universidad Privada                 85500 HRBP        Masculino
##  6 Universidad Pública                 73600 Analista    Femenino 
##  7 Universidad Pública                 80000 HRBP        Masculino
##  8 Universidad Pública                 98948 Responsable Femenino 
##  9 Universidad Privada                 85000 HRBP        Femenino 
## 10 No estudié en la Universidad        60241 Analista    Masculino
## # … with 518 more rows

Podemos crear nuevos data frames con la selección de columnas que necesitemos. Esto es muy útil cuando estamos trabajando con muchos datos porque permite que la computadora sea más eficiente.

# Crear un nuevo data frame con las columnas seleccionadas
encuesta_chica <- encuesta %>%    # Asigno al nuevo data frame del data frame original
  select(genero, puesto, sueldo_bruto, tipo_universidad)                        # Selecciono las columnas que quiera

Con el signo menos (-) le podemos indicar a R las columnas que no necesitamos.

encuesta_chica %>% 
  select(-tipo_universidad)
## # A tibble: 528 × 3
##    genero    puesto      sueldo_bruto
##    <chr>     <chr>              <dbl>
##  1 Femenino  Analista           55700
##  2 Masculino HRBP               55000
##  3 Femenino  Analista           47000
##  4 Femenino  Gerente            86747
##  5 Masculino HRBP               85500
##  6 Femenino  Analista           73600
##  7 Masculino HRBP               80000
##  8 Femenino  Responsable        98948
##  9 Femenino  HRBP               85000
## 10 Masculino Analista           60241
## # … with 518 more rows

Para saber más sobre esta función recomendamos leer la página del paquete.

Ejercicio select()

Para realizar un análisis salarial hay columnas que no son interesantes para el análisis. Seleccionar las columnas ID, ANTIGUEDAD, EDAD, AREA, PUESTO y SUELDO.

# Seleccionar las columnas ID, ANTIGUEDAD, EDAD, AREA, PUESTO y SUELDO
# Llamar al nuevo data frame maestro2
maestro2 <- maestro_full %>% 
  select(ID, ANTIGUEDAD, EDAD, AREA, PUESTO, SUELDO)

filter()

Así como select() trabaja sobre las columnas, la función filter() lo hace sobre las filas. filter() nos permite seleccionar las observaciones que cumplan con los criterios que nos interesan.

Para esto, vamos a necesitar la ayuda de los operadores lógicos.

Operadores Lógicos
Símbolo Operador
== Igual a
!= Distinto a
< Menor que
<= Menor o igual que
> Mayor que
>= Mayor o igual que
& Y (AND)
| O (OR)

Para hacer los ejemplos más legibles vamos a crear una nueva versión de la encuesta seleccionando las variables genero, sueldo_bruto, provincia, anios_experiencia, edad y puesto. Esto lo vamos a almacenar en un nuevo data frame llamado encuesta2.

# Creamos el data frame encuesta2 con las variables genero, sueldo_bruto, provincia, anios_experiencia, edad y puesto
encuesta2 <- encuesta %>% 
  select(genero, sueldo_bruto, provincia, anios_experiencia, edad, puesto)

Por ejemplo, si quiero ver las respuestas de las personas de la provincia de San Juan tenemos que hacer lo siguiente:

# Seleccionar respuestas de San Juan
encuesta2 %>% 
  filter(provincia == "San Juan")
## # A tibble: 37 × 6
##    genero    sueldo_bruto provincia anios_experiencia  edad puesto        
##    <chr>            <dbl> <chr>                 <dbl> <dbl> <chr>         
##  1 Masculino       190000 San Juan                  7    33 Jefe          
##  2 Femenino         70800 San Juan                  8    35 Jefe          
##  3 Femenino         41000 San Juan                  8    31 Administrativo
##  4 Femenino         30000 San Juan                 14    39 Responsable   
##  5 Femenino         68000 San Juan                 12    36 Jefe          
##  6 Femenino         40000 San Juan                  9    28 Administrativo
##  7 Masculino       110000 San Juan                  9    36 Analista      
##  8 Femenino         62000 San Juan                  2    23 Responsable   
##  9 Masculino        73000 San Juan                 12    34 Responsable   
## 10 Femenino         80000 San Juan                  4    26 Gerente       
## # … with 27 more rows

Si yo quiero filtrar por ejemplo, las respuestas de Buenos Aires y cuya edad sea menor o igual a 30, podemos hacer lo siguiente:

# Seleccionar respuestas de Buenos Aires y que sean hasta 30 años
encuesta2 %>% 
  filter(provincia == "Buenos Aires" & 
           edad <= 30)
## # A tibble: 34 × 6
##    genero    sueldo_bruto provincia    anios_experiencia  edad puesto     
##    <chr>            <dbl> <chr>                    <dbl> <dbl> <chr>      
##  1 Femenino         47000 Buenos Aires                 2    24 Analista   
##  2 Femenino         62000 Buenos Aires                 8    29 Responsable
##  3 Femenino         69500 Buenos Aires                 5    30 Responsable
##  4 Masculino       116000 Buenos Aires                10    29 Responsable
##  5 Femenino         78000 Buenos Aires                 5    30 Analista   
##  6 Femenino         90000 Buenos Aires                 9    30 Analista   
##  7 Femenino         45000 Buenos Aires                 3    22 Jefe       
##  8 Masculino        70000 Buenos Aires                 5    27 Analista   
##  9 Femenino         65000 Buenos Aires                 5    27 Analista   
## 10 Femenino         60000 Buenos Aires                 2    27 Analista   
## # … with 24 more rows
# Reemplazar el símbolo & por una coma (,)
encuesta2 %>% 
  filter(provincia == "Buenos Aires",
         edad <= 30)
## # A tibble: 34 × 6
##    genero    sueldo_bruto provincia    anios_experiencia  edad puesto     
##    <chr>            <dbl> <chr>                    <dbl> <dbl> <chr>      
##  1 Femenino         47000 Buenos Aires                 2    24 Analista   
##  2 Femenino         62000 Buenos Aires                 8    29 Responsable
##  3 Femenino         69500 Buenos Aires                 5    30 Responsable
##  4 Masculino       116000 Buenos Aires                10    29 Responsable
##  5 Femenino         78000 Buenos Aires                 5    30 Analista   
##  6 Femenino         90000 Buenos Aires                 9    30 Analista   
##  7 Femenino         45000 Buenos Aires                 3    22 Jefe       
##  8 Masculino        70000 Buenos Aires                 5    27 Analista   
##  9 Femenino         65000 Buenos Aires                 5    27 Analista   
## 10 Femenino         60000 Buenos Aires                 2    27 Analista   
## # … with 24 more rows

Si prestamos atención, cuando filtramos por algún valor que contenga texto necesitamos usar las comillas, mientras que para las variables numéricas esto no es necesario.

Si queremos filtrar varios valores de una misma columna lo ideal es trabajar con un vector.

# Selección poco eficiente
encuesta2 %>% 
  filter(provincia == "Buenos Aires" | provincia == "Santa Fe" | provincia == "Corrientes" |
           provincia == "Córdoba" | provincia == "San Juan")
## # A tibble: 222 × 6
##    genero    sueldo_bruto provincia    anios_experiencia  edad puesto        
##    <chr>            <dbl> <chr>                    <dbl> <dbl> <chr>         
##  1 Femenino         55700 Buenos Aires                 7    35 Analista      
##  2 Femenino         47000 Buenos Aires                 2    24 Analista      
##  3 Femenino         86747 Buenos Aires                10    32 Gerente       
##  4 Masculino        85500 Buenos Aires                14    32 HRBP          
##  5 Masculino        65662 Buenos Aires                 9    33 HRBP          
##  6 Masculino       190000 San Juan                     7    33 Jefe          
##  7 Femenino         70800 San Juan                     8    35 Jefe          
##  8 Femenino         41000 San Juan                     8    31 Administrativo
##  9 Femenino         30000 San Juan                    14    39 Responsable   
## 10 Femenino         40000 Santa Fe                    20    38 Responsable   
## # … with 212 more rows
# Selección más eficiente con el operador %in%
encuesta2 %>% 
  filter(provincia %in% c("Buenos Aires", "Santa Fe", "Corrientes", "Córdoba", "San Juan"))
## # A tibble: 222 × 6
##    genero    sueldo_bruto provincia    anios_experiencia  edad puesto        
##    <chr>            <dbl> <chr>                    <dbl> <dbl> <chr>         
##  1 Femenino         55700 Buenos Aires                 7    35 Analista      
##  2 Femenino         47000 Buenos Aires                 2    24 Analista      
##  3 Femenino         86747 Buenos Aires                10    32 Gerente       
##  4 Masculino        85500 Buenos Aires                14    32 HRBP          
##  5 Masculino        65662 Buenos Aires                 9    33 HRBP          
##  6 Masculino       190000 San Juan                     7    33 Jefe          
##  7 Femenino         70800 San Juan                     8    35 Jefe          
##  8 Femenino         41000 San Juan                     8    31 Administrativo
##  9 Femenino         30000 San Juan                    14    39 Responsable   
## 10 Femenino         40000 Santa Fe                    20    38 Responsable   
## # … with 212 more rows

Hay funciones auxiliares de filter que nos permiten hacer cosas interesantes. Una de ellas es between() que permite hacer selecciones entre un rango de valores:

# Filtrar los sueldos brutos entre 50.000 y 100.000
encuesta2 %>% 
  filter(between(sueldo_bruto, # Nombre de la variable
                 50000,        # Valor mínimo del filtro
                 100000))      # Valor máximo del filtro
## # A tibble: 315 × 6
##    genero    sueldo_bruto provincia                       anios_e…¹  edad puesto
##    <chr>            <dbl> <chr>                               <dbl> <dbl> <chr> 
##  1 Femenino         55700 Buenos Aires                            7    35 Anali…
##  2 Masculino        55000 Chaco                                   8    33 HRBP  
##  3 Femenino         86747 Buenos Aires                           10    32 Geren…
##  4 Masculino        85500 Buenos Aires                           14    32 HRBP  
##  5 Femenino         73600 Ciudad Autónoma de Buenos Aires         5    26 Anali…
##  6 Masculino        80000 Ciudad Autónoma de Buenos Aires         5    29 HRBP  
##  7 Femenino         98948 Ciudad Autónoma de Buenos Aires         4    30 Respo…
##  8 Femenino         85000 Jujuy                                  12    34 HRBP  
##  9 Masculino        60241 Tucumán                                10    41 Anali…
## 10 Masculino        65662 Buenos Aires                            9    33 HRBP  
## # … with 305 more rows, and abbreviated variable name ¹​anios_experiencia

Ejercicio filter()

Filtrar de encuesta2 las respuestas cuyo puesto sea Analista y HRBP.

# Filtrar las respuestas cuyo puesto sea Analista o HRBP
encuesta2 %>% 
  filter(puesto %in% c("Analista", "HRBP"))
## # A tibble: 265 × 6
##    genero    sueldo_bruto provincia                       anios_e…¹  edad puesto
##    <chr>            <dbl> <chr>                               <dbl> <dbl> <chr> 
##  1 Femenino         55700 Buenos Aires                            7    35 Anali…
##  2 Masculino        55000 Chaco                                   8    33 HRBP  
##  3 Femenino         47000 Buenos Aires                            2    24 Anali…
##  4 Masculino        85500 Buenos Aires                           14    32 HRBP  
##  5 Femenino         73600 Ciudad Autónoma de Buenos Aires         5    26 Anali…
##  6 Masculino        80000 Ciudad Autónoma de Buenos Aires         5    29 HRBP  
##  7 Femenino         85000 Jujuy                                  12    34 HRBP  
##  8 Masculino        60241 Tucumán                                10    41 Anali…
##  9 Masculino        65662 Buenos Aires                            9    33 HRBP  
## 10 Masculino        70000 Ciudad Autónoma de Buenos Aires         5    31 Anali…
## # … with 255 more rows, and abbreviated variable name ¹​anios_experiencia

Crear un nuevo data frame, llamado mensuales que contenga todo lo que no sea NA de la columna PUESTO del data frame maestro2. Usar !is.na() dentro de filter().

# Crear un nuevo data frame llamado mensuales de maestro2
# Filtrar todo lo que NO SEA nulo (NA) de la columna PUESTO
mensuales <- maestro_full %>% 
filter(!is.na(PUESTO))


# Ver los resultados con glimpse()
glimpse(mensuales)
## Rows: 135
## Columns: 10
## $ ID           <dbl> 597, 789, 791, 804, 816, 820, 851, 864, 867, 872, 892, 89…
## $ ANTIGUEDAD   <dbl> 23, 12, 12, 11, 10, 10, 9, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, …
## $ EDAD         <dbl> 55, 48, 34, 33, 40, 37, 41, 33, 28, 43, 41, 30, 30, 42, 3…
## $ ESTADO_CIVIL <chr> "C", "C", "C", "C", "C", "K", "C", "C", "S", "K", "C", "C…
## $ HIJOS        <dbl> 5, 2, 0, 3, 3, 2, 1, 2, 0, 1, 1, 1, 2, 3, 2, 4, 2, 0, 2, …
## $ AREA         <chr> "PRODUCCION 1", "CALIDAD", "PRODUCCION 1", "PRODUCCION 1"…
## $ ID_CAT       <chr> "G", "F", "F", "F", "F", "E", "F", "F", "G", "F", "F", "F…
## $ N_CATEG      <chr> "Cat : G", "Cat : F", "LIDER DE EQUIPO", "LIDER DE EQUIPO…
## $ PUESTO       <chr> "TEC. MANTENIMIENTO", "VOLANTE", "JEFE", "LIDER DE EQUIPO…
## $ SUELDO       <dbl> 25129, 24474, 44344, 34235, 26401, 26682, 23851, 25811, 2…

group_by() + summarise()

group_by() es una función que se usa mucho en conjunto con summarise(). Por ejemplo cuando decimos:

  • Quiero saber la edad promedio por categoría, agrupamos por categoría y calculando por edad.

  • Quiero saber la mediana salarial por género estamos agrupando por género y calculando por sueldo.

  • Quiero saber la antigüedad máxima por sector estamos agrupando por sector y calculando por antiguedad.

Con group_by() lo que hacemos es agrupar todas las filas de un data frame y reducirlas a una cantidad más chica de filas. La cantidad de filas va a estar determinada por los distintos valores que haya en una columna.

Por otro lado summarise() lo que hace es añadir una columna nueva a ese data frame agrupado con algún cálculo que nos interese realizar.

Usemos el data frame encuesta2 para ver algunos ejemplos. Primero, calculemos los años de experiencia promedio por puesto.

unique(encuesta2$puesto)
## [1] "Analista"       "HRBP"           "Gerente"        "Responsable"   
## [5] "Jefe"           "Administrativo"
# Agrupar por puesto y calcular el promedio de anios_experiencia
encuesta2 %>% 
  group_by(puesto) %>% 
  summarise(antiguedad_promedio = mean(anios_experiencia))
## # A tibble: 6 × 2
##   puesto         antiguedad_promedio
##   <chr>                        <dbl>
## 1 Administrativo                4.03
## 2 Analista                      6.52
## 3 Gerente                      13.3 
## 4 HRBP                          7.5 
## 5 Jefe                         12.9 
## 6 Responsable                   8.42

También podemos agrupar por más de una variable. También podemos hacer más de un cálculo a la vez. Calculemos la mediana y el promedio de los sueldos por puesto y por genero.

# Agrupar por puesto y genero y calcular la mediana y el promedio
encuesta2 %>% 
  group_by(puesto, genero) %>% 
  summarise(mediana_salarial = median(sueldo_bruto),
            promedio_salarial = mean(sueldo_bruto))
## # A tibble: 12 × 4
## # Groups:   puesto [6]
##    puesto         genero    mediana_salarial promedio_salarial
##    <chr>          <chr>                <dbl>             <dbl>
##  1 Administrativo Femenino             44840            48107.
##  2 Administrativo Masculino            48200            53736.
##  3 Analista       Femenino             60000            62586.
##  4 Analista       Masculino            70000            75153.
##  5 Gerente        Femenino            147500           135608.
##  6 Gerente        Masculino           147000           156023.
##  7 HRBP           Femenino             85200            93234.
##  8 HRBP           Masculino            84500            95882.
##  9 Jefe           Femenino            102500           104159.
## 10 Jefe           Masculino           104500           119824.
## 11 Responsable    Femenino             70000            73944.
## 12 Responsable    Masculino            80000            89864.

En R siempre hay más de una forma de llegar al mismo resultado. Por ejemplo. Si queremos contar la cantidad de respuestas por tipo_universidad podemos:

# Agrupar por tipo_universidad + summarise() + n()
encuesta %>% 
  group_by(tipo_universidad) %>% 
  summarise(cantidad = n())
## # A tibble: 3 × 2
##   tipo_universidad             cantidad
##   <chr>                           <int>
## 1 No estudié en la Universidad       22
## 2 Universidad Privada               238
## 3 Universidad Pública               268
# Agrupar por tipo_universidad + tally()
encuesta %>% 
  group_by(tipo_universidad) %>% 
  tally()
## # A tibble: 3 × 2
##   tipo_universidad                 n
##   <chr>                        <int>
## 1 No estudié en la Universidad    22
## 2 Universidad Privada            238
## 3 Universidad Pública            268
# Contar por tipo de universidad count()
encuesta %>% 
  count(tipo_universidad)
## # A tibble: 3 × 2
##   tipo_universidad                 n
##   <chr>                        <int>
## 1 No estudié en la Universidad    22
## 2 Universidad Privada            238
## 3 Universidad Pública            268

Ejercicio group_by() + summarise()

Del data frame mensuales calcular el promedio salarial por puesto. No hace falta guardar los resultados en un objeto.

# Calcular el sueldo promedio por puesto
mensuales %>% 
  group_by(PUESTO) %>% 
  summarise(sueldo_promedio = mean(SUELDO))
## # A tibble: 10 × 2
##    PUESTO             sueldo_promedio
##    <chr>                        <dbl>
##  1 ANALISTA                    22971.
##  2 ENCARGADO                   26738 
##  3 GERENTE                     75098.
##  4 GERENTE GENERAL             94128 
##  5 INGENIERO                   31240.
##  6 JEFE                        44547 
##  7 LIDER DE EQUIPO             31434.
##  8 TEC. DESARROLLO             26755.
##  9 TEC. MANTENIMIENTO          28940.
## 10 VOLANTE                     24422.

Calcular la antigüedad promedio por área.

# Calcular la antigüedad promedio por área
mensuales %>% 
  group_by(AREA) %>% 
  summarise(edad_promedio = mean(EDAD))
## # A tibble: 12 × 2
##    AREA             edad_promedio
##    <chr>                    <dbl>
##  1 ALMACEN                   45.8
##  2 CALIDAD                   36.8
##  3 COMERCIAL                 29.8
##  4 COMPRAS                   47.3
##  5 DESARROLLO                40.5
##  6 FINANZAS                  29.5
##  7 GERENCIA GENERAL          45.5
##  8 LOGISTICA                 37.6
##  9 MASS                      30  
## 10 PRODUCCION 1              40.5
## 11 PRODUCCION 2              35.6
## 12 RRHH                      35.4

Calcular el sueldo máximo y el mínimo por puesto.

mensuales %>% 
  group_by(PUESTO) %>% 
  summarise(sueldo_minimo = min(SUELDO),
            sueldo_maximo = max(SUELDO))
## # A tibble: 10 × 3
##    PUESTO             sueldo_minimo sueldo_maximo
##    <chr>                      <dbl>         <dbl>
##  1 ANALISTA                   14776         38010
##  2 ENCARGADO                  25811         27665
##  3 GERENTE                    64016         86366
##  4 GERENTE GENERAL            94128         94128
##  5 INGENIERO                  22200         42824
##  6 JEFE                       31879         61994
##  7 LIDER DE EQUIPO            22163         41410
##  8 TEC. DESARROLLO            18114         37618
##  9 TEC. MANTENIMIENTO         15975         40680
## 10 VOLANTE                    22380         27124

mutate()

mutate es una función que nos permite agregar nuevas columnas al data frame en base a columnas existentes en nuestra tabla.

Tomemos la tabla encuesta2 y agreguemos una columna nueva con el sueldo en dólares. Para eso hay que dividir a la columna sueldo_bruto por 100

# Agregar a encuesta2 la columna sueldo_dolar
encuesta2 <- encuesta2 %>% 
  mutate(sueldo_dolar = sueldo_bruto / 100)

# Ver el resultado
glimpse(encuesta2)
## Rows: 528
## Columns: 7
## $ genero            <chr> "Femenino", "Masculino", "Femenino", "Femenino", "Ma…
## $ sueldo_bruto      <dbl> 55700, 55000, 47000, 86747, 85500, 73600, 80000, 989…
## $ provincia         <chr> "Buenos Aires", "Chaco", "Buenos Aires", "Buenos Air…
## $ anios_experiencia <dbl> 7, 8, 2, 10, 14, 5, 5, 4, 12, 10, 13, 9, 5, 10, 3, 3…
## $ edad              <dbl> 35, 33, 24, 32, 32, 26, 29, 30, 34, 41, 37, 33, 31, …
## $ puesto            <chr> "Analista", "HRBP", "Analista", "Gerente", "HRBP", "…
## $ sueldo_dolar      <dbl> 557.00, 550.00, 470.00, 867.47, 855.00, 736.00, 800.…

También nos permite modificar las variables existentes. Por ejemplo, calculemos el sueldo promedio por puesto.

# Calculemos el sueldo promedio por puesto con group_by() y summarise()
encuesta2 %>% 
  group_by(puesto) %>% 
  summarise(sueldo_promedio = mean(sueldo_bruto))
## # A tibble: 6 × 2
##   puesto         sueldo_promedio
##   <chr>                    <dbl>
## 1 Administrativo          49144.
## 2 Analista                66040.
## 3 Gerente                144169.
## 4 HRBP                    94411.
## 5 Jefe                   109543.
## 6 Responsable             78475.

El resultado está ordenado alfabéticamente, lo cual no es ideal. Esto ocurre porque la variable puesto es una variable maestrol (tipo character). Para convertirla en una variable ordinal necesitamos transformar la variable en una variable de tipo factor.

# Sobrescribir la variable puesto como factor
encuesta2 <- encuesta2 %>% 
  mutate(puesto = factor(puesto,
                         levels = c("Gerente", "Jefe", "Responsable",
                                    "HRBP", "Analista", "Administrativo")))

glimpse(encuesta2)
## Rows: 528
## Columns: 7
## $ genero            <chr> "Femenino", "Masculino", "Femenino", "Femenino", "Ma…
## $ sueldo_bruto      <dbl> 55700, 55000, 47000, 86747, 85500, 73600, 80000, 989…
## $ provincia         <chr> "Buenos Aires", "Chaco", "Buenos Aires", "Buenos Air…
## $ anios_experiencia <dbl> 7, 8, 2, 10, 14, 5, 5, 4, 12, 10, 13, 9, 5, 10, 3, 3…
## $ edad              <dbl> 35, 33, 24, 32, 32, 26, 29, 30, 34, 41, 37, 33, 31, …
## $ puesto            <fct> Analista, HRBP, Analista, Gerente, HRBP, Analista, H…
## $ sueldo_dolar      <dbl> 557.00, 550.00, 470.00, 867.47, 855.00, 736.00, 800.…

Hay varias funciones auxiliares, una muy interesante para construir categorías en función de una variable numérica es case_when() que se usa dentro de mutate().

# Crear una variable nueva con rangos de edad y guardarlo en encuesta3
encuesta3 <- encuesta2 %>% 
  select(edad, sueldo_bruto) %>% 
  mutate(rango_edad = case_when(
    edad <= 30 ~ "Hasta 30",
    edad <= 40 ~ "Hasta 40",
    edad = TRUE ~ "Más de 40"
  ))

# Calcular el sueldo promedio por rango de edad.
encuesta3 %>% 
  group_by(rango_edad) %>% 
  summarise(sueldo_promedio = mean(sueldo_bruto))
## # A tibble: 3 × 2
##   rango_edad sueldo_promedio
##   <chr>                <dbl>
## 1 Hasta 30            66364.
## 2 Hasta 40            87458.
## 3 Más de 40           98767.

Ejercicio mutate()

En Argentina los impuestos que tienen que pagar las personas asalariadas son:

  • 11% por Jubilación.

  • 3% por Seguridad Social

  • 3% por Obra Social (cobertura de salud).

Crear una columna nueva que se llame carga_social que resulte de multiplicar la columna SUELDO por 0.17

# Calcular las cargas sociales de los sueldos
mensuales <- mensuales %>% 
  mutate(carga_social = SUELDO * 0.17)

# Ver los resultados con glimpse()
glimpse(mensuales)
## Rows: 135
## Columns: 11
## $ ID           <dbl> 597, 789, 791, 804, 816, 820, 851, 864, 867, 872, 892, 89…
## $ ANTIGUEDAD   <dbl> 23, 12, 12, 11, 10, 10, 9, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, …
## $ EDAD         <dbl> 55, 48, 34, 33, 40, 37, 41, 33, 28, 43, 41, 30, 30, 42, 3…
## $ ESTADO_CIVIL <chr> "C", "C", "C", "C", "C", "K", "C", "C", "S", "K", "C", "C…
## $ HIJOS        <dbl> 5, 2, 0, 3, 3, 2, 1, 2, 0, 1, 1, 1, 2, 3, 2, 4, 2, 0, 2, …
## $ AREA         <chr> "PRODUCCION 1", "CALIDAD", "PRODUCCION 1", "PRODUCCION 1"…
## $ ID_CAT       <chr> "G", "F", "F", "F", "F", "E", "F", "F", "G", "F", "F", "F…
## $ N_CATEG      <chr> "Cat : G", "Cat : F", "LIDER DE EQUIPO", "LIDER DE EQUIPO…
## $ PUESTO       <chr> "TEC. MANTENIMIENTO", "VOLANTE", "JEFE", "LIDER DE EQUIPO…
## $ SUELDO       <dbl> 25129, 24474, 44344, 34235, 26401, 26682, 23851, 25811, 2…
## $ carga_social <dbl> 4271.93, 4160.58, 7538.48, 5819.95, 4488.17, 4535.94, 405…

Crear una columna nueva que se llama costo_anual que sea el resultado de multiplicar las columnas SUELDO más carga_social por 13.

# Crear una columna que sume SUELDO + carga_social y lo multiplique por 13
mensuales <- mensuales %>% 
  mutate(costo_anual = (SUELDO + carga_social)*13)

# Ver los resultados con glimpse()
glimpse(mensuales)
## Rows: 135
## Columns: 12
## $ ID           <dbl> 597, 789, 791, 804, 816, 820, 851, 864, 867, 872, 892, 89…
## $ ANTIGUEDAD   <dbl> 23, 12, 12, 11, 10, 10, 9, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, …
## $ EDAD         <dbl> 55, 48, 34, 33, 40, 37, 41, 33, 28, 43, 41, 30, 30, 42, 3…
## $ ESTADO_CIVIL <chr> "C", "C", "C", "C", "C", "K", "C", "C", "S", "K", "C", "C…
## $ HIJOS        <dbl> 5, 2, 0, 3, 3, 2, 1, 2, 0, 1, 1, 1, 2, 3, 2, 4, 2, 0, 2, …
## $ AREA         <chr> "PRODUCCION 1", "CALIDAD", "PRODUCCION 1", "PRODUCCION 1"…
## $ ID_CAT       <chr> "G", "F", "F", "F", "F", "E", "F", "F", "G", "F", "F", "F…
## $ N_CATEG      <chr> "Cat : G", "Cat : F", "LIDER DE EQUIPO", "LIDER DE EQUIPO…
## $ PUESTO       <chr> "TEC. MANTENIMIENTO", "VOLANTE", "JEFE", "LIDER DE EQUIPO…
## $ SUELDO       <dbl> 25129, 24474, 44344, 34235, 26401, 26682, 23851, 25811, 2…
## $ carga_social <dbl> 4271.93, 4160.58, 7538.48, 5819.95, 4488.17, 4535.94, 405…
## $ costo_anual  <dbl> 382212.1, 372249.5, 674472.2, 520714.3, 401559.2, 405833.…

Check out

Tidyverse es un paquete que está en continuo desarrollo. Pueden explorar el contenido actualizado en tidyverse.org y aprender más sobre su uso en el libro R para Ciencia de Datos.

Recuerden hacer todas las consultas en el canal #auxilio en Slack

LS0tDQp0aXRsZTogIkNsYXNlIDEgLSBDYXJnYSB5IExtcGllemEgZGUgRGF0b3MiDQphdXRob3I6ICJTZXJnaW8gR2FyY2lhIE1vcmEgfCBEYXRhIDRIUiINCmRhdGU6ICIxMS8xMC8yMDIyIg0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0aGVtZTogbHVtZW4NCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLnJldGluYSA9IDMsIG91dC53aWR0aCA9ICI4MCUiKQ0KYGBgDQoNCiMgQ3Vyc28gSW50cm9kdWNjacOzbiBhIFIgcGFyYSBSUkhIDQoNCkJpZW52ZW5pZGFzIHkgYmllbnZlbmlkb3MgYWwgY3Vyc28gKipJbnRyb2R1Y2Npw7NuIGEgUiBwYXJhIFJSSEgqKiDwn5Gp4oCN8J+Suy4gUiBlcyB1biBsZW5ndWFqZSBvcGVuIHNvdXJjZSwgZXMgZGVjaXIgcXVlIGVzIGFiaWVydG8sIGdyYXR1aXRvLCB5IHF1ZSBhZGVtw6FzIGxhIGNvbXVuaWRhZCBkZSB1c3VhcmlhcyB5IHVzdWFyaW9zIGNvbnRyaWJ1eWUgYSBzdSBtYW50ZW5pbWllbnRvLg0KDQojIyBDcmVhciBwcm95ZWN0b3MgZW4gUlN0dWRpbw0KDQpUcmFiYWphciBjb24gcHJveWVjdG9zIGVuIFJTdHVkaW8gaGFjZSBxdWUgdG9kbyBlbCB0cmFiYWpvIHNlYSBtw6FzIHNlbmNpbGxvLiBMb3MgcHJveWVjdG9zIGNyZWFuIHVuYSBjYXJwZXRhIGVuIG51ZXN0cmEgUEMgZW4gZG9uZGUgc2UgYWxtYWNlbmFyw6FuIGxvcyBhcmNoaXZvcywgdGFibGFzLCBzY3JpcHRzLCB5IGhhY2UgcXVlIHRvZG8gc2VhIG3DoXMgb3JnYW5pemFkby4NCg0KUGFyYSBjcmVhciB1biBwcm95ZWN0byB0ZW7DqXMgcG9kw6lzIGVudHJhciBlbjogXCogKkZpbGUqIFwqICpOZXcgcHJvamVjdCoNCg0KWSBsdWVnbyBwb25lciBlbCBub21icmUgZGUgbGEgY2FycGV0YS4NCg0KIVtdKEFyY2hpdm9zL25ld19wcm9qZWN0LnBuZyl7d2lkdGg9IjMzNyJ9DQoNClNlIGFicmUgdW5hIHZlbnRhbmEsIGhhY2Vtb3MgY2xpY2sgZW4gKk5ldyBQcm9qZWN0KiwgbHVlZ28gZW4gKk9rKiB5IGZpbmFsbWVudGUgZW4gbGEgdGVyY2VyYSB2ZW50YW5hIHBvZGVtb3MgZGFybGUgdW4gbm9tYnJlIGFsIHByb3llY3RvLCB5IGFzaWduYXIgbGEgY2FycGV0YSBlbiBsYSBxdWUgcXVlcmVtb3MgZ3VhcmRhcmxvIGNvbiBlbCBib3TDs24gKkJyb3dzZSouDQoNCiFbXShBcmNoaXZvcy9uZXdfcHJvamVjdG8ucG5nKQ0KDQojIyBPYmpldG9zDQoNClBvciBkZWZpbmljacOzbiwgUiBlcyB1biBsZW5ndWFqZSAqb3JpZW50YWRvIGEgb2JqZXRvcyouIEVzdG8gc2lnbmlmaWNhIHF1ZSBhIHVuYSB0YWJsYSwgdW4gdmFsb3IsIHVuYSB2YXJpYWJsZSwgZXRjLiBsZSB2b3kgYSBwb25lciB1biBub21icmUsIHkgZXNhIGNvc2EgcXVlIGFob3JhIHRpZW5lIG5vbWJyZSBzZSBsbGFtYSAqKm9iamV0byoqLg0KDQpFc3RvIG5vcyBzaW1wbGlmaWNhIHVuIG1vbnTDs24gbGEgdmlkYS4gTGEgYXNpZ25hY2nDs24gbGEgaGFjZW1vcyBjb24gZWwgc8OtbWJvbG8gYDwtYCBxdWUgc2ltdWxhIHVuYSBmbGVjaGEgaGFjaWEgbGEgaXpxdWllcmRhLiBUYW1iacOpbiBzZSBwdWVkZSBoYWNlciBjb24gZWwgc8OtbWJvbG8gYD1gIHBlcm8gY29tbyBtdWNoYXMgdmVjZXMgbG8gdXRpbGl6YW1vcyBwYXJhIGNvbmZpZ3VyYXIgdW4gcGFyw6FtZXRybyBkZSB1bmEgZnVuY2nDs24gdXNhbW9zICoibGEgZmxlY2hpdGEiKi4NCg0KIyMjIFZhbG9yZXMNCg0KYGBge3Igb2JqZXRvc30NCiMgRXN0byBlcyB1biBjb21lbnRhcmlvDQoNCm9iamV0b18xIDwtIDE5NzkNCm9iamV0b18yIDwtIDIwMjENCg0KIyBQYXJhIHZlciBlbCByZXN1bHRhZG8gZGVsIG9iamV0byB1c28gbGFzIHRlY2xhcyBDdHJsICsgRW50ZXINCiMgVGFtYmnDqW4gcHVlZG8gdXNhciBlbCDDrWNvbm8gICJSdW4iDQpvYmpldG9fMQ0KDQojIFBvZGVtb3MgaGFjZXIgb3BlcmFjaW9uZXMgY29uIG9iamV0b3MNCm9iamV0b18yIC0gb2JqZXRvXzENCmBgYA0KDQojIyMgVmVjdG9yZXMNCg0KVW5vIGRlIGxvcyBvYmpldG9zIG3DoXMgdXRpbGl6YWRvcyBzb24gbG9zICoqdmVjdG9yZXMqKiBxdWUgc29uIGNvbmp1bnRvcyBkZSB2YWxvcmVzLCBudW3DqXJpY29zLCBkZSB0ZXh0bywgbyBjb21iaW5hZG9zLiBQYXJhIGNyZWFyIHVuIHZlY3RvciBuZWNlc2l0YW1vcyB1c2FyIGxhIGZ1bmNpw7NuIGBjKClgIHF1ZSBzaWduaWZpY2EgKmNvbWJpbmUqLg0KDQpMb3MgdmVjdG9yZXMgcHVlZGVuIHRlbmVyIHRleHRvIG8gbsO6bWVyby4NCg0KYGBge3IgdmVjdG9yZXN9DQojIFZlY3RvciBkZSBUZXh0bw0Kbm9tYnJlIDwtIGMoIkd1c3Rhdm8iLCAiQ2hhcmx5IiwgIlpldGEiKSAjIExhcyB2YXJpYWJsZXMgZGUgdGV4dG8gdmFuIHNpZW1wcmUgY29uIGNvbWlsbGFzLg0KDQojIFZlciBlbCBjb250ZW5pZG8NCm5vbWJyZQ0KDQojIFNlbGVjY2lvbmFyIHVuIGVsZW1lbnRvIGRlbCB2ZWN0b3IgDQpub21icmVbMl0NCg0KIyBWZWN0b3IgbnVtw6lyaWNvDQphbmlvX25hY2ltaWVudG8gPC0gYygxOTU5LCAxOTYzLCAxOTU4KQ0KDQpgYGANCg0KTG9zIHZlY3RvcmVzIHNvbiBtdXkgdXRpbGl6YWRvcyBwYXJhIGZpbHRyYXIgZWxlbWVudG9zLCBjYW1iaWFyIG5vbWJyZXMgZGUgdGFibGFzLCBldGMuLg0KDQojIyMgRGF0YSBmcmFtZXMNCg0KTG9zICoqZGF0YSBmcmFtZXMqKiBzb24gdGFibGVzIHF1ZSBjb250aWVuZW4gZmlsYXMgKG9ic2VydmFjaW9uZXMpIHkgY29sdW1uYXMgKHZhcmlhYmxlcykuDQoNClBvZGVtb3MgY3JlYXIgdW4gZGF0YSBmcmFtZSB1c2FuZG8gdmVjdG9yZXMgcXVlICoqdGVuZ2FuIGxhIG1pc21hIGNhbnRpZGFkIGRlIGVsZW1lbnRvcyoqIHVzYW5kbyBsYSBmdW5jacOzbiBgZGF0YS5mcmFtZSgpYC4gRGVudHJvIGRlIGxhIGZ1bmNpw7NuIHBvZGVtb3MgcG9uZXIgbG9zIG5vbWJyZXMNCg0KYGBge3IgZGF0YWZyYW1lc30NCiMgQ3JlYXIgZWwgZGF0YSBmcmFtZSBzb2RhX3N0ZXJlbyB1c2FuZG8gbG9zIHZlY3RvcmVzIG5vbWJyZSB5IGFuaW9fbmFjaW1pZW50bw0Kc29kYV9zdGVyZW8gPC0gZGF0YS5mcmFtZShub21icmUsIGFuaW9fbmFjaW1pZW50bykNCg0KIyBTZWxlY2Npb25hciBlbGVtZW50b3MgZGUgdW4gdmVjdG9yIHVzYW5kbyBsYSBzaW50YXhpczogbm9tYnJlX2RhdGFmcmFtZVtmaWxhLCBjb2x1bW5hXQ0Kc29kYV9zdGVyZW9bMiwxXQ0KYGBgDQoNCk5vcm1hbG1lbnRlIGxvcyBkYXRhIGZyYW1lcyBsb3MgY2FyZ2Ftb3MgZGUgYWxnw7puIGFyY2hpdm8uIFBvciBlamVtcGxvLCBjYXJndWVtb3MgZWwgYXJjaGl2byBgcm90YWNpb24uY3N2YCB1c2FuZG8gbGEgZnVuY2nDs24gYHJlYWQuY3N2YC4NCg0KYGBge3IgY2FyZ2ExfQ0KIyBDcmVhciB1biBvYmpldG8gbGxhbWFkbyByb3RhY2lvbiBsZXllbmRvIGVsIGFyY2hpdm8gcm90YWNpb24uY3N2DQpyb3RhY2lvbiA8LSByZWFkLmNzdigicm90YWNpb24uY3N2IiwNCiAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICI7IikNCg0KIyBWZXIgZWwgY29udGVuaWRvIGRlbCBkYXRhIGZyYW1lDQpyb3RhY2lvbiAjIEN0cmwgKyBFbnRlcg0KYGBgDQoNCiMjIyBOb3RhcyBzb2JyZSBlbCBub21icmUgZGUgbG9zIG9iamV0b3MNCg0KU2kgYmllbiBhIGxvcyBvYmpldG9zIHBvZGVtb3Mgbm9tYnJhcmxvcyBkZSBjdWFscXVpZXIgbWFuZXJhLCBoYXkgYnVlbmFzIHByw6FjdGljYXMgcmVjb21lbmRhZGFzLiBQb3IgZWplbXBsbzoNCg0KLSAgIFVzYXIgbm9tYnJlcyBjb3J0b3MsIHkgZGVzY3JpcHRpdm9zDQotICAgTG9zIG5vbWJyZXMgdGllbmVuIHF1ZSBlbXBlemFyIGNvbiB1bmEgbGV0cmEuDQotICAgTG9zIHPDrW1ib2xvcyB2w6FsaWRvcyBxdWUgc2UgcHVlZGVuIHVzYXIgc29uIGVsIGd1acOzbiBiYWpvIChgX2ApIG8gZWwgcHVudG8gKGAuYCkuDQotICAgU2UgcHVlZGVuIHVzYXIgbWF5w7pzY3VsYXMsIHBlcm8gZXMgbcOhcyBjw7Ntb2RvIHF1ZSBzaWVtcHJlIHVzYXIgbWluw7pzY3VsYXMuDQotICAgUmVjdWVyZGVuIHF1ZSBSIGVzICpjYXNlIHNlbnNpdGl2ZSosIG8gc2VhIHF1ZSBoYXkgcXVlIHByZXN0YXIgYXRlbmNpw7NuIGEgbWF5w7pzY3VsYXMgeSBtw61udXNjdWxhcw0KDQpgYGB7ciBub21icmVzLCBldmFsPUZBTFNFfQ0KIyBOb21icmVzIHbDoWxpZG9zIGRlIG9iamV0b3MNCiMjIE1lam9yZXMgb3BjaW9uZXMNCnJlc3B1ZXN0YXNfY2xpbWENCg0KcmVzcHVlc3Rhcy5jbGltYQ0KDQojIEVzdG8gZnVuY2lvbmEgcGVybyBlcyBtZW5vcyByZWNvbWVuZGFibGUNClJlc3B1ZXN0YXNDbGltYQ0KYGBgDQoNClBvciBvdHJvIGxhZG86DQoNCi0gICBMb3Mgb2JqZXRvcyBubyBwdWVkZW4gY29tZW56YXIgY29uIG7Dum1lcm9zLg0KLSAgIE5vIHNlIHB1ZWRlbiB1c2FyIGFjZW50b3MsIMOxLCB1IG90cm9zIGNhcmFjdGVyZXMgZXNwZWNpYWxlcy4NCi0gICBObyBzZSByZWNvbWllbmRhbiB1c2FyIGxldHJhcyBvIG5vbWJyZXMgZGUgZnVuY2lvbmVzLg0KDQojIyBQYXF1ZXRlcw0KDQpMb3MgcGFxdWV0ZXMgbyBsaWJyZXLDrWFzIHNvbiBleHRlbnNpb25lcyBkZXNhcnJvbGxhZGFzIHBvciBsYSBjb211bmlkYWQgbyBwb3IgZW1wcmVzYXMgcXVlIGZhY2lsaXRhbiBlbCB1c28gZGUgUiB5IGV4cGFuZGVuIHN1cyBjYXBhY2lkYWRlcy4gRW4gZXN0ZSBlbmN1ZW50cm8gdmFtb3MgYSB1c2FyIGxvcyBzaWd1aWVudGVzIHBhcXVldGVzOg0KDQotICAgYHJlYWR4bGA6IERlc2Fycm9sbGFkbyBwb3IgSGFkbGV5IFdpY2toYW0gYW5kIEplbm5pZmVyIEJyeWFuLiBbTGluayBhIGxhIHdlYl0oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1yZWFkeGwpLg0KLSAgIGBvcGVueGxzeGA6IERlc2Fycm9sbGFkbyBwb3IgUGhpbGlwcCBTY2hhdWJlcmdlciBhbmQgQWxleGFuZGVyIFdhbGtlci4gW0xpbmsgYSBsYSB3ZWJdKGh0dHBzOi8vQ1JBTi5SLXByb2plY3Qub3JnL3BhY2thZ2U9b3Blbnhsc3gpLg0KLSAgIGB0aWR5dmVyc2VgOiBEZXNhcnJvbGxhZG8gcG9yIEhhZGxleSBXaWNraGFtIHkgbXVjaG9zIG3DoXMuIFtMaW5rIGEgbGEgd2ViXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykuDQoNCkVzdGUgw7psdGltbyBwYXF1ZXRlLCBgdGlkeXZlcnNlYCBlcyB1bmEgY29sZWNjacOzbiBkZSBwYXF1ZXRlcyBxdWUgcGVybWl0ZW4gcmVhbGl6YXIgbXVjaGFzIHRhcmVhcyBkZSBleHBsb3JhY2nDs24sIGxpbXBpZXphIHkgdHJhbnNmb3JtYWNpw7NuIGRlIGRhdG9zLg0KDQpQYXJhIHV0aWxpemFyIHVuIHBhcXVldGUsIGxvIHByaW1lcm8gcXVlIHRlbmVtb3MgcXVlIGhhY2VyIGVzIGluc3RhbGFybG9zLiBFc28gbG8gaGFjZW1vcyBjb24gbGEgZnVuY2nDs24gYGluc3RhbGwucGFja2FnZXMoKWAgeSBkZW50cm8gZGVsIHBhcsOpbnRlc2lzIHRlbmVtb3MgcXVlIHBvbmVyIGVsIG5vbWJyZSBkZWwgcGFxdWV0ZS4gKipUZW5nYW4gZW4gY3VlbnRhIHF1ZToqKg0KDQotICAgUiBlcyB1biBsZW5ndWFqZSAqY2FzZSBzZW5zaXRpdmUqIG8gc2VhIHF1ZSBoYXkgcXVlIHByZXN0YXIgYXRlbmNpw7NuIGEgbWF5w7pzY3VsYXMgeSBtaW7DunNjdWxhcy4NCg0KLSAgIFBhcmEgaW5zdGFsYXIgbG9zIHBhcXVldGVzIGhheSBxdWUgdXNhciBjb21pbGxhcw0KDQpFc3RlIGVzIHVuIHBhc28gcXVlIGhhY2Vtb3MgdW5hIHNvbGEgdmV6IHBvciBjb21wdXRhZG9yYS4NCg0KUGFyYSBjb3JyZXIgZWwgY8OzZGlnbyBzZSB0aWVuZW4gcXVlIHBhcmFyIGVuIGxhIGzDrW5lYSBkZSBjw7NkaWdvIHF1ZSBxdWllcmVuIHVzYXIgeSBhcHJldGFyIGxhcyB0ZWNsYXMgYEN0cmxgICsgYEVudGVyYCBvIGJpZW4gZWwgdHJpw6FuZ3VsbyB2ZXJkZSAoY29tbyBzaSBmdWVyYSB1biDDrWNvbm8gZGUgKiJQbGF5Iiog4pa277iPKQ0KDQpgYGB7ciBwYXF1ZXRlcywgZXZhbCA9IEZBTFNFfQ0KIyBJbnN0YWxhciBsb3MgcGFxdWV0ZXMgcmVhZHhsIHkgdGlkeXZlcnNlDQppbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KaW5zdGFsbC5wYWNrYWdlcygicmVhZHhsIikNCmBgYA0KDQpFc3RvIGxvIHF1ZSBoYWNlIGVzIGluc3RhbGFyIHBhcXVldGVzIGRlc2RlIFtDUkFOXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pLCBxdWUgZXMgdW4gcmVwb3NpdG9yaW8gZG9uZGUgc2UgcHVibGljYW4gbG9zIHBhcXVldGVzLCBhc2VndXJhbmRvIHVuIGVzdMOhbmRhciBkZSBjYWxpZGFkIHkgZGUgZG9jdW1lbnRhY2nDs24gcXVlIGhhY2UgcXVlIHRyYWJhamFyIGNvbiBjdWFscXVpZXIgcGFxdWV0ZSBkZSBDUkFOIHNlYSBzZWd1cm8uDQoNClBhcmEgdXNhciBsYXMgZnVuY2lvbmVzIGRlIGxvcyBwYXF1ZXRlcyBxdWUgaW5zdGFsYW1vcywgYWhvcmEgdGVuZW1vcyBxdWUgKipjYXJnYXJsb3MuKiogRXN0byBsbyBxdWUgaGFjZSBlcyBkZSBhbGd1bmEgbWFuZXJhICoiYWN0aXZhciIqIGVsIHBhcXVldGUgeSBxdWUgcG9kYW1vcyBlbXBlemFyIHN1cyBmdW5jaW9uZXMuDQoNClBhcmEgY2FyZ2FyIHVuIHBhcXVldGUgdGVuZW1vcyBxdWUgdXNhciBsYSBmdW5jacOzbiBgbGlicmFyeSgpYC4gUmVjdWVyZGVuIHByZXN0YXIgYXRlbmNpw7NuIGEgbGFzIG1hecO6c2N1bGFzIHkgbWluw7pzY3VsYXMuIEFob3JhIG5vIHNvbiBuZWNlc2FyaWFzIGxhcyBjb21pbGxhcy4NCg0KYGBge3IgY2FyZ2EtcGFxdWV0ZXN9DQojIENhcmdhciBsb3MgcGFxdWV0ZXMgcmVhZHhsIHkgdGlkeXZlcnNlDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNClVuYSBkZSBsYXMgZm9ybWFzIGVuIGxhcyBxdWUgbm9zIGRhbW9zIGN1ZW50YSBxdWUgZWwgcGFxdWV0ZSBlc3TDoSBpbnN0YWxhZG8gZXMgY3VhbmRvIGVtcGV6YW1vcyBhIGVzY3JpYmlyIHN1IG5vbWJyZSB5IG5vcyBhcGFyZWNlIGVsIG5vbWJyZSBkZWwgcGFxdWV0ZSBwYXJhIGF1dG9jb21wbGV0YXIuIEVzdGEgZXMgdW5hIGRlIGxhcyB2ZW50YWphcyBkZSB0cmFiYWphciBlbiBSU3R1ZGlvLg0KDQohW10oQXJjaGl2b3MvcGFxdWV0ZV9hY3Rpdm8ucG5nKQ0KDQo+IFJlY3VlcmRlbjoNCj4NCj4gTGEgaW5zdGFsYWNpw7NuIGRlIGxvcyBwYXF1ZXRlcyBzZSBoYWNlICoqdW5hIHNvbGEgdmV6KiogcG9yIGNvbXB1dGFkb3JhLg0KPg0KPiBMYSBjYXJnYSBkZSBsb3MgcGFxdWV0ZSBzZSBzZSBoYWNlICoqY2FkYSB2ZXogcXVlIHNlIGFicmUqKiB1biBzY3JpcHQuDQoNCiMjIyBJbnN0YWxhciB5IGNhcmdhciAqKmludHJvUjRocioqDQoNClBhcmEgZXN0ZSBjdXJzbyBkZXNhcnJvbGxhbW9zIHVuIHBhcXVldGUsIGBpbnRyb1I0aHJgIHF1ZSBjb25zaXN0ZSBlbiB1bmEgc2VyaWUgZGUgdHV0b3JpYWxlcyBpbnRlcmFjdGl2b3MgcXVlIGlyZW1vcyBjb21wYXJ0aWVuZG8gYSBsbyBsYXJnbyBkZWwgY3Vyc28uDQoNCmBpbnRyb1I0aHJgIG5vIGVzIHVuIHBhcXVldGUgcHVibGljYWRvIGVuIFtDUkFOXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pIHBvciBsbyBjdWFsIGhheSBxdWUgZGVzY2FyZ2FyIGxhIHZlcnNpw7NuIGRlIGRlc2Fycm9sbG8gZGVzZGUgZWwgW3JlcG9zaXRvcmlvIGRlIEdpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL2NoZWNob2lkL2ludHJvUjRocikgeSBzZWd1aXIgbG9zIHNpZ3VpZW50ZXMgcGFzb3M6DQoNCmBgYHtyIGludHJvcjRocjEsIGV2YWw9RkFMU0V9DQojIEluc3RhbGFyIGxvcyBwYXF1ZXRlcyByZW1vdGVzIHkgbGVhcm5yDQppbnN0YWxsLnBhY2thZ2VzKCJyZW1vdGVzIikNCmluc3RhbGwucGFja2FnZXMoImxlYXJuciIpDQoNCiMgSW5zdGFsYXIgZWwgcGFxdWV0ZSBpbnRyb1I0aHIgZGVzZGUgR2l0SHViDQpyZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiY2hlY2hvaWQvaW50cm9SNGhyIikNCmBgYA0KDQpMYSBub3RhY2nDs24gYHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKClgIHJlZW1wbGF6YSBwYXJjaWFsbWVudGUgYSBsYSBmdW5jacOzbiBgbGlicmFyeSgpYCB5YSBxdWUgbm8gY2FyZ2EgZWwgcGFxdWV0ZSAoYWhvcnJhbmRvIG1lbW9yaWEpIHkgc8OzbG8gdXRpbGl6YSBsYSBmdW5jacOzbiBxdWUgZXN0w6EgaW52b2NhbmRvLg0KDQpQb3IgZXNvIGx1ZWdvIHVzYW1vcyBsYSBzaWd1aWVudGUgc2ludGF4aXMgcGFyYSBjYXJnYXIgdW4gdHV0b3JpYWwgaW50ZXJhY3Rpdm8uDQoNCmBgYHtyIGludHJvcjRocjIsIGV2YWw9RkFMU0V9DQpsZWFybnI6OnJ1bl90dXRvcmlhbCgic2VzaW9uMCIsICJpbnRyb1I0aHIiKQ0KYGBgDQoNCkRvbmRlOg0KDQotICAgYHNlc2lvbjBgIGVzIGVsIG5vbWJyZSBkZWwgdHV0b3JpYWwNCi0gICBgaW50cm9SNGhyYCBlcyBlbCBub21icmUgZGVsIHBhcXVldGUNCg0KIyBDYXJnYSBkZSBkYXRvcw0KDQpDb21vIGdyYW4gcGFydGUgZGUgbG9zIGRhdG9zIHF1ZSB2YW1vcyBhIHVzYXIgZW4gbGEgdmlkYSBzb24gYXJjaGl2b3MgZGUgRXhjZWwgdmFtb3MgYSB1c2FyIGxhIGZ1bmNpw7NuIGByZWFkX2V4Y2VsKClgIGRlbCBwYXF1ZXRlIGByZWFkeGxzYCBwYXJhIGxldmFudGFyIGRvcyBhcmNoaXZvczogZWwgYXJjaGl2byBgbWFlc3Ryby54bHN4YCBxdWUgY29udGllbmUgdW4gbGlzdGFkbyBkZSBlbXBsZWFkb3MgZGUgdW5hIGVtcHJlc2EsIHkgZWwgYXJjaGl2byBgc2FsYXJpb3MueGxzeGAgcXVlIGNvbnRpZW5lIGVsIHN1ZWxkbyBiYXNlIGRlIGFsZ3Vub3MgZGUgbG9zIGVtcGxlYWRvcyBkZWwgbGlzdGFkby4NCg0KYGBge3IgY2FyZ2EtZXhjZWx9DQojIENhcmdhciBlbCBhcmNoaXZvIG1hZXN0cm8ueGxzeA0KbWFlc3RybyA8LSByZWFkX2V4Y2VsKCJtYWVzdHJvLnhsc3giKQ0KDQojIENhcmdhciBlbCBhcmNoaXZvIHNhbGFyaW9zLnhsc3gNCnNhbGFyaW9zIDwtIHJlYWRfZXhjZWwoInNhbGFyaW9zLnhsc3giKQ0KYGBgDQoNCkxvcyBhcmNoaXZvcyBubyBuZWNlc2FyaWFtZW50ZSB0aWVuZW4gcXVlIGVzdGFyIGVuIHVuYSBjb21wdXRhZG9yYS4gVGFtYmnDqW4gc2UgcHVlZGVuIGNhcmdhciBhcmNoaXZvcyBkZXNkZSBpbnRlcm5ldCwgcG9yIGVqZW1wbG8sIGRlc2RlIFtyZXBvc2l0b3Jpb3MgZGUgR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vcjRoci9raXdpMjAyMC9ibG9iL21haW4vcmhfYXIuY3N2KToNCg0KYGBge3IgY2FyZ2EtY3N2fQ0KIyBDYXJnYXIgdW4gYXJjaGl2byBkZXNkZSBHaXRIdWINCmVuY3Vlc3RhIDwtIHJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcjRoci9raXdpMjAyMC9tYWluL3JoX2FyLmNzdiIsIA0KICAgICAgICAgICAgICAgICAgICAgc2VwID0gIjsiLA0KICAgICAgICAgICAgICAgICAgICAgZW5jb2RpbmcgPSAiVVRGLTgiKQ0KYGBgDQoNCk90cmEgYWx0ZXJuYXRpdmEgcGFyYSBsZWVyIGFyY2hpdm9zIGAuY3N2YCBjdWFuZG8gZWwgZGVsaW1pdGFkb3IgZW50cmUgY2FtcG9zIG5vIGVzIHVuYSBjb21hIChgLGApIGVzIHVzYXIgbGEgZnVuY2nDs24gYHJlYWRfZGVsaW0oKWAgZGVsIHBhcXVldGUgYHJlYWRyYCBxdWUgdmllbmUgZW4gYHRpZHl2ZXJzZWAuDQoNCmBgYHtyIGNhcmdhLWNzdjJ9DQplbmN1ZXN0YSA8LSByZWFkX2RlbGltKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcjRoci9raXdpMjAyMC9tYWluL3JoX2FyLmNzdiIsDQogICAgICAgICAgICAgICAgICAgICAgIGRlbGltID0gIjsiKSAjIEluZGljYW1vcyBlbCBkZWxpbWl0YWRvciBkZSBjYW1wb3MNCmBgYA0KDQpVbmEgZnVuY2nDs24gcXVlIG5vcyBwZXJtaXRlIHZlciBlbCBjb250ZW5pZG8gZGUgZXN0b3MgKmRhdGEgZnJhbWVzKiBlcyBsYSBmdW5jacOzbiBgVmlldygpYC4NCg0KYGBge3IgZWRhMX0NCiMgVmVyIGVsIGNvbnRlbmlkbyBkZSBsb3MgZGF0YSBmcmFtZXMgbWFlc3Rybywgc2FsYXJpb3MgeSBlbmN1ZXN0YQ0KVmlldyhtYWVzdHJvKQ0KVmlldyhzYWxhcmlvcykNClZpZXcoZW5jdWVzdGEpDQpgYGANCg0KT3RyYSBmdW5jacOzbiBpbnRlcmVzYW50ZSBwYXJhIGV4cGxvcmFyIGVsIGNvbnRlbmlkbyBkZSB1biBkYXRhIGZyYW1lIGVzIGxhIGZ1bmNpw7NuIGBnbGltcHNlKClgIGRlbCBwYXF1ZXRlIGBkcGx5cmAgcXVlIGZvcm1hIHBhcnRlIGRlbCBwYXF1ZXRlIGB0aWR5dmVyc2VgLg0KDQpgYGB7cn0NCiMgRXhwbG9yYXIgbG9zIGRhdGFmcmFtZXMgbWFlc3Rybywgc2FsYXJpb3MgeSBlbmN1ZXN0YQ0KZ2xpbXBzZShtYWVzdHJvKQ0KZ2xpbXBzZShzYWxhcmlvcykNCnN0cihlbmN1ZXN0YSkNCmBgYA0KDQpFc3RvIHF1ZSBlc3RhbW9zIGhhY2llbmRvIHNlIGxsYW1hICoqQW7DoWxpc2lzIEV4cGxvcmF0b3JpbyBkZSBEYXRvcyoqLCBwdWVkZW4gdmVyIGxhIHNlc2nDs24gcXVlIGhpY2ltb3MgZW4gUjRIUiBDbHViIGRlIFIgcGFyYSBSUkhIIGVuIFtZb3VUdWJlXShodHRwczovL3lvdXR1LmJlL1FUV0VuSW92UF80KS4NCg0KIyBUaWR5dmVyc2UNCg0KUlN0dWRpbyBhZGVtw6FzIGRlIGRlc2Fycm9sbGFyIGxhIElERSBwYXJhIHVzYXIgUiwgZXMgdGFtYmnDqW4gdW5vIGRlIGxvcyBwcmluY2lwYWxlcyBkZXNhcnJvbGxhZG9yZXMgZGUgcGFxdWV0ZXMuDQoNClVubyBkZSBzdXMgcGFxdWV0ZXMsIHF1ZSBlbmdsb2JhIHZhcmlvcyBwYXF1ZXRlcywgc2UgY29udmlydGnDsyBlbiB1biBlc3TDoW5kYXIgZGUgbGEgbGltcGllemEgeSB0cmFuc2Zvcm1hY2nDs24gZGUgZGF0b3MsIHF1ZSBlcyAqKnRpZHl2ZXJzZSoqLiBBIGVzdGUgZXN0w6FuZGFyIGRlIGxpbXBpZXphIGRlIGRhdG9zIHNlIGxvIGxsYW1hICp0aWR5IGRhdGEqLCBlbiBkb25kZSBlbnRyZSBzdXMgcHJpbmNpcGlvcyBkaWNlOg0KDQo+IENhZGEgdmFyaWFibGUgZm9ybWEgdW5hIGNvbHVtbmEuDQoNCj4gQ2FkYSBvYnNlcnZhY2nDs24gdGllbmUgcXVlIGVzdGFyIGVuIHVuYSBmaWxhLg0KDQohW10oQXJjaGl2b3MvaGV4X3RpZHl2ZXJzZS5wbmcpDQoNCkNvbW8gZGVjw61hbW9zLCBgdGlkeXZlcnNlYCBlcyB1biBwYXF1ZXRlIHF1ZSBlbmdsb2JhIHZhcmlvcyBwYXF1ZXRlcywgdG9kb3MgbXV5IHV0aWxpemFkb3MsIGEgdmVjZXMgZGVjbGFyYXRpdmFtZW50ZSwgb3RyYXMgdmVjZXMgUiBsbyB1c2EgZGV0csOhcyBkZSBlc2NlbmFzLiBFbiBlc3RhIHNlc2nDs24gdmFtb3MgYSB1c2FyIHByaW5jaXBhbG1lbnRlIGxvcyBwYXF1ZXRlcyAqKmRwbHlyKiouDQoNCiFbXShBcmNoaXZvcy90aWR5dmVyc2VfcGFja2FnZXMucG5nKQ0KDQpVbmEgZGUgbGFzIHJhem9uZXMgcG9yIGxhcyBjdWFsZXMgdGlkeXZlcnNlIHNlIGhpem8gdGFuIHBvcHVsYXIgZXMgcXVlIGxhIGzDs2dpY2EgZGUgbGFzIGZ1bmNpb25lcyBpbWl0YSBlbCByYXpvbmFtaWVudG8gcXVlIGhhcsOtYW1vcyB2ZXJiYWxtZW50ZS4NCg0KUG9yIGVqZW1wbG8sIMK/Y8OzbW8gaGFyw61hbiBwYXJhIGNhbGN1bGFyIGxhIGVkYWQgcHJvbWVkaW8gcG9yIMOhcmVhPw0KDQpgYGB7ciBkcGx5cjF9DQptYWVzdHJvICU+JSANCiAgc2VsZWN0KEFSRUEsIEVEQUQpICU+JSAjIFNlbGVjY2lvbmEgbGFzIGNvbHVtbmFzDQogIGdyb3VwX2J5KEFSRUEpICU+JSAgICAgIyBBZ3J1cGEgcG9yIGxhIHZhcmlhYmxlIEFSRUENCiAgc3VtbWFyaXNlKGVkYWRfcHJvbWVkaW8gPSBtZWFuKEVEQUQpKSAlPiUgIyBDcmVhIHVuYSB2YXJpYWJsZSBjb24gbGEgZWRhZCBwcm9tZWRpbw0KICBhcnJhbmdlKC1lZGFkX3Byb21lZGlvKSAjIE9yZGVuYSBkZXNjZW5kZW50ZW1lbnRlIGxvcyByZXN1bHRhZG9zIHBvciBsYSB2YXJpYWJsZSBxdWUgcGFzZW1vcy4NCmBgYA0KDQojIyBkcGx5cg0KDQrCv1ZpZXJvbiBlc3RlIHPDrW1ib2xvIGAlPiVgIGVuIGxhIHNsaWRlIGFudGVyaW9yPyBFbiBsYSBqZXJnYSBsbyBjb25vY2Vtb3MgY29tbyAqInBpcGUiKiAoYXRham8gZGUgdGVjbGFkbyBgQ3RybGAgKyBgTWF5w7pzYCArIGBNYCkuIExvIHF1ZSBub3MgcGVybWl0ZSBlc3RlICoidHVibyIqIGVzIG9yZGVuYXIgZWwgY8OzZGlnbyBlbiBzZWN1ZW5jaWFzLCBoYWNpw6luZG9sbyBtw6FzIGNvbXByZW5zaWJsZSBhIGxhIGxlY3R1cmEuDQoNClVuYSBkZSBsYXMgdmVudGFqYXMgZGVsICpwaXBlKiBlcyBxdWUgbm8gbmVjZXNpdGFtb3MgaW52b2NhciBhbCBkYXRhZnJhbWUgZW4gY2FkYSBmdW5jacOzbiBxdWUgZW5jYWRlbmFtb3MgZW4gZWwgcGlwZS4NCg0KIVtdKEFyY2hpdm9zL2RwbHlyX2Z1bmNpb25lcy5wbmcpe3dpZHRoPSI3NSUifQ0KDQpQb3IgZWplbXBsbywgcXVlcmVtb3MgdmVyIGxvcyBlbXBsZWRvcyBxdWUgdGllbmVuIGhpam9zIHBvciBjYWRhIMOhcmVhLiBMb3MgcGFzb3Mgc29uOg0KDQoxLiAgRWxlZ2lyIGxhcyB2YXJpYWJsZXMgQVJFQSwgSUQsIEhJSk9TLg0KMi4gIEZpbHRyYXIgZGUgbGEgdmFyaWFibGUgSElKT1MgbG9zIGNhc29zIGVuIHF1ZSBzw60gdGVuZ2FuIGhpam9zLg0KMy4gIE9yZGVuYXIgbG9zIHJlc3VsdGFkb3MgcG9yIMOhcmVhLg0KDQpgYGB7ciBkcGx5cjJ9DQojIFZlcnNpw7NuIHNpbiBwaXBlDQphcnJhbmdlKGZpbHRlcihzZWxlY3QobWFlc3RybywgQVJFQSxJRCwgSElKT1MpLCBISUpPUyA+IDApLCBBUkVBKQ0KYGBgDQoNCkVzdG8gbWlzbW8gdXNhbmRvIGVsIHBpcGUgZGUgZHBseXIgbG8gaGFjZW1vcyBhc8OtOg0KDQpgYGB7ciBkcGx5cjN9DQptYWVzdHJvICU+JQ0KICBzZWxlY3QoQVJFQSwgSUQsIEhJSk9TKSAlPiUNCiAgZmlsdGVyKEhJSk9TPjApICU+JQ0KICBhcnJhbmdlKEFSRUEpIA0KYGBgDQoNCiMjIyBDaGVhdHNoZWV0cw0KDQpIYWJpdHVhbG1lbnRlIGEgbGFzIGZ1bmNpb25lcyBkZWwgcGFxdWV0ZSBkcGx5ciBsYXMgbGxhbWFtb3MgKnZlcmJvcyouIFBhcmEgbXVjaG9zIHBhcXVldGVzIGRlc2Fycm9sbGFkb3MgcG9yIFJTdHVkaW8gZXhpc3RlIHVuICpjaGVhdCBzaGVldCogbyBndcOtYSBkZSByZWZlcmVuY2lhIHBhcmEgYXl1ZGFybm9zIGEgcmVjb3JkYXIgbGFzIGZ1bmNpb25lcyBtw6FzIHV0aWxpemFkYXMgeSBzZSBlbmN1ZW50cmFuIGVuIGVzdGUgbGluayBkZSBbc3UgcMOhZ2luYV0oaHR0cHM6Ly9yc3R1ZGlvLmNvbS9yZXNvdXJjZXMvY2hlYXRzaGVldHMvKS4NCg0KRW4gbGEgc2VjY2nDs24gKlRyYW5zbGF0aW9ucyogcG9kZW1vcyBlbmNvbnRyYXIgbXVjaG9zIGNoZWF0IHNoZWV0cyBlbiBlc3Bhw7FvbC4gQWPDoSBwdWVkZW4gZGVzY2FyZ2FyIGVsIGRlIFt0cmFuc2Zvcm1hY2nDs24gZGUgZGF0b3NdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL3Jhdy9tYXN0ZXIvdHJhbnNsYXRpb25zL3NwYW5pc2gvZGF0YS10cmFuc2Zvcm1hdGlvbl9TcGFuaXNoLnBkZikgcXVlIHZlcmVtb3MgaG95Lg0KDQojIyBsZWZ0X2pvaW4oKQ0KDQohW10oQXJjaGl2b3MvZG9uYXMucG5nKQ0KDQpMYSBmdW5jacOzbiBgbGVmdF9qb2luKClgIGVzIHVuIGVxdWl2YWxlbnRlIGEgbGEgZnVuY2nDs24gZGUgRXhjZWwgKkJVU0NBUlYqIG8gKlZMT09LVVAqLiBFc3RvIG5vcyBwZXJtaXRlIHRyYWJhamFyIGNvbiBtw7psdGlwbGVzIGFyY2hpdm9zIHNpbiBuZWNlc2lkYWQgZGUgcGFzYXJsb3MgbWFudWFsbWVudGUgdW5vIHBvciB1bm8gYSB1biBhcmNoaXZvIHF1ZSBjb250ZW5nYSBhIHRvZG9zLCBzaW5vIHRyYWVyIGxhIGluZm9ybWFjacOzbiBxdWUgbmVjZXNpdGVtb3MuDQoNCkhheSBtdWNob3MgdGlwb3MgZGUgYGpvaW4oKWAgZGlzdGludG9zLCBwb3IgZWplbXBsbyBgcmlnaHRfam9pbigpYCwgYGlubmVyX2pvaW4oKWAsIG8gYGFudGlfam9pbigpYCBwb3IgZWplbXBsbyBxdWUgbm8gc2Vyw6FuIHRlbWEgZGUgZXN0ZSBjdXJzby4NCg0KVmFtb3MgYSBjcmVhciB1biBkYXRhIGZyYW1lIGNvbWJpbmFuZG8gbGFzIHRhYmxhcyBgbWFlc3Ryb2AgeSBgc2FsYXJpb3NgLg0KDQpgYGB7ciBsZWZ0LWpvaW59DQojIEhhY2VyIHVuIGxlZnRfam9pbiBkZSBtYWVzdHJvIHkgc2FsYXJpb3MgeSBndWFyZGFybG8gZW4gdW4gZGF0YSBmcmFtZSBsbGFtYWRvIG1hZXN0cm9fZnVsbA0KbWFlc3Ryb19mdWxsIDwtIGxlZnRfam9pbihtYWVzdHJvLCBzYWxhcmlvcywgYnkgPSAiSUQiKQ0KDQojIFVzYXIgbGEgZnVuY2nDs24gZ2xpbXBzZSgpIGVuIGVsIG51ZXZvIGRhdGEgZnJhbWUNCmdsaW1wc2UobWFlc3Ryb19mdWxsKQ0KYGBgDQoNCkFob3JhIGNvbnRhbW9zIGNvbiBsb3MgZGF0b3MgZGVsIGxpc3RhZG8gZGUgcGVyc29uYWwgeSB0YW1iacOpbiBkZSBsb3Mgc2FsYXJpb3MgZGUgdW4gc2VnbWVudG8gZGUgbGEgZ2VudGUgZGUgbGEgY29tcGHDscOtYS4gQ29uIGxvIGN1YWwgcG9kcmVtb3MgaGFjZXIgdW4gKiphbsOhbGlzaXMgZGUgbG9zIHNhbGFyaW9zIGRlIGxhIG9yZ2FuaXphY2nDs24qKi4NCg0KIyMgc2VsZWN0KCkNCg0KQ29uIGxhIGZ1bmNpw7NuIGBzZWxlY3QoKWAgbG8gcXVlIGhhY2Vtb3MgZXMgZWxlZ2lyIGNvbiBxdcOpIGNvbHVtbmFzICh2YXJpYWJsZXMgcXVlcmVtb3MgdHJhYmFqYXIpLiBQb3IgZWplbXBsbywgZWxpamFtb3MgZGVsIGRhdGEgZnJhbWUgYGVuY3Vlc3RhYCBsYXMgdmFyaWFibGVzIGBnZW5lcm9gLCBgcHVlc3RvYCwgYHRpcG9fdW5pdmVyc2lkYWRgIHkgYHN1ZWxkb19icnV0b2AuDQoNCmBgYHtyIHNlbGVjdDF9DQojIFNlbGVjY2lvbmFyIGRlbCBkYXRhIGZyYW1lIGVuY3Vlc3RhIGxhcyB2YXJpYWJsZXMgZ2VuZXJvIHkgc3VlbGRvX2JydXRvDQplbmN1ZXN0YSAlPiUgICAgICAjIGVsIGF0YWpvIGRlbCBwaXBlIGVzIEN0cmwgKyBNYXnDunMgKyBNDQogIHNlbGVjdChnZW5lcm8sIHB1ZXN0bywgdGlwb191bml2ZXJzaWRhZCwgc3VlbGRvX2JydXRvKQ0KYGBgDQoNCmBzZWxlY3QoKWAgbm9zIHBlcm1pdGUgdGFtYmnDqW4gZWxlZ2lyIGVsIG9yZGVuIGVuIHF1ZSBxdWVyZW1vcyBsYXMgY29sdW1uYXMuDQoNCmBgYHtyIHNlbGVjdDJ9DQojIFNlbGVjY2lvbmFyIGxhcyBtaXNtYXMgdmFyaWFibGVzIGVuIG9yZGVuIGRpZmVyZW50ZQ0KZW5jdWVzdGEgJT4lDQogIHNlbGVjdCh0aXBvX3VuaXZlcnNpZGFkLCBzdWVsZG9fYnJ1dG8sIHB1ZXN0bywgZ2VuZXJvKQ0KYGBgDQoNClBvZGVtb3MgY3JlYXIgbnVldm9zIGRhdGEgZnJhbWVzIGNvbiBsYSBzZWxlY2Npw7NuIGRlIGNvbHVtbmFzIHF1ZSBuZWNlc2l0ZW1vcy4gRXN0byBlcyAqKm11eSDDunRpbCoqIGN1YW5kbyBlc3RhbW9zIHRyYWJhamFuZG8gY29uIG11Y2hvcyBkYXRvcyBwb3JxdWUgcGVybWl0ZSBxdWUgbGEgY29tcHV0YWRvcmEgc2VhIG3DoXMgZWZpY2llbnRlLg0KDQpgYGB7ciBzZWxlY3QzfQ0KIyBDcmVhciB1biBudWV2byBkYXRhIGZyYW1lIGNvbiBsYXMgY29sdW1uYXMgc2VsZWNjaW9uYWRhcw0KZW5jdWVzdGFfY2hpY2EgPC0gZW5jdWVzdGEgJT4lICAgICMgQXNpZ25vIGFsIG51ZXZvIGRhdGEgZnJhbWUgZGVsIGRhdGEgZnJhbWUgb3JpZ2luYWwNCiAgc2VsZWN0KGdlbmVybywgcHVlc3RvLCBzdWVsZG9fYnJ1dG8sIHRpcG9fdW5pdmVyc2lkYWQpICAgICAgICAgICAgICAgICAgICAgICAgIyBTZWxlY2Npb25vIGxhcyBjb2x1bW5hcyBxdWUgcXVpZXJhDQoNCmBgYA0KDQpDb24gZWwgc2lnbm8gbWVub3MgKGAtYCkgbGUgcG9kZW1vcyBpbmRpY2FyIGEgUiBsYXMgY29sdW1uYXMgcXVlIG5vIG5lY2VzaXRhbW9zLg0KDQpgYGB7ciBzZWxlY3Q0fQ0KZW5jdWVzdGFfY2hpY2EgJT4lIA0KICBzZWxlY3QoLXRpcG9fdW5pdmVyc2lkYWQpDQpgYGANCg0KUGFyYSBzYWJlciBtw6FzIHNvYnJlIGVzdGEgZnVuY2nDs24gcmVjb21lbmRhbW9zIGxlZXIgbGEgW3DDoWdpbmEgZGVsIHBhcXVldGVdKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2Uvc2VsZWN0Lmh0bWwpLg0KDQojIyMgRWplcmNpY2lvIHNlbGVjdCgpDQoNClBhcmEgcmVhbGl6YXIgdW4gYW7DoWxpc2lzIHNhbGFyaWFsIGhheSBjb2x1bW5hcyBxdWUgbm8gc29uIGludGVyZXNhbnRlcyBwYXJhIGVsIGFuw6FsaXNpcy4gU2VsZWNjaW9uYXIgbGFzIGNvbHVtbmFzIGBJRGAsIGBBTlRJR1VFREFEYCwgYEVEQURgLCBgQVJFQWAsIGBQVUVTVE9gIHkgYFNVRUxET2AuDQoNCmBgYHtyIHNlbGVjdC1lamVyY2ljaW99DQojIFNlbGVjY2lvbmFyIGxhcyBjb2x1bW5hcyBJRCwgQU5USUdVRURBRCwgRURBRCwgQVJFQSwgUFVFU1RPIHkgU1VFTERPDQojIExsYW1hciBhbCBudWV2byBkYXRhIGZyYW1lIG1hZXN0cm8yDQptYWVzdHJvMiA8LSBtYWVzdHJvX2Z1bGwgJT4lIA0KICBzZWxlY3QoSUQsIEFOVElHVUVEQUQsIEVEQUQsIEFSRUEsIFBVRVNUTywgU1VFTERPKQ0KYGBgDQoNCiMjIGZpbHRlcigpDQoNCkFzw60gY29tbyBgc2VsZWN0KClgIHRyYWJhamEgc29icmUgbGFzIGNvbHVtbmFzLCBsYSBmdW5jacOzbiBgZmlsdGVyKClgIGxvIGhhY2Ugc29icmUgbGFzIGZpbGFzLiBgZmlsdGVyKClgIG5vcyBwZXJtaXRlIHNlbGVjY2lvbmFyIGxhcyBvYnNlcnZhY2lvbmVzIHF1ZSBjdW1wbGFuIGNvbiBsb3MgY3JpdGVyaW9zIHF1ZSBub3MgaW50ZXJlc2FuLg0KDQpQYXJhIGVzdG8sIHZhbW9zIGEgbmVjZXNpdGFyIGxhIGF5dWRhIGRlIGxvcyBvcGVyYWRvcmVzIGzDs2dpY29zLg0KDQp8IFPDrW1ib2xvIHwgICAgIE9wZXJhZG9yICAgICAgfA0KfDotLS0tLS0tOnw6LS0tLS0tLS0tLS0tLS0tLS06fA0KfCAgYD09YCAgIHwgICAgICBJZ3VhbCBhICAgICAgfA0KfCAgYCE9YCAgIHwgICAgRGlzdGludG8gYSAgICAgfA0KfCAgIGA8YCAgIHwgICAgIE1lbm9yIHF1ZSAgICAgfA0KfCAgYDw9YCAgIHwgTWVub3IgbyBpZ3VhbCBxdWUgfA0KfCAgIGA+YCAgIHwgICAgIE1heW9yIHF1ZSAgICAgfA0KfCAgYD49YCAgIHwgTWF5b3IgbyBpZ3VhbCBxdWUgfA0KfCAgIGAmYCAgIHwgICAgICBZIChBTkQpICAgICAgfA0KfCAgIGB8YCAgIHwgICAgICBPIChPUikgICAgICAgfA0KDQo6IE9wZXJhZG9yZXMgTMOzZ2ljb3MNCg0KUGFyYSBoYWNlciBsb3MgZWplbXBsb3MgbcOhcyBsZWdpYmxlcyB2YW1vcyBhIGNyZWFyIHVuYSBudWV2YSB2ZXJzacOzbiBkZSBsYSBlbmN1ZXN0YSBzZWxlY2Npb25hbmRvIGxhcyB2YXJpYWJsZXMgYGdlbmVyb2AsIGBzdWVsZG9fYnJ1dG9gLCBgcHJvdmluY2lhYCwgYGFuaW9zX2V4cGVyaWVuY2lhYCwgYGVkYWRgIHkgYHB1ZXN0b2AuIEVzdG8gbG8gdmFtb3MgYSBhbG1hY2VuYXIgZW4gdW4gbnVldm8gZGF0YSBmcmFtZSBsbGFtYWRvIGBlbmN1ZXN0YTJgLg0KDQpgYGB7ciBmaWx0ZXItc2VsZWNjaW9ufQ0KIyBDcmVhbW9zIGVsIGRhdGEgZnJhbWUgZW5jdWVzdGEyIGNvbiBsYXMgdmFyaWFibGVzIGdlbmVybywgc3VlbGRvX2JydXRvLCBwcm92aW5jaWEsIGFuaW9zX2V4cGVyaWVuY2lhLCBlZGFkIHkgcHVlc3RvDQplbmN1ZXN0YTIgPC0gZW5jdWVzdGEgJT4lIA0KICBzZWxlY3QoZ2VuZXJvLCBzdWVsZG9fYnJ1dG8sIHByb3ZpbmNpYSwgYW5pb3NfZXhwZXJpZW5jaWEsIGVkYWQsIHB1ZXN0bykNCmBgYA0KDQpQb3IgZWplbXBsbywgc2kgcXVpZXJvIHZlciBsYXMgcmVzcHVlc3RhcyBkZSBsYXMgcGVyc29uYXMgZGUgbGEgcHJvdmluY2lhIGRlIGBTYW4gSnVhbmAgdGVuZW1vcyBxdWUgaGFjZXIgbG8gc2lndWllbnRlOg0KDQpgYGB7ciBmaWx0ZXIxfQ0KIyBTZWxlY2Npb25hciByZXNwdWVzdGFzIGRlIFNhbiBKdWFuDQplbmN1ZXN0YTIgJT4lIA0KICBmaWx0ZXIocHJvdmluY2lhID09ICJTYW4gSnVhbiIpDQpgYGANCg0KU2kgeW8gcXVpZXJvIGZpbHRyYXIgcG9yIGVqZW1wbG8sIGxhcyByZXNwdWVzdGFzIGRlIGBCdWVub3MgQWlyZXNgIHkgY3V5YSBgZWRhZGAgc2VhIG1lbm9yIG8gaWd1YWwgYSAzMCwgcG9kZW1vcyBoYWNlciBsbyBzaWd1aWVudGU6DQoNCmBgYHtyIGZpbHRlcjJ9DQojIFNlbGVjY2lvbmFyIHJlc3B1ZXN0YXMgZGUgQnVlbm9zIEFpcmVzIHkgcXVlIHNlYW4gaGFzdGEgMzAgYcOxb3MNCmVuY3Vlc3RhMiAlPiUgDQogIGZpbHRlcihwcm92aW5jaWEgPT0gIkJ1ZW5vcyBBaXJlcyIgJiANCiAgICAgICAgICAgZWRhZCA8PSAzMCkNCg0KIyBSZWVtcGxhemFyIGVsIHPDrW1ib2xvICYgcG9yIHVuYSBjb21hICgsKQ0KZW5jdWVzdGEyICU+JSANCiAgZmlsdGVyKHByb3ZpbmNpYSA9PSAiQnVlbm9zIEFpcmVzIiwNCiAgICAgICAgIGVkYWQgPD0gMzApDQoNCg0KYGBgDQoNClNpIHByZXN0YW1vcyBhdGVuY2nDs24sIGN1YW5kbyBmaWx0cmFtb3MgcG9yIGFsZ8O6biB2YWxvciBxdWUgY29udGVuZ2EgdGV4dG8gbmVjZXNpdGFtb3MgdXNhciBsYXMgY29taWxsYXMsIG1pZW50cmFzIHF1ZSBwYXJhIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcyBlc3RvIG5vIGVzIG5lY2VzYXJpby4NCg0KU2kgcXVlcmVtb3MgZmlsdHJhciB2YXJpb3MgdmFsb3JlcyBkZSB1bmEgbWlzbWEgY29sdW1uYSBsbyBpZGVhbCBlcyB0cmFiYWphciBjb24gdW4gdmVjdG9yLg0KDQpgYGB7ciBmaWx0ZXI0fQ0KIyBTZWxlY2Npw7NuIHBvY28gZWZpY2llbnRlDQplbmN1ZXN0YTIgJT4lIA0KICBmaWx0ZXIocHJvdmluY2lhID09ICJCdWVub3MgQWlyZXMiIHwgcHJvdmluY2lhID09ICJTYW50YSBGZSIgfCBwcm92aW5jaWEgPT0gIkNvcnJpZW50ZXMiIHwNCiAgICAgICAgICAgcHJvdmluY2lhID09ICJDw7NyZG9iYSIgfCBwcm92aW5jaWEgPT0gIlNhbiBKdWFuIikNCg0KIyBTZWxlY2Npw7NuIG3DoXMgZWZpY2llbnRlIGNvbiBlbCBvcGVyYWRvciAlaW4lDQplbmN1ZXN0YTIgJT4lIA0KICBmaWx0ZXIocHJvdmluY2lhICVpbiUgYygiQnVlbm9zIEFpcmVzIiwgIlNhbnRhIEZlIiwgIkNvcnJpZW50ZXMiLCAiQ8OzcmRvYmEiLCAiU2FuIEp1YW4iKSkNCmBgYA0KDQpIYXkgZnVuY2lvbmVzIGF1eGlsaWFyZXMgZGUgZmlsdGVyIHF1ZSBub3MgcGVybWl0ZW4gaGFjZXIgY29zYXMgaW50ZXJlc2FudGVzLiBVbmEgZGUgZWxsYXMgZXMgYGJldHdlZW4oKWAgcXVlIHBlcm1pdGUgaGFjZXIgc2VsZWNjaW9uZXMgZW50cmUgdW4gcmFuZ28gZGUgdmFsb3JlczoNCg0KYGBge3IgZmlsdGVyM30NCiMgRmlsdHJhciBsb3Mgc3VlbGRvcyBicnV0b3MgZW50cmUgNTAuMDAwIHkgMTAwLjAwMA0KZW5jdWVzdGEyICU+JSANCiAgZmlsdGVyKGJldHdlZW4oc3VlbGRvX2JydXRvLCAjIE5vbWJyZSBkZSBsYSB2YXJpYWJsZQ0KICAgICAgICAgICAgICAgICA1MDAwMCwgICAgICAgICMgVmFsb3IgbcOtbmltbyBkZWwgZmlsdHJvDQogICAgICAgICAgICAgICAgIDEwMDAwMCkpICAgICAgIyBWYWxvciBtw6F4aW1vIGRlbCBmaWx0cm8NCmBgYA0KDQojIyMgRWplcmNpY2lvIGZpbHRlcigpDQoNCkZpbHRyYXIgZGUgYGVuY3Vlc3RhMmAgbGFzIHJlc3B1ZXN0YXMgY3V5byBgcHVlc3RvYCBzZWEgYEFuYWxpc3RhYCB5IGBIUkJQYC4NCg0KYGBge3IgZWplcmNpY2lvLWZpbHRlcjF9DQojIEZpbHRyYXIgbGFzIHJlc3B1ZXN0YXMgY3V5byBwdWVzdG8gc2VhIEFuYWxpc3RhIG8gSFJCUA0KZW5jdWVzdGEyICU+JSANCiAgZmlsdGVyKHB1ZXN0byAlaW4lIGMoIkFuYWxpc3RhIiwgIkhSQlAiKSkNCmBgYA0KDQpDcmVhciB1biBudWV2byBkYXRhIGZyYW1lLCBsbGFtYWRvIGBtZW5zdWFsZXNgIHF1ZSBjb250ZW5nYSB0b2RvIGxvIHF1ZSBubyBzZWEgYE5BYCBkZSBsYSBjb2x1bW5hIGBQVUVTVE9gIGRlbCBkYXRhIGZyYW1lIGBtYWVzdHJvMmAuIFVzYXIgYCFpcy5uYSgpYCBkZW50cm8gZGUgYGZpbHRlcigpYC4NCg0KYGBge3IgZWplcmNpY2lvLWZpbHRlcjJ9DQojIENyZWFyIHVuIG51ZXZvIGRhdGEgZnJhbWUgbGxhbWFkbyBtZW5zdWFsZXMgZGUgbWFlc3RybzINCiMgRmlsdHJhciB0b2RvIGxvIHF1ZSBOTyBTRUEgbnVsbyAoTkEpIGRlIGxhIGNvbHVtbmEgUFVFU1RPDQptZW5zdWFsZXMgPC0gbWFlc3Ryb19mdWxsICU+JSANCmZpbHRlcighaXMubmEoUFVFU1RPKSkNCg0KDQojIFZlciBsb3MgcmVzdWx0YWRvcyBjb24gZ2xpbXBzZSgpDQpnbGltcHNlKG1lbnN1YWxlcykNCmBgYA0KDQojIyBncm91cF9ieSgpICsgc3VtbWFyaXNlKCkNCg0KYGdyb3VwX2J5KClgIGVzIHVuYSBmdW5jacOzbiBxdWUgc2UgdXNhIG11Y2hvIGVuIGNvbmp1bnRvIGNvbiBgc3VtbWFyaXNlKClgLiBQb3IgZWplbXBsbyBjdWFuZG8gZGVjaW1vczoNCg0KLSAgICpRdWllcm8gc2FiZXIgbGEgZWRhZCBwcm9tZWRpbyBwb3IgY2F0ZWdvcsOtYSosIGFncnVwYW1vcyBwb3IgY2F0ZWdvcsOtYSB5IGNhbGN1bGFuZG8gcG9yIGVkYWQuDQoNCi0gICAqUXVpZXJvIHNhYmVyIGxhIG1lZGlhbmEgc2FsYXJpYWwgcG9yIGfDqW5lcm8qIGVzdGFtb3MgYWdydXBhbmRvIHBvciBnw6luZXJvIHkgY2FsY3VsYW5kbyBwb3Igc3VlbGRvLg0KDQotICAgKlF1aWVybyBzYWJlciBsYSBhbnRpZ8O8ZWRhZCBtw6F4aW1hIHBvciBzZWN0b3IqIGVzdGFtb3MgYWdydXBhbmRvIHBvciBzZWN0b3IgeSBjYWxjdWxhbmRvIHBvciBhbnRpZ3VlZGFkLg0KDQpDb24gYGdyb3VwX2J5KClgIGxvIHF1ZSBoYWNlbW9zIGVzIGFncnVwYXIgdG9kYXMgbGFzIGZpbGFzIGRlIHVuIGRhdGEgZnJhbWUgeSByZWR1Y2lybGFzIGEgdW5hIGNhbnRpZGFkIG3DoXMgY2hpY2EgZGUgZmlsYXMuIExhIGNhbnRpZGFkIGRlIGZpbGFzIHZhIGEgZXN0YXIgZGV0ZXJtaW5hZGEgcG9yIGxvcyBkaXN0aW50b3MgdmFsb3JlcyBxdWUgaGF5YSBlbiB1bmEgY29sdW1uYS4NCg0KUG9yIG90cm8gbGFkbyBgc3VtbWFyaXNlKClgIGxvIHF1ZSBoYWNlIGVzIGHDsWFkaXIgdW5hIGNvbHVtbmEgbnVldmEgYSBlc2UgZGF0YSBmcmFtZSBhZ3J1cGFkbyBjb24gYWxnw7puIGPDoWxjdWxvIHF1ZSBub3MgaW50ZXJlc2UgcmVhbGl6YXIuDQoNClVzZW1vcyBlbCBkYXRhIGZyYW1lIGBlbmN1ZXN0YTJgIHBhcmEgdmVyIGFsZ3Vub3MgZWplbXBsb3MuIFByaW1lcm8sIGNhbGN1bGVtb3MgbG9zIGHDsW9zIGRlIGV4cGVyaWVuY2lhIHByb21lZGlvIHBvciBwdWVzdG8uDQoNCmBgYHtyIGdiLXN1bTF9DQp1bmlxdWUoZW5jdWVzdGEyJHB1ZXN0bykNCg0KIyBBZ3J1cGFyIHBvciBwdWVzdG8geSBjYWxjdWxhciBlbCBwcm9tZWRpbyBkZSBhbmlvc19leHBlcmllbmNpYQ0KZW5jdWVzdGEyICU+JSANCiAgZ3JvdXBfYnkocHVlc3RvKSAlPiUgDQogIHN1bW1hcmlzZShhbnRpZ3VlZGFkX3Byb21lZGlvID0gbWVhbihhbmlvc19leHBlcmllbmNpYSkpDQpgYGANCg0KVGFtYmnDqW4gcG9kZW1vcyBhZ3J1cGFyIHBvciBtw6FzIGRlIHVuYSB2YXJpYWJsZS4gVGFtYmnDqW4gcG9kZW1vcyBoYWNlciBtw6FzIGRlIHVuIGPDoWxjdWxvIGEgbGEgdmV6LiBDYWxjdWxlbW9zIGxhIG1lZGlhbmEgeSBlbCBwcm9tZWRpbyBkZSBsb3Mgc3VlbGRvcyBwb3IgYHB1ZXN0b2AgeSBwb3IgYGdlbmVyb2AuDQoNCmBgYHtyIGdiLXN1bTJ9DQojIEFncnVwYXIgcG9yIHB1ZXN0byB5IGdlbmVybyB5IGNhbGN1bGFyIGxhIG1lZGlhbmEgeSBlbCBwcm9tZWRpbw0KZW5jdWVzdGEyICU+JSANCiAgZ3JvdXBfYnkocHVlc3RvLCBnZW5lcm8pICU+JSANCiAgc3VtbWFyaXNlKG1lZGlhbmFfc2FsYXJpYWwgPSBtZWRpYW4oc3VlbGRvX2JydXRvKSwNCiAgICAgICAgICAgIHByb21lZGlvX3NhbGFyaWFsID0gbWVhbihzdWVsZG9fYnJ1dG8pKQ0KDQpgYGANCg0KRW4gUiBzaWVtcHJlIGhheSBtw6FzIGRlIHVuYSBmb3JtYSBkZSBsbGVnYXIgYWwgbWlzbW8gcmVzdWx0YWRvLiBQb3IgZWplbXBsby4gU2kgcXVlcmVtb3MgY29udGFyIGxhIGNhbnRpZGFkIGRlIHJlc3B1ZXN0YXMgcG9yIGB0aXBvX3VuaXZlcnNpZGFkYCBwb2RlbW9zOg0KDQpgYGB7ciBnYi1zdW0zfQ0KIyBBZ3J1cGFyIHBvciB0aXBvX3VuaXZlcnNpZGFkICsgc3VtbWFyaXNlKCkgKyBuKCkNCmVuY3Vlc3RhICU+JSANCiAgZ3JvdXBfYnkodGlwb191bml2ZXJzaWRhZCkgJT4lIA0KICBzdW1tYXJpc2UoY2FudGlkYWQgPSBuKCkpDQoNCiMgQWdydXBhciBwb3IgdGlwb191bml2ZXJzaWRhZCArIHRhbGx5KCkNCmVuY3Vlc3RhICU+JSANCiAgZ3JvdXBfYnkodGlwb191bml2ZXJzaWRhZCkgJT4lIA0KICB0YWxseSgpDQoNCiMgQ29udGFyIHBvciB0aXBvIGRlIHVuaXZlcnNpZGFkIGNvdW50KCkNCmVuY3Vlc3RhICU+JSANCiAgY291bnQodGlwb191bml2ZXJzaWRhZCkNCmBgYA0KDQojIyMgRWplcmNpY2lvIGdyb3VwX2J5KCkgKyBzdW1tYXJpc2UoKQ0KDQpEZWwgZGF0YSBmcmFtZSBgbWVuc3VhbGVzYCBjYWxjdWxhciBlbCBwcm9tZWRpbyBzYWxhcmlhbCBwb3IgcHVlc3RvLiBObyBoYWNlIGZhbHRhIGd1YXJkYXIgbG9zIHJlc3VsdGFkb3MgZW4gdW4gb2JqZXRvLg0KDQpgYGB7ciBlamVyY2ljaW8tZ3JvdXBieTF9DQojIENhbGN1bGFyIGVsIHN1ZWxkbyBwcm9tZWRpbyBwb3IgcHVlc3RvDQptZW5zdWFsZXMgJT4lIA0KICBncm91cF9ieShQVUVTVE8pICU+JSANCiAgc3VtbWFyaXNlKHN1ZWxkb19wcm9tZWRpbyA9IG1lYW4oU1VFTERPKSkNCmBgYA0KDQpDYWxjdWxhciBsYSBhbnRpZ8O8ZWRhZCBwcm9tZWRpbyBwb3Igw6FyZWEuDQoNCmBgYHtyIGVqZXJjaWNpby1ncm91cGJ5Mn0NCiMgQ2FsY3VsYXIgbGEgYW50aWfDvGVkYWQgcHJvbWVkaW8gcG9yIMOhcmVhDQptZW5zdWFsZXMgJT4lIA0KICBncm91cF9ieShBUkVBKSAlPiUgDQogIHN1bW1hcmlzZShlZGFkX3Byb21lZGlvID0gbWVhbihFREFEKSkNCmBgYA0KDQpDYWxjdWxhciBlbCBzdWVsZG8gbcOheGltbyB5IGVsIG3DrW5pbW8gcG9yIHB1ZXN0by4NCg0KYGBge3IgZWplcmNpY2lvLWdyb3VwYnl9DQptZW5zdWFsZXMgJT4lIA0KICBncm91cF9ieShQVUVTVE8pICU+JSANCiAgc3VtbWFyaXNlKHN1ZWxkb19taW5pbW8gPSBtaW4oU1VFTERPKSwNCiAgICAgICAgICAgIHN1ZWxkb19tYXhpbW8gPSBtYXgoU1VFTERPKSkNCmBgYA0KDQojIyBtdXRhdGUoKQ0KDQpgbXV0YXRlYCBlcyB1bmEgZnVuY2nDs24gcXVlIG5vcyBwZXJtaXRlIGFncmVnYXIgbnVldmFzIGNvbHVtbmFzIGFsIGRhdGEgZnJhbWUgZW4gYmFzZSBhIGNvbHVtbmFzIGV4aXN0ZW50ZXMgZW4gbnVlc3RyYSB0YWJsYS4NCg0KVG9tZW1vcyBsYSB0YWJsYSBgZW5jdWVzdGEyYCB5IGFncmVndWVtb3MgdW5hIGNvbHVtbmEgbnVldmEgY29uIGVsIHN1ZWxkbyBlbiBkw7NsYXJlcy4gUGFyYSBlc28gaGF5IHF1ZSBkaXZpZGlyIGEgbGEgY29sdW1uYSBgc3VlbGRvX2JydXRvYCBwb3IgMTAwDQoNCmBgYHtyIG11dGF0ZTF9DQojIEFncmVnYXIgYSBlbmN1ZXN0YTIgbGEgY29sdW1uYSBzdWVsZG9fZG9sYXINCmVuY3Vlc3RhMiA8LSBlbmN1ZXN0YTIgJT4lIA0KICBtdXRhdGUoc3VlbGRvX2RvbGFyID0gc3VlbGRvX2JydXRvIC8gMTAwKQ0KDQojIFZlciBlbCByZXN1bHRhZG8NCmdsaW1wc2UoZW5jdWVzdGEyKQ0KYGBgDQoNClRhbWJpw6luIG5vcyBwZXJtaXRlIG1vZGlmaWNhciBsYXMgdmFyaWFibGVzIGV4aXN0ZW50ZXMuIFBvciBlamVtcGxvLCBjYWxjdWxlbW9zIGVsIHN1ZWxkbyBwcm9tZWRpbyBwb3IgcHVlc3RvLg0KDQpgYGB7ciBtdXRhdGUyfQ0KIyBDYWxjdWxlbW9zIGVsIHN1ZWxkbyBwcm9tZWRpbyBwb3IgcHVlc3RvIGNvbiBncm91cF9ieSgpIHkgc3VtbWFyaXNlKCkNCmVuY3Vlc3RhMiAlPiUgDQogIGdyb3VwX2J5KHB1ZXN0bykgJT4lIA0KICBzdW1tYXJpc2Uoc3VlbGRvX3Byb21lZGlvID0gbWVhbihzdWVsZG9fYnJ1dG8pKQ0KYGBgDQoNCkVsIHJlc3VsdGFkbyBlc3TDoSBvcmRlbmFkbyBhbGZhYsOpdGljYW1lbnRlLCBsbyBjdWFsIG5vIGVzIGlkZWFsLiBFc3RvIG9jdXJyZSBwb3JxdWUgbGEgdmFyaWFibGUgYHB1ZXN0b2AgZXMgdW5hIHZhcmlhYmxlIG1hZXN0cm9sICh0aXBvIGBjaGFyYWN0ZXJgKS4gUGFyYSBjb252ZXJ0aXJsYSBlbiB1bmEgdmFyaWFibGUgb3JkaW5hbCBuZWNlc2l0YW1vcyB0cmFuc2Zvcm1hciBsYSB2YXJpYWJsZSBlbiB1bmEgdmFyaWFibGUgZGUgdGlwbyBgZmFjdG9yYC4NCg0KYGBge3IgbXV0YXRlM30NCiMgU29icmVzY3JpYmlyIGxhIHZhcmlhYmxlIHB1ZXN0byBjb21vIGZhY3Rvcg0KZW5jdWVzdGEyIDwtIGVuY3Vlc3RhMiAlPiUgDQogIG11dGF0ZShwdWVzdG8gPSBmYWN0b3IocHVlc3RvLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkdlcmVudGUiLCAiSmVmZSIsICJSZXNwb25zYWJsZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSFJCUCIsICJBbmFsaXN0YSIsICJBZG1pbmlzdHJhdGl2byIpKSkNCg0KZ2xpbXBzZShlbmN1ZXN0YTIpDQpgYGANCg0KSGF5IHZhcmlhcyBmdW5jaW9uZXMgYXV4aWxpYXJlcywgdW5hIG11eSBpbnRlcmVzYW50ZSBwYXJhIGNvbnN0cnVpciBjYXRlZ29yw61hcyBlbiBmdW5jacOzbiBkZSB1bmEgdmFyaWFibGUgbnVtw6lyaWNhIGVzIGBjYXNlX3doZW4oKWAgcXVlIHNlIHVzYSBkZW50cm8gZGUgYG11dGF0ZSgpYC4NCg0KYGBge3IgbXV0YXRlNH0NCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIG51ZXZhIGNvbiByYW5nb3MgZGUgZWRhZCB5IGd1YXJkYXJsbyBlbiBlbmN1ZXN0YTMNCmVuY3Vlc3RhMyA8LSBlbmN1ZXN0YTIgJT4lIA0KICBzZWxlY3QoZWRhZCwgc3VlbGRvX2JydXRvKSAlPiUgDQogIG11dGF0ZShyYW5nb19lZGFkID0gY2FzZV93aGVuKA0KICAgIGVkYWQgPD0gMzAgfiAiSGFzdGEgMzAiLA0KICAgIGVkYWQgPD0gNDAgfiAiSGFzdGEgNDAiLA0KICAgIGVkYWQgPSBUUlVFIH4gIk3DoXMgZGUgNDAiDQogICkpDQoNCiMgQ2FsY3VsYXIgZWwgc3VlbGRvIHByb21lZGlvIHBvciByYW5nbyBkZSBlZGFkLg0KZW5jdWVzdGEzICU+JSANCiAgZ3JvdXBfYnkocmFuZ29fZWRhZCkgJT4lIA0KICBzdW1tYXJpc2Uoc3VlbGRvX3Byb21lZGlvID0gbWVhbihzdWVsZG9fYnJ1dG8pKQ0KYGBgDQoNCiMjIyBFamVyY2ljaW8gbXV0YXRlKCkNCg0KRW4gQXJnZW50aW5hIGxvcyBpbXB1ZXN0b3MgcXVlIHRpZW5lbiBxdWUgcGFnYXIgbGFzIHBlcnNvbmFzIGFzYWxhcmlhZGFzIHNvbjoNCg0KLSAgIDExJSBwb3IgSnViaWxhY2nDs24uDQoNCi0gICAzJSBwb3IgU2VndXJpZGFkIFNvY2lhbA0KDQotICAgMyUgcG9yIE9icmEgU29jaWFsIChjb2JlcnR1cmEgZGUgc2FsdWQpLg0KDQpDcmVhciB1bmEgY29sdW1uYSBudWV2YSBxdWUgc2UgbGxhbWUgYGNhcmdhX3NvY2lhbGAgcXVlIHJlc3VsdGUgZGUgbXVsdGlwbGljYXIgbGEgY29sdW1uYSBgU1VFTERPYCBwb3IgMC4xNw0KDQpgYGB7ciBlamVyY2ljaW8tbXV0YXRlMX0NCiMgQ2FsY3VsYXIgbGFzIGNhcmdhcyBzb2NpYWxlcyBkZSBsb3Mgc3VlbGRvcw0KbWVuc3VhbGVzIDwtIG1lbnN1YWxlcyAlPiUgDQogIG11dGF0ZShjYXJnYV9zb2NpYWwgPSBTVUVMRE8gKiAwLjE3KQ0KDQojIFZlciBsb3MgcmVzdWx0YWRvcyBjb24gZ2xpbXBzZSgpDQpnbGltcHNlKG1lbnN1YWxlcykNCmBgYA0KDQpDcmVhciB1bmEgY29sdW1uYSBudWV2YSBxdWUgc2UgbGxhbWEgYGNvc3RvX2FudWFsYCBxdWUgc2VhIGVsIHJlc3VsdGFkbyBkZSBtdWx0aXBsaWNhciBsYXMgY29sdW1uYXMgYFNVRUxET2AgbcOhcyBgY2FyZ2Ffc29jaWFsYCBwb3IgMTMuDQoNCmBgYHtyIGVqZXJjaWNpby1tdXRhdGUyfQ0KIyBDcmVhciB1bmEgY29sdW1uYSBxdWUgc3VtZSBTVUVMRE8gKyBjYXJnYV9zb2NpYWwgeSBsbyBtdWx0aXBsaXF1ZSBwb3IgMTMNCm1lbnN1YWxlcyA8LSBtZW5zdWFsZXMgJT4lIA0KICBtdXRhdGUoY29zdG9fYW51YWwgPSAoU1VFTERPICsgY2FyZ2Ffc29jaWFsKSoxMykNCg0KIyBWZXIgbG9zIHJlc3VsdGFkb3MgY29uIGdsaW1wc2UoKQ0KZ2xpbXBzZShtZW5zdWFsZXMpDQpgYGANCg0KIyMgQ2hlY2sgb3V0DQoNClRpZHl2ZXJzZSBlcyB1biBwYXF1ZXRlIHF1ZSBlc3TDoSBlbiBjb250aW51byBkZXNhcnJvbGxvLiBQdWVkZW4gZXhwbG9yYXIgZWwgY29udGVuaWRvIGFjdHVhbGl6YWRvIGVuIFt0aWR5dmVyc2Uub3JnXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykgeSBhcHJlbmRlciBtw6FzIHNvYnJlIHN1IHVzbyBlbiBlbCBsaWJybyBbUiBwYXJhIENpZW5jaWEgZGUgRGF0b3NdKGh0dHBzOi8vZXMucjRkcy5oYWRsZXkubnovaW5kZXguaHRtbCkuDQoNClJlY3VlcmRlbiBoYWNlciB0b2RhcyBsYXMgY29uc3VsdGFzIGVuIGVsIGNhbmFsICoqI2F1eGlsaW8qKiBlbiBbU2xhY2tdKGh0dHBzOi8vam9pbi5zbGFjay5jb20vdC9yNGhyL3NoYXJlZF9pbnZpdGUvenQtb2NyaXl4NWUtbnFYdXdXZUR5T0ttMklDVUpqaHU2ZykNCg==