Filtros e a criação de colunas no pandas podem ser mais simples.
Tags: pandas, python, datascience
Published: 2020-07-15 02:28:17


Todo mundo que trabalha usando o pandas às vezes se depara com uma linha de código com uma centena de caracteres, que na prática só faz uma série de filtros aninhados com condições de “e” e “ou” quase que infinitas.

```python
df[
    (df['Classificação'] != "Normal") 
    & 
    (df['Altura'] < 1.70)
    & 
    (df['Peso'] > 60)
]
```


O Grande problema disso é que é muito fácil se perder nesses filtros com indexação booleana e é bem trabalhoso para dar manutenção em caso de bugs ou se houver a necessidade de complementar os filtros.

Para criar colunas em função de outras, geralmente usamos uma série de caracteres repetidos e isso nos soa um alerta de que talvez haja ou deveria haver uma forma mais simples de fazer isso.

```python
df2['IMC'] = df2['Peso'] / df2['Altura'] ** 2 
```
Então eu vou demonstrar a melhor forma que eu encontrei para fazer essas operações ficarem mais legíveis:


```python
import pandas as pd

# Criando dois dataframes para os exemplos:
df = pd.DataFrame({"Altura":[1.75, 1.64, 1.50, 1.45, 1.90],
                    "Peso": [90,85,70,60,75]})

df2 = pd.DataFrame({"Altura":[1.75, 1.64, 1.50, 1.45, 1.90],
                     "Peso": [90,85,70,60,75]})

# Fazendo um cálculo com base nos valores de duas colunas:
df2['IMC'] = df2['Peso'] / df2['Altura'] ** 2
```

|    |   Altura |   Peso |     IMC |
|---:|---------:|-------:|--------:|
|  0 |     1.75 |     90 | 29.3878 |
|  1 |     1.64 |     85 | 31.6032 |
|  2 |     1.5  |     70 | 31.1111 |
|  3 |     1.45 |     60 | 28.5375 |
|  4 |     1.9  |     75 | 20.7756 |

O Jeito mais legível é com o método eval. Basta montar a expressão como uma string simples:

````python
df.eval("IMC = Peso / Altura ** 2", inplace=True)
``` 
|    |   Altura |   Peso |     IMC |
|---:|---------:|-------:|--------:|
|  0 |     1.75 |     90 | 29.3878 |
|  1 |     1.64 |     85 | 31.6032 |
|  2 |     1.5  |     70 | 31.1111 |
|  3 |     1.45 |     60 | 28.5375 |
|  4 |     1.9  |     75 | 20.7756 |

Também podemos criar colunas usando funções, selecionando a coluna e com o metodo apply passar o nome da função como parâmetro. Geralmente é a minha última alternativa e só compensa em casos envolvendo muitos "ifs".

```python
def classificacao_imc(imc):
    if imc < 18.5:
        return "Magro"
    elif imc >= 18.5 and imc <= 24.9:
        return "Normal"
    elif imc > 24.9 and imc < 29.9:
        return "Sobrepeso"
    elif imc >= 30.0 and imc  < 39.9:
        return "Obesidade"
    elif imc >= 40:
        return "Obesidade Grave"

# e então:
df["Classificação"] = df['IMC'].apply(classificacao_imc)

print(df)
```
|    |   Altura |   Peso |     IMC | Classificação   |
|---:|---------:|-------:|--------:|:----------------|
|  0 |     1.75 |     90 | 29.3878 | Sobrepeso       |
|  1 |     1.64 |     85 | 31.6032 | Obesidade       |
|  2 |     1.5  |     70 | 31.1111 | Obesidade       |
|  3 |     1.45 |     60 | 28.5375 | Sobrepeso       |
|  4 |     1.9  |     75 | 20.7756 | Normal          |
````python
df2["Classificação"] = df2['IMC'].apply(classificacao_imc)

df.eval("Peso_ideal = 24.9 * Altura ** 2", inplace=True)
df2.eval("Peso_ideal = 24.9 * Altura ** 2", inplace=True)
print(df)
```
|    |   Altura |   Peso |     IMC | Classificação   |   Peso_ideal |
|---:|---------:|-------:|--------:|:----------------|-------------:|
|  0 |     1.75 |     90 | 29.3878 | Sobrepeso       |      76.2562 |
|  1 |     1.64 |     85 | 31.6032 | Obesidade       |      66.971  |
|  2 |     1.5  |     70 | 31.1111 | Obesidade       |      56.025  |
|  3 |     1.45 |     60 | 28.5375 | Sobrepeso       |      52.3522 |
|  4 |     1.9  |     75 | 20.7756 | Normal          |      89.889  |

Filtro do jeito clássico (indexação booleana):

````python
df[df['Classificação'] != 'Normal']
```
|    |   Altura |   Peso |     IMC | Classificação   |   Peso_ideal |
|---:|---------:|-------:|--------:|:----------------|-------------:|
|  0 |     1.75 |     90 | 29.3878 | Sobrepeso       |      76.2562 |
|  1 |     1.64 |     85 | 31.6032 | Obesidade       |      66.971  |
|  2 |     1.5  |     70 | 31.1111 | Obesidade       |      56.025  |
|  3 |     1.45 |     60 | 28.5375 | Sobrepeso       |      52.3522 |

#### Filtro do jeito mais legível, com o método query.
Você só precisa passar uma string com os nomes de colunas e comparar com strings (coloque '  ao redor), números ou variáveis. Para comparar com variáveis, basta usar @nome_da_variável.

```python
df2.query("Classificação != 'Normal'")
```
|    |   Altura |   Peso |     IMC | Classificação   |   Peso_ideal |
|---:|---------:|-------:|--------:|:----------------|-------------:|
|  0 |     1.75 |     90 | 29.3878 | Sobrepeso       |      76.2562 |
|  1 |     1.64 |     85 | 31.6032 | Obesidade       |      66.971  |
|  2 |     1.5  |     70 | 31.1111 | Obesidade       |      56.025  |
|  3 |     1.45 |     60 | 28.5375 | Sobrepeso       |      52.3522 |

```python
variavel = 'Obesidade'
df2.query("Classificação == @variavel")
```

|    |   Altura |   Peso |     IMC | Classificação   |   Peso_ideal |
|---:|---------:|-------:|--------:|:----------------|-------------:|
|  1 |     1.64 |     85 | 31.6032 | Obesidade       |       66.971 |
|  2 |     1.5  |     70 | 31.1111 | Obesidade       |       56.025 |

Filtro aninhado e com indexação booleana:
```python
df[
    (df['Classificação'] != "Normal") 
    & 
    (df['Altura'] < 1.70)
    & 
    (df['Peso'] > 60)
]
```
|    |   Altura |   Peso |     IMC | Classificação   |   Peso_ideal |
|---:|---------:|-------:|--------:|:----------------|-------------:|
|  1 |     1.64 |     85 | 31.6032 | Obesidade       |       66.971 |
|  2 |     1.5  |     70 | 31.1111 | Obesidade       |       56.025 |

Com o método query:

```python
df.query("Classificação != 'Normal'and Altura < 1.70 and Peso > 60")
```
|    |   Altura |   Peso |     IMC | Classificação   |   Peso_ideal |
|---:|---------:|-------:|--------:|:----------------|-------------:|
|  1 |     1.64 |     85 | 31.6032 | Obesidade       |       66.971 |
|  2 |     1.5  |     70 | 31.1111 | Obesidade       |       56.025 |

Você também pode fazer:
```python
query_string = (
    "Classificação != 'Normal'"
    "and Altura < 1.70"
    "and Peso > 60"
)

df.query(query_string)
```
|    |   Altura |   Peso |     IMC | Classificação   |   Peso_ideal |
|---:|---------:|-------:|--------:|:----------------|-------------:|
|  1 |     1.64 |     85 | 31.6032 | Obesidade       |       66.971 |
|  2 |     1.5  |     70 | 31.1111 | Obesidade       |       56.025 |

Por fim:

```python
query_string = (
    "Classificação != 'Normal'"
    "and Altura < 1.70"
    "and Peso > 60"
)
df.query(query_string)
```
É mais legível e organizado do que:

```python
df[
    (df['Classificação'] != "Normal") 
    & 
    (df['Altura'] < 1.70)
    & 
    (df['Peso'] > 60)
]
```


Não?