キーを共有する表
実際のデータが1つの表で届くことはまれです。ポジションは1つの表、各銘柄のセクターは別の表、価格は3つ目の表にあります。それらをつなぐのがキーで、ここでは銘柄コードです。left.merge(right, on="キー") は left の各行について、同じキーを持つ right の行を探して横に並べます。SQL の結合(join)と同じ考え方です。
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・outer
上の結果から TSLA が消え、F も消えています。既定の how="inner" は両方の表にあるキーだけを残します。目的に合わせて指定しましょう。how="left" は左の表の全行を残し、右側の欠けた値を NaN にします。how="right" はその逆、how="outer" は両方のキーを残します。表に情報を足したいときはほぼ常に left です。行を黙って失わないからです。
# 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 NaN一致しなかったものを見つける
黙って入る NaN はデータの問題を隠すことがあるので確認しましょう。indicator=True は各行のキーが left_only・right_only・both のどこで見つかったかを示す _merge 列を加えます。これで絞り込めば「価格のないポジションはどれか」に答えられます。もう1つの罠は重複キーです。右側で同じキーが2回現れると、一致する左の行が繰り返されます。validate="many_to_one"(または "one_to_one")を渡せば、pandas は行数を黙って変える代わりにエラーを出します。
# 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__) # MergeErrorキー名の違いと列名の衝突
2つの表でキーの名前が違うときは left_on="symbol", right_on="ticker" を使います。両方の列が残ります。キー以外で同名の列がある場合(ここでは qty)、pandas は両方を残して接尾辞 _x と _y を付けます。suffixes=("_a", "_b") で分かりやすいものに変えましょう。外部結合の後は欠けた数量が NaN になり、列は浮動小数点になります。fillna(0) のあと astype("int64") で整数に戻します。
# 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() を比べ、全行を残したいときは how="left"、キーが一意だと考えるときは validate= を付けましょう。left.merge(right, on="キー")はキーを共有する2つの表の行を突き合わせ、left_on/right_onは名前が違う場合に使うhow="inner"(既定)は両方にあるキー、"left"は左の全行、"outer"はすべてのキーを残すindicator=Trueは一致しなかった行を示し、validate=とsuffixes=は重複キーと列名の衝突を防ぐ
次は単純リターンです。価格の系列を、ある期間から次の期間への変化率に変換します。
ラボ:採点される内容
attach_sectorで各約定にセクターを付ける25点value_positionsで価格のあるポジションを評価する25点unpriced_symbolsで価格のないポジションを見つける25点combine_booksで2つのブックを1つの表にまとめる25点