[pandas超入門]DataFrameの要素を選択するさまざまな方法Pythonデータ処理入門

pandasのDataFrameオブジェクトの要素を選択するにはたくさんの方法があります。その中からat属性とiat属性、それからブーリアンインデクシングと呼ばれる方法を用いてアクセスする方法を紹介しましょう。

» 2024年06月07日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「Pythonデータ処理入門」のインデックス

連載目次

本シリーズと本連載について

 本シリーズ「Pythonデータ処理入門」は、Pythonの基礎をマスターした人を対象に以下のような、Pythonを使ってデータを処理しようというときに便利に使えるツールやライブラリ、フレームワークの使い方の基礎を説明するものです。

  • NumPy(「NumPy超入門」の目次はこちら
  • pandas(本連載)
  • Matplotlib

 なお、本連載では以下のバージョンを使用しています。

  • Python 3.12
  • pandas 2.2.1

 第3回ではブラケット「[]」やiloc属性/loc属性を用いて行や列、特定要素を選択する方法を紹介しました。が、pandasのDataFrameオブジェクトではその他にもさまざまな方法で行や列(Seriesオブジェクト)、DataFrameオブジェクトを構成する一部のDataFrameオブジェクト、特定の要素などを選択できます。今回はそれらの方法を紹介していきます。

at属性/iat属性による特定要素の選択

 前述の通り、第3回ではブラケットやiloc属性/loc属性を使ってDataFrameオブジェクトの特定の行や列、要素を選択する方法を紹介しました。繰り返しになりますが、以下にその例を示しておきましょう。

 なお、ここでは以下のようなDataFrameオブジェクトを定義して例として使うことにします。

df = pd.DataFrame([['kawasaki', 99, 175, 80],
                   ['isshiki', 45, 160, 60],
                   ['endo', 55, 165, 70]],
                   columns=['name', 'age', 'height', 'weight'])

print(df)
# 出力結果:
#       name  age  height  weight
#0  kawasaki   99     175      80
#1   isshiki   45     160      60
#2      endo   55     165      70

サンプルのDataFrameオブジェクト

 以下にブラケットに列名を指定して列を選択する例、loc属性に列ラベル(と全行を意味するコロン「:」)を指定して列を選択する例、iloc属性に行インデックスを指定して行を選択する例、loc属性に行と列を指定して特定の要素を選択する例を示します。おさらいなので詳しい解説は省略します。

# ブラケットに列を指定して選択
s = df['name']
print(s)
# 出力結果:
#0    kawasaki
#1     isshiki
#2        endo
#Name: name, dtype: object

# loc属性を使って列を選択
s = df.loc[:, 'name'# 全ての行の'name'列を選択
print(s)
# 出力結果:
#0    kawasaki
#1     isshiki
#2        endo
#Name: name, dtype: object

# iloc属性を使って行を選択
s = df.iloc[0# 第0行を選択
print(s)
# 出力結果:
#name      kawasaki
#age             99
#height         175
#weight          80
#Name: 0, dtype: object

# loc属性を使って特定の要素を選択
item = df.loc[0, 'age'# 第0行の'age'列の値を選択
print(item)  # 99

特定の行や列、要素をブラケット、iloc属性、loc属性で選択する

 DataFrameオブジェクトにはiloc属性とloc属性に似たiat属性at属性があります。iat属性では整数インデックスを指定して、at属性では軸ラベルを指定するというのもiloc属性とloc属性と同様です。ただし、大きな違いがあります。それはiat属性とat属性では行と列の指定が必須であり、かつスライスは指定できない点です。簡単にいうと、特定の要素を1つだけ選択するのにこれらの属性を使用します。

 以下に例を示します。

age = df.iat[0, 1# 第0行の第1列('age'列)の値を選択
print(age)  # 99

df.iat[0, 1] = 100  # 第0行の第1列('age'列)の値を変更
print(df)
# 出力結果:
#       name  age  height  weight
#0  kawasaki  100     175      80
#1   isshiki   45     160      60
#2      endo   55     165      70

height = df.at[1, 'height'# 第1行の'height'列の値を選択
print(height)  # 160

weights = df.at[1:, 'weight'# KeyError例外

iloc属性とloc属性による要素の選択

 最初の例は第0行の第1列('age'列)の値をiat属性で選択しているところです。iatに続くブランケットには「[行インデックス, 列インデックス]」の順で要素を特定する値を記述します。

 次の例では同じ方法で特定要素を選択し、そこに新しく値を代入しています。loc/iloc/at/iatの各属性を使って要素を選択して、そこに代入するだけで、特定要素の値を変更できるということです。

 最後の例ではat属性を使って最初の例と同様な処理を行っていますが、使っているのが軸ラベルである点が異なります(行の指定では整数インデックスを使っているように見えますが、あくまでも軸ラベルとして扱われている点には覚えておきましょう)。

ブラケットによる列選択と属性としての列選択

 これまでブラケットに列ラベルを記述することで、その列を選択するコードを書いてきました。しかし、「DataFrameオブジェクト名」にドット「.」と列名を記述することでも列を選択できます。以下に例を示します。

s = df.name  # 'name'列を選択
print(s)
# 出力結果:
#0    kawasaki
#1     isshiki
#2        endo
#Name: name, dtype: object

「DataFrameオブジェクト.列名」という記法による列選択

 ただし、この記法ではDataFrameオブジェクトが持っている属性やメソッドと同じ名前の列は選択できないようです。

df2 = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=['loc', 'iloc', 'info'])
print(df2.loc)  # <pandas.core.indexing._LocIndexer object at 0x11745fca0>
print(df2['loc'])
# 出力結果:
#0    1
#1    4
#Name: loc, dtype: int64

DataFrameオブジェクトが持つ属性と同じ名前の'loc'列は選択できなかった

 この例では、わざとらしく列名を'loc'、'iloc'、'info'とDataFrameオブジェクトが持つ属性とメソッドの名前に設定しています。そして、「df2.loc」としたときに'loc'列が選択されるかどうかを試していますが、出力結果からはそうはなっていないことが分かります。

 一方、「df2.loc['loc']」としたときには'loc'列が選択されました。ブラケットに列名を含める書き方であれば常に列が選択できますが、タイプする文字数が少し増えます。ドットに続けて列名を記述する方がタイプ量は減りますが、望んだ結果が得られない可能性もあります(そういう列名の付け方をするなという話ですが)。一貫した記述が求められるのであればブラケットを使うのがよいかもしれませんね。


ブーリアンインデクシング

 DataFrameオブジェクトから特定の条件を満たしている行だけを選択したいということはよくあります。こうしたときに便利に使えるのが「ブーリアンインデクシング」と呼ばれる手法です。

簡単な例

 ブーリアンインデクシングを実際に試してみる前に、サンプルのDataFrameオブジェクトの要素を確認しておきましょう。

print(df)
# 出力結果:
#       name  age  height  weight
#0  kawasaki  100     175      80
#1   isshiki   45     160      60
#2      endo   55     165      70

サンプルのDataFrameオブジェクトの要素の値を確認

 ブーリアンインデクシングは真偽値(True/False)を要素とするSeriesオブジェクトや配列、リストなどをブランケットの中に含めることで、その要素がTrue(真)となっている行だけを選択するものです。このとき、ブランケットに含めるSeriesオブジェクトや配列、リストの要素数は行数と一致している必要があります。

 例えば、次のように[True, False, True]というリストをブラケットの中に含めるとどうなるでしょう(要素数の3はこのDataFrameオブジェクトの行数に一致している点に注目)。

result = df[[True, False, True]]
print(result)
# 出力結果:
#       name  age  height  weight
#0  kawasaki  100     175      80
#2      endo   55     165      70

Trueに対応する行だけが選択されている

 DataFrameオブジェクトには[True, False, True]というリストを渡しているので、その値がTrueである第0要素と第2要素に対応する第0行と第2行が選択されていることが分かりますね。

 ここで、先のDataFrameオブジェクトで体重('weight'列)が60より大きな行だけを取り出したいとします。これは次のようなコードで調べられます。

mask = df['weight'] > 60
print(mask)
# 出力結果:
#0     True
#1    False
#2     True
#Name: weight, dtype: bool

'weight'列の要素の値が60より大きいかどうかを調べる

 変数maskには'weight'列の要素の値が60より大きいかどうかに応じてTrueかFalseの値を格納するSeriesオブジェクトが代入されます。この場合は、出力結果を見ると分かる通り、第0行と第2行の要素の値がTrueで、第1行の要素の値がFalseとなります(上のリストの要素と同じなのをわざとらしく感じる人もいるかもしれませんね)。また、'weight'列の各要素に対してその値が60より大きいかどうかを比較しているので、得られるSeriesオブジェクトの要素数は行数に等しくなっている点も覚えておきましょう。

 そして、元のDataFrameオブジェクトに対してこれをブラケットに囲んで指定することで、そこからSeriesオブジェクトの要素の値がTrueとなっているものに対応する行だけを選択できます。

result = df[mask]
print(result)
# 出力結果:
#       name  age  height  weight
#0  kawasaki  100     175      80
#2      endo   55     165      70

'weight'列の要素の値が60より大きい行だけが選択されている

 上の例では条件判定の結果を変数maskに保存していましたが、そうする必要は特にありません。ブラケットの中に直接記述可能です。

result = df[df['weight'] > 60]
print(result)  # 上と同じ結果

ブラケットの中に条件を直接記述

複数の条件を指定する

 また、条件を複数指定することも可能です。ただし、このときに注意する点として、それぞれの条件をかっこ「()」で囲むことと、以下の演算子でそれらをつなぐことです。

  • OR演算(いずれかの条件がTrueなら選択):|演算子
  • AND演算(全ての条件がTrueなら選択):&演算子
  • NOT演算(指定した演算がFalseなら選択):~演算子

 例えば、'weight'列の要素の値が60より大きくて、かつ'height'列の値が170より大きい行だけを選択するのなら次のようなコードを書きます。

result = df[(df['weight'] > 60) & (df['height'] > 170)]
print(result)
# 出力結果:
#       name  age  height  weight
#0  kawasaki  100     175      80

複数の条件を満たす行だけを選択

 ここでは「(df['weight'] > 60)」と「(df['height'] > 170)」という2つの条件を&演算子でつないでいるので、両方の条件が一致したときにだけ式全体「(df['weight'] > 60) & (df['height'] > 170)」がTrueになります。よって、これに合致する第0列だけが選択されたわけです。今も述べましたが、次のように条件を囲むかっこを忘れると例外が発生するので確実にかっこで囲むようにしましょう。

result = df[df['weight'] > 60 & df['height'] > 170# ValueError

複数の条件があるときに、それらを囲むかっこを忘れるとValueError例外が発生する

loc属性やiloc属性と組み合わせる

 なお、ブーリアンインデクシングはloc属性やiloc属性と組み合わせることも可能です(at属性やiat属性では使えません)。以下はloc属性と組み合わせて使用する例です。

mask = (df['height'] > 160) & (df['weight'] > 70)
print(mask)
# 出力結果:
#0     True
#1    False
#2    False

result = df.loc[mask, 'name':]
print(result)
# 出力結果:
#      name  age  height  weight
#0  kawasaki  100     175      80

ブーリアンインデクシングをloc属性と組み合わせて使用する

 ただし、iloc属性と組み合わせる場合は、ブーリアンインデクシングで使用するのはSeriesオブジェクトではなく配列(やリストなど)にしてください。配列は簡単にはSeriesオブジェクトのvalues属性で取得できます。

result = df.iloc[mask.values, 0:]
print(result)  # 出力結果は上と同じ

ブーリアンインデクシングをiloc属性と組み合わせて使用する

より高度な方法

 真偽値を返す関数やラムダ式を使って条件を判定することも可能です。以下に簡単な例を示します。

result = df[lambda x: x['weight'] > 60]
print(result)
# 出力結果:
#       name  age  height  weight
#0  kawasaki  100     175      80
#2      endo   55     165      70

ラムダ式を使って条件に合致する行を選択

 もちろん、この程度の簡単な例であれば、次のように書けるでしょう。

result = df[df['weight'] > 60]
print(result)  # 上と同じ結果

同じことをラムダ式を使わずに判定

 あるいはDataFrameオブジェクトのqueryメソッドを使って次のようにも書けます(queryメソッドについては後続の回で紹介できたらいいなと思っています)。

result = df.query('weight > 60')
print(result)  # 上と同じ結果

同じことをqueryメソッドを使って判定

 また、DataFrameオブジェクトやSeriesオブジェクトのisinメソッドを使うと、DataFrameオブジェクトの各要素の値がisinメソッドに渡した値一覧に含まれているかどうかを調べることも可能です(isinメソッドについても後続の回で紹介したいと考えています)。

mask = df['name'].isin(['isshiki', 'endo'])
print(mask)
# 出力結果:
#0    False
#1     True
#2     True
#Name: name, dtype: bool

result = df[mask]
print(result)
# 出力結果:
#      name  age  height  weight
#1  isshiki   45     160      60
#2     endo   55     165      70

isinメソッドで'name'列の要素の値が'isshiki'か'endo'な行を選択する

 最初のコード群ではisinメソッドを使って、'name'列の要素の値が'isshiki'か'endo'に合致するかどうかを調べ、その結果を変数maskに保存しています。そして、次のコード群でその結果を基にDataFrameオブジェクトで対応する行のみを選択しています。

 余談ではありますが、Pythonプログラマーの心構えを説いた「Zen of Python」という文言集には「There should be one-- and preferably only one --obvious way to do it.」(何かをするのに、これしかないという方法が1つ、できれば1つだけあるはずだ)というものがあります。このことからすると、上で見たように同じことをするのに幾つも方法があるというのは、実はあまりPython的ではないと言えるかもしれません。

 しかし、これは逆にpandasがどれほどの柔軟性を持っているか、プログラマーの頭に浮かんだ処理をどれだけ簡単にコードに落とし込めるかを表しているとも言えるでしょう。コードだけではなく、大量のデータも相手にしようというときに、思い付いたアイデアを「この処理はこれこれこうすればうまいことイケそう」などと考えながらPythonのプログラムに翻訳するのではなく、なるべくそのままの形でプログラムとして記述できるのであれば、その方がストレスなく自分の作業に没頭できるかもしれません。


 次回はDataFrameオブジェクトが持つ要素をさまざまな形で調べたり、加工したりする方法について見ていく予定です。

「Pythonデータ処理入門」のインデックス

Pythonデータ処理入門

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

AI for エンジニアリング
「サプライチェーン攻撃」対策
1P情シスのための脆弱性管理/対策の現実解
OSSのサプライチェーン管理、取るべきアクションとは
Microsoft & Windows最前線2024
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。