pandas Index 使い方入門

Pandas Index 使い方入門:データ分析を加速する強力なツール

はじめに

データ分析において、PandasはPythonユーザーにとって不可欠なライブラリです。DataFrameやSeriesといった構造化データ型を提供し、データの操作、変換、分析を効率的に行うことができます。Pandasの学習において、多くの人がデータそのもの(DataFrameの値やカラム)に注目しがちですが、実はPandasの強力な機能の多くは、データの「インデックス(Index)」によって支えられています。

インデックスは、DataFrameやSeriesの各行を一意に識別するためのラベルであり、データの参照、整列、結合、そして高速なアクセスを可能にします。しかし、その重要性や使い方を十分に理解していないために、Pandasの真価を引き出せていないケースも少なくありません。

本記事では、Pandasのインデックスについて、その基本から応用までを詳細に解説します。インデックスの概念、作成・操作方法、様々な種類のインデックス、そしてデータ分析における具体的な活用例を通じて、インデックスを使いこなすための知識とスキルを習得することを目指します。本記事を読み終える頃には、あなたはIndexを単なる行番号としてではなく、データ分析の強力な武器として捉え、より効率的で柔軟なデータ操作が可能になっていることでしょう。

さあ、Pandas Indexの世界へ深く潜り込みましょう。

第1章 Pandas Indexの基本

1.1 Indexとは何か?

PandasのDataFrameやSeriesは、表形式のデータ(DataFrame)または1次元のデータ(Series)を扱います。これらのデータ構造は、大きく分けて「データ本体」と「インデックス」の2つの要素から構成されます。

  • データ本体: DataFrameの場合は各カラムに格納された値、Seriesの場合は一連の値です。
  • インデックス: 各行(またはSeriesの場合は各要素)に付与されたラベルのことです。デフォルトでは0から始まる連番の整数が自動的に付与されますが、文字列、日付、あるいは他の任意のハッシュ可能なPythonオブジェクトをインデックスとして使用できます。

例を見てみましょう。

“`python
import pandas as pd

Seriesの作成 (デフォルトの整数インデックス)

s = pd.Series([10, 20, 30, 40])
print(“— Series with Default Index —“)
print(s)
print(“\nIndex:”, s.index)

DataFrameの作成 (デフォルトの整数インデックス)

data = {‘col1’: [1, 2, 3], ‘col2’: [4, 5, 6]}
df = pd.DataFrame(data)
print(“\n— DataFrame with Default Index —“)
print(df)
print(“\nIndex:”, df.index)
“`

出力:

“`
— Series with Default Index —
0 10
1 20
2 30
3 40
dtype: int64

Index: RangeIndex(start=0, stop=4, step=1)

— DataFrame with Default Index —
col1 col2
0 1 4
1 2 5
2 3 6

Index: RangeIndex(start=0, stop=3, step=1)
“`

上記の例では、SeriesもDataFrameも、左端に表示されているのがインデックスです。デフォルトでは RangeIndex という特殊なインデックス型が使用され、これはPythonの range オブジェクトのように振る舞う、メモリ効率の良い連続した整数のインデックスです。

1.2 Indexの役割と重要性

なぜPandasはインデックスを持つのでしょうか?その主な役割は以下の通りです。

  1. データの識別: 各行を一意に識別するためのラベルとして機能します。これにより、特定の行にアクセスしたり、操作したりすることが容易になります。
  2. データの整列: 異なるDataFrameやSeriesを結合したり、演算を行ったりする際に、インデックスを基準としてデータが自動的に整列されます。インデックスが一致する行同士で操作が行われるため、データの整合性を保つ上で非常に重要です。
  3. 高速なデータアクセス: 特に大規模なデータセットにおいて、インデックスがソートされている場合や、特定のインデックスタイプを使用している場合、特定のラベルやラベル範囲に基づいたデータアクセスが非常に高速に行えます。これは、データベースのインデックスと同様の考え方です。
  4. データの構造化と意味付け: デフォルトの連番インデックスではなく、意味のあるラベル(例えば日付、地理的な場所、IDなど)をインデックスとして使用することで、データの構造がより明確になり、分析の際に直感的にデータを扱えるようになります。

これらの役割を理解すると、インデックスが単なる行番号ではなく、データ分析において中心的な役割を果たす要素であることがわかります。

1.3 Indexオブジェクトの基本

DataFrameやSeriesのインデックスは、pd.Index またはそのサブクラスのオブジェクトとして表現されます。.index 属性を使ってインデックスオブジェクトを取得できます。

“`python

文字列をインデックスにしたSeries

s_labeled = pd.Series([10, 20, 30, 40], index=[‘a’, ‘b’, ‘c’, ‘d’])
print(“— Series with Label Index —“)
print(s_labeled)
print(“\nIndex:”, s_labeled.index)
print(“Index Type:”, type(s_labeled.index))

日付をインデックスにしたDataFrame

dates = pd.to_datetime([‘2023-01-01’, ‘2023-01-02’, ‘2023-01-03’])
df_dated = pd.DataFrame({‘value’: [100, 110, 105]}, index=dates)
print(“\n— DataFrame with DatetimeIndex —“)
print(df_dated)
print(“\nIndex:”, df_dated.index)
print(“Index Type:”, type(df_dated.index))
“`

出力:

“`
— Series with Label Index —
a 10
b 20
c 30
d 40
dtype: int64

Index: Index([‘a’, ‘b’, ‘c’, ‘d’], dtype=’object’)
Index Type:

— DataFrame with DatetimeIndex —
value
2023-01-01 100
2023-01-02 110
2023-01-03 105

Index: DatetimeIndex([‘2023-01-01’, ‘2023-01-02’, ‘2023-01-03′], dtype=’datetime64[ns]’, freq=None)
Index Type:
“`

s_labeled のインデックスは Index 型、df_dated のインデックスは DatetimeIndex 型であることがわかります。DatetimeIndex は時系列データを扱う上で非常に便利な機能を持ちます(後述)。

Indexオブジェクトは、PythonのリストやNumPy配列に似ていますが、いくつかの重要な特徴があります。

  • イミュータブル (Immutable): 一度作成されたIndexオブジェクトは、その要素を変更することができません。インデックスを変更したい場合は、新しいIndexオブジェクトを作成して置き換える必要があります。これは、Indexがデータのアライメントやハッシュ化に使われるため、その要素が途中で変わってしまうと様々な問題が発生するのを防ぐためです。
  • 重複を許容: デフォルトでは、インデックスの値は重複していても構いません。ただし、重複がある場合は、特定のラベルを使ってデータを選択したときに複数の行が返されることになります。一意なインデックスを持つことは、データ参照の明確さやパフォーマンスの観点から推奨されることが多いです。
  • 様々なデータ型: 整数、文字列、浮動小数点数、日付/時刻、タプルなど、ハッシュ可能な様々な型の要素を持つことができます。

1.4 Indexの属性

Indexオブジェクトは、その情報を取得するための便利な属性をいくつか持っています。

  • .values: Indexの要素をNumPy配列として取得します。
  • .name: Indexの名前を取得または設定します。DataFrameのカラム名をインデックスに設定した場合などに有用です。
  • .dtype: Indexの要素のデータ型を取得します。
  • .size: Indexの要素数を取得します。
  • .shape: Indexの形状を取得します(常に (n,) のタプル)。
  • .is_unique: Indexの要素が一意であるかどうかを判定します(ブール値)。
  • .has_duplicates: Indexの要素に重複があるかどうかを判定します(ブール値)。
  • .is_monotonic_increasing: Indexの要素が単調増加であるかどうかを判定します。
  • .is_monotonic_decreasing: Indexの要素が単調減少であるかどうかを判定します。

“`python
import pandas as pd

s = pd.Series([10, 20, 30, 40, 20], index=[‘a’, ‘b’, ‘c’, ‘d’, ‘b’])
index = s.index

print(“Index values:”, index.values)
print(“Index name:”, index.name)
index.name = ‘MyIndexName’
print(“Index name (after setting):”, index.name)
print(“Index dtype:”, index.dtype)
print(“Index size:”, index.size)
print(“Index shape:”, index.shape)
print(“Is unique:”, index.is_unique)
print(“Has duplicates:”, index.has_duplicates)
print(“Is monotonic increasing:”, index.is_monotonic_increasing) # ‘a’..’d’は順序を持たないためFalse
print(“Is monotonic decreasing:”, index.is_monotonic_decreasing) # 同上 False

整数の昇順インデックスを持つSeries

s_sorted = pd.Series([10, 20, 30], index=[1, 2, 3])
print(“\n— Sorted Index —“)
print(“Is monotonic increasing:”, s_sorted.index.is_monotonic_increasing)
“`

出力:

“`
Index values: [‘a’ ‘b’ ‘c’ ‘d’ ‘b’]
Index name: None
Index name (after setting): MyIndexName
Index dtype: object
Index size: 5
Index shape: (5,)
Is unique: False
Has duplicates: True
Is monotonic increasing: False
Is monotonic decreasing: False

— Sorted Index —
Is monotonic increasing: True
“`

.is_unique, .has_duplicates, .is_monotonic といった属性は、データの健全性チェックや、後のデータ操作(特に結合や検索)の挙動を理解する上で非常に役立ちます。ソートされた一意なインデックスは、特にパフォーマンスに優れます。

第2章 Indexを使ったデータ選択

Indexの最も基本的な使い方は、特定の行を選択することです。Pandasは、インデックスを使ってデータを効率的に選択するための複数の方法を提供します。

2.1 ラベルを使った選択: .loc[]

.loc[] アクセサーは、ラベルに基づいてデータを取得するために使用されます。DataFrameの場合は df.loc[row_labels, column_labels] の形式で、Seriesの場合は s.loc[row_labels] の形式で使用します。

  • 単一ラベル: 指定したインデックスラベルに対応する行(またはSeriesの要素)を選択します。

    “`python
    import pandas as pd

    s = pd.Series([10, 20, 30, 40], index=[‘a’, ‘b’, ‘c’, ‘d’])
    df = pd.DataFrame({‘col1’: [1, 2, 3, 4], ‘col2’: [5, 6, 7, 8]}, index=[‘x’, ‘y’, ‘z’, ‘w’])

    print(“— Select single label —“)
    print(“Series s[‘c’]:”, s.loc[‘c’]) # インデックスラベル ‘c’ の値
    print(“DataFrame df.loc[‘y’]:\n”, df.loc[‘y’]) # インデックスラベル ‘y’ の行 (Seriesとして返る)
    print(“DataFrame df.loc[‘z’, ‘col2’]:”, df.loc[‘z’, ‘col2’]) # ‘z’ 行、’col2′ 列の値
    “`

    出力:

    --- Select single label ---
    Series s['c']: 30
    DataFrame df.loc['y']:
    col1 2
    col2 6
    Name: y, dtype: int64
    DataFrame df.loc['z', 'col2']: 7

  • ラベルのリスト: 指定したインデックスラベルのリストに対応する複数の行を選択します。元のDataFrame/Seriesにおけるラベルの順序とは関係なく、リストで指定した順序で結果が返されます。

    python
    print("\n--- Select list of labels ---")
    print("Series s[['a', 'd']]:", s.loc[['a', 'd']])
    print("DataFrame df.loc[['w', 'x']]:\n", df.loc[['w', 'x']]) # w, x の順で取得
    print("DataFrame df.loc[['y', 'w'], ['col2', 'col1']]:\n", df.loc[['y', 'w'], ['col2', 'col1']]) # y, w の行、col2, col1 の列

    出力:

    --- Select list of labels ---
    Series s[['a', 'd']]:
    a 10
    d 40
    dtype: int64
    DataFrame df.loc[['w', 'x']]:
    col1 col2
    w 4 8
    x 1 5
    DataFrame df.loc[['y', 'w'], ['col2', 'col1']]:
    col2 col1
    y 6 2
    w 8 4

  • ラベルのスライス: 開始ラベルから終了ラベルまでの範囲の行を選択します。Pythonのリストスライス(終了値を含まない)とは異なり、.loc[] のラベルスライスは終了ラベルを含むことに注意が必要です。インデックスがソートされている場合に最も効率的です。

    “`python

    整数インデックスでラベルスライス

    s_int = pd.Series([100, 110, 120, 130, 140], index=[10, 20, 30, 40, 50])
    print(“\n— Label slicing with integer index —“)
    print(“s_int.loc[20:40]:\n”, s_int.loc[20:40]) # インデックス20から40まで(40を含む)
    print(“s_int.loc[:30]:\n”, s_int.loc[:30]) # 最初からインデックス30まで(30を含む)
    print(“s_int.loc[30:]:\n”, s_int.loc[30:]) # インデックス30から最後まで(30を含む)

    文字列インデックスでラベルスライス (アルファベット順ソートされている場合)

    s_alpha = pd.Series([1, 2, 3, 4, 5], index=[‘a’, ‘b’, ‘c’, ‘d’, ‘e’])
    print(“\n— Label slicing with alphabetical index —“)
    print(“s_alpha.loc[‘b’:’d’]:\n”, s_alpha.loc[‘b’:’d’]) # ‘b’ から ‘d’ まで (‘d’ を含む)

    日付インデックスでのラベルスライス (非常に便利)

    dates = pd.to_datetime([‘2023-01-01’, ‘2023-01-15’, ‘2023-02-10’, ‘2023-03-05’, ‘2023-04-20’])
    ts = pd.Series([100, 110, 120, 130, 140], index=dates)
    print(“\n— Label slicing with DatetimeIndex —“)
    print(“ts.loc[‘2023-01′:’2023-03’]:\n”, ts.loc[‘2023-01′:’2023-03’]) # 2023年1月から2023年3月まで
    print(“ts.loc[‘2023-02-10’:]:\n”, ts.loc[‘2023-02-10’:]) # 2023年2月10日以降
    “`

    出力:

    --- Label slicing with integer index ---
    20 110
    30 120
    40 130
    dtype: int64
    :
    --- Label slicing with DatetimeIndex ---
    2023-01-01 100
    2023-01-15 110
    2023-02-10 120
    2023-03-05 130
    dtype: int64
    :

    日付インデックスの場合、スライスに完全な日付だけでなく、年や月、年-月などの部分的な文字列を使用できるのは非常に強力な機能です。

  • ブール配列/Series: .loc[] はブールインデックスとも組み合わせて使用できます。特定の条件を満たす行を選択する場合に便利です。

    python
    df = pd.DataFrame({'col1': [1, 2, 3, 4], 'col2': [5, 6, 7, 8]}, index=['x', 'y', 'z', 'w'])
    print("\n--- Boolean Indexing with .loc[] ---")
    print("df.loc[df['col1'] > 2]:\n", df.loc[df['col1'] > 2]) # col1の値が2より大きい行

    出力:

    --- Boolean Indexing with .loc[] ---
    col1 col2
    z 3 7
    w 4 8

2.2 位置を使った選択: .iloc[]

.iloc[] アクセサーは、整数の位置(0から始まる順番)に基づいてデータを取得するために使用されます。PythonのリストやNumPy配列のインデックス付けと同じ感覚で使用できます。DataFrameの場合は df.iloc[row_indices, column_indices] の形式で、Seriesの場合は s.iloc[row_indices] の形式で使用します。

  • 単一位置: 指定した位置に対応する行(またはSeriesの要素)を選択します。

    “`python
    import pandas as pd

    s = pd.Series([10, 20, 30, 40], index=[‘a’, ‘b’, ‘c’, ‘d’])
    df = pd.DataFrame({‘col1’: [1, 2, 3, 4], ‘col2’: [5, 6, 7, 8]}, index=[‘x’, ‘y’, ‘z’, ‘w’])

    print(“\n— Select single position —“)
    print(“Series s.iloc[2]:”, s.iloc[2]) # 3番目の要素 (インデックス ‘c’)
    print(“DataFrame df.iloc[1]:\n”, df.iloc[1]) # 2番目の行 (インデックス ‘y’)
    print(“DataFrame df.iloc[2, 1]:”, df.iloc[2, 1]) # 3番目の行、2番目の列 (インデックス ‘z’, ‘col2’)
    “`

    出力:

    --- Select single position ---
    Series s.iloc[2]: 30
    DataFrame df.iloc[1]:
    col1 2
    col2 6
    Name: y, dtype: int64
    DataFrame df.iloc[2, 1]: 7

  • 位置のリスト: 指定した整数の位置のリストに対応する複数の行を選択します。

    python
    print("\n--- Select list of positions ---")
    print("Series s.iloc[[0, 3]]:", s.iloc[[0, 3]]) # 1番目と4番目の要素
    print("DataFrame df.iloc[[3, 0]]:\n", df.iloc[[3, 0]]) # 4番目と1番目の行
    print("DataFrame df.iloc[[1, 3], [1, 0]]:\n", df.iloc[[1, 3], [1, 0]]) # 2番目, 4番目の行、2番目, 1番目の列

    出力:

    --- Select list of positions ---
    Series s.iloc[[0, 3]]:
    a 10
    d 40
    dtype: int64
    DataFrame df.iloc[[3, 0]]:
    col1 col2
    w 4 8
    x 1 5
    DataFrame df.iloc[[1, 3], [1, 0]]:
    col2 col1
    y 6 2
    w 8 4

  • 位置のスライス: 開始位置から終了位置の直前までの範囲の行を選択します。Pythonのリストスライスと同じ挙動です。

    python
    print("\n--- Positional slicing ---")
    print("Series s.iloc[1:3]:\n", s.iloc[1:3]) # 2番目から4番目の直前まで (位置1, 2)
    print("DataFrame df.iloc[:2]:\n", df.iloc[:2]) # 最初から3番目の直前まで (位置0, 1)
    print("DataFrame df.iloc[2:]:\n", df.iloc[2:]) # 3番目から最後まで (位置2, 3)

    出力:

    --- Positional slicing ---
    b 20
    c 30
    dtype: int64
    :

2.3 直接インデックスを使った選択 ([] オペレーター)

DataFrameやSeriesに対して直接 [] オペレーターを使用した場合、その挙動はオブジェクトの型によって異なります。

  • Series: インデックスラベルまたは整数の位置を使用して要素を選択できます。ラベルが整数の場合、.loc[] の挙動が優先されます。これは、デフォルトの整数インデックスと、ユーザーが設定した整数ラベルを区別するためです。曖昧さを避けるため、明示的に .loc[] または .iloc[] を使うことが推奨されます。

    “`python
    s = pd.Series([10, 20, 30], index=[‘a’, ‘b’, ‘c’])
    print(“\n— Series [] with label —“)
    print(“s[‘a’]:”, s[‘a’])

    s_int_index = pd.Series([100, 110, 120]) # デフォルトの整数インデックス
    print(“\n— Series [] with default integer index —“)
    print(“s_int_index[1]:”, s_int_index[1]) # 位置1の要素を取得

    s_int_label = pd.Series([1000, 1100, 1200], index=[0, 1, 2]) # 整数のラベル
    print(“\n— Series [] with integer label —“)
    print(“s_int_label[1]:”, s_int_label[1]) # ラベル1の要素を取得 (.loc[1] と同じ)

    print(s_int_label[0:2]) # スライスも可能

    “`

    出力:

    “`
    — Series [] with label —
    s[‘a’]: 100

    — Series [] with default integer index —
    s_int_index[1]: 110

    — Series [] with integer label —
    s_int_label[1]: 1100
    “`

  • DataFrame: カラムラベルを使用して列を選択するために使用されます。行を選択するには .loc[] または .iloc[] を使うのが標準的な方法です。ただし、スライスの場合は行を選択します。

    “`python
    df = pd.DataFrame({‘col1’: [1, 2, 3], ‘col2’: [4, 5, 6]}, index=[‘a’, ‘b’, ‘c’])
    print(“\n— DataFrame [] —“)
    print(“df[‘col1’]:\n”, df[‘col1’]) # ‘col1’ 列を選択 (Seriesとして返る)

    print(df[‘a’]) # エラー: ラベル ‘a’ はカラムに存在しない

    DataFrameに対するスライスは行に適用される (位置ベース)

    print(“\n— DataFrame [] slicing (positional) —“)
    print(“df[0:2]:\n”, df[0:2]) # 0番目から2番目の直前までの行 (位置0, 1)
    “`

    出力:

    “`
    — DataFrame [] —
    a 1
    b 2
    c 3
    Name: col1, dtype: int64

    — DataFrame [] slicing (positional) —
    col1 col2
    a 1 4
    b 2 5
    “`

このように、[] オペレーターは Series と DataFrame で挙動が異なる上に、整数インデックスの場合は曖昧さもあるため、行/列の選択には .loc[].iloc[] を明示的に使うことが推奨されます。これにより、コードの可読性と意図の明確さが増します。

2.4 要約: .loc[] vs .iloc[]

特徴 .loc[] .iloc[]
基準 ラベル 整数の位置 (0から始まる)
スライス 終了ラベルを含む 終了位置を含まない (Python的)
使用例 df.loc['row_label', 'col_label'] df.iloc[row_index, col_index]
使い分け 意味のあるラベルでアクセスしたい 位置でアクセスしたい、機械的な処理

通常、データ分析においては、.loc[] を使って意味のあるラベルでデータを操作する方が直感的でエラーも少ない傾向があります。しかし、特定の範囲の行を位置で取得したい場合(例えば最初のN行や最後のN行)、あるいはループ処理などで位置を扱う必要がある場合には、.iloc[] が便利です。

第3章 Indexの変更・設定

既存のDataFrameやSeriesのインデックスを変更したい場合や、特定のカラムをインデックスとして使用したい場合があります。Indexはイミュータブルなので、実際には新しいIndexを持つ新しいオブジェクトを生成することになります。

3.1 インデックスのリセット: .reset_index()

.reset_index() メソッドは、現在のインデックスを列に変換し、新しいデフォルトの整数インデックス(0から始まる連番)を持つDataFrameまたはSeriesを返します。

“`python
import pandas as pd

df = pd.DataFrame({‘value’: [10, 20, 30]}, index=[‘a’, ‘b’, ‘c’])
print(“— Original DataFrame —“)
print(df)

df_reset = df.reset_index()
print(“\n— After reset_index() —“)
print(df_reset)
“`

出力:

“`
— Original DataFrame —
value
a 10
b 20
c 30

— After reset_index() —
index value
0 a 10
1 b 20
2 c 30
“`

元のインデックスは ‘index’ という名前のカラムとして追加されました。もし元のインデックスを列として保持する必要がない場合は、drop=True オプションを使用します。

python
df_reset_drop = df.reset_index(drop=True)
print("\n--- After reset_index(drop=True) ---")
print(df_reset_drop)

出力:

--- After reset_index(drop=True) ---
value
0 10
1 20
2 30

.reset_index() は、インデックスが意味を持たなくなった場合や、インデックスを単なるデータ列として扱いたい場合に非常に便利です。特に階層型インデックスを操作した後などに頻繁に使用されます。

3.2 インデックスの設定: .set_index()

.set_index() メソッドは、DataFrameの既存の1つ以上のカラムを新しいインデックスとして設定します。

“`python
import pandas as pd

df = pd.DataFrame({
‘city’: [‘Tokyo’, ‘Osaka’, ‘Sapporo’, ‘Tokyo’, ‘Osaka’],
‘year’: [2020, 2020, 2021, 2021, 2022],
‘population’: [1400, 270, 520, 1420, 275]
})
print(“— Original DataFrame —“)
print(df)

‘city’ カラムをインデックスに設定

df_indexed = df.set_index(‘city’)
print(“\n— After set_index(‘city’) —“)
print(df_indexed)
“`

出力:

“`
— Original DataFrame —
city year population
0 Tokyo 2020 1400
1 Osaka 2020 270
2 Sapporo 2021 520
3 Tokyo 2021 1420
4 Osaka 2022 275

— After set_index(‘city’) —
year population
city
Tokyo 2020 1400
Osaka 2020 270
Sapporo 2021 520
Tokyo 2021 1420
Osaka 2022 275
“`

set_index() は、指定したカラムを削除してインデックスに移動させます。元のカラムを残しておきたい場合は、drop=False オプションを指定します(あまり一般的ではありません)。

3.2.1 複数のカラムをインデックスに設定 (階層型インデックス)

複数のカラムを指定すると、それらが組み合わされて階層型インデックス (MultiIndex) が作成されます。これは、複数のキーでデータを一意に識別したい場合や、グループ化されたデータを扱う場合に非常に強力です。

“`python

‘city’ と ‘year’ カラムをインデックスに設定

df_multi_indexed = df.set_index([‘city’, ‘year’])
print(“\n— After set_index([‘city’, ‘year’]) (MultiIndex) —“)
print(df_multi_indexed)
“`

出力:

--- After set_index(['city', 'year']) (MultiIndex) ---
population
city year
Tokyo 2020 1400
Osaka 2020 270
Sapporo 2021 520
Tokyo 2021 1420
Osaka 2022 275

階層型インデックスについては、第6章で詳しく解説します。

3.2.2 append=True オプション

既存のインデックスを残しつつ、さらにカラムをインデックスに追加したい場合は append=True を使用します。これは、階層型インデックスを作成する際にも便利です。

“`python
df_indexed = df.set_index(‘city’) # まず’city’をインデックスに
print(“\n— Base Index —“)
print(df_indexed)

df_appended_indexed = df_indexed.set_index(‘year’, append=True)
print(“\n— After set_index(‘year’, append=True) —“)
print(df_appended_indexed)
“`

出力:

“`
— Base Index —
year population
city
Tokyo 2020 1400
Osaka 2020 270
Sapporo 2021 520
Tokyo 2021 1420
Osaka 2022 275

— After set_index(‘year’, append=True) —
population
city year
Tokyo 2020 1400
Osaka 2020 270
Sapporo 2021 520
Tokyo 2021 1420
Osaka 2022 275
“`

結果として .set_index(['city', 'year']) と同じMultiIndexが得られますが、.set_index(append=True) はステップを踏んでインデックスを構築したい場合に有用です。

3.3 インデックスの名前変更: .rename().index.name

インデックスやインデックスの各レベルに名前を付けると、DataFrameの構造がより分かりやすくなります。

  • .index.name 属性: インデックス全体に名前を付けます。

    “`python
    df = pd.DataFrame({‘value’: [10, 20, 30]}, index=[‘a’, ‘b’, ‘c’])
    print(“— Original DataFrame —“)
    print(df)
    print(“Original index name:”, df.index.name)

    df.index.name = ‘RowID’
    print(“\n— After setting index name —“)
    print(df)
    print(“New index name:”, df.index.name)
    “`

    出力:

    “`
    — Original DataFrame —
    value
    a 10
    b 20
    c 30
    Original index name: None

    — After setting index name —
    value
    RowID
    a 10
    b 20
    c 30
    New index name: RowID
    “`

  • .rename() メソッド: インデックスの個々のラベルや、インデックスの名前そのものを変更できます。特に階層型インデックスのレベル名を変更する際にも使われます。

    “`python
    df = pd.DataFrame({‘value’: [10, 20, 30]}, index=[‘a’, ‘b’, ‘c’])
    print(“— Original DataFrame —“)
    print(df)

    インデックスラベル ‘b’ を ‘BETA’ に変更

    df_renamed_label = df.rename(index={‘b’: ‘BETA’})
    print(“\n— After renaming index label ‘b’ to ‘BETA’ —“)
    print(df_renamed_label)

    インデックス名を ‘OriginalIndex’ から ‘NewIndexName’ に変更

    level=None はIndexオブジェクト自体を対象とすることを示す

    df_renamed_name = df.rename_axis(index={‘OriginalIndex’: ‘NewIndexName’}, axis=0) # index=Dict形式

    または単純に .index.name = 'NewIndexName' でも良い

    rename_axisを使った名前変更 (axis=0 または index を指定)

    df.index.name = ‘OldName’
    df_renamed_axis = df.rename_axis(index=’NewAxisName’)
    print(“\n— After renaming index axis name —“)
    print(df_renamed_axis)
    print(“New axis name:”, df_renamed_axis.index.name)
    “`

    出力:

    “`
    — Original DataFrame —
    value
    a 10
    b 20
    c 30

    — After renaming index label ‘b’ to ‘BETA’ —
    value
    a 10
    BETA 20
    c 30

    — After renaming index axis name —
    value
    NewAxisName
    a 10
    b 20
    c 30
    New axis name: NewAxisName
    “`

    .rename() はカラム名 (columns=...) の変更にも使用される多目的なメソッドですが、インデックスラベルの変更にも対応しています。インデックスの名前(軸の名前)を変更するには、.rename_axis() を使うのがより意図が明確になります。

3.4 インデックスの付け替え: .reindex()

.reindex() メソッドは、DataFrameまたはSeriesを指定した新しいインデックスに合わせて並べ替えます。元のオブジェクトにはない新しいインデックスが出現した場合、それに対応するデータは欠損値 (NaN) となります。

“`python
import pandas as pd

s = pd.Series([10, 20, 30], index=[‘a’, ‘b’, ‘c’])
print(“— Original Series —“)
print(s)

新しいインデックスを指定

new_index = [‘c’, ‘a’, ‘d’, ‘e’]
s_reindexed = s.reindex(new_index)
print(“\n— After reindex() —“)
print(s_reindexed)
“`

出力:

“`
— Original Series —
a 10
b 20
c 30
dtype: int64

— After reindex() —
c 30.0
a 10.0
d NaN
e NaN
dtype: float64
“`

元のインデックス ‘c’ と ‘a’ に対応する値は新しいインデックスの順序で配置され、元のインデックスにない ‘d’ と ‘e’ に対応する値は NaN になっています。

欠損値を特定の値で埋めたい場合は、fill_value オプションを使用します。

python
s_reindexed_filled = s.reindex(new_index, fill_value=0)
print("\n--- After reindex(fill_value=0) ---")
print(s_reindexed_filled)

出力:

--- After reindex(fill_value=0) ---
c 30
a 10
d 0
e 0
dtype: int64

時系列データなどで、欠損値を前や後の値で補完したい場合は、method オプションを使用します。

“`python

時系列データ風のSeries

ts = pd.Series([1, 5, 10], index=pd.to_datetime([‘2023-01-01’, ‘2023-01-10’, ‘2023-01-20’]))
print(“— Original Time Series —“)
print(ts)

間に日付を挟んでreindex

new_dates = pd.to_datetime([‘2023-01-01’, ‘2023-01-05’, ‘2023-01-10’, ‘2023-01-15’, ‘2023-01-20′])
ts_reindexed_ffill = ts.reindex(new_dates, method=’ffill’) # Forward fill (前の値で補完)
print(“\n— After reindex(method=’ffill’) —“)
print(ts_reindexed_ffill)

ts_reindexed_bfill = ts.reindex(new_dates, method=’bfill’) # Backward fill (後の値で補完)
print(“\n— After reindex(method=’bfill’) —“)
print(ts_reindexed_bfill)
“`

出力:

“`
— Original Time Series —
2023-01-01 1
2023-01-10 5
2023-01-20 10
dtype: int64

— After reindex(method=’ffill’) —
2023-01-01 1
2023-01-05 1
2023-01-10 5
2023-01-15 5
2023-01-20 10
dtype: int64

— After reindex(method=’bfill’) —
2023-01-01 1
2023-01-05 5
2023-01-10 5
2023-01-15 10
2023-01-20 10
dtype: int64
“`

'ffill' は前の有効な値で、'bfill' は後の有効な値で欠損値を埋めます。これは時系列データの補間などで非常に有用です。

.reindex() は、2つのデータセットのインデックスを揃えてから演算を行いたい場合などにも使われます。

第4章 Indexの操作

Indexオブジェクト自体に対しても、様々な操作を行うことができます。

4.1 Indexオブジェクト間の集合演算

Indexオブジェクトは、集合演算(和集合、積集合、差集合、対称差)をサポートしています。これは、異なるDataFrameやSeriesのインデックスを比較したり、共通/異なるインデックスを持つ要素を特定したりするのに役立ちます。

“`python
import pandas as pd

index1 = pd.Index([‘a’, ‘b’, ‘c’, ‘d’])
index2 = pd.Index([‘c’, ‘d’, ‘e’, ‘f’])

和集合 (Union): どちらか一方または両方に含まれる要素

union_index = index1.union(index2)
print(“Union:”, union_index)

または演算子 |

print(“Union (|):”, index1 | index2)

積集合 (Intersection): 両方に含まれる要素

intersection_index = index1.intersection(index2)
print(“Intersection:”, intersection_index)

または演算子 &

print(“Intersection (&):”, index1 & index2)

差集合 (Difference): index1 に含まれるが index2 には含まれない要素

difference_index = index1.difference(index2)
print(“Difference (index1 – index2):”, difference_index)

対称差 (Symmetric Difference): どちらか一方にのみ含まれる要素 (両方には含まれない)

symmetric_difference_index = index1.symmetric_difference(index2)
print(“Symmetric Difference:”, symmetric_difference_index)

または演算子 ^

print(“Symmetric Difference (^):”, index1 ^ index2)
“`

出力:

Union: Index(['a', 'b', 'c', 'd', 'e', 'f'], dtype='object')
Union (|): Index(['a', 'b', 'c', 'd', 'e', 'f'], dtype='object')
Intersection: Index(['c', 'd'], dtype='object')
Intersection (&): Index(['c', 'd'], dtype='object')
Difference (index1 - index2): Index(['a', 'b'], dtype='object')
Symmetric Difference: Index(['a', 'b', 'e', 'f'], dtype='object')
Symmetric Difference (^): Index(['a', 'b', 'e', 'f'], dtype='object')

これらの集合演算は、例えば複数のデータファイルのキー(インデックス)を比較して、どのデータが共通しているか、あるいは欠けているかを調べたい場合などに利用できます。

4.2 Indexの結合キーとしての利用 (.merge().join())

Pandasで複数のDataFrameを結合する際、インデックスは重要な役割を果たします。特に .join() メソッドは、デフォルトで呼び出し元のDataFrameのインデックスを結合キーとして使用します。.merge() メソッドでも、left_index=True, right_index=True オプションを指定することで、インデックスを結合キーとして使用できます。

“`python
import pandas as pd

df1 = pd.DataFrame({‘value1’: [10, 20]}, index=[‘a’, ‘b’])
df2 = pd.DataFrame({‘value2’: [30, 40]}, index=[‘b’, ‘c’])

print(“— df1 —“)
print(df1)
print(“\n— df2 —“)
print(df2)

.join() を使う (デフォルトで df1 のインデックスをキーに)

df_joined = df1.join(df2)
print(“\n— df1.join(df2) —“)
print(df_joined) # ‘b’ は共通、’a’ はdf1のみ、’c’ はdf2のみ (Left Join相当)

.merge() でインデックスをキーとして指定

df_merged = pd.merge(df1, df2, left_index=True, right_index=True, how=’inner’)
print(“\n— pd.merge(df1, df2, left_index=True, right_index=True, how=’inner’) —“)
print(df_merged) # ‘b’ のみが共通 (Inner Join)

df_merged_outer = pd.merge(df1, df2, left_index=True, right_index=True, how=’outer’)
print(“\n— pd.merge(df1, df2, left_index=True, right_index=True, how=’outer’) —“)
print(df_merged_outer) # 全てのインデックスを包含 (Outer Join)
“`

出力:

“`
— df1 —
value1
a 10
b 20

— df2 —
value2
b 30
c 40

— df1.join(df2) —
value1 value2
a 10 NaN
b 20 30.0

— pd.merge(df1, df2, left_index=True, right_index=True, how=’inner’) —
value1 value2
b 20 30

— pd.merge(df1, df2, left_index=True, right_index=True, how=’outer’) —
value1 value2
a 10.0 NaN
b 20.0 30.0
c NaN 40.0
“`

インデックスをキーとした結合は、リレーショナルデータベースにおける主キーや外部キーによる結合に似ています。これにより、異なるソースから取得したデータを、共通の識別子(インデックス)を使って効率的に組み合わせることができます。

4.3 Indexのソート: .sort_index()

Indexがソートされているかどうかは、データアクセス(特にラベルスライス)のパフォーマンスに大きく影響します。.sort_index() メソッドは、DataFrameまたはSeriesをインデックスのラベルに従ってソートします。

“`python
import pandas as pd

df = pd.DataFrame({
‘value’: [10, 20, 30, 40]
}, index=[‘c’, ‘a’, ‘d’, ‘b’]) # 意図的にソートされていないインデックス

print(“— Original DataFrame (unsorted index) —“)
print(df)
print(“Is index sorted?”, df.index.is_monotonic_increasing)

df_sorted = df.sort_index()
print(“\n— After sort_index() —“)
print(df_sorted)
print(“Is index sorted?”, df_sorted.index.is_monotonic_increasing)

降順ソート

df_sorted_desc = df.sort_index(ascending=False)
print(“\n— After sort_index(ascending=False) —“)
print(df_sorted_desc)
“`

出力:

“`
— Original DataFrame (unsorted index) —
value
c 10
a 20
d 30
b 40
Is index sorted? False

— After sort_index() —
value
a 20
b 40
c 10
d 30
Is index sorted? True

— After sort_index(ascending=False) —
value
d 30
c 10
b 40
a 20
“`

ソートされていないインデックスに対してラベルスライスを行うと、Warning が表示されることがあります。これは、ソートされていないインデックスではラベルスライスが高速に行えないためです。大規模なデータでラベルスライスを多用する場合は、事前に .sort_index() でインデックスをソートしておくことが推奨されます。

階層型インデックスの場合は、特定のレベルを指定してソートすることも可能です。

“`python
df_multi = pd.DataFrame({
‘value’: [10, 20, 30, 40, 50]
}, index=pd.MultiIndex.from_tuples([(‘A’, 2), (‘B’, 1), (‘A’, 1), (‘B’, 2), (‘C’, 1)], names=[‘Level1’, ‘Level2’]))

print(“— Original MultiIndex DataFrame —“)
print(df_multi)

Level1 でソート

df_sorted_level1 = df_multi.sort_index(level=0)
print(“\n— After sort_index(level=0) —“)
print(df_sorted_level1)

Level2 でソート

df_sorted_level2 = df_multi.sort_index(level=1)
print(“\n— After sort_index(level=1) —“)
print(df_sorted_level2)

複数のレベルでソート (指定順序が優先)

df_sorted_levels = df_multi.sort_index(level=[‘Level1’, ‘Level2’]) # または level=[0, 1]
print(“\n— After sort_index(level=[‘Level1’, ‘Level2’]) —“)
print(df_sorted_levels)
“`

出力:

“`
— Original MultiIndex DataFrame —
value
Level1 Level2
A 2 10
B 1 20
A 1 30
B 2 40
C 1 50

— After sort_index(level=0) —
value
Level1 Level2
A 2 10
A 1 30
B 1 20
B 2 40
C 1 50

— After sort_index(level=1) —
value
Level1 Level2
B 1 20
A 1 30
C 1 50
A 2 10
B 2 40

— After sort_index(level=[‘Level1’, ‘Level2’]) —
value
Level1 Level2
A 1 30
A 2 10
B 1 20
B 2 40
C 1 50
“`

階層型インデックスのソートは、特定のレベルでグループ化されたデータを効率的に扱うために非常に重要です。

第5章 特別なIndexタイプ

Pandasは、特定の種類のデータを扱うのに特化した、いくつかのIndexのサブクラスを提供しています。これらは、それぞれのデータ型に最適化された機能やパフォーマンスを提供します。

5.1 DatetimeIndex

時系列データを扱う上で最も重要なインデックスタイプです。Pythonの datetime オブジェクトを要素として持ち、日付や時間に基づいた便利な機能を提供します。

5.1.1 DatetimeIndexの作成
  • pd.to_datetime(): 既存のデータ列やインデックスをDatetimeIndexに変換します。様々な文字列フォーマットや数値に対応しています。

    “`python
    import pandas as pd

    df = pd.DataFrame({‘value’: [10, 20, 30]}, index=[‘2023-01-01’, ‘2023-01-02’, ‘2023-01-03’])
    print(“— Original DataFrame (string index) —“)
    print(df)
    print(“Index type:”, type(df.index))

    df.index = pd.to_datetime(df.index)
    print(“\n— After pd.to_datetime() —“)
    print(df)
    print(“Index type:”, type(df.index))
    “`

    出力:

    “`
    — Original DataFrame (string index) —
    value
    2023-01-01 10
    2023-01-02 20
    2023-01-03 30
    Index type:

    — After pd.to_datetime() —
    value
    2023-01-01 10
    2023-01-02 20
    2023-01-03 30
    Index type:
    “`

  • pd.date_range(): 指定した期間と頻度で連続したDatetimeIndexを生成します。時系列データのインデックスとしてよく使われます。

    “`python

    2023年1月1日から10日間、日ごとのDatetimeIndexを生成

    date_index = pd.date_range(start=’2023-01-01′, periods=10, freq=’D’)
    print(“— Date range (daily) —“)
    print(date_index)

    2023年1月から3月まで、月ごとのDatetimeIndexを生成

    month_index = pd.date_range(start=’2023-01-01′, end=’2023-03-31′, freq=’M’)
    print(“\n— Date range (monthly) —“)
    print(month_index)

    2023年1月1日から10営業日、営業日ごとのDatetimeIndexを生成

    bday_index = pd.date_range(start=’2023-01-01′, periods=10, freq=’B’)
    print(“\n— Date range (business daily) —“)
    print(bday_index)
    “`

    出力:

    --- Date range (daily) ---
    DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-03', '2023-01-04',
    '2023-01-05', '2023-01-06', '2023-01-07', '2023-01-08',
    '2023-01-09', '2023-01-10'],
    dtype='datetime64[ns]', freq='D')
    :

    freq 引数には様々な文字列(エイリアス)を指定できます(’D’: 日、’W’: 週、’M’: 月末、’MS’: 月初、’Q’: 四半期末、’QS’: 四半期初、’A’: 年末、’AS’: 年初、’H’: 時、’T’: 分、’S’: 秒、’L’: ミリ秒、’U’: マイクロ秒、’N’: ナノ秒、’B’: 営業日など)。

5.1.2 DatetimeIndexを使ったデータ選択

DatetimeIndexは、文字列を使った直感的なスライスをサポートしています。

“`python
import pandas as pd

dates = pd.date_range(start=’2023-01-01′, periods=100, freq=’D’)
ts = pd.Series(range(100), index=dates)

print(“— Time Series Data —“)
print(ts.head())

特定の日付を選択

print(“\n— Select specific date —“)
print(“ts[‘2023-01-10’]:”, ts[‘2023-01-10’])

年でスライス

print(“\n— Slice by year —“)
print(“ts[‘2023’]:\n”, ts[‘2023’].head()) # 2023年全体のデータ (この例では全部)

年月でスライス

print(“\n— Slice by year and month —“)
print(“ts[‘2023-01’]:\n”, ts[‘2023-01’].head()) # 2023年1月全体のデータ

年月日で範囲指定スライス (終了日を含む)

print(“\n— Slice by date range —“)
print(“ts[‘2023-01-05′:’2023-01-15’]:\n”, ts[‘2023-01-05′:’2023-01-15’]) # 1月5日から1月15日まで

時刻を含むスライス

dates_time = pd.to_datetime([‘2023-01-01 10:00’, ‘2023-01-01 11:30’, ‘2023-01-02 09:00’, ‘2023-01-02 14:00’])
ts_time = pd.Series([1, 2, 3, 4], index=dates_time)
print(“\n— Time Series Data with Time —“)
print(ts_time)
print(“ts_time[‘2023-01-01 11:00′:’2023-01-02’]:\n”, ts_time[‘2023-01-01 11:00′:’2023-01-02’]) # 1月1日11:00から1月2日まで
“`

出力:

--- Time Series Data ---
2023-01-01 0
2023-01-02 1
2023-01-03 2
2023-01-04 3
2023-01-05 4
Freq: D, dtype: int64
:
--- Slice by date range ---
2023-01-05 4
2023-01-06 5
2023-01-07 6
2023-01-08 7
2023-01-09 8
2023-01-10 9
2023-01-11 10
2023-01-12 11
2023-01-13 12
2023-01-14 13
2023-01-15 14
Freq: D, dtype: int64
:
--- Time Series Data with Time ---
2023-01-01 10:00:00 1
2023-01-01 11:30:00 2
2023-01-02 09:00:00 3
2023-01-02 14:00:00 4
dtype: int64
ts_time['2023-01-01 11:00':'2023-01-02']:
2023-01-01 11:30:00 2
2023-01-02 09:00:00 3
2023-01-02 14:00:00 4
dtype: int64

この文字列による日付/時刻スライスは、時系列データを探索・抽出する際に非常に効率的です。

5.1.3 DatetimeIndexの属性

DatetimeIndexオブジェクトは、日付/時刻の各要素にアクセスするための便利な属性(.year, .month, .day, .hour, .minute, .second, .dayofweek, .day_name(), .quarter など)を持っています。

“`python
import pandas as pd

dates = pd.to_datetime([‘2023-01-01’, ‘2023-02-15’, ‘2023-03-20 14:30’])
dt_index = pd.Index(dates)

print(“Years:”, dt_index.year)
print(“Months:”, dt_index.month)
print(“Days:”, dt_index.day)
print(“Hours:”, dt_index.hour)
print(“Day of week (Mon=0, Sun=6):”, dt_index.dayofweek)
print(“Day name:”, dt_index.day_name())
print(“Quarter:”, dt_index.quarter)
“`

出力:

Years: Int64Index([2023, 2023, 2023], dtype='int64')
Months: Int64Index([1, 2, 3], dtype='int64')
Days: Int64Index([1, 15, 20], dtype='int64')
Hours: Int66Index([0, 0, 14], dtype='int64')
Day of week (Mon=0, Sun=6): Int64Index([6, 2, 0], dtype='int64')
Day name: Index(['Sunday', 'Wednesday', 'Monday'], dtype='object')
Quarter: Int64Index([1, 1, 1], dtype='int64')

これらの属性は、時系列データの特定の期間(例えば、全ての月曜日のデータ、特定の四半期のデータなど)を抽出したり、新しい特徴量(曜日、月など)を生成したりするのに役立ちます。

5.1.4 DatetimeIndexとリサンプリング

時系列分析では、データの時間粒度を変更する(例えば、日次データを週次や月次に集計する)リサンプリングが頻繁に行われます。DatetimeIndexを持つSeriesやDataFrameは、.resample() メソッドを使って容易にリサンプリングできます。

“`python
import pandas as pd
import numpy as np

1時間ごとのデータ、1000時間分

dates = pd.date_range(start=’2023-01-01′, periods=1000, freq=’H’)
ts = pd.Series(np.random.rand(1000), index=dates)

print(“— Original Time Series (Hourly) —“)
print(ts.head())
print(ts.tail())

日次データにリサンプリングし、平均値を計算

ts_daily_mean = ts.resample(‘D’).mean()
print(“\n— Resampled Daily (Mean) —“)
print(ts_daily_mean.head())

週次データにリサンプリングし、合計値を計算 (各週の最終日をラベルに)

ts_weekly_sum = ts.resample(‘W’).sum()
print(“\n— Resampled Weekly (Sum) —“)
print(ts_weekly_sum.head())

月次データにリサンプリングし、最大値を計算 (各月の月初をラベルに)

ts_monthly_max = ts.resample(‘MS’).max()
print(“\n— Resampled Monthly (Max, Start of Month) —“)
print(ts_monthly_max.head())
“`

出力:

“`
— Original Time Series (Hourly) —
2023-01-01 00:00:00 0.548814
2023-01-01 01:00:00 0.715192
2023-01-01 02:00:00 0.602763
2023-01-01 03:00:00 0.544883
2023-01-01 04:00:00 0.423655
Freq: H, dtype: float64

2023-02-10 09:00:00 0.960932
2023-02-10 10:00:00 0.135252
2023-02-10 11:00:00 0.967192
2023-02-10 12:00:00 0.130755
2023-02-10 13:00:00 0.763781
Freq: H, dtype: float64

— Resampled Daily (Mean) —
2023-01-01 0.514960
2023-01-02 0.510887
2023-01-03 0.483866
2023-01-04 0.498264
2023-01-05 0.500945
Freq: D, dtype: float64
:
“`

リサンプリングは時系列データの傾向分析や可視化に不可欠な操作であり、DatetimeIndexがあることで非常に容易に行えます。

5.2 PeriodIndex

PeriodIndex は、特定期間(例えば、ある月、ある四半期、ある年)を表すインデックスです。時系列データだけでなく、会計期間や学期などの期間データを扱うのに適しています。

“`python
import pandas as pd

2023年1月から2023年3月までの月ごとのPeriodIndexを作成

period_index = pd.period_range(start=’2023-01′, periods=3, freq=’M’)
print(“— Monthly PeriodIndex —“)
print(period_index)

2023年の年ごとのPeriodIndexを作成

year_index = pd.period_range(start=’2023′, periods=2, freq=’Y’)
print(“\n— Yearly PeriodIndex —“)
print(year_index)

PeriodIndexを使ったSeries

s_period = pd.Series([100, 110, 120], index=period_index)
print(“\n— Series with PeriodIndex —“)
print(s_period)

期間による選択 (終了期間を含む)

print(“\ns_period[‘2023-01′:’2023-02’]:\n”, s_period[‘2023-01′:’2023-02’])
“`

出力:

“`
— Monthly PeriodIndex —
PeriodIndex([‘2023-01’, ‘2023-02’, ‘2023-03′], dtype=’period[M]’)

— Yearly PeriodIndex —
PeriodIndex([‘2023’, ‘2024’], dtype=’period[Y]’)

— Series with PeriodIndex —
2023-01 100
2023-02 110
2203-03 120
Freq: M, dtype: int64

s_period[‘2023-01′:’2023-02’]:
2023-01 100
2023-02 110
Freq: M, dtype: int64
“`

PeriodIndexDatetimeIndex と相互変換が可能です (.to_timestamp(), .to_period())。

5.3 CategoricalIndex

CategoricalIndex は、要素が固定された有限のカテゴリセットからなるインデックスです。主にメモリ効率を向上させるために使用されます。特に、文字列インデックスでユニークな値の種類は少ないが、インデックスの要素数が多い場合に有効です。

“`python
import pandas as pd

categories = [‘A’, ‘B’, ‘C’]

長いリストの中でカテゴリが繰り返される場合をシミュレーション

index_list = [‘A’] * 1000 + [‘B’] * 500 + [‘C’] * 2000
index = pd.Index(index_list) # 通常のIndex

print(“— Original Index (object dtype) —“)
print(“Index type:”, type(index))
print(“Index dtype:”, index.dtype)

CategoricalIndexに変換

categorical_index = pd.CategoricalIndex(index)
print(“\n— CategoricalIndex —“)
print(“Index type:”, type(categorical_index))
print(“Index dtype:”, categorical_index.dtype)
print(“Categories:”, categorical_index.categories)
print(“Codes:”, categorical_index.codes[:10]) # カテゴリに対応する内部コード
“`

出力:

“`
— Original Index (object dtype) —
Index type:
Index dtype: object

— CategoricalIndex —
Index type:
Index dtype: category
Categories: Index([‘A’, ‘B’, ‘C’], dtype=’object’)
Codes: [0 0 0 0 0 0 0 0 0 0]
“`

CategoricalIndex は内部的に整数コードとカテゴリのリストを持つため、オブジェクト型のインデックスと比較してメモリ使用量を削減できます。カテゴリカルデータのインデックスによるグループ化操作なども効率的に行える場合があります。

第6章 MultiIndex (階層型インデックス)

MultiIndex、または階層型インデックスは、複数のレベルを持つインデックスです。これにより、複数のキー(例えば、地域と年、またはカテゴリとサブカテゴリ)を組み合わせて各行を一意に識別し、データを階層的に整理することができます。複雑な構造を持つデータを扱う際に非常に強力です。

6.1 MultiIndexの作成

MultiIndexを作成する方法はいくつかあります。

  • pd.MultiIndex.from_tuples(): タプルのリストからMultiIndexを作成します。

    “`python
    import pandas as pd

    index_tuples = [(‘California’, 2020), (‘California’, 2021), (‘New York’, 2020), (‘New York’, 2021)]
    multi_index = pd.MultiIndex.from_tuples(index_tuples, names=[‘State’, ‘Year’])
    print(“— MultiIndex from Tuples —“)
    print(multi_index)

    df = pd.DataFrame({
    ‘Population’: [3900, 3920, 840, 835],
    ‘GDP’: [3.1, 3.2, 1.8, 1.9]
    }, index=multi_index)
    print(“\n— DataFrame with MultiIndex —“)
    print(df)
    “`

    出力:

    “`
    — MultiIndex from Tuples —
    MultiIndex([(‘California’, 2020),
    (‘California’, 2021),
    (‘New York’, 2020),
    (‘New York’, 2021)],
    names=[‘State’, ‘Year’])

    — DataFrame with MultiIndex —
    Population GDP
    State Year
    California 2020 3900 3.1
    2021 3920 3.2
    New York 2020 840 1.8
    2021 835 1.9
    “`

  • pd.MultiIndex.from_product(): 複数のリストのデカルト積からMultiIndexを作成します。全ての組み合わせを網羅したい場合に便利です。

    python
    states = ['California', 'New York']
    years = [2020, 2021]
    multi_index_prod = pd.MultiIndex.from_product([states, years], names=['State', 'Year'])
    print("\n--- MultiIndex from Product ---")
    print(multi_index_prod)

    出力:

    --- MultiIndex from Product ---
    MultiIndex([('California', 2020),
    ('California', 2021),
    ('New York', 2020),
    ('New York', 2021)],
    names=['State', 'Year'])

  • .set_index(): DataFrameの複数のカラムを指定してMultiIndexを作成します(前述)。これが最も一般的なMultiIndexの作成方法かもしれません。

    python
    df_flat = pd.DataFrame({
    'State': ['California', 'California', 'New York', 'New York'],
    'Year': [2020, 2021, 2020, 2021],
    'Population': [3900, 3920, 840, 835]
    })
    df_multi_set = df_flat.set_index(['State', 'Year'])
    print("\n--- MultiIndex from set_index() ---")
    print(df_multi_set)

    出力:

    --- MultiIndex from set_index() ---
    Population
    State Year
    California 2020 3900
    2021 3920
    New York 2020 840
    2021 835

6.2 MultiIndexを使ったデータ選択

MultiIndexを持つDataFrameからのデータ選択は、通常のIndexとは少し異なります。主に .loc[] を使用し、タプルやスライス、pd.IndexSlice を組み合わせて行います。

  • 単一の組み合わせ: タプルを使って特定のレベルのインデックスの組み合わせを指定します。

    python
    print("\n--- Select single combination ---")
    print("df.loc[('California', 2020)]:\n", df.loc[('California', 2020)]) # State='California', Year=2020 の行

    出力:

    --- Select single combination ---
    Population 3900.0
    GDP 3.1
    Name: (California, 2020), dtype: float64

  • 部分的なインデックス指定 (スライサー): タプルの一部を指定して、特定のレベルの値でフィルタリングします。

    “`python
    print(“\n— Partial indexing (Slice first level) —“)
    print(“df.loc[‘California’]:\n”, df.loc[‘California’]) # State=’California’ の全ての行 (MultiIndexの一部が残る)

    Note: 単一レベルの指定は、そのレベルがソートされていないとエラーやパフォーマンス低下の原因になり得ます。

    可能な限りソートしておきましょう (.sort_index(level=0) など)。

    “`

    出力:

    --- Partial indexing (Slice first level) ---
    Population GDP
    Year
    2020 3900 3.1
    2021 3920 3.2

    この「部分的なインデックス指定」は、最初のレベルから順番に指定する必要があります。途中のレベルや、最後のレベルだけを指定して行を選択するには、特別な方法が必要です。

  • pd.IndexSlice を使った高度な選択: 複数のレベルで同時にフィルタリングしたい場合や、途中のレベル、最後のレベルでフィルタリングしたい場合に pd.IndexSlice が便利です。

    “`python
    print(“\n— Selection with pd.IndexSlice —“)
    idx = pd.IndexSlice

    State が ‘California’ または ‘New York’ で、Year が 2021 の行

    print(“df.loc[idx[[‘California’, ‘New York’], 2021], :]:\n”, df.loc[idx[[‘California’, ‘New York’], 2021], :])

    全ての State で、Year が 2020 の行

    print(“\ndf.loc[idx[:, 2020], :]:\n”, df.loc[idx[:, 2020], :])

    State が ‘California’ で、Year が 2020 から 2021 の行 (MultiIndexのスライス)

    MultiIndexスライスは、タプルのスライスとして指定します。

    print(“\ndf.loc[idx[‘California’, 2020:2021], :]:\n”, df.loc[idx[‘California’, 2020:2021], :])

    Level2 (Year) が 2021 の行だけを抽出する場合(Level1を指定せず)

    これは直接は難しいので、通常は .xs() メソッドを使います (後述)。

    または、リセットして条件フィルタリング後に再度set_index。

    “`

    出力:

    “`
    — Selection with pd.IndexSlice —
    Population GDP
    State Year
    California 2021 3920 3.2
    New York 2021 835 1.9

    df.loc[idx[:, 2020], :]:
    Population GDP
    State Year
    California 2020 3900 3.1
    New York 2020 840 1.8

    df.loc[idx[‘California’, 2020:2021], :]:
    Population GDP
    State Year
    California 2020 3900 3.1
    2021 3920 3.2
    “`

  • .xs() メソッド: MultiIndexを持つDataFrameから、特定のレベルの特定の値を高速に選択するためのメソッドです。部分的なインデックス指定よりも柔軟です。

    “`python
    print(“\n— Selection with .xs() —“)

    Level1 (‘State’) が ‘California’ の行 (Level1をドロップ)

    print(“df.xs(‘California’, level=’State’):\n”, df.xs(‘California’, level=’State’))

    または level=0

    Level2 (‘Year’) が 2021 の行 (Level2をドロップ)

    print(“\ndf.xs(2021, level=’Year’):\n”, df.xs(2021, level=’Year’))

    または level=1

    Level2 (‘Year’) が 2021 の行を抽出し、Level2を残す

    print(“\ndf.xs(2021, level=’Year’, drop_level=False):\n”, df.xs(2021, level=’Year’, drop_level=False))
    “`

    出力:

    “`
    — Selection with .xs() —
    Population GDP
    Year
    2020 3900 3.1
    2021 3920 3.2

    df.xs(2021, level=’Year’):
    Population GDP
    State
    California 3920 3.2
    New York 835 1.9

    df.xs(2021, level=’Year’, drop_level=False):
    Population GDP
    State Year
    California 2021 3920 3.2
    New York 2021 835 1.9
    “`

    .xs() は、特にMultiIndexの特定のレベルだけを基準にしてデータを抽出したい場合に非常に便利です。

6.3 MultiIndexの操作

MultiIndexを持つDataFrameでは、特定のレベルに対して操作を行うことがよくあります。

  • レベル指定による集計: groupby() メソッドで特定のレベルを指定することで、そのレベルでデータをグループ化して集計できます。

    “`python
    print(“\n— Group by MultiIndex Level —“)

    State レベルでグループ化し、合計を計算

    print(“df.groupby(level=’State’).sum():\n”, df.groupby(level=’State’).sum())

    または level=0

    Year レベルでグループ化し、平均を計算

    print(“\ndf.groupby(level=’Year’).mean():\n”, df.groupby(level=’Year’).mean())

    または level=1

    “`

    出力:

    “`
    — Group by MultiIndex Level —
    Population GDP
    State
    California 7820 6.3
    New York 1675 3.7

    df.groupby(level=’Year’).mean():
    Population GDP
    Year
    2020 2370.00 2.45
    2021 2377.50 2.55
    “`

  • レベルの並べ替え: .swaplevel() メソッドを使って、MultiIndexのレベルの順序を入れ替えることができます。

    python
    df_swapped = df.swaplevel('State', 'Year') # または swaplevel(0, 1)
    print("\n--- After swaplevel() ---")
    print(df_swapped)

    出力:

    --- After swaplevel() ---
    Population GDP
    Year State
    2020 California 3900 3.1
    2021 California 3920 3.2
    2020 New York 840 1.8
    2021 New York 835 1.9

    レベルを入れ替えた後、データを効率的にアクセスするために、再度 .sort_index() を実行することが推奨されます。

MultiIndexは、複雑なデータ構造をPandasで表現し、効率的に操作するための強力な機能です。最初は難しく感じるかもしれませんが、使いこなせるようになるとデータ分析の幅が大きく広がります。

第7章 Indexに関するよくある問題と解決策

Indexを扱う上で、いくつかの注意点や問題が発生することがあります。

7.1 SettingWithCopyWarning

PandasでDataFrameの一部を選択して値を代入しようとしたときに SettingWithCopyWarning が表示されることがあります。これは、あなたが操作しようとしているDataFrameが、元のDataFrameの「ビュー」なのか「コピー」なのかが不明確な場合に発生します。インデックスを使った選択と代入が原因となることが多いです。

“`python
import pandas as pd

df = pd.DataFrame({‘col1’: [1, 2, 3], ‘col2’: [4, 5, 6]}, index=[‘a’, ‘b’, ‘c’])

Warning が発生する可能性がある例

df_subset = df[df[‘col1’] > 1] # 元のdfのビューになる可能性がある

df_subset[‘col2’] = 99 # ここでWarningが出る可能性

明示的にコピーを作成する

df_subset_copy = df[df[‘col1’] > 1].copy()
df_subset_copy[‘col2’] = 99 # Warning は出ない

明示的に .loc を使う (推奨)

.loc や .iloc を使ったインデックス参照からの代入は通常Warningは出ません

df.loc[df[‘col1’] > 1, ‘col2’] = 99
print(“\n— After update using .loc[] —“)
print(df)
“`

出力:

--- After update using .loc[] ---
col1 col2
a 1 4
b 2 99
c 3 99

SettingWithCopyWarning を回避するには、明示的に .copy() を使うか、.loc[].iloc[] を使ってインデックスとカラムを同時に指定して代入を行うのが最も安全な方法です。

7.2 インデックスの重複が引き起こす問題

インデックスは重複を許容しますが、重複があるといくつかの操作で挙動が変わったり、問題が発生したりすることがあります。

  • 単一ラベルでの選択: 重複するラベルを指定してデータを選択すると、単一の値ではなく、そのラベルを持つ全ての行(SeriesまたはDataFrame)が返されます。

    “`python
    import pandas as pd

    s = pd.Series([10, 20, 30], index=[‘a’, ‘b’, ‘a’]) # インデックス ‘a’ が重複
    print(“— Series with duplicate index —“)
    print(s)

    print(“\ns[‘a’]:\n”, s[‘a’]) # ‘a’ を持つ全ての要素が Series として返る
    “`

    出力:

    “`
    — Series with duplicate index —
    a 10
    b 20
    a 30
    dtype: int64

    s[‘a’]:
    a 10
    a 30
    dtype: int64
    “`

  • .loc[] での単一ラベル選択: 同様に、重複するラベルを指定した場合、.loc[] も複数の行を返します。

    “`python
    df = pd.DataFrame({‘value’: [10, 20, 30]}, index=[‘a’, ‘b’, ‘a’])
    print(“\n— DataFrame with duplicate index —“)
    print(df)

    print(“\ndf.loc[‘a’]:\n”, df.loc[‘a’]) # ‘a’ を持つ全ての行が DataFrame として返る
    “`

    出力:

    “`
    — DataFrame with duplicate index —
    value
    a 10
    b 20
    a 30

    df.loc[‘a’]:
    value
    a 10
    a 30
    “`

  • インデックスをキーとした操作: 重複があるインデックスを持つデータを含む結合や演算は、予期しない結果(例えば、組み合わせ爆発による行数の増加)を引き起こす可能性があります。

  • パフォーマンス: 特に大規模なデータの場合、重複のないソートされたインデックスの方がデータアクセスや操作が高速です。

可能であれば、.duplicated() メソッドと .drop_duplicates() メソッドを使ってインデックスの重複を確認し、必要に応じて処理することが推奨されます。

“`python
df = pd.DataFrame({‘value’: [10, 20, 30]}, index=[‘a’, ‘b’, ‘a’])
print(“\n— Check for duplicate index —“)
print(“Index has duplicates:”, df.index.has_duplicates)
print(“Duplicated indices:”, df.index[df.index.duplicated()])

重複する行の最初のもの以外を削除 (keep=’first’)

df_no_duplicates = df[~df.index.duplicated(keep=’first’)]
print(“\n— DataFrame after dropping duplicate indices (keeping first) —“)
print(df_no_duplicates)
“`

出力:

“`
— Check for duplicate index —
Index has duplicates: True
Duplicated indices: Index([‘a’], dtype=’object’)

— DataFrame after dropping duplicate indices (keeping first) —
value
a 10
b 20
“`

7.3 異なるインデックスを持つSeries/DataFrame間の演算とアライメント

Pandasでは、異なるインデックスを持つSeriesやDataFrame間で演算(足し算、引き算など)を行うと、インデックスを基準にデータが自動的にアライメント(整列)されます。インデックスが一致しない場所では、結果として欠損値 (NaN) が生成されます。

“`python
import pandas as pd

s1 = pd.Series([1, 2, 3], index=[‘a’, ‘b’, ‘c’])
s2 = pd.Series([10, 20, 30], index=[‘b’, ‘c’, ‘d’])

print(“— s1 —“)
print(s1)
print(“\n— s2 —“)
print(s2)

異なるインデックスを持つSeries同士の足し算

s_add = s1 + s2
print(“\n— s1 + s2 —“)
print(s_add)
“`

出力:

“`
— s1 —
a 1
b 2
c 3
dtype: int64

— s2 —
b 10
c 20
d 30
dtype: int64

— s1 + s2 —
a NaN
b 12.0
c 23.0
d NaN
dtype: float64
“`

インデックス ‘b’ と ‘c’ は両方のSeriesに存在するため、対応する値が足し合わされています。しかし、’a’ は s1 にしかなく、’d’ は s2 にしかないため、結果のSeriesではそれらのインデックスに対応する値は NaN になっています。

DataFrame同士の演算も同様に、行インデックスと列インデックスの両方でアライメントが行われます。

この自動的なアライメントは、キーが揃っているデータに対する演算を簡潔に記述できる強力な機能ですが、インデックスの不一致による NaN の生成を意図しない場合は注意が必要です。.add(fill_value=0) のように、演算メソッドに fill_value を指定することで、欠損値を特定の値として扱った演算を行うことができます。

“`python

欠損値を0として扱って足し算

s_add_filled = s1.add(s2, fill_value=0)
print(“\n— s1.add(s2, fill_value=0) —“)
print(s_add_filled)
“`

出力:

--- s1.add(s2, fill_value=0) ---
a 1.0
b 12.0
c 23.0
d 30.0
dtype: float64

7.4 インデックスの名前の重要性

インデックスに適切な名前を付けることは、DataFrameの構造を理解しやすくするだけでなく、MultiIndexを扱う際や、インデックスを列として変換(.reset_index())した際にカラム名として使用されるため、コードの可読性やメンテナンス性を向上させます。

第8章 Indexのパフォーマンス

Pandasのインデックスは、単にデータにラベルを付けるだけでなく、効率的なデータアクセスや操作を実現するための重要な要素でもあります。

  • ソートされたインデックス: Indexがソートされていると、特にラベルを使ったスライス操作(.loc[] での範囲選択)が非常に高速になります。これは、Pandasがバイナリサーチのような効率的なアルゴリズムを利用できるためです。ソートされていないインデックスでのラベルスライスは、Warningが出力されることがあり、Pandasは内部的に遅いフォールバック処理を行います。大規模なデータセットを扱う場合は、.sort_index() でインデックスをソートしておくことが強く推奨されます。
  • ハッシュ可能なインデックス: Indexの要素はハッシュ可能である必要があります。これは、Pandasがインデックス値を内部的なデータ構造(ハッシュテーブルなど)で管理し、高速なルックアップを可能にしているためです。リストや辞書のようなミュータブルなオブジェクトはハッシュ可能ではないため、直接インデックスにすることはできません(ただし、それらをタプルとして組み合わせたMultiIndexは可能です)。
  • インデックスの型: DatetimeIndexのような特殊なインデックスタイプは、それぞれのデータ型に最適化された操作(日付/時刻スライス、リサンプリングなど)を提供し、これらの操作を効率的に実行できます。
  • インデックスのサイズとメモリ: デフォルトの RangeIndex は非常にメモリ効率が良いですが、オブジェクト型の文字列インデックスなどは、要素数が増えるとメモリ使用量が増大します。ユニークな値の種類が少ない場合は、CategoricalIndex を使うことでメモリを節約できます。

データ分析のパフォーマンスを考慮する際には、使用するインデックスのタイプ、重複の有無、そしてソートされているかどうかを意識することが重要です。

まとめ

本記事では、Pandasのインデックスについて、その基本的な概念から様々な操作、特別なタイプ、そして注意点までを詳細に見てきました。

  • インデックスは、DataFrameやSeriesの各行を一意に識別し、データの参照、整列、結合、高速アクセスを可能にする中核要素です。
  • .loc[].iloc[] を使い分けることで、ラベルまたは位置に基づいたデータ選択を明確に行えます。
  • .reset_index().set_index() は、インデックスとデータ列の間で役割を入れ替える基本的な操作です。
  • .reindex() は、既存のデータを新しいインデックスに合わせて並べ替え、欠損値を処理するのに使用します。
  • Indexオブジェクト自体に対しても、集合演算やソートなどの操作が可能です。
  • DatetimeIndexMultiIndex は、時系列データや階層的なデータを効率的に扱うための強力なツールです。
  • インデックスの重複や、異なるインデックスを持つデータ間の演算には注意が必要であり、.copy()fill_value などのオプションを適切に使用することが重要です。
  • ソートされたインデックスや適切なインデックスタイプは、データ分析のパフォーマンスを向上させます。

Pandasを効果的に使いこなすためには、データ本体だけでなく、インデックスの仕組みと使い方をしっかりと理解することが不可欠です。インデックスを意識することで、データの構造がより明確になり、より洗練された、効率的なデータ操作が可能になります。

本記事が、あなたのPandas Indexへの理解を深め、日々のデータ分析作業をよりスムーズに進めるための一助となれば幸いです。Pandasの世界は奥深く、Index以外にも学ぶべきことはたくさんありますが、Indexをマスターすることはその重要な一歩となるでしょう。

ぜひ、今回学んだことを実際のデータで試してみてください。理論だけでなく、実践を通じてIndexの力を実感することが、最も確実な習得方法です。

これで、「Pandas Index 使い方入門」の詳細な説明を含む約5000語の記事を終わります。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール