データを指す2つの方法:ラベルと位置
DataFrame の指し方は2通りあり、混同するのが pandas で最もよくあるバグです。.loc はラベルで選びます(行はインデックスのラベル、列は列名)。.iloc は、リストや NumPy 配列と同じ0始まりの整数位置で選びます。どちらも [行, 列] を取ります。df.loc["AAPL", "price"] は1つの値、df.iloc[0, 0] は1行目1列目の値です。後半を省くと行全体になり、df.loc["AAPL"] は AAPL の行を Series として返します。
import pandas as pd
df = pd.DataFrame(
{"price": [184.25, 410.2, 171.0, 251.0],
"volume": [200, 50, 300, 120],
"sector": ["tech", "tech", "tech", "auto"]},
index=["AAPL", "MSFT", "GOOG", "TSLA"],
)
print(df.loc["AAPL", "price"], df.iloc[0, 0]) # 184.25 184.25 same cell, two addresses
print(df.loc["TSLA"].to_dict()) # {'price': 251.0, 'volume': 120, 'sector': 'auto'}
print(df.iloc[-1].to_dict()) # the same row: position -1 is the last
print(df.loc[["TSLA", "AAPL"], "price"]) # a list of labels picks in that order
# TSLA 251.00
# AAPL 184.25
# Name: price, dtype: float64
try:
df.loc["XXX", "price"] # a label that does not exist
except KeyError as error:
print("KeyError", error) # KeyError 'XXX'スライス:ラベルのスライスは終端を含む
スライスは2つの方式で挙動が違います。位置のスライス df.iloc[1:3] は Python の規則どおり終端を含みません(位置1と2)。ラベルのスライス df.loc["MSFT":"TSLA"] は両端のラベルを含みます。ラベルには自然な「次」がないからです。ラベルのスライスはインデックスの現在の並び順に従い、ソート済みである必要はありません。列はリスト(["price", "volume"])を渡すと、指定した順に取り出せます。どちらの方式かを忘れると、エラーなしに1つずれた結果になります。
import pandas as pd
df = pd.DataFrame(
{"price": [184.25, 410.2, 171.0, 251.0], "volume": [200, 50, 300, 120]},
index=["AAPL", "MSFT", "GOOG", "TSLA"],
)
print(df.iloc[1:3]) # positions 1 and 2: the end is excluded
# price volume
# MSFT 410.2 50
# GOOG 171.0 300
print(df.loc["MSFT":"TSLA", ["volume", "price"]]) # labels MSFT..TSLA: the end is included
# volume price
# MSFT 50 410.2
# GOOG 300 171.0
# TSLA 120 251.0
print(df.iloc[:, 0].tolist()) # the first column, by position
# [184.25, 410.2, 171.0, 251.0]loc による条件指定と代入
.loc は行にブールマスクも受け付けます。df.loc[df["volume"] > 100, "price"] は、条件を満たす行の価格列を1ステップで返します。また、選んだ部分への代入にも .loc を使います。df.loc["MSFT", "price"] = 1.0 は1セルを、df.loc[df["volume"] < 100, "volume"] = 0 は一致するすべての行を変更します。df[df["volume"] < 100]["volume"] = 0 のような連鎖した書き方は避けましょう。一時的なコピーに作用するからです。pandas 3 では派生した表が元を変更することは決してないので、関数内で表を変更するには、先に df.copy() し、.loc で代入してコピーを返します。
import pandas as pd
df = pd.DataFrame(
{"price": [184.25, 410.2, 171.0, 251.0], "volume": [200, 50, 300, 120]},
index=["AAPL", "MSFT", "GOOG", "TSLA"],
)
print(df.loc[df["volume"] > 100, "price"]) # a Boolean mask for rows, a label for the column
# AAPL 184.25
# GOOG 171.00
# TSLA 251.00
# Name: price, dtype: float64
def with_price(frame, symbol, price):
out = frame.copy() # never edit the caller's table
out.loc[symbol, "price"] = price
return out
changed = with_price(df, "MSFT", 1.0)
print(changed.loc["MSFT", "price"], df.loc["MSFT", "price"]) # 1.0 410.2
fixed = df.copy()
fixed.loc[fixed["volume"] < 100, "volume"] = 0 # assign to all matching rows at once
print(fixed["volume"].tolist()) # [200, 0, 300, 120]import pandas as pd
# Integer labels are labels, not positions: a classic trap
d = pd.DataFrame({"x": [1.0, 2.0, 3.0]}, index=[10, 20, 30])
print(d.loc[20, "x"], d.iloc[0, 0]) # 2.0 1.0 label 20 is the second row
print(d.loc[10:20]["x"].tolist()) # [1.0, 2.0] labels 10 through 20, end included
print(d.iloc[0:2]["x"].tolist()) # [1.0, 2.0] positions 0 and 1, end excluded
try:
d.loc[0, "x"] # there is no label 0, even though position 0 exists
except KeyError as error:
print("KeyError", error) # KeyError 0.loc を、位置そのものが目的のとき(「最後の行」「先頭の3行」)だけ .iloc を使いましょう。ラベルに基づくコードは行が並べ替えられても動き続けますが、位置に基づくコードは黙って別の行を選び始めます。単なる df[...] は複数の意味(列・列のリスト・行マスク)を持つ略記なので、重要な関数では .loc か .iloc を明示します。ラボがどちらかを指定するのはそのためです。df.loc[行ラベル, 列ラベル]はラベル、df.iloc[行位置, 列位置]は0始まりの位置ilocのスライスは終端を含まず、locのラベルスライスは含む。存在しないラベルはKeyErrordf.loc[mask, "col"]は絞り込みと選択。.copy()に.locで代入して返す
次は groupby と集計です。表をグループに分け、それぞれを要約します。
ラボ:採点される内容
price_ofでシンボルから価格を1つ引く25点label_rangeで2つのシンボルの間の行を取り出す25点position_rangeで位置を指定して行を取り出す25点set_priceでコピーの中の価格を1つ修正する25点