Análise de dados usando elasticsearch aggregations

elasticsearch

O ElasticSearch é uma ferramenta que surgiu inicialmente com intenção de ser uma máquina de busca distribuída desenvolvida em cima da biblioteca Apache Lucene. Ao longo do tempo, com a adição de novas features, foram surgindo diferentes casos de uso da ferramenta que vão muito além da busca textual para qual foi inicialmente desenvolvida.

Agregações

A partir da versão 1.0, o ElasticSearch lançou uma feature que permite agregações de dados. Agregações são computações feitas em cima dos documentos retornados por alguma consulta. Por exemplo, dado um conjunto de documentos que casam com uma consulta, é possivel computar quais são os termos mais frequentes, quantos documentos caem em uma determinada faixa de tempo, quais documentos estão dentro de uma distância geográfica, e por ai vai.

No ElasticSearch existem diversass implementações de agregações disponíveis que podem ser classificadas em duas categorias:

  • Bucketing aggregations – Permitem o agrupamento dos documentos resultantes de uma consulta em partições de acordo com valores extraídos dos documentos. O resultado final de uma agregação será uma lista de buckets, cada um contendo um subconjunto dos documentos da consulta. Dependendo da agregração utilizada, um bucket pode ser um intervalo numérico, um intervalo de datas, um termo, um raio de distância geográfica, etc.
  • Metrics aggregations – Agregações de métricas computam valores a partir de um documento, sejam extraíndo o valor diretamente do documento, ou realizando alguma computação em cima de valores extraídos dos documentos.

Uma das características mais importantes das agregações é que elas podem ser aninhadas, ou seja, você pode computar agregações em cima dos resultados de outras agregações, como veremos nos exemplos mais a frente.

Agregrações tem a seguinte estrutura básica:

"aggregations" : {
    "<aggregation_name>" : {
        "<aggregation_type>" : {
            <aggregation_body>
        }
        [,"aggregations" : { [<sub_aggregation>]+ } ]?
    }
    [,"<aggregation_name_2>" : { ... } ]*
}

onde: aggregation_name é um nome qualquer escolhido por você para identificar uma agregação, aggregation_type é o tipo da agregação (como por ex: min, max, avg, stats, terms, geo_distance, etc.), aggregation_body são os parâmetros da agregação e aggregations é um array (opcional) de agregações aninhadas que irão operar sobre os resultados da agregação definida no nível acima. As subagregações definidas no array aggregations também podem ter subagregações aninhadas. Não há um número máximo para a quantidade de agregações aninhadas.

Exemplo de uso de agregações com dados reais

Para esse exemplo, vamos utilizar dados sobre os candidatos a Deputado Estadual e Federal do estado de Minas Gerais nas Eleições de 2014. Os dados estão disponíveis no site do TSE neste link (também disponível em formato CSV). Também dá pra visualizar a lista completa aqui (deputados federais) e aqui (deputados estaduais).

Inicialmente os dados precisam ser indexados no ElasticSearch. Para isso, vamos organizar os dados em documento JSON da seguinte forma:

{
  "partido": "PARTIDO",
  "nome": "NOME DO CANDIDATO",
  "numero": 99999,
  "coligacao": "PARTIDO1 / PARTIDO2 / PARTIDO3"
}

Mas antes de indexar os documentos, vamos criar mappings para informar explicitamente ao ElasticSearch que o texto de alguns campos não deve ser “pré-processado”. Por padrão, o ElasticSearch realiza um processo de análise de texto que envove quebra o texto em termos, tranformação de caracteres em minúsculas, remoção de sufixos, e outros. A criação de mappings no ElasticSearch pode ser comparada a definição do esquema de uma tabela em um banco de dados relacional, porém no ElasticSearch este passo é opcional.

O objetivo de criar os mappings nesse exemplo é somente para evitar o comportamento padrão do ElasticSearch, que é fazer pre-processamento do texto antes de indexar. Para isso, vamos mapear os campos partido e coligacao do documento JSON como não analizados.

Vamos indexar cada tipo de candidato em tipos diferentes no ElasticSearch: dep_federal e dep_estadual, como comando cURL a seguir:

curl -XPUT 'http://localhost:9200/eleicoes2014' -d '{
   "mappings": {
      "dep_federal": {
         "properties": {
            "partido": {
               "type": "string",
               "index": "not_analyzed"
            },
            "coligacao": {
               "type": "string",
               "index": "not_analyzed"
            },
            "numero": {
               "type": "integer"
            }
         }
      },
      "dep_estadual": {
         "properties": {
            "partido": {
               "type": "string",
               "index": "not_analyzed"
            },
            "coligacao": {
               "type": "string",
               "index": "not_analyzed"
            },
            "numero": {
               "type": "integer"
            }
         }
      }
   }
}'

Para indexar os documentos precisamos enviar os documentos em requisições POST para a API de indexação do ElasticSearch. Para esse exemplo, implementei isso usando JavaScript e Node.js. O códido está aqui https://github.com/aecio/presentations/blob/master/sample/estadual.js e aqui https://github.com/aecio/presentations/blob/master/sample/federal.js

Análise exploratória de dados usando agregações

Finalmente podemos fazer uma análise exploratória nos dados. Vamos começar com uma pergunta bem simples. Há quantos cadidatos de cada tipo? A seguinte agregação responde essa pergunta:

curl -XPOST "http://localhost:9200/eleicoes2014/_search" -d'
{
   "size": 0,
   "aggregations": {
      "tipo_de_candidato": {
         "terms": {
            "field": "_type"
         }
      }
   }
}'

Neste exemplo, estamos executando uma agregação que identificamos como tipo_de_candidato. Essa agregação é do tipo terms, que agrupa os documentos em buckets para cada termo distinto encontrado. Como parâmetro, informamos que a agregação seja feita usando o campo _type, que é um campo especial presente em todos documento indexados e que armazena o tipo do documento. Como temos somente dois tipos são retornados dois buckets, com a quantidade de documentos em cada bucket:

{
   "took": 2,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 1898,
      "max_score": 0,
      "hits": []
   },
   "aggregations": {
      "tipo_de_candidato": {
         "buckets": [
            {
               "key": "dep_estadual",
               "doc_count": 1200
            },
            {
               "key": "dep_federal",
               "doc_count": 698
            }
         ]
      }
   }
}

Note que como não especificamos nenhuma query para o ElasticSearch, a agregação operou em todos os documentos indexados (1898 hits). Se quisessemos fazer uma pergunta mais específica, como por exemplo: Há quantos cadidatos de cada tipo que se chamam joão? Nesse caso, poderiamos fazer a seguinte agregação:

curl -XPOST "http://localhost:9200/eleicoes2014/_search?q=nome:joao" -d'
{
   "size": 0,
   "aggregations": {
      "tipo_de_candidato": {
         "terms": {
            "field": "_type"
         }
      }
   }
}'

Note a adição da query q=nome:joao no final da URL da requisição. Esta query retorna somente 8 caditados com o termo joao no nome, os quais são agregados em dois buckets:

{
   "took": 12,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 8,
      "max_score": 0,
      "hits": []
   },
   "aggregations": {
      "tipo_de_candidato": {
         "buckets": [
            {
               "key": "dep_estadual",
               "doc_count": 6
            },
            {
               "key": "dep_federal",
               "doc_count": 2
            }
         ]
      }
   }
}

Suponha agora que precisamos de mais detalhes. Para cada tipo de cadidato, precisamos saber:

  • Quais as duas coligações que tem mais candidatos?
  • Quais os dois partidos que tem mais candidatos?
  • Quais os valores minimo, máximo e médio dos números dos cadidatos?

Podemos reponder todas essas pergutas somente com uma requisição, utilizando agregações aninhadas:

curl -XPOST "http://localhost:9200/eleicoes2014/_search" -d'
{
   "size": 0,
   "aggregations": {
      "tipo_de_candidato": {
         "terms": {
            "field": "_type"
         },
         "aggregations": {
            "por_partido": {
               "terms": {
                  "field": "partido",
                  "size": 2
               }
            },
            "por_coligacao": {
               "terms": {
                  "field": "coligacao",
                  "size": 2
               }
            },
            "estatisticas_de_numero": {
               "stats": {
                  "field": "numero"
               }
            }
         }
      }
   }
}'

Nessa agregação utilizamos a mesma agregação do primeiro exemplo, com adição de 3 subagregações, uma para cada pergunta. A agregação por_partido fará agreagação de termos usando os valores do campo candidato, a agregação por_coligacao fará agregação utilizando o campo coligacao, e a agregação estatisticas_de_numero, que é do tipo stats, retornará estatisticas sumarizando os números encontrados no campo número. A reposta para essa agregação pode ser vista a seguir:

{
   "took": 6,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 1898,
      "max_score": 0,
      "hits": []
   },
   "aggregations": {
      "tipo_de_candidato": {
         "buckets": [
            {
               "key": "dep_estadual",
               "doc_count": 1200,
               "por_coligacao": {
                  "buckets": [
                     {
                        "key": "PRP / PEN / PHS",
                        "doc_count": 133
                     },
                     {
                        "key": "PT / PROS / PMDB / PRB",
                        "doc_count": 108
                     }
                  ]
               },
               "por_partido": {
                  "buckets": [
                     {
                        "key": "PT do B",
                        "doc_count": 85
                     },
                     {
                        "key": "PC do B",
                        "doc_count": 41
                     }
                  ]
               },
               "estatisticas_de_numero": {
                  "count": 1200,
                  "min": 10000,
                  "max": 90999,
                  "avg": 36122.61666666667,
                  "sum": 43347140
               }
            },
            {
               "key": "dep_federal",
               "doc_count": 698,
               "por_coligacao": {
                  "buckets": [
                     {
                        "key": "PTdo B / PRP / PHS / PEN",
                        "doc_count": 81
                     },
                     {
                        "key": "DEM / PSDB / PP / PR / PSD / SD",
                        "doc_count": 47
                     }
                  ]
               },
               "por_partido": {
                  "buckets": [
                     {
                        "key": "PT do B",
                        "doc_count": 55
                     },
                     {
                        "key": "PSB",
                        "doc_count": 37
                     }
                  ]
               },
               "estatisticas_de_numero": {
                  "count": 698,
                  "min": 1000,
                  "max": 9090,
                  "avg": 3420.3409742120343,
                  "sum": 2387398
               }
            }
         ]
      }
   }
}

Conclusão

Neste post vimos como o ElasticSearch pode ser uma ferramenta que vai muito além de busca textual. A nova funcionalidade de agregação de dados permite realização de consultas ad-hoc aos dados, possibilitando a exploração de grande bases de dados com pouco esforço de programação. O tipos de agregações disponíveis no ElasticSearch vão muito além das utilizadas nesse post. Existem agregações por distância geográfica, faixas de IPv4, percentis, histogramas, faixas de data, e etc, que ainda podem ser combinadas com os diversos tipos de queries e filtros disponíveis. Veja lá na documentação a lista de agregações disponíveis.

Se alguma coisa tiver ficado muito confusa, deixem as dúvidas nos comentários que vou tentando esclarecer e melhorar o post.

Até a próxima! 🙂

Using Dolphin/KDE to manage Git repositories (or other VCS)

Dolphin is the default file manager of KDE.  Dolphin is very extensible and allows you to install plugins that provides a lot of new features. With the KDE SDK plugins for Dolphin its possible to manage a Git repository directly from the Dolphin GUI. In KDE 4.8, the plugin also works with Bazaar, Subversion (SVN), and Mercurial.

To do that in Linux Mint, Ubuntu and similar distros, you need to install the package kdesdk-dolphin-plugins by running:

sudo apt-get install kdesdk-dolphin-plugins

After that, you need to enable the feature:

  1. Open Dolphin and go to menu Settings > Configure Dolphin
  2. Click in the tab Services;
  3. Check the option Git (and any other Version Control System you want to use).
  4. Confirm the changes by clicking in Apply/OK.
  5. Restart Dolphin.

After that, whenever you enter a folder that is a Git repository, Dolphin will show some info about the repository, such as files that have changed. The following figure shows an example. When you click with the right button in the folder (or files), the context menu shows you options to checkout, commit, create tags, pull and push. It may be a very useful tool even if you know how to use Git by the command line.

Options using Dolphin Git Plugin

Custom Latex Beamer Theme

I would like to share a beamer theme that I created recently. Beamer is a Latex class used to create presentations. Default beamer themes are usually full of stuff that waste the usefull space of the presentation. So, I created a custom beamer theme that is simple and clean.

If you already know latex and beamer, it’s pretty easy to use the theme. Download the file style.tex and put it on the same folder of you latex document. Then, you just need to include the file using the command “input{style.tex}” at the begining of your latex document. It should look like this:

\documentclass[t,14pt,mathserif]{beamer}
% include your packages
\input{style.tex}
\title{Presentation Title}
\author{Author Name}
\begin{document}
% your document content
\end{document}

You can download the theme on my github: https://github.com/aecio/beamer-theme. There you will also find a demo presentation and a PDF of the demo presentation generated using the theme. You can use this demo as a template for your own presentations.

Problemas de ‘Permission denied (publickey)’ no github ao tentar realizar pull e push no Windows XP

Ontem perdi um bom tempo tentando configurar um projeto no github. Estava ocorrendo uns erros de autenticação (Permission denied) ao tentar fazer pull ou push.

Depois de muitas buscas na web e não encontrar nada, descobri que o erro estava sendo causado por codificação de caracteres. O nome da minha conta de usuário no Windows era ‘Aécio’ e por algum motivo o git bash não reconhecia o acento do nome. Ao chamar o comando ssh-keygen para gerar a chave de autenticação, ao invés do git bash gerar a chave detro da pasta de usuário padrão “C:Documents and SettingsAécio.ssh”, ele estava criando outra pasta com nome “C:Documents and SettingsA’cio.ssh”. Consegui adicionar a chave gerada no github (foi aceita sem problemas). Até consegui fazer o primeiro push em um projeto, mas depois apareceram alguns erros ao tentar fazer push. Tentei gerar a chave ssh em outra pasta, mas o problema continuava.

Só depois de muito tempo consegui resolver o problema mudando o nome do usuário para outro sem ‘caracteres especiais’. Mudar o nome de Usuário no “Painel de Controle -> Contas de usuário” não adianta. Só muda o nome que aparece no menu iniciar e na inicialização. Para mudar é necessário fazer mudanças no registro do sistema e renomear a pasta de usuário manualmente. Existe alguns tutoriais sobre como fazer isso na internet.

Fica a dica pra quem se deparar com o mesmo problema. Uma dica melhor ainda é usar Linux (consegui configurar tudo sem problemas em menos de 10 minutos =).

Se você conseguiu resolver este problema de outra forma, me deixe saber.

Alguns links que podem ajudar:

Criação de chaves SSH (Generating SSH keys)

Resolvendo problemas de autenticação com SSH (Addressing authentication problems with SSH)