Enquanto criava um Kaggle Kernel para a base de dados Killed by Police, 2015–2016, eu tive a ideia de visualizar os dados com uma animação. Já que as tabelas tinham informações sobre toda morte causada por policiais entre 2015 e 2016 com as coordenadas de cada morte, pensei que cada frame poderia ser um gráfico que representasse todas as mortes até um dia em particular. Parecia bastante simples, mas no final demorou mais do que eu imaginava.

Criando o gráfico estático

O primeiro passo era criar um gráfico estático para que eu pudesse ter uma ideia de como eu queria que a imagem final ficasse. Eu nunca tinha feito nenhum gráfico em R com dados geográficos, então demorei um pouco antes de ter certeza de que tudo estava funcionando. Eu decidi criar uma camada base para o gráfico e só então me preocupar com os dados.

plot_deaths <- ggplot() +
  geom_polygon(data = map_data("usa"), aes(long, lat, group = group), fill = "#e6e6e6") +
  theme(axis.text.x = element_blank(), axis.text.y = element_blank(),
        axis.title.x = element_blank(), axis.title.y = element_blank(),
        axis.line = element_blank(), axis.ticks = element_blank(),
        panel.background = element_blank(), panel.border = element_blank(),
        panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
        legend.position = "none") +
  coord_quickmap()

O código acima cria essa imagem:

Então filtrei a base para que ela contivesse apenas informação sobre os Estados Unidos continentais, que eu chamei de cont_deaths. Depois de uma limpeza, eu também criai a lista de cidades (e suas respectivas localizações) que tinha mais de 5 mortes registradas na base: deadly_cities.

plot_deaths +
  geom_text_repel(data = deadly_cities, aes(long, lat, label = city), size = 4) +
  geom_point(data = cont_deaths, aes(longitude, latitude), alpha = 0.2, color = "red") +
  ggtitle("Killed by Police (showing cities with most deaths)")

Eu tentei usar a função geom_text do pacote ggplot mas muitas cidades se sobrepunham, então procurei uma solução e acamei descobrindo o pacote ggrepel. Com ggrepel::geom_text_repel, o gráfico acabou ficando bem legal.

Na imagem abaixo, as cidades nomeadas são as com 5 ou mais mortes.

Eu estava satisfeito com os resultados, então decidi começar a trabalhar na animação.

Criando a animação

Para criar a animação, usei o pacote animation e instalei um programa chamado ImageMagick. Com animation::saveGIF tudo que tive que fazer foi um loop em que gerava o gráfico de fada frame e o pacote cuidava do resto.

saveGIF(for (i in 0:730) {

  # Filter deaths up to a certain date
  time_deaths <- cont_deaths %>%
    filter(date <= ymd("2015-01-01") + i)

  # Get the cities that have already had more than 5 deaths
  time_cities <- deadly_cities %>%
    left_join(time_deaths, c("city" = "city", "country.etc" = "state")) %>%
    group_by(city, country.etc) %>%
    summarise(count = n(), long = long[1], lat = lat[1]) %>%
    ungroup() %>%
    mutate(alph = count > 5)

  # Plot deaths
  print(plot_deaths +
    geom_text_repel(data = time_cities, size = 4, segment.alpha = 0,
                    aes(long, lat, label = city, alpha = factor(alph))) +
    scale_alpha_manual(values = c(0, 1)) +
    geom_point(data = time_deaths, aes(longitude, latitude), alpha = 0.2, color = "red") +
    ggtitle(paste0("Deaths until ", ymd("2015-01-01") + i,
                   " (showing when each city crosses the 5 deaths line)")))

}, "deaths.gif", interval = 0.005, ani.width = 900, ani.height = 630)

Neste código fiz um loop nos 730 dias da base e gerei gráficos somente com as mortes até cada data. Também verifiquei para ver se nenhuma cidade tinha cruzado a linha das 5 mortes para começar a mostrar o seu nome.

Esta é a animação final:

Palavras finais

Tentar criar essa animação foi uma experiência muito interessante. Eu tive que procurar quase tudo que tentava fazer, mas no final aprendi bastante. Créditos especiais para Rob Harrand, pessoa cujo Kernel me ensinou a usar o pacote animation.

A pior parte foi fazer os rótulos das cidades se comportarem. Dado que ggrepel::geom_text_repel acha o melhor lugar para cada rótulo, conforme novas cidades cruzavam a linha das 5 mortes, os outros rótulos pulavam de um lado para o outro por alguns frames. Eu consertei isso fazendo com que todos os rótulos fossem mostrados desde o primeiro frame e deixando os rótulos das cidades que ainda não deveriam aparecer com o texto transparente.

Se você quiser dar uma olhada no código fonte completo, dê uma olhada no meu Kernel.