Intro

This is some quick visualization for dog names in NYC as provided from the Kaggle dataset uploaded here: https://www.kaggle.com/new-york-city/nyc-dog-names

Data Clean-up

Let’s take a quick look at the data.

str(dt)
'data.frame':   81542 obs. of  11 variables:
 $ dog_name          : Factor w/ 13803 levels "A.","A.A.","Aaliyah",..: 1718 8761 12 2453 5665 12860 4783 11482 5562 4522 ...
 $ gender            : Factor w/ 3 levels "F","M","n/a": 2 1 1 1 1 2 1 2 2 2 ...
 $ breed             : Factor w/ 138 levels "Afghan Hound",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ birth             : Factor w/ 267 levels "0","2","3","4",..: 91 125 206 93 231 94 147 233 75 98 ...
 $ dominant_color    : Factor w/ 20 levels "APRICOT","BLACK",..: 6 2 2 20 3 3 10 2 20 15 ...
 $ secondary_color   : Factor w/ 20 levels "APRICOT","BLACK",..: 2 14 19 3 20 20 14 20 14 20 ...
 $ third_color       : Factor w/ 20 levels "APRICOT","BLACK",..: 14 14 14 14 2 2 14 13 14 14 ...
 $ spayed_or_neutered: Factor w/ 2 levels "No","Yes": 2 2 2 2 2 2 2 1 1 2 ...
 $ guard_or_trained  : Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 1 1 1 1 ...
 $ borough           : Factor w/ 5 levels "Bronx","Brooklyn",..: 3 3 3 3 3 1 3 3 4 1 ...
 $ zip_code          : int  10003 10021 10034 10024 10022 10472 10021 10023 11354 10469 ...

We have a bit of cleaning up to do, it seems R isn’t properly understanding the “n/a” values provided. We can identify all the columns with a problem with a custom function.

is_na_f <- function(x) {
  sum(x == "n/a")
}
apply(dt,2,is_na_f)
          dog_name             gender              breed              birth     dominant_color    secondary_color        third_color spayed_or_neutered 
              4025                 62                  0                  0                771              25528              64921                  0 
  guard_or_trained            borough           zip_code 
                 0                  0                  0 

Let’s manually change them.

dt$dog_name <- as.factor(as.character(ifelse(dt$dog_name == 'n/a',NA,as.character(dt$dog_name))))
dt$gender <- as.factor(as.character(ifelse(dt$gender == 'n/a',NA,as.character(dt$gender))))
dt$dominant_color <- as.factor(as.character(ifelse(dt$dominant_color == 'n/a',NA,as.character(dt$dominant_color))))
dt$secondary_color <- as.factor(as.character(ifelse(dt$secondary_color == 'n/a',NA,as.character(dt$secondary_color))))
dt$third_color <- as.factor(as.character(ifelse(dt$third_color == 'n/a',NA,as.character(dt$third_color))))
apply(apply(dt,2,is.na),2,sum)
          dog_name             gender              breed              birth     dominant_color    secondary_color        third_color spayed_or_neutered 
              4025                 62                  0                  0                771              25528              64921                  0 
  guard_or_trained            borough           zip_code 
                 0                  0                  0 

Data exploration

We can visualize a bit to get to know our data better.

plot_gender <- dt %>%
  filter(is.na(gender) == F) %>%
  ggplot(aes(x=gender)) +
  geom_bar(fill = 'light blue', alpha = 1, width = .3) +
  theme_light() +
  labs(title = "Gender Distribution", x= "Gender", y = "Total Count")
  
plot_color <- dt %>%
  filter(is.na(dominant_color) == F) %>%
  group_by(dominant_color) %>%
  summarize(count = n()) %>%
  arrange(count) %>%
  ggplot(aes(x=factor(dominant_color,levels= dominant_color), y = count)) +
  geom_bar(fill = 'light blue', alpha = 1, width = .3, stat = 'identity') +
  theme_light() +
  labs(title = "Color Distribution", x= "Dominant Color", y = "Total Count") +
  coord_flip()
plot_gt <- dt %>%
  ggplot(aes(x=guard_or_trained)) +
  geom_bar(fill = 'light blue', alpha = 1, width = .3) +
  theme_light() +
  labs(title = "Guard/Trained Distribution", x= "Guard/Trained", y = "Total Count")
plot_sn <- dt %>%
  ggplot(aes(x=spayed_or_neutered)) +
  geom_bar(fill = 'light blue', alpha = 1, width = .3) +
  theme_light() +
  labs(title = "Spayed/Neutered Distribution", x= "Spayed/Neutered", y = "Total Count")
plot_borough <- dt %>%
  filter(is.na(borough) == F) %>%
  group_by(borough) %>%
  summarize(count = n()) %>%
  arrange(count) %>%
  ggplot(aes(x=factor(borough,levels= borough), y = count)) +
  geom_bar(fill = 'light blue', alpha = 1, width = .3, stat = 'identity') +
  theme_light() +
  labs(title = "Borough Distribution", x= "Borough", y = "Total Count")
grid.arrange(plot_gender, plot_borough, plot_gt, plot_sn)

Let’s dig deeper into the differences in dog names based on gender.

plot_m <- dt %>%
  filter(gender == 'M', is.na(dog_name) == F) %>%
  group_by(dog_name) %>%
  summarize(count = n()) %>%
  arrange(count) %>%
  top_n(15) %>%
  ggplot(aes(x=factor(dog_name,levels= dog_name),y=count)) +
  geom_bar(fill = 'green', alpha = .8, width = .4, stat = 'identity') +
  theme_light() + 
  coord_flip() +
  labs(title = "Popular Male Names", x = "Name", y = "Count")
Selecting by count
plot_f <- dt %>%
  filter(gender == 'F', is.na(dog_name) == F) %>%
  group_by(dog_name) %>%
  summarize(count = n()) %>%
  arrange(count) %>%
  top_n(15) %>%
  ggplot(aes(x=factor(dog_name,levels= dog_name),y=count)) +
  geom_bar(fill = 'green', alpha = .8, width = .4, stat = 'identity') +
  theme_light() + 
  coord_flip()+
  labs(title = "Popular Female Names", x = "Name", y = "Count")
Selecting by count
grid.arrange(plot_m, plot_f)

There are some clear differences for names based on gender. Which names are popular for male and females?

dt %>%
  filter(is.na(dog_name) == F) %>%
  group_by(dog_name) %>%
  summarize(count = n(),
            count_female = length(gender[gender == 'F']), 
            count_male = length(gender[gender == 'M']),
            male_ratio = (count_male)/ (count_female + count_male),
            diff = abs(male_ratio - .5)
            ) %>%
  filter(count > 50) %>%
  arrange(diff) %>%
  top_n(-15) %>%
  ggplot(aes(x=count_female, y=count_male, fill = dog_name, label = dog_name)) +
  geom_point(alpha = .7, color = 'gray',size = 3) +
  geom_label_repel( fontface = 'bold', color = 'white', segment.color = 'grey50') + 
  theme_light() + 
  labs(title = "Most Popular Gender-Neutral Dog Names", x='Female Occurences', y='Male Occurences')
Selecting by diff

This is quite interesting, among these names it seems likely this is direcly related to their appearance or personality.

I can take a guess which borough usually registers dog’s named Brooklyn.

dt %>%
  filter(dog_name == 'Brooklyn') %>%
  ggplot(aes(x=borough, fill=gender)) +
  geom_bar( alpha = .8) + 
  theme_light() +
  labs(title = 'Dogs Named Brooklyn', y = 'Count', x = 'Borough')

Actually not as strong of a distribution as I would expect, but this could be evidence of owners traveling to a different borough when they visit a vet or just moving around in general.

Let’s do the same for “Snow” and “Snowy”… I’d expect their colors to tell the story here.

dt %>%
  filter(dog_name == 'Snow' | dog_name == 'Snowy', is.na(dominant_color) == F) %>%
  ggplot(aes(x=dominant_color, fill=gender)) +
  geom_bar( alpha = .8) + 
  theme_light() +
  labs(title = 'Dogs Named Snow/Snowy', y = 'Count', x = 'Color')

Pretty close. I am interested in the black dog named snowy, perhaps it is spotted.

dt %>%
  filter(dog_name == 'Snow' | dog_name == 'Snowy', dominant_color == 'BLACK') %>%
  select(dog_name, gender, breed, dominant_color, secondary_color, third_color)

The secondary color is almost always white, except for one poor chihuahua. Perhaps Snowy was named in irony.

Let’s take a quick look at the breeds.

dt %>%
  filter(is.na(breed) == F) %>%
  group_by(breed) %>%
  summarize(count = n()) %>%
  arrange(count) %>%
  top_n(25) %>%
  ggplot(aes(x=factor(breed,levels= breed),y=count)) +
  geom_bar(fill = 'light blue', alpha = .8, width = .4, stat = 'identity') +
  theme_light() + 
  coord_flip()+
  labs(title = "Popular Breeds", x = "Breed", y = "Count")
Selecting by count

There’s a lot of granularity here, this is only the top 25. Interesting to see the amount of muts in the population, this is roughly a quarter.

For fun, let’s take a closer look at the most popular names for Golden Retrievers.

dt %>%
  filter(breed == 'Golden Retriever', is.na(dog_name) == F) %>%
  group_by(dog_name) %>%
  summarize(count = n()) %>%
  arrange(desc(count)) %>%
  top_n(15) %>%
  inner_join(dt) %>%
  filter(breed == 'Golden Retriever') %>%
  ggplot(aes(x=dog_name, fill= dominant_color)) + 
  geom_bar(alpha = .8) +
  theme_light() +
  coord_flip() +
  labs(title = 'Top Golden Retriever Names', x = 'Name', y = 'Count')
Selecting by count
Joining, by = "dog_name"

The names are pretty generic, but it is interesting that “Rusty” is a popular name for the dominant “Rust” colored retrievers. I would be a bit curious if there is selection bias when filling out the color of the dog when its name is already Rusty!

LS0tDQp0aXRsZTogIkRvZyBOYW1lcyBpbiBOWUMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojSW50cm8NClRoaXMgaXMgc29tZSBxdWljayB2aXN1YWxpemF0aW9uIGZvciBkb2cgbmFtZXMgaW4gTllDIGFzIHByb3ZpZGVkIGZyb20gdGhlIEthZ2dsZSBkYXRhc2V0IHVwbG9hZGVkIGhlcmU6DQpodHRwczovL3d3dy5rYWdnbGUuY29tL25ldy15b3JrLWNpdHkvbnljLWRvZy1uYW1lcw0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkoZ2dyZXBlbCkNCmxpYnJhcnkoeHRhYmxlKQ0KDQpkdCA8LSByZWFkLmNzdigiRG9ncyBvZiBOWUMgLSBXTllDLmNzdiIpDQpgYGANCiNEYXRhIENsZWFuLXVwDQoNCkxldCdzIHRha2UgYSBxdWljayBsb29rIGF0IHRoZSBkYXRhLg0KYGBge3J9DQpzdHIoZHQpDQpgYGANCldlIGhhdmUgYSBiaXQgb2YgY2xlYW5pbmcgdXAgdG8gZG8sIGl0IHNlZW1zIFIgaXNuJ3QgcHJvcGVybHkgdW5kZXJzdGFuZGluZyB0aGUgIm4vYSIgdmFsdWVzIHByb3ZpZGVkLiBXZSBjYW4gaWRlbnRpZnkgYWxsIHRoZSBjb2x1bW5zIHdpdGggYSBwcm9ibGVtIHdpdGggYSBjdXN0b20gZnVuY3Rpb24uDQpgYGB7cn0NCmlzX25hX2YgPC0gZnVuY3Rpb24oeCkgew0KICBzdW0oeCA9PSAibi9hIikNCn0NCg0KYXBwbHkoZHQsMixpc19uYV9mKQ0KYGBgDQpMZXQncyBtYW51YWxseSBjaGFuZ2UgdGhlbS4NCmBgYHtyfQ0KZHQkZG9nX25hbWUgPC0gYXMuZmFjdG9yKGFzLmNoYXJhY3RlcihpZmVsc2UoZHQkZG9nX25hbWUgPT0gJ24vYScsTkEsYXMuY2hhcmFjdGVyKGR0JGRvZ19uYW1lKSkpKQ0KZHQkZ2VuZGVyIDwtIGFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIoaWZlbHNlKGR0JGdlbmRlciA9PSAnbi9hJyxOQSxhcy5jaGFyYWN0ZXIoZHQkZ2VuZGVyKSkpKQ0KZHQkZG9taW5hbnRfY29sb3IgPC0gYXMuZmFjdG9yKGFzLmNoYXJhY3RlcihpZmVsc2UoZHQkZG9taW5hbnRfY29sb3IgPT0gJ24vYScsTkEsYXMuY2hhcmFjdGVyKGR0JGRvbWluYW50X2NvbG9yKSkpKQ0KZHQkc2Vjb25kYXJ5X2NvbG9yIDwtIGFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIoaWZlbHNlKGR0JHNlY29uZGFyeV9jb2xvciA9PSAnbi9hJyxOQSxhcy5jaGFyYWN0ZXIoZHQkc2Vjb25kYXJ5X2NvbG9yKSkpKQ0KZHQkdGhpcmRfY29sb3IgPC0gYXMuZmFjdG9yKGFzLmNoYXJhY3RlcihpZmVsc2UoZHQkdGhpcmRfY29sb3IgPT0gJ24vYScsTkEsYXMuY2hhcmFjdGVyKGR0JHRoaXJkX2NvbG9yKSkpKQ0KDQphcHBseShhcHBseShkdCwyLGlzLm5hKSwyLHN1bSkNCmBgYA0KI0RhdGEgZXhwbG9yYXRpb24NCldlIGNhbiB2aXN1YWxpemUgYSBiaXQgdG8gZ2V0IHRvIGtub3cgb3VyIGRhdGEgYmV0dGVyLg0KYGBge3J9DQpwbG90X2dlbmRlciA8LSBkdCAlPiUNCiAgZmlsdGVyKGlzLm5hKGdlbmRlcikgPT0gRikgJT4lDQogIGdncGxvdChhZXMoeD1nZW5kZXIpKSArDQogIGdlb21fYmFyKGZpbGwgPSAnbGlnaHQgYmx1ZScsIGFscGhhID0gMSwgd2lkdGggPSAuMykgKw0KICB0aGVtZV9saWdodCgpICsNCiAgbGFicyh0aXRsZSA9ICJHZW5kZXIgRGlzdHJpYnV0aW9uIiwgeD0gIkdlbmRlciIsIHkgPSAiVG90YWwgQ291bnQiKQ0KICANCnBsb3RfY29sb3IgPC0gZHQgJT4lDQogIGZpbHRlcihpcy5uYShkb21pbmFudF9jb2xvcikgPT0gRikgJT4lDQogIGdyb3VwX2J5KGRvbWluYW50X2NvbG9yKSAlPiUNCiAgc3VtbWFyaXplKGNvdW50ID0gbigpKSAlPiUNCiAgYXJyYW5nZShjb3VudCkgJT4lDQogIGdncGxvdChhZXMoeD1mYWN0b3IoZG9taW5hbnRfY29sb3IsbGV2ZWxzPSBkb21pbmFudF9jb2xvciksIHkgPSBjb3VudCkpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICdsaWdodCBibHVlJywgYWxwaGEgPSAxLCB3aWR0aCA9IC4zLCBzdGF0ID0gJ2lkZW50aXR5JykgKw0KICB0aGVtZV9saWdodCgpICsNCiAgbGFicyh0aXRsZSA9ICJDb2xvciBEaXN0cmlidXRpb24iLCB4PSAiRG9taW5hbnQgQ29sb3IiLCB5ID0gIlRvdGFsIENvdW50IikgKw0KICBjb29yZF9mbGlwKCkNCg0KcGxvdF9ndCA8LSBkdCAlPiUNCiAgZ2dwbG90KGFlcyh4PWd1YXJkX29yX3RyYWluZWQpKSArDQogIGdlb21fYmFyKGZpbGwgPSAnbGlnaHQgYmx1ZScsIGFscGhhID0gMSwgd2lkdGggPSAuMykgKw0KICB0aGVtZV9saWdodCgpICsNCiAgbGFicyh0aXRsZSA9ICJHdWFyZC9UcmFpbmVkIERpc3RyaWJ1dGlvbiIsIHg9ICJHdWFyZC9UcmFpbmVkIiwgeSA9ICJUb3RhbCBDb3VudCIpDQoNCnBsb3Rfc24gPC0gZHQgJT4lDQogIGdncGxvdChhZXMoeD1zcGF5ZWRfb3JfbmV1dGVyZWQpKSArDQogIGdlb21fYmFyKGZpbGwgPSAnbGlnaHQgYmx1ZScsIGFscGhhID0gMSwgd2lkdGggPSAuMykgKw0KICB0aGVtZV9saWdodCgpICsNCiAgbGFicyh0aXRsZSA9ICJTcGF5ZWQvTmV1dGVyZWQgRGlzdHJpYnV0aW9uIiwgeD0gIlNwYXllZC9OZXV0ZXJlZCIsIHkgPSAiVG90YWwgQ291bnQiKQ0KDQpwbG90X2Jvcm91Z2ggPC0gZHQgJT4lDQogIGZpbHRlcihpcy5uYShib3JvdWdoKSA9PSBGKSAlPiUNCiAgZ3JvdXBfYnkoYm9yb3VnaCkgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoY291bnQpICU+JQ0KICBnZ3Bsb3QoYWVzKHg9ZmFjdG9yKGJvcm91Z2gsbGV2ZWxzPSBib3JvdWdoKSwgeSA9IGNvdW50KSkgKw0KICBnZW9tX2JhcihmaWxsID0gJ2xpZ2h0IGJsdWUnLCBhbHBoYSA9IDEsIHdpZHRoID0gLjMsIHN0YXQgPSAnaWRlbnRpdHknKSArDQogIHRoZW1lX2xpZ2h0KCkgKw0KICBsYWJzKHRpdGxlID0gIkJvcm91Z2ggRGlzdHJpYnV0aW9uIiwgeD0gIkJvcm91Z2giLCB5ID0gIlRvdGFsIENvdW50IikNCg0KZ3JpZC5hcnJhbmdlKHBsb3RfZ2VuZGVyLCBwbG90X2Jvcm91Z2gsIHBsb3RfZ3QsIHBsb3Rfc24pDQpgYGANCkxldCdzIGRpZyBkZWVwZXIgaW50byB0aGUgZGlmZmVyZW5jZXMgaW4gZG9nIG5hbWVzIGJhc2VkIG9uIGdlbmRlci4NCmBgYHtyfQ0KcGxvdF9tIDwtIGR0ICU+JQ0KICBmaWx0ZXIoZ2VuZGVyID09ICdNJywgaXMubmEoZG9nX25hbWUpID09IEYpICU+JQ0KICBncm91cF9ieShkb2dfbmFtZSkgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoY291bnQpICU+JQ0KICB0b3BfbigxNSkgJT4lDQogIGdncGxvdChhZXMoeD1mYWN0b3IoZG9nX25hbWUsbGV2ZWxzPSBkb2dfbmFtZSkseT1jb3VudCkpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICdncmVlbicsIGFscGhhID0gLjgsIHdpZHRoID0gLjQsIHN0YXQgPSAnaWRlbnRpdHknKSArDQogIHRoZW1lX2xpZ2h0KCkgKyANCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicyh0aXRsZSA9ICJQb3B1bGFyIE1hbGUgTmFtZXMiLCB4ID0gIk5hbWUiLCB5ID0gIkNvdW50IikNCg0KcGxvdF9mIDwtIGR0ICU+JQ0KICBmaWx0ZXIoZ2VuZGVyID09ICdGJywgaXMubmEoZG9nX25hbWUpID09IEYpICU+JQ0KICBncm91cF9ieShkb2dfbmFtZSkgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoY291bnQpICU+JQ0KICB0b3BfbigxNSkgJT4lDQogIGdncGxvdChhZXMoeD1mYWN0b3IoZG9nX25hbWUsbGV2ZWxzPSBkb2dfbmFtZSkseT1jb3VudCkpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICdncmVlbicsIGFscGhhID0gLjgsIHdpZHRoID0gLjQsIHN0YXQgPSAnaWRlbnRpdHknKSArDQogIHRoZW1lX2xpZ2h0KCkgKyANCiAgY29vcmRfZmxpcCgpKw0KICBsYWJzKHRpdGxlID0gIlBvcHVsYXIgRmVtYWxlIE5hbWVzIiwgeCA9ICJOYW1lIiwgeSA9ICJDb3VudCIpDQoNCmdyaWQuYXJyYW5nZShwbG90X20sIHBsb3RfZikNCmBgYA0KVGhlcmUgYXJlIHNvbWUgY2xlYXIgZGlmZmVyZW5jZXMgZm9yIG5hbWVzIGJhc2VkIG9uIGdlbmRlci4gV2hpY2ggbmFtZXMgYXJlIHBvcHVsYXIgZm9yIG1hbGUgYW5kIGZlbWFsZXM/DQpgYGB7cn0NCmR0ICU+JQ0KICBmaWx0ZXIoaXMubmEoZG9nX25hbWUpID09IEYpICU+JQ0KICBncm91cF9ieShkb2dfbmFtZSkgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSwNCiAgICAgICAgICAgIGNvdW50X2ZlbWFsZSA9IGxlbmd0aChnZW5kZXJbZ2VuZGVyID09ICdGJ10pLCANCiAgICAgICAgICAgIGNvdW50X21hbGUgPSBsZW5ndGgoZ2VuZGVyW2dlbmRlciA9PSAnTSddKSwNCiAgICAgICAgICAgIG1hbGVfcmF0aW8gPSAoY291bnRfbWFsZSkvIChjb3VudF9mZW1hbGUgKyBjb3VudF9tYWxlKSwNCiAgICAgICAgICAgIGRpZmYgPSBhYnMobWFsZV9yYXRpbyAtIC41KQ0KICAgICAgICAgICAgKSAlPiUNCiAgZmlsdGVyKGNvdW50ID4gNTApICU+JQ0KICBhcnJhbmdlKGRpZmYpICU+JQ0KICB0b3BfbigtMTUpICU+JQ0KICBnZ3Bsb3QoYWVzKHg9Y291bnRfZmVtYWxlLCB5PWNvdW50X21hbGUsIGZpbGwgPSBkb2dfbmFtZSwgbGFiZWwgPSBkb2dfbmFtZSkpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IC43LCBjb2xvciA9ICdncmF5JyxzaXplID0gMykgKw0KICBnZW9tX2xhYmVsX3JlcGVsKCBmb250ZmFjZSA9ICdib2xkJywgY29sb3IgPSAnd2hpdGUnLCBzZWdtZW50LmNvbG9yID0gJ2dyZXk1MCcpICsgDQogIHRoZW1lX2xpZ2h0KCkgKyANCiAgbGFicyh0aXRsZSA9ICJNb3N0IFBvcHVsYXIgR2VuZGVyLU5ldXRyYWwgRG9nIE5hbWVzIiwgeD0nRmVtYWxlIE9jY3VyZW5jZXMnLCB5PSdNYWxlIE9jY3VyZW5jZXMnKQ0KYGBgDQpUaGlzIGlzIHF1aXRlIGludGVyZXN0aW5nLCBhbW9uZyB0aGVzZSBuYW1lcyBpdCBzZWVtcyBsaWtlbHkgdGhpcyBpcyBkaXJlY2x5IHJlbGF0ZWQgdG8gdGhlaXIgYXBwZWFyYW5jZSBvciBwZXJzb25hbGl0eS4NCg0KSSBjYW4gdGFrZSBhIGd1ZXNzIHdoaWNoIGJvcm91Z2ggdXN1YWxseSByZWdpc3RlcnMgZG9nJ3MgbmFtZWQgQnJvb2tseW4uDQpgYGB7cn0NCmR0ICU+JQ0KICBmaWx0ZXIoZG9nX25hbWUgPT0gJ0Jyb29rbHluJykgJT4lDQogIGdncGxvdChhZXMoeD1ib3JvdWdoLCBmaWxsPWdlbmRlcikpICsNCiAgZ2VvbV9iYXIoIGFscGhhID0gLjgpICsgDQogIHRoZW1lX2xpZ2h0KCkgKw0KICBsYWJzKHRpdGxlID0gJ0RvZ3MgTmFtZWQgQnJvb2tseW4nLCB5ID0gJ0NvdW50JywgeCA9ICdCb3JvdWdoJykNCmBgYA0KQWN0dWFsbHkgbm90IGFzIHN0cm9uZyBvZiBhIGRpc3RyaWJ1dGlvbiBhcyBJIHdvdWxkIGV4cGVjdCwgYnV0IHRoaXMgY291bGQgYmUgZXZpZGVuY2Ugb2Ygb3duZXJzIHRyYXZlbGluZyB0byBhIGRpZmZlcmVudCBib3JvdWdoIHdoZW4gdGhleSB2aXNpdCBhIHZldCBvciBqdXN0IG1vdmluZyBhcm91bmQgaW4gZ2VuZXJhbC4NCg0KTGV0J3MgZG8gdGhlIHNhbWUgZm9yICJTbm93IiBhbmQgIlNub3d5Ii4uLiBJJ2QgZXhwZWN0IHRoZWlyIGNvbG9ycyB0byB0ZWxsIHRoZSBzdG9yeSBoZXJlLg0KYGBge3J9DQpkdCAlPiUNCiAgZmlsdGVyKGRvZ19uYW1lID09ICdTbm93JyB8IGRvZ19uYW1lID09ICdTbm93eScsIGlzLm5hKGRvbWluYW50X2NvbG9yKSA9PSBGKSAlPiUNCiAgZ2dwbG90KGFlcyh4PWRvbWluYW50X2NvbG9yLCBmaWxsPWdlbmRlcikpICsNCiAgZ2VvbV9iYXIoIGFscGhhID0gLjgpICsgDQogIHRoZW1lX2xpZ2h0KCkgKw0KICBsYWJzKHRpdGxlID0gJ0RvZ3MgTmFtZWQgU25vdy9Tbm93eScsIHkgPSAnQ291bnQnLCB4ID0gJ0NvbG9yJykNCmBgYA0KUHJldHR5IGNsb3NlLiBJIGFtIGludGVyZXN0ZWQgaW4gdGhlIGJsYWNrIGRvZyBuYW1lZCBzbm93eSwgcGVyaGFwcyBpdCBpcyBzcG90dGVkLg0KYGBge3J9DQpkdCAlPiUNCiAgZmlsdGVyKGRvZ19uYW1lID09ICdTbm93JyB8IGRvZ19uYW1lID09ICdTbm93eScsIGRvbWluYW50X2NvbG9yID09ICdCTEFDSycpICU+JQ0KICBzZWxlY3QoZG9nX25hbWUsIGdlbmRlciwgYnJlZWQsIGRvbWluYW50X2NvbG9yLCBzZWNvbmRhcnlfY29sb3IsIHRoaXJkX2NvbG9yKQ0KYGBgDQpUaGUgc2Vjb25kYXJ5IGNvbG9yIGlzIGFsbW9zdCBhbHdheXMgd2hpdGUsIGV4Y2VwdCBmb3Igb25lIHBvb3IgY2hpaHVhaHVhLiBQZXJoYXBzIFNub3d5IHdhcyBuYW1lZCBpbiBpcm9ueS4NCg0KTGV0J3MgdGFrZSBhIHF1aWNrIGxvb2sgYXQgdGhlIGJyZWVkcy4NCmBgYHtyfQ0KZHQgJT4lDQogIGZpbHRlcihpcy5uYShicmVlZCkgPT0gRikgJT4lDQogIGdyb3VwX2J5KGJyZWVkKSAlPiUNCiAgc3VtbWFyaXplKGNvdW50ID0gbigpKSAlPiUNCiAgYXJyYW5nZShjb3VudCkgJT4lDQogIHRvcF9uKDI1KSAlPiUNCiAgZ2dwbG90KGFlcyh4PWZhY3RvcihicmVlZCxsZXZlbHM9IGJyZWVkKSx5PWNvdW50KSkgKw0KICBnZW9tX2JhcihmaWxsID0gJ2xpZ2h0IGJsdWUnLCBhbHBoYSA9IC44LCB3aWR0aCA9IC40LCBzdGF0ID0gJ2lkZW50aXR5JykgKw0KICB0aGVtZV9saWdodCgpICsgDQogIGNvb3JkX2ZsaXAoKSsNCiAgbGFicyh0aXRsZSA9ICJQb3B1bGFyIEJyZWVkcyIsIHggPSAiQnJlZWQiLCB5ID0gIkNvdW50IikNCmBgYA0KVGhlcmUncyBhIGxvdCBvZiBncmFudWxhcml0eSBoZXJlLCB0aGlzIGlzIG9ubHkgdGhlIHRvcCAyNS4gSW50ZXJlc3RpbmcgdG8gc2VlIHRoZSBhbW91bnQgb2YgbXV0cyBpbiB0aGUgcG9wdWxhdGlvbiwgdGhpcyBpcyByb3VnaGx5IGEgcXVhcnRlci4NCg0KRm9yIGZ1biwgbGV0J3MgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBtb3N0IHBvcHVsYXIgbmFtZXMgZm9yIEdvbGRlbiBSZXRyaWV2ZXJzLg0KYGBge3J9DQpkdCAlPiUNCiAgZmlsdGVyKGJyZWVkID09ICdHb2xkZW4gUmV0cmlldmVyJywgaXMubmEoZG9nX25hbWUpID09IEYpICU+JQ0KICBncm91cF9ieShkb2dfbmFtZSkgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhjb3VudCkpICU+JQ0KICB0b3BfbigxNSkgJT4lDQogIGlubmVyX2pvaW4oZHQpICU+JQ0KICBmaWx0ZXIoYnJlZWQgPT0gJ0dvbGRlbiBSZXRyaWV2ZXInKSAlPiUNCiAgZ2dwbG90KGFlcyh4PWRvZ19uYW1lLCBmaWxsPSBkb21pbmFudF9jb2xvcikpICsgDQogIGdlb21fYmFyKGFscGhhID0gLjgpICsNCiAgdGhlbWVfbGlnaHQoKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIGxhYnModGl0bGUgPSAnVG9wIEdvbGRlbiBSZXRyaWV2ZXIgTmFtZXMnLCB4ID0gJ05hbWUnLCB5ID0gJ0NvdW50JykNCmBgYA0KVGhlIG5hbWVzIGFyZSBwcmV0dHkgZ2VuZXJpYywgYnV0IGl0IGlzIGludGVyZXN0aW5nIHRoYXQgIlJ1c3R5IiBpcyBhIHBvcHVsYXIgbmFtZSBmb3IgdGhlIGRvbWluYW50ICJSdXN0IiBjb2xvcmVkIHJldHJpZXZlcnMuIEkgd291bGQgYmUgYSBiaXQgY3VyaW91cyBpZiB0aGVyZSBpcyBzZWxlY3Rpb24gYmlhcyB3aGVuIGZpbGxpbmcgb3V0IHRoZSBjb2xvciBvZiB0aGUgZG9nIHdoZW4gaXRzIG5hbWUgaXMgYWxyZWFkeSBSdXN0eSE=