A post from data.visualisation.free.fr

library(ggplot2)
library(ggthemes)
#library(doBy)
library(lubridate)
library(dplyr)
library(xtable)
library(readr)
library(reshape2)
###### --- General ---- 
Myroot <- "c:/Chris/Cours2016/MOOC/"
#Myroot <- "F:/ZChris/Cours2016/MOOC/"

Why ``cheating’’ is sometimes useful in data science

Recently, I’ve worked on data from a MOOC we have created with some colleagues. The dataset was quite impressive since more than 3000 learners joined the course, viewed or interacted with some ressources (called ``steps‘’), posted comments and pass some tests. One of our goal was to create a data visualisation that alowed us to see the results of the learners’ tests, and, if possible, to detect some pattern in learners’ results over the 5 tests. The data set looks like that:

sample_n(select(Scorebylearners, learner_id, step, test_score), 10)

Using that dataset, we wanted to answer some questions:

Are there some visible patterns? Are learners with good results for one test still good at another?

So my first reflex was a plot with all the learners’results over the 5 steps:

Plot.Point <- ggplot(Scorebylearners, aes(x=step, y= test_score)) + 
  geom_point(color = "grey", alpha=0.80) +
  scale_x_discrete(name="Test step number", limits=c("1.15", "2.12", "3.21" , "4.4.", "4.10")) +
  scale_y_discrete(name ="Score",  limits=c(0,3,6,9, 12)) +
  labs(title = "Learners' score for each test ", 
       subtitle = paste("N=", nrow(Scorebylearners), "learners - ", nrow(TestAnalysis),"observations"),
       caption = "Source: MOOC ``Manage your prices'', FutureLearn (2017)"
       ) +
  coord_cartesian(ylim = c(0,12)) +
  theme_tufte()
Plot.Point

Of course, the results to these tests are integers and take only some fixed values from 0 to 12 and many observations are overlapping.

This is a begginers’ mistake!

Well, so my second reflex was to use classical statistical representation such as the good old box-and-whiskers plot (boxplots)! .

Plot.Box <-  ggplot(Scorebylearners, aes(x=step, y= test_score)) + 
  geom_boxplot(outlier.colour= "grey", color= "darkgrey", fill="grey") + 
  guides(colour=FALSE, fill=FALSE)+
  scale_x_discrete(name="Test step number", limits=c("1.15", "2.12", "3.21" , "4.4.", "4.10")) +
  scale_y_discrete(name ="Score",  limits=c(0,3,6,9, 12)) +
  labs(title = "Distribution of learners' score for each test (Box plot)", 
       subtitle = paste("N=", nrow(Scorebylearners), "learners - ", nrow(TestAnalysis),"observations"),
       caption = "Source: MOOC ``Manage your prices'', FutureLearn (2017)"
       ) +
  coord_cartesian(ylim = c(0,12)) +
  theme_tufte()
Plot.Box

That’s better, and I can see that there is some noticeable difference in the test results. But I wanted to see the individuals performances inside the boxes.

For that I have no choice but to cheat a little bit …

Cheating a little bit by adding some random noise…

In order to avoid overlapping, there are 2 basic tricks: * to use transparency (or brushing, or alpha-transparency) * to jitter the data by adding some random component to either the horizontal or vertical component.

Let us add transparency and horizontal jitter only.

Plot.Jitter.H <- ggplot(Scorebylearners, aes(x=step, y= test_score)) + 
  geom_jitter(color = "grey", alpha=0.20, width=0.20, height = 0) +
  scale_x_discrete(name="Test step number", limits=c("1.15", "2.12", "3.21" , "4.4.", "4.10")) +
  scale_y_discrete(name ="Score",  limits=c(0,3,6,9, 12)) +
  labs(title = "Learners' score for each test (horizontal jitter)", 
       subtitle = paste("N=", nrow(Scorebylearners), "learners - ", nrow(TestAnalysis),"observations"),
       caption = "Source: MOOC ``Manage your prices'', FutureLearn (2017)"
       ) +
  coord_cartesian(ylim = c(0,12)) +
  theme_tufte()
Plot.Jitter.H

The points we see now (thanks to jiter) are not the original ones. Is that cheating?

Let us do transparency and vertical jitter.

Let us add transparency with horizontal and vertical jitter.

Plot.Jitter <- ggplot(Scorebylearners, aes(x=step, y= test_score)) + 
  geom_jitter(color = "grey", alpha=0.60, width=0.40) +
  scale_x_discrete(name="Test step number", limits=c("1.15", "2.12", "3.21" , "4.4.", "4.10")) +
  scale_y_discrete(name ="Score",  limits=c(0,3,6,9, 12)) +
  labs(title = "Learners' score for each test (Horizontal + vertical jitter)", 
       subtitle = paste("N=", nrow(Scorebylearners), "learners - ", nrow(TestAnalysis),"observations"),
       caption = "Source: MOOC ``Manage your prices'', FutureLearn (2017)"
       ) +
  coord_cartesian(ylim = c(0,12)) +
  theme_tufte()
Plot.Jitter

How cheating helps for ploting lines in parallel plots

Now, if we want to follow learners results over time (over tests), we can une parallel plots and draw lines linking each result.

#Spaghetti plot original 
Plot.spaghetti <-  ggplot(Scorebylearners, 
                          aes(x=step, y= test_score,
                              group=factor(learner_id))) +
  guides(colour=FALSE) + 
  scale_x_discrete(name="Test step number", limits=c("1.15", "2.12", "3.21" , "4.4.", "4.10")) +
  scale_y_discrete(name ="Score",  limits=c(0,3,6,9, 12)) +
  labs(title = "Learners' score for each test  (Parallel plot)", 
       subtitle = paste("N=", nrow(Scorebylearners), "learners - ", nrow(TestAnalysis),"observations"),
       caption = "Source: MOOC ``Manage your prices'', FutureLearn (2017)"
       ) +
  coord_cartesian(ylim = c(0,12)) +
  theme_tufte()
 
#Plot Spaghetti brut
Plot.spaghetti + 
  geom_line( color="grey", size=1) +
  theme_tufte()

Since the score range from 1 to 12 and are discrete. many lines overlap and it is quite impossible to see some “pattern” in learners score. Nothing emerges from this simultation.

Let us cheat a little bit

#Adding jitter on Ys, and alpha-brushing 
Plot.spaghetti + 
  geom_line(alpha=0.30, color="grey", size=1, 
            aes(y = jitter(test_score, 2), x = step , group=factor(learner_id))) + 
  theme_tufte()

Now we see it !

The difference between the two graph is quite striking. By adding some vertical noise on the Y axis - that is modifying randomly the score value so that it is not integer any more - and using some brushing , help revealing some unseen and unnoticed patterns.

We can also add some colour to follow individuals over those

#
Plot.spaghetti + 
  geom_line(alpha=0.10, color=rainbow(nrow(Scorebylearners)), size=1.5, 
            aes(y = jitter(test_score, 2), x = step , group=factor(learner_id))) + 
  theme_tufte()


Done in Toulouse (France), by Xtophe. Usual citation policy and disclaimer apply. Comments on my twitter account are welcome

LS0tDQp0aXRsZTogIkV2ZXJ5Ym9keSdzIGdvdCB0byBjaGVhdCBzb21ldGltZXMuLi5ldmVuIG1lISINCmF1dGhvcjogIlh0b3BoZSBCb250ZW1wcyINCmRhdGU6ICIyNSBzZXB0ZW1icmUgMjAxNyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgdGhlbWU6IGpvdXJuYWwNCi0tLQ0KDQoqQSBwb3N0IGZyb20gW2RhdGEudmlzdWFsaXNhdGlvbi5mcmVlLmZyXShodHRwOi8vZGF0YS52aXN1YWxpc2F0aW9uLmZyZWUuZnIpKg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gRkFMU0UpDQpgYGANCg0KYGBge3IgcGFja2FnZXMsIG1lc3NhZ2UgPUZBTFNFLHJlc3VsdHM9J2hpZGUnfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShnZ3RoZW1lcykNCiNsaWJyYXJ5KGRvQnkpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHh0YWJsZSkNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KDQpgYGANCg0KYGBge3IgZGF0YS1wcm9jZXNzaW5nLCByZXN1bHRzPSdoaWRlJywgZWNobz1GQUxTRX0NCiMjIyMjIyAtLS0gR2VuZXJhbCAtLS0tIA0KTXlyb290IDwtICJjOi9DaHJpcy9Db3VyczIwMTYvTU9PQy8iDQojTXlyb290IDwtICJGOi9aQ2hyaXMvQ291cnMyMDE2L01PT0MvIg0KDQojIyMjIyAtLS0tLSBVU0VSIERlZmluZWQgQ291cnNlIGluZm8tLS0tDQojIENvdXJzZSBpbmZvDQpjb3Vyc2V0aXRsZSA8LSAicHJpY2luZy1zdHJhdGVneS1yZXZlbnVlLW1hbmFnZW1lbnQiDQpjb3Vyc2VydW4gPC0gMQ0KY291cnNlU2x1ZyA8LSBwYXN0ZSgicHJpY2luZy1zdHJhdGVneS1yZXZlbnVlLW1hbmFnZW1lbnQtIixjb3Vyc2VydW4sIHNlcD0iIikNCmNvdXJzZVN0YXJ0RGF0ZSA8LSBhcy5EYXRlKCIyMDE3LTAxLTE2IikNCg0KIyMgSW1wb3J0aW5nIGRhdGENCg0KcXVlc3Rpb25SZXNwb25zZSA8LSByZWFkX2NzdihwYXN0ZShNeXJvb3QsIkRhdGFSZXBvcnQtIixjb3Vyc2VydW4sIi9kYXRhLyIsIGNvdXJzZVNsdWcsICJfcXVlc3Rpb24tcmVzcG9uc2UuY3N2Iiwgc2VwPSIiKSwgY29sX3R5cGVzID0gbGlzdCgNCiAgcXVpel9xdWVzdGlvbiA9IGNvbF9jaGFyYWN0ZXIoKSwNCiAgcmVzcG9uc2UgPSBjb2xfY2hhcmFjdGVyKCksDQogIGNvcnJlY3QgPSBjb2xfY2hhcmFjdGVyKCkNCikpDQoNCiMjIC0tLS1RdWl6eiBhbmQgVGVzdA0KcXVpemJ5bGVhcm5lciA8LSBxdWVzdGlvblJlc3BvbnNlICU+JQ0KICAgZ3JvdXBfYnkobGVhcm5lcl9pZCkgJT4lDQogIHN1bW1hcmlzZSAoDQogICAgbmJfcXVpej0gbl9kaXN0aW5jdChxdWl6X3F1ZXN0aW9uKQ0KICAgICAgKQ0KDQogU3VjY2Vzc2J5bGVhcm5lclF1aXp6IDwtIHF1ZXN0aW9uUmVzcG9uc2UgJT4lDQogIGdyb3VwX2J5KGxlYXJuZXJfaWQsIHF1aXpfcXVlc3Rpb24pICU+JSANCiAgIHN1bW1hcml6ZSgNCiAgICAgYXR0ZW1wdHMgPSBuKCksDQogICAgIGNvcnJlY3QgPSBzdW0oY29ycmVjdD09InRydWUiKSwNCiAgICAgc2NvcmUgPSBhYnMoYXR0ZW1wdHMtNCkNCiAgICklPiUNCiAgIHVuZ3JvdXAoKQ0KICAgDQpRdWl6ekFuYWx5c2lzIDwtIG1lcmdlKFN1Y2Nlc3NieWxlYXJuZXJRdWl6eiAsIHF1aXpieWxlYXJuZXIsIGJ5Lng9ImxlYXJuZXJfaWQiKQ0KIA0KUXVpenpBbmFseXNpcyA8LSBRdWl6ekFuYWx5c2lzICU+JSANCiAgZ3JvdXBfYnkobGVhcm5lcl9pZCkgJT4lDQogIG11dGF0ZSggDQogICAgICB0b3RhbHNjb3JlID0gc3VtKHNjb3JlKSwNCiAgICAgIGF2ZXJhZ2VzY29yZSA9IHRvdGFsc2NvcmUvbmJfcXVpeg0KICAgICAgICAgICAgICApDQoNCiMjLS0tLS1URVNUIEFOQUxZU0lTIC0tLS0jIyMjDQoNCiMgVGhlIFN0ZXBzIGZvciBvdXIgVEVTVCBhcmUgMS4xNSwgMi4xMiwgMy4yMSAsIDQuNCBhbmQgNC4xMCAhISENClRlc3RBbmFseXNpcyA8LSAgbXV0YXRlIChTdWNjZXNzYnlsZWFybmVyUXVpenosIHN0ZXA9c3Vic3RyKFN1Y2Nlc3NieWxlYXJuZXJRdWl6eiRxdWl6X3F1ZXN0aW9uLCAxLDQpKSU+JQ0KICBmaWx0ZXIoc3RlcCA9PSAiMS4xNSIgfCBzdGVwID09ICIyLjEyInwgc3RlcCA9PSAiMy4yMSJ8IHN0ZXAgPT0gIjQuNC4ifCBzdGVwID09ICI0LjEwIiApJT4lDQogIGdyb3VwX2J5KGxlYXJuZXJfaWQpICAlPiUNCiAgbXV0YXRlKCBuYl90ZXN0ID0gbl9kaXN0aW5jdChzdGVwKSApICU+JQ0KICBncm91cF9ieShsZWFybmVyX2lkLCBzdGVwKSAlPiUNCiAgbXV0YXRlKCB0ZXN0X3Njb3JlID0gc3VtKHNjb3JlKQ0KICAgICAgICAgICAgICAgICAgICAgICAgKQ0KDQojbWFpbiBzb3VyY2UgZm9yIHRlc3QgYW5hbHlzaXMgKHNjb3JlIGZvciBlYWNoIGxlYXJuZXIsIGVhY2ggdGVzdCkNClNjb3JlYnlsZWFybmVycyA8LSAgVGVzdEFuYWx5c2lzICU+JQ0KICBzZWxlY3QobGVhcm5lcl9pZCwgc3RlcCwgbmJfdGVzdCwgdGVzdF9zY29yZSApDQpTY29yZWJ5bGVhcm5lcnMgPC11bmlxdWUoU2NvcmVieWxlYXJuZXJzKSAgDQoNCiNOYiBvZiBsZWFybmVycyBhdHRlbmRpZyB0aGUgdGVzdHMNCkxlYXJuZXJzYnlUZXN0IDwtICBtdXRhdGUgKHF1ZXN0aW9uUmVzcG9uc2UsIHN0ZXA9c3Vic3RyKHF1ZXN0aW9uUmVzcG9uc2UkcXVpel9xdWVzdGlvbiwgMSw0KSklPiUNCiAgZmlsdGVyKHN0ZXAgPT0gIjEuMTUiIHwgc3RlcCA9PSAiMi4xMiJ8IHN0ZXAgPT0gIjMuMjEifCBzdGVwID09ICI0LjQuInwgc3RlcCA9PSAiNC4xMCIgKSU+JQ0KICBncm91cF9ieShzdGVwICkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICAgICAgICAgIE5iID0gbl9kaXN0aW5jdChsZWFybmVyX2lkKQ0KICApICU+JQ0KICB1bmdyb3VwKCkNCg0KI1dlIGFkZCBpbmZvcm1hdGlvbiBvbiBuYiBvZiBsZXJhbmVycyBmb3IgZWFjaCBzdGVwDQpTY29yZWJ5bGVhcm5lcnMgPC0gbWVyZ2UoU2NvcmVieWxlYXJuZXJzLCBMZWFybmVyc2J5VGVzdCwgYnk9InN0ZXAiKQ0KYGBgDQoNCg0KIyMjIFdoeSBgYGNoZWF0aW5nJycgaXMgc29tZXRpbWVzIHVzZWZ1bCBpbiBkYXRhIHNjaWVuY2UNCg0KUmVjZW50bHksIEkndmUgd29ya2VkIG9uIGRhdGEgZnJvbSBhIE1PT0Mgd2UgaGF2ZSBjcmVhdGVkIHdpdGggc29tZSBjb2xsZWFndWVzLiBUaGUgZGF0YXNldCB3YXMgcXVpdGUgaW1wcmVzc2l2ZSBzaW5jZSBtb3JlIHRoYW4gMzAwMCBsZWFybmVycyBqb2luZWQgdGhlIGNvdXJzZSwgdmlld2VkIG9yIGludGVyYWN0ZWQgd2l0aCBzb21lIHJlc3NvdXJjZXMgKGNhbGxlZCBgYCpzdGVwcyonJyksICBwb3N0ZWQgY29tbWVudHMgYW5kIHBhc3Mgc29tZSB0ZXN0cy4gT25lIG9mIG91ciBnb2FsIHdhcyB0byBjcmVhdGUgYSBkYXRhIHZpc3VhbGlzYXRpb24gdGhhdCBhbG93ZWQgdXMgdG8gc2VlIHRoZSByZXN1bHRzIG9mIHRoZSBsZWFybmVycycgdGVzdHMsIGFuZCwgaWYgcG9zc2libGUsIHRvIGRldGVjdCBzb21lIHBhdHRlcm4gaW4gbGVhcm5lcnMnIHJlc3VsdHMgb3ZlciB0aGUgNSB0ZXN0cy4gVGhlIGRhdGEgc2V0IGxvb2tzIGxpa2UgdGhhdDoNCg0KDQpgYGB7cn0NCnNhbXBsZV9uKHNlbGVjdChTY29yZWJ5bGVhcm5lcnMsIGxlYXJuZXJfaWQsIHN0ZXAsIHRlc3Rfc2NvcmUpLCAxMCkNCmBgYA0KDQoNClVzaW5nIHRoYXQgZGF0YXNldCwgd2Ugd2FudGVkIHRvIGFuc3dlciBzb21lIHF1ZXN0aW9uczogICANCg0KPiBBcmUgIHRoZXJlIHNvbWUgdmlzaWJsZSBwYXR0ZXJucz8gQXJlIGxlYXJuZXJzIHdpdGggZ29vZCByZXN1bHRzIGZvciBvbmUgdGVzdCBzdGlsbCBnb29kIGF0IGFub3RoZXI/ICANCg0KU28gbXkgZmlyc3QgcmVmbGV4IHdhcyBhIHBsb3Qgd2l0aCBhbGwgdGhlIGxlYXJuZXJzJ3Jlc3VsdHMgb3ZlciB0aGUgNSBzdGVwczogIA0KDQpgYGB7ciBwb2ludH0NClBsb3QuUG9pbnQgPC0gZ2dwbG90KFNjb3JlYnlsZWFybmVycywgYWVzKHg9c3RlcCwgeT0gdGVzdF9zY29yZSkpICsgDQogIGdlb21fcG9pbnQoY29sb3IgPSAiZ3JleSIsIGFscGhhPTAuODApICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lPSJUZXN0IHN0ZXAgbnVtYmVyIiwgbGltaXRzPWMoIjEuMTUiLCAiMi4xMiIsICIzLjIxIiAsICI0LjQuIiwgIjQuMTAiKSkgKw0KICBzY2FsZV95X2Rpc2NyZXRlKG5hbWUgPSJTY29yZSIsICBsaW1pdHM9YygwLDMsNiw5LCAxMikpICsNCiAgbGFicyh0aXRsZSA9ICJMZWFybmVycycgc2NvcmUgZm9yIGVhY2ggdGVzdCAiLCANCiAgICAgICBzdWJ0aXRsZSA9IHBhc3RlKCJOPSIsIG5yb3coU2NvcmVieWxlYXJuZXJzKSwgImxlYXJuZXJzIC0gIiwgbnJvdyhUZXN0QW5hbHlzaXMpLCJvYnNlcnZhdGlvbnMiKSwNCiAgICAgICBjYXB0aW9uID0gIlNvdXJjZTogTU9PQyBgYE1hbmFnZSB5b3VyIHByaWNlcycnLCBGdXR1cmVMZWFybiAoMjAxNykiDQogICAgICAgKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLDEyKSkgKw0KICB0aGVtZV90dWZ0ZSgpDQoNClBsb3QuUG9pbnQNCmBgYA0KDQoNCk9mIGNvdXJzZSwgdGhlIHJlc3VsdHMgdG8gdGhlc2UgdGVzdHMgYXJlIGludGVnZXJzIGFuZCB0YWtlIG9ubHkgc29tZSBmaXhlZCB2YWx1ZXMgZnJvbSAwIHRvIDEyIGFuZCBtYW55IG9ic2VydmF0aW9ucyBhcmUgKm92ZXJsYXBwaW5nKi4gIA0KDQo+IFRoaXMgaXMgYSBiZWdnaW5lcnMnIG1pc3Rha2UhIA0KDQpXZWxsLCBzbyBteSBzZWNvbmQgcmVmbGV4IHdhcyB0byB1c2UgY2xhc3NpY2FsIHN0YXRpc3RpY2FsIHJlcHJlc2VudGF0aW9uIHN1Y2ggYXMgdGhlIGdvb2Qgb2xkIGJveC1hbmQtd2hpc2tlcnMgcGxvdCAoYm94cGxvdHMpISAgLiANCmBgYHtyIGJveCB9DQpQbG90LkJveCA8LSAgZ2dwbG90KFNjb3JlYnlsZWFybmVycywgYWVzKHg9c3RlcCwgeT0gdGVzdF9zY29yZSkpICsgDQogIGdlb21fYm94cGxvdChvdXRsaWVyLmNvbG91cj0gImdyZXkiLCBjb2xvcj0gImRhcmtncmV5IiwgZmlsbD0iZ3JleSIpICsgDQogIGd1aWRlcyhjb2xvdXI9RkFMU0UsIGZpbGw9RkFMU0UpKw0KICBzY2FsZV94X2Rpc2NyZXRlKG5hbWU9IlRlc3Qgc3RlcCBudW1iZXIiLCBsaW1pdHM9YygiMS4xNSIsICIyLjEyIiwgIjMuMjEiICwgIjQuNC4iLCAiNC4xMCIpKSArDQogIHNjYWxlX3lfZGlzY3JldGUobmFtZSA9IlNjb3JlIiwgIGxpbWl0cz1jKDAsMyw2LDksIDEyKSkgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBsZWFybmVycycgc2NvcmUgZm9yIGVhY2ggdGVzdCAoQm94IHBsb3QpIiwgDQogICAgICAgc3VidGl0bGUgPSBwYXN0ZSgiTj0iLCBucm93KFNjb3JlYnlsZWFybmVycyksICJsZWFybmVycyAtICIsIG5yb3coVGVzdEFuYWx5c2lzKSwib2JzZXJ2YXRpb25zIiksDQogICAgICAgY2FwdGlvbiA9ICJTb3VyY2U6IE1PT0MgYGBNYW5hZ2UgeW91ciBwcmljZXMnJywgRnV0dXJlTGVhcm4gKDIwMTcpIg0KICAgICAgICkgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwxMikpICsNCiAgdGhlbWVfdHVmdGUoKQ0KDQpQbG90LkJveA0KYGBgDQoNClRoYXQncyBiZXR0ZXIsIGFuZCBJIGNhbiBzZWUgdGhhdCB0aGVyZSBpcyBzb21lIG5vdGljZWFibGUgZGlmZmVyZW5jZSBpbiB0aGUgdGVzdCByZXN1bHRzLiANCkJ1dCBJIHdhbnRlZCB0byBzZWUgdGhlIGluZGl2aWR1YWxzIHBlcmZvcm1hbmNlcyBpbnNpZGUgdGhlIGJveGVzLiANCg0KDQo+IEZvciB0aGF0IEkgaGF2ZSBubyBjaG9pY2UgYnV0ICoqdG8gY2hlYXQgYSBsaXR0bGUgYml0IC4uLioqDQoNCiMjIyBDaGVhdGluZyBhIGxpdHRsZSBiaXQgYnkgYWRkaW5nIHNvbWUgcmFuZG9tIG5vaXNlLi4uDQoNCkluIG9yZGVyIHRvIGF2b2lkIG92ZXJsYXBwaW5nLCB0aGVyZSBhcmUgMiBiYXNpYyB0cmlja3M6DQoqIHRvIHVzZSAqdHJhbnNwYXJlbmN5KiAob3IgYnJ1c2hpbmcsIG9yIGFscGhhLXRyYW5zcGFyZW5jeSkNCiogdG8gKmppdHRlciogdGhlIGRhdGEgYnkgYWRkaW5nIHNvbWUgcmFuZG9tIGNvbXBvbmVudCB0byBlaXRoZXIgdGhlIGhvcml6b250YWwgb3IgdmVydGljYWwgY29tcG9uZW50LiANCg0KTGV0IHVzIGFkZCAqKnRyYW5zcGFyZW5jeSoqIGFuZCAqKmhvcml6b250YWwgaml0dGVyKiogb25seS4gDQoNCiAgDQpgYGB7ciBqaXR0ZXIuSCB9DQpQbG90LkppdHRlci5IIDwtIGdncGxvdChTY29yZWJ5bGVhcm5lcnMsIGFlcyh4PXN0ZXAsIHk9IHRlc3Rfc2NvcmUpKSArIA0KICBnZW9tX2ppdHRlcihjb2xvciA9ICJncmV5IiwgYWxwaGE9MC4yMCwgd2lkdGg9MC4yMCwgaGVpZ2h0ID0gMCkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKG5hbWU9IlRlc3Qgc3RlcCBudW1iZXIiLCBsaW1pdHM9YygiMS4xNSIsICIyLjEyIiwgIjMuMjEiICwgIjQuNC4iLCAiNC4xMCIpKSArDQogIHNjYWxlX3lfZGlzY3JldGUobmFtZSA9IlNjb3JlIiwgIGxpbWl0cz1jKDAsMyw2LDksIDEyKSkgKw0KICBsYWJzKHRpdGxlID0gIkxlYXJuZXJzJyBzY29yZSBmb3IgZWFjaCB0ZXN0IChob3Jpem9udGFsIGppdHRlcikiLCANCiAgICAgICBzdWJ0aXRsZSA9IHBhc3RlKCJOPSIsIG5yb3coU2NvcmVieWxlYXJuZXJzKSwgImxlYXJuZXJzIC0gIiwgbnJvdyhUZXN0QW5hbHlzaXMpLCJvYnNlcnZhdGlvbnMiKSwNCiAgICAgICBjYXB0aW9uID0gIlNvdXJjZTogTU9PQyBgYE1hbmFnZSB5b3VyIHByaWNlcycnLCBGdXR1cmVMZWFybiAoMjAxNykiDQogICAgICAgKSArDQogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLDEyKSkgKw0KICB0aGVtZV90dWZ0ZSgpDQpQbG90LkppdHRlci5IDQpgYGANCg0KPiBUaGUgcG9pbnRzIHdlIHNlZSBub3cgKHRoYW5rcyB0byAqaml0ZXIqKSBhcmUgbm90IHRoZSBvcmlnaW5hbCBvbmVzLiBJcyB0aGF0ICpjaGVhdGluZyo/ICANCg0KTGV0IHVzIGRvICoqdHJhbnNwYXJlbmN5KiogYW5kICoqdmVydGljYWwgaml0dGVyKiouIA0KDQogIA0KYGBge3Igaml0dGVyLlYsIGVjaG89RkFMU0UsICwgcmVzdWx0cz0iaGlkZSJ9DQpQbG90LkppdHRlci5WIDwtIGdncGxvdChTY29yZWJ5bGVhcm5lcnMsIGFlcyh4PXN0ZXAsIHk9IHRlc3Rfc2NvcmUpKSArIA0KICBnZW9tX2ppdHRlcihjb2xvciA9ICJncmV5IiwgYWxwaGE9MC4xMCwgd2lkdGg9MCwgaGVpZ2h0ID0gMC4yMCkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKG5hbWU9IlRlc3Qgc3RlcCBudW1iZXIiLCBsaW1pdHM9YygiMS4xNSIsICIyLjEyIiwgIjMuMjEiICwgIjQuNC4iLCAiNC4xMCIpKSArDQogIHNjYWxlX3lfZGlzY3JldGUobmFtZSA9IlNjb3JlIiwgIGxpbWl0cz1jKDAsMyw2LDksIDEyKSkgKw0KICBsYWJzKHRpdGxlID0gIkxlYXJuZXJzJyBzY29yZSBmb3IgZWFjaCB0ZXN0ICh2ZXJ0aWNhbCBqaXR0ZXIpIiwgDQogICAgICAgc3VidGl0bGUgPSBwYXN0ZSgiTj0iLCBucm93KFNjb3JlYnlsZWFybmVycyksICJsZWFybmVycyAtICIsIG5yb3coVGVzdEFuYWx5c2lzKSwib2JzZXJ2YXRpb25zIiksDQogICAgICAgY2FwdGlvbiA9ICJTb3VyY2U6IE1PT0MgYGBNYW5hZ2UgeW91ciBwcmljZXMnJywgRnV0dXJlTGVhcm4gKDIwMTcpIg0KICAgICAgICkgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwxMikpICsNCiAgdGhlbWVfdHVmdGUoKQ0KUGxvdC5KaXR0ZXIuVg0KDQpgYGANCg0KDQpMZXQgdXMgYWRkICoqdHJhbnNwYXJlbmN5Kiogd2l0aCAgKipob3Jpem9udGFsIGFuZCB2ZXJ0aWNhbCBqaXR0ZXIqKi4gDQoNCiAgDQpgYGB7ciBqaXR0ZXIsICByZXN1bHRzPSJoaWRlIn0NClBsb3QuSml0dGVyIDwtIGdncGxvdChTY29yZWJ5bGVhcm5lcnMsIGFlcyh4PXN0ZXAsIHk9IHRlc3Rfc2NvcmUpKSArIA0KICBnZW9tX2ppdHRlcihjb2xvciA9ICJncmV5IiwgYWxwaGE9MC42MCwgd2lkdGg9MC40MCkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKG5hbWU9IlRlc3Qgc3RlcCBudW1iZXIiLCBsaW1pdHM9YygiMS4xNSIsICIyLjEyIiwgIjMuMjEiICwgIjQuNC4iLCAiNC4xMCIpKSArDQogIHNjYWxlX3lfZGlzY3JldGUobmFtZSA9IlNjb3JlIiwgIGxpbWl0cz1jKDAsMyw2LDksIDEyKSkgKw0KICBsYWJzKHRpdGxlID0gIkxlYXJuZXJzJyBzY29yZSBmb3IgZWFjaCB0ZXN0IChIb3Jpem9udGFsICsgdmVydGljYWwgaml0dGVyKSIsIA0KICAgICAgIHN1YnRpdGxlID0gcGFzdGUoIk49IiwgbnJvdyhTY29yZWJ5bGVhcm5lcnMpLCAibGVhcm5lcnMgLSAiLCBucm93KFRlc3RBbmFseXNpcyksIm9ic2VydmF0aW9ucyIpLA0KICAgICAgIGNhcHRpb24gPSAiU291cmNlOiBNT09DIGBgTWFuYWdlIHlvdXIgcHJpY2VzJycsIEZ1dHVyZUxlYXJuICgyMDE3KSINCiAgICAgICApICsNCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsMTIpKSArDQogIHRoZW1lX3R1ZnRlKCkNClBsb3QuSml0dGVyDQoNCmBgYA0KDQojIyMgSG93ICpjaGVhdGluZyogIGhlbHBzIGZvciBwbG90aW5nIGxpbmVzIGluICBwYXJhbGxlbCBwbG90cyANCg0KTm93LCBpZiB3ZSB3YW50IHRvIGZvbGxvdyBsZWFybmVycyByZXN1bHRzIG92ZXIgdGltZSAob3ZlciB0ZXN0cyksIHdlICBjYW4gdW5lICpwYXJhbGxlbCBwbG90cyogYW5kIGRyYXcgbGluZXMgbGlua2luZyBlYWNoIHJlc3VsdC4gIA0KDQpgYGB7ciBTcGFnaGV0dGlCcnV0LCByZXN1bHRzPSJoaWRlIn0NCiNTcGFnaGV0dGkgcGxvdCBvcmlnaW5hbCANClBsb3Quc3BhZ2hldHRpIDwtICBnZ3Bsb3QoU2NvcmVieWxlYXJuZXJzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHg9c3RlcCwgeT0gdGVzdF9zY29yZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwPWZhY3RvcihsZWFybmVyX2lkKSkpICsNCiAgZ3VpZGVzKGNvbG91cj1GQUxTRSkgKyANCiAgc2NhbGVfeF9kaXNjcmV0ZShuYW1lPSJUZXN0IHN0ZXAgbnVtYmVyIiwgbGltaXRzPWMoIjEuMTUiLCAiMi4xMiIsICIzLjIxIiAsICI0LjQuIiwgIjQuMTAiKSkgKw0KICBzY2FsZV95X2Rpc2NyZXRlKG5hbWUgPSJTY29yZSIsICBsaW1pdHM9YygwLDMsNiw5LCAxMikpICsNCiAgbGFicyh0aXRsZSA9ICJMZWFybmVycycgc2NvcmUgZm9yIGVhY2ggdGVzdCAgKFBhcmFsbGVsIHBsb3QpIiwgDQogICAgICAgc3VidGl0bGUgPSBwYXN0ZSgiTj0iLCBucm93KFNjb3JlYnlsZWFybmVycyksICJsZWFybmVycyAtICIsIG5yb3coVGVzdEFuYWx5c2lzKSwib2JzZXJ2YXRpb25zIiksDQogICAgICAgY2FwdGlvbiA9ICJTb3VyY2U6IE1PT0MgYGBNYW5hZ2UgeW91ciBwcmljZXMnJywgRnV0dXJlTGVhcm4gKDIwMTcpIg0KICAgICAgICkgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwxMikpICsNCiAgdGhlbWVfdHVmdGUoKQ0KIA0KI1Bsb3QgU3BhZ2hldHRpIGJydXQNClBsb3Quc3BhZ2hldHRpICsgDQogIGdlb21fbGluZSggY29sb3I9ImdyZXkiLCBzaXplPTEpICsNCiAgdGhlbWVfdHVmdGUoKQ0KDQoNCmBgYA0KDQpTaW5jZSB0aGUgc2NvcmUgcmFuZ2UgZnJvbSAxIHRvIDEyIGFuZCBhcmUgZGlzY3JldGUuIG1hbnkgbGluZXMgb3ZlcmxhcCBhbmQgaXQgaXMgcXVpdGUgaW1wb3NzaWJsZSB0byBzZWUgc29tZSAicGF0dGVybiIgaW4gbGVhcm5lcnMgc2NvcmUuIE5vdGhpbmcgZW1lcmdlcyBmcm9tIHRoaXMgc2ltdWx0YXRpb24uIA0KDQo+IExldCB1cyAqY2hlYXQqIGEgbGl0dGxlIGJpdCANCg0KYGBge3IgU3BhZ2hldHRpSml0dGVyLCAgcmVzdWx0cz0iaGlkZSJ9DQojQWRkaW5nIGppdHRlciBvbiBZcywgYW5kIGFscGhhLWJydXNoaW5nIA0KUGxvdC5zcGFnaGV0dGkgKyANCiAgZ2VvbV9saW5lKGFscGhhPTAuMzAsIGNvbG9yPSJncmV5Iiwgc2l6ZT0xLCANCiAgICAgICAgICAgIGFlcyh5ID0gaml0dGVyKHRlc3Rfc2NvcmUsIDIpLCB4ID0gc3RlcCAsIGdyb3VwPWZhY3RvcihsZWFybmVyX2lkKSkpICsgDQogIHRoZW1lX3R1ZnRlKCkNCg0KDQpgYGANCg0KPiBOb3cgd2Ugc2VlIGl0ICENCg0KVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIGdyYXBoIGlzIHF1aXRlIHN0cmlraW5nLiBCeSBhZGRpbmcgc29tZSB2ZXJ0aWNhbCBub2lzZSBvbiB0aGUgWSBheGlzIC0gdGhhdCBpcyBtb2RpZnlpbmcgcmFuZG9tbHkgdGhlICBzY29yZSB2YWx1ZSBzbyB0aGF0IGl0IGlzIG5vdCBpbnRlZ2VyIGFueSBtb3JlIC0gYW5kIHVzaW5nIHNvbWUgKmJydXNoaW5nKiAsIGhlbHAgcmV2ZWFsaW5nIHNvbWUgdW5zZWVuIGFuZCB1bm5vdGljZWQgcGF0dGVybnMuICANCg0KV2UgY2FuIGFsc28gYWRkIHNvbWUgY29sb3VyIHRvIGZvbGxvdyBpbmRpdmlkdWFscyBvdmVyIHRob3NlIA0KDQoNCmBgYHtyIFNwYWdoZXR0aUNvbG91ciwgIHJlc3VsdHM9ImhpZGUifQ0KIw0KUGxvdC5zcGFnaGV0dGkgKyANCiAgZ2VvbV9saW5lKGFscGhhPTAuMTAsIGNvbG9yPXJhaW5ib3cobnJvdyhTY29yZWJ5bGVhcm5lcnMpKSwgc2l6ZT0xLjUsIA0KICAgICAgICAgICAgYWVzKHkgPSBqaXR0ZXIodGVzdF9zY29yZSwgMiksIHggPSBzdGVwICwgZ3JvdXA9ZmFjdG9yKGxlYXJuZXJfaWQpKSkgKyANCiAgdGhlbWVfdHVmdGUoKQ0KDQoNCg0KYGBgDQoNCg0KDQoNCg0KKioqDQoqRG9uZSBpbiBUb3Vsb3VzZSAoRnJhbmNlKSwgYnkgW1h0b3BoZV0obWFpbHRvOlh0b3BoZS5Cb250ZW1wc0BmcmVlLmZyKS4gVXN1YWwgY2l0YXRpb24gcG9saWN5IGFuZCBkaXNjbGFpbWVyIGFwcGx5LiBDb21tZW50cyBvbiBteSBbdHdpdHRlciBhY2NvdW50XShodHRwczovL3R3aXR0ZXIuY29tL1h0b3BoZV9Cb250ZW1wcykgYXJlIHdlbGNvbWUqIA0KDQoNCg==