Tabelas que compartilham uma chave
Dados reais raramente chegam em uma só tabela. Suas posições estão em uma, o setor de cada símbolo em outra, os preços em uma terceira. O que as liga é uma chave, aqui o símbolo. left.merge(right, on="chave") encontra, para cada linha de left, as linhas de right com a mesma chave e as coloca lado a lado. É a mesma ideia de um join em SQL.
import pandas as pd
positions = pd.DataFrame({"symbol": ["AAPL", "MSFT", "TSLA", "GOOG"], "qty": [100, 50, 20, 10]})
reference = pd.DataFrame({"symbol": ["AAPL", "MSFT", "GOOG", "F"], "sector": ["tech", "tech", "tech", "auto"]})
merged = positions.merge(reference, on="symbol")
print(merged)
# symbol qty sector
# 0 AAPL 100 tech
# 1 MSFT 50 tech
# 2 GOOG 10 techinner, left, right e outer
TSLA sumiu do resultado acima, e F também. O padrão, how="inner", mantém só as chaves presentes em ambas as tabelas. Diga o que quer: how="left" mantém todas as linhas da tabela da esquerda e preenche com NaN os valores que faltam à direita, how="right" faz o oposto e how="outer" mantém as chaves das duas. Para enriquecer uma tabela quase sempre se quer left, porque ele nunca perde uma linha em silêncio.
# how="left" keeps every position; TSLA has no sector, so it gets NaN
left = positions.merge(reference, on="symbol", how="left")
print(left)
# symbol qty sector
# 0 AAPL 100 tech
# 1 MSFT 50 tech
# 2 TSLA 20 NaN
# 3 GOOG 10 tech
# how="outer" keeps the keys of both tables (sorted); quantities become floats because of the NaN
print(positions.merge(reference, on="symbol", how="outer"))
# symbol qty sector
# 0 AAPL 100.0 tech
# 1 F NaN auto
# 2 GOOG 10.0 tech
# 3 MSFT 50.0 tech
# 4 TSLA 20.0 NaNEncontrar o que não casou
Um NaN silencioso pode esconder um problema nos dados, então verifique. indicator=True adiciona uma coluna _merge que diz, para cada linha, se a chave foi encontrada em left_only, right_only ou both. Filtrar por ela responde a perguntas como "quais posições não têm preço?". Chaves duplicadas são a outra armadilha: se uma chave aparece duas vezes à direita, cada linha correspondente da esquerda é repetida. Passe validate="many_to_one" (ou "one_to_one") e o pandas levanta um erro em vez de mudar em silêncio o número de linhas.
# Which positions have no sector?
check = positions.merge(reference, on="symbol", how="left", indicator=True)
print(check)
# symbol qty sector _merge
# 0 AAPL 100 tech both
# 1 MSFT 50 tech both
# 2 TSLA 20 NaN left_only
# 3 GOOG 10 tech both
print(check.loc[check["_merge"] == "left_only", "symbol"].tolist()) # ['TSLA']# Duplicate keys repeat rows: AAPL has two quotes, so the AAPL position appears twice
quotes = pd.DataFrame({"symbol": ["AAPL", "AAPL", "MSFT"], "price": [184.25, 184.5, 410.1]})
print(len(positions.merge(quotes, on="symbol"))) # 3
# validate turns the surprise into an error
try:
positions.merge(quotes, on="symbol", validate="one_to_one")
except Exception as error:
print(type(error).__name__) # MergeErrorChaves com nomes diferentes e colunas em conflito
Quando a chave tem nomes diferentes nas duas tabelas, use left_on="symbol", right_on="ticker"; as duas colunas são mantidas. Quando as duas tabelas têm uma coluna de mesmo nome que não é a chave (aqui qty), o pandas mantém as duas e adiciona os sufixos _x e _y. Escolha outros mais claros com suffixes=("_a", "_b"). Depois de um outer join as quantidades que faltam são NaN e a coluna é de ponto flutuante: use fillna(0) e depois astype("int64") para voltar a inteiros.
# Different key names: both columns are kept
book_a = pd.DataFrame({"symbol": ["AAPL", "MSFT"], "qty": [100, 50]})
book_b = pd.DataFrame({"ticker": ["MSFT", "TSLA"], "qty": [-20, 30]})
print(book_a.merge(book_b, left_on="symbol", right_on="ticker", how="outer", suffixes=("_a", "_b")))
# symbol qty_a ticker qty_b
# 0 AAPL 100.0 NaN NaN
# 1 MSFT 50.0 MSFT -20.0
# 2 NaN NaN TSLA 30.0
# Same key name, outer join, suffixes, then back to whole numbers
both = book_a.merge(book_b.rename(columns={"ticker": "symbol"}), on="symbol", how="outer", suffixes=("_a", "_b"))
both[["qty_a", "qty_b"]] = both[["qty_a", "qty_b"]].fillna(0).astype("int64")
print(both)
# symbol qty_a qty_b
# 0 AAPL 100 0
# 1 MSFT 50 -20
# 2 TSLA 0 30len() antes e depois, use how="left" quando quiser manter todas as linhas e acrescente validate= quando achar que as chaves são únicas.left.merge(right, on="chave")casa as linhas de duas tabelas que compartilham uma chave;left_on/right_ontratam nomes diferentes.how="inner"(padrão) mantém as chaves de ambas,"left"todas as linhas da esquerda,"outer"todas as chaves.indicator=Truemostra o que não casou;validate=esuffixes=protegem contra chaves duplicadas e nomes em conflito.
A seguir: os retornos simples, que transformam uma série de preços na variação percentual de um período para o seguinte.
Lab: o que é avaliado
- Adicionar o setor de cada operação com
attach_sector25 pts - Avaliar as posições que têm preço com
value_positions25 pts - Encontrar as posições sem preço com
unpriced_symbols25 pts - Juntar dois livros em uma só tabela com
combine_books25 pts