Покемоны: анализ и визуализация

Некоторое время назад существовало всего 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 = "
", pointFormat = tooltip, footerFormat = "
" ) %>% hc_add_theme( hc_theme_null( chart = list( backgroundColor = "transparent", style = list( fontFamily = "Roboto" ) ) ) ) hctsne

Интерактивная визуализация — источник 

Относительно последней визуализации следует отметить следующие интересные моменты:

  • Покемоны сгруппированы по типу.
  • Алгоритм сохранил логику эволюции покемонов. Например, мы видим, что Чармандер (Charmander), Чармелеон (Charmeleon) и Чаризард (Charizard) расположены рядом друг с другом.
  • Самые тяжелые покемоны (Стиликс (Steelix), Оникс (Onix) и Вэйлорд (Wailord)) также располагаются рядом.

В итоге мы добились хороших результатов. t-SNE – отличный алгоритм, который может быть полезен при анализе других наборов данных.

Добавить комментарий

Ваш адрес email не будет опубликован.

закрыть

Поделиться

Отправить на почту
закрыть

Вход

закрыть

Регистрация

+ =