pandas

【pandas】Indexの基本

【pandas】Indexの基本

pandasのSeriesDataFrameのインデックスで使用されているIndexの基本について解説します。

pandasのIndex

pandasでよく使用されるSeriesDataFrameでは、要素のアクセスにインデックスを使用します。pandasでは、このインデックスはIndexクラスとして実装されています。Indexは、値とそのデータ型(dtype)を持っています。

以下のSeriesDataFrameの作成例で見てみましょう。

import numpy as np
import pandas as pd

# Series
ser = pd.Series([1, 2, 3], index=["A", "B", "C"])
print(ser)
print(ser.index)
print("\n")

# DataFrame
df = pd.DataFrame(
    np.arange(9).reshape((3, 3)), index=[1, 2, 3], columns=["A", "B", "C"]
)
print(df)
print(df.index)
print(df.columns)
【実行結果】
A    1
B    2
C    3
dtype: int64
Index(['A', 'B', 'C'], dtype='object')


   A  B  C
1  0  1  2
2  3  4  5
3  6  7  8
Index([1, 2, 3], dtype='int64')
Index(['A', 'B', 'C'], dtype='object')

上記例でindexcolumnsを表示しいますが、型がIndexオブジェクトとなっていることが分かります。また、dtypeには、インデックス値の型が示されています。

このIndexクラスのオブジェクトは、それ自体に特徴的な部分があります。以降では、Indexオブジェクトの操作や特徴について説明していきます。

Indexオブジェクトの操作の基本

Indexオブジェクトの作成方法

Indexオブジェクトは、以下のようにPythonのリストを受け取って生成することできます。

import pandas as pd

ind = pd.Index([1, 2, 3])
print(ind)

ind = pd.Index(["A", "B", "C"])
print(ind)
【実行結果】
Index([1, 2, 3], dtype='int64')
Index(['A', 'B', 'C'], dtype='object')

Indexオブジェクトに対するアクセス

Indexオブジェクトの要素へは、Pythonのリスト等と同じようにアクセスすることが可能です。

import pandas as pd

ind = pd.Index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

# 要素へのアクセス
print(ind[1])
# スライスによるアクセス
print(ind[1:9:2])
# 各種情報へのアクセス
print(ind.size)
print(ind.shape)
print(ind.ndim)
print(ind.dtype)
【実行結果】
2
Index([2, 4, 6, 8], dtype='int64')
10
(10,)
1
int64

上記例のように、位置を指定したり、スライス記法によって一部を抽出することができます。また、NumPyの配列(ndarray)のように、sizeshapendimdtypeといった属性値にアクセスができます。

Indexオブジェクトはイミュータブル(immutable)

Indexオブジェクトは、「イミュータブル(immutable)」で変更ができないという点に特徴があります。

インデックスはデータを識別するために非常に重要な情報です。例えば、簡単にインデックスが変更されてしまうと、データフレームの行の識別が困難になり、誤ったデータ操作や分析結果を引き起こす原因となる可能性があります。pandasにおいてインデックスを表現するIndexオブジェクトがイミュータブル(immutable)であることで安全性が向上するため、この性質は非常に重要な特性です。

Indexオブジェクトがイミュータブル(immutable)であるということを簡単な例で見てみましょう。

import pandas as pd

ind = pd.Index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

# Indexオブジェクトはイミュータブル(immutable)なため要素の変更はできない
ind[0] = 10
【実行結果例】
TypeError: Index does not support mutable operations

上記のように、インデックス要素の値を変更しようとすると、TypeErrorとなります。これは、変更を行うような操作はサポートしていないということを示すエラーとなっています。

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

pandasのIndexオブジェクトは、複数データの結合といった操作を容易にできるようにするために集合演算ができるようになっています。Python組み込みの集合であるsetと同じように演算をすることが可能です。

なお、Python組み込みの集合(set)の演算については「集合(set)の演算と包含関係の判定」でまとめていますので興味があれば参考にしてください。

import pandas as pd

indA = pd.Index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
indB = pd.Index([6, 7, 8, 9, 10, 11, 12, 13, 14, 15])

print(f"積集合 A&B: {indA.intersection(indB)}")
print(f"和集合 A|B: {indA.union(indB)}")
print(f"差集合 A-B: {indA.difference(indB)}")
print(f"差集合 B-A: {indB.difference(indA)}")
print(f"排他的論理和 A^B: {indA.symmetric_difference(indB)}")
【実行結果】
積集合 A&B: Index([6, 7, 8, 9, 10], dtype='int64')
和集合 A|B: Index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], dtype='int64')
差集合 A-B: Index([1, 2, 3, 4, 5], dtype='int64')
差集合 B-A: Index([11, 12, 13, 14, 15], dtype='int64')
排他的論理和 A^B: Index([1, 2, 3, 4, 5, 11, 12, 13, 14, 15], dtype='int64')

上記例のように、積集合(intersection)、和集合(union)、差集合(difference)、排他的論理和(symmetric_difference)といった組み込みの集合(set)と同じメソッドを使用することができます。

様々なタイプのインデックス

上記では、一般的でシンプルなケースでのIndexオブジェクトの話を紹介しました。pandasでは、利用ケースに特化したようなインデックスタイプも用意されているので、それらについても紹介したいと思います。

具体的には以降でMultiIndexDatetimeIndexPeriodIndexCategoricalIndexについて説明します。これらの利用を検討するべきケースとしてまとめてみると以下のようになります。

名称用途
MultiIndex階層的なデータ構造を扱う場合
DatetimeIndex時系列データ処理等、日時データをインデックスとして使用する場合
PeriodIndex特定の期間(月、四半期、年など)の単位で分析する場合
CategoricalIndex限られた数のカテゴリーを持つデータを扱う場合

これらのインデックスタイプは、特定種類のデータや特定の分析ニーズに合わせて最適化されているため、データ構造や分析の目的に応じて選択を検討することが重要です。

MultiIndex

MultiIndexは、階層的なデータ構造を扱う場合に適したインデックスです。以下のように作成して使うことができます。

import pandas as pd

# MultiIndexを作成 (from_tuples)
multi_index = pd.MultiIndex.from_tuples(
    [("A", 1), ("A", 2), ("B", 1), ("B", 2), ("B", 3)],
    names=["Level 1", "Level 2"],
)
print(multi_index, "\n")

# データフレームの作成
df_multi = pd.DataFrame(
    data=[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]],
    index=multi_index,
    columns=["col1", "col2", "col3"],
)
print(df_multi)
【実行結果】
MultiIndex([('A', 1),
            ('A', 2),
            ('B', 1),
            ('B', 2),
            ('B', 3)],
           names=['Level 1', 'Level 2']) 

                 col1  col2  col3
Level 1 Level 2                  
A       1           1     2     3
        2           4     5     6
B       1           7     8     9
        2          10    11    12
        3          13    14    15

上記例のように、MultiIndexを作る場合には、MultiIndexクラスのfrom_tuplesメソッドを使用することができます。引数にはタプルで各行に対するインデックスの情報を列挙したリストを指定しています。タプルの1要素目がLevel1、2要素目がLevel2、…というように該当します。また、names引数にリストで各レベルの名称を指定します。

作成されるインデックスはMultiIndexオブジェクトとなります。データフレームもあわせて作成して表示していますが、階層構造を持ったインデックスとなっていることが分かるかと思います。

なお、同様のことをfrom_arraysメソッドを使用して作成することも可能です。

# MultiIndexを作成 (from_arrays)
multi_index = pd.MultiIndex.from_arrays(
    [["A", "A", "B", "B", "B"], [1, 2, 1, 2, 3]],
    names=["Level 1", "Level 2"],
)

上記を見ていただけばわかるように、from_tuplesの場合とインデックスに関する情報の渡し方が異なっています。from_tuplesでは各行のインデックス情報のリストを渡していましたが、from_arraysでは、各レベル毎の要素を含むリストを渡しています。

from_tuplesfrom_arraysいずれでもMultiIndexは作成できますので、持っているデータ形式や開発者の使いやすさで決めてもらえばよいかと思います。

DatetimeIndex

DatetimeIndexは、時系列データ処理等、日時データをインデックスとして使用する場合に適したインデックスです。以下のように作成して使うことができます。

import pandas as pd

# 時間単位のDatetimeIndexを作成
date_index_h = pd.date_range(
    "2024-01-01",
    periods=5,
    freq="H",
)
print(date_index_h, "\n")

# データフレームの作成
df_time_value = pd.DataFrame(
    data=[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15]],
    index=date_index_h,
    columns=["val1", "val2", "val3"],
)
print(df_time_value)
【実行結果】
DatetimeIndex(['2024-01-01 00:00:00', '2024-01-01 01:00:00',
               '2024-01-01 02:00:00', '2024-01-01 03:00:00',
               '2024-01-01 04:00:00'],
              dtype='datetime64[ns]', freq='H') 

                     val1  val2  val3
2024-01-01 00:00:00     1     2     3
2024-01-01 01:00:00     4     5     6
2024-01-01 02:00:00     7     8     9
2024-01-01 03:00:00    10    11    12
2024-01-01 04:00:00    13    14    15

上記例のようにDatetimeIndexを作成する場合には、date_range関数を使用します。引数としては、開始日時と共に、時間の数をperiodsで、時間間隔をfreqで指定します。

上記例では、"2024-01-01"period=5freq="H"を指定していますので、2024-01-01 00:00:00から1時間単位で5つのインデックスを作成します。なお、freqには、"T":分単位、"H":時間単位、"D":日単位等いろいろなオプションがあるので目的に合わせて調べてみてください。

作成されるインデックスはDatetimeIndexオブジェクトとなります。データフレームもあわせて作成して表示していますが、各時間に値を持つようなデータとなっています。このようなデータは、特に設備データの時系列データを表す場合などに最適です。

また、少し異なった指定方法として以下のようにstartendを指定する方法もあります。

# 最初(start)と最後(end)を指定することも可能
# start、end、periods(時間の数)の3引数を同時に使用することはできないので注意
date_index_s_e = pd.date_range(
    start="2024-01-01 00:00:00",
    end="2024-01-01 04:00:00",
    freq="H",
)
print(date_index_s_e)

ほとんど使い方は同じで、startendの間をfreqの時間間隔で埋めたインデックスが作成されます。なお、startendperiodsの3引数を同時に使用することはできないので注意が必要です。

PeriodIndex

PeriodIndexは、特定の期間(月、四半期、年など)の単位で分析する場合に適したインデックスです。

DatetimeIndexに似ているかと思うかもしれませんが、DatetimeIndexが時刻に関する概念を持っているのに対して、PeriodIndexはあくまで期間を表現するためのインデックスであるという点で違いがあります。

例えば、2024-01は2024年の1月全体を指しますが、その中に日付や時刻といった考えは含まれません。このため、一定の期間単位(月単位、四半期、年単位等)で集計などの分析を行うようなデータの管理に特に適しています。

PeriodIndexは、以下のように作成して使うことができます。

import pandas as pd

# 月単位のPeriodIndexを作成
monthly_period = pd.period_range(
    start="2024-01",
    end="2024-12",
    freq="M",
)
print(monthly_period, "\n")

# データフレームの作成
df_period = pd.DataFrame(
    data=list(range(1, 13)),
    index=monthly_period,
    columns=["data"],
)
print(df_period)
【実行結果】
PeriodIndex(['2024-01', '2024-02', '2024-03', '2024-04', '2024-05', '2024-06',
             '2024-07', '2024-08', '2024-09', '2024-10', '2024-11', '2024-12'],
            dtype='period[M]') 

         data
2024-01     1
2024-02     2
2024-03     3
2024-04     4
2024-05     5
2024-06     6
2024-07     7
2024-08     8
2024-09     9
2024-10    10
2024-11    11
2024-12    12

上記例のようにPeriodIndexを作成する場合は、period_range関数を使用します。引数としては、開始日のstart、終了日のend、時間間隔のfreqを指定します。

上記例では、start="2024-01"end="2024-12"freq="M"としていますので、2024年1月~12月までを月単位で作成したインデックスとなります。なお、freqは、"D":日単位、"M":月単位、"Q":四半期単位等いろいろなオプションが使用できます。freqで指定できるものは、date_rangeと似ていますが概念としては異なりますので両方に同じオプションが必ずあるとは限らない点に注意が必要です。

作成されるインデックスは、PeriodIndexオブジェクトとなります。データフレームもあわせて作成して表示していますが期間を表すようなデータとなっています。一定期間ごとの集計データを扱う場合等に最適です。

CategoricalIndex

CategoricalIndexは、限られた数のカテゴリーを持つデータを扱う場合に適したインデックスです。以下のように作成して使うことができます。

import pandas as pd

# カテゴリーで使うラベルのリスト
category_data = ["High", "Medium", "Low", "Low"]

# CategoricalIndexの作成
# categoriesに指定するものがカテゴリーとして許容するリスト
# ordered=Trueとすることで"Low"<"Medium"<"High"の順序があることを示す
category_index = pd.CategoricalIndex(
    category_data,
    categories=["Low", "Medium", "High"],
    ordered=True,
)
print(category_index, "\n")

# データフレームの作成
df_categorical = pd.DataFrame(
    data=[[70, 80, 90], [40, 50, 60], [10, 20, 30], [10, 5, 10]],
    index=category_index,
    columns=["col1", "col2", "col3"],
)
print(df_categorical)
【実行結果】
CategoricalIndex(['High', 'Medium', 'Low', 'Low'], categories=['Low', 'Medium', 'High'], ordered=True, dtype='category') 

        col1  col2  col3
High      70    80    90
Medium    40    50    60
Low       10    20    30
Low       10     5    10

上記例のようにCategoricalIndexを作成する場合は、CategoricalIndexクラスのオブジェクトを生成して使用します。引数で渡しているものが少しわかりにくいかもしれませんが、category_dataが実際にデータに割り当てられるインデックスのリストです。categories引数で指定しているのは、カテゴリーとして許容するリストとなっており、orderd=Trueとするとcategoriesで指定した順序性があることを示します。

上記の例でいうとカテゴリーとして許容されるのは、"Low""Medium""High"の3つで、ordered=Trueとすることで"Low"<"Medium"<"High"の順序があることを示しています。

作成されるインデックスは、CategoricalIndexオブジェクトなります。データフレームもあわせて作成して表示していますが、上からHighMediumLowLowというカテゴリラベルが付与されたデータであると思ってもらえれば分かりやすいかと思います。このようにデータをカテゴリ分けして扱いたい場合には最適なインデックスです。

まとめ

pandasのSeriesDataFrameのインデックスで使用されているIndexの基本について解説しました。

Indexは、SeriesDataFrameでは、要素のアクセスする際に使用されます。Indexの作成方法やアクセス方法、演算などについて紹介をしています。また、その他の様々なインデックスの例ということでMultiIndexDatetimeIndexPeriodIndexCategoricalIndexといったインデックスについても簡単に紹介しました。

Indexは、SeriesDataFrameを適切に操作するために内容を十分に理解しておくべき事項です。ぜひ、Indexの理解を深めて、SeriesDataFrameを適切に扱いましょう。

Note

Indexに関する公式ドキュメントの記載はこちらを参照してください。