Executando verificação de segurança...
2

[Pandas] Instruções separadas ou métodos encadeados?

Opa! Já faz pouco mais de 2 anos que estudo Pandas com Python e constantemente venho procurando formas de deixar o código mais legível...

Um padrão legal que já uso faz um bom tempo e gostaria de compartilhar com vocês é o "chained methods"! Vale lembrar que aqui não existe o errado e nem o certo.

Primeiramente vamos definir alguns dataframes abstratos para exemplificar:

import pandas as pd

# Definindo dataframes
df1 = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
df2 = pd.DataFrame({'C': [4, 5, 6], 'D': [10, 11, 12]})

Método tradicional, com instruções isoladas (Como eu costumava fazer):

# Retorna do df1 onde A é maior que 1
df = df1.query('A > 1')

# Adiciona colunas de df2 onde as linhas de "B" e "C" correspondem
df = df.merge(df2, left_on=['B'], right_on=['C'])

# Adiciona uma nova coluna "E" que é a soma de "A" e "D"
df = df.assign(
    E=df['A'] + df['D']
)

# Agrupa por "C" e soma "E" para cada grupo
df = df.groupby('C').agg(dict(E=sum))

# Renomeia coluna "E" para "F"
df = df.rename(columns={'E': 'F'})

Chained Methods (Como eu prefiro fazer agora):

df = (
    df1
    # Retorna do df1 onde A é maior que 1
    .query('A > 1')
    # Adiciona colunas de df2 onde as linhas de "B" e "C" correspondem
    .merge(
        df2, 
        left_on=['B'], right_on=['C']
    )
    # Adiciona uma nova coluna "E" que é a soma de "A" e "D"
    .assign(
        E=lambda x: x['A'] + x['D']
    )
    # Agrupa por "C" e soma "E" para cada grupo
    .groupby('C')
    .agg(dict(
        E=sum
    ))
    # Renomeia coluna "E" para "F"
    .rename(columns=dict(
        E="F"
    ))
)

Ainda não masterizei 100% o padrão, mas uma coisa que percebi é que isso pode facilmente virar um pesadelo caso você faça tudo em uma cadeia só!

Quando estamos trabalhando com transformações de dados, nada é muito fácil, mas o ideal é trabalhar com várias cadeias, cada cadeia com poucas responsabilidades e responsabilidades que não sejam muito distantes uma da outra.

Outra coisa a ser levada em consideração nesse padrão é o aumento na dificuldade em debugar o código já que você acaba perdendo a possibilidade de executar a transformação dos dados passo a passo.

Para ajudar nesse problema, podemos usar o modo interativo do VS Code.

Por exemplo, digamos que eu queira ver como fica o dataframe após adicionar a coluna "E":

  1. Copie instrução começando na abertura do parênteses até onde queremos parar.
  2. Cole no modo interativo.
  3. Feche o bloco com o parênteses que falta.

Eaí turma, o que acharam do padrão proposto? Não conheciam, conhecem, usam ou preferem não usar?

Carregando publicação patrocinada...
1

Bom dia WilianZilv!

Muito legal essa maneira que você colocou no exemplo!

No R é quase que uma boa prática fazer isso com o pacote dplyr, a gente chama isso de pipe, mas nunca tinha parado pra pensar dessa maneira em Python e ainda mais com os comentários, obrigado!

1

Eu conheço e prefiro usar a retribuição da variável, as vezes até salvando mais variáveis!

Sei que não é uma boa prática de memória, mas como não trabalhei com grandes bases e foi com objetivo didático, dessa forma é mais compreensível o que ocorre em cada passo

1

O que você disse sobre memória é real!

Aqui na empresa eu já lidei com um projeto em que um dataframe ocupava mais ou menos 8GB, então eu não podia manter outras variações dele em outras variáveis porque atingia o limite de RAM.

1

Nesse caso, Wilian, não seria melhor usar outro manipulador? Não lembro os nomes de imediato, mas sei que tem outras opções mais eficientes com Df's maiores!

2

Acredito que sim. Acabei testando alguns, mas pra migrar o projeto pra outra biblioteca, mesmo que seja muito parecida com o pandas, demandaria muito tempo, tanto pra mim quanto pro pessoal aqui do trabalho que tem um conhecimento básico.
Então com alguns ajustes consegui salvar bastante memória em momentos de stress do processo e ficou por aí. Mas não vou descartar a possibilidade de usar outras libs no futuro caso apareça algum projeto ainda maior.

E ah, eu lembro de ter testado o Dask!

1
1
2

só é necessário se atentar aos limites reais das colunas, de acordo com a quantidade de bits...

Por exemplo, não faz sentido usar float para idade, pode usar uint8 que tem 8 bits de limite, que é mais que suficiente!

O método é df.col=df.col.astype("uint8")

Pode-se usar loc tbm, ou como preferir

1

DeividBraian, na verdade não é uma má prática criar/reatribuiar variáveis, pelo contrário, é a forma padrão que a interface do pandas sugere.

Isso acontece porque o pandas preza pela imutabilidade dos dados, isto é, ao realizar uma operação sobre um dataframe, por padrão o pandas cria um novo dado, ao invés de alterar o dado antigo. Isso é considerado uma boa prática pois evite alguns erros comuns.

Muitas linguagens funcionais utilizam este padrão, Haskell e Rust são exemplos.

2

Acho que vale a pena utilizar esse modelo Pipeline(O pandas tem uma função pipeline também, não é? 🤔)
quando o projeto já está finalizado e não será necessário modificar. Mas na realidade nunca se sabe quando será necessário modificar, né? 🤷‍♂️

1

É bem difícil dizer o que o pandas sugere, como o @DeividBraian disse, o pandas também trabalha com pipelines.

Então o que eu consigo enxergar são 3 padrões até agora. O jeito pythonico, chained methods e pipes.

E eu acredito que devemos usá-los dependendo da situação.

1

É bem difícil dizer o que o pandas sugere

Eu quis dizer que o pandas sugere o uso de imutabilidade, por que digo isso? porque as três formas que você citou "o jeito pythonico, chained methods e pipes" não alteram dados, eles criam dados novos. Para alterar, você precisa explicitamente indicar ao pandas que você quer fazer isso atrávez do parâmetro inplace.

Ou seja, por padrão o pandas usa imutabilidade, pra mim, isso é uma clara sugestão. Entende?

1
1