Некоторое время назад существовало всего 150 различных покемонов. С течением времени их количество возросло до 700: появились новые типы, новые регионы, и т.д. Чтобы проанализировать текущее положение вещей в мире покемонов, мы загрузим необходимые данные и создадим несколько диаграмм.
Данные
Существует специализированная база данных с информацией о покемонах, с которой можно взаимодействовать посредством API: http://pokeapi.co/. Но нам нужны все данные сразу, поэтому мы воспользуемся следующим репозиторием: https://github.com/phalt/pokeapi/tree/master/data/v2/csv. Нам также потребуются другие данные, такие как цвета типов и изображения покемонов. Эти данные можно найти на следующих ресурсах: http://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_base_stats_(Generation_VI-present) и http://pokemon-uranium.wikia.com/wiki/Template:fire_color.
path % select(-order, -is_default) %>% rename(pokemon = identifier) dfstat % rename(stat_id = id) %>% right_join(read_csv(path("pokemon_stats.csv")), by = "stat_id") %>% mutate(identifier = str_replace(identifier, "-", "_")) %>% select(pokemon_id, identifier, base_stat) %>% spread(identifier, base_stat) %>% rename(id = pokemon_id) dftype % rename(type_id = id) %>% right_join(read_csv(path("pokemon_types.csv")), by = "type_id") %>% select(pokemon_id, identifier, slot) %>% mutate(slot = paste0("type_", slot)) %>% spread(slot, identifier) %>% rename(id = pokemon_id) dfegg % rename(egg_group_id = id) %>% right_join(read_csv(path("pokemon_egg_groups.csv")), by = "egg_group_id") %>% group_by(species_id) %>% mutate(ranking = row_number(), ranking = paste0("egg_group_", ranking)) %>% select(species_id, ranking, identifier) %>% spread(ranking, identifier) dfimg % read_html() %>% html_nodes("tr.js-navigation-item > .content > .css-truncate a") %>% map_df(function(x){ url % html_attr("href") data_frame( id = str_extract(basename(url), "\\d+"), url_image = basename(url) ) }) %>% mutate(id = as.numeric(id)) url_bulbapedia_list % read_html(encoding = "UTF-8") %>% html_node("table.sortable") %>% html_table() %>% .[[1]] %>% as.numeric() url_icon % read_html() %>% html_nodes("table.sortable img") %>% html_attr("src") dficon % filter(!is.na(id)) %>% distinct(id) dfcolor % sprintf(t) %>% read_html() %>% html_nodes("span > b") %>% html_text() data_frame(ENGINE= t, color = paste0("#", col)) }) dfcolorf % tbl_df() %>% group_by(color_1, color_2) %>% do({ n = 100;p = 0.25 data_frame(color_f = colorRampPalette(c(.$color_1, .$color_2))(n)[round(n*p)]) }) # THE join df % left_join(dftype, by = "id") %>% left_join(dfstat, by = "id") %>% left_join(dfcolor %>% rename(type_1 = type, color_1 = color), by = "type_1") %>% left_join(dfcolor %>% rename(type_2 = type, color_2 = color), by = "type_2") %>% left_join(dfcolorf, by = c("color_1", "color_2")) %>% left_join(dfegg, by = "species_id") %>% left_join(dfimg, by = "id") %>% left_join(dficon, by = "id") rm(dftype, dfstat, dfcolor, dfcolorf, dfegg, dfimg, dficon) rm(id, url_bulbapedia_list, url_icon)
Далее мы удаляем покемонов, у которых нет изображений.
df % mutate(color_f = ifelse(is.na(color_f), color_1, color_f)) %>% filter(!is.na(url_image)) head(df)
id | pokemon | species_id | height | weight | base_experience | type_1 | type_2 | attack | defense | hp | special_attack | special_defense | speed | color_1 | color_2 | color_f | egg_group_1 | egg_group_2 | url_image | url_icon |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | bulbasaur | 1 | 7 | 69 | 64 | grass | poison | 49 | 49 | 45 | 65 | 65 | 45 | #78C850 | #A040A0 | #81A763 | monster | plant | 1.png | http://cdn.bulbagarden.net/upload/e/ec/001MS.png |
2 | ivysaur | 2 | 10 | 130 | 142 | grass | poison | 62 | 63 | 60 | 80 | 80 | 60 | #78C850 | #A040A0 | #81A763 | monster | plant | 2.png | http://cdn.bulbagarden.net/upload/6/6b/002MS.png |
3 | venusaur | 3 | 20 | 1000 | 236 | grass | poison | 82 | 83 | 80 | 100 | 100 | 80 | #78C850 | #A040A0 | #81A763 | monster | plant | 3.png | http://cdn.bulbagarden.net/upload/d/df/003MS.png |
4 | charmander | 4 | 6 | 85 | 62 | fire | NA | 52 | 43 | 39 | 60 | 50 | 65 | #F08030 | NA | #F08030 | monster | dragon | 4.png | http://cdn.bulbagarden.net/upload/b/bb/004MS.png |
5 | charmeleon | 5 | 11 | 190 | 142 | fire | NA | 64 | 58 | 58 | 80 | 65 | 80 | #F08030 | NA | #F08030 | monster | dragon | 5.png | http://cdn.bulbagarden.net/upload/d/dc/005MS.png |
6 | charizard | 6 | 17 | 905 | 240 | fire | flying | 84 | 78 | 78 | 109 | 85 | 100 | #F08030 | #A890F0 | #DE835E | monster | dragon | 6.png | http://cdn.bulbagarden.net/upload/0/01/006MS.png |
Гистограмма
Подсчитаем количество покемонов, принадлежащих каждому из первичных типов, и визуализируем эти данные с помощью гистограммы.
dstype % count(type_1, color_1) %>% ungroup() %>% arrange(desc(n)) %>% mutate(x = row_number()) %>% rename(name = type_1, color = color_1, y = n) %>% select(y, name, color) %>% list.parse3() hcbar % hc_xAxis(categories = unlist(pluck(dstype, i = 2))) %>% hc_yAxis(title = NULL) %>% hc_add_series(data = dstype, type = "bar", showInLegend = FALSE, name = "Number of species") hcbar
Интерактивная визуализация — источник
Можно отметить большое количество водных (water) и обычных (normal) покемонов, при этом летающих (flying) покемонов достаточно мало.
Древовидная диаграмма
Теперь выполним визуализацию с учетом вторичного типа покемонов. Это можно сделать с помощью древовидной диаграммы (treemap). Воспользуемся пакетом treemap.
set.seed(3514) tm % mutate(type_2 = ifelse(is.na(type_2), paste("only", type_1), type_2), type_1 = type_1) %>% group_by(type_1, type_2) %>% summarise(n = n()) %>% ungroup() %>% treemap::treemap(index = c("type_1", "type_2"), vSize = "n", vColor = "type_1")
Добавим соответствующий цвет каждого типа, чтобы получить более информативное представление.
tm$tm % tbl_df() %>% left_join(df %>% select(type_1, type_2, color_f) %>% distinct(), by = c("type_1", "type_2")) %>% left_join(df %>% select(type_1, color_1) %>% distinct(), by = c("type_1")) %>% mutate(type_1 = paste0("Main ", type_1), color = ifelse(is.na(color_f), color_1, color_f)) hctm % hc_add_series_treemap(tm, allowDrillToNode = TRUE, layoutAlgorithm = "squarified") hctm
Интерактивная визуализация — источник
Визуализация с помощью t-SNE
Я узнал об алгоритме уменьшения размерности t-SNE из статьи на Kaggle. Этот алгоритм был разработан Лоренсом ван дер Маатеном (Laurens van der Maaten) и, согласно официальному сайту (https://lvdmaaten.github.io/tsne/), является победителем соревнований по визуализации. В описании сказано, что он прекрасно подходит для визуализации данных большой размерности.
Давайте применим t-SNE к нашим данным и посмотрим, что получится. Отбираем признаки, преобразуем строковые значения с помощью model.matrix, а затем применяем tsne.
set.seed(13242) tsne_poke % select(type_1, type_2, weight, height, base_experience, attack, defense, special_attack, special_defense, speed, base_experience, hp, egg_group_1, egg_group_2) %>% map(function(x){ ifelse(is.na(x), "NA", x) }) %>% as.data.frame() %>% tbl_df() %>% model.matrix(~., data = .) %>% as.data.frame() %>% tbl_df() %>% .[-1] %>% tsne(perplexity = 60) df % mutate(x = tsne_poke[, 1], y = tsne_poke[, 2]) dfcenters % group_by(type_1, color_1) %>% summarise(cx = mean(x), cy = mean(y), sdcx = sd(x), sdcy = sd(y))
Далее воспользуемся пакетом ggplot, чтобы визуализировать покемонов в двумерном пространстве.
cols % select(type_1, color_1) %>% distinct() %>% { setNames(.$color_1, .$type_1) } ggМы видим, что в результате преобразования с помощью t-SNE, выделились группы покемонов, соответствующие первичным типам. Все группы являются относительно сконцентрированными, за исключением следующих типов: драконьего (dragon), волшебного (fairy), ядовитого (poison) и стального (steel). Это может быть связано с тем, что покемоны данных первичных типов имеют различные вторичные типы и, соответственно, не очень похожи между собой.
Далее, используя t-SNE, мы визуализируем всех покемонов на одной диаграмме. Каждый покемон будет представлен соответствующим изображением.
ds % select(pokemon, type_1, type_2, weight, height, attack, defense, special_attack, special_defense, url_image, url_icon, color = color_1, x, y) %>% list.parse3() %>% map(function(x){ x$marker$symbol % select(color = color_1, x, y) %>% mutate(color = hex_to_rgba(color, 0.05)) %>% list.parse3() urlimage % map(function(x){ tags$tr( tags$th(str_replace_all(str_to_title(x), "_", " ")), tags$td(paste0("{point.", x, "}")) ) }) %>% do.call(tagList, .) %>% tagList( tags$img(src = paste0(urlimage, "{point.url_image}"), width = "125px", height = "125px") ) %>% as.character() hctsne % hc_chart(zoomType = "xy") %>% hc_xAxis(minRange = diff(range(df$x))/5) %>% hc_yAxis(minRange = diff(range(df$y))/5) %>% hc_add_series(data = ds, type = "scatter", name = "pokemons", states = list(hover = list(halo = list( size = 50, attributes = list( opacity = 1) )))) %>% hc_add_series(data = ds2, type = "scatter", marker = list(radius = 75, symbol = "circle"), zIndex = -3, enableMouseTracking = FALSE, linkedTo = ":previous") %>% hc_plotOptions(series = list()) %>% hc_tooltip( useHTML = TRUE, borderRadius = 0, borderWidth = 5, headerFormat = "
Интерактивная визуализация — источник
Относительно последней визуализации следует отметить следующие интересные моменты:
- Покемоны сгруппированы по типу.
- Алгоритм сохранил логику эволюции покемонов. Например, мы видим, что Чармандер (Charmander), Чармелеон (Charmeleon) и Чаризард (Charizard) расположены рядом друг с другом.
- Самые тяжелые покемоны (Стиликс (Steelix), Оникс (Onix) и Вэйлорд (Wailord)) также располагаются рядом.
В итоге мы добились хороших результатов. t-SNE – отличный алгоритм, который может быть полезен при анализе других наборов данных.