本記事は、Pandas の公式ドキュメントのUser Guide - MultiIndex / Advanced Indexingを機械翻訳した後、一部の不自然な文章を手直ししたものである。
誤訳の指摘・代訳案・質問等があればコメント欄や編集リクエストでお願いします。
Pandas公式ドキュメント日本語訳記事一覧
- データの取得と選択
- マルチインデックス・高度な索引
- mergeとjoinとconcatenateとcompare
- テーブルの整形とピボットテーブル
- テキストデータの操作
- 欠損データの操作
- 重複ラベル
- カテゴリデータ
- Group by - 分割・適用・結合
マルチインデックス・高度な索引
この章では、マルチインデックスによる索引およびその他の高度な索引機能について説明します。
基本的な索引に関するドキュメントは、データの索引と選択を参照してください。
代入操作において、コピーと参照のどちらが返されるのかは、場合によって異なります。これは連鎖代入chained assignment
と呼ばれ、避けるべきものです。返るのはビューかコピーかを参照してください。
より応用的な操作については、cook bookもご覧ください。
階層型インデックス(マルチインデックス)
階層型・マルチレベルのインデックスは、特に高次元のデータを扱う場合、高度なデータ分析と操作を行うのに非常に有用です。簡潔には、Series
(一次元)やDataFrame
(二次元)のような低次元のデータ構造に、任意の次元数のデータを格納し、操作できるようになります。
この節では、「階層型」インデックスとは具体的にどのような意味なのか、そしてそれが上記および前の章で説明したpandasの全てのインデックス機能とどのように統合されるかを示します。後にグループ化とテーブルの整形とピボットテーブルについて説明するとき、分析のためのデータの構造化にどのように役立つかを説明するための重要なアプリケーションを紹介します。
より応用的なやり方については、cook bookもご覧ください。
マルチインデックス(階層型インデックス)オブジェクトの作成
MultiIndex
は、一般的にpandasオブジェクトの軸ラベルを格納する通常のIndex
オブジェクトが、階層的になったものです。また、それぞれがユニークなタプルの配列と考えることもできます。MultiIndex
は、(MultiIndex.from_arrays()
を使用して)配列のリスト、(MultiIndex.from_tuples()
を使用して)タプルの配列、(MultiIndex.from_product()
を使用して)イテラブルの直積、(MultiIndex.from_frame()
を使用して)DataFrame
から作成することができます。Index
コンストラクタは、タプルのリストが渡されるとMultiIndex
を返そうとします。以下に、マルチインデックスを初期化するさまざまな方法を紹介します。
arrays = [
["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
["one", "two", "one", "two", "one", "two", "one", "two"],
]
tuples = list(zip(*arrays))
tuples
# [('bar', 'one'),
# ('bar', 'two'),
# ('baz', 'one'),
# ('baz', 'two'),
# ('foo', 'one'),
# ('foo', 'two'),
# ('qux', 'one'),
# ('qux', 'two')]
index = pd.MultiIndex.from_tuples(tuples, names=["first", "second"])
index
# MultiIndex([('bar', 'one'),
# ('bar', 'two'),
# ('baz', 'one'),
# ('baz', 'two'),
# ('foo', 'one'),
# ('foo', 'two'),
# ('qux', 'one'),
# ('qux', 'two')],
# names=['first', 'second'])
s = pd.Series(np.random.randn(8), index=index)
s
# first second
# bar one 0.469112
# two -0.282863
# baz one -1.509059
# two -1.135632
# foo one 1.212112
# two -0.173215
# qux one 0.119209
# two -1.044236
# dtype: float64
2つのイテラブルの要素のすべての組み合わせ(直積)がほしい場合は、MultiIndex.from_product()
メソッドを使用するのが便利です。
iterables = [["bar", "baz", "foo", "qux"], ["one", "two"]]
pd.MultiIndex.from_product(iterables, names=["first", "second"])
# MultiIndex([('bar', 'one'),
# ('bar', 'two'),
# ('baz', 'one'),
# ('baz', 'two'),
# ('foo', 'one'),
# ('foo', 'two'),
# ('qux', 'one'),
# ('qux', 'two')],
# names=['first', 'second'])
MultiIndex.from_frame()
メソッドを使用して、DataFrame
から直接MultiIndex
を作成することもできます。これはMultiIndex.to_frame()
を補完するメソッドです。
df = pd.DataFrame(
[["bar", "one"], ["bar", "two"], ["foo", "one"], ["foo", "two"]],
columns=["first", "second"],
)
pd.MultiIndex.from_frame(df)
# MultiIndex([('bar', 'one'),
# ('bar', 'two'),
# ('foo', 'one'),
# ('foo', 'two')],
# names=['first', 'second'])
また便利な方法として、配列のリストをSeries
またはDataFrame
に直接渡すことで、MultiIndex
を自動的に作成できます。
arrays = [
np.array(["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"]),
np.array(["one", "two", "one", "two", "one", "two", "one", "two"]),
]
s = pd.Series(np.random.randn(8), index=arrays)
s
# bar one -0.861849
# two -2.104569
# baz one -0.494929
# two 1.071804
# foo one 0.721555
# two -0.706771
# qux one -1.039575
# two 0.271860
# dtype: float64
df = pd.DataFrame(np.random.randn(8, 4), index=arrays)
df
# 0 1 2 3
# bar one -0.424972 0.567020 0.276232 -1.087401
# two -0.673690 0.113648 -1.478427 0.524988
# baz one 0.404705 0.577046 -1.715002 -1.039268
# two -0.370647 -1.157892 -1.344312 0.844885
# foo one 1.075770 -0.109050 1.643563 -1.469388
# two 0.357021 -0.674600 -1.776904 -0.968914
# qux one -1.294524 0.413738 0.276662 -0.472035
# two -0.013960 -0.362543 -0.006154 -0.923061
すべてのMultiIndex
コンストラクタは、レベル自身の文字列名(ラベル)を格納するnames
引数を受け取ります。名前が指定されていない場合は、None
が割り当てられます。
df.index.names
# FrozenList([None, None])
このインデックスはpandasオブジェクトのどの方向の軸にも設定することができ、またインデックスのレベルの数は自由に決められます。
df = pd.DataFrame(np.random.randn(3, 8), index=["A", "B", "C"], columns=index)
df
# first bar baz ... foo qux
# second one two one ... two one two
# A 0.895717 0.805244 -1.206412 ... 1.340309 -1.170299 -0.226169
# B 0.410835 0.813850 0.132003 ... -1.187678 1.130127 -1.436737
# C -1.413681 1.607920 1.024180 ... -2.211372 0.974466 -2.006747
#
# [3 rows x 8 columns]
pd.DataFrame(np.random.randn(6, 6), index=index[:6], columns=index[:6])
# first bar baz foo
# second one two one two one two
# first second
# bar one -0.410001 -0.078638 0.545952 -1.219217 -1.226825 0.769804
# two -1.281247 -0.727707 -0.121306 -0.097883 0.695775 0.341734
# baz one 0.959726 -1.110336 -0.619976 0.149748 -0.732339 0.687738
# two 0.176444 0.403310 -0.154951 0.301624 -2.179861 -1.369849
# foo one -0.954208 1.462696 -1.743161 -0.826591 -0.345352 1.314232
# two 0.690579 0.995761 2.396780 0.014871 3.357427 -0.317441
pandasでは、コンソール出力を見やすくするために、より高いレベルのインデックスを「スパース」しています。インデックスの表示方法は、pandas.set_options()
のmulti_sparse
オプションを使用して制御できます。
with pd.option_context("display.multi_sparse", False):
df
不可分な単一のラベルとしてタプルを用いても問題ないことは覚えておいてもよいでしょう。
pd.Series(np.random.randn(8), index=tuples)
# (bar, one) -1.236269
# (bar, two) 0.896171
# (baz, one) -0.487602
# (baz, two) -0.082240
# (foo, one) -2.182937
# (foo, two) 0.380396
# (qux, one) 0.084844
# (qux, two) 0.432390
# dtype: float64
MultiIndex
がなぜ重要なのかというと、以下および以降の章で説明するように、MultiIndex
を利用してグループ化・選択・形状変更操作を実行できるためです。後のセクションで見るように、自分で明示的にMultiIndex
を作成することなく、階層的にインデックス付けされたデータを操作することができます。ただし、ファイルからデータを読み込む場合、データセットを準備するときに独自のMultiIndex
を生成したいこともあるかもしれません。
レベルラベルの再構築
get_level_values()
メソッドは、特定のレベルの各位置ラベルのベクトルを返します。
index.get_level_values(0)
# Index(['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'], dtype='object', name='first')
index.get_level_values('second')
# Index(['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two'], dtype='object', name='second')
マルチインデックスを使用した軸の基本的なインデックス付け
階層型インデックスの重要な機能の1つは、データ内のサブグループを識別する「partial」ラベルによってデータを選択できることです。partial選択は、通常のデータフレームの列を選択するのとまったく同じ方法で、結果の階層型インデックスのレベルを「ドロップ」します。
df["bar"]
# second one two
# A 0.895717 0.805244
# B 0.410835 0.813850
# C -1.413681 1.607920
df["bar", "one"]
# A 0.895717
# B 0.410835
# C -1.413681
# Name: (bar, one), dtype: float64
df["bar"]["one"]
# A 0.895717
# B 0.410835
# C -1.413681
# Name: one, dtype: float64
s["qux"]
# one -1.039575
# two 0.271860
# dtype: float64
より深いレベルで選択する方法については、階層インデックス付きの断面を参照してください。
レベル定義
MultiIndex
は、実際に使用されていなくても、定義されたすべてのレベルのインデックスを保持します。インデックスをスライスするとき、これに気付くかもしれません。例えば、
df.columns.levels # もとのマルチインデックス
# FrozenList([['bar', 'baz', 'foo', 'qux'], ['one', 'two']])
df[["foo","qux"]].columns.levels # スライスしたもの
# FrozenList([['bar', 'baz', 'foo', 'qux'], ['one', 'two']])
これは、レベルの再計算を避けてスライスのパフォーマンスを向上させるための実装です。使用されているレベルのみを表示したい場合は、get_level_values()
メソッドを使用します。
df[["foo", "qux"]].columns.to_numpy()
# array([('foo', 'one'), ('foo', 'two'), ('qux', 'one'), ('qux', 'two')],
# dtype=object)
# 特定のレベルの場合
df[["foo", "qux"]].columns.get_level_values(0)
# Index(['foo', 'foo', 'qux', 'qux'], dtype='object', name='first')
使用レベルのみでMultiIndex
を再構築するには、remove_unused_levels()
メソッドを使用できます。
new_mi = df[["foo", "qux"]].columns.remove_unused_levels()
new_mi.levels
# FrozenList([['foo', 'qux'], ['one', 'two']])
データのアライメントとreindex
の使用
軸にMultiIndex
を持つ異なるインデックスのオブジェクト間の操作は、期待どおりに機能します。データのアライメントは、タプルのインデックスと同じように機能します。
s + s[:-2]
# bar one -1.723698
# two -4.209138
# baz one -0.989859
# two 2.143608
# foo one 1.443110
# two -1.413542
# qux one NaN
# two NaN
# dtype: float64
s + s[::2]
# bar one -1.723698
# two NaN
# baz one -0.989859
# two NaN
# foo one 1.443110
# two NaN
# qux one -2.079150
# two NaN
# dtype: float64
Series
/DataFrames
のreindex()
メソッドは、別のMultiIndex
、またはタプルのリストまたは配列も受け取ることができます。
s.reindex(index[:3])
# first second
# bar one -0.861849
# two -2.104569
# baz one -0.494929
# dtype: float64
s.reindex([("foo", "two"), ("bar", "one"), ("qux", "one"), ("baz", "one")])
# foo two -0.706771
# bar one -0.861849
# qux one -1.039575
# baz one -0.494929
# dtype: float64
階層型インデックスを使用した高度なインデックス作成
MultiIndex
に.loc
を用いて高度なインデックス作成を行わせることは構文的に少し難しいですが、私達はそれを実現するためにあらゆる努力をしました。一般に、マルチインデックスのキーはタプルの形式を取ります。たとえば、以下は期待どおりに機能します。
df = df.T
df
# A B C
# first second
# bar one 0.895717 0.410835 -1.413681
# two 0.805244 0.813850 1.607920
# baz one -1.206412 0.132003 1.024180
# two 2.565646 -0.827317 0.569605
# foo one 1.431256 -0.076467 0.875906
# two 1.340309 -1.187678 -2.211372
# qux one -1.170299 1.130127 0.974466
# two -0.226169 -1.436737 -2.006747
df.loc[("bar", "two")]
# A 0.805244
# B 0.813850
# C 1.607920
# Name: (bar, two), dtype: float64
この例の場合、df.loc['bar', 'two']
でも機能しますが、この略記法は一般的に誤解を招く可能性があることに注意してください。
.loc
を使用して特定の列から索引を行う場合は、次のようにタプルを使用しなければなりません。
df.loc[("bar", "two"), "A"]
# 0.8052440253863785
タプルの最初の要素のみを渡す場合は、MultiIndex のすべてのレベルを指定する必要はありません。たとえば次のように「partial」インデックスを使用して、第1レベルにbar
を持つすべての要素を取得できます。
df.loc["bar"]
# A B C
# second
# one 0.895717 0.410835 -1.413681
# two 0.805244 0.813850 1.607920
これは、より冗長な表記であるdf.loc[('bar',),]
(この例ではまたdf.loc['bar',]
と同等)のショートカットです。
「Partial」スライスも非常にうまく機能します。
df.loc["baz":"foo"]
# A B C
# first second
# baz one -1.206412 0.132003 1.024180
# two 2.565646 -0.827317 0.569605
# foo one 1.431256 -0.076467 0.875906
# two 1.340309 -1.187678 -2.211372
タプルのスライスを渡すことにより、値の「範囲」でスライスできます。
df.loc[("baz", "two"):("qux", "one")]
# A B C
# first second
# baz two 2.565646 -0.827317 0.569605
# foo one 1.431256 -0.076467 0.875906
# two 1.340309 -1.187678 -2.211372
# qux one -1.170299 1.130127 0.974466
df.loc[("baz", "two"):"foo"]
# A B C
# first second
# baz two 2.565646 -0.827317 0.569605
# foo one 1.431256 -0.076467 0.875906
# two 1.340309 -1.187678 -2.211372
インデックスの再作成と同様、ラベルまたはタプルのリストを渡すこともできます。
df.loc[[("bar", "two"), ("qux", "one")]]
# A B C
# first second
# bar two 0.805244 0.813850 1.607920
# qux one -1.170299 1.130127 0.974466
pandasでは、インデックス作成の際にタプルとリストが同じように扱われないことに注意してください。タプルは1つのマルチレベルキーとして解釈されますが、リストは複数のキーを指します。言い換えると、タプルは水平に移動し(レベルの横断)、リストは垂直に移動します(レベルのスキャン)。
重要なことに、タプルのリストは複数の完全なMultiIndex
キーを引きますが、リストのタプルはレベル内のいくつかの値を参照します。
s = pd.Series(
[1, 2, 3, 4, 5, 6],
index=pd.MultiIndex.from_product([["A", "B"], ["c", "d", "e"]]),
)
s.loc[[("A", "c"), ("B", "d")]] # タプルのリスト
# A c 1
# B d 5
# dtype: int64
s.loc[(["A", "B"], ["c", "d"])] # リストのタプル
# A c 1
# d 2
# B c 4
# d 5
# dtype: int64
スライサーの使用
複数のインデクサを与えることにより、MultiIndex
をスライスできます。
ラベルによる選択で見た、スライス・ラベルのリスト・ラベル・真偽値配列のどのセレクターも、同様に使用することができます。
slice(None)
を使用して、そのレベルのすべての要素を選択できます。すべてのより深いレベルを指定する必要はありません。それらはslice(None)
によって暗示されます。
他と同様、これはラベルのインデックス付けであるため、スライサーの両側が含まれます。
.loc
では、すべての軸を指定する必要があります。つまり、indexとcolumnsの両方のインデクサを渡してください。渡されたインデクサが、行のMultiIndex
などではなく、両方の軸のインデックスとして誤って解釈される可能性がある、あいまいなケースがいくつかあります。
以下のように記述してください。
df.loc[(slice("A1", "A3"), ...), :] # noqa: E999
以下のようには記述しないでください。
df.loc[(slice("A1", "A3"), ...)] # noqa: E999
def mklbl(prefix, n):
return ["%s%s" % (prefix, i) for i in range(n)]
miindex = pd.MultiIndex.from_product(
[mklbl("A", 4), mklbl("B", 2), mklbl("C", 4), mklbl("D", 2)]
)
micolumns = pd.MultiIndex.from_tuples(
[("a", "foo"), ("a", "bar"), ("b", "foo"), ("b", "bah")], names=["lvl0", "lvl1"]
)
dfmi = (
pd.DataFrame(
np.arange(len(miindex) * len(micolumns)).reshape(
(len(miindex), len(micolumns))
),
index=miindex,
columns=micolumns,
)
.sort_index()
.sort_index(axis=1)
)
dfmi
# lvl0 a b
# lvl1 bar foo bah foo
# A0 B0 C0 D0 1 0 3 2
# D1 5 4 7 6
# C1 D0 9 8 11 10
# D1 13 12 15 14
# C2 D0 17 16 19 18
# ... ... ... ... ...
# A3 B1 C1 D1 237 236 239 238
# C2 D0 241 240 243 242
# D1 245 244 247 246
# C3 D0 249 248 251 250
# D1 253 252 255 254
#
# [64 rows x 4 columns]
スライス・リスト・ラベルを使用した基本的なマルチインデックススライシング。
dfmi.loc[(slice("A1", "A3"), slice(None), ["C1", "C3"]), :]
# lvl0 a b
# lvl1 bar foo bah foo
# A1 B0 C1 D0 73 72 75 74
# D1 77 76 79 78
# C3 D0 89 88 91 90
# D1 93 92 95 94
# B1 C1 D0 105 104 107 106
# ... ... ... ... ...
# A3 B0 C3 D1 221 220 223 222
# B1 C1 D0 233 232 235 234
# D1 237 236 239 238
# C3 D0 249 248 251 250
# D1 253 252 255 254
#
# [24 rows x 4 columns]
Pandas.IndexSlice
を使用して、slice(None)
ではなく:
を使用したより自然な構文を用いることができます。
idx = pd.IndexSlice
dfmi.loc[idx[:, :, ["C1", "C3"]], idx[:, "foo"]]
# lvl0 a b
# lvl1 foo foo
# A0 B0 C1 D0 8 10
# D1 12 14
# C3 D0 24 26
# D1 28 30
# B1 C1 D0 40 42
# ... ... ...
# A3 B0 C3 D1 220 222
# B1 C1 D0 232 234
# D1 236 238
# C3 D0 248 250
# D1 252 254
#
# [32 rows x 2 columns]
このメソッドを使用すると、非常に複雑な選択を複数の軸で同時に実行することができます。
dfmi.loc["A1", (slice(None), "foo")]
# lvl0 a b
# lvl1 foo foo
# B0 C0 D0 64 66
# D1 68 70
# C1 D0 72 74
# D1 76 78
# C2 D0 80 82
# ... ... ...
# B1 C1 D1 108 110
# C2 D0 112 114
# D1 116 118
# C3 D0 120 122
# D1 124 126
#
# [16 rows x 2 columns]
dfmi.loc[idx[:, :, ["C1", "C3"]], idx[:, "foo"]]
# lvl0 a b
# lvl1 foo foo
# A0 B0 C1 D0 8 10
# D1 12 14
# C3 D0 24 26
# D1 28 30
# B1 C1 D0 40 42
# ... ... ...
# A3 B0 C3 D1 220 222
# B1 C1 D0 232 234
# D1 236 238
# C3 D0 248 250
# D1 252 254
#
# [32 rows x 2 columns]
ブールインデクサを使用すると、値に関連する選択を提供できます。
mask = dfmi[("a", "foo")] > 200
dfmi.loc[idx[mask, :, ["C1", "C3"]], idx[:, "foo"]]
# lvl0 a b
# lvl1 foo foo
# A3 B0 C1 D1 204 206
# C3 D0 216 218
# D1 220 222
# B1 C1 D0 232 234
# D1 236 238
# C3 D0 248 250
# D1 252 254
.loc
にaxis
引数を指定して、単一の軸で渡されたスライサーを解釈することもできます。
dfmi.loc(axis=0)[:, :, ["C1", "C3"]]
# lvl0 a b
# lvl1 bar foo bah foo
# A0 B0 C1 D0 9 8 11 10
# D1 13 12 15 14
# C3 D0 25 24 27 26
# D1 29 28 31 30
# B1 C1 D0 41 40 43 42
# ... ... ... ... ...
# A3 B0 C3 D1 221 220 223 222
# B1 C1 D0 233 232 235 234
# D1 237 236 239 238
# C3 D0 249 248 251 250
# D1 253 252 255 254
#
# [32 rows x 4 columns]
さらに、次の方法を使用して値を代入できます。
df2 = dfmi.copy()
df2.loc(axis=0)[:, :, ["C1", "C3"]] = -10
df2
# lvl0 a b
# lvl1 bar foo bah foo
# A0 B0 C0 D0 1 0 3 2
# D1 5 4 7 6
# C1 D0 -10 -10 -10 -10
# D1 -10 -10 -10 -10
# C2 D0 17 16 19 18
# ... ... ... ... ...
# A3 B1 C1 D1 -10 -10 -10 -10
# C2 D0 241 240 243 242
# D1 245 244 247 246
# C3 D0 -10 -10 -10 -10
# D1 -10 -10 -10 -10
#
# [64 rows x 4 columns]
整列可能なオブジェクトの右辺も使用できます。
df2 = dfmi.copy()
df2.loc[idx[:, :, ["C1", "C3"]], :] = df2 * 1000
df2
# lvl0 a b
# lvl1 bar foo bah foo
# A0 B0 C0 D0 1 0 3 2
# D1 5 4 7 6
# C1 D0 9000 8000 11000 10000
# D1 13000 12000 15000 14000
# C2 D0 17 16 19 18
# ... ... ... ... ...
# A3 B1 C1 D1 237000 236000 239000 238000
# C2 D0 241 240 243 242
# D1 245 244 247 246
# C3 D0 249000 248000 251000 250000
# D1 253000 252000 255000 254000
#
# [64 rows x 4 columns]
Cross-section
DataFrame
のxs()
メソッドは、さらにMultiIndex
の特定のレベルでデータを選択しやすくするために level 引数を取ります。
df
# A B C
# first second
# bar one 0.895717 0.410835 -1.413681
# two 0.805244 0.813850 1.607920
# baz one -1.206412 0.132003 1.024180
# two 2.565646 -0.827317 0.569605
# foo one 1.431256 -0.076467 0.875906
# two 1.340309 -1.187678 -2.211372
# qux one -1.170299 1.130127 0.974466
# two -0.226169 -1.436737 -2.006747
df.xs("one", level="second")
# A B C
# first
# bar 0.895717 0.410835 -1.413681
# baz -1.206412 0.132003 1.024180
# foo 1.431256 -0.076467 0.875906
# qux -1.170299 1.130127 0.974466
# スライスの使用
df.loc[(slice(None), "one"), :]
# A B C
# first second
# bar one 0.895717 0.410835 -1.413681
# baz one -1.206412 0.132003 1.024180
# foo one 1.431256 -0.076467 0.875906
# qux one -1.170299 1.130127 0.974466
axis引数を指定することにより、xs
で列を選択することもできます。
df = df.T
df.xs("one", level="second", axis=1)
# first bar baz foo qux
# A 0.895717 -1.206412 1.431256 -1.170299
# B 0.410835 0.132003 -0.076467 1.130127
# C -1.413681 1.024180 0.875906 0.974466
# スライスの使用
df.loc[:, (slice(None), "one")]
# first bar baz foo qux
# second one one one one
# A 0.895717 -1.206412 1.431256 -1.170299
# B 0.410835 0.132003 -0.076467 1.130127
# C -1.413681 1.024180 0.875906 0.974466
xs
では、複数のキーを使用した選択も可能です。
df.xs(("one", "bar"), level=("second", "first"), axis=1)
# first bar
# second one
# A 0.895717
# B 0.410835
# C -1.413681
# スライスの使用
df.loc[:, ("bar", "one")]
# A 0.895717
# B 0.410835
# C -1.413681
# Name: (bar, one), dtype: float64
drop_level=False
をxs
に渡すと、選択したレベルを保持できます。
df.xs("one", level="second", axis=1, drop_level=False)
# first bar baz foo qux
# second one one one one
# A 0.895717 -1.206412 1.431256 -1.170299
# B 0.410835 0.132003 -0.076467 1.130127
# C -1.413681 1.024180 0.875906 0.974466
上の結果をdrop_level=True
(デフォルト値)の場合と比べてみてください。
df.xs("one", level="second", axis=1, drop_level=True)
# first bar baz foo qux
# A 0.895717 -1.206412 1.431256 -1.170299
# B 0.410835 0.132003 -0.076467 1.130127
# C -1.413681 1.024180 0.875906 0.974466
高度な再インデックス付けとアライメント
pandas オブジェクトのreindex()
およびalign()
メソッドでlevel
引数を使用すると、レベル全体に値をブロードキャストするのに役立ちます。例えば:
midx = pd.MultiIndex(
levels=[["zero", "one"], ["x", "y"]], codes=[[1, 1, 0, 0], [1, 0, 1, 0]]
)
df = pd.DataFrame(np.random.randn(4, 2), index=midx)
df
# 0 1
# one y 1.519970 -0.493662
# x 0.600178 0.274230
# zero y 0.132885 -0.023688
# x 2.410179 1.450520
df2 = df.groupby(level=0).mean()
df2
# 0 1
# one 1.060074 -0.109716
# zero 1.271532 0.713416
df2.reindex(df.index, level=0)
# 0 1
# one y 1.060074 -0.109716
# x 1.060074 -0.109716
# zero y 1.271532 0.713416
# x 1.271532 0.713416
# アラインメント
df_aligned, df2_aligned = df.align(df2, level=0)
df_aligned
# 0 1
# one y 1.519970 -0.493662
# x 0.600178 0.274230
# zero y 0.132885 -0.023688
# x 2.410179 1.450520
df2_aligned
# 0 1
# one y 1.060074 -0.109716
# x 1.060074 -0.109716
# zero y 1.271532 0.713416
# x 1.271532 0.713416
swaplevel
によるレベルの順序交換
swaplevel()
メソッドは、2 つのレベルの順序を交換することができます。
df[:5]
# 0 1
# one y 1.519970 -0.493662
# x 0.600178 0.274230
# zero y 0.132885 -0.023688
# x 2.410179 1.450520
df[:5].swaplevel(0, 1, axis=0)
# 0 1
# y one 1.519970 -0.493662
# x one 0.600178 0.274230
# y zero 0.132885 -0.023688
# x zero 2.410179 1.450520
reorder_levels
によるレベルの並べ替え
reorder_levels()
メソッドは、swaplevel
メソッドを一般化して、階層的インデックスのレベルを1ステップで置換できるようにします。
df[:5].reorder_levels([1, 0], axis=0)
# 0 1
# y one 1.519970 -0.493662
# x one 0.600178 0.274230
# y zero 0.132885 -0.023688
# x zero 2.410179 1.450520
Index
あるいはMultiIndex
の名前の変更
通常DataFrame
の列名を変更するために使用されるrename()
メソッドは、MultiIndex
のラベルの名前を変更することもできます。 rename
のcolumns
引数には名前を変更する列のみを含む辞書を指定できます。
df.rename(columns={0: "col0", 1: "col1"})
# col0 col1
# one y 1.519970 -0.493662
# x 0.600178 0.274230
# zero y 0.132885 -0.023688
# x 2.410179 1.450520
このメソッドは、DataFrame
のメインインデックスの特定のラベルの名前を変更するためにも使用できます。
df.rename(index={"one": "two", "y": "z"})
# 0 1
# two z 1.519970 -0.493662
# x 0.600178 0.274230
# zero z 0.132885 -0.023688
# x 2.410179 1.450520
rename_axis()
メソッドは、Index
またはMultiIndex
の名前(name
属性)を変更します。特に、MultiIndex
のレベルの名前を指定できますが、これは後でreset_index()
を使用して値をMultiIndex
から通常の列に移動する場合に便利です。
df.rename_axis(index=["abc", "def"])
# 0 1
# abc def
# one y 1.519970 -0.493662
# x 0.600178 0.274230
# zero y 0.132885 -0.023688
# x 2.410179 1.450520
DataFrame
の列はインデックスであるため、rename_axis
をcolumns
引数とともに使用すると、そのインデックスの名前が変更されることに注意してください。
df.rename_axis(columns="Cols").columns
# RangeIndex(start=0, stop=2, step=1, name='Cols')
Rename
およびrename_axis
は、ラベル/名前を新しい値にマッピングするための、辞書・Series
・マッピング関数の指定をサポートしています。
DataFrame
経由ではなく、Index
オブジェクトを直接操作する場合、Index.set_names()
を使用して名前を変更できます。
mi = pd.MultiIndex.from_product([[1, 2], ["a", "b"]], names=["x", "y"])
mi.names
# FrozenList(['x', 'y'])
mi2 = mi.rename("new name", level=0)
mi2
# MultiIndex([(1, 'a'),
# (1, 'b'),
# (2, 'a'),
# (2, 'b')],
# names=['new name', 'y'])
レベルを介してマルチインデックスの名前を設定することはできません。
mi.levels[0].name = "name via level"
# ---------------------------------------------------------------------------
# RuntimeError Traceback (most recent call last)
# Input In [100], in <module>
# ----> 1 mi.levels[0].name = "name via level"
#
# File ~/work/pandas/pandas/pandas/core/indexes/base.py:1728, in Index.name(self, value)
# 1724 @name.setter
# 1725 def name(self, value: Hashable):
# 1726 if self._no_setting_name:
# 1727 # Used in MultiIndex.levels to avoid silently ignoring name updates.
# -> 1728 raise RuntimeError(
# 1729 "Cannot set name on a level of a MultiIndex. Use "
# 1730 "'MultiIndex.set_names' instead."
# 1731 )
# 1732 maybe_extract_name(value, None, type(self))
# 1733 self._name = value
#
# RuntimeError: Cannot set name on a level of a MultiIndex. Use 'MultiIndex.set_names' instead.
代わりにIndex.set_names()
を使用してください。
MultiIndex
のソート
MultiIndex
が適用されたオブジェクトを効果的にインデックスおよびスライスするには、ソートする必要があります。他のインデックスと同様に、sort_index()
を使用できます。
import random
random.shuffle(tuples)
s = pd.Series(np.random.randn(8), index=pd.MultiIndex.from_tuples(tuples))
s
# baz two 0.206053
# qux two -0.251905
# foo two -2.213588
# bar one 1.063327
# qux one 1.266143
# foo one 0.299368
# baz one -0.863838
# bar two 0.408204
# dtype: float64
s.sort_index()
# bar one 1.063327
# two 0.408204
# baz one -0.863838
# two 0.206053
# foo one 0.299368
# two -2.213588
# qux one 1.266143
# two -0.251905
# dtype: float64
s.sort_index(level=0)
# bar one 1.063327
# two 0.408204
# baz one -0.863838
# two 0.206053
# foo one 0.299368
# two -2.213588
# qux one 1.266143
# two -0.251905
# dtype: float64
s.sort_index(level=1)
# bar one 1.063327
# baz one -0.863838
# foo one 0.299368
# qux one 1.266143
# bar two 0.408204
# baz two 0.206053
# foo two -2.213588
# qux two -0.251905
# dtype: float64
MultiIndex
のレベルに名前が付けられている場合は、レベル名をsort_index
に渡すことができます。
s.index.set_names(["L1", "L2"], inplace=True)
s.sort_index(level="L1")
# L1 L2
# bar one 1.063327
# two 0.408204
# baz one -0.863838
# two 0.206053
# foo one 0.299368
# two -2.213588
# qux one 1.266143
# two -0.251905
# dtype: float64
s.sort_index(level="L2")
# L1 L2
# bar one 1.063327
# baz one -0.863838
# foo one 0.299368
# qux one 1.266143
# bar two 0.408204
# baz two 0.206053
# foo two -2.213588
# qux two -0.251905
# dtype: float64
高次元のオブジェクトでは、MultiIndex
がある場合、インデックス以外の軸でもレベルでソートできます。
df.T.sort_index(level=1, axis=1)
# one zero one zero
# x x y y
# 0 0.600178 2.410179 1.519970 0.132885
# 1 0.274230 1.450520 -0.493662 -0.023688
インデックス付けは、データが並べ替えられていない場合でも機能しますが、かなり非効率的です(そしてPerformanceWarning
が表示されます)。また、ビューではなくデータのコピーを返します。
dfm = pd.DataFrame(
{"jim": [0, 0, 1, 1], "joe": ["x", "x", "z", "y"], "jolie": np.random.rand(4)}
)
dfm = dfm.set_index(["jim", "joe"])
dfm
# jolie
# jim joe
# 0 x 0.490671
# x 0.120248
# 1 z 0.537020
# y 0.110968
dfm.loc[(1, 'z')]
# PerformanceWarning: indexing past lexsort depth may impact performance.
# jolie
# jim joe
# 1 z 0.64094
さらに、完全にレックスソートされていない場合に索引すると、以下のようなエラーが発生することがあります。
dfm.loc[(0, 'y'):(1, 'z')]
# UnsortedIndexError: 'Key length (2) was greater than MultiIndex lexsort depth (1)'
MultiIndex
のis_monotonic_increasing()
メソッドは、インデックスがソートされているかどうかを表示します。
dfm.index.is_monotonic_increasing
# False
dfm = dfm.sort_index()
dfm
# jolie
# jim joe
# 0 x 0.490671
# x 0.120248
# 1 y 0.110968
# z 0.537020
dfm.index.is_monotonic_increasing
# True
これで、期待通りに選択が機能するようになりました。
dfm.loc[(0, "y"):(1, "z")]
# jolie
# jim joe
# 1 y 0.110968
# z 0.537020
takeメソッド
NumPyのndarraysと同様に、pandasのIndex
・Series
・DataFrame
は、特定のインデックスで特定の軸に沿って要素を取得するtake()
メソッドも提供します。指定されたインデックスは、リストまたは整数インデックス位置のndarrayでなければなりません。take
は、オブジェクトの末尾からの相対位置として負の整数も受け取ることができます。
index = pd.Index(np.random.randint(0, 1000, 10))
index
# Int64Index([214, 502, 712, 567, 786, 175, 993, 133, 758, 329], dtype='int64')
positions = [0, 9, 3]
index[positions]
# Int64Index([214, 329, 567], dtype='int64')
index.take(positions)
# Int64Index([214, 329, 567], dtype='int64')
ser = pd.Series(np.random.randn(10))
ser.iloc[positions]
# 0 -0.179666
# 9 1.824375
# 3 0.392149
# dtype: float64
ser.take(positions)
# 0 -0.179666
# 9 1.824375
# 3 0.392149
# dtype: float64
DataFrameの場合、指定されたインデックスは、行または列の位置を指定する一次元のリストまたはndarrayである必要があります。
frm = pd.DataFrame(np.random.randn(5, 3))
frm.take([1, 4, 3])
# 0 1 2
# 1 -1.237881 0.106854 -1.276829
# 4 0.629675 -1.425966 1.857704
# 3 0.979542 -1.633678 0.615855
frm.take([0, 2], axis=1)
# 0 2
# 0 0.595974 0.601544
# 1 -1.237881 -1.276829
# 2 -0.767101 1.499591
# 3 0.979542 0.615855
# 4 0.629675 1.857704
pandasオブジェクトのtake
メソッドは、ブールインデックスで動作するように意図されておらず、予期しない結果を返す可能性があることに注意してください。
arr = np.random.randn(10)
arr.take([False, False, True, True])
# array([-1.1935, -1.1935, 0.6775, 0.6775])
arr[[0, 1]]
# array([-1.1935, 0.6775])
ser = pd.Series(np.random.randn(10))
ser.take([False, False, True, True])
# 0 0.233141
# 0 0.233141
# 1 -0.223540
# 1 -0.223540
# dtype: float64
ser.iloc[[0, 1]]
# 0 0.233141
# 1 -0.223540
# dtype: float64
最後に、パフォーマンスについて少し触れておくと、take
メソッドはより狭い範囲の入力を処理するため、ファンシーインデックスよりもはるかに高速なパフォーマンスを提供できます。
arr = np.random.randn(10000, 5)
indexer = np.arange(10000)
random.shuffle(indexer)
%timeit arr[indexer]
%timeit arr.take(indexer, axis=0)
# 186 us +- 1.53 us per loop (mean +- std. dev. of 7 runs, 10,000 loops each)
# 56.1 us +- 5.76 us per loop (mean +- std. dev. of 7 runs, 10,000 loops each)
ser = pd.Series(arr[:, 0])
%timeit ser.iloc[indexer]
%timeit ser.take(indexer)
# 103 us +- 7.31 us per loop (mean +- std. dev. of 7 runs, 10,000 loops each)
# 83.9 us +- 6.52 us per loop (mean +- std. dev. of 7 runs, 10,000 loops each)
インデックスの型
ここまででMultiIndex
についてかなり広範囲に説明しました。DatetimeIndex
およびPeriodIndex
に関するドキュメントはこちら、TimedeltaIndex
に関するドキュメントはこちらを参照してください。
以下のサブセクションでは、他のいくつかのインデックスタイプを強調します。
CategoricalIndex
CategoricalIndex
は、重複のある索引をサポートするのに役立つインデックスです。これは、Categorical
を囲むコンテナであり、多数の重複した要素を含むインデックスの効率的なインデックス作成と保存を可能にします。
from pandas.api.types import CategoricalDtype
df = pd.DataFrame({"A": np.arange(6), "B": list("aabbca")})
df["B"] = df["B"].astype(CategoricalDtype(list("cab")))
df
# A B
# 0 0 a
# 1 1 a
# 2 2 b
# 3 3 b
# 4 4 c
# 5 5 a
df.dtypes
# A int64
# B category
# dtype: object
df["B"].cat.categories
# Index(['c', 'a', 'b'], dtype='object')
インデックスを設定すると、CategoricalIndex
が作成されます。
df2 = df.set_index("B")
df2.index
# CategoricalIndex(['a', 'a', 'b', 'b', 'c', 'a'], categories=['c', 'a', 'b'], ordered=False, dtype='category', name='B')
__getitem__/.iloc/.loc
を使用した索引は、Index
と同様に機能します。インデクサはカテゴリに属していなければなりません。そうでない場合はKeyError
が発生します。
df2.loc["a"]
# A
# B
# a 0
# a 1
# a 5
CategoricalIndex
は、索引後も保持されます。
df2.loc["a"].index
# CategoricalIndex(['a', 'a', 'a'], categories=['c', 'a', 'b'], ordered=False, dtype='category', name='B')
インデックスを並べ替えると、カテゴリの順序で並べ替えられます(CategoricalDtype(list('cab'))
でインデックスを作成したため、cab
の順に並び替えられます)。
df2.sort_index()
# A
# B
# c 4
# a 0
# a 1
# a 5
# b 2
# b 3
インデックスに対するGroupby操作でも同様に、インデックスの性質が保持されます。
df2.groupby(level=0).sum()
# A
# B
# c 4
# a 6
# b 5
df2.groupby(level=0).sum().index
# CategoricalIndex(['c', 'a', 'b'], categories=['c', 'a', 'b'], ordered=False, dtype='category', name='B')
インデックスの再作成操作は、渡されたインデクサの型に基づいて結果のインデックスを返します。リストを渡すと、普通のIndex
が返されます。Categorical
を渡すと、その渡されたCategorical
dtypeのカテゴリに従ってインデックス付けされたCategoricalIndex
を返します。これにより、pandasのインデックスを再作成する方法と同様に、カテゴリに存在しない値でもこれらを任意にインデックス化できます。
df3 = pd.DataFrame(
{"A": np.arange(3), "B": pd.Series(list("abc")).astype("category")}
)
df3 = df3.set_index("B")
df3
# A
# B
# a 0
# b 1
# c 2
df3.reindex(["a", "e"])
# A
# B
# a 0.0
# e NaN
df3.reindex(["a", "e"]).index
# Index(['a', 'e'], dtype='object', name='B')
df3.reindex(pd.Categorical(["a", "e"], categories=list("abe")))
# A
# B
# a 0.0
# e NaN
df3.reindex(pd.Categorical(["a", "e"], categories=list("abe"))).index
# CategoricalIndex(['a', 'e'], categories=['a', 'b', 'e'], ordered=False, dtype='category', name='B')
CategoricalIndex
に対して再形成や比較といった操作を行う場合は、同じカテゴリを持っている必要があります。そうでない場合はTypeError
が発生します。
df4 = pd.DataFrame({"A": np.arange(2), "B": list("ba")})
df4["B"] = df4["B"].astype(CategoricalDtype(list("ab")))
df4 = df4.set_index("B")
df4.index
# CategoricalIndex(['b', 'a'], categories=['a', 'b'], ordered=False, dtype='category', name='B')
df5 = pd.DataFrame({"A": np.arange(2), "B": list("bc")})
df5["B"] = df5["B"].astype(CategoricalDtype(list("bc")))
df5 = df5.set_index("B")
df5.index
# CategoricalIndex(['b', 'c'], categories=['b', 'c'], ordered=False, dtype='category', name='B')
pd.concat([df4, df5])
# TypeError: categories must match existing categories when appending
Int64Index と RangeIndex
バージョン1.4.0から非推奨:pandasのバージョン2.0では、Int64Index
・Float64Index
・UInt64Index
の代わりにIndex
が数値型のデフォルトインデックス型となり、したがってこれらのインデックス型は非推奨であり、将来のバージョンで削除される予定です。RangeIndex
は整数インデックスの最適化されたバージョンであるため、削除されることはありません。
Int64Index
は、pandasのインデックスの基礎中の基礎です。これは、順序付けられたスライス可能なセットを実装する不変の配列です。
RangeIndex
は、すべてのNDFrame
オブジェクトのデフォルトインデックスを提供するInt64Index
のサブクラスです。RangeIndex
は、Int64Index
が単調な順序セットを表すために最適化されたバージョンです。これらはPythonのrange型
に類似しています。
Float64Index
バージョン1.4.0から非推奨:pandasのバージョン2.0では、Int64Index
・Float64Index
・UInt64Index
の代わりにIndex
が数値型のデフォルトインデックス型となり、したがってこれらのインデックス型は非推奨であり、将来のバージョンで削除される予定です。RangeIndex
は整数インデックスの最適化されたバージョンであるため、削除されることはありません。
既定では、Float64Index
は、インデックス作成で浮動少数、または整数と浮動小数の混合値を渡すときに自動的に作成されます。これにより、スカラーインデックスとスライシングの[]
・ix
・loc
をまったく同じように動作させる純粋なラベルベースのスライシングパラダイムが可能になります。
indexf = pd.Index([1.5, 2, 3, 4.5, 5])
indexf
# Float64Index([1.5, 2.0, 3.0, 4.5, 5.0], dtype='float64')
sf = pd.Series(range(5), index=indexf)
sf
# 1.5 0
# 2.0 1
# 3.0 2
# 4.5 3
# 5.0 4
# dtype: int64
[]
・.loc
のスカラー選択は、常にラベルベースになります。整数の指定は、等しいfloatインデックスに一致します(たとえば、3
は3.0
と同等です)。
sf[3]
# 2
sf[3.0]
# 2
sf.loc[3]
# 2
sf.loc[3.0]
# 2
唯一の位置インデックスはiloc
経由です。
sf.iloc[3]
# 3
見つからないスカラーインデックスはKeyError
を送出します。スライスは、主に[]
・ix
・loc
を使用する場合はインデックスの値に基づいており、iloc
を使用する場合は常に位置に基づきます。例外は、スライスが真偽値の場合です。この場合、常に位置に基づきます。
sf[2:4]
# 2.0 1
# 3.0 2
# dtype: int64
sf.loc[2:4]
# 2.0 1
# 3.0 2
# dtype: int64
sf.iloc[2:4]
# 3.0 2
# 4.5 3
# dtype: int64
floatインデックスでは、浮動小数を使用したスライスを使うことができます。
sf[2.1:4.6]
# 3.0 2
# 4.5 3
# dtype: int64
sf.loc[2.1:4.6]
# 3.0 2
# 4.5 3
# dtype: int64
floatインデックスでない場合、浮動小数を使用したスライスはTypeError
を発生させます。
pd.Series(range(5))[3.5]
# TypeError: the label [3.5] is not a proper indexer for this index type (Int64Index)
pd.Series(range(5))[3.5:4.5]
# TypeError: the slice start [3.5] is not a proper indexer for this index type (Int64Index)
このタイプのインデックスを使用する典型的な使用例を紹介します。データがfloatとして記録されている、不規則なtimedeltaライクのインデックススキームを想像してください。これは、例えばミリ秒のオフセットかもしれません。
dfir = pd.concat(
[
pd.DataFrame(
np.random.randn(5, 2), index=np.arange(5) * 250.0, columns=list("AB")
),
pd.DataFrame(
np.random.randn(6, 2),
index=np.arange(4, 10) * 250.1,
columns=list("AB"),
),
]
)
dfir
# A B
# 0.0 -0.435772 -1.188928
# 250.0 -0.808286 -0.284634
# 500.0 -1.815703 1.347213
# 750.0 -0.243487 0.514704
# 1000.0 1.162969 -0.287725
# 1000.4 -0.179734 0.993962
# 1250.5 -0.212673 0.909872
# 1500.6 -0.733333 -0.349893
# 1750.7 0.456434 -0.306735
# 2000.8 0.553396 0.166221
# 2250.9 -0.101684 -0.734907
選択操作は、すべての選択演算子に対して常に値ベースで機能します。
dfir[0:1000.4]
# A B
# 0.0 -0.435772 -1.188928
# 250.0 -0.808286 -0.284634
# 500.0 -1.815703 1.347213
# 750.0 -0.243487 0.514704
# 1000.0 1.162969 -0.287725
# 1000.4 -0.179734 0.993962
dfir.loc[0:1001, "A"]
# 0.0 -0.435772
# 250.0 -0.808286
# 500.0 -1.815703
# 750.0 -0.243487
# 1000.0 1.162969
# 1000.4 -0.179734
# Name: A, dtype: float64
dfir.loc[1000.4]
# A -0.179734
# B 0.993962
# Name: 1000.4, dtype: float64
次のようにしてデータの最初の1秒(1000ミリ秒)を取得できます。
dfir[0:1000]
# A B
# 0.0 -0.435772 -1.188928
# 250.0 -0.808286 -0.284634
# 500.0 -1.815703 1.347213
# 750.0 -0.243487 0.514704
# 1000.0 1.162969 -0.287725
整数位置ベースの選択が必要な場合は、iloc
を使用してください。
dfir.iloc[0:5]
# A B
# 0.0 -0.435772 -1.188928
# 250.0 -0.808286 -0.284634
# 500.0 -1.815703 1.347213
# 750.0 -0.243487 0.514704
# 1000.0 1.162969 -0.287725
IntervalIndex
IntervalIndex
は、独自のdtypeであるIntervalDtype
およびInterval
スカラー型とともに、pandasでインターバル表記の第一級にサポートします。
IntervalIndex
はいくつかの一意のインデックスを作成でき、cut()
およびqcut()
のカテゴリの戻り値の型としても使用されます。
IntervalIndex
による索引
IntervalIndex
は、Series
およびDataFrame
でインデックスとして使用できます。
df = pd.DataFrame(
{"A": [1, 2, 3, 4]}, index=pd.IntervalIndex.from_breaks([0, 1, 2, 3, 4])
)
df
# A
# (0, 1] 1
# (1, 2] 2
# (2, 3] 3
# (3, 4] 4
インターバルの端に沿った.loc
を介したラベルベースのインデックスは、期待どおりに機能し、その特定のインターバルを選択します。
df.loc[2]
# A 2
# Name: (1, 2], dtype: int64
df.loc[[2, 3]]
# A
# (1, 2] 2
# (2, 3] 3
インターバル内に含まれるラベルを選択すると、そのインターバルごと選択されます。
df.loc[2.5]
# A 3
# Name: (2, 3], dtype: int64
df.loc[[2.5, 3.5]]
# A
# (2, 3] 3
# (3, 4] 4
Interval
を使用して選択すると、完全に一致するもののみが返されます(pandas 0.25.0以降)。
df.loc[pd.Interval(1, 2)]
# A 2
# Name: (1, 2], dtype: int64
IntervalIndex
に正確に含まれていないInterval
を選択しようとすると、KeyError
が発生します。
df.loc[pd.Interval(0.5, 2.5)]
# ---------------------------------------------------------------------------
# KeyError: Interval(0.5, 2.5, closed='right')
特定のInterval
に重なるすべてのIntervals
を選択するには、overlaps()
メソッドを使用してブールインデクサを作成します。
idxr = df.index.overlaps(pd.Interval(0.5, 2.5))
idxr
# array([ True, True, True, False])
df[idxr]
# A
# (0, 1] 1
# (1, 2] 2
# (2, 3] 3
cut
およびqcut
を使用したデータのビンニング
cut()
とqcut()
は両方ともCategorical
オブジェクトを返し、それらが作成するビンは.categories
属性にIntervalIndex
として保存されます。
c = pd.cut(range(4), bins=2)
c
# [(-0.003, 1.5], (-0.003, 1.5], (1.5, 3.0], (1.5, 3.0]]
# Categories (2, interval[float64, right]): [(-0.003, 1.5] < (1.5, 3.0]]
c.categories
# IntervalIndex([(-0.003, 1.5], (1.5, 3.0]], dtype='interval[float64, right]')
cut()
は、bins
引数にIntervalIndex
を渡すこともできます。これにより、便利なpandasイディオムが可能になります。最初に、いくつかのデータとbins
を固定数に設定してcut()
を呼び出し、ビンを生成します。次に、その.categories
の値を、続いて呼び出したcut()
のbins
引数に渡すことで、新しいデータを同じビンでビニングすることができます。
pd.cut([0, 3, 5, 1], bins=c.categories)
# [(-0.003, 1.5], (1.5, 3.0], NaN, (-0.003, 1.5]]
# Categories (2, interval[float64, right]): [(-0.003, 1.5] < (1.5, 3.0]]
すべてのビンの外側にある値には、NaN
値が割り当てられます。
インターバルの範囲の作成
もし、一定の間隔のインターバルが必要な場合、interval_range()
関数を使用して、start
・end
・periods
のさまざまな組み合わせを使用してIntervalIndex
を作成できます。interval_range
のデフォルトの周期は、数値のインターバルの場合は1、datetimeライクなインターバルの場合は暦日です。
pd.interval_range(start=0, end=5)
# IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]], dtype='interval[int64, right]')
pd.interval_range(start=pd.Timestamp("2017-01-01"), periods=4)
# IntervalIndex([(2017-01-01, 2017-01-02], (2017-01-02, 2017-01-03], (2017-01-03, 2017-01-04], (2017-01-04, 2017-01-05]], dtype='interval[datetime64[ns], right]')
pd.interval_range(end=pd.Timedelta("3 days"), periods=3)
# IntervalIndex([(0 days 00:00:00, 1 days 00:00:00], (1 days 00:00:00, 2 days 00:00:00], (2 days 00:00:00, 3 days 00:00:00]], dtype='interval[timedelta64[ns], right]')
freq
引数は、デフォルト以外の周期を指定するために使用でき、datetimeライクなインターバルのではさまざまな周期エイリアスを利用できます。
pd.interval_range(start=0, periods=5, freq=1.5)
# IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0], (6.0, 7.5]], dtype='interval[float64, right]')
pd.interval_range(start=pd.Timestamp("2017-01-01"), periods=4, freq="W")
# IntervalIndex([(2017-01-01, 2017-01-08], (2017-01-08, 2017-01-15], (2017-01-15, 2017-01-22], (2017-01-22, 2017-01-29]], dtype='interval[datetime64[ns], right]')
pd.interval_range(start=pd.Timedelta("0 days"), periods=3, freq="9H")
# IntervalIndex([(0 days 00:00:00, 0 days 09:00:00], (0 days 09:00:00, 0 days 18:00:00], (0 days 18:00:00, 1 days 03:00:00]], dtype='interval[timedelta64[ns], right]')
さらに、closed
引数を使用して、インターバルを閉じる側を指定できます。デフォルトでは、インターバルは右側で閉じられます。
pd.interval_range(start=0, end=4, closed="both")
# IntervalIndex([[0, 1], [1, 2], [2, 3], [3, 4]], dtype='interval[int64, both]')
pd.interval_range(start=0, end=4, closed="neither")
# IntervalIndex([(0, 1), (1, 2), (2, 3), (3, 4)], dtype='interval[int64, neither]')
start
・end
・periods
を指定すると、結果として得られるIntervalIndex
は、start
からend
まで等間隔にperiods
の数だけ要素があるインターバルが作成されます。
pd.interval_range(start=0, end=6, periods=4)
# IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0]], dtype='interval[float64, right]')
pd.interval_range(pd.Timestamp("2018-01-01"), pd.Timestamp("2018-02-28"), periods=3)
# IntervalIndex([(2018-01-01, 2018-01-20 08:00:00], (2018-01-20 08:00:00, 2018-02-08 16:00:00], (2018-02-08 16:00:00, 2018-02-28]], dtype='interval[datetime64[ns], right]')
その他のインデックス作成に関するFAQ
整数による索引
整数軸ラベルを使用したラベルベースのインデックス付けは、厄介なトピックです。メーリングリストや科学的なPythonコミュニティのさまざまなメンバーの間で頻繁に議論されています。pandasでは、私たちの一般的な視点は、ラベルが整数の位置よりも重要であるということです。したがって、整数軸インデックスでは、.loc
などの標準ツールではラベルベースのインデックス付けのみが可能です。次のコードは例外を生成します。
s = pd.Series(range(5))
s[-1]
# ---------------------------------------------------------------------------
# ValueError Traceback (most recent call last)
# File ~/work/pandas/pandas/pandas/core/indexes/range.py:385, in RangeIndex.get_loc(self, key, method, tolerance)
# 384 try:
# --> 385 return self._range.index(new_key)
# 386 except ValueError as err:
#
# ValueError: -1 is not in range
#
# The above exception was the direct cause of the following exception:
#
# KeyError Traceback (most recent call last)
# Input In [217], in <module>
# ----> 1 s[-1]
#
# File ~/work/pandas/pandas/pandas/core/series.py:958, in Series.__getitem__(self, key)
# 955 return self._values[key]
# 957 elif key_is_scalar:
# --> 958 return self._get_value(key)
# 960 if is_hashable(key):
# 961 # Otherwise index.get_value will raise InvalidIndexError
# 962 try:
# 963 # For labels that don't resolve as scalars like tuples and frozensets
#
# File ~/work/pandas/pandas/pandas/core/series.py:1066, in Series._get_value(self, label, takeable)
# 1063 return self._values[label]
# 1065 # Similar to Index.get_value, but we do not fall back to positional
# -> 1066 loc = self.index.get_loc(label)
# 1067 return self.index._get_values_for_loc(self, loc, label)
# File ~/work/pandas/pandas/pandas/core/indexes/range.py:387, in RangeIndex.get_loc(self, key, method, tolerance)
# 385 return self._range.index(new_key)
# 386 except ValueError as err:
# --> 387 raise KeyError(key) from err
# 388 self._check_indexing_error(key)
# 389 raise KeyError(key)
#
# KeyError: -1
df = pd.DataFrame(np.random.randn(5, 4))
df
# 0 1 2 3
# 0 -0.130121 -0.476046 0.759104 0.213379
# 1 -0.082641 0.448008 0.656420 -1.051443
# 2 0.594956 -0.151360 -0.069303 1.221431
# 3 -0.182832 0.791235 0.042745 2.069775
# 4 1.446552 0.019814 -1.389212 -0.702312
df.loc[-2:]
# 0 1 2 3
# 0 -0.130121 -0.476046 0.759104 0.213379
# 1 -0.082641 0.448008 0.656420 -1.051443
# 2 0.594956 -0.151360 -0.069303 1.221431
# 3 -0.182832 0.791235 0.042745 2.069775
# 4 1.446552 0.019814 -1.389212 -0.702312
この意図的な決定は、あいまいさと微妙なバグを防ぐために行われました(位置ベースのインデックスを「フォールバック」しなくなるようにAPIを変更したところ、多くのユーザーがバグを見つけたと報告しました)。
非単調なインデックスには完全一致が必要
Series
またはDataFrame
のインデックスが単調に増加または減少している場合、通常のPythonリストのスライスインデックス付けのように、ラベルベースのスライスの境界がインデックスの範囲外にある可能性があります。インデックスの単調性は、is_monotonic_increasing()
およびis_monotonic_decreasing()
属性でテストできます。
df = pd.DataFrame(index=[2, 3, 3, 4, 5], columns=["data"], data=list(range(5)))
df.index.is_monotonic_increasing
# True
# 行0,1は存在しないが、行2,3(両方とも),4を返す
df.loc[0:4, :]
# data
# 2 0
# 3 1
# 3 2
# 4 3
# スライスがインデックス外にあるため、空のDataFrameが返される
df.loc[13:15, :]
# Empty DataFrame
# Columns: [data]
# Index: []
一方、インデックスが単調でない場合、両方のスライス境界はインデックスの一意の値でなければなりません。
df = pd.DataFrame(index=[2, 3, 1, 4, 3, 5], columns=["data"], data=list(range(6)))
df.index.is_monotonic_increasing
# False
# 2も4もインデックスに存在するので問題ない
df.loc[2:4, :]
# data
# 2 0
# 3 1
# 1 2
# 4 3
# 0はインデックスに存在しない
df.loc[0:4, :]
# KeyError: 0
# 3は一意なラベルではない
df.loc[2:3, :]
# KeyError: 'Cannot get right slice bound for non-unique label: 3'
Index.is_monotonic_increasing
およびIndex.is_monotonic_decreasing
は、インデックスが単調であるかどうかを軽くチェックするだけです。厳密な単調性を確認するには、そのどちらかをis_unique()
属性と組み合わせます。
weakly_monotonic = pd.Index(["a", "b", "c", "c"])
weakly_monotonic
# Index(['a', 'b', 'c', 'c'], dtype='object')
weakly_monotonic.is_monotonic_increasing
# True
weakly_monotonic.is_monotonic_increasing & weakly_monotonic.is_unique
# False
終点は含まれる
標準のPythonシーケンスのスライスが終点を含まないのと違い、pandasのラベルベースのスライスは終点が含まれます。この主な理由は、インデックス内の特定のラベルの後の「後続ラベル」または次の要素を簡単に判別できないことが多いためです。たとえば、次のSeires
を考えてみてください。
s = pd.Series(np.random.randn(6), index=list("abcdef"))
s
# a 0.301379
# b 1.240445
# c -0.846068
# d -0.043312
# e -1.658747
# f -0.819549
# dtype: float64
整数を使用してc
からe
をスライスしたいとします。これは次のようにして実行します。
s[2:5]
# c -0.846068
# d -0.043312
# e -1.658747
# dtype: float64
しかし、c
とe
のみから指定する場合、インデックスにおける次の要素を決定するのは多少複雑になる可能性があります。たとえば、次は機能しません。
s.loc['c':'e' + 1]
非常に一般的な使用例は、2つの特定の日付で開始および終了するような特定時系列を指定することです。これを可能にするために、ラベルベースのスライスに両方のエンドポイントが含まれるような設計にしました。
s.loc["c":"e"]
# c -0.846068
# d -0.043312
# e -1.658747
# dtype: float64
これは間違いなく「実用性が純粋さよりも優れている」ものですが、ラベルベースのスライスが標準のPython整数スライスとまったく同じように動作することを期待する場合は注意が必要です。
暗黙的にSeriesのdtypeが変更されるインデックス付け
異なるインデックス操作により、Series
のdtypeが変更される可能性があります。
series1 = pd.Series([1, 2, 3])
series1.dtype
# dtype('int64')
res = series1.reindex([0, 4])
res.dtype
# dtype('float64')
res
# 0 1.0
# 4 NaN
# dtype: float64
series2 = pd.Series([True])
series2.dtype
# dtype('bool')
res = series2.reindex_like(series1)
res.dtype
# dtype('O')
res
# 0 True
# 1 NaN
# 2 NaN
# dtype: object
これは、上記の(再)インデックス操作によりNaN
が暗黙的に挿入され、それに応じてdtype
が変更されるためです。これにより、numpy.logical_and
などのnumpy ufuncs
を使用するときに問題が発生する可能性があります。
詳細については、GH2388を参照してください。