{"cells":[{"metadata":{"_uuid":"21a128f8db2e1da7ee3ddbd69c6f476a3be1f9a6"},"cell_type":"markdown","source":"# Table of contents\n\n* [Introduction](#Introduction)\n* [1. Loading libraries and preparing the data](#1.-Loading-libraries-and-preparing-the-data)\n* [2. Data exploration](#2.-Data-exploration)\n  * [2.1 Dogs versus Cats, Breed, and Maturity Size](#2.1-Dogs-versus-Cats,-Breed,-and-Maturity-Size)\n  * [2.2 Age](#2.2-Age)\n  * [2.3 Gender and Groups](#2.3-Gender-and-Groups)\n  * [2.4 The influence of the Fee](#2.4-The-influence-of-the-Fee)\n  * [2.5 Health and medical status](#2.5-Health-and-medical-status)\n  * [2.6 Regional differences](#2.6-Regional-differences)\n  * [2.7 Text mining the Description](#2.7-Text-mining-the-Description)\n  * [2.8 Pets with a name versus pets without name](#2.8-Pets-with-a-name-versus-pets-without-name)\n* [3. Baseline XGBoost model](#3.-Baseline-XGBoost-model)"},{"metadata":{"_uuid":"c629004320eedd729e2fa9460e160dd4380c69de"},"cell_type":"markdown","source":"# Introduction\n\nKaggle describes this competition as follows:\n\nMillions of stray animals suffer on the streets or are euthanized in shelters every day around the world. If homes can be found for them, many precious lives can be saved — and more happy families created.\n\nPetFinder.my has been Malaysia’s leading animal welfare platform since 2008, with a database of more than 150,000 animals. PetFinder collaborates closely with animal lovers, media, corporations, and global organizations to improve animal welfare.\n\nAnimal adoption rates are strongly correlated to the metadata associated with their online profiles, such as descriptive text and photo characteristics. As one example, PetFinder is currently experimenting with a simple AI tool called the Cuteness Meter, which ranks how cute a pet is based on qualities present in their photos.\n\nIn this competition you will be developing algorithms to predict the adoptability of pets - specifically, how quickly is a pet adopted? If successful, they will be adapted into AI tools that will guide shelters and rescuers around the world on improving their pet profiles' appeal, reducing animal suffering and euthanization.\n\nTop participants may be invited to collaborate on implementing their solutions into AI tools for assessing and improving pet adoption performance, which will benefit global animal welfare.\n<br>\n<center><img src=\"https://i.imgur.com/21xwXVe.png\"></center>\n<br>\nPicture: Grumpy cat and a random cute dog ;-)\n<br>\n<br>\n## The target variable: Adoption Speed\n\nContestants are required to predict this value. The value is determined by how quickly, if at all, a pet is adopted. The values are determined in the following way: \n\n0 - Pet was adopted on the same day as it was listed. \n\n1 - Pet was adopted between 1 and 7 days (1st week) after being listed. \n\n2 - Pet was adopted between 8 and 30 days (1st month) after being listed.\n\n3 - Pet was adopted between 31 and 90 days (2nd & 3rd month) after being listed.\n\n4 - No adoption after 100 days of being listed. (There are no pets in this dataset that waited between 90 and 100 days).\n\n## Data Fields in the train and test set\n\n* PetID - Unique hash ID of pet profile\n* AdoptionSpeed - Categorical speed of adoption. Lower is faster. This is the value to predict. See below section for more info.\n* Type - Type of animal (1 = Dog, 2 = Cat)\n* Name - Name of pet (Empty if not named)\n* Age - Age of pet when listed, in months\n* Breed1 - Primary breed of pet (Refer to BreedLabels dictionary)\n* Breed2 - Secondary breed of pet, if pet is of mixed breed (Refer to BreedLabels dictionary)\n* Gender - Gender of pet (1 = Male, 2 = Female, 3 = Mixed, if profile represents group of pets)\n* Color1 - Color 1 of pet (Refer to ColorLabels dictionary)\n* Color2 - Color 2 of pet (Refer to ColorLabels dictionary)\n* Color3 - Color 3 of pet (Refer to ColorLabels dictionary)\n* MaturitySize - Size at maturity (1 = Small, 2 = Medium, 3 = Large, 4 = Extra Large, 0 = Not Specified)\n* FurLength - Fur length (1 = Short, 2 = Medium, 3 = Long, 0 = Not Specified)\n* Vaccinated - Pet has been vaccinated (1 = Yes, 2 = No, 3 = Not Sure)\n* Dewormed - Pet has been dewormed (1 = Yes, 2 = No, 3 = Not Sure)\n* Sterilized - Pet has been spayed / neutered (1 = Yes, 2 = No, 3 = Not Sure)\n* Health - Health Condition (1 = Healthy, 2 = Minor Injury, 3 = Serious Injury, 0 = Not Specified)\n* Quantity - Number of pets represented in profile\n* Fee - Adoption fee (0 = Free)\n* State - State location in Malaysia (Refer to StateLabels dictionary)\n* RescuerID - Unique hash ID of rescuer\n* VideoAmt - Total uploaded videos for this pet\n* PhotoAmt - Total uploaded photos for this pet\n* Description - Profile write-up for this pet. The primary language used is English, with some in Malay or Chinese."},{"metadata":{"_uuid":"b79fcee3b1a8f485d5104cce67549f61b0bc622f"},"cell_type":"markdown","source":" # 1. Loading libraries and preparing the data"},{"metadata":{"_uuid":"763a2a66845f294f383d63422e430475bd3e22f3","_execution_state":"idle","trusted":true,"_kg_hide-output":true,"_kg_hide-input":true},"cell_type":"code","source":"library(tidyverse)\nlibrary(jsonlite)\nlibrary(scales)\nlibrary(lubridate)\nlibrary(repr)\nlibrary(ggrepel)\nlibrary(gridExtra)\nlibrary(tidytext)\nlibrary(grid)\nlibrary(rjson)\nlibrary(xgboost)\nlibrary(caret)\nlibrary(Metrics)\nlibrary(Ckmeans.1d.dp)\n\n#list.files(path = \"../input\")\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"09e1e151e65200e9a07dc1e2ffdb94a2f168ab73"},"cell_type":"markdown","source":"In the code below, I am loading the train and test files. In addition, I am recoding some values for EDA purposes (such as replacing the values of Type (1 and 2) by Cat and Dog). Altogether, have have about 15,000 pets in our train set."},{"metadata":{"_kg_hide-output":true,"_kg_hide-input":true,"trusted":true,"_uuid":"93761827c044a0d162da3ad8c2d08c32cdbef63b"},"cell_type":"code","source":"train <- read_csv(\"../input/train/train.csv\")\ntest <- read_csv(\"../input/test/test.csv\")\nstate_labels <- read_csv(\"../input/state_labels.csv\")\nbreed_labels <- read_csv(\"../input/breed_labels.csv\")\ncolor_labels <- read_csv(\"../input/color_labels.csv\")\ntr_te <- bind_rows(train, test)\n\ntrain <- left_join(train, state_labels %>% rename(State=StateID), by=\"State\")\ntrain <- left_join(train, breed_labels %>% select(Breed1=BreedID, MainBreed=BreedName), by=\"Breed1\")\ntrain <- left_join(train, breed_labels %>% select(Breed2=BreedID, SecondBreed=BreedName), by=\"Breed2\")\ntrain <- left_join(train, color_labels %>% select(Color1=ColorID, ColorName1=ColorName), by=\"Color1\")\ntrain <- left_join(train, color_labels %>% select(Color2=ColorID, ColorName2=ColorName), by=\"Color2\")\ntrain <- left_join(train, color_labels %>% select(Color3=ColorID, ColorName3=ColorName), by=\"Color3\")\n\ntrain <- train %>% select(-State, -Breed1, -Breed2, - Color1, -Color2, -Color3)\n\ntrain <- train %>% mutate_at(vars(Type, Gender, AdoptionSpeed), as.factor)\ntrain <- train %>% mutate(Type=recode(Type, \"1\"= \"Dog\", \"2\"= \"Cat\"),\n                         Gender=recode(Gender, \"1\"= \"Male\", \"2\" = \"Female\", \"3\"= \"Mixed\"),\n                         AdoptionSpeed=recode(AdoptionSpeed,\n                                              \"0\"= \"0 - Adopted on the same day\",\n                                              \"1\" = \"1 - Adopted between 1 and 7 days\",\n                                              \"2\" = \"2 - Adopted between 8 and 30 days\",\n                                             \"3\" = \"3 - Adopted between 31 and 90 days\",\n                                            \"4\" = \"4 - No adoption after 100 days\"))\ntrain <- train %>% mutate_if(is_character, as_factor)\nglimpse(train)\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"e91a43212805e42ca8d94f266af3e2faba3c8689"},"cell_type":"markdown","source":"# 2. Data exploration\n\n## 2.1 Dogs versus Cats, Breed, and Maturity Size\n\n### 2.1.1 Dogs versus Cats\nFirst of all, let's see what the total adoption speed distribution looks like in the train set. Split by Cats and Dogs. There are a little more Dogs than Cats in the train set. However, Cats are adopted a bit faster on average."},{"metadata":{"trusted":true,"_uuid":"5039c78ce49d71c7acad525c5cb2125b97808eb7","_kg_hide-input":true},"cell_type":"code","source":"options(repr.plot.height=5)\n\noverall <- train %>% count(Type)\n\no1 <- overall %>%\nggplot(aes(x=Type, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"green\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=overall$n)\n\noverall1 <- train %>%\ncount(Type, AdoptionSpeed) \noverall1 <- left_join(overall1, overall %>% rename(total=n), by=\"Type\")\noverall1 <- overall1 %>% mutate(perc = round((n/total) *100))\n\no2 <- overall1 %>%\nggplot(aes(x=AdoptionSpeed, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"blue\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=paste(overall1$perc, \"%\"), size=3) +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nfacet_grid(~Type)\n\ngrid.arrange(o1, o2, widths=c(1,2))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"0bd13d18e3c27746a32ce9ec3c19a7e02a91be10"},"cell_type":"markdown","source":"\n"},{"metadata":{"_uuid":"0fb09415f8bd9c83bd0d5aa81eceae44159e0f8b"},"cell_type":"markdown","source":"###  2.1.2 Breeds\nI have displayed the Top10 of MainBreeds. The info seems a bit messy, and the Secondary Breed seems more confusing than useful. All Cats starting with \"Domestic\" seem not pure breed Cats. In addition, the Dogs that are not pure breeds all seem describes as Mixed Breed. I have created a new variable that specifies all these \"Domestic\" Cats and Mixed Breed Dogs are not Pure Breed."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"350aa072147bdb0a6289ac529d7696ac29c7b997"},"cell_type":"code","source":"not_pure <- c(\"Domestic Short Hair\", \"Domestic Medium Hair\", \"Domestic Long Hair\", \"Mixed Breed\")\ntrain$pure_breed <- ifelse(train$MainBreed %in% not_pure, 0, 1)\n\ntrain %>% filter(pure_breed==1) %>% count(Type, MainBreed) %>% group_by(Type) %>% top_n(10, n) %>%\nggplot(aes(x=reorder(MainBreed, -n), y=n))+\ngeom_bar(stat=\"identity\", fill=\"blue\") +\nfacet_wrap(~Type, scales = \"free_x\") +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nlabs(x=\"Most common pure breeds\", y=\"number of pets\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"71dfac94e18265a885014dac01d37c2d074137e3"},"cell_type":"markdown","source":"Below, we can see that pure breeds are doing better regarding the Adoption Speed!"},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"03afdba8429f92ea73f004662f5ab0b66cc9ec22"},"cell_type":"code","source":"train %>% count(AdoptionSpeed, pure_breed) %>%\nggplot(aes(x=AdoptionSpeed, y=n, fill=as.factor(pure_breed))) +\ngeom_bar(stat=\"identity\", position=\"fill\") +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nguides(fill=guide_legend(title=\"Pure Breed\")) +\nscale_y_continuous(labels=percent) +\nlabs(x=\"\", y=\"percent\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"46c75cf5621105ccc0ea3e6dca54405a48763c65"},"cell_type":"markdown","source":"### 2.1.3 Maturity Size\nI was wondering if there is a difference between especially small and large Dogs, assuming the all Cats are relatively small anyway. However:\n\n*  The distribution (Small - Medium-Large-Extra Large) is relatively similar to the Dogs distributin for Cats\n* I do not see any significant differences among the MaturitySize categories (all have similar shapes)."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"335ed1c872e773579403526c0c5e36f104d9a4cc"},"cell_type":"code","source":"train %>% count(Type, MaturitySize, AdoptionSpeed) %>%\nggplot(aes(x=Type, y=n, fill=AdoptionSpeed)) +\ngeom_bar(stat=\"identity\", position=\"dodge\") +\nfacet_grid(~MaturitySize)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c57e135c78cabd094d07924409d24769ab17c210"},"cell_type":"markdown","source":"## 2.2 Age\nMost Pets in the train set are very young, between 0 and 12 months old. I limited the histogram to pets that 12 years at most."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"a725b1b5fff66eec32d0f7082aea11c3c9e93855"},"cell_type":"code","source":"options(repr.plot.height=4)\n\ntrain %>%\nggplot(aes(x=Age)) +\ngeom_histogram(breaks = seq(0, 144, by=12), fill=\"blue\") +\nscale_x_continuous(breaks = seq(0, 144, by=12)) +\nlabs(x=\"Age in Months\", y=\"number of pets\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"7be923bf6af8c16a115e3df3b92b1487769a198a"},"cell_type":"markdown","source":"We see that pets that were not adopted with 100 days are generall older, as expected. The median age for this category is higest for both Cats and Dogs, and Q3 is high (especially for Dogs). It is interesting to see that Q3 is also high for Dogs that were adopted on the same Day. However, we should realize that those Q3 boundaries are all at young Ages (most Pets offered are young anyway). The four oldest Dogs were not adopted, and the two oldest Cats were adopted slowly (between 31 and 90 Days).\n\nBinning of Age seems a good option!"},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"8d9835188c3447230225f14762e893d2e7766d31"},"cell_type":"code","source":"options(repr.plot.height=5)\n\na1 <- train %>%\nggplot(aes(x=AdoptionSpeed, y=Age, fill=Type)) +\ngeom_boxplot() +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1))+\nlabs(x=\"\", y=\"Age in Months\", title=\"Age and Adoption\")\n\na2 <- train %>%\nggplot(aes(x=AdoptionSpeed, y=Age, fill=Type)) +\ngeom_boxplot(show.legend=FALSE) +\ncoord_cartesian(ylim = c(0, 24)) + \nscale_y_continuous(breaks= seq(0, 24, by=12)) +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nlabs(x=\"\", y=\"Age in Months\", title=\"Age and Adoption Zoomed\")\n\ngrid.arrange(a1, a2, nrow=1, widths=c(1.3,1))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"def2a18537ad0563565e27372c0dc1cd5d4be240"},"cell_type":"markdown","source":"Creating 6 AgeBins:\n\n* 0-6 months\n* 6 months - 1 year\n* 1 year - 3 years\n* 3 years - 5 years\n* 5 years - 8 years\n* older than 8 years"},{"metadata":{"trusted":true,"_uuid":"607382236d79617a2cb19a67e6684c2f6a324c38","_kg_hide-input":true},"cell_type":"code","source":"train$AgeBins <- Hmisc::cut2(train$Age, c(6, 12, 36, 60, 96))\ntrain %>% count(AgeBins)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"75ac4c97ab37fb04f79cc323719cf4b54f688f46"},"cell_type":"markdown","source":"Puppies/kittens have a low chance to not get adopted within 100 days. What's interesting to see is that older pets do not always have the worst chances to get adopted. In particular, cats older than 5 years have better chances to get adopted within 100 days than cats between 6 months and 5 years. I am guessing this might be because some people feel sympathy for those older pets and want to give a good remainder of their lives."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"f14077749614b82905b52a037cd0594601279e68"},"cell_type":"code","source":"train %>% count(Type, AdoptionSpeed, AgeBins) %>%\nggplot(aes(x=AgeBins, y=n, fill=AdoptionSpeed)) +\ngeom_bar(stat=\"identity\", position= \"fill\") +\nfacet_grid(~Type) +\ntheme(legend.position=\"bottom\", legend.title = element_blank()) +\nguides(fill=guide_legend(nrow=2, byrow = TRUE)) +\nscale_y_continuous(labels=percent) +\nlabs(y=\"percent\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"040ee8d398ab5d321d55ce81bcca8f8a72468521"},"cell_type":"markdown","source":"## 2.3 Gender and Groups\n\n### 2.3.1 Gender\nBesides Male and Female pets, there is a 3rd Gender \"type\"; Gender is \"Mixed\", if profile represents group of pets.\n\nWe see that the number of Female pets is significantly larger than the number of Male pets in the training set, and that the Male pets are adopted quicker. Groups are also adopted slower than Male pets."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"325b77ca0423b7fff9d25e5825dbffb5e06b5c22"},"cell_type":"code","source":"options(repr.plot.height=5)\n\noverall <- train %>% count(Gender)\n\no1 <- overall %>%\nggplot(aes(x=Gender, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"green\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=overall$n)\n\noverall1 <- train %>%\ncount(Gender, AdoptionSpeed) \noverall1 <- left_join(overall1, overall %>% rename(total=n), by=\"Gender\")\noverall1 <- overall1 %>% mutate(perc = round((n/total) *100))\n\no2 <- overall1 %>%\nggplot(aes(x=AdoptionSpeed, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"blue\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=paste(overall1$perc, \"%\"), size=3) +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nfacet_grid(~Gender)\n\ngrid.arrange(o1, o2, widths=c(1,2))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"894fae502a37b9842bfea8444e81c67bd0a54ebf"},"cell_type":"markdown","source":"### 2.3.2 Groups\nThe Gender type \"Mixed\" shows that talking about the \"number of pets\" is not always entirely correct. The PetID is actually an advertisementID that usually refers to one Cat or Dog, but it is basically a groupID if we are talking about an ad with more than one pet (but these count the same way as single pet predictions in the competition)."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"b262abb5b77e47d849deca7f2193bc7df924f440"},"cell_type":"code","source":"options(repr.plot.height=4)\n\ntrain %>% filter(Quantity>1) %>%\nggplot(aes(x=as.factor(Quantity))) +\ngeom_bar(stat=\"count\", fill=\"blue\") +\nlabs(x=\"number of pets (groups only, so quantity > 1)\", y=\"number of ads (=PetID)\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2c2279971ea2468c331b1df8ccb8109ff384cc55"},"cell_type":"markdown","source":"## 2.4 The influence of the Fee\nI wanted to find if the cost of adopting has a significant influence. To do so, I created 6 Bins."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"024262892a0f1ec50128d792a3442cc9d2a3a1bb"},"cell_type":"code","source":"train$FeeBins <- Hmisc::cut2(train$Fee, m=500) #m desired minimum number of observations in a group \ntrain %>% count(FeeBins)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"e253e7034a8acc1bcf9533a18ae75273027f9aae"},"cell_type":"markdown","source":"As you can see, there is not a lot of difference. The only group that is adopted a little slower are the middle-priced pets ($59-108)"},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"c243d63648c98b3fb731c606b8a05f4562c946ca"},"cell_type":"code","source":"train %>% count(AdoptionSpeed, FeeBins) %>%\nggplot(aes(x=FeeBins, y=n, fill=AdoptionSpeed)) +\ngeom_bar(stat=\"identity\", position= \"fill\")+\nscale_y_continuous(labels=percent) +\nlabs(x=\"\", y=\"percent\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c44e5fc5fc13682ca00826967d27b621b416ba4d"},"cell_type":"markdown","source":"## 2.5 Health and medical status\nAltogether, there are four variables that are related to the health of a pet or the medical actions that a pet has had. These are:\n\n* Vaccinated - Pet has been vaccinated (1 = Yes, 2 = No, 3 = Not Sure)\n* Dewormed - Pet has been dewormed (1 = Yes, 2 = No, 3 = Not Sure)\n* Sterilized - Pet has been spayed / neutered (1 = Yes, 2 = No, 3 = Not Sure)\n* Health - Health Condition (1 = Healthy, 2 = Minor Injury, 3 = Serious Injury, 0 = Not Specified)\n\nAs there are costs associated with vaccinating, deworming, and steralizing, people might be willing to pay more for pets that have had these medical actions.\n\n### 2.5.1 Overall Health\nHealth - Health Condition (1 = Healthy, 2 = Minor Injury, 3 = Serious Injury, 0 = Not Specified)\n\nWe see that the vast majority of pets is Healthy. Therefore, the impact of this variable seems minor."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"c59970e03be96a888355d9120aeaf2ca5b9065ca"},"cell_type":"code","source":"train %>%\nggplot(aes(x=Health)) +\ngeom_bar(stat=\"count\", fill=\"green\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"75d16d052fd789a33bc0c6ff6aef119e1669533f"},"cell_type":"markdown","source":"### 2.5.2 Vaccinated, Dewormed, Sterilized\nI started by looking at \"Vaccinated\". To my surprise, pets that were **not vaccinated get adopted quicker**! After thinking about this, I think this has probably got something to do with Age. Very young puppies/kittens are getting adopted quickly, but are likely not fully vaccinated yet. When looking for answers on the internet, I came across this schedule for Dogs: [DOG VACCINATION SCHEDULE FOR PUPPY’S FIRST YEAR](https://www.dummies.com/pets/dogs/dog-vaccination-schedule-for-puppys-first-year/).\n\n>Your new puppy definitely needs a series of vaccinations in the first year of life to protect him from many dangerous diseases as his doggy immune system develops. Different veterinarians recommend slightly different vaccination schedules and vaccines according to the specific dog’s risk factors."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"d3dc4b63f460b367def03e98e6c8fd48877a2e87"},"cell_type":"code","source":"options(repr.plot.height=5)\n\noverall <- train %>% count(Vaccinated)\n\no1 <- overall %>%\nggplot(aes(x=Vaccinated, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"green\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=overall$n)\n\noverall1 <- train %>%\ncount(Vaccinated, AdoptionSpeed) \noverall1 <- left_join(overall1, overall %>% rename(total=n), by=\"Vaccinated\")\noverall1 <- overall1 %>% mutate(perc = round((n/total) *100))\n\no2 <- overall1 %>%\nggplot(aes(x=AdoptionSpeed, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"blue\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=paste(overall1$perc, \"%\"), size=3) +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nfacet_grid(~Vaccinated)\n\ntitle <- textGrob(\"Vaccinated - Pet has been vaccinated (1 = Yes, 2 = No, 3 = Not Sure)\", gp=gpar(fontface=\"bold\"))\ngrid.arrange(o1, o2, widths=c(1,2), top=title)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8bd7b711f8d858d4cf7945de8d325228a4155b09"},"cell_type":"markdown","source":"If we look at Dewormed, again \"No\" scores better regarding the adoption speed. However, significantly more pets have been Dewormed than Vaccinated. Again, I have looked up some info, and this info confirms that this happens at younger ages ([Deworming puppies](https://pets.webmd.com/dogs/deworming-dogs-puppies#2), advice for kittens is similar):\n\n> Because worms are so common in puppies, vets recommend de-worming them for the first time when they're 2 to 3 weeks old. "},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"8fe19b8ccff87670e0a9db5f721cea3a77cf4fd0"},"cell_type":"code","source":"options(repr.plot.height=5)\n\noverall <- train %>% count(Dewormed)\n\no1 <- overall %>%\nggplot(aes(x=Dewormed, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"green\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=overall$n)\n\noverall1 <- train %>%\ncount(Dewormed, AdoptionSpeed) \noverall1 <- left_join(overall1, overall %>% rename(total=n), by=\"Dewormed\")\noverall1 <- overall1 %>% mutate(perc = round((n/total) *100))\n\no2 <- overall1 %>%\nggplot(aes(x=AdoptionSpeed, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"blue\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=paste(overall1$perc, \"%\"), size=3) +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nfacet_grid(~Dewormed)\n\ntitle <- textGrob(\"Dewormed - Pet has been dewormed (1 = Yes, 2 = No, 3 = Not Sure)\", gp=gpar(fontface=\"bold\"))\ngrid.arrange(o1, o2, widths=c(1,2), top=title)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c093ccba37731836fdd85c6c81849ddf3278df50"},"cell_type":"markdown","source":"When looking Sterilized, the number of pets that have not had the treatment is much lower than Vaccinated and Dewormed. I think this is because this is more like a personal choice and no prerequisite to keep your pet healthy. Again, \"No\" scores better regarding adoption speed. What especially stands out is the high percentages that did not get adopted after 100 days in the \"Yes\" and \"Not sure\" categories. I believe that the better scores for \"No\" can to a certain extend again be explained by young pets not having had the treatment yet anyway."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"fa2a69ba021c26f9548906575c5628e19fad6250"},"cell_type":"code","source":"options(repr.plot.height=5)\n\noverall <- train %>% count(Sterilized)\n\no1 <- overall %>%\nggplot(aes(x=Sterilized, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"green\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=overall$n)\n\noverall1 <- train %>%\ncount(Sterilized, AdoptionSpeed) \noverall1 <- left_join(overall1, overall %>% rename(total=n), by=\"Sterilized\")\noverall1 <- overall1 %>% mutate(perc = round((n/total) *100))\n\no2 <- overall1 %>%\nggplot(aes(x=AdoptionSpeed, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"blue\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=paste(overall1$perc, \"%\"), size=3) +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nfacet_grid(~Sterilized)\n\ntitle <- textGrob(\"Sterilized - Pet has been spayed / neutered (1 = Yes, 2 = No, 3 = Not Sure)\", gp=gpar(fontface=\"bold\"))\ngrid.arrange(o1, o2, widths=c(1,2), top=title)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"fbe9698590d53e62361d43fb060ec861cf80d48e"},"cell_type":"markdown","source":"## 2.6 Regional differences\nWe see that there are two states/regions with large numbers of pets."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"80d13b0f2cab224b35bffa7cadba80ca2100c57d"},"cell_type":"code","source":"train %>% count(StateName) %>%\nggplot(aes(x=reorder(StateName, -n), y=n)) +\ngeom_bar(stat=\"identity\", fill=\"blue\")  +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nlabs(x=\"\", y=\"number of pets\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"08bcb4907ab1fd5099b636671f0096bd834742a4"},"cell_type":"markdown","source":"I am Grouping the small states into one group of \"Other\" States. What we now see that differences in adoption speed are significant. The adoption speed is best in the large Selangor state. Also, the percentage of pets not adopted after 100 days is significantly smaller in Selangor, especially when compared to the Other states."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"ce13ff19e46634c75d8a7d417fa89dcda24e4ac6"},"cell_type":"code","source":"train <- train %>%\nmutate(StateGroup = case_when(StateName==\"Selangor\" ~ \"Selangor\",\n                             StateName==\"Kuala Lumpur\" ~ \"Kuala Lumpur\",\n                             !StateName %in% c(\"Selangor\", \"Kuala Lumpur\") ~ \"Other\"))\n\noverall <- train %>% count(StateGroup)\noverall1 <- train %>% count(StateGroup, AdoptionSpeed) \noverall1 <- left_join(overall1, overall %>% rename(total=n), by=\"StateGroup\")\noverall1 <- overall1 %>% mutate(perc = round((n/total) *100))\n\noverall1 %>%\nggplot(aes(x=AdoptionSpeed, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"blue\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=paste(overall1$perc, \"%\"), size=3) +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nfacet_grid(~StateGroup)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"37afa9ac91ce925f68dcc6932b4fa2630d6a8147"},"cell_type":"markdown","source":"## 2.7 Text mining the Description\nWhat I want to do here is to text mine the description. I can imagine that a Description with a lot of positive words could boost adoption speed!\n\n### 2.7.1. My own text mining"},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"e8726b954357e949743b9389920870498957aa63"},"cell_type":"code","source":"head(train %>% select(PetID, Type, Age, AdoptionSpeed, Description))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ec48039e218acbbd9a297f8b2589efb20900877c"},"cell_type":"markdown","source":"The bing lexicon categorizes words in a binary fashion into positive and negative categories."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"c4ee118c55fb62fd16b8b6a1499c6fe8c7a1b138"},"cell_type":"code","source":"train <- train %>% mutate_at(vars(Description), as.character)\nwords <- train %>% select(PetID, Name, AdoptionSpeed, Description) %>% unnest_tokens(output=word, input=Description)\n\nBing <- words %>% inner_join(get_sentiments(\"bing\"), by=\"word\")\nhead(Bing %>% select(-AdoptionSpeed), 9)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2662c7084768b092c26e310579f995d556ccc81d"},"cell_type":"markdown","source":"Is this useful? Well, especially pets with very long descriptions get many positive words. Negative words don't seem very useful anyway, as the usually refer to the previous owner  an not to the pet itself (for instance: Their pregnant mother was dumped by her irresponsible owner). In addition, the positive words seem too generic. We probably need a lexicon that is more cuteness specific ;-).\n\nI thought that the description length could be potentially the best predictor, as people may \"compensate\"  by adding a lot of text for pets that are hard to get adopted.Below, I am displaying the 20 pets with the longest desciptions. Although this list contains a fair number of pets not adopted within 100 days indeed, it is also a mixed bag as for instance the pet with the second longest description actually got adopted quickly."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"a0ace7b58db4237ee3fbc1b64d18dd47af73a5c1"},"cell_type":"code","source":"Bing <- Bing %>% count(PetID, Name, AdoptionSpeed, sentiment) %>% spread(key=sentiment, value=n) %>%\nmutate_at(vars(negative, positive), funs(replace(., is.na(.), 0)))\nwords <- words %>% group_by(PetID) %>% summarize(total_words=n())\nBing <- left_join(Bing, words, by=\"PetID\")\nBing <- Bing %>% arrange(desc(total_words))\nhead(Bing, 20)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"303a99104c46eb16aa95a9e45bfc492190e72a10"},"cell_type":"markdown","source":"When looking at the boxplots of the description lenght, the only thing that seems visible is that the slow adopting categories seem to have some more outliers with very long descriptions."},{"metadata":{"trusted":true,"_uuid":"faace0cc0add015ba26a5e1f288655034da8f996","_kg_hide-input":true},"cell_type":"code","source":"options(repr.plot.height=5)\n\na1 <- Bing %>%\nggplot(aes(x=AdoptionSpeed, y=total_words)) +\ngeom_boxplot() +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1))+\nlabs(x=\"\", y=\"Total words\", title=\"Total words and Adoption\")\n\na2 <- Bing %>%\nggplot(aes(x=AdoptionSpeed, y=total_words)) +\ngeom_boxplot(show.legend=FALSE) +\ncoord_cartesian(ylim = c(0, 100)) + \nscale_y_continuous(breaks= seq(0, 100, by=10)) +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nlabs(x=\"\", y=\"Total Words\", title=\"Total words and Adoption Zoomed\")\n\ngrid.arrange(a1, a2, nrow=1)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"622a349e9113cb077bba7501ddeb34a09ab38372"},"cell_type":"markdown","source":"### 2.7.2 The Sentiment data provided\nI have used François' kernel ([Transform sentiment data to a dataframe with R](https://www.kaggle.com/francoisdubois/transform-sentiment-data-to-a-dataframe-with-r)) to load the data. Please upvote his kernel to give him the credit that he deserves!"},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"83a000b025646b55bdb84c57f92ed91f55ca6137"},"cell_type":"code","source":"#Getting all the file path\nfilenames_train <- list.files(\"../input/train_sentiment\", full.names=TRUE)\nfnames<-filenames_train\n\n#for loop to proceed each JSON file\nsentiments_train<-c()\nfor( i in (1:length(fnames))){\n        temp<-fnames[i]\n        temp_json<-fromJSON(file=temp)\n        petid <- unlist(strsplit(substring(temp, 26), \".json\")) #PetID from JSON file name\n        magnitude<-temp_json[[\"documentSentiment\"]][[\"magnitude\"]] # Magnitude\n        score<-temp_json[[\"documentSentiment\"]][[\"score\"]] #Sentiment Score\n        language<-temp_json[[\"language\"]] #language of the descritpion\n       \n    ### Part to get the 5 first word entities\n        number_entities_orignal<-length(temp_json[[\"entities\"]]) \n        \n        if (number_entities_orignal>5){\n            number_entities<-5\n        }else{\n            number_entities<-number_entities_orignal\n        }\n        entities_vector<-c(\"\",\"\",\"\",\"\",\"\")\n        if (number_entities!=0){\n            for( entities in (1:number_entities)){\n            ent<-temp_json[[\"entities\"]][[entities]]$name\n            entities_vector[entities]<-ent\n            }\n        }\n    sentiments_uni<-c(petid,magnitude,score,language,number_entities_orignal,entities_vector)\n    sentiments_train<-rbind(sentiments_train,sentiments_uni)\n}\n\n#transform the matrix in a data frame\nsentiments_train<-as.data.frame(sentiments_train)\nnames(sentiments_train)<-c(\"PetID\",\"magnitude\",\"score\",\"language\",\"number_entities_orignal\",\"word1\",\"word2\",\"word3\",\"word4\",\"word5\")\nprint(\"sentiment_train is done\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"5da2059a4d1508a15e81d715c38a3f830e6380b2"},"cell_type":"markdown","source":"From [Google Cloud](https://cloud.google.com/natural-language/docs/basics):\n\n* score of the sentiment ranges between -1.0 (negative) and 1.0 (positive) and corresponds to the overall emotional leaning of the text.\n* magnitude indicates the overall strength of emotion (both positive and negative) within the given text, between 0.0 and +inf. Unlike score, magnitude is not normalized; each expression of emotion within the text (both positive and negative) contributes to the text's magnitude (so longer text blocks may have greater magnitudes)."},{"metadata":{"trusted":true,"_uuid":"1f2e28461f58789df29af356307aedafaf98e38e","_kg_hide-input":true},"cell_type":"code","source":"head(sentiments_train)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c88b6cb6932992abf78535995bf2a735de01757b"},"cell_type":"markdown","source":"However, when looking at the boxplots it seems as if Google also did not manage to capture the correlation with the adoption speed well. The median scores are all the same, and similar to the description length, magnitude has some more outliers with high values at the slow adopting categories."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"649e6fc2a2d85457524372e029353491b05a8d18"},"cell_type":"code","source":"sentiments_train <- sentiments_train %>%\nmutate_at(vars(PetID), as.character) %>%\nmutate_at(vars(magnitude, score), as.character) %>% mutate_at(vars(magnitude, score), parse_number)\n\nBing <- Bing %>% mutate_at(vars(PetID), as.character)\n\n#train <- left_join(train, sentiments_train %>% select(PetID, magnitude, score), by=\"PetID\")\n#str(sentiments_train)\nBing <- left_join(Bing, sentiments_train %>% select(PetID, magnitude, score), by=\"PetID\")\n#head(Bing %>% arrange(desc(score)), 20)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"93e3692e524e8cae49e7bcbb1f287d8d86ac15cb","_kg_hide-input":true},"cell_type":"code","source":"options(repr.plot.height=5)\n\na1 <- Bing %>%\nggplot(aes(x=AdoptionSpeed, y=score)) +\ngeom_boxplot() +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1))+\nlabs(x=\"\", y=\"Score\", title=\"Score and Adoption\")\n\na2 <- Bing %>%\nggplot(aes(x=AdoptionSpeed, y=magnitude)) +\ngeom_boxplot() +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1))+\nlabs(x=\"\", y=\"Magnitude\", title=\"Magnitude and Adoption\")\n\ngrid.arrange(a1, a2, nrow=1)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"c41234cbae1578322ca269000baeab1c66a8c870"},"cell_type":"markdown","source":"## 2.8 Pets with a name versus pets without name\nI noticed that pets don't always have a name. This sounds impersonal to me, and I suspect that having a name might increase adoption speed. Let's create the new variable \"has_name\" and find out!\n\nWe see that there seem to be significant differences in especially the slow adoption speed categories. Without a name, pets are adopted less frequently after 31-90 days, and more of them end up not being adopted after 100 days."},{"metadata":{"_kg_hide-input":true,"trusted":true,"_uuid":"c2eadbd7d187b6354674ca2872cd69d0225f352a"},"cell_type":"code","source":"options(repr.plot.height=5)\n\ntrain$has_name <- ifelse(is.na(train$Name), \"No\", \"Yes\")\ntrain$has_name <- as.factor(train$has_name)\n\noverall <- train %>% count(has_name)\n\no1 <- overall %>%\nggplot(aes(x=has_name, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"green\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=overall$n)\n\noverall1 <- train %>%\ncount(has_name, AdoptionSpeed) \noverall1 <- left_join(overall1, overall %>% rename(total=n), by=\"has_name\")\noverall1 <- overall1 %>% mutate(perc = round((n/total) *100))\n\no2 <- overall1 %>%\nggplot(aes(x=AdoptionSpeed, y=n)) +\ngeom_bar(stat=\"identity\", fill=\"blue\") +\nlabs(x=\"\", y=\"Number of pets\") +\ngeom_label(label=paste(overall1$perc, \"%\"), size=3) +\ntheme(axis.text.x = element_text(angle = 45, hjust = 1)) +\nfacet_grid(~has_name)\n\ngrid.arrange(o1, o2, widths=c(1,2))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"a689fb79ed48787887626ee22f44b2dd49bfeb79"},"cell_type":"markdown","source":"# 3. Baseline XGBoost model\nI initially wanted to make a LightGBM baseline model, but had issues with the ScoreQuadraticWeightedKappa() function in LGB. This version contains a working xgb.cv model, but a lot of optimization is required for a good score."},{"metadata":{"trusted":true,"_uuid":"18bc627d6b371390d63283e6488fc77f3ee3eaae"},"cell_type":"code","source":"tr_te <- left_join(tr_te, state_labels %>% rename(State=StateID), by=\"State\")\ntr_te <- left_join(tr_te, breed_labels %>% select(Breed1=BreedID, MainBreed=BreedName), by=\"Breed1\")\n\n#creating Has Name variable\ntr_te$has_name <- ifelse(is.na(tr_te$Name), 0, 1)\n\n#creating Pure Breed variable\nnot_pure <- c(\"Domestic Short Hair\", \"Domestic Medium Hair\", \"Domestic Long Hair\", \"Mixed Breed\")\ntr_te$pure_breed <- ifelse(tr_te$MainBreed %in% not_pure, 0, 1)\n\n#making Not Specified in ordinal factors NA (just in case there are any in stage 2)\ntr_te$MaturitySize[tr_te$MaturitySize==0] <- NA\ntr_te$FurLength[tr_te$FurLength==0] <- NA\ntr_te$Health[tr_te$Health==0] <- NA\n\ncategorical_vars <- c(\"Type\", \"Gender\", \"Vaccinated\", \"Dewormed\", \"Sterilized\", \"StateName\", \"MainBreed\", \"has_name\", \"pure_breed\", \"Breed2\", \"Color1\", \"Color2\", \"Color3\")\n\ntr_te <- tr_te %>% select(-Name, -Breed1, -RescuerID, -Description, -State, -PetID) %>%\nmutate_at(categorical_vars, funs(factor(.))) %>% mutate_if(is.numeric, as.integer)\n\nglimpse(tr_te)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"ae4b9f82d26cf83ab9f4786a020755930a011bf4"},"cell_type":"code","source":"#one hot encoding\ndummies <- dummyVars(~., data=tr_te)\nmatrix <- predict(dummies, newdata = tr_te)\ntr_te_ohe <- data.frame(matrix)\ndim(tr_te_ohe)\nstr(tr_te_ohe[,1:3])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"c94c017925f31227b519fdfc692f36ca703acc23"},"cell_type":"code","source":"#splitting into train and test again\ntr <- tr_te_ohe[!is.na(tr_te_ohe$AdoptionSpeed),]\nte <- tr_te_ohe[is.na(tr_te_ohe$AdoptionSpeed),]\ntrainLabel <- tr$AdoptionSpeed\ntr$AdoptionSpeed <- NULL\nte$AdoptionSpeed <- NULL","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"1ccc15c3caa7a56baf8589c5373e03791d9eb86d"},"cell_type":"code","source":"eval_kappa <- function(preds, dtrain) {\n  actuals <- getinfo(dtrain, \"label\")\n  error <- ScoreQuadraticWeightedKappa(rater.a = actuals,rater.b = as.integer(round(preds)), min.rating = 0, max.rating = 4)\n  return(list(metric = \"QWkappa\", value = error))\n}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"24e4f174aa9692400544bbc85ad6a24d8123bb79"},"cell_type":"code","source":"dtrain <- xgb.DMatrix(data=as.matrix(tr),label=trainLabel)\ndtest <- xgb.DMatrix(data=as.matrix(te))\n\nxgb.grid <- list(objective = \"multi:softmax\", num_class = 5, booster = \"gbtree\", eta=0.1)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5f205ca6164fa6c40e99e388a05dac4e774c2720"},"cell_type":"code","source":"set.seed(2019)\n\nxgbcv <- xgb.cv( params = xgb.grid, data = dtrain, nrounds = 200, nfold = 5, showsd = F, stratified = T, print_every_n = 10, early_stopping_rounds = 20, maximize = T, eval_metric = eval_kappa)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"43775d36715f605c192a8ca182e8f23c9371b77a"},"cell_type":"code","source":"xgb_mod <- xgb.train(data = dtrain, params=xgb.grid, nrounds = xgbcv$best_iteration, maximize=T, eval_metric = eval_kappa)\npredictions <- predict(xgb_mod, dtest)\n\ntable(predictions)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"02fc0dd6a6fe11fc207ef8b234d919c8ab9a6649"},"cell_type":"code","source":"#view variable importance plot\nmat <- xgb.importance (feature_names = colnames(tr),model = xgb_mod)\nxgb.ggplot.importance(importance_matrix = mat[1:20], rel_to_first = TRUE)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"3620a56b464e76829c547f7aa96d866a888c88ec"},"cell_type":"code","source":"#submission\nPetID <- test$PetID\nsub <- data.frame(PetID, AdoptionSpeed=predictions)\nwrite.csv(sub, file = \"submission.csv\", row.names=FALSE, quote = FALSE)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"3067119cedff97f6964ab201a14735e67be03c08"},"cell_type":"markdown","source":"**To be continued!**"}],"metadata":{"kernelspec":{"display_name":"R","language":"R","name":"ir"},"language_info":{"mimetype":"text/x-r-source","name":"R","pygments_lexer":"r","version":"3.4.2","file_extension":".r","codemirror_mode":"r"}},"nbformat":4,"nbformat_minor":1}