Vinheta elasticR

Vinheta são documentações de casos de uso de pacotes do R. Os pacotes do R, que geralmente estão CRAN utilizam desse artifício para explicar as principais funcionalidade do pacote.

Na MAPI, empresa que trabalhava do ramo de ciência de dados para o mercado imobiliário, extraímos anúncios de imóveis em portal e imobiliárias de Curitiba e no estado de São Paulo. A partir desses dados transformavamos em informação criando produtos para o segmento de incorporação nas métricas de exclusividade, precificação e valorização do empreendimento. Esse produto era parte da empresa e pode ser encontrado na Isket.

Entretanto para acessar esses dados, estavam disponível no ElasticSearch. Confesso que quando entrei nunca tinha trabalho com essa estrutura de dados, também considerados NOSQL e no caso a query a ser feita era bem mais complexa. Nós usamos elasticsearch devido a agilidade na busca dos filtros da plataforma Loopim, produto para corretor de imóvel e imobiliária.

Os pacotes existentes no R elasticsearchr e elastic, não obitve êxito. Sendo assim, decidimos criar nossas próprias funções de extração de dados por meio de uma requisição GET. O pacote é elasticR no qual envelopa as principais query utilizadas. A intenção do conteúdo é expor todas as funções e claro contar com a sua contribuição para melhorar o pacote.


Instalação do Pacote:

devtools::install_github("gabrielsartori/elasticR")
require(elasticR)

Para usar praticamente todas as funções é necessário carregar os pacotes dplyr, purrr e sf. Na vinheta, apresento o resultado das querys espaciais em mapa, sendo assim carrego os pacotes ggplot2 e magrittr para manipulação de dados.

require(dplyr)
require(purrr)
require(sf)

require(ggplot2)
require(magrittr)

Todas as funções começas com es.*. A primeira situação é extrair quais as variáveis existentes na tabela de interesse. Neste exemplo de vamos trabalhar com a tabela “realties” que são informações dos anúncios de imóveis vigentes.

elasticR::es.variable(
  user = "nome_usuário",
  passwd = "senha",
  find_search = "search", # Geralmente search
  database =  "nome_base_de_dados", #  nome do banco de dados
  table =  "realties" # nome da tabela
)

O resultado mostra um vetor de caracteres dos nomes das variáveis. Apresento a seguir algumas das variáveis presentes na base de dados.

## [1] "finalidade"   "latitude"     "longitude"    "opcionais"    "tipo_negocio"
## [6] "valor"

Além de saber os nomes das variáveis presentes, é importante saber qual a tipagem da variável. O elasticsearch como veremos logo a seguir tem características particulares.

type_variable <- 
  elasticR::es.variable.type(
  user = "nome_usuário",
  passwd = "senha",
  find_search = "search", # Geralmente search
  database =  "nome_base_de_dados", #  nome do banco de dados
  table =  "realties"
)

O retorno da type variable é uma lista nomeada com três categorias. list_variable retorna uma lista de variáveis com as respectivas variáveis que fazem parte e as tipagens. No caso, os opcionais retornam três colunas presentes na lista. Repare que location e name tratam do tipo keyword, é um atributo do tipo texto, que somente são filtradas pelor valor exato do campo. Caso queira mais detalhes acesse a nota do elasticsearch.

type_variable %>% 
  names()
## [1] "list_variable"       "univariate_variable" "raw_variables"
type_variable %>% 
  .$list_variable %>% 
  .$opcionais
## # A tibble: 1 x 3
##   category location name   
##   <chr>    <chr>    <chr>  
## 1 text     keyword  keyword

univariate_variable retorna diretamente a tipagem do campo existente. No exemplo, temos valor, latitude e longitude como campos numéricos, já tipo negócio e finalidade como texto. A relação completa das tipagem de variáveis e comparando com o padrão do SQL está dispónível aqui.

type_variable %>% 
  .$univariate_variable %>% 
  select(c("valor", "tipo_negocio", "finalidade", "latitude", "longitude")) %>% 
  knitr::kable(align = "c") %>% 
  kableExtra::kable_styling(position    = "center")
valor tipo_negocio finalidade latitude longitude
float text keyword float float

A raw_variable são as variáveis que quando for usada para agregações é necessário colocar .raw depois da variável. Caso queira mais detalhes acesse a nota do elasticsearch.

type_variable %>% 
  .$raw_variables 
## [1] "default"      "descricao"    "tipo_imovel"  "tipo_negocio"

Acessando a tabela de dados

Toda e qualquer função é necessário passar as credencias. A credencial nada mais é que o caminho da requisição no caso, reforçando vamos trabalhar com dados de imóveis. A função recebe os mesmos parâmetros de es.variable e es.variable.type.

credential_realties <- 
  elasticR::es.credential(
    user = "nome_usuário",
    passwd = "senha",
    find_search = "search",
    database =  "nome_base_de_dados", 
    table =  "realties"
  )

Para pegar todas as colunas, mas com restrição nas linhas utilize a função es.alldata. Todo query por padrão no elasticsearch retorna 10 resultados, em todas as funções é possível aumentar a quantidade de dados.

realties <- 
  elasticR::es.alldata(
  credential = credential_realties,
  size = 100
)
realties %>% 
   select(c("valor", "tipo_negocio", "finalidade", "latitude", "longitude")) %>% 
   nrow()
## [1] 100
realties %>% 
   select(c("valor", "tipo_negocio", "finalidade", "latitude", "longitude")) %>% 
   head()
##     valor tipo_negocio  finalidade latitude longitude
## 1  650000        Venda Residencial       NA        NA
## 2  620000        Venda       Rural       NA        NA
## 3 2128000        Venda Residencial       NA        NA
## 4 1300000        Venda Residencial       NA        NA
## 5  750000        Venda Residencial       NA        NA
## 6 1600000        Venda Residencial       NA        NA

Para pré-selecionar antes as variáveis utilize a função e com restrição nas linhas utilize es.columndata.

realties_var <- 
  elasticR::es.columndata(
  credential = credential_realties,
  size = 100,
  variable = c("valor", "tipo_negocio", "finalidade", "latitude", "longitude"))
realties_var %>% 
  nrow()
## [1] 100
realties_var %>% 
   head()
##   tipo_negocio  finalidade   valor latitude longitude
## 1        Venda Residencial  650000       NA        NA
## 2        Venda       Rural  620000       NA        NA
## 3        Venda Residencial 2128000       NA        NA
## 4        Venda Residencial 1300000       NA        NA
## 5        Venda Residencial  750000       NA        NA
## 6        Venda Residencial 1600000       NA        NA

Filtrando os dados

Para extrair os dados com filtros de resposta há duas funções a es.filterun que somente trabalha com um único filtro e uma única resposta. A função es.filtermult é possível indicar mais filtros e com resposta múltiplas. Os filtros acrescidos, funcionam como operadores (&,e) ou seja, ainda não é possível colocar no filtro a situação um ou outro.

realties_rural <- 
  elasticR::es.filterun(
  credential =  credential_realties,
  variable =  c("valor", "tipo_negocio", "finalidade", "latitude", "longitude"),   
  size = 100,
  filter = "finalidade",
  answer = "Rural"
)
realties_rural %>% 
  select(finalidade) %>% 
  table()
## .
## Rural 
##   100

Na próxima faremos uso do .raw, vamos fazer uma query filtrando pelo tipo negócio e o bairro. O bairro é uma variável lista, para acessar o nome deve ser especificado neste formato: bairro.nome. Segue abaixo a aggregação da contagem de cada variável.

realties_cwb_centro_cristo_rei <-
  elasticR::es.filtermult(
  credential =  credential_realties,
  variable =  c("cidade_uf", "quarto", "valor", "tipo_negocio", "finalidade", "bairro.nome"),   
  size = 1000,
  es_filter = 
    list(
      "finalidade" = "Comercial",
      "cidade_uf" = "curitiba_pr",
      "quarto" = "1",
      "tipo_negocio.raw" = "Locação",
      "bairro.nome.raw" = c("Centro", "Cristo Rei")
  )
)
## $tipo_negocio
## 
## Locação 
##     101 
## 
## $quarto
## 
##   1 
## 101 
## 
## $cidade_uf
## 
## curitiba_pr 
##         101 
## 
## $finalidade
## 
## Comercial 
##       101 
## 
## $bairro
## 
##     Centro Cristo Rei 
##         93          8

Agregação

Uma das facilidade em trabalhar com ferramentas deste banco de dados é usar a performance de agregações que são estatísticas sumarisadas. No caso há duas funções, es.catcount e es.summary. A primeira, conta a quantidade de informação na variável selecionada, use somente para variáveis do tipo texto e keywords. No exemplo, o tipo imóvel apartamento predomina na base de dados.

elasticR::es.catcount(
  credential = credential_realties,
  variable = "tipo_imovel.raw",
  size = 3) 
tipo_imovel_count
tipo_imovel.raw count
Apartamento 3238649
Casa 1458577
Terreno 443701

A próxima função usa-se para variáveis numéricas, retorna medidas descritivas(mínimo, máximo, média) e os percentis. É possível também amplificar seu potencial usando a função map_dfr passando várias variáveis para serem sumarisadas.

elasticR::es.summary(
  credential = credential_realties,
  variable = "valor") %>% 
  knitr::kable(align = "c")
variable pct_1 pct_5 pct_25 pct_50 pct_75 pct_95 pct_99 count min max avg sum
valor 738.3736 1600.013 218362.3 410000.9 799998.4 2501151 6505320 6136693 0 1.45e+09 804225.4 4.935284e+12
quarto_banheiro_summarise <- 
  purrr::map_dfr(
    .x = c("banheiro", "quarto"),
    .f = ~elasticR::es.summary(
        credential = credential_realties,
        variable = .x)
)
variable pct_1 pct_5 pct_25 pct_50 pct_75 pct_95 pct_99 count min max avg sum
banheiro 0 1 1 2 3 5 7 2327336 0 45 2.275807 5296568
quarto 0 1 2 3 3 4 5 5194647 0 60 2.525473 13118943

Por enquanto, não é ainda possível adicionar filtros nas querys de agregações.

Query Espaciais

Uma vantagem de ter um banco de dados no elasticsearch é realizar consultas com filtros geográficos. Os tipos permitidos está disponíveis na documentação. As funções aqui criadas, farão uso da localização da academia studio fit, para encontrar anúncios de até 1km.

# Lat/long academia
academia <- ggmap::geocode("Av. São José, 991 - Cristo Rei, Curitiba - PR, 80050-350")

imovel_academia <- 
  elasticR::es.buffer(
    credential = credential_realties,
    variable = c("valor", "tipo_negocio", "finalidade", "tipo_imovel", "quarto", "latitude", "longitude"),
    latitude = academia$lat,
    longitude =  academia$lon,
    size = 10000,
    buffer = "1km"
  )

Realizando filtros, queremos só imóveis com a locação do tipo apartamento residencial e de 1 quarto.

locacao_ap_residencial <- 
  imovel_academia %>% 
  filter(finalidade == "Residencial", 
         tipo_imovel == "Apartamento", 
         tipo_negocio == "Locação",
         quarto == 1)

Manipulando as características geográficas da academia e dos anúncios temos o mapa que representa os dados extraídos.

Outra situação é encontrar imóvel entre duas faixas geográficas em um raio. Na função es.buffer.ring extraimos dados entre dois raios geográficos. Realizando os mesmos filtros de anúncios e exploramos o mapa a seguir.

academia_1km_3km <- 
  elasticR::es.buffer.ring(
    credential = credential_realties,
    variable = c("valor", "tipo_negocio", "finalidade", "tipo_imovel", "quarto", "latitude", "longitude"),
    latitude = academia$lat,
    longitude =  academia$lon,
    size = 10000,
    buffer = "1km",
    ring = "3km"
)

A primeira versão do pacote é essa! Há muitas outras implentações que podem contribuir como filtros nas agregações e extrações de dados espaciais. Adicionar paginações, já que o elastic, limita a quantidade de dados geralmente 10.000 na configuração padrão a serem extraídos por cada query. Caso queira contribuir fique à vontade.