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 😊

Migrando do SQL para o Pandas

Eu acho a documentação do Pandas sensacional e tem um item que ajudou muito em converter meu raciocínio forjado no SQL para os objetos do Pandas.

Estou falando da página “Comparison with other tools”, que além do SQL mostra exemplos de R, SAS e Stata de forma simples e direta.

Vou mostrar alguns exemplos, mas recomendo a leitura diretamente na documentação para uma visão mais abrangente.

Select

Com certeza, a primeira palavra que digitamos quando começamos no SQL. Com este comando de DML, buscamos selecionar informações da base de dados. Podemos informar do SGDB quais as colunas a serem retornadas ou trazer todas usando o popular select * from table. No exemplo abaixo, buscamos algumas colunas da tabela tips, limitando a 5 linhas de retorno:

SELECT total_bill, tip, smoker, time
FROM tips
LIMIT 5;

No Pandas o mesmo resultado é obtido passa uma lista de nomes de colunas para nosso DataFrame:

In [6]: tips[['total_bill', 'tip', 'smoker', 'time']].head(5)
Out[6]: 
   total_bill   tip smoker    time
0       16.99  1.01     No  Dinner
1       10.34  1.66     No  Dinner
2       21.01  3.50     No  Dinner
3       23.68  3.31     No  Dinner
4       24.59  3.61     No  Dinner

Where

As vezes esquecido nos updates e deletes (brincadeira!), a cláusula WHERE no SQL especifica que uma instrução deve afetar apenas as linhas que atendem aos critérios especificados. No exemplo abaixo, queremos ver todas as linhas da tabela tips onde o time for igual a dinner.

SELECT *
FROM tips
WHERE time = 'Dinner'
LIMIT 5;

No Pandas, o DataFrame pode ser filtrado de várias maneiras. A mais intuitiva é usando a indexação booleana, que não vem ao caso agora. Vamos ao código:

In [8]: tips[tips['time'] == 'Dinner'].head(5)
Out[8]: 
   total_bill   tip     sex smoker  day    time  size
0       16.99  1.01  Female     No  Sun  Dinner     2
1       10.34  1.66    Male     No  Sun  Dinner     3
2       21.01  3.50    Male     No  Sun  Dinner     3
3       23.68  3.31    Male     No  Sun  Dinner     2
4       24.59  3.61  Female     No  Sun  Dinner     4

Esse post tá ficando maior do que eu imaginei. Vou quebra-lo em algumas outras partes para não cansar 😁

Em seguida, vou trazer exemplos de GROUP BY, JOIN, UNION, funções de agregação, UPDATE e DELETE.

Até breve!