Python Pandas 初心者向け: Series.equals を使ったデータ検証入門

2025-05-27

具体的には、以下の条件がすべて満たされる場合に True を返します。

  • インデックスが同じであること
    2つの Series のインデックスが同じ順序で同じラベルを持っている必要があります。
  • データ型が同じであること
    2つの Seriesdtype が一致している必要があります。
  • 要素が同じであること
    同じ位置にある要素同士の値がすべて等しい必要があります。
  • 長さが同じであること
    2つの Series の要素数が一致している必要があります。

これらの条件のいずれか一つでも満たされない場合、pandas.Series.equalsFalse を返します。

使用例

import pandas as pd
import numpy as np

# 2つの等しい Series を作成
s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])

# 値は同じだがデータ型が異なる Series を作成
s3 = pd.Series([1.0, 2.0, 3.0], index=['a', 'b', 'c'])

# 値は同じだがインデックスが異なる Series を作成
s4 = pd.Series([1, 2, 3], index=['x', 'y', 'z'])

# 値が異なる Series を作成
s5 = pd.Series([1, 4, 3], index=['a', 'b', 'c'])

# equals メソッドで比較
print(f"s1.equals(s2): {s1.equals(s2)}")  # 出力: True
print(f"s1.equals(s3): {s1.equals(s3)}")  # 出力: False (データ型が異なるため)
print(f"s1.equals(s4): {s1.equals(s4)}")  # 出力: False (インデックスが異なるため)
print(f"s1.equals(s5): {s1.equals(s5)}")  # 出力: False (要素が異なるため)

# NumPy の NaN (Not a Number) の比較
s6 = pd.Series([1.0, np.nan, 3.0])
s7 = pd.Series([1.0, np.nan, 3.0])
s8 = pd.Series([1.0, float('nan'), 3.0]) # np.nan と float('nan') は等しいと評価される

print(f"s6.equals(s7): {s6.equals(s7)}")  # 出力: True (NaN 同士は等しいと評価される)
print(f"s6.equals(s8): {s6.equals(s8)}")  # 出力: True
  • NaN (Not a Number) 同士は equals メソッドでは等しいと評価されます。
  • 浮動小数点数の比較においては、equals は数値的に等しいと見なされる場合に True を返します。
  • 単純な == 演算子を使った比較は、要素ごとの比較結果を bool 型の Series として返しますが、equals メソッドは単一の bool 値(TrueFalse)を返します。


データ型の不一致 (Data Type Mismatch)

  • トラブルシューティング
    • Seriesdtype を確認します。s1.dtypes2.dtype で確認できます。
    • 必要に応じて、astype() メソッドを使ってデータ型を明示的に変換してから比較します。
    import pandas as pd
    
    s1 = pd.Series([1, 2, 3])
    s2 = pd.Series([1.0, 2.0, 3.0])
    
    print(f"s1.dtype: {s1.dtype}")
    print(f"s2.dtype: {s2.dtype}")
    print(f"s1.equals(s2): {s1.equals(s2)}")  # False
    
    s2_int = s2.astype(int)
    print(f"s2_int.dtype: {s2_int.dtype}")
    print(f"s1.equals(s2_int): {s1.equals(s2_int)}")  # True
    
  • 原因
    比較している2つの Series の要素の値は同じに見えても、データ型 (dtype) が異なっている場合があります。例えば、一方は整数の int64 で、もう一方は浮動小数点数の float64 である場合などです。
  • エラー
    equalsFalse を返す。

インデックスの不一致 (Index Mismatch)

  • トラブルシューティング
    • Seriesindex 属性を確認します。s1.indexs2.index で確認できます。
    • インデックスが異なる場合は、必要に応じて reindex() メソッドを使ってインデックスを揃えるか、インデックスを無視して値だけを比較する場合は、.values 属性で NumPy 配列を取得して比較します(ただし、順序が重要でない場合に限ります)。
    import pandas as pd
    
    s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
    s2 = pd.Series([1, 2, 3], index=['x', 'y', 'z'])
    s3 = pd.Series([1, 2, 3], index=['c', 'b', 'a'])
    
    print(f"s1.index: {s1.index}")
    print(f"s2.index: {s2.index}")
    print(f"s1.equals(s2): {s1.equals(s2)}")  # False
    
    print(f"s1.index: {s1.index}")
    print(f"s3.index: {s3.index}")
    print(f"s1.equals(s3): {s1.equals(s3)}")  # False (インデックスの順序が異なる)
    
    print(f"s1.values == s3.values: {s1.values == s3.values}") # 要素ごとの比較 (順序は考慮)
    print(f"all(s1.values == s3.values): {all(s1.values == s3.values)}") # 全ての要素が等しいか確認
    
  • 原因
    要素の値と順序は同じでも、インデックスのラベルが異なる場合や、ラベルの順序が異なる場合に False が返ります。
  • エラー
    equalsFalse を返す。

浮動小数点数の比較 (Floating-Point Comparison)

  • トラブルシューティング
    • 浮動小数点数の比較では、完全に等しいかどうかではなく、ある程度の許容範囲内で近いかどうかを評価する必要がある場合があります。その場合は、numpy.allclose() などの関数を使用することを検討してください。ただし、equals はあくまで厳密な比較を行うメソッドであるため、この状況で equalsTrue を返すようにすることはできません。
    import pandas as pd
    import numpy as np
    
    a = 0.1 + 0.2
    s1 = pd.Series([a])
    s2 = pd.Series([0.3])
    
    print(f"s1.equals(s2): {s1.equals(s2)}")  # False
    print(f"np.allclose(s1, s2): {np.allclose(s1, s2)}")  # True (許容範囲内で近い)
    
  • 原因
    浮動小数点数の演算は、コンピュータの内部表現の都合上、わずかな誤差が生じることがあります。そのため、直接 == で比較すると False になることがあります。equals も厳密な比較を行うため、このような場合に False を返すことがあります。
  • エラー
    数値的に非常に近い浮動小数点数が equalsFalse と評価される。

NaN (Not a Number) の扱い

  • トラブルシューティング
    • NaN の扱いについて理解しておくことが重要です。NaN の存在が比較結果に影響を与える場合は、fillna() メソッドなどで NaN を特定の値に置き換えてから比較することを検討してください。
    import pandas as pd
    import numpy as np
    
    s1 = pd.Series([1.0, np.nan, 3.0])
    s2 = pd.Series([1.0, np.nan, 3.0])
    s3 = pd.Series([1.0, None, 3.0]) # None は NaN として扱われる
    
    print(f"s1.equals(s2): {s1.equals(s2)}")  # True
    print(f"s1.equals(s3): {s1.equals(s3)}")  # True (None は NaN に変換される)
    
  • 原因
    NumPy の NaN は、それ自身を含めてどの値とも等しくないと評価されます(np.nan == np.nanFalse)。しかし、pandas.Series.equals では、同じ位置にある NaN 同士は等しいと評価されます。
  • エラー
    NaN を含む Series の比較で予期しない結果になる。
  • トラブルシューティング
    • object 型の Series に格納されている要素の型と、それらの比較方法を確認します。必要に応じて、より具体的な型に変換するか、要素ごとの比較ロジックを検討します。
  • 原因
    object 型の Series は、Python の任意のオブジェクトを格納できます。格納されているオブジェクトの __eq__ メソッドの挙動によって比較結果が変わる可能性があります。
  • エラー
    オブジェクト型の Series で、要素が同じに見えても equalsFalse を返す。


基本的な等価性比較

まずは、最も基本的な使い方として、値、データ型、インデックスが完全に一致する2つの Series を比較する例です。

import pandas as pd

# 完全に等しい Series を作成
s1 = pd.Series([10, 20, 30], index=['A', 'B', 'C'])
s2 = pd.Series([10, 20, 30], index=['A', 'B', 'C'])

# equals メソッドで比較
result = s1.equals(s2)
print(f"s1 と s2 は等しいか?: {result}")  # 出力: s1 と s2 は等しいか?: True

この例では、s1s2 は要素の値、順序、データ型(デフォルトでは int64)、そしてインデックスのラベルと順序がすべて同じであるため、equals メソッドは True を返します。

データ型が異なる場合の比較

次に、要素の値は同じでも、データ型が異なる Series を比較する例です。

import pandas as pd

# 値は同じだがデータ型が異なる Series を作成
s3 = pd.Series([10, 20, 30])  # デフォルトは int64
s4 = pd.Series([10.0, 20.0, 30.0])  # float64

# equals メソッドで比較
result = s3.equals(s4)
print(f"s3 と s4 は等しいか?: {result}")  # 出力: s3 と s4 は等しいか?: False

# データ型を明示的に変換してから比較
s4_int = s4.astype(int)
result_after_astype = s3.equals(s4_int)
print(f"s3 と s4 (int 型に変換後) は等しいか?: {result_after_astype}")  # 出力: s3 と s4 (int 型に変換後) は等しいか?: True

この例では、s3 は整数の Series で、s4 は浮動小数点数の Series です。equals メソッドはデータ型も比較するため、最初の比較では False が返ります。しかし、s4astype(int) で整数型に変換した後では、値とデータ型が s3 と一致するため True が返ります。

インデックスが異なる場合の比較

要素の値と順序は同じでも、インデックスが異なる Series を比較する例です。

import pandas as pd

# 値は同じだがインデックスが異なる Series を作成
s5 = pd.Series([10, 20, 30], index=['X', 'Y', 'Z'])
s6 = pd.Series([10, 20, 30], index=['A', 'B', 'C'])

# equals メソッドで比較
result = s5.equals(s6)
print(f"s5 と s6 は等しいか?: {result}")  # 出力: s5 と s6 は等しいか?: False

# インデックスの順序が異なる場合
s7 = pd.Series([10, 20, 30], index=['C', 'B', 'A'])
result_order = s1.equals(s7)
print(f"s1 と s7 は等しいか?: {result_order}")  # 出力: s1 と s7 は等しいか?: False (インデックスの順序が異なる)

この例では、s5s6 はインデックスのラベルが異なるため、equalsFalse を返します。また、s1s7 はインデックスのラベルは同じですが順序が異なるため、これも False となります。

NaN (Not a Number) を含む場合の比較

欠損値である NaN を含む Series を比較する例です。

import pandas as pd
import numpy as np

# NaN を含む Series を作成
s8 = pd.Series([1.0, np.nan, 3.0])
s9 = pd.Series([1.0, np.nan, 3.0])
s10 = pd.Series([1.0, None, 3.0])  # None も NaN として扱われる

# equals メソッドで比較
result_nan1 = s8.equals(s9)
print(f"s8 と s9 は等しいか?: {result_nan1}")  # 出力: s8 と s9 は等しいか?: True (NaN 同士は等しいと評価される)

result_none = s8.equals(s10)
print(f"s8 と s10 は等しいか?: {result_none}")  # 出力: s8 と s10 は等しいか?: True (None は NaN として扱われる)

s11 = pd.Series([1.0, np.nan, 3.0])
s12 = pd.Series([1.0, float('nan'), 3.0]) # float('nan') も np.nan と同じ
result_float_nan = s11.equals(s12)
print(f"s11 と s12 は等しいか?: {result_float_nan}") # 出力: s11 と s12 は等しいか?: True

pandasequals メソッドでは、同じ位置にある NaN 同士は等しいと評価されます。Python の Nonepandas では NaN として扱われるため、同様に等しいと評価されます。

浮動小数点数の比較

浮動小数点数の演算誤差が影響する可能性のある比較の例です。

import pandas as pd

# 浮動小数点数を含む Series を作成
a = 0.1 + 0.2
s13 = pd.Series([a])
s14 = pd.Series([0.3])

# equals メソッドで比較
result_float = s13.equals(s14)
print(f"s13 と s14 は等しいか?: {result_float}")  # 出力: s13 と s14 は等しいか?: False

# 値が非常に近い場合は True になる
s15 = pd.Series([0.3])
s16 = pd.Series([0.3 + 1e-9]) # 非常に小さな誤差
result_close_float = s15.equals(s16)
print(f"s15 と s16 は等しいか?: {result_close_float}") # 出力: s15 と s16 は等しいか?: False

浮動小数点数の比較では、数値的に非常に近い値であっても、内部表現のわずかな違いにより equalsFalse を返すことがあります。厳密な浮動小数点数の比較には注意が必要です。もし許容範囲内の誤差を考慮した比較を行いたい場合は、numpy.allclose() などの関数を Series.values 属性に対して使用することを検討してください。ただし、equals はあくまで厳密な比較を行うメソッドです。



== 演算子による要素ごとの比較と all() メソッドの組み合わせ

== 演算子を Series 同士に適用すると、要素ごとに比較した結果が bool 型の Series として返ってきます。この結果に対して all() メソッドを使用することで、すべての要素が等しいかどうかを判定できます。データ型やインデックスが完全に一致していなくても、値が同じであれば True と評価できます。

import pandas as pd

s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'], dtype='int64')
s2 = pd.Series([1.0, 2.0, 3.0], index=['a', 'b', 'c'], dtype='float64')
s3 = pd.Series([1, 2, 3], index=['x', 'y', 'z'], dtype='int64')

# 値は同じだがデータ型が異なる場合
comparison_values_dtype = (s1 == s2).all()
print(f"s1 と s2 (値比較): {comparison_values_dtype}")  # 出力: True

# 値は同じだがインデックスが異なる場合
comparison_values_index = (s1 == s3).all()
print(f"s1 と s3 (値比較): {comparison_values_index}")  # 出力: False (インデックスが異なるため要素の対応がなくなる)

# インデックスを揃えてから比較
s3_reindexed = s3.reindex(s1.index, fill_value=None)
comparison_reindexed = (s1 == s3_reindexed).all()
print(f"s1 と s3 (インデックス揃え後): {comparison_reindexed}") # 出力: False (fill_value が NaN になるため)

この方法は、主に要素の値が同じかどうかを比較したい場合に有効です。ただし、インデックスが異なる場合は、要素の対応関係が崩れるため注意が必要です。reindex() などでインデックスを揃えることもできますが、欠損値の扱いなどを考慮する必要があります。

.values 属性による NumPy 配列の比較

Series.values 属性は、その要素を NumPy 配列として返します。NumPy 配列は要素ごとの比較が可能であり、np.array_equal() 関数を使うと、2つの配列が同じ形状と要素を持つかどうかを比較できます。この方法は、インデックスを無視して値の配列が等しいかどうかを比較したい場合に便利です。

import pandas as pd
import numpy as np

s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s4 = pd.Series([1, 2, 3], index=['x', 'y', 'z'])
s5 = pd.Series([3, 2, 1], index=['a', 'b', 'c'])

# インデックスが異なっても値の配列が同じ場合
comparison_values_array = np.array_equal(s1.values, s4.values)
print(f"s1 と s4 (値の配列比較): {comparison_values_array}")  # 出力: True

# 値の順序が異なる場合
comparison_order_array = np.array_equal(s1.values, s5.values)
print(f"s1 と s5 (値の配列比較): {comparison_order_array}")  # 出力: False

.values を使うと、データ型が異なる場合でも、NumPy が暗黙的に型変換を行って比較することがあります。厳密な型比較が必要な場合は、.dtype 属性も比較する必要があります。

インデックスのみの比較

インデックスが同じであるかどうかだけを比較したい場合は、Series.index 属性を使って比較できます。

import pandas as pd

s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s6 = pd.Series([4, 5, 6], index=['a', 'b', 'c'])
s7 = pd.Series([7, 8, 9], index=['x', 'y', 'z'])

# インデックスが同じかどうか比較
index_comparison1 = s1.index.equals(s6.index)
print(f"s1 と s6 のインデックスは等しいか?: {index_comparison1}")  # 出力: True

index_comparison2 = s1.index.equals(s7.index)
print(f"s1 と s7 のインデックスは等しいか?: {index_comparison2}")  # 出力: False

Series.indexpandas.Index オブジェクトであり、これにも .equals() メソッドがあります。

特定の条件に基づいた比較

特定の条件を満たす要素だけを比較したい場合は、ブールインデックスを使って Series をフィルタリングしてから比較できます。

import pandas as pd

s8 = pd.Series([10, 20, 30, 40, 50], index=['A', 'B', 'C', 'D', 'E'])
s9 = pd.Series([10, 25, 30, 45, 50], index=['A', 'B', 'C', 'D', 'E'])

# 値が 30 以上の要素のみを比較
mask = s8 >= 30
comparison_filtered = (s8[mask] == s9[mask]).all()
print(f"s8 と s9 の 30 以上の要素は等しいか?: {comparison_filtered}")  # 出力: True

この例では、値が 30 以上の要素だけを取り出し、それらを比較しています。

近似的な浮動小数点数の比較

浮動小数点数の比較で厳密な等価性ではなく、ある程度の許容範囲内で近いかどうかを比較したい場合は、numpy.allclose() 関数を .values 属性に対して使用します。

import pandas as pd
import numpy as np

s10 = pd.Series([0.1 + 0.2])
s11 = pd.Series([0.3])

# equals による比較 (厳密)
strict_comparison = s10.equals(s11)
print(f"s10 と s11 (equals): {strict_comparison}")  # 出力: False

# numpy.allclose による比較 (近似的)
approximate_comparison = np.allclose(s10.values, s11.values)
print(f"s10 と s11 (allclose): {approximate_comparison}")  # 出力: True

np.allclose() は、指定した許容誤差内で2つの配列が要素ごとに近いかどうかを判定します。