Migrando do SQL para o Pandas – Parte 2

No primeiro post sobre esse assunto, falei sobre como implementar no Pandas o SELECT e também as clausula WHERE. Para acessar a primeira parte é só clicar aqui.

Agora vou mostrar como tratar funções de agrupamento, junções e uniões.

Group By

O GROUP BY é utilizado para separar os registros selecionados em grupos específicos. Com os agrupamentos criados, podemos aplicar as funções de agregação, que podem ser contar os registros do grupo, somar valores, valores máximos e mínimos e etc.

Vamos a um exemplo

No código abaixo, contamos os registros agrupados pela coluna sex.

SELECT sex, count(*)
FROM tips
GROUP BY sex;
/*
Female     87
Male      157
*/ 

O equivalente no Pandas pode ser:

In [18]: tips.groupby('sex').size()
Out[18]: 
sex
Female     87
Male      157
dtype: int64

Perceba que no pandas usamos size() e não count(). Isso acontece porque count() é aplicado a cada coluna, retornando o número de registros não nulos de cada uma.

Também é possível a busca de várias agregações em uma única instrução. Nesse caso, devemos utilizar a função agg() e passar um dicionário com as colunas e a função desejada. Bem de boa:

SELECT day, AVG(tip), COUNT(*)
FROM tips
GROUP BY day;
/*
Fri   2.734737   19
Sat   2.993103   87
Sun   3.255132   76
Thur  2.771452   62
*/
In [21]: tips.groupby('day').agg({'tip': np.mean, 'day': np.size})
Out[21]: 
           tip  day
day                
Fri   2.734737   19
Sat   2.993103   87
Sun   3.255132   76
Thur  2.771452   62

Inner Join

Utilizadíssimo nos selects, o inner join é um tipo de junção que busca apenas os dados que existem nas duas tabelas juntadas. Vamos considerar os seguintes DataFrames como exemplo:

In [23]: df1 = pd.DataFrame({'key': ['A', 'B', 'C', 'D'],
   ....:                     'value': np.random.randn(4)})
   ....: 

In [24]: df2 = pd.DataFrame({'key': ['B', 'D', 'D', 'E'],
   ....:                     'value': np.random.randn(4)})
   ....: 

Assumimos que temos as mesmas tabelas na base com a mesma estrutura

SELECT *
FROM df1
INNER JOIN df2
  ON df1.key = df2.key;
# merge performs an INNER JOIN by default
In [25]: pd.merge(df1, df2, on='key')
Out[25]: 
  key   value_x   value_y
0   B -0.282863  1.212112
1   D -1.135632 -0.173215
2   D -1.135632  0.119209

O método merge()permite também que o join seja feito por colunas com nomes diferentes:

[in] df1 = pd.DataFrame({'fruta': ['maçã', 'laranja', 'pera', 'pêssego'],
                    'value': [1, 2, 3, 5]})
[in] df2 = pd.DataFrame({'alimento': ['maçã', 'pão', 'pera', 'pêssego'],
                    'value': [5, 6, 7, 8]})
[in] df1.merge(df2, left_on='fruta', right_on='alimento', 
                        suffixes=('_fruta', '_alimento'))
  
[out]
        fruta	value_fruta	alimento	value_alimento
0	maçã	1	        maçã	        5
1	pera	3	        pera	        7
2	pêssego	5	        pêssego	        8

Left (Outer) Join

O left join traz todos os registros da primeira tabela declarada (da esquerda) e se houverem dados tabela relacionada também serão projetados. No exemplo, utilizo o mesmo data frame do inner join. No pandas, o tipo de junção é passada no parâmetro how do merge().

-- mostra todos os registros de df1
SELECT *
FROM df1
LEFT OUTER JOIN df2
  ON df1.key = df2.key;
# mostra todos os registros de df1
In [28]: pd.merge(df1, df2, on='key', how='left')
Out[28]: 
  key   value_x   value_y
0   A  0.469112       NaN
1   B -0.282863  1.212112
2   C -1.509059       NaN
3   D -1.135632 -0.173215
4   D -1.135632  0.119209

Union

No pandas temos o mesmo resultado usando a função concat().

In [31]: df1 = pd.DataFrame({'city': ['Chicago', 'San Francisco', 'New York City'],
   ....:                     'rank': range(1, 4)})
   ....: 

In [32]: df2 = pd.DataFrame({'city': ['Chicago', 'Boston', 'Los Angeles'],
   ....:                     'rank': [1, 4, 5]})
SELECT city, rank
FROM df1
UNION ALL
SELECT city, rank
FROM df2;
/*
         city  rank
      Chicago     1
San Francisco     2
New York City     3
      Chicago     1
       Boston     4
  Los Angeles     5
*/
In [33]: pd.concat([df1, df2])
Out[33]: 
            city  rank
0        Chicago     1
1  San Francisco     2
2  New York City     3
0        Chicago     1
1         Boston     4
2    Los Angeles     5

Em SQL, UNION é similar do UNION ALL, porém o UNION remove os registros duplicados.

SELECT city, rank
FROM df1
UNION
SELECT city, rank
FROM df2;
-- notice that there is only one Chicago record this time
/*
         city  rank
      Chicago     1
San Francisco     2
New York City     3
       Boston     4
  Los Angeles     5
*/

No Pandas, o método drop_duplicates() pode ser usado em conjunto com o concat() para repetir esse comportado.

In [34]: pd.concat([df1, df2]).drop_duplicates()
Out[34]: 
            city  rank
0        Chicago     1
1  San Francisco     2
2  New York City     3
1         Boston     4
2    Los Angeles     5

Por hoje era isso…

Trouxe apenas um resumo do que me ajudou bastante a pensar mais como pandas e menos sql. Espero que possa ajudar mais alguém. Vou deixar o link da documentação do pandas, especificamente dessa parte, com vários outros exemplos.

https://pandas.pydata.org/docs/getting_started/comparison/comparison_with_sql.html?highlight=database

Um abraço e até a próxima 😊