Polars e a desmistificação do ensino de Data Science com Pandas
Introdução
Sabe-se que, quanto a organização e representação gráfica de dados de forma tabular, Pandas se destaca, sendo a API mais popular, em conjunto com Python. Entretanto, tamanha popularidade talvez provoque a famosa visão em túnel, dificultando com que os programadores experientes, e principalmente os que estão iniciando, se atentem a tecnologias periféricas, que, mesmo em desenvolvimento, apresentam ganhos significativos em performance - essencial no desenvolvimento em Python.
Por vários motivos, Python não apresenta uma boa performance em comparação com outras linguagens, como, por exemplo, a queridinha do momento: Rust.
Inspirado nisto, trago-lhes a possível solução para este problema: Polars API, a irmã urso adotiva da Pandas API.
Este artigo tem como intuito:
- Explicar a finalidade das APIs
- Apontar as diferenças que acarretam no ganho de performance usando Polars
- Apresentar comparações entre ambos os pacote
1 - Finalidade
Grosso modo, ambas as APIs buscam facilitar a manipulação de DataFrames (tabelas de dados), fornecendo funções, classes e métodos que simplificam os procedimentos mais realizados eu sua análise e organização.
DataFrames são visualizados como tabelas, por isso, são definidos como uma representação gráfica tabular dos dados.
Figura 1 - Tabela composta de alunos, matérias e notas
Fonte: Excel Easy
Sabendo-se da principal finalidade, que ambas as APIs compartilham, o entendimento de suas principais diferenças se torna muito mais agradável.
2 - Diferenças
Apesar de ambas partilharem da mesma funcionalidade, elas se diferem em alguns pontos cruciais. Nota-se que, na manipulação de um DataFrame, pode se exigir o agrupamento de dados, modificação de valores, o tratamento de dados inválidos - como o NaN (not a number - não numérico) - e muitas outras operações muito custosas para o sistema por serem realizadas em arquivos enormes, de bilhões de linhas.
Visando solucionar o problema, muitas empresas buscam soluções em Cloud (nuvem), como o Google Colab - ambiente interativo em Python - que pode ser utilizado online.
Figura 2 - Google Colab
Fonte: Geonetcast
Mesmo utilizando GPUs (Graphic Processing Unit - Unidade de Processamento Gráfico) poderosas, o custo das operações permanecem o mesmo, ou seja, apesar de disponibilizar-se mais recursos para serem consumidos, continuam sendo custosas, e, utilizando Pandas, permanecem sendo single-threaded, grosso modo, realizando uma tarefa por vez.
Por ser single-threaded, Pandas acaba utilizando muita memória RAM (Random Access Memory - Memória de Acesso Aleatório) do sistema, e pouco da CPU (Central Processing Unit - Unidade Central de Processamento), o que pode ocasionar no esgotamento de recursos.
Figura 4 - Crash por esgotamento
Fonte: Towards Data Science
2.1 - Multi-threading
Boa parte do ganho de performance ao se utilizar a Polars nasce de uma mudança relativamente simples: a concorrência, ou seja, realizar as instruções de maneira multi-threaded.
Figura 5 - Pandas, baixo uso de CPU, alto uso de memoria
Figura 6 - Polars, uso de CPU mais elevado.
Fonte: Canal Jeff Heaton - Youtube (editadas pelo autor)
Acima, tem-se o comparativo de uma operação de agrupamento, utilizando ambas as APIs, onde o arquivo possuia um bilhão de linhas. Pandas possui um elevado consumo de memoria. Entretanto, o mais impressionante se mostra no uso de CPU.
Utilizando Pandas, quase não se consome CPU, já na segunda, observa-se picos de uso, chegando a bater cem por cento, resultado do multi-threading.
Realizando a divisão de tarefas, o tempo de execução da operação foi cerca de dezesseis vezes menor utilizando Polars.
2.2 - Rust
Outro motivo da elevada performance se mostra na linguagem em que foi desenvolvida. Rust se consagrou como uma linguagem incrivelmente rápida, que chega a ser, em muitos casos, mais performática que a família C. Enquanto esta utiliza uma linguagem extremamente rápida, segura e versátil, Pandas se compõe em um misto de C, Cython e Python, sendo a ultima, em termos de velocidade, uma linguagem muito menos efetiva.
2.3 - Lazy API
A galinha dos ovos de ouro da Polars. Com ela, conseguimos:
- Trabalhar com datasets maiores do que a RAM disponível, utilizando streaming
- Aplicar otimizações de consulta automaticamente com o query optimizer
- Localizar erros de schema (grosso modo, relação entre as colunas e os tipos de dados nela existentes) antes do processamento
Ao contrário de sua irmã urso mais velha, Polars fornece a chamada Lazy API, que, ao contrario da Eager API, equivalente ao modo default, coleta os resultados apenas quando "necessário", evitando a execução em locais desinteressantes, pois a etapa de leitura e consulta são realizadas separadamente, resultando em ganho de performance e economia de recursos do sistema, permitindo, assim, manipular um arquivo que de outra forma esgotaria os recursos disponíveis.
As otimizações de query são realizadas automaticamente, podendo ser executadas de uma a n vezes. Como exemplo, temos: a simplificação de expressões, ordenamento de join, e a coerção de tipos. Nota-se que há oito otimizações descritas no site oficial, entretanto, não sendo limitadas a elas.
Para utilizar o modo Lazy, basta adicionar o método lazy() ao final
# Eager API
df = pl.read_csv("exemplo.csv")
# Lazy API
df = pl.read_csv("exemplo.csv").lazy()
Para se coletar os resultados, deve se chamar o método collect(). Faz-se necessário salientar que alguns métodos são implicitamente lazy, como, por exemplo, scan_csv(), portanto, o código abaixo utiliza a Lazy API:
df = pl.scan_csv('exemplo.csv')
Apesar de não precisarmos acrescentar nenhum método para utilizar a Eager API, fazendo com que a mesma seja o modo "padrão", os desenvolvedores recomendam o uso da Lazy API sempre que possível, e, ate mesmo o modo Eager a utiliza por debaixo dos panos.
Embora as diferenças acima expostas serem consideradas as mais impactantes, não são as únicas. Caso haja mais interesse sobre o assunto, sinta-se a vontade para visitar o site oficial, que possui inúmeras informações valiosas sobre a API.
4 - Benchmarks
Abaixo estarão expostos alguns benchmarks realizados, encontrados no site Towardsdatascience. Faz-se mister elucidar que todas as imagens foram retiradas do artigo em questão, não sendo nenhuma de autoria própria.
Obviamente, todas as comparações utilizaram o mesmo código ou equivalente. Para mais informações, sugiro que visite o artigo supra citado.
Conclusão
O presente artigo tem como intuito apresentar um novo caminho em relação a ciência de dados. Apesar do titulo propositalmente provocativo, cada um deve utilizar aquilo que convém, seja Pandas, Polars, ou até mesmo uma terceira opção, como Dask.
Salienta-se que a API Pandas lançou há pouco sua versão 2.0, integrando melhorias relativas a performance, inclusive utilizando a biblioteca PyArrow, utilizada pela Polars.
Assim sendo, utilizando um ou outro, não deve, jamais, blindar-se de novas tecnologias e experiências, de modo que, assim como as APIs citadas, continuemos evoluindo.