
Pandas e polares: é hora de migrar? definitivamente talvez 🤔
Onde estamos? 🗺️
Se você é cientista de dados, engenheiro de aprendizado de máquina ou está envolvido em um projeto orientado por dados, é muito provável que tenha usado pandas para limpar, higienizar, filtrar e preparar os dados a serem usados como entrada para os métodos, modelos ou algoritmos escolhidos. Nessa etapa inicial, que geralmente ocupa uma fração não trivial do cronograma total do projeto, você não está executando instruções que consomem muita CPU, mas a execução não é tão rápida quanto você gostaria. Além disso, se o conjunto de dados em que você está trabalhando for grande, as limitações de hardware, como memória, são iminentes. Essas limitações são resultado de duas coisas: o Python geralmente é incrível (mas talvez o gerenciamento de simultaneidade de threads não seja o auge) e o Pandas tem uma execução rápida (isso significa que uma instrução está sendo lida e executada no momento, falaremos sobre isso mais tarde). É aqui que entram os polares! Construído em Rust, é muito mais rápido do que os bons e velhos Pandas. Ao contrário do Python, o Rust é ótimo em simultaneidade e, com o divisor de águas que é a execução lenta, muitas otimizações podem ser aplicadas antes da execução.
Uma nova alternativa chegou! 🆕
Para ver o que está por trás do capô, testamos as duas bibliotecas, explorando os tempos necessários para suas operações no conjunto de dados de anúncios do AirBnB para NYC 🗽. Em nosso cenário de teste, a melhoria no trabalho com o Pandas foi de cerca de um fator de 5 🤯 e pode ser ainda maior dependendo do contexto (dados envolvidos), tamanho e volumes. Recriamos uma atribuição regular de pré-processamento de uma fonte de dados de um projeto, carregando o conjunto na memória, aplicando filtragem, eliminando colunas não utilizadas do quadro, realizando algumas operações matemáticas, unindo outros conjuntos de dados relacionados e terminando por classificar o resultado final, comparando as APIs das bibliotecas em cada caso (o repositório pode ser encontrado e baixado) aqui). Nas seções abaixo, você pode ver como o tempo de execução é executado por operação.
Vamos começar 💪
Examinaremos um conjunto de operações com essas especificações de configuração e conjuntos de dados:
- Memória do sistema: 8GB
- Processador central: Mac M1 (MacBook Pro)
- Python = 3.9.13
- Pandas = 1.4.3
- Polares = 0,16.11
- listagens (41533, 75)
- revisões (1088372, 6)
Leia CSV
A primeira coisa a fazer para disponibilizar os dados é carregá-los na memória como uma variável, isso é feito por Pandas: pd.read_csv (nome do caminho do arquivo, pouca memória=FALSE) Polares (ansiosos): polars.read_csv (dataframe_nome do arquivo) Polares (preguiçosos): polars.scan_csv (dataframe_filename, ignore_errors=True) e os horários para recuperá-los são: Arquivo fonte: Polars (s) Pandas (s) Ansioso, preguiçoso listagens 0.20 2.00e-03 1,02 avaliações 6.34e-01 3.18e-04 4.11 Aqui podemos ver que em ambos os dataframes, Polars é muito mais rápido em qualquer um de seus sabores.
Filtro (por valor de colunas)
Em seguida, passamos a filtrar os dados com alguns valores fixos. Especificamente, decidimos nos concentrar em alguns vizinhos de Manhattan ('East Village', 'Greenpoint', 'Upper East Side', 'Lower East Side', 'Upper West Side'), excelentes anfitriões -superhosts- e aluguéis que valem para todo o lugar. O código executado em cada caso é: Pandas filtered_pd = listings_dataset_pd [(listings_dataset_pd ['neighborhood'] .isin (desirable_neighbor)) & (listings_dataset_pd ['host_is_superhost'] == 't') & (listings_dataset_pd ['room_type'] == 'Casa inteira'] == 'Casa inteira' ')] Polares (ansiosos): filtered_pl_eager = listings_dataset_pl_eager.filter ((polars.col ('neighborhood') .is_in (desirable_neighbor)) & (polars.col ('host_is_superhost') == 't') & (polars.col ('room_type') == 'Casa inteira')) Polares (preguiçosos): filtered_pl_lazy = (listings_dataset_pl_lazy .filter (polars.col ('bairhood_cleaned') .is_in (desirable_neighbor)) .filter (polars.col ('host_is_superhost') == 't') .filter (polars.col ('room_type') == 'Casa inteira')) Nota: verifique se a sintaxe não é exatamente a mesma, mas semelhante o suficiente para ser entendida facilmente. Os tempos obtidos para cada abordagem são os mostrados abaixo: Arquivo fonte: Polars (s) Pandas (s) Ansioso, preguiçoso listagens 7.60e-05 1.9e-02 2.8e-02 Mais uma vez, polars foi materialmente melhor em como filtrar as colunas indesejadas. Lembre-se de que nosso escopo é curto e estamos evitando grandes números, apenas comparando magnitudes.
Ordenar
A classificação talvez seja um recurso mais desejado pelos cientistas de dados do que pelos engenheiros de aprendizado de máquina ou engenheiros de dados, no que diz respeito à captura de alguns valores discrepantes ou insights. Além disso, classificamos os dados com: Pandas: sorted_pd = filtreted_pd.sort_values (by= [nome_coluna]) Polares (ansiosos): sorted_pl_eager = filtered_pl_eager.sort ([nome_coluna]) Polares (preguiçosos): sorted_pl_lazy filtered_pl_lazy.sort ([column_name]) e os horários recuperados são: Conjunto de dados POLARS (s) Pandas (s) Ansioso, preguiçoso anúncios 4.00e-03 1.79e-05 8.80e-04
Remover (descartar) colunas
Outra manipulação que queremos tentar é remover, devido à vastidão dos dados que são carregados em nosso pipeline, notebooks ou ouvintes de eventos. Escolhemos algumas colunas não essenciais, como ('source', 'name', 'description', 'picture_url', 'host_location', 'host_about'). Dessa forma, a partir de agora, os conjuntos de dados carregados ficam mais leves. Os códigos para fazer isso são: Pandas sorted_pd.drop (columns = columns_to_remove, inplace=True) Nota: use inplace quando possível, é um pouco mais eficiente em relação à memória. Polares (ansiosos) para coluna em not_wanted_columns: sorted_pl_eager.drop_in_place (coluna) Polares (preguiçosos) sorted_pl_lazy = sorted_pl_lazy.drop (columns) Os tempos de cada execução foram: Conjuntos de dados Polars (s) Pandas (s) Ansioso, preguiçoso anúncios 2.67e-04 2.81e-05 2.21e-03
Unir
Com o objetivo de criar um conjunto de dados mais rico, procuramos juntar os dois que estão sendo alocados na memória, listagens e avaliações. De uma coluna mútua 'id_do_lista', que precisa de um pouco de pré-processamento nas listagens, temos que extrair o id de 'listing_url' que é feito com mapa, lambda e um pouco de regex -verifique o código-fonte para obter detalhes-. Além disso, até agora, estamos empilhando as operações na pista lenta, adiando sua execução real; na verdade, são tipos de dados diferentes, polares.Quadro preguiçoso não é o mesmo que polares.Quadro de dados (e não é o mesmo que pandas.DataFrame -o que pode ser um pouco confuso no começo-). Essa abordagem garante o melhor dos Polars, otimizando as transformações em um conjunto de dados, que a projeção é um grupo de etapas ordenadas mescladas, se possível, para um melhor desempenho. Aqui está a nossa: [caption id="attachment_2284" align="aligncenter” width="1024"]

Gráfico representando transformações otimizadas em um conjunto de dados [/caption] Transformação projetada para cada conjunto de dados ao aplicar transformações empilhadas, listado à esquerda e revisões à direita. Esses gráficos foram gerados com o método show_graph (). Acima, você pode ver que a vantagem da abordagem preguiçosa é alcançada se você acabar com alguns blocos, onde as operações são combinadas de uma para outra, como é o caso da listagem, mas não das avaliações. Os códigos para fazer isso são: Pandas sorted_pd.join (avaliações, on = joining_column_name, lsuffix='_left', rsuffix='_right') Polares sorted_pd.join (reviews, on = joining_column_name, lsuffix='_left', rsuffix='_right') Os horários de cada execução são: Conjunto de dados
Polares (s)
Pandas (s) Ansioso, preguiçoso
ambos
5.50e-02 9.36e-01 3.40e-02 Pela primeira vez nesta exploração, os Pandas venceram os Polars. Isso ocorre devido às etapas pré-necessárias para ingressar, para a execução lenta, incluindo coletam (como é chamado na biblioteca) as operações de dados, todas elas!
Embrulhar
Esta etapa é “obrigatória” se a adoção do Polars for gradual (o que recomendamos - falaremos mais sobre isso mais tarde). Os tipos de dados Polars e Pandas podem ser convertidos para frente e para trás entre eles, então aqui faremos uma pequena conversão de Polars para Pandas, para não interferir em outras tarefas já em desenvolvimento ou em aplicativos avançados. Usar um único comando de linha é suficiente! O código para encerrar a variável é: migrated_dataset = joined_pl.to_pandas () A mudança do tipo de dados consome o tempo abaixo: Conjunto (s) de dados (s) - Ansioso conjunto unido 3.88e-02
Tempos gerais de execução ⌚
Resumindo todos os tempos envolvidos em todas as operações (filtrar, classificar, remover, unir e agrupar), podemos ver duas coisas claras: a primeira é sobre a grande diferença ou ganho entre a execução lenta e a rápida, mas não se esqueça de que nosso escopo era pequeno e, se a transformação dos dados for muito maior, essa distância aumentará. O segundo é o incrível fator de melhoria, somos capazes de obter o mesmo resultado x5 mais rápido! Polares (s) Pandas 🥉 Preguiçosos 🥇 Ansiosos 🥈 0,93 0,95 5.19 Além disso, se o validarmos de uma perspectiva mais granular, com base em caso a caso, obteremos: Operação | Biblioteca Polars EagerPolars LazyPandas Leia CSV 0.83 2.32e-03 5.13 2º 1º 3º
Filtro
7.60e-05 1.90e-02 2.80e-02 1º 2º 3º Ordenar 4.00e-03 1.79e-05 8.80e-04 3º 1º 2º Remover col 2.67e-04 2.81e-05 2.21e-03 2º 1º 3º
Unir
5.50e-02 9.36e-01 3.40e-02 1st 3rd 2nd Wrap 3.88e-02 - - Aviso: esta operação está fora de comparação por ter sido executada por uma das alternativas. Atribuindo pontos com base na classificação de cada uma das tarefas acima (3 para a 1ª, 2 para a 2ª e 1 para a 3ª), obtemos 11 pontos para Polars Eager, 12 para Polars Lazy e 7 para Pandas, o que também reflete os benefícios em termos de tempo de execução de trabalhar com qualquer um dos sabores Polars em vez de Pandas.
Como devemos proceder? 🤔
Como pudemos ver nesta exploração, O Polars parece ser uma ótima alternativa para economizar (muito) tempo nas tarefas do projeto devido à sua implementação e às otimizações disponíveis. Mas é seguro transferir todos os projetos, pipelines, notebooks ou ativos atuais que usam pandas para a Polars? Não parece uma ideia inteligente fazer isso tão repentinamente. No entanto, com uma abordagem iterativa, trabalhar com polares em novos projetos (talvez os pequenos no início) usando o método wrapper permitirá que a equipe ganhe força sobre a biblioteca e fique satisfeita com seu impacto, parece um investimento que vale a pena. Caso esteja interessado em uma comparação mais exaustiva, você pode verificar isso site github onde muitas ferramentas (todas ótimas) são comparadas.
Um pequeno bônus
Não hesite em explorar outros blogs nos quais trabalhamos sobre dados e ar em geral.
