LoginSignup
0
0

More than 1 year has passed since last update.

Pandasユーザーガイド「重複ラベル」(公式ドキュメント日本語訳)

Last updated at Posted at 2022-02-21

本記事は、Pandas の公式ドキュメントのUser Guide - Duplicate Labelsを機械翻訳した後、一部の不自然な文章を手直ししたものである。

誤訳の指摘・代訳案・質問等があればコメント欄や編集リクエストでお願いします。

重複ラベル

Indexオブジェクトは一意である必要はなく、行や列のラベルが重複していてもかまいません。最初はこのことに少し戸惑うかもしれません。SQLに詳しい人なら、行ラベルはテーブルの主キーに似ていて、SQLテーブルでは決して重複を望まないことをご存じでしょう。しかし、pandasの役割の一つは、下流のシステムに行く前に、厄介な実世界のデータをきれいにすることです。そして実世界のデータには、たとえ一意であるはずのフィールドであっても重複があります。

このセクションでは、重複ラベルによって特定の操作の挙動がどのように変わるか、操作中に重複が発生しないようにする方法、または発生した場合にそれを検出する方法について説明します。

重複ラベルの影響

いくつかのpandasのメソッド(例えば Series.reindex())には、重複があると動作しないものがあります。出力が確定できないので、pandasはエラーを送出します。

s1 = pd.Series([0, 1, 2], index=["a", "b", "b"])

s1.reindex(["a", "b", "c"])
# ---------------------------------------------------------------------------
# ValueError                                Traceback (most recent call last)
# Input In [4], in <module>
# ----> 1 s1.reindex(["a", "b", "c"])
#
# File /pandas/pandas/core/series.py:4672, in Series.reindex(self, *args, **kwargs)
#    4668         raise TypeError(
#    4669             "'index' passed as both positional and keyword argument"
#    4670         )
#    4671     kwargs.update({"index": index})
# -> 4672 return super().reindex(**kwargs)
#
# File /pandas/pandas/core/generic.py:4974, in NDFrame.reindex(self, *args, **kwargs)
#    4971     return self._reindex_multi(axes, copy, fill_value)
#    4973 # perform the reindex on the axes
# -> 4974 return self._reindex_axes(
#    4975     axes, level, limit, tolerance, method, fill_value, copy
#    4976 ).__finalize__(self, method="reindex")
#
# File /pandas/pandas/core/generic.py:4994, in NDFrame._reindex_axes(self, axes, level, limit, tolerance, method, fill_value, copy)
#    4989 new_index, indexer = ax.reindex(
#    4990     labels, level=level, limit=limit, tolerance=tolerance, method=method
#    4991 )
#    4993 axis = self._get_axis_number(a)
# -> 4994 obj = obj._reindex_with_indexers(
#    4995     {axis: [new_index, indexer]},
#    4996     fill_value=fill_value,
#    4997     copy=copy,
#    4998     allow_dups=False,
#    4999 )
#    5000 # If we've made a copy once, no need to make another one
#    5001 copy = False
#
# File /pandas/pandas/core/generic.py:5040, in NDFrame._reindex_with_indexers(self, reindexers, fill_value, copy, allow_dups)
#    5037     indexer = ensure_platform_int(indexer)
#    5039 # TODO: speed up on homogeneous DataFrame objects (see _reindex_multi)
# -> 5040 new_data = new_data.reindex_indexer(
#    5041     index,
#    5042     indexer,
#    5043     axis=baxis,
#    5044     fill_value=fill_value,
#    5045     allow_dups=allow_dups,
#    5046     copy=copy,
#    5047 )
#    5048 # If we've made a copy once, no need to make another one
#    5049 copy = False
#
# File /pandas/pandas/core/internals/managers.py:679, in BaseBlockManager.reindex_indexer(self, new_axis, indexer, axis, fill_value, allow_dups, copy, consolidate, only_slice, use_na_proxy)
#     677 # some axes don't allow reindexing with dups
#     678 if not allow_dups:
# --> 679     self.axes[axis]._validate_can_reindex(indexer)
#     681 if axis >= self.ndim:
#     682     raise IndexError("Requested axis not found in manager")
#
# File /pandas/pandas/core/indexes/base.py:4107, in Index._validate_can_reindex(self, indexer)
#    4105 # trying to reindex on an axis with duplicates
#    4106 if not self._index_as_unique and len(indexer):
# -> 4107     raise ValueError("cannot reindex on an axis with duplicate labels")
#
# ValueError: cannot reindex on an axis with duplicate labels

その他のメソッド、例えばインデックス付けは、非常に驚くべき結果をもたらすことがあります。一般に、スカラーを使ったインデックスを作成すると、次元が下がりますDataFrameをスカラーでスライスすると、Seriesが返されます。Seriesをスカラーでスライスすると、スカラーが返されます。しかし、重複ラベルがある場合はそうはなりません。

df1 = pd.DataFrame([[0, 1, 2], [3, 4, 5]], columns=["A", "A", "B"])

df1
#    A  A  B
# 0  0  1  2
# 1  3  4  5

列に重複ラベルが存在しています。このとき、'B'をスライスした場合は、シリーズが返却されます。

df1["B"]  # シリーズ
# 0    2
# 1    5
# Name: B, dtype: int64

しかし、'A'をスライスすると、データフレームが返却されます。

df1["A"]  # データフレーム
#    A  A
# 0  0  1
# 1  3  4

この挙動は行ラベルに対しても適用されます。

df2 = pd.DataFrame({"A": [0, 1, 2]}, index=["a", "a", "b"])

df2
#    A
# a  0
# a  1
# b  2

df2.loc["b", "A"]  # スカラー
# 2

df2.loc["a", "A"]  # シリーズ
# a    0
# a    1
# Name: A, dtype: int64

重複ラベルの検出

(行または列のラベルを格納する)Indexが一意であるかどうかは、Index.is_uniqueで確認できます。

df2
#    A
# a  0
# a  1
# b  2

df2.index.is_unique
# False

df2.columns.is_unique
# True

インデックスが一意であるかどうかのチェックは、大きなデータセットではやや高価です。pandasはこの結果をキャッシュするので、同じインデックスに対する再チェックは非常に高速です。

Index.duplicated()は、ラベルが重複しているかどうかを示す真偽値のndarrayを返します。

df2.index.duplicated()
# array([False,  True, False])

これは、重複する行を削除するためのブーリアンフィルタとして使用することができます。

df2.loc[~df2.index.duplicated(), :]
#    A
# a  0
# b  2

重複ラベルを処理する際に、単にそのラベルを削除するだけではなく、追加のロジックが必要な場合は、インデックスにgroupby()を使用するのが一般的なトリックです。たとえば、同じラベルを持つすべての行の平均をとって、重複を解決してみましょう。

df2.groupby(level=0).mean()
#      A
# a  0.5
# b  2.0

重複ラベルを禁止する

バージョン1.2.0で追加

前述のように、重複の処理というのは生データを読み込む際に重要な機能です。とはいえ、(pandas.concat()rename()などのメソッドからの)データ処理パイプラインの一部として重複を導入するのは避けたいかもしれません。SeriesDataFrameは、.set_flags(ables_duplicate_labels=False)を呼び出すことで、ラベルの重複を禁止します(デフォルトでは許可します)。重複するラベルがある場合は、例外が発生します。

pd.Series([0, 1, 2], index=["a", "b", "b"]).set_flags(allows_duplicate_labels=False)
# ---------------------------------------------------------------------------
# DuplicateLabelError                       Traceback (most recent call last)
# Input In [19], in <module>
# ----> 1 pd.Series([0, 1, 2], index=["a", "b", "b"]).set_flags(allows_duplicate_labels=False)
#
# File /pandas/pandas/core/generic.py:438, in NDFrame.set_flags(self, copy, allows_duplicate_labels)
#     436 df = self.copy(deep=copy)
#     437 if allows_duplicate_labels is not None:
# --> 438     df.flags["allows_duplicate_labels"] = allows_duplicate_labels
#     439 return df
#
# File /pandas/pandas/core/flags.py:105, in Flags.__setitem__(self, key, value)
#     103 if key not in self._keys:
#     104     raise ValueError(f"Unknown flag {key}. Must be one of {self._keys}")
# --> 105 setattr(self, key, value)
#
# File /pandas/pandas/core/flags.py:92, in Flags.allows_duplicate_labels(self, value)
#      90 if not value:
#      91     for ax in obj.axes:
# ---> 92         ax._maybe_check_unique()
#      94 self._allows_duplicate_labels = value
#
# File /pandas/pandas/core/indexes/base.py:715, in Index._maybe_check_unique(self)
#     712 duplicates = self._format_duplicate_message()
#     713 msg += f"\n{duplicates}"
# --> 715 raise DuplicateLabelError(msg)
#
# DuplicateLabelError: Index has duplicates.
#       positions
# label
# b        [1, 2]

これはDataFrameの行ラベルと列ラベルの両方に適用されます。

pd.DataFrame([[0, 1, 2], [3, 4, 5]], columns=["A", "B", "C"],).set_flags(
    allows_duplicate_labels=False
)
#    A  B  C
# 0  0  1  2
# 1  3  4  5

これは、そのオブジェクトが重複してラベルを持つことができるかどうかを示すallow_duplicate_labels属性によって、チェックまたは設定することができます。

df = pd.DataFrame({"A": [0, 1, 2, 3]}, index=["x", "y", "X", "Y"]).set_flags(
    allows_duplicate_labels=False
)


df
#    A
# x  0
# y  1
# X  2
# Y  3

df.flags.allows_duplicate_labels
# False

DataFrame.set_flags()は、allow_duplicate_labelsなどの属性に値を設定した新しいDataFrameを返すときに使用できます。

df2 = df.set_flags(allows_duplicate_labels=True)

df2.flags.allows_duplicate_labels
# True

この新しいDataFrameは、古いDataFrameと同じデータを参照するビューとして返されます。あるいは、同じオブジェクトに直接プロパティを設定することもできます。

df2.flags.allows_duplicate_labels = False

df2.flags.allows_duplicate_labels
# False

生の乱雑なデータを処理する場合、まず(ラベルが重複している可能性がある)乱雑なデータを読み込み、重複の削除を行い、その後、データパイプラインが重複を発生させないようにするのが良いでしょう。

raw = pd.read_csv("...")
deduplicated = raw.groupby(level=0).first()  # 重複の削除
deduplicated.flags.allows_duplicate_labels = False  # 後の重複を禁ずる

ラベルが重複しているSeriesDataFrameに対してallow_duplicate_labels=Falseを設定しようとしたり、重複を禁止しているSeriesDataFrameに対して重複したラベルを導入する操作を行うと、errors.DuplicateLabelErrorが発生します。

df.rename(str.upper)
# ---------------------------------------------------------------------------
# DuplicateLabelError                       Traceback (most recent call last)
# Input In [28], in <module>
# ----> 1 df.rename(str.upper)
#
# File /pandas/pandas/core/frame.py:5077, in DataFrame.rename(self, mapper, index, columns, axis, copy, inplace, level, errors)
#    4958 def rename(
#    4959     self,
#    4960     mapper: Renamer | None = None,
#    (...)
#    4968     errors: str = "ignore",
#    4969 ) -> DataFrame | None:
#    4970     """
#    4971     Alter axes labels.
#    4972
#    (...)
#    5075     4  3  6
#    5076     """
# -> 5077     return super()._rename(
#    5078         mapper=mapper,
#    5079         index=index,
#    5080         columns=columns,
#    5081         axis=axis,
#    5082         copy=copy,
#    5083         inplace=inplace,
#    5084         level=level,
#    5085         errors=errors,
#    5086     )
#
# File /pandas/pandas/core/generic.py:1171, in NDFrame._rename(self, mapper, index, columns, axis, copy, inplace, level, errors)
#    1169     return None
#    1170 else:
# -> 1171     return result.__finalize__(self, method="rename")
#
# File /pandas/pandas/core/generic.py:5549, in NDFrame.__finalize__(self, other, method, **kwargs)
#    5546 for name in other.attrs:
#    5547     self.attrs[name] = other.attrs[name]
# -> 5549 self.flags.allows_duplicate_labels = other.flags.allows_duplicate_labels
#    5550 # For subclasses using _metadata.
#    5551 for name in set(self._metadata) & set(other._metadata):
#
# File /pandas/pandas/core/flags.py:92, in Flags.allows_duplicate_labels(self, value)
#      90 if not value:
#      91     for ax in obj.axes:
# ---> 92         ax._maybe_check_unique()
#      94 self._allows_duplicate_labels = value
#
# File /pandas/pandas/core/indexes/base.py:715, in Index._maybe_check_unique(self)
#     712 duplicates = self._format_duplicate_message()
#     713 msg += f"\n{duplicates}"
# --> 715 raise DuplicateLabelError(msg)
#
# DuplicateLabelError: Index has duplicates.
#       positions
# label
# X        [0, 2]
# Y        [1, 3]

このエラーメッセージには、重複しているラベルと、SeriesまたはDataFrame内の重複しているラベル(「オリジナル」を含む)の数値位置が含まれています。

重複ラベルの伝搬

一般に、重複を禁止することは「粘着性(sticky)」があり、操作によって保存されます。

s1 = pd.Series(0, index=["a", "b"]).set_flags(allows_duplicate_labels=False)

s1
# a    0
# b    0
# dtype: int64

s1.head().rename({"a": "b"})
# ---------------------------------------------------------------------------
# DuplicateLabelError                       Traceback (most recent call last)
# Input In [31], in <module>
# ----> 1 s1.head().rename({"a": "b"})
#
# File /pandas/pandas/core/series.py:4601, in Series.rename(self, index, axis, copy, inplace, level, errors)
#    4598     axis = self._get_axis_number(axis)
#    4600 if callable(index) or is_dict_like(index):
# -> 4601     return super()._rename(
#    4602         index, copy=copy, inplace=inplace, level=level, errors=errors
#    4603     )
#    4604 else:
#    4605     return self._set_name(index, inplace=inplace)
#
# File /pandas/pandas/core/generic.py:1171, in NDFrame._rename(self, mapper, index, columns, axis, copy, inplace, level, errors)
#    1169     return None
#    1170 else:
# -> 1171     return result.__finalize__(self, method="rename")
#
# File /pandas/pandas/core/generic.py:5549, in NDFrame.__finalize__(self, other, method, **kwargs)
#    5546 for name in other.attrs:
#    5547     self.attrs[name] = other.attrs[name]
# -> 5549 self.flags.allows_duplicate_labels = other.flags.allows_duplicate_labels
#    5550 # For subclasses using _metadata.
#    5551 for name in set(self._metadata) & set(other._metadata):
#
# File /pandas/pandas/core/flags.py:92, in Flags.allows_duplicate_labels(self, value)
#      90 if not value:
#      91     for ax in obj.axes:
# ---> 92         ax._maybe_check_unique()
#      94 self._allows_duplicate_labels = value
#
# File /pandas/pandas/core/indexes/base.py:715, in Index._maybe_check_unique(self)
#     712 duplicates = self._format_duplicate_message()
#     713 msg += f"\n{duplicates}"
# --> 715 raise DuplicateLabelError(msg)
#
# DuplicateLabelError: Index has duplicates.
#       positions
# label
# b        [0, 1]

これは試験的な機能です。現在、多くのメソッドがallow_duplicate_labelsの値の伝播に失敗しています。将来のバージョンでは、1つ以上のDataFrameまたはSeriesオブジェクトを取得したり返したりするすべてのメソッドがallows_duplicate_labelsを伝搬することが期待されています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0