SQL
(Structured Query Language) é uma linguagem de programação de uso específico utilizada para consultar, extrair e gerenciar bancos de dados relacionais. Pandas é uma biblioteca do Python especializada para o tratamento e análise de dados estruturados, incluindo uma gama de formas de extrair dados.
Esse artigo faz uma comparação entre as consultas feitas a dataframes do pandas e as consultas SQL
, explorando similaridades e diferenças entre os dois sistemas. Ele serve para descrever as funcionalidades de busca e edição do pandas e pode ser particularmente útil para aqueles que conhecem SQL
e pretendem usar o pandas (ou vice-versa).
Para realizar os experimentos abaixo usamos o Jupyter Notebook
, um aplicativo que roda dentro de um navegador, que pode ser facilmente instalado e permite a reprodução se todo o código aqui descrito. Você pode ler mais sobre Jupyter Notebook e Linguagem de Consultas SQL nesse site.
Esse texto é baseado em parte do manual do pandas e expandido. Ele usa um conjunto de dados baixados do github
renomeado aqui para dfGorjeta
. Nomes e valores dos campos foram traduzidos para o português.
# importar as bibliotecas necessárias import pandas as pd import numpy as np url = "https://raw.github.com/pandas-dev/pandas/master/pandas/tests/io/data/csv/tips.csv" dfGorjeta = pd.read_csv(url) dfGorjeta.head()
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 |
Para efeito de testar os comandos do dataframe
vamos alterar os nomes dos campos e traduzir os conteúdos dos dados. Para descobrir quais são os valores dos campos, sem repetições, transformamos as séries em sets
, uma vez que valores de um set (conjunto) não se repetem.
print(set(dfGorjeta["sexo"])) print(set(dfGorjeta["fumante"])) print(set(dfGorjeta["dia"])) print(set(dfGorjeta["hora"]))
{‘No’, ‘Yes’}
{‘Sat’, ‘Sun’, ‘Fri’, ‘Thur’}
{‘Lunch’, ‘Dinner’}
No código seguinte alteramos os nomes de campos e traduzimos o conteúdo. A sintaxe da operação de edição do dataframe será discutida mais tarde no artigo:
# muda os nomes dos campos dfGorjeta.rename(columns={"total_bill":"valor_conta", "tip":"gorjeta", "smoker":"fumante", "sex":"sexo","day":"dia", "time":"hora","size":"pessoas"}, inplace=True) # traduzindo os valores dos campos: dfGorjeta.loc[dfGorjeta["fumante"] == "No", "fumante"] = "não" dfGorjeta.loc[dfGorjeta["fumante"] == "Yes", "fumante"] = "sim" dfGorjeta.loc[dfGorjeta["sexo"] == "Female", "sexo"] = "mulher" dfGorjeta.loc[dfGorjeta["sexo"] == "Male", "sexo"] = "homem" dfGorjeta.loc[dfGorjeta["hora"] == "Dinner", "hora"] = "jantar" dfGorjeta.loc[dfGorjeta["hora"] == "Lunch", "hora"] = "almoço" dfGorjeta.loc[dfGorjeta["dia"] == "Fri", "dia"] = "sex" dfGorjeta.loc[dfGorjeta["dia"] == "Sat", "dia"] = "sab" dfGorjeta.loc[dfGorjeta["dia"] == "Sun", "dia"] = "dom" dfGorjeta.loc[dfGorjeta["dia"] == "Thur", "dia"] = "qui" # Temos agora o seguinte dataframe dfGorjeta
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | mulher | não | dom | jantar | 2 |
1 | 10.34 | 1.66 | homem | não | dom | jantar | 3 |
2 | 21.01 | 3.50 | homem | não | dom | jantar | 3 |
3 | 23.68 | 3.31 | homem | não | dom | jantar | 2 |
4 | 24.59 | 3.61 | mulher | não | dom | jantar | 4 |
… | … | … | … | … | … | … | … |
239 | 29.03 | 5.92 | homem | não | sab | jantar | 3 |
240 | 27.18 | 2.00 | mulher | sim | sab | jantar | 2 |
241 | 22.67 | 2.00 | homem | sim | sab | jantar | 2 |
242 | 17.82 | 1.75 | homem | não | sab | jantar | 2 |
243 | 18.78 | 3.00 | mulher | não | qui | jantar | 2 |
As consultas SQL
realizadas a seguir pressupõe a existência de um banco de dados com o mesmo nome, a mesma estrutura e dados que o dataframe dfGorjetas
.
SELECT
Nas consultas SQL
as seleções são feitas com uma lista de nomes de campos que se deseja retornar, separados por vírgula, ou através do atalho *
(asterisco) para selecionar todas as colunas. No pandas a seleção de colunas é feita passando-se uma lista de nomes de campos para o DataFrame. Uma chamada ao dataframe sem uma lista de nomes de colunas resulta no retorno de todas as colunas, da mesma forma que usar *
no SQL
.
–– sql: consulta (query) usando select SELECT valor_conta, gorjeta, fumante, hora FROM dfGorjeta LIMIT 5;
# pandas: dfGorjeta[["valor_conta", "gorjeta", "hora"]].head()
valor_conta | gorjeta | hora | |
---|---|---|---|
0 | 16.99 | 1.01 | jantar |
1 | 10.34 | 1.66 | jantar |
2 | 21.01 | 3.50 | jantar |
3 | 23.68 | 3.31 | jantar |
4 | 24.59 | 3.61 | jantar |
O método head(n)
limita o retorno do dataframe às n
primeiras linhas. n = 5
é o default. Para listar as últimas linhas usamos tail(n)
. Linhas também podem ser selecionadas por chamadas ao sei indice.
# Para acessar as últimas linhas do dataframe podemos usar # dfGorjeta[["valor_conta", "gorjeta", "hora"]].tail() # selecionando linhas por meio de seu índice. dfGorjeta.iloc[[1,239,243]]
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | |
---|---|---|---|---|---|---|---|
1 | 10.34 | 1.66 | homem | não | dom | jantar | 3 |
239 | 29.03 | 5.92 | homem | não | sab | jantar | 3 |
243 | 18.78 | 3.00 | mulher | não | qui | jantar | 2 |
Os dataframes possuem a propriedade shape
que contém sua dimensionalidade. No nosso caso temos
dfGorjeta.shape
o que significa que são 244 linhas em 7 campos.
No SQL você pode retornar uma coluna resultado de um cálculo usando elementos de outras colunas. No pandas podemos usar o método assign()
para inserir uma coluna calculada:
–– sql: SELECT *, gorjeta/valor_conta*100 as percentual FROM dfGorjeta LIMIT 4;
# pandas: método assign() dfGorjeta.assign(percentual = dfGorjeta["gorjeta"] / dfGorjeta["valor_conta" *100]).head(4)
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | percentual | |
---|---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | mulher | não | dom | jantar | 2 | 5.944673 |
1 | 10.34 | 1.66 | homem | não | dom | jantar | 3 | 16.054159 |
2 | 21.01 | 3.50 | homem | não | dom | jantar | 3 | 16.658734 |
3 | 23.68 | 3.31 | homem | não | dom | jantar | 2 | 13.978041 |
Essa coluna é retornada mas não fica anexada ao dataframe. Para anexar uma coluna ao dataframe podemos atribuir o resultado do cálculo a uma nova coluna:
dfGorjeta["percentual"] = dfGorjeta["gorjeta"] / dfGorjeta["valor_conta"] * 100 print("Nessa estapa temos as colunas:\n", dfGorjeta.columns) # Vamos apagar a coluna recém criada para manter a simplicidade da tabela dfGorjeta.drop(["percentual"], axis=1, inplace=True)
Index([‘valor_conta’, ‘gorjeta’, ‘sexo’, ‘fumante’, ‘dia’, ‘hora’, ‘pessoas’, ‘percentual’],
dtype=’object’)
WHERE
Filtragem de dados em consultas SQL são feitas através da cláusula WHERE
. DataFrames podem ser filtrados de várias formas diferentes. O indexamento com valores booleanos é provavelmente o mais simples:
–– cláusula WHERE do sql SELECT * FROM dfGorjeta WHERE hora = "jantar" LIMIT 5;
# filtragem por indexamento no pandas dfGorjeta[dfGorjeta["hora"] == "jantar"].head(5)
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | mulher | não | dom | jantar | 2 |
1 | 10.34 | 1.66 | homem | não | dom | jantar | 3 |
2 | 21.01 | 3.50 | homem | não | dom | jantar | 3 |
3 | 23.68 | 3.31 | homem | não | dom | jantar | 2 |
4 | 24.59 | 3.61 | mulher | não | dom | jantar | 4 |
A consulta acima funciona da seguinte forma:
# is_jantar é uma série contendo True e False (True para jantares, False para almoços) is_jantar = dfGorjeta["hora"] == "jantar" # usamos display para exibir a contagem de falsos/verdadeiros display("Quantos jantares/almoços:", is_jantar.value_counts()) # para negar a série inteira, invertendo True ↔ False usamos ~ (til) # a linha abaixo imprime o número de almoços na tabela print("A lista contém %d almoços" % dfGorjeta[~is_jantar]["hora"].count()) # também podemos obter a lista das entradas que não correspondem a "jantar" usando # dfGorjeta[dfGorjeta["hora"] != "jantar"]
True 176
False 68
Name: hora, dtype: int64A lista contém 68 almoços
Quando essa série é passada para o dataframe apenas as linhas correspondentes à True
são retornados. A última consulta é equivalente à dfGorjeta[~is_jantar].head()
.
No SQL podemos procurar por partes de uma string com a cláusula LIKE
. No pandas transformamos o campo dfGorjeta["sexo"]
em uma string que possui o método startswith("string")
.
–– sql: SELECT TOP 2 sexo, valor_conta FROM dfGorjeta WHERE sexo LIKE 'ho%';
dfGorjeta.loc[dfGorjeta['sexo'].str.startswith('ho'),['sexo','valor_conta']].head(2)
que retorna as 2 primeiras linhas em que o campo sexo
começa com o texto “ho”.
Também podemos procurar por campos que estão incluidos em um conjunto de valores:
–– sql: SELECT * FROM dfGorjeta WHERE dia IN ('sab', 'dom');
dfGorjeta.loc[dfGorjeta['dia'].isin(["dom", "sab"])]
que retorna todas as linhas em que o campo dia
é “dom” ou “sab”.
Assim como se pode usar operadores lógicos AND
e OR
nas consultas SQL para inserir múltiplas condições, o mesmo pode ser feito com dataframes usando |
(OR) e &
(AND). Por ex., para listar as gorjetas com valor superior à $5.00 dadas em jantares:
–– SQL: múltiplas condições em WHERE SELECT * FROM dfGorjeta WHERE hora = 'jantar' AND gorjeta > 6.00;
# no pandas dfGorjeta[(dfGorjeta["hora"] == "jantar") & (dfGorjeta["gorjeta"] > 6.00)]
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | |
---|---|---|---|---|---|---|---|
23 | 39.42 | 7.58 | homem | não | sab | jantar | 4 |
59 | 48.27 | 6.73 | homem | não | sab | jantar | 4 |
170 | 50.81 | 10.00 | homem | sim | sab | jantar | 3 |
183 | 23.17 | 6.50 | homem | sim | dom | jantar | 4 |
212 | 48.33 | 9.00 | homem | não | sab | jantar | 4 |
214 | 28.17 | 6.50 | mulher | sim | sab | jantar | 3 |
Podemos obter uma lista dos dados correspondentes a gorjetas dadas por grupos com 5 ou mais pessoas ou com contas de valor acima de $45.00, limitada aos 4 primeiros registros:
–– SQL: SELECT * FROM dfGorjeta WHERE pessoas >= 5 OR valor_conta > 45 LIMIT 4;
# pandas dfGorjeta[(dfGorjeta["pessoas"] >= 5) | (dfGorjeta["valor_conta"] > 45)].head(4)
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | |
---|---|---|---|---|---|---|---|
59 | 48.27 | 6.73 | homem | não | sab | jantar | 4 |
125 | 29.80 | 4.20 | mulher | não | qui | almoço | 6 |
141 | 34.30 | 6.70 | homem | não | qui | almoço | 6 |
142 | 41.19 | 5.00 | homem | não | qui | almoço | 5 |
Dados ausentes são representados por NULL
no, uma marca especial para indicar que um valor não existe no banco de dados. Nos dataframes do pandas o mesmo papel é desempenhado por NaN
(Not a Number). Esses marcadores podem surgir, por ex., na leitura de um arquivo csv
(valores separados por vírgulas) quando um valor está ausente ou não é um valor numérico em uma coluna de números. Para verificar o comportamento do pandas com NaN
criamos um dataframe com valores ausentes. Verificações de nulos é feita com os métodos notna()
e isna()
.
frame = pd.DataFrame({"col1": ["A", "B", np.NaN, "C", "D"], "col2": ["F", np.NaN, "G", "H", "I"]}) frame
col1 | col2 | |
---|---|---|
0 | A | F |
1 | B | NaN |
2 | NaN | G |
3 | C | H |
4 | D | I |
Se temos um banco de dados SQL
com essa estrutura e conteúdo podemos extrair as linhas onde col2
é NULL usando a consulta:
–– sql SELECT * FROM frame WHERE col2 IS NULL;
# no case do pandas usamos frame[frame["col2"].isna()]
col1 | col2 | |
---|---|---|
1 | B | NaN |
De forma análoga, podemos extrair as linhas para as quais col1 não é NULL. No pandas usamos notna()
.
–– sql SELECT * FROM frame WHERE col1 IS NOT NULL;
# pandas: linhas em que col1 não é nula frame[frame["col1"].notna()]
col1 | col2 | |
---|---|---|
0 | A | F |
1 | B | NaN |
3 | C | H |
4 | D | I |
GROUP BY
No SQL consultas com agrupamentos são feitas usando-se as operações GROUP BY
. No pandas existe o método groupby()
que tipicamente particiona o conjunto de dados em grupos e aplica alguma função (em geral de agregamento), combinando depois os grupos resultantes.
Um exemplo comum é o de particionar os dados em grupos menores e contar os elementos desses grupos. Voltando ao nosso dataframe dfGorjeta
podemos consultar quantas gorjetas foram dadas por grupos de cada sexo:
–– sql SELECT sexo, count(*) FROM dfGorjeta GROUP BY sexo;
# o equivalente em pandas seria dfGorjeta.groupby("sexo").size()
mulher 87
homem 157
dtype: int64
O resultado é uma series
cujos valores podem ser retornados por seu nome de index
ou pelo número desse indice.
print("A lista contém %d homens" % dfGorjeta.groupby("sexo").size()[0]) print("\t\t e %d mulheres" % dfGorjeta.groupby("sexo").size()["mulher"])
e 87 mulheres
É possível aplicar o método count()
para cada coluna, individualmente:
dfGorjeta.groupby("sexo").count()
valor_conta | gorjeta | fumante | almoço | hora | pessoas | |
---|---|---|---|---|---|---|
sexo | ||||||
mulher | 87 | 87 | 87 | 87 | 87 | 87 |
homem | 157 | 157 | 157 | 157 | 157 | 157 |
Observe que no código do pandas usamos size()
e não count()
. Isso foi feito porque o método count()
é aplicado sobre cada coluna e retorna tantos valores quantas colunas existem, com valores não null.
Também se pode aplicar o método count()
para uma coluna específica:
# para contar valores em uma única coluna primeiro ela é selecionada, depois contada dfGorjeta.groupby("sexo")["valor_conta"].count()
mulher 87
homem 157
Name: valor_conta, dtype: int64
Existem diversas funções de agregamento. São elas:
função | descrição |
---|---|
mean() | calcula médias para cada grupo |
sum() | soma dos valores do grupo |
size() | *tamanhos dos grupos |
count() | número de registros no grupo |
std() | desvio padrão dos grupos |
var() | variância dos grupos |
sem() | erro padrão da média dos grupos |
describe() | gera estatísticas descritivas |
first() | primeiro valor no grupo |
last() | último valor no grupo |
nth() | n-ésimo valor (ou subconjunto se n for uma lista) |
min() | valor mínimo no grupo |
max() | valor máximo no grupo |
* A função size()
retorna o número de linhas em uma serie e o número de linhas × colunas em dataframes.
Para obter um resumo estatístico relativo ao campo gorjeta
, agrupado pelo campo sexo
podemos usar:
dfGorjeta.groupby("sexo")["gorjeta"].describe()
count | mean | std | min | 25% | 50% | 75% | max | |
---|---|---|---|---|---|---|---|---|
sexo | ||||||||
homem | 157.0 | 3.089618 | 1.489102 | 1.0 | 2.0 | 3.00 | 3.76 | 10.0 |
mulher | 87.0 | 2.833448 | 1.159495 | 1.0 | 2.0 | 2.75 | 3.50 | 6.5 |
Múltiplas funções podem ser aplicadas de uma vez. Suponha que queremos determinar como os valores das gorjetas variam por dia da semana. O método agg()
(de agregar) permite que se passe um dicionário para o dataframe agrupado, indicando que função deve ser aplicada a cada coluna.
–– sql (agrupe os dados por dia, calcule a média para cada dia e o número de entradas contadas) SELECT dia, AVG(gorjeta), COUNT(*) FROM dfGorjeta GROUP BY dia;
# na pandas, use mean no campo gorjeta, size no campo dia dfGorjeta.groupby("dia").agg({"gorjeta": np.mean, "dia": np.size})
gorjeta | dia | |
---|---|---|
dia | ||
dom | 3.255132 | 76 |
qui | 2.771452 | 62 |
sab | 2.993103 | 87 |
sex | 2.734737 | 19 |
Também é possível realizar o agrupamento por mais de uma coluna. Para fazer isso passamos uma lista de colunas para o método groupby()
.
–– agrupe primeiro por "fumante", depois por "dia" –– realize a contagem dos registros e a média das gorjetas SELECT fumante, dia, COUNT(*), AVG(gorjeta) FROM dfGorjeta GROUP BY fumante, dia;
# no pandas dfGorjeta.groupby(["fumante", "dia"]).agg({"gorjeta": [np.size, np.mean]})
gorjeta | |||
---|---|---|---|
size | mean | ||
fumante | dia | ||
não | dom | 57.0 | 3.167895 |
qui | 45.0 | 2.673778 | |
sab | 45.0 | 3.102889 | |
sex | 4.0 | 2.812500 | |
sim | dom | 19.0 | 3.516842 |
qui | 17.0 | 3.030000 | |
sab | 42.0 | 2.875476 | |
sex | 15.0 | 2.714000 |
JOIN
No SQL tabelas podem ser juntadas ou agrupadas através da cláusula JOIN
. Junções podem ser LEFT, RIGHT, INNER, FULL
. No pandas se usa os métodos join()
ou merge()
. Por defaultjoin()
juntará os DataFrames por seus índices. Cada método tem parâmetros que permitem escolher o tipo da junção (LEFT, RIGHT, INNER, FULL), ou as colunas que devem ser juntadas (por nome das colunas ou índices). [Linguagem de Consultas SQL]
# para os exercícios que se seguem criamos os dataframes df1 = pd.DataFrame({"key": ["A", "B", "C", "D"], "value": [11, 12, 13, 14]}) df2 = pd.DataFrame({"key": ["B", "D", "D", "E"], "value": [21, 22, 23, 24]}) # para exibir esses dataframes com formatação usamos display() display(df1) display(df2)
key | value | |
---|---|---|
0 | A | 11 |
1 | B | 12 |
2 | C | 13 |
3 | D | 14 |
key | value | |
---|---|---|
0 | B | 21 |
1 | D | 22 |
2 | D | 23 |
3 | E | 24 |
Como antes supomos a existência de duas tabelas de dados sql como as mesmas estruturas e dados para considerarmos as várias formas de JOIN
s.
INNER JOIN
–– junção das duas tabelas ligadas por suas chaves - key SELECT * FROM df1 INNER JOIN df2 ON df1.key = df2.key;
# por default merge() faz um INNER JOIN pd.merge(df1, df2, on="key")
key | value_x | value_y | |
---|---|---|---|
0 | B | 12 | 21 |
1 | D | 14 | 22 |
2 | D | 14 | 23 |
O método merge()
também oferece parâmetros para que sejam feitas junções de uma coluna de um dataframe com o índice de outro dataframe. Para ver isso vamos criar outro dataframe a partir de df2
, usando o campo key
como índice.
# novo dataframe tem campo "key" como índice df2_indice = df2.set_index("key") display(df2_indice) pd.merge(df1, df2_indice, left_on="key", right_index=True)
value | |
---|---|
key | |
B | 21 |
D | 22 |
D | 23 |
E | 24 |
key | value_x | value_y | |
---|---|---|---|
1 | B | 12 | 21 |
3 | D | 14 | 22 |
3 | D | 14 | 23 |
LEFT OUTER JOIN
A junção LEFT OUTER JOIN
recupera todos as campos à esquerda, existindo ou não uma linha correspondente à direita. O parâmetro how="left"
é o equivalente no pandas.
–– sql: recupera todos os valores de df1 existindo ou não correspondente em df2 SELECT * FROM df1 LEFT OUTER JOIN df2 ON df1.key = df2.key;
# pandas: how="left" equivale a LEFT OUTER JOIN pd.merge(df1, df2, on="key", how="left")
key | value_x | value_y | |
---|---|---|---|
0 | A | 11 | NaN |
1 | B | 12 | 21 |
2 | C | 13 | NaN |
3 | D | 14 | 22 |
4 | D | 14 | 23 |
Observe que df2
não possui campos com key = "A"
ou key = "C"
e, por isso o dataframe resultante tem NaN
nessas entradas. key = "A"
. Como df2
tem 2 linhas para key = "D"
a linha aparece duplicada para essa key
em df1
.
RIGHT JOIN
A junção RIGH OUTER JOIN
recupera todos as campos à direita, existindo ou não uma linha correspondente à esquerda. O parâmetro how="right"
é o equivalente no pandas.
–– sql: recupera todos os registros em df2 SELECT * FROM df1 RIGHT OUTER JOIN df2 ON df1.key = df2.key;
# pandas: how="right" equivale a RIGHT OUTER JOIN pd.merge(df1, df2, on="key", how="right")
key | value_x | value_y | |
---|---|---|---|
0 | B | 12 | 21 |
1 | D | 14 | 22 |
2 | D | 14 | 23 |
3 | E | NaN | 24 |
FULL JOIN
A junção FULL OUTER JOIN
recupera todos as campos à direita ou à esquerda, representando como NaN
os valores ausentes em uma ou outra. Todos as linhas das duas tabelas são retornadas com junção onde a campo key
existe em ambas. O parâmetro how="outer"
é o equivalente no pandas. Observe que nem todos os gerenciadores de bancos de dados permitem essa operação.
–– sql: retorna todos os registros em ambas as tabelas SELECT * FROM df1 FULL OUTER JOIN df2 ON df1.key = df2.key;
# pandas: how="outer" é o equivalente em dataframes pd.merge(df1, df2, on="key", how="outer")
key | value_x | value_y | |
---|---|---|---|
0 | A | 11 | NaN |
1 | B | 12 | 21 |
2 | C | 13 | NaN |
3 | D | 14 | 22 |
4 | D | 14 | 23 |
5 | E | NaN | 24 |
UNION
Para os exemplos seguintes definimos mais 2 dataframes:
df3 = pd.DataFrame({"cidade": ["Rio de Janeiro", "São Paulo", "Belo Horizonte"], "nota": [1, 2, 3]}) df4 = pd.DataFrame({"cidade": ["Rio de Janeiro", "Curitiba", "Brasília"], "nota": [1, 4, 5]})
No SQL a clásula UNION ALL
é usada para juntar as linhas retornadas em dois (ou mais) instruções de SELECT
. Linhas duplicadas são mantidas. O mesmo efeito pode ser conseguido no pandas usando-se o método concat()
.
–– sql: UNION ALL SELECT city, rank FROM df3 UNION ALL SELECT cidade, nota FROM df4;
# pandas: concat pd.concat([df3, df4])
cidade | nota | |
---|---|---|
0 | Rio de Janeiro | 1 |
1 | São Paulo | 2 |
2 | Belo Horizonte | 3 |
0 | Rio de Janeiro | 1 |
1 | Curitiba | 14 |
2 | Brasília | 5 |
No SQL a cláusula UNION
tem o mesmo efeito que UNION ALL
mas remove as linhas duplicadas. No pandas isso pode ser conseguido se fazendo a conactenação concat()
seguida de drop_duplicates()
.
–– SQL UNION SELECT city, rank FROM df1 UNION SELECT city, rank FROM df2; –– o registro duplicado no Rio de Janeiro fica excluído
# pandas: concat() seguido de drop_duplicates() pd.concat([df1, df2]).drop_duplicates()
cidade | nota | |
---|---|---|
0 | Rio de Janeiro | 1 |
1 | São Paulo | 2 |
2 | Belo Horizonte | 3 |
1 | Curitiba | 14 |
2 | Brasília | 5 |
Outras funções analíticas e de agregamento
Para os próximos exemplos vamos retornar ao nosso dataframe dfGorjeta
: para listar as 5 gorjetas mais altas, no MySQL (a sintaxe varia de um para outro gerenciador).
–– MySQL: retorna todos os campos em ordem decrescente, 5 linhas SELECT * FROM dfGorjeta ORDER BY gorjeta DESC LIMIT 10 OFFSET 5;
# pandas: seleciona 15 maiores e exibe as 10 de menor valor dfGorjeta.nlargest(15, columns="gorjeta").tail(10)
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | |
---|---|---|---|---|---|---|---|
183 | 23.17 | 6.50 | homem | sim | Dom | jantar | 4 |
214 | 28.17 | 6.50 | mulher | sim | sab | jantar | 3 |
47 | 32.40 | 6.00 | homem | não | Dom | jantar | 4 |
239 | 29.03 | 5.92 | homem | não | sab | jantar | 3 |
88 | 24.71 | 5.85 | homem | não | Thur | almoço | 2 |
181 | 23.33 | 5.65 | homem | sim | Dom | jantar | 2 |
44 | 30.40 | 5.60 | homem | não | Dom | jantar | 4 |
52 | 34.81 | 5.20 | mulher | não | Dom | jantar | 4 |
85 | 34.83 | 5.17 | mulher | não | Thur | almoço | 4 |
211 | 25.89 | 5.16 | homem | sim | sab | jantar | 4 |
UPDATE
Há muitas formas de alterar um valor em um campo de um dataframe. Por exemplo, abaixo realizamos uma alteração em todos os valores de gorjeta
sempre que gorjeta < 2
.
–– sql: em todas as linhas duplique a gorjeta se gorjeta for menor que 1.1 UPDATE dfGorjeta SET gorjeta = gorjeta*2 WHERE gorjeta < 1.1;
# pandas: o mesmo resultado pode ser obtido da aseguinte forma # dfGorjeta.loc[dfGorjeta["gorjeta"] < 1.1, "gorjeta"] *= 2
Para explicar com mais detalhes o funcionamento deste código, armazenamos abaixo a lista dos índices das linhas de gorjetas mais baixas e exibimos essas linhas. Em seguida multiplicamos apenas as gorjetas dessas linhas por 2 e examinamos o resultado:
indices = dfGorjeta[dfGorjeta["gorjeta"] < 1.1].index print("Índices de gorjetas < 1.1:", indices) display("Lista de gorjetas < 1.1", dfGorjeta.iloc[indices]) # multiplica essas gorjetas por 2 dfGorjeta.loc[dfGorjeta["gorjeta"] < 1.1, "gorjeta"] *= 2 # lista as mesmas linhas após a operação display("Gorjetas após a operação:", dfGorjeta.iloc[indices])
Index([0, 67, 92, 111, 236], dtype=’int64′)
‘Lista de gorjetas < 1.1’
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | mulher | não | dom | jantar | 2 |
67 | 3.07 | 1.00 | mulher | sim | sab | jantar | 1 |
92 | 5.75 | 1.00 | mulher | sim | sex | jantar | 2 |
111 | 7.25 | 1.00 | mulher | não | sab | jantar | 1 |
236 | 12.60 | 1.00 | homem | sim | sab | jantar | 2 |
‘Gorjetas após a operação:’
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 2.02 | mulher | não | dom | jantar | 2 |
67 | 3.07 | 2.00 | mulher | sim | sab | jantar | 1 |
92 | 5.75 | 2.00 | mulher | sim | sex | jantar | 2 |
111 | 7.25 | 2.00 | mulher | não | sab | jantar | 1 |
236 | 12.60 | 2.00 | homem | sim | sab | jantar | 2 |
–– sql: alterar um campo de uma linha específica (supondo a existência de um campo id) UPDATE dfGorjeta SET sexo = 'NI' WHERE id = 239
# para alterar o campo sexo para 'NI' (não informado) dfGorjeta.loc[239, 'sexo'] ='NI'
DELETE
Existem muitas formas de se excluir linhas de um dataframe mas é comum a prática de selecionar as linhas que devem ser mantidas e copiar para um novo dataframe.
DELETE FROM dfGorjeta WHERE gorjeta > 9;
# pandas: como novo dataframe tem o mesmo nome do original, o antigo é sobrescrito e perdido dfTop = dfGorjeta.loc[dfGorjeta["gorjeta"] > 9] dfTop
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | |
---|---|---|---|---|---|---|---|
170 | 50.81 | 10.0 | homem | sim | sab | jantar | 3 |
Também é possível apagar linhas usando seu índice:
# apagar linha com index = 4, inplace para substituir o dataframe dfGorjeta.drop(index=4, inplace=True) # apagar linhas com index = 0 até 3 dfGorjeta.drop(index=[0, 1, 2, 3], inplace=True) dfGorjeta.head()
valor_conta | gorjeta | sexo | fumante | dia | hora | pessoas | |
---|---|---|---|---|---|---|---|
5 | 25.29 | 4.71 | homem | não | dom | jantar | 4 |
6 | 8.77 | 12.00 | homem | não | dom | jantar | 2 |
7 | 26.88 | 3.12 | homem | não | dom | jantar | 4 |
8 | 15.04 | 1.96 | homem | não | dom | jantar | 2 |
9 | 14.78 | 3.23 | homem | não | dom | jantar | 2 |
Bibliografia
- McKinney, Wes: Python for Data Analysis, Data Wrangling with Pandas, NumPy,and IPython
O’Reilly Media, 2018. - Pandas: página oficial, acessada em janeiro de 2021.
Muito boa as comparações feitas; formas de consultas no SQL e por pandas.
Obrigado Jorge. Aproveita para dar uma olhada geral aí no site.
Parabens pelo conteúdo.
Você saberia me dizer se a velocidade é muito maior no SQL que no Pandas?
Abraço
Marcos,
essa é uma pergunta difícil de responder porque os usos são bem diferentes. Além disso existem diversas implementações do SQL, como SQlite, MySQL, Postgre, MS SQL Server, etc. O Python geralmente não é muito rápido, mas a biblioteca Pandas é escrita em C e são bastante rápidas. Os problemas que cada um resolve são diferentes, sendo que SQL não é realmente uma linguagem de programação de propósito geral e é, em geral, utilizada em conjunto com outra linguagem. O Pandas usa Numpy que é otimizado para a manipulação matemática rápida de vetores, matrizes e tensores. Consulta SQL são especialmente indicadas para acesso a dados altamente estruturados. Pode ocorrer que um projeto use as duas tecnologias.