Nous allons maintenant voir comment explorer de la donnée au travers de plusieurs graphiques, la librairie exploitée sera ggplot2, comprise dans tidyverse. Un cheat sheet se trouve ici pour ggplot2.
Bien entendu il existe également d’autres librairies graphiques telles que plotly (existante également sur Python), permettant de réaliser des graphiques intéractifs, exportables au format HTML, PDF, etc. Nous utiliserons une fois encore un fichier issu d’une compétition kaggle, référençant une liste d’airbnb de la ville Melbourne en Australie.

Objectifs

Récupération du fichier

Le fichier est ici
Allez dans la partie “Data” > “Data Sources” > Sélectionnez “listings_summary_dec18.csv” > en-dessous cliquez sur l’icone download.

Lecture des fichiers

library(tidyverse)

df = read_csv('C:/Users/DIONGA/Downloads/dataset/melbourne airbnb/listings_summary_dec18.csv')
Parsed with column specification:
cols(
  id = col_double(),
  name = col_character(),
  host_id = col_double(),
  host_name = col_character(),
  neighbourhood_group = col_logical(),
  neighbourhood = col_character(),
  latitude = col_double(),
  longitude = col_double(),
  room_type = col_character(),
  price = col_double(),
  minimum_nights = col_double(),
  number_of_reviews = col_double(),
  last_review = col_date(format = ""),
  reviews_per_month = col_double(),
  calculated_host_listings_count = col_double(),
  availability_365 = col_double()
)
df

Analyses Univariées (une seule variable)

Histogramme - variable continue (un grand nombre de valeurs distinctes)

Ici on souhaite afficher un histogramme concernant le prix des airbnb, pour connaitre la distribution de cette variable.
On lui passe donc la variable price, on lui précise de découper le graphique en 100 barres, de se limiter à l’affichage des prix compris entre 0 et 1000, ensuite la fonction seq(0, 1000, 50), détermine l’échelle de l’axe x, on affiche l’axe x de 0 à 1000 par pas de 50.

ggplot(df) +
  aes(price) +
  geom_histogram(bins = 100, fill="lightblue", color="darkblue") +
  scale_x_continuous(breaks = seq(0, 1000, 50), limit=c(0,1000)) +
  ggtitle("Prix des airbnb") +
  xlab("Prix") +
  ylab("Effectifs")

On voit ici que les prix sont principalement situés entre 25€ et 200€, avec une forte densité entre 50 et 150€.

Courbe gaussian

On affiche ici la densité sous forme de courbe, afin de voir si la variable prix pourrait suivre une loi normale.
On passe le paramètre adjust=2, afin de lisser la courbe.

ggplot(df) +
  aes(price) +
  geom_density(kernel = "gaussian", adjust=2, fill="lightblue", color="darkblue", alpha=0.6) +
  scale_x_continuous(breaks = seq(0, 1000, 50), limit=c(0,1000)) +
  ggtitle("Prix des airbnb") +
  xlab("Prix") +
  ylab("Effectifs")

Courbe gaussian centrée réduite

Avec la focntion scale(), on centre la variable, c’est à dire que la moyenne des prix est soustraite à chaque valeur, puis réduite, c’est à dire qu’on divise chaque valeur par l’écart type.

ggplot(df) +
  aes(scale(price)) +
  geom_density(kernel = "gaussian", adjust=2, fill="lightblue", color="darkblue", alpha=0.6) +
  scale_x_continuous(breaks = seq(0, 5, 0.2), limit=c(0,5)) +
  ggtitle("Prix des airbnb") +
  xlab("Prix") +
  ylab("Effectifs")

On observe donc que les prix sont fortement regroupés, avec une longue queue de valeurs extrêmes (prix élevés). On aurait aussi pu utiliser l’échelle logarithmique log2(price), ou la racine carrée de price sqrt(price).

Box plot ou boite à moustaches

La fonction ici permet d’afficher rapidement pour une variable le 1er et 3e quartile, médiane, moyenne, valeurs min et max, permettant d’apprécier d’une autre façon la répartition des valeurs.

summary(df$price)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      0      71     111     148     165   12624 

Ici on l’affiche de manière graphique les résultats précédents, dont le principe est le suivant :

Il y a 25% des données entre chaque partie, les points représentes des valeurs extrêmes.

ggplot(df) +
  aes(x = "", y = price) +
  geom_boxplot(na.rm = TRUE, fill="lightblue", color="darkblue", alpha=0.6) +
  coord_flip(ylim=c(0, 2000)) +
  scale_y_continuous(breaks = seq(0, 2000, 100)) +
  ggtitle("Prix des airbnb") +
  xlab("") +
  ylab("Prix")

Diagramme de fréquence - variable discrète (quelques modalités)

ggplot(df) +
  aes(x = room_type) +
  geom_bar(color="red", fill="red", alpha=0.6, width=0.2) +
  coord_flip() +
  ggtitle("Type de logements") +
  xlab("") +
  ylab("Effectifs")

On s’aperçoit très rapidement que les chambres partagés ne représentent pas beaucoup de logements.

ggplot(df) +
  aes(x = neighbourhood) +
  geom_bar(color="red", fill="red", alpha=0.6, width=0.2) +
  coord_flip() +
  ggtitle("Quartiers") +
  xlab("") +
  ylab("Effectifs")

On voit ici que le cartier de Melbourne représente plus de 7000 logements sur 22800, soit près d’un tiers.

Analyses Bivariées (deux variables)

Box plot ou diagramme à moustaches

Il pourrait être intéressant désormais d’afficher la répartition des prix, mais cette fois en fonction des quartier


ggplot(df) +
  aes(x = neighbourhood, y = price) +
  geom_boxplot(na.rm = TRUE, fill="lightblue", color="darkblue", alpha=0.6) +
  coord_flip(ylim=c(0, 2000)) +
  scale_y_continuous(breaks = seq(0, 2000, 200)) +
  ggtitle("Prix des airbnb") +
  xlab("Quartiers") +
  ylab("Prix")

On voit que certains quartiers comme Bayside ou Yarra Ranges possèdent une plus grande diversité de prix que Greater Dandenong par exemple. Egalement ces 2 quartiers ont leur dernier quartile avec des prix plus élevés.

Nuage de points

A partir de là, d’autres idées commencent à émerger, on voudrait savoir qu’est ce qui tire les prix vers le haut, est-ce le type de logement (maison entière, chambre…), l’emplacement, …

ggplot(df) +
  aes(x = room_type, y = price) +
  geom_point(color="red", alpha=0.6) +
  ggtitle("Airbnb") +
  xlab("Type de logement") +
  ylab("Prix")

On voit ici que les prix montent plus haut sur un logement entier, ce qui parait relativement normal.

ggplot(df) +
  aes(x = neighbourhood, y = price) +
  geom_point(color="red", alpha=0.6) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
  ggtitle("Airbnb") +
  xlab("Quartiers") +
  ylab("Prix")

Là encore on voit que certains quartiers possèdent des valeurs très extrêmes de prix.

ggplot(df) +
  aes(x = minimum_nights, y = price) +
  geom_point(color="red", alpha=0.6) +
  scale_y_continuous(limits = c(0, 2000)) +
  ggtitle("Airbnb") +
  xlab("Nombre de nuits minimum") +
  ylab("Prix")

On peut conclure ici qu’un certain nombre d’hébergeurs demandent un nombre de nuits minimum très élevé, il peut s’agir ici d’erreurs de saisies.
La plupart demandent un nombre de nuits proche de 0, une autre colonne se lève après 300 jours, certainement des hébergeurs louant à l’année.

Analyses multivariées

Matrice de corrélation

On pourrait également se demander si revoir régulièrement son annonce et mettre régulièrement à jour ses textes permet de mieux louer.
Autrement dit le nombre de revues et les disponibilités sont elles corrélées.
Egalement le fait de mettre moins de nuits minimum, augmente t-il le nombre de réservations.

var_comp <- df %>% select(availability_365, number_of_reviews, price, minimum_nights, number_of_reviews, calculated_host_listings_count, availability_365)

matrix_corr <-cor(var_comp)
head(round(matrix_corr,2))
                               availability_365 number_of_reviews price minimum_nights
availability_365                           1.00              0.14  0.11           0.00
number_of_reviews                          0.14              1.00 -0.03          -0.04
price                                      0.11             -0.03  1.00           0.02
minimum_nights                             0.00             -0.04  0.02           1.00
calculated_host_listings_count             0.14              0.06  0.08           0.00
                               calculated_host_listings_count
availability_365                                         0.14
number_of_reviews                                        0.06
price                                                    0.08
minimum_nights                                           0.00
calculated_host_listings_count                           1.00

Afin de mettre en forme cette matrice de corrélation, nous aurons besoin du pckage corrplot :

install.packages("corrplot" ,repos='http://cran.r-project.org')
Error in install.packages : Updating loaded packages
library(corrplot)

col <- colorRampPalette(c("#BB4444", "#EE9988", "#FFFFFF", "#77AADD", "#4477AA"))
corrplot(matrix_corr, method="color", col=col(200), type="upper", tl.cex = 0.8,
         addCoef.col = "black", # Ajout du coefficient de corrélation
         tl.col="black", tl.srt=45, #Rotation des etiquettes de textes
         diag=FALSE # Cacher les coefficients de corrélation sur la diagonale
)

Un coefficent de corrélation évolue entre [-1;1], -1 signifiant une forte corrélation négative, 1 une forte corrélation positive. Ici le coefficent le plus élevé est 0.14, soit une faible corrélation entre le nombre de disponibilités et le nombre de mises à jour de l’annonce, ainsi que le nombre d’annonces pour un propriétaire. Ainsi un propriétaire avec plusieurs annonces et une fréquence de mise à jour plus élevé, à légérement tendance à mieux louer.

Régression linéaire

Un élément intéressant à visualiser, peut être une regression linéaire, afin de visualiser le lien qui existe entre deux variables.
Pour cette dernière analyse, je vous propose d’utiliser le jeu de données suivant sur ce lien.
Ce jeu de données contient des prêts bancaires avec des informations client afin de prévenir les défauts de paiement.

df_bank = read_csv('C:/Users/DIONGA/Downloads/dataset/bank loan/bankloan.csv')
Parsed with column specification:
cols(
  age = col_double(),
  ed = col_double(),
  employ = col_double(),
  address = col_double(),
  income = col_double(),
  debtinc = col_double(),
  creddebt = col_double(),
  othdebt = col_double(),
  default = col_double(),
  preddef1 = col_double(),
  preddef2 = col_double(),
  preddef3 = col_double()
)
df_bank
ggplot(df_bank) +
  aes(x = income, y = employ) +
  geom_smooth(method = "lm") +
  geom_point() +
  xlab("Revenus") +
  ylab("Types d'emplois")

cor(df_bank$income, df_bank$employ) ^ 2
[1] 0.3907407

Ici on voit qu’il y a un lien fort entre le type d’emploi et le revenu, ce qui en soit parait assez évident, pour autant la régression linéaire ici n’explique que 39% de la variance totale.

Synthèse

Fonctions R Univariées / biavariées / Multivariées Types de graphiques
Histograme Univarié geom_histogram()
Courbe gaussian ou non Univarié geom_density()
Boite à moustache Univarié et bivarié geom_boxplot()
Diagramme en bâtons Univarié geom_bar()
Nuage de points Bivarié geom_point()
Quelques plus
Matrice de corrélations Bivarié et multivarié corrplot()
Régression linéaire Bivarié geom_smooth()
LS0tDQp0aXRsZTogIlRyYWNlciBkZXMgZ3JhcGhpcXVlcyBhdmVjIFIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpOb3VzIGFsbG9ucyBtYWludGVuYW50IHZvaXIgY29tbWVudCBleHBsb3JlciBkZSBsYSBkb25uw6llIGF1IHRyYXZlcnMgZGUgcGx1c2lldXJzIGdyYXBoaXF1ZXMsIGxhIGxpYnJhaXJpZSBleHBsb2l0w6llIHNlcmEgZ2dwbG90MiwgY29tcHJpc2UgZGFucyB0aWR5dmVyc2UuDQpVbiBjaGVhdCBzaGVldCBzZSB0cm91dmUgW2ljaV0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTUvMDMvZ2dwbG90Mi1jaGVhdHNoZWV0LnBkZikgcG91ciBnZ3Bsb3QyLiAgDQpCaWVuIGVudGVuZHUgaWwgZXhpc3RlIMOpZ2FsZW1lbnQgZCdhdXRyZXMgbGlicmFpcmllcyBncmFwaGlxdWVzIHRlbGxlcyBxdWUgcGxvdGx5IChleGlzdGFudGUgw6lnYWxlbWVudCBzdXIgUHl0aG9uKSwgcGVybWV0dGFudCBkZSByw6lhbGlzZXIgZGVzIGdyYXBoaXF1ZXMgaW50w6lyYWN0aWZzLCBleHBvcnRhYmxlcyBhdSBmb3JtYXQgSFRNTCwgUERGLCBldGMuDQpOb3VzIHV0aWxpc2Vyb25zIHVuZSBmb2lzIGVuY29yZSB1biBmaWNoaWVyIGlzc3UgZCd1bmUgY29tcMOpdGl0aW9uIGthZ2dsZSwgcsOpZsOpcmVuw6dhbnQgdW5lIGxpc3RlIGQnYWlyYm5iIGRlIGxhIHZpbGxlIE1lbGJvdXJuZSBlbiBBdXN0cmFsaWUuDQoNCiMgT2JqZWN0aWZzDQoNCiogU2F2b2lyIHRyYWNlciBkZXMgZ3JhcGhpcXVlcyBhdmVjIHVuZSwgZGV1eCBvdSBwbHVzaWV1cnMgdmFyaWFibGVzDQoqIFNlIGZhbWlsaWFyaXNlciBhdmVjIGxhIGJpYmxpb3Row6hxdWUgZ2dwbG90Mg0KKiBDb21wcmVuZHJlIGNvbW1lbnQgdmlzdWFsaXNlciBsYSByw6lwYXJ0aXRpb24gZCd1bmUgdmFyaWFibGUNCiogTWV0dHJlIGVuIGF2YW50IGxlcyBsaWVucyBleGlzdGFudCBvdSBub24gZW50cmUgcGx1c2lldXJzIHZhcmlhYmxlcw0KDQoNCiMgUsOpY3Vww6lyYXRpb24gZHUgZmljaGllcg0KDQoNCkxlIGZpY2hpZXIgZXN0IFtpY2ldKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vdHlsZXJ4L21lbGJvdXJuZS1haXJibmItb3Blbi1kYXRhI2xpc3RpbmdzX3N1bW1hcnlfZGVjMTguY3N2KSAgDQpBbGxleiBkYW5zIGxhIHBhcnRpZSAiRGF0YSIgPiAiRGF0YSBTb3VyY2VzIiA+IFPDqWxlY3Rpb25uZXogImxpc3RpbmdzX3N1bW1hcnlfZGVjMTguY3N2IiA+IGVuLWRlc3NvdXMgY2xpcXVleiBzdXIgbCdpY29uZSBkb3dubG9hZC4NCiAgDQoNCiMgTGVjdHVyZSBkZXMgZmljaGllcnMgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KZGYgPSByZWFkX2NzdignQzovVXNlcnMvRElPTkdBL0Rvd25sb2Fkcy9kYXRhc2V0L21lbGJvdXJuZSBhaXJibmIvbGlzdGluZ3Nfc3VtbWFyeV9kZWMxOC5jc3YnKQ0KZGYNCmBgYA0KDQojIEFuYWx5c2VzIFVuaXZhcmnDqWVzICh1bmUgc2V1bGUgdmFyaWFibGUpDQoNCiMjIEhpc3RvZ3JhbW1lIC0gdmFyaWFibGUgY29udGludWUgKHVuIGdyYW5kIG5vbWJyZSBkZSB2YWxldXJzIGRpc3RpbmN0ZXMpDQoNCkljaSBvbiBzb3VoYWl0ZSBhZmZpY2hlciB1biBoaXN0b2dyYW1tZSBjb25jZXJuYW50IGxlIHByaXggZGVzIGFpcmJuYiwgcG91ciBjb25uYWl0cmUgbGEgZGlzdHJpYnV0aW9uIGRlIGNldHRlIHZhcmlhYmxlLiAgDQpPbiBsdWkgcGFzc2UgZG9uYyBsYSB2YXJpYWJsZSBwcmljZSwgb24gbHVpIHByw6ljaXNlIGRlIGTDqWNvdXBlciBsZSBncmFwaGlxdWUgZW4gMTAwIGJhcnJlcywgZGUgc2UgbGltaXRlciDDoCBsJ2FmZmljaGFnZSBkZXMgcHJpeCBjb21wcmlzIGVudHJlIDAgZXQgMTAwMCwgZW5zdWl0ZSBsYSBmb25jdGlvbiBzZXEoMCwgMTAwMCwgNTApLCBkw6l0ZXJtaW5lIGwnw6ljaGVsbGUgZGUgbCdheGUgeCwgb24gYWZmaWNoZSBsJ2F4ZSB4IGRlIDAgw6AgMTAwMCBwYXIgcGFzIGRlIDUwLg0KDQpgYGB7cn0NCmdncGxvdChkZikgKw0KICBhZXMocHJpY2UpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMCwgZmlsbD0ibGlnaHRibHVlIiwgY29sb3I9ImRhcmtibHVlIikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEwMDAsIDUwKSwgbGltaXQ9YygwLDEwMDApKSArDQogIGdndGl0bGUoIlByaXggZGVzIGFpcmJuYiIpICsNCiAgeGxhYigiUHJpeCIpICsNCiAgeWxhYigiRWZmZWN0aWZzIikNCmBgYA0KDQpPbiB2b2l0IGljaSBxdWUgbGVzIHByaXggc29udCBwcmluY2lwYWxlbWVudCBzaXR1w6lzIGVudHJlIDI14oKsIGV0IDIwMOKCrCwgYXZlYyB1bmUgZm9ydGUgZGVuc2l0w6kgZW50cmUgNTAgZXQgMTUw4oKsLg0KDQojIyBDb3VyYmUgZ2F1c3NpYW4NCg0KT24gYWZmaWNoZSBpY2kgbGEgZGVuc2l0w6kgc291cyBmb3JtZSBkZSBjb3VyYmUsIGFmaW4gZGUgdm9pciBzaSBsYSB2YXJpYWJsZSBwcml4IHBvdXJyYWl0IHN1aXZyZSB1bmUgbG9pIG5vcm1hbGUuICANCk9uIHBhc3NlIGxlIHBhcmFtw6h0cmUgYWRqdXN0PTIsIGFmaW4gZGUgbGlzc2VyIGxhIGNvdXJiZS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGYpICsNCiAgYWVzKHByaWNlKSArDQogIGdlb21fZGVuc2l0eShrZXJuZWwgPSAiZ2F1c3NpYW4iLCBhZGp1c3Q9MiwgZmlsbD0ibGlnaHRibHVlIiwgY29sb3I9ImRhcmtibHVlIiwgYWxwaGE9MC42KSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTAwMCwgNTApLCBsaW1pdD1jKDAsMTAwMCkpICsNCiAgZ2d0aXRsZSgiUHJpeCBkZXMgYWlyYm5iIikgKw0KICB4bGFiKCJQcml4IikgKw0KICB5bGFiKCJFZmZlY3RpZnMiKQ0KYGBgDQoNCg0KIyMgQ291cmJlIGdhdXNzaWFuIGNlbnRyw6llIHLDqWR1aXRlDQoNCkF2ZWMgbGEgZm9jbnRpb24gc2NhbGUoKSwgb24gY2VudHJlIGxhIHZhcmlhYmxlLCBjJ2VzdCDDoCBkaXJlIHF1ZSBsYSBtb3llbm5lIGRlcyBwcml4IGVzdCBzb3VzdHJhaXRlIMOgIGNoYXF1ZSB2YWxldXIsIHB1aXMgcsOpZHVpdGUsIGMnZXN0IMOgIGRpcmUgcXUnb24gZGl2aXNlIGNoYXF1ZSB2YWxldXIgcGFyIGwnw6ljYXJ0IHR5cGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGRmKSArDQogIGFlcyhzY2FsZShwcmljZSkpICsNCiAgZ2VvbV9kZW5zaXR5KGtlcm5lbCA9ICJnYXVzc2lhbiIsIGFkanVzdD0yLCBmaWxsPSJsaWdodGJsdWUiLCBjb2xvcj0iZGFya2JsdWUiLCBhbHBoYT0wLjYpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA1LCAwLjIpLCBsaW1pdD1jKDAsNSkpICsNCiAgZ2d0aXRsZSgiUHJpeCBkZXMgYWlyYm5iIikgKw0KICB4bGFiKCJQcml4IikgKw0KICB5bGFiKCJFZmZlY3RpZnMiKQ0KYGBgDQoNCk9uIG9ic2VydmUgZG9uYyBxdWUgbGVzIHByaXggc29udCBmb3J0ZW1lbnQgcmVncm91cMOpcywgYXZlYyB1bmUgbG9uZ3VlIHF1ZXVlIGRlIHZhbGV1cnMgZXh0csOqbWVzIChwcml4IMOpbGV2w6lzKS4NCk9uIGF1cmFpdCBhdXNzaSBwdSB1dGlsaXNlciBsJ8OpY2hlbGxlIGxvZ2FyaXRobWlxdWUgYGxvZzIocHJpY2UpYCwgb3UgbGEgcmFjaW5lIGNhcnLDqWUgZGUgcHJpY2UgYHNxcnQocHJpY2UpYC4NCg0KDQojIyBCb3ggcGxvdCBvdSBib2l0ZSDDoCBtb3VzdGFjaGVzDQoNCkxhIGZvbmN0aW9uIGljaSBwZXJtZXQgZCdhZmZpY2hlciByYXBpZGVtZW50IHBvdXIgdW5lIHZhcmlhYmxlIGxlIDFlciBldCAzZSBxdWFydGlsZSwgbcOpZGlhbmUsIG1veWVubmUsIHZhbGV1cnMgbWluIGV0IG1heCwgcGVybWV0dGFudCBkJ2FwcHLDqWNpZXIgZCd1bmUgYXV0cmUgZmHDp29uIGxhIHLDqXBhcnRpdGlvbiBkZXMgdmFsZXVycy4NCg0KYGBge3J9DQpzdW1tYXJ5KGRmJHByaWNlKQ0KYGBgDQoNCkljaSBvbiBsJ2FmZmljaGUgZGUgbWFuacOocmUgZ3JhcGhpcXVlIGxlcyByw6lzdWx0YXRzIHByw6ljw6lkZW50cywgZG9udCBsZSBwcmluY2lwZSBlc3QgbGUgc3VpdmFudCA6DQoNCiFbXShDOi9Vc2Vycy9ESU9OR0EvRG93bmxvYWRzL2JveHBsb3QucG5nKQ0KDQpJbCB5IGEgMjUlIGRlcyBkb25uw6llcyBlbnRyZSBjaGFxdWUgcGFydGllLCBsZXMgcG9pbnRzIHJlcHLDqXNlbnRlcyBkZXMgdmFsZXVycyBleHRyw6ptZXMuDQoNCmBgYHtyfQ0KZ2dwbG90KGRmKSArDQogIGFlcyh4ID0gIiIsIHkgPSBwcmljZSkgKw0KICBnZW9tX2JveHBsb3QobmEucm0gPSBUUlVFLCBmaWxsPSJsaWdodGJsdWUiLCBjb2xvcj0iZGFya2JsdWUiLCBhbHBoYT0wLjYpICsNCiAgY29vcmRfZmxpcCh5bGltPWMoMCwgMjAwMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAyMDAwLCAxMDApKSArDQogIGdndGl0bGUoIlByaXggZGVzIGFpcmJuYiIpICsNCiAgeGxhYigiIikgKw0KICB5bGFiKCJQcml4IikNCmBgYA0KDQojIyBEaWFncmFtbWUgZGUgZnLDqXF1ZW5jZSAtIHZhcmlhYmxlIGRpc2Nyw6h0ZSAocXVlbHF1ZXMgbW9kYWxpdMOpcykNCg0KYGBge3J9DQpnZ3Bsb3QoZGYpICsNCiAgYWVzKHggPSByb29tX3R5cGUpICsNCiAgZ2VvbV9iYXIoY29sb3I9InJlZCIsIGZpbGw9InJlZCIsIGFscGhhPTAuNiwgd2lkdGg9MC4yKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIGdndGl0bGUoIlR5cGUgZGUgbG9nZW1lbnRzIikgKw0KICB4bGFiKCIiKSArDQogIHlsYWIoIkVmZmVjdGlmcyIpDQpgYGANCg0KT24gcydhcGVyw6dvaXQgdHLDqHMgcmFwaWRlbWVudCBxdWUgbGVzIGNoYW1icmVzIHBhcnRhZ8OpcyBuZSByZXByw6lzZW50ZW50IHBhcyBiZWF1Y291cCBkZSBsb2dlbWVudHMuDQoNCg0KYGBge3J9DQpnZ3Bsb3QoZGYpICsNCiAgYWVzKHggPSBuZWlnaGJvdXJob29kKSArDQogIGdlb21fYmFyKGNvbG9yPSJyZWQiLCBmaWxsPSJyZWQiLCBhbHBoYT0wLjYsIHdpZHRoPTAuMikgKw0KICBjb29yZF9mbGlwKCkgKw0KICBnZ3RpdGxlKCJRdWFydGllcnMiKSArDQogIHhsYWIoIiIpICsNCiAgeWxhYigiRWZmZWN0aWZzIikNCmBgYA0KDQpPbiB2b2l0IGljaSBxdWUgbGUgY2FydGllciBkZSBNZWxib3VybmUgcmVwcsOpc2VudGUgcGx1cyBkZSA3MDAwIGxvZ2VtZW50cyBzdXIgMjI4MDAsIHNvaXQgcHLDqHMgZCd1biB0aWVycy4NCg0KDQojIEFuYWx5c2VzIEJpdmFyacOpZXMgKGRldXggdmFyaWFibGVzKQ0KDQojIyBCb3ggcGxvdCBvdSBkaWFncmFtbWUgw6AgbW91c3RhY2hlcw0KDQpJbCBwb3VycmFpdCDDqnRyZSBpbnTDqXJlc3NhbnQgZMOpc29ybWFpcyBkJ2FmZmljaGVyIGxhIHLDqXBhcnRpdGlvbiBkZXMgcHJpeCwgbWFpcyBjZXR0ZSBmb2lzIGVuIGZvbmN0aW9uIGRlcyBxdWFydGllcg0KDQpgYGB7ciBmaWcuaGVpZ2h0ID0gNywgZmlnLndpZHRoID0gMTB9DQoNCmdncGxvdChkZikgKw0KICBhZXMoeCA9IG5laWdoYm91cmhvb2QsIHkgPSBwcmljZSkgKw0KICBnZW9tX2JveHBsb3QobmEucm0gPSBUUlVFLCBmaWxsPSJsaWdodGJsdWUiLCBjb2xvcj0iZGFya2JsdWUiLCBhbHBoYT0wLjYpICsNCiAgY29vcmRfZmxpcCh5bGltPWMoMCwgMjAwMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAyMDAwLCAyMDApKSArDQogIGdndGl0bGUoIlByaXggZGVzIGFpcmJuYiIpICsNCiAgeGxhYigiUXVhcnRpZXJzIikgKw0KICB5bGFiKCJQcml4IikNCmBgYA0KDQpPbiB2b2l0IHF1ZSBjZXJ0YWlucyBxdWFydGllcnMgY29tbWUgQmF5c2lkZSBvdSBZYXJyYSBSYW5nZXMgcG9zc8OoZGVudCB1bmUgcGx1cyBncmFuZGUgZGl2ZXJzaXTDqSBkZSBwcml4IHF1ZSBHcmVhdGVyIERhbmRlbm9uZyBwYXIgZXhlbXBsZS4NCkVnYWxlbWVudCBjZXMgMiBxdWFydGllcnMgb250IGxldXIgZGVybmllciBxdWFydGlsZSBhdmVjIGRlcyBwcml4IHBsdXMgw6lsZXbDqXMuDQoNCg0KIyMgTnVhZ2UgZGUgcG9pbnRzDQoNCkEgcGFydGlyIGRlIGzDoCwgZCdhdXRyZXMgaWTDqWVzIGNvbW1lbmNlbnQgw6Agw6ltZXJnZXIsIG9uIHZvdWRyYWl0IHNhdm9pciBxdSdlc3QgY2UgcXVpIHRpcmUgbGVzIHByaXggdmVycyBsZSBoYXV0LCBlc3QtY2UgbGUgdHlwZSBkZSBsb2dlbWVudCAobWFpc29uIGVudGnDqHJlLCBjaGFtYnJlLi4uKSwgbCdlbXBsYWNlbWVudCwgLi4uICANCg0KDQpgYGB7cn0NCmdncGxvdChkZikgKw0KICBhZXMoeCA9IHJvb21fdHlwZSwgeSA9IHByaWNlKSArDQogIGdlb21fcG9pbnQoY29sb3I9InJlZCIsIGFscGhhPTAuNikgKw0KICBnZ3RpdGxlKCJBaXJibmIiKSArDQogIHhsYWIoIlR5cGUgZGUgbG9nZW1lbnQiKSArDQogIHlsYWIoIlByaXgiKQ0KYGBgDQoNCk9uIHZvaXQgaWNpIHF1ZSBsZXMgcHJpeCBtb250ZW50IHBsdXMgaGF1dCBzdXIgdW4gbG9nZW1lbnQgZW50aWVyLCBjZSBxdWkgcGFyYWl0IHJlbGF0aXZlbWVudCBub3JtYWwuDQoNCmBgYHtyfQ0KZ2dwbG90KGRmKSArDQogIGFlcyh4ID0gbmVpZ2hib3VyaG9vZCwgeSA9IHByaWNlKSArDQogIGdlb21fcG9pbnQoY29sb3I9InJlZCIsIGFscGhhPTAuNikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArDQogIGdndGl0bGUoIkFpcmJuYiIpICsNCiAgeGxhYigiUXVhcnRpZXJzIikgKw0KICB5bGFiKCJQcml4IikNCmBgYA0KDQpMw6AgZW5jb3JlIG9uIHZvaXQgcXVlIGNlcnRhaW5zIHF1YXJ0aWVycyBwb3Nzw6hkZW50IGRlcyB2YWxldXJzIHRyw6hzIGV4dHLDqm1lcyBkZSBwcml4Lg0KDQoNCmBgYHtyfQ0KZ2dwbG90KGRmKSArDQogIGFlcyh4ID0gbWluaW11bV9uaWdodHMsIHkgPSBwcmljZSkgKw0KICBnZW9tX3BvaW50KGNvbG9yPSJyZWQiLCBhbHBoYT0wLjYpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMjAwMCkpICsNCiAgZ2d0aXRsZSgiQWlyYm5iIikgKw0KICB4bGFiKCJOb21icmUgZGUgbnVpdHMgbWluaW11bSIpICsNCiAgeWxhYigiUHJpeCIpDQpgYGANCg0KT24gcGV1dCBjb25jbHVyZSBpY2kgcXUndW4gY2VydGFpbiBub21icmUgZCdow6liZXJnZXVycyBkZW1hbmRlbnQgdW4gbm9tYnJlIGRlIG51aXRzIG1pbmltdW0gdHLDqHMgw6lsZXbDqSwgaWwgcGV1dCBzJ2FnaXIgaWNpIGQnZXJyZXVycyBkZSBzYWlzaWVzLiAgDQpMYSBwbHVwYXJ0IGRlbWFuZGVudCB1biBub21icmUgZGUgbnVpdHMgcHJvY2hlIGRlIDAsIHVuZSBhdXRyZSBjb2xvbm5lIHNlIGzDqHZlIGFwcsOocyAzMDAgam91cnMsIGNlcnRhaW5lbWVudCBkZXMgaMOpYmVyZ2V1cnMgbG91YW50IMOgIGwnYW5uw6llLiAgDQoNCiMgQW5hbHlzZXMgbXVsdGl2YXJpw6llcw0KDQojIyBNYXRyaWNlIGRlIGNvcnLDqWxhdGlvbg0KDQpPbiBwb3VycmFpdCDDqWdhbGVtZW50IHNlIGRlbWFuZGVyIHNpIHJldm9pciByw6lndWxpw6hyZW1lbnQgc29uIGFubm9uY2UgZXQgbWV0dHJlIHLDqWd1bGnDqHJlbWVudCDDoCBqb3VyIHNlcyB0ZXh0ZXMgcGVybWV0IGRlIG1pZXV4IGxvdWVyLiAgDQpBdXRyZW1lbnQgZGl0IGxlIG5vbWJyZSBkZSByZXZ1ZXMgZXQgbGVzIGRpc3BvbmliaWxpdMOpcyBzb250IGVsbGVzIGNvcnLDqWzDqWVzLiAgDQpFZ2FsZW1lbnQgbGUgZmFpdCBkZSBtZXR0cmUgbW9pbnMgZGUgbnVpdHMgbWluaW11bSwgYXVnbWVudGUgdC1pbCBsZSBub21icmUgZGUgcsOpc2VydmF0aW9ucy4gIA0KDQpgYGB7cn0NCnZhcl9jb21wIDwtIGRmICU+JSBzZWxlY3QoYXZhaWxhYmlsaXR5XzM2NSwgbnVtYmVyX29mX3Jldmlld3MsIHByaWNlLCBtaW5pbXVtX25pZ2h0cywgbnVtYmVyX29mX3Jldmlld3MsIGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudCwgYXZhaWxhYmlsaXR5XzM2NSkNCg0KbWF0cml4X2NvcnIgPC1jb3IodmFyX2NvbXApDQpoZWFkKHJvdW5kKG1hdHJpeF9jb3JyLDIpKQ0KYGBgDQoNCkFmaW4gZGUgbWV0dHJlIGVuIGZvcm1lIGNldHRlIG1hdHJpY2UgZGUgY29ycsOpbGF0aW9uLCBub3VzIGF1cm9ucyBiZXNvaW4gZHUgcGNrYWdlIGBjb3JycGxvdGAgOg0KDQpgYGB7cn0NCmluc3RhbGwucGFja2FnZXMoImNvcnJwbG90IiAscmVwb3M9J2h0dHA6Ly9jcmFuLnItcHJvamVjdC5vcmcnKQ0KYGBgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KGNvcnJwbG90KQ0KDQpjb2wgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCIjQkI0NDQ0IiwgIiNFRTk5ODgiLCAiI0ZGRkZGRiIsICIjNzdBQUREIiwgIiM0NDc3QUEiKSkNCmNvcnJwbG90KG1hdHJpeF9jb3JyLCBtZXRob2Q9ImNvbG9yIiwgY29sPWNvbCgyMDApLCB0eXBlPSJ1cHBlciIsIHRsLmNleCA9IDAuOCwNCiAgICAgICAgIGFkZENvZWYuY29sID0gImJsYWNrIiwgIyBBam91dCBkdSBjb2VmZmljaWVudCBkZSBjb3Jyw6lsYXRpb24NCiAgICAgICAgIHRsLmNvbD0iYmxhY2siLCB0bC5zcnQ9NDUsICNSb3RhdGlvbiBkZXMgZXRpcXVldHRlcyBkZSB0ZXh0ZXMNCiAgICAgICAgIGRpYWc9RkFMU0UgIyBDYWNoZXIgbGVzIGNvZWZmaWNpZW50cyBkZSBjb3Jyw6lsYXRpb24gc3VyIGxhIGRpYWdvbmFsZQ0KKQ0KYGBgDQoNClVuIGNvZWZmaWNlbnQgZGUgY29ycsOpbGF0aW9uIMOpdm9sdWUgZW50cmUgWy0xOzFdLCAtMSBzaWduaWZpYW50IHVuZSBmb3J0ZSBjb3Jyw6lsYXRpb24gbsOpZ2F0aXZlLCAxIHVuZSBmb3J0ZSBjb3Jyw6lsYXRpb24gcG9zaXRpdmUuDQpJY2kgbGUgY29lZmZpY2VudCBsZSBwbHVzIMOpbGV2w6kgZXN0IDAuMTQsIHNvaXQgdW5lIGZhaWJsZSBjb3Jyw6lsYXRpb24gZW50cmUgbGUgbm9tYnJlIGRlIGRpc3BvbmliaWxpdMOpcyBldCBsZSBub21icmUgZGUgbWlzZXMgw6Agam91ciBkZSBsJ2Fubm9uY2UsIGFpbnNpIHF1ZSBsZSBub21icmUgZCdhbm5vbmNlcyBwb3VyIHVuIHByb3ByacOpdGFpcmUuIEFpbnNpIHVuIHByb3ByacOpdGFpcmUgYXZlYyBwbHVzaWV1cnMgYW5ub25jZXMgZXQgdW5lIGZyw6lxdWVuY2UgZGUgbWlzZSDDoCBqb3VyIHBsdXMgw6lsZXbDqSwgw6AgbMOpZ8OpcmVtZW50IHRlbmRhbmNlIMOgIG1pZXV4IGxvdWVyLg0KDQoNCiMjIFLDqWdyZXNzaW9uIGxpbsOpYWlyZQ0KDQpVbiDDqWzDqW1lbnQgaW50w6lyZXNzYW50IMOgIHZpc3VhbGlzZXIsIHBldXQgw6p0cmUgdW5lIHJlZ3Jlc3Npb24gbGluw6lhaXJlLCBhZmluIGRlIHZpc3VhbGlzZXIgbGUgbGllbiBxdWkgZXhpc3RlIGVudHJlIGRldXggdmFyaWFibGVzLiAgDQpQb3VyIGNldHRlIGRlcm5pw6hyZSBhbmFseXNlLCBqZSB2b3VzIHByb3Bvc2UgZCd1dGlsaXNlciBsZSBqZXUgZGUgZG9ubsOpZXMgc3VpdmFudCAgW3N1ciBjZSBsaWVuXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vam9obi1ib3llci1waGQvVGVuc29yRmxvdy1TYW1wbGVzL21hc3Rlci9OZXVyYWwlMjBOZXQvYmFua2xvYW5EYXRhLmNzdikuICANCkNlIGpldSBkZSBkb25uw6llcyBjb250aWVudCBkZXMgcHLDqnRzIGJhbmNhaXJlcyBhdmVjIGRlcyBpbmZvcm1hdGlvbnMgY2xpZW50IGFmaW4gZGUgcHLDqXZlbmlyIGxlcyBkw6lmYXV0cyBkZSBwYWllbWVudC4NCg0KDQpgYGB7cn0NCmRmX2JhbmsgPSByZWFkX2NzdignQzovVXNlcnMvRElPTkdBL0Rvd25sb2Fkcy9kYXRhc2V0L2JhbmsgbG9hbi9iYW5rbG9hbi5jc3YnKQ0KZGZfYmFuaw0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QoZGZfYmFuaykgKw0KICBhZXMoeCA9IGluY29tZSwgeSA9IGVtcGxveSkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKSArDQogIGdlb21fcG9pbnQoKSArDQogIHhsYWIoIlJldmVudXMiKSArDQogIHlsYWIoIlR5cGVzIGQnZW1wbG9pcyIpDQpgYGANCg0KYGBge3J9DQpjb3IoZGZfYmFuayRpbmNvbWUsIGRmX2JhbmskZW1wbG95KSBeIDINCmBgYA0KDQpJY2kgb24gdm9pdCBxdSdpbCB5IGEgdW4gbGllbiBmb3J0IGVudHJlIGxlIHR5cGUgZCdlbXBsb2kgZXQgbGUgcmV2ZW51LCBjZSBxdWkgZW4gc29pdCBwYXJhaXQgYXNzZXogw6l2aWRlbnQsIHBvdXIgYXV0YW50IGxhIHLDqWdyZXNzaW9uIGxpbsOpYWlyZSBpY2kgbidleHBsaXF1ZSBxdWUgMzklIGRlIGxhIHZhcmlhbmNlIHRvdGFsZS4gIA0KDQojIFN5bnRow6hzZQ0KDQp8Rm9uY3Rpb25zIFJ8VW5pdmFyacOpZXMgLyBiaWF2YXJpw6llcyAvIE11bHRpdmFyacOpZXN8VHlwZXMgZGUgZ3JhcGhpcXVlc3wNCnwtLXwtLXwtLXwNCnxIaXN0b2dyYW1lfFVuaXZhcmnDqXxgZ2VvbV9oaXN0b2dyYW0oKWB8DQp8Q291cmJlIGdhdXNzaWFuIG91IG5vbnxVbml2YXJpw6l8YGdlb21fZGVuc2l0eSgpYHwNCnxCb2l0ZSDDoCBtb3VzdGFjaGV8VW5pdmFyacOpIGV0IGJpdmFyacOpfGBnZW9tX2JveHBsb3QoKWB8DQp8RGlhZ3JhbW1lIGVuIGLDonRvbnN8VW5pdmFyacOpfGBnZW9tX2JhcigpYHwNCnxOdWFnZSBkZSBwb2ludHN8Qml2YXJpw6l8YGdlb21fcG9pbnQoKWB8DQp8KipRdWVscXVlcyBwbHVzKip8fHwNCnxNYXRyaWNlIGRlIGNvcnLDqWxhdGlvbnN8Qml2YXJpw6kgZXQgbXVsdGl2YXJpw6l8YGNvcnJwbG90KClgfA0KfFLDqWdyZXNzaW9uIGxpbsOpYWlyZXxCaXZhcmnDqXxgZ2VvbV9zbW9vdGgoKWB8DQoNCg==