Gerando Mapas com R

Em nosso último post falamos um pouco sobre modelagem de dados aqui dentro da 99 utilizando R, algumas bibliotecas e o R Shiny para compartilhamento e interação com o usuário.

Periodicamente, nós da área de Business Inteligence e Market & Insights da 99, paramos nossas atividades durante metade de um dia para realizar nosso Hack Day. Dedicamos esse tempo para que toda a equipe foque em um problema e encontre sua solução, ou analisar potenciais pontos que podem gerar curiosidade. Ao final, compartilhamos nossos resultados com o restante da equipe a fim de trazer maior inteligência para todos.

Em um de nossos Hack Days, a equipe de BI criou um desafio de gerar visualizações gráficas e em mapas que pudessem trazer novos insights sobre origem e destino dos passageiros. As principais ferramentas utilizadas foram QGis, R e Gephi. Exploraremos nesse post alguns aspectos do R, e as demais ferramentas nos próximos posts.

Bases de Dados

Para nosso Hack Day utilizamos duas bases diferentes: PostgreSQL e MySQL. O R possui bibliotecas para ambas as tecnologias e são de fácil utilização. Antes de mais nada precisamos instalar e carregá-las:

install.packages(c("RMySQL", "RPostgreSQL"))

library(RMySQL) # Biblioteca para MySQL
library(RPostgreSQL) # Biblioteca para PostgreSQL

Lembrem-se que para utilizar as bibliotecas é preciso ter os drivers instalados na máquina. Ambos estão disponíveis em suas respectivas páginas: http://www.mysql.com/downloads/ e http://www.postgresql.org/download/.

Com as bibliotecas e os softwares instalados, criar uma conexão é muito simples. Utilizaremos a função dbConnect(drv, …), onde passaremos o driver como primeiro argumento e os demais dados da conexão, como autenticação e base de dados. 

conPostgre <- dbConnect(PostgreSQL(),
                        dbname = "nome do banco",
                        host = "endereço do servidor",
                        port = 5432,
                        user = "usuário",
                        password = "senha")

conMySQL = dbConnect(MySQL(),
                     dbname = "nome do banco",
                     host = "endereço do servidor",
                     port = 3306,
                     user = "usuário",
                     password = "senha")

Em caso de sucesso na conexão, as variáveis irão manter a conexão aberta, dado necessário para executar as consultas nos bancos de dados. Caso ocorra uma falha, o erro será exibido na tela de console:

Error in postgresqlNewConnection(drv, ...): RS-DBI driver: (could not connect 
            usuario@endereco on dbname "bd"
)

Existem duas maneiras de executar suas consultas dentro das conexões. Elas são essêncialmente iguais, mas a maneira como se recupera as linhas é diferente. O primeiro método é utilizando a função dbSendQuery(conn, statement, …) e, posteriormente, a fetch(res, n = -1, …) para retornar as linhas pesquisadas. No código abaixo mostramos um exemplo utilizando a conexão MySQL, mas o método está disponível para qualquer tipo de banco de dados.

resultado = dbSendQuery(conMySQL, "SELECT
    DATE_FORMAT(data_pedido, '%Y%m%d%H%i') as´data´,
    latitude,
    longitude
        FROM chamadas")

corridas = fetch(resultado, n=-1)

Utilizamos então a função fetch onde o resultado da função anterior é passada como primeiro argumento, e passamos também um valor para n que será o número máximo de linhas processadas por vez. Caso n=-1, todos os resultados serão lidos. A variável corridas irá conter um data frame com n linhas de corridas.A função dbSendQuery receberá como argumentos a conexão que realizamos anteriormente, e a consulta a ser realizada. No caso, selecionamos as chamadas de nossa base de dados, em conjunto com a data (formatada para agrupamento posterior no R), latitude e longitude do passageiro. Esta função irá deixar uma “fila” de resultados pendentes.

Uma opção mais fácil de se implementar e agilizar no desenvolvimento do código, é a função dbGetQuery(conn, statement, …). Esta já apresenta uma implementação padrão interna das funções dbSendQuery e fetch, assim, ao enviar o comando para a coneção, a função retornará automaticamente o data frame com o seu resultado, melhorando a visualização do código e sua organização.

corridas = dbGetQuery(conMySQL, "SELECT
    DATE_FORMAT(data_pedido, '%Y-%m-%d') as´data´,
    latitude,
    longitude
        FROM chamadas")

Com os dados em mãos, podemos continuar com nossa análise.

Manipulação de Dados

Uma biblioteca bastante utilizada por nós no R é a dplyr, que pode ser instalada e carregada com os seguintes comandos:

install.packages("dplyr")
library(dplyr)

Esta biblioteca tem uma série de funções (em formato de verbos em inglês) que visam facilitar a manipulação dos dados. Quando falamos de data frames, esta ferramenta é excelente por dar agilidade e facilidade na compreensão das funçoes. Dentre elas:

Função Descrição
filter() Filtrar dados de acordo com as condições
arrange() Reordenar as linhas por uma ou mais colunas
select() Seleciona algumas colunas quando trabalhando com muitos dados
distinct() Seleciona apenas valores distintos de uma ou mais colunas
mutate() Insere colunas na série de dados sendo trabalhada
summarise() Cria um sumário de uma única linha de todos os dados da tabela

Outro aspecto importante desta biblioteca é que, ao contrário de muitas funções, não é preciso fazer funções aninhadas. A dplyr possui uma funcionalidade que permite passar o resultado da função anterior para a seguinte utilizando apenas o comando %>% ao final. Vejamos:

datasDasCorridas <- group_by(corridas, data)
datasDasCorridas <- distinct(datasDasCorridas, data)
datasDasCorridas <- select(datasDasCorridas, data)

#ambos os resultados de datasDasCorridas terão os mesmos valores

datasDasCorridas <- group_by(corridas, data) %>%
                    distinct(data) %>%
                    select(data)

O nosso objetivo é selecionar as diferentes datas em que ocorram corridas dentro da 99. A primeira opção é a mais simples de se entender, mas com muitas repetições. A segunda opção trará os mesmos resultados, mas de uma maneira mais direta e fácil de se ler.

Outra importante função da dplyr é a aplicação de filtros. Podemos, por exemplo, filtrar para utilizar apenas as corridas do dia anterior que será rodado o código:

corridasOntem <- filter(corridas, data = Sys.Date() - 1) %>%
                 select(latitude, longitude)

Notem que com as corridas filtradas, aproveitamos para selecionar apenas as latitudes e longitudes de origem das chamadas. Agora estamos com todos os dados prontos para gerar a visualização em mapa.

Gerando Mapas

Existem diversas bibliotecas disponíveis em R para se trabalhar com mapas. Porém, gostamos de utilizar a ggmap. Como padrão, instalamos e carregamos a biblioteca para utilizála:

install.packages("ggmap")
library(ggmap)

Com diversas funções disponíveis, a ggmap nos permite carregar e plotar mapas utilizando diversos serviços, como Google Maps, OpenStreetMap, Stamen Maps e CloudMade. Ainda, é possível solicitar diferentes estilos como satélite, elevação, híbrido ou ruas. Focamos em São Paulo e para isso passamos as coordenadas (longitude e latitude) para que a biblioteca baixe o mapa:

saoPaulo = c(-46.403654, -23.688747, -46.877542, -23.424317)
meuMapa = get_map(location = saoPaulo,
                  zoom = 11, 
                  source = "google",
                  maptype="roadmap")

Carregamos o mapa pelo Google Maps com o estilo de ruas para a geolocalização de São Paulo. O argumento zoom é um número inteiro entre 3 e 21 com valor padrão de 10, onde 3 representa o zoom do continent, 21 para prédios e 10 a cidade.

Zoom Level

Exibir esse mapa é tão simples quanto chamar uma única função, sendo ela a ggmap(ggmap, extent = “panel”, base_layer, maprange = FALSE, legend = “right”, padding = 0.02, darken = c(0, “black”), …), sendo o primeiro parâmetro o resultado do nosso get_map na variável meuMapa. Acima mostramos alguns dos resultados com diferentes valores de zoom na função.

ggmap(meuMapa)

A partir de agora, interagir com o mapa é muito mais simples. Imagine que o ggmap irá criar um plano cartesiano onde o eixo X é a longitude e o eixo Y é a latitude. O ponto inferior esquerdo, onde ocorre o cruzamento entre os eixos, representará as latitudes e longitudes mínimas informadas da área do mapa. Assim, plotar pontos no mapa basta utilizar as coordenadas geográficas e estarão disponíveis sem nenhuma necessidade de tratamento.

Por meio da função geom_point(mapping = NULL, data = NULL, stat = “identity”, position = “identity”, na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, …) podemos plotar esses pontos no gráfico. Uma característica interessante dessa função é que ao invés de passarmos o resultado do ggmap, podemos utilizar o comando de adição (+) para concatenar os dados, facilitando o processo. Para efeitos de testes, vejamos alguns dados plotados no mapa:

ggmap(meuMapa, extent = "device") + geom_point(data = corridas[1:18,], aes(x = longitude, y = latitude, alpha = 0.5, size = 0.5))

image03

Selecionamos apenas uma pequena parcela das corridas utilizando o delimitador [1:18,]. A vírgula está presente para selecionar todas as colunas desses dados. Ao diminuirmos os tamanhos dos pontos e plotarmos mais dados, é possível ver as principais área de concentração de chamadas, ou ter uma visão mais detalhada de um ponto da cidade.

image00 image01

Animação

Mais do que tentar trazer algo novo para o Hack Day,  buscamos trazer inovação e novidades. Para isso, trouxemos a visualização animada dos dados da 99 para que nossos colaboradores pudessem visualizar de uma melhor maneira. Uma boa biblioteca para fazer isso é a animation.

install.packages("animation")
library(animation)

Apesar de bastante completa, permitindo exportar em HTML (diversas imagens animadas com um controlador em javascript), GIF utilizando o ImageMagick, SWF ou vídeo utilizando o ffmpeg.

As funções para exportar nos diferentes formatos são bastante parecidas. Pela facilidade de colocarmos aqui, utilizaremos o método da imagem GIF com o comando saveGIF(expr, movie.name = “animation.gif”, img.name = “Rplot”, convert = “convert”, cmd.fun, clean = TRUE, …), lembrando da necessidade do ImageMagick estar instalado na máquina.

Um passo importante é definir algumas configurações padrões da biblioteca animation com o comando abaixo:

oopt = ani.options(convert = "C:/Program Files/ImageMagick-6.9.3-Q16/convert.exe")

Definimos aqui o caminho para o ImageMagick caso não esteja no PATH do sistema operacional sendo utilizado. Para os casos onde se deseja exportar um vídeo, deve-se definir o caminho do ffmpeg. Em seguida, chamamos a função saveGIF onde o primeiro argumento será uma expressão que irá gerar os diferentes quadros da animação:

saveGIF({
  for(i in 1:NROW(datasDasCorridas$data)) {
    thisData <- filter(corridas, date == datasDasCorridas$data[i]) %>%
                select(longitude, latitude)
    displayMap = ggmap(meuMap, extent = "device") +
                 geom_point(data = thisData,
                            aes(x = startlong,
                                y = startlat),
                            alpha = 0.5,
                            size = 0.05)
    print(displayMap)
    ani.pause()
  }
}, movie.name = "animation.gif", img.name = "Rplot", clean = TRUE)

Faremos um loop pelas diferentes datas das corridas selecionadas de nosso banco de dados utilizando a função de loop for. Em seguida, utilizando a dplyr, filtramos os dados das corridas para a data selecionada e deixamos apenas as latitudes e longitudes a fim de minimizar a memória RAM utilizada. Como falamos anteriormente, a função ggmap irá gerar o mapa e a geom_point irá plotar os pontos das corridas, mas por uma questão técnica da bilbioteca animation, deixamos o mapa salvo em uma variável e, em seguida, utilizando a função print enviarmos para o buffer para que seja criado um frame da animação.

É possível customizar o intervalo entre um frame e outro, dentre outras carcterísticas, chamando a função ani.options com o argumento interval preenchido. Dentro do loop da animação, chamamos a ani.pause que irá parar a animação de acordo com este intervalo definido. O resultado será uma animação como esta:

 

Caso encontre erros como os listados abaixo:

I cannot find ImageMagick with convert = "convert"
but I can find it from the "Program Files" directory: C:\Program Files/ImageMagick-6.9.1-Q16/convert.exe
Executing: 
  "C:\Program Files\ImageMagick-6.9.1-Q16\convert.exe" -loop 0 -delay 100 Rplot1.png Rplot2.png Rplot3.png
Rplot4.png Rplot5.png Rplot6.png Rplot7.png Rplot8.png Rplot9.png Rplot10.png "animation.gif"
'C:\Program' is not recognized as an internal or external command,
operable program or batch file.
convert.exe: unable to load module `C:\Program Files\ImageMagick-6.9.1-Q16\modules\coders\IM_MOD_RL_PNG_.dll': The specified module could not be found.
@ error/module.c/OpenModule/1282.
convert.exe: no decode delegate for this image format `PNG' @ error/constitute.c/ReadImage/501.
convert.exe: unable to load module `C:\Program Files\ImageMagick-6.9.1-Q16\modules\coders\IM_MOD_RL_PNG_.dll': The specified module could not be found.
@ error/module.c/OpenModule/1282.
@ error/module.c/OpenModule/1282.
convert.exe: no decode delegate for this image format `PNG' @ error/constitute.c/ReadImage/501.
convert.exe: no images defined `animation.gif' @ error/convert.c/ConvertImageCommand/3230.
an error occurred in the conversion... see Notes in ?im.convert

Lembre-se de executar o R (ou o R-Studio) com permissões elevadas (administrador). Muitas vezes esses erros são comuns em máquinas windows.

Gostou? Entre em contato com o time de BI clicando aqui!

Previsão de Demanda com R

Olá, este é o primeiro de uma série de posts que faremos sobre a área de Business Intelligence e Análise de Dados da 99. Sempre que quiser, você encontrará tudo na área de 99 Dados.

Aqui na 99 buscamos trazer qualidade para uma tomada de decisão mais assertiva. Fazemos isso com dois objetivos:

  1. Do lado do Taxista, maximizar o número de corridas aumentando a rentabilidade de todos;
  2. Do lado do passageiro, garantir que haverá um taxista pronto para atender sua corrida.

Cumprir estes objetivos exige modelagens complexas, análises históricas, previsões e entendimento de comportamento do mercado. Esta é uma tarefa bastante complicada, mas é o desafio que nos move e nos faz querer ir mais longe.

Análise complexa e profunda é O trabalho para a equipe de Business Intelligence e Market & Insights da 99 – queremos maximizar a experiência com a 99 (taxista e usuário) e levar cada vez mais pessoas para seus destinos.

 

Antes de entrarmos em alguns detalhes, precisamos esclarecer algo sobre economia básica: curva de oferta e demanda. Em qualquer livro de introdução à economia, nos primeiro capítulos serão abordados os aspectos destas curvas.

Curvas de oferta e demanda são a chave para garantir nossos objetivos. Entendemos que ambas são elásticas e sujeitas a alterações com variações, principalmente, no preço. Mas como isso afeta a 99?

Com a entrada de novas promoções ocorre uma variação das tarifas. Assim, a previsão da oferta e da demanda sofrerão alterações proporcionais à variação do preço das corridas. Cabe ao time de Inteligência da 99 entender essas mudanças e montar modelos de previsão de oferta e demanda.

Para chegar na previsão, fazemos uso de:

  • Dados internos históricos
  • Informações de promoções oferecidas
  • R-Project com Shiny

Dados Internos Históricos

Ao longo dos últimos três anos de 99, geramos dados sobre 7,5 milhões de usuários e 140 mil motoristas de táxi em todo o Brasil. São mais de 40 milhões de corridas realizadas pelo país.

Olhar para o passado pode nos ajudar a entender o comportamento de expansão do mercado, melhorar a maneira como oferecemos nossos serviços e trazer insights sobre mobilidade urbana. Essas informações tornam-se úteis não só para nossa tomada de decisão, mas também para trabalharmos com órgãos públicos e criar cidades mais inteligentes e integradas.

Dados Internos

Diversos tópicos relacionados aos dados internos históricos ainda serão explorados em outros posts. Focaremos aqui no caso onde  esses dados foram utilizados para dimensionar o tamanho mercado (curva de oferta e de demanda), receitas e modelagens dos cenários diante das variações de preço por conta de promoções.

Informações de Promoções Oferecidas

Claro Clube (50% de desconto) e Johnnie Walker® (até R$30 reais de desconto) são apenas dois exemplos das diversas campanhas realizadas pela 99. Em conjunto com os dados históricos, pode-se analisar o efeito das promoções no número de corridas e no comportamento de nossos usuários e taxistas, bem como de suas corridas.

R-Project com Shiny

Com os dados em mãos, é preciso ter uma boa ferramenta para importar, filtrar e analisar os dados para, finalmente, fazer sua modelagem. Utilizamos o R, um projeto open-source e gratuito (GNU General Public License version 2) com uma linguagem poderosa que exige conhecimentos em lógica de programação.

R

Sua interface é totalmente via scripts e linha de comando. Por mais assustadora que pareça, é uma linguagem fácil de ser aprendida e com uma grande comunidade disposta a dar todo o suporte, independente do nível de conhecimento do usuário. Uma vez interado de suas funcionalidades, o R promove a experimentação e a exploração por ser uma linguagem interativa, trabalhando em tempo real dos dados.

Visualização de dados e gráficos é uma parte essencial do R implementado desde seu core e por diversas bibliotecas públicas (como Lattice e ggplot2). Um conjunto de ferramentas estastíticas são padrões do software, como as mais tradicionais regressões e ANOVA, mas também manipulação de dados como transformação e agregar.

Como mencionei antes, existem diversas bibliotecas disponíveis publicamente para o R em seu repositório CRAN (Comprehensive R Archive Network), mas uma em especial chama a atenção para a criação de modelagens dinâmicas. A biblioteca Shiny, mantida pelos mesmos desenvolvedores do RStudio (interface gráfica para desenvolvimento em R), é uma ferramenta que permite a criação de páginas web com interação com as funcionalidades do R.

Sem a necessidade de conhecimento técnico ou de programação, criar um aplicativo Shiny o acesso e a simulação a todos os usuários interessados em simular e entender o modelo sugerido. Assim, garantimos que todos tenham acesso às mesmas informações.

Resultado

Esta modelagem, em conjunto com suas simulações, fornece para os diferentes times da 99 hipóteses e perspectivas para uma melhor tomada de decisão visando atingir nossos objetivos de melhor a experiência do usuário e maximizar o resultado dos taxistas.

Gostou? Entre em contato com o time de BI clicando aqui!