列と行の選択 — 名前または位置による指定
表の一部を指し示す操作: 角括弧による列の選択、locによるラベルでの行選択、ilocによる位置での行選択、atによる単一セルの選択。そして、表の切り出しやソートの後に両者が一致しなくなるため、どちらの方法を使っているかを常に把握する方法を学びます。
どうやって学びますか?
解決する問題
第3章で学んだとおり、お店の注文ファイルを読み込んで中身を確認しました。すると、いろいろな人から質問が来るようになりました。
在庫チームは言います。「製品名と数量だけ送ってください——それ以外は不要です。」 経理担当者は言います。「注文1006の内容と金額を教えてください。」 そして上司は言います。「最後に届いた注文を3件見せてください。」
どれも表全体に対する計算ではありません。それぞれ指し示す作業です——いくつかの列を、ある1行を、末尾の数行を、1つのセルを。8行なら全部印刷して指で確認できますが、8000行ではそうはいきません。プログラムの中では「指」が使えないのはなおさらです。
pandasには複数の指し示し方があります。複数ある理由こそが、この章の本当のテーマです。行を指し示す方法は2種類あります。名前(インデックスのラベル)で指し示す方法と、位置(先頭、2番目、末尾)で指し示す方法です。表を読み込んだばかりのときは、この2つがたまたま同じ数値になるため、初心者は2種類あることに気づきません。末尾の3行を取り出したり、ソートしたり、フィルタリングしたりした瞬間、2つは一致しなくなります——そのとき、混同して書いたコードはエラーを出さずに間違った行を返すようになります。
この章は選択がテーマですが、核心はどちらの方法を使っているかを常に把握することです。
この章を終える頃には
- 1つの列を選択して
Seriesを取得し、複数の列を選択してDataFrameを取得できるようになり、今手元にあるのがどちらかを判断できるようになる .locでラベルを使って行を選択し、.ilocで位置を使って行を選択できるようになり、両者のスライスの終端が異なる理由を説明できるようになる.loc[rows, columns]で行と列を同時に選択し、結果の形を予測できるようになる(.locや.atで1つの値まで絞り込む操作を含む)set_index()で行に意味のあるラベルを付け、それが.locの意味をどう変えるかを理解できるようになる- pandas 3において選択結果を変更しても元の表が変更されない理由を説明でき、元の表を正しく変更できるようになる。また、選択操作によって発生する
KeyErrorやIndexErrorのメッセージを読み解き、修正できるようになる
前提条件: CSVの読み込みとデータの確認。
まずファイルを作る
プロジェクトフォルダに、以下の9行でorders.csvを作成してください。このファイルはコース全体を通して使うので、あとで見つけやすい場所に保存してください。
order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0各行が1件の注文です。注文番号、日付、販売した店舗の支店、売れた商品、商品カテゴリ、数量、単価が並んでいます。
コードを書く前に
選択はとても小さな作業に見えるため、すぐに括弧を打ち始めたくなります。でも最初に1分だけ次の手順を踏んでください。この章における選択の間違いは、どれかを飛ばしたことから生まれます。
1. 質問を1文で言い、名詞に下線を引く。 名詞が「何を指し示すか」を教えてくれます。
- 「すべての注文の製品と数量」——列が2つ、全行
- 「注文1006」——注文番号で名指しされた行が1つ
- 「最後の3件の注文」——末尾から数えた位置による行
- 「注文1006の価格」——セルが1つ:行と列の組み合わせ
2. 指し示す前に表を確認する。 存在する名前しか指し示せません。選択において重要なのは3つ——サイズ、正確な列名、そしてインデックス(左側に並ぶラベル)です。
import pandas as pd
orders = pd.read_csv("orders.csv")
print(orders.shape)
print(list(orders.columns))
print(orders.index)(8, 7)
['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
RangeIndex(start=0, stop=8, step=1)列名はリスト形式でクォートつきで返ってきます——'price'が小文字で余分なスペースがないことを確認できます。インデックスは0から7までのRangeIndexで、ラベルは単なる連番です。この点を覚えておいてください。章全体に関わる「偶然の一致」だからです。今この瞬間、ラベルが2の行は位置も2です。しかしそれは常に成り立つわけではありません。
3. 実行前に答えの形を描く。 何が出てくるべきか——数値1つ、列1つ、それとも小さな表か?行と列はいくつか?在庫チームの依頼なら、8行2列の表です。「注文1006」なら1行。「注文1006の価格」なら数値1つ、850.0です。期待する形がわかっていれば、結果を見た瞬間に選択ミスが一目でわかります。
4. 何を知っているかからツールを選ぶ。 この一覧がこの章の全体像です。
- 列名がわかっている → 角括弧:
orders["quantity"]、orders[["product", "quantity"]] - 行のラベル(インデックス上の名前)がわかっている →
.loc:orders.loc[2] - 行の位置がわかっている——先頭、末尾、5番目 →
.iloc:orders.iloc[-3:] - 行と列を同時に指定したい →
.loc[rows, cols]または.iloc[rows, cols]:orders.loc[2:4, ["product", "price"]] - セル1つを指定したい →
.at[row, col]または.loc[row, col]:orders.at[5, "price"]
5. 確認方法を決めておく。 選択のあとは必ず.shapeかtype()を結果に対して表示し、描いた形と比べてください。1行追加するだけで、以下に挙げる間違いのほとんどを数値が狂う前に発見できます。
あとは各ツールを1つずつ、解説つきの例とともに見ていきます。
列1つ:名前を角括弧に入れる
列名を角括弧に入れると、その列が返ってきます。
quantity = orders["quantity"]
print(type(quantity))
print(quantity)<class 'pandas.Series'>
0 12
1 5
2 2
3 7
4 30
5 1
6 20
7 4
Name: quantity, dtype: int64返ってくるのはSeriesです——第2章で登場したあのオブジェクトです。値、左側のインデックス、Name(取り出した列名)、そして1つのdtypeがあります。インデックスも一緒についてきている点に注目してください。0から7は飾りではなく、各数値がどの行のものかを示しています。
Seriesなので、Seriesのメソッドはすべてそのまま使えます。
print(orders["quantity"].sum())81列を1つ選択する一番の目的は、その列に対して何かを問うことです。
複数の列:括弧の中にリスト
在庫チームには2列が必要です。括弧の中に名前のリストを入れます。
stock = orders[["product", "quantity"]]
print(type(stock))
print(stock)<class 'pandas.DataFrame'>
product quantity
0 pen 12
1 notebook 5
2 bag 2
3 bottle 7
4 eraser 30
5 bag 1
6 pen 20
7 bottle 4二重括弧は特別な構文ではありません。この点をはっきり理解することが大切です。外側の括弧が選択で、内側の括弧は普通のPythonのリスト["product", "quantity"]です。最初にリストを作ってから渡しても同じです——wanted = ["product", "quantity"]とおいてorders[wanted]と書いても全く同じ意味になります。列はリストの順番どおりに返ってくるので、列の並び替えにも使えます。
結果はDataFrameです。2列の表は表だからです。そしてここに、何ヶ月も人を悩ませる落とし穴があります——リストに名前が1つだけ入っていても、返ってくるのはDataFrameです。
print(orders["product"].shape)
print(orders[["product"]].shape)(8,)
(8, 1)(8,)はSeries——1次元で値が8つ。(8, 1)は8行1列のDataFrameです。表示形式が異なり、使えるメソッドも異なり、ファイルに保存するとDataFrameは列ヘッダーを保持します。ルールはシンプルです。名前を渡すとSeries、リストを渡すとDataFrame——リストに何個の名前が入っていても同じです。
なぜorders.quantityではないのか
多くのチュートリアルでは、ドット記法で列にアクセスしているのを見かけます。
print(orders.branch.head(2))0 north
1 south
Name: branch, dtype: strこれはここでは動作しますし、記述が短くなります。しかし、列名が有効なPythonの識別子であり、かつ他のものと名前が衝突しない場合にしか使えません——DataFrameにはメソッドや属性が数百個あります。ここに、countとsizeという列名を持つ小さな表があります。どちらもお店のデータとしては十分あり得る名前です。
d = pd.DataFrame({"count": [1, 2], "size": [3, 4]})
print(d.size)
print(d["size"])4
0 3
1 4
Name: size, dtype: int64d.sizeは列を返しませんでした。DataFrame自体のsize属性——セルの総数、2 × 2 = 4——を何の警告もなく返しました。エラーなし、ただ間違った答えが出るだけです。unit priceのようにスペースが含まれる名前は、そもそもドット記法では書けません。括弧にはこれらの問題が一切ありません。残しておくコードのルールはdf["col"]を使うことです。ドット記法はターミナルでさっと確認するときだけにしましょう。
ラベルで行を選ぶ:.loc
行を選ぶのに普通の括弧は使いません(普通の括弧に名前を入れると「列」を意味するため、pandasはどちらを意図したか推測しません)。行には専用のツールが2つあります。1つ目が.loc——label-based location(ラベル基準の位置指定)です。インデックスのラベルを渡します。
print(orders.loc[2])order_id 1003
date 2024-03-02
branch north
product bag
category accessories
quantity 2
price 850.0
Name: 2, dtype: object1行がSeriesとして横向きに返ってきます。列名がインデックスになり、Nameには行のラベル2が入っています。
dtype: objectに注目してください。列は1つの型を持ちますが、行は複数の列をまたいで走ります——数値、文字列、小数——それらをすべて保持できる唯一の型が汎用型のobjectです。pandasが列単位で考えるのはこれが理由の1つです。行は型が混在し、列は混在しません。
次に重要な部分です。.locでラベルの範囲を指定します。
print(orders.loc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0
4 1005 2024-03-03 north eraser stationery 30 8.03行返ってきました——2、3、そして4もです。.locのスライスは終端を含みます。Pythonではrange(2, 4)やlist[2:4]が4の手前で止まることを知っている人は驚くはずです。これは矛盾ではなく、理由があります。
ラベルは必ずしも数値ではありません。ラベルが商品名なら、loc["bag":"eraser"]のように書くことになります。終端を除外するには、"eraser"の次のラベルを名前で指定しなければなりません——しかし名前だと、事前に調べなければ「次の一つ」がわかりません。だからラベルでは、欲しい最初のものと最後のものを指定すると、両方が含まれます。位置は違います——4の次の位置は常に5なので、「終端の手前で止まる」というPythonのルールでも何の不都合もありません。
位置で行を選ぶ:.iloc
2つ目のツールは.iloc——integer location(整数位置指定)です。ラベルを完全に無視し、Pythonのリストと全く同じように0から位置を数えます。
print(orders.iloc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.02行返ってきました——位置2と3。位置4はPythonのすべてのスライスと同様に除外されます。2つの出力を並べると、同じ表・同じ括弧内の数値で、loc[2:4]は3行、iloc[2:4]は2行を返しています。
.ilocは位置基準なので、負の数値は末尾からの数え方になります——上司の質問に直接答えられます。
print(orders.iloc[-3:])order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0「最後の3件」は位置についての質問なので、.ilocが合うツールです。.locには「最後」という概念がありません。名前しかわからないのです。
ラベルと位置は別物
ここまでは、ラベル2と位置2が同じ行だったため、2つのツールを結果で区別できませんでした。最後の3件の注文を独立した表として取り出すと、ずれが生まれます。
last3 = orders.tail(3)
print(last3)order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0行はラベルを保持したままです——5、6、7。pandasは意図的に番号を振り直しません——ラベルこそが、表を切り取ったあとも行が同じ行であり続けるための識別子だからです。「先頭の行」を両方の方法で取り出してみましょう。
print(last3.iloc[0])order_id 1006
date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 5, dtype: objectlast3.loc[0]KeyError: 0.iloc[0]は「先頭にある行」を意味し、それはラベル5の注文です。.loc[0]は「ラベルが0の行」を意味しますが、この表にそのような行はないため、pandasはKeyErrorを発生させます。これがコースの後半で引っかかるミスです——行をフィルタリングしたりソートしたりすると(どちらも後ほど出てきます)、位置は変わりラベルは変わりません。覚えておくべきルール:
- 場所について聞いている——先頭、末尾、ソート後の上位3件? →
.ilocを使う。 - 同一性について聞いている——この注文、この製品? →
.locを使う。
そして怖いのはKeyErrorが出るケースではありません。本当に怖いのは、表の別の場所にラベル0が存在するケースです——そのとき.loc[0]はエラーなしに、意図とは別の行を静かに返します。
.locは名前を求め、.ilocは場所を求めます。読み込んだばかりの表では偶然にも両者が一致するため、混同して書いたコードは初日の確認をすべてパスします——そして表を切り取ったり、フィルタリングやソートを行ったりしたときに初めて、エラーも出さずに間違った行を返すようになります。
行に意味のあるラベルを付ける:set_index
経理担当者は注文1006について尋ねました。デフォルトのインデックスでは、注文1006がラベル5の位置にあることを知らなければなりません——それはファイルの構造に関する知識であって、注文に関する知識ではありません。行の本当の名前は注文番号なので、注文番号をインデックスにしましょう。
by_id = orders.set_index("order_id")
print(by_id.head(3))
print(by_id.shape)date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0
1003 2024-03-02 north bag accessories 2 850.0
(8, 6)3つのことが変わりました。注文番号が左側のインデックスに移動しました。インデックスに名前がつき、order_idがその上の行に表示されています。そして形が(8, 7)から(8, 6)になりました——order_idはもはや列ではなくインデックスです。また、set_indexは新しい表を返したのでby_idと名付けました。orders自体には変更がありません。
これで.locが質問の言葉で話せるようになりました。
print(by_id.loc[1006])date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 1006, dtype: objectラベルのスライスは今や注文番号の範囲を意味し、両端が含まれます。
print(by_id.loc[1003:1005, ["product", "quantity"]])product quantity
order_id
1003 bag 2
1004 bottle 7
1005 eraser 30.ilocは何も変わっていません——位置は位置であり、ラベルが何であれ同じです。
print(by_id.iloc[0:2])date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0ここで2つのツールははっきりと分かれました。by_id.loc[0]は今やKeyErrorです(0は注文番号ではないため)。一方by_id.iloc[0]は注文1001です。注文番号を列に戻したいときはby_id.reset_index()で元に戻せます。
値が一意な列を選ぶこと。 インデックスは行に名前を付けるためのものです。2行が同じ名前を持つと、1行を指し示せません。product(繰り返しがある)で試してみましょう。
by_product = orders.set_index("product")
print(by_product.index.is_unique)
print(by_product.loc["bag"])False
order_id date branch category quantity price
product
bag 1003 2024-03-02 north accessories 2 850.0
bag 1006 2024-03-03 south accessories 1 850.0エラーは出ません——しかしloc["bag"]はSeries(1行)ではなく2行のDataFrameを返しました。1行を想定していたコード(たとえばpriceを1つの数値として読み取るコードなど)は、2つの値を受け取ることになります。だからインデックスとして使う列を設定するときは、index.is_uniqueを1行確認する価値があります。
行と列を同時に
.locと.ilocのどちらも、カンマで区切って2つの引数を取ります——行が先、列が後です。それぞれに単一のラベル、リスト、スライスが使えます。
print(orders.loc[2:4, ["product", "price"]])product price
2 bag 850.0
3 bottle 120.0
4 eraser 8.0print(orders.loc[[0, 3], ["branch", "product"]])branch product
0 north pen
3 east bottle.locの包含ルールは列のスライスにも適用されます——「branchからcategoryまですべての列」。:だけ書くと「全行」を意味します。
print(orders.loc[:, "branch":"category"])branch product category
0 north pen stationery
1 south notebook stationery
2 north bag accessories
3 east bottle accessories
4 north eraser stationery
5 south bag accessories
6 east pen stationery
7 north bottle accessories.ilocでも位置を使って同じことができます。列も0から数えるので、branchは列2、productは列3です。
print(orders.iloc[[0, 3], [2, 3]])branch product
0 north pen
3 east bottle2つ前の.loc版と同じ結果ですが、読みにくさに注目してください。[2, 3]は何も語りませんが、["branch", "product"]はすべてを語り、しかもファイルの先頭に列が追加されても正しく動作します。名前がわかっているなら名前を使いましょう。
返ってくる形は?
単一の値、Series、DataFrameのどれが返るかは、カンマの両側に何を入れるかだけで決まります。単一のラベルはその次元を消し、リストまたはスライスはその次元を残します。
print(type(by_id.loc[1006, "price"]).__name__)
print(type(by_id.loc[1006, ["product", "price"]]).__name__)
print(type(by_id.loc[[1003, 1006], "price"]).__name__)
print(type(by_id.loc[[1003, 1006], ["product", "price"]]).__name__)float64
Series
Series
DataFrame- ラベル1つ、ラベル1つ → 単一の値
- ラベル1つ、リストまたはスライス →
Series(1行) - リストまたはスライス、ラベル1つ →
Series(1列) - リストまたはスライス、リストまたはスライス →
DataFrame
「コードを書く前に」の手順3——答えの形を描く——をルール化したものです。1行であっても表として返したいなら、両側にリストを置きましょう:by_id.loc[[1006], ["price"]]。
値1つ:.locと.at
経理担当者の質問——注文1006はいくらだったか?——は1つのセルです。行のラベルと列名の組み合わせです。
print(by_id.loc[1006, "price"])
print(by_id.at[1006, "price"])850.0
850.0どちらも同じ数値を返します。.atは行と列をそれぞれ1つだけ受け取ります。それ以外は受け付けません。そのぶんわずかに高速で、意図が明確になります——これは1つのセルだ、と。(位置版のツインは.iatで、by_id.iat[5, 5]のように使います。)読みやすいほうを使ってください。.locでも問題ありません。
単一の値は普通の数値なのでそのまま計算に使えます。注文1004の合計金額——数量×価格:
print(by_id.at[1004, "quantity"] * by_id.at[1004, "price"])840.0選択結果は独立したオブジェクト
実際のプログラムで選択を使うには、もう1つ理解しておくことがあります——選択を変更するとどうなるか、です。
import pandas as pd
orders = pd.read_csv("orders.csv")
prices = orders["price"]
prices[0] = 999.0
print(prices.head(2))
print(orders.loc[0, "price"])0 999.0
1 60.0
Name: price, dtype: float64
15.0pricesは変わりました。ordersは変わっていません。pandas 3では、すべての選択は独立したコピーとして振る舞います——自由に変更しても、元の表には一切触れません。(内部的にはpandasは書き込みが発生するまで実際のコピーを避けています——これがそのメカニズムの名前、Copy-on-Writeです——しかし必要なのは仕組みではなく振る舞いのルールです。)
複数列の選択でも同じです。
stock = orders[["product", "price"]]
stock["price"] = 0
print(orders["price"].head(2))0 15.0
1 60.0
Name: price, dtype: float64これは安全な動作です——選択結果を受け取って編集する関数が、裏で元の表を壊してしまう心配がありません。ただし、知っておくべき重要な注意点が1つあります。元の表を意図的に変更したい場合は、元の表に対して1ステップで.locを使う必要があります。
orders.loc[0, "price"] = 16.0
print(orders.loc[0, "price"])16.0やりたくなる2ステップ版——列を選択してからセルを設定する——は動作しません。pandas 3はそれを教えてくれます。
orders["price"][0] = 1.0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentprint(orders.loc[0, "price"])16.0まだ16.0です——代入は一時的なコピーに対して行われ、破棄されました。orders["price"]が選択結果を生成し、[0] = 1.0はその選択結果を変更しただけで、ordersは変更されませんでした。これがチェーン代入と呼ばれるものです。=の左側に括弧が2つ連続するパターンです。修正方法は警告が示すとおり常に同じです。行と列の両方を指定した1つの.loc、orders.loc[0, "price"] = 1.0を使います。
古いチュートリアルを読むと、SettingWithCopyWarningと「元の表が変わったり変わらなかったりする」という説明が出てきます。それはpandas 1と2の話です。pandas 3ではルールはシンプルで常に同じです——選択は元の表を変えない。元の表への.locだけが変える。
完全な例
章の冒頭にあった3つのリクエストを、1つのプログラムで答えます。select_orders.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
# 1. Check what arrived before pointing into it
print("Shape :", orders.shape)
print("Columns:", list(orders.columns))
# 2. Name the rows by what they are
by_id = orders.set_index("order_id")
print("Order ids unique:", by_id.index.is_unique)
print()
# Stock team: two columns, every row
stock = orders[["product", "quantity"]]
print("Stock view:", stock.shape)
print(stock.head(3))
print()
# Accountant: one order by its number, then one cell
print(by_id.loc[1006, ["product", "quantity", "price"]])
print("Order 1006 total:", by_id.at[1006, "quantity"] * by_id.at[1006, "price"])
print()
# Manager: the last three orders, a question about position
print(by_id.iloc[-3:, [1, 2, 4]])Shape : (8, 7)
Columns: ['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
Order ids unique: True
Stock view: (8, 2)
product quantity
0 pen 12
1 notebook 5
2 bag 2
product bag
quantity 1
price 850.0
Name: 1006, dtype: object
Order 1006 total: 850.0
branch product quantity
order_id
1006 south bag 1
1007 east pen 20
1008 north bottle 4このように書いた理由:
- 指し示す前に確認している。
shapeとlist(columns)を最初に表示しているので、スペルミスや余分なスペースが含まれる列名が選択を失敗させる前に目で確認できます。 - インデックスが一意かどうかを確認してから注文を検索します。そのため
by_id.loc[1006]は確実に1行、by_id.at[...]は確実に1つの数値になります。 - 各リクエストに合ったツールを使っています。 列は名前で括弧を使い、注文は同一性で
.locと.atを使い、「最後の3件」は位置で.ilocを使います。 - 在庫ビューの形を表示しています——
(8, 2)はまさに描いた形、全注文・2列です。 - 最後の選択には改善の余地があります。
[1, 2, 4]はby_idにおいてたまたまbranch、product、quantityを指しているにすぎません——order_idがインデックスになったため位置が1つずれている点に注意してください。位置による指定は壊れやすいものです。自分のコードではby_id.iloc[-3:][["branch", "product", "quantity"]]やby_id.loc[by_id.index[-3:], ["branch", "product", "quantity"]]のように名前を使って書けば、同じ意図を明確に表現できます。
問題が起きたとき
KeyError: 'Price' その名前の列は存在しません。列名は大文字と小文字を区別します——priceとPriceは別の名前です。list(df.columns)を表示してそこから名前をコピーしてください。名前のリストを使う場合、orders[["product", "Price"]]ではKeyError: "['Price'] not in index"となり、どの名前が見つからなかったかが正確にわかります。ドット記法の場合、orders.PriceはAttributeError: 'DataFrame' object has no attribute 'Price'になります。原因も修正方法も同じです。
KeyError: 'price'——ファイルには明らかにpriceが存在するのに発生する場合 ヘッダーのカンマの後にスペースが入っている可能性があります——order_id, product, priceのように、そのスペースが名前の一部になっています。
from io import StringIO
import pandas as pd
raw = "order_id, product, price\n1001, pen, 15.0\n"
padded = pd.read_csv(StringIO(raw))
print(list(padded.columns))
padded["price"]['order_id', ' product', ' price']
KeyError: 'price'リストに' price'の先頭スペースが見えます。これが第3章でリスト形式で列を表示するよう言った理由です。読み込み時にpd.read_csv("file.csv", skipinitialspace=True)で修正するか、読み込み後にdf.columns = df.columns.str.strip()で修正してください。
KeyError: ('product', 'price') orders["product", "price"]と書いています——括弧が1組しかありません。内側のリストなしでは、Pythonは2つの名前を1つのタプルとして渡し、pandasは('product', 'price')という名前の列を1つ探します。orders[["product", "price"]]と書いてください。
.locからのKeyError: 0 位置を.locに渡しています。表が切り取られた(tail、フィルタ、ソート)ためにラベル0が存在しないか、インデックスを設定したため注文番号がラベルになっています。「先頭の行」を意味するなら.iloc[0]と書いてください。インデックスが文字列の表で、スライスを使って同じミスをした場合(orders.set_index("product").loc[0:2])は、代わりにTypeError: cannot do slice indexing on Index with these indexers [0] of type intが発生します。修正方法は同じで、.iloc[0:2]です。
IndexError: single positional indexer is out-of-bounds .ilocに存在しない位置が渡されました——8行の表でorders.iloc[8]を指定した場合などです(位置は0から7までです)。df.shapeを確認してください。末尾の行は常にdf.iloc[-1]です。列の位置が大きすぎる場合も同じメッセージが出ます——orders.iloc[:, 7]など。
ValueError: Location based indexing can only have [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types .ilocに列名を渡しています——orders.iloc[0, "branch"]のように。.ilocは位置しか受け取りません。ラベルを使うなら.locを使ってください——orders.loc[0, "branch"]——あるいは位置が本当に必要ならorders.columns.get_loc("branch")で調べてください(2が返ります)。
ステップ 4 / 6 — 予想する
理解度をチェック
同じ表で、角括弧の中の数値も同じです。何が出力されますか?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
print(len(orders.loc[2:5]), len(orders.iloc[2:5]))- A4 4
- B3 4
- C3 3
- D4 3
これは末尾3件の注文のうち最初の注文の製品名を出力することを意図したコードです。実際には何が起こりますか?また、その理由は何ですか?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
last3 = orders.tail(3)
# Wanted: the product of the first of these three orders
print(last3.loc[0, "product"])- A3行のうち先頭行の製品である`bag`が出力される
- B`IndexError` — 表には3行しかないため
- C`KeyError: 0` — `tail(3)`はラベル5、6、7を保持しており、`.loc`はラベル0を探すため
- D元の表の先頭行の製品である`pen`が出力される
Seriesではなく、price列のみを保持するDataFrameが必要です。それを取得できるのはどの行ですか?
- Aorders.loc[:, "price"]
- Borders.price
- Corders["price"]
- Dorders[["price"]]
回答にはアカウントが必要です
ログインして答えを確認
問題は上にあります。頭の中で答えを考えることがいちばん大切です。ログインすると、正解・解説・3 段階のヒントを確認できます。
やってみよう
同じorders.csvを使ってselect.pyを書いてください。コードの前に、各選択が何を返すか——値1つ、Series、DataFrame——と行数をコメントで書きます。プログラムは4つの条件を満たす必要があります。
- 列は常に名前で選択する——
date、branch、quantityを合わせたshapeを表示する。 - インデックスが一意であることを確認した上で、
order_idでインデックスを設定した表から同一性を使って注文を検索する——注文1007の行全体を表示し、注文1002から1005のproductとbranchを4件すべて表示する。 - 「先頭」と「末尾」は位置で答える——先頭2件と末尾2件の
productとpriceを表示する。そして.atを使って注文1005の数量と注文1003の合計金額(数量×価格)を表示する。 - インデックス付きの表で注文1001の価格を1つの
.locで16.0に変更し、変更が反映されたことを表示で証明する。
出力はこのとおりになるはずです。
(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0次に、以下の3行それぞれについて結果を予測してから書き留め、実行してください。
by_id.loc[0]orders.iloc[8]by_id["price"][1001] = 99.0——実行後、注文1001の価格をもう一度表示する
解答
まず形を描く。 コメントブロックは「コードを書く前に」の手順3にある、各選択が何を返すかのスケッチです。
- 3列 →
orders[[...]]→ 形が(8, 3)のDataFrame - 1件の注文を同一性で →
by_id.loc[1007]→Series(1行) - 同一性で4件の注文、2列 →
by_id.loc[1002:1005, [...]]→ 4×2のDataFrame(.locは1005を含む) - 先頭2件と末尾2件を位置で →
.iloc[:2]と.iloc[-2:]→ 2×2のDataFrameが2つ - セル1つ →
.at→ 普通の数値 - 元の表を変更 →
by_idに対して1つの.loc→ 表自体が変わる
select.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
# Columns by name -> a DataFrame of 8 rows, 3 columns
print(orders[["date", "branch", "quantity"]].shape)
# Rows named by their order number; check the names are unique first
by_id = orders.set_index("order_id")
print("unique:", by_id.index.is_unique)
# One order by identity -> a Series; four orders -> 4 x 2 (loc includes 1005)
print(by_id.loc[1007])
print(by_id.loc[1002:1005, ["product", "branch"]])
# First two and last two by place -> .iloc for rows, names for columns
print(by_id.iloc[:2][["product", "price"]])
print(by_id.iloc[-2:][["product", "price"]])
# Single cells -> plain numbers
print("qty of 1005:", by_id.at[1005, "quantity"])
print("total of 1003:", by_id.at[1003, "quantity"] * by_id.at[1003, "price"])
# Change the original, in one step
by_id.loc[1001, "price"] = 16.0
print("price of 1001:", by_id.at[1001, "price"])(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0すべての結果が計画と一致しています。3列で(8, 3)になり、.locは終端を含むため1002:1005は4件となり、位置による選択はそれぞれ2行になります。条件3では行を位置で、列を名前で取り出しています——行に.ilocを使い、続けて名前のリストを指定しているため、コードの意図がそのまま表現されています。
3つの予測:
by_id.loc[0]KeyError: 0ラベルは今や注文番号です。注文0は存在しません。位置で先頭の行を取るならby_id.iloc[0]です。
orders.iloc[8]IndexError: single positional indexer is out-of-bounds8行なので位置は0から7までです。末尾の行はorders.iloc[7]、またはより良い書き方としてorders.iloc[-1]です。
by_id["price"][1001] = 99.0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentprint(by_id.at[1001, "price"])16.0まだ16.0です——by_id["price"]が独立した選択を作り、99.0はそこに入り、by_idには入りませんでした。条件4が成功してこれが失敗した違いは、.loc1つと連続した括弧2つの差だけです。