カテゴリカルデータ#

これは、pandasのカテゴリカルデータ型への入門であり、Rのfactorとの簡単な比較を含みます。

Categoricalsは、統計におけるカテゴリカル変数に対応するpandasのデータ型です。カテゴリカル変数は、限られた、通常は固定された数の可能な値 (categories; Rではlevels) を持ちます。例としては、性別、社会階級、血液型、所属国、観測時間、またはリッカート尺度による評価などがあります。

統計的なカテゴリカル変数とは対照的に、カテゴリカルデータは順序を持つ場合があります(例:「強く同意する」対「同意する」または「最初の観測」対「2番目の観測」)が、数値演算(加算、除算、...)は不可能です。

カテゴリカルデータのすべての値は、categoriesまたはnp.nanのいずれかです。順序は、値の辞書順ではなく、categoriesの順序によって定義されます。内部的には、データ構造はcategories配列と、categories配列内の実際の値を指す整数のcodes配列で構成されています。

カテゴリカルデータ型は、次のような場合に役立ちます。

  • わずかな異なる値のみで構成される文字列変数。このような文字列変数をカテゴリカル変数に変換すると、メモリを節約できます。詳しくはこちらを参照してください。

  • 変数の辞書順が論理順序と同じではない場合(「1」、「2」、「3」)。カテゴリカルに変換し、カテゴリに順序を指定することで、ソートとmin/maxは辞書順ではなく論理順序を使用します。詳しくはこちらを参照してください。

  • この列をカテゴリカル変数として扱うべきであることを他のPythonライブラリに伝えるためのシグナルとして(例:適切な統計手法やプロットタイプを使用するため)。

カテゴリカルに関するAPIドキュメントも参照してください。

オブジェクトの作成#

Seriesの作成#

SeriesまたはDataFrameの列のカテゴリカルは、いくつかの方法で作成できます。

Seriesを構築する際にdtype="category"を指定する

In [1]: s = pd.Series(["a", "b", "c", "a"], dtype="category")

In [2]: s
Out[2]: 
0    a
1    b
2    c
3    a
dtype: category
Categories (3, object): ['a', 'b', 'c']

既存のSeriesまたは列をcategoryのdtypeに変換する

In [3]: df = pd.DataFrame({"A": ["a", "b", "c", "a"]})

In [4]: df["B"] = df["A"].astype("category")

In [5]: df
Out[5]: 
   A  B
0  a  a
1  b  b
2  c  c
3  a  a

データを離散的なビンにグループ化する、cut()などの特別な関数を使用する。ドキュメントのタイリングの例を参照してください。

In [6]: df = pd.DataFrame({"value": np.random.randint(0, 100, 20)})

In [7]: labels = ["{0} - {1}".format(i, i + 9) for i in range(0, 100, 10)]

In [8]: df["group"] = pd.cut(df.value, range(0, 105, 10), right=False, labels=labels)

In [9]: df.head(10)
Out[9]: 
   value    group
0     65  60 - 69
1     49  40 - 49
2     56  50 - 59
3     43  40 - 49
4     43  40 - 49
5     91  90 - 99
6     32  30 - 39
7     87  80 - 89
8     36  30 - 39
9      8    0 - 9

pandas.CategoricalオブジェクトをSeriesに渡すか、DataFrameに割り当てる。

In [10]: raw_cat = pd.Categorical(
   ....:     ["a", "b", "c", "a"], categories=["b", "c", "d"], ordered=False
   ....: )
   ....: 

In [11]: s = pd.Series(raw_cat)

In [12]: s
Out[12]: 
0    NaN
1      b
2      c
3    NaN
dtype: category
Categories (3, object): ['b', 'c', 'd']

In [13]: df = pd.DataFrame({"A": ["a", "b", "c", "a"]})

In [14]: df["B"] = raw_cat

In [15]: df
Out[15]: 
   A    B
0  a  NaN
1  b    b
2  c    c
3  a  NaN

カテゴリカルデータには、特定のcategory dtypeがあります。

In [16]: df.dtypes
Out[16]: 
A      object
B    category
dtype: object

DataFrameの作成#

単一の列がカテゴリカルに変換された前のセクションと同様に、DataFrameのすべての列は、構築中または構築後にカテゴリカルにバッチ変換できます。

DataFrameコンストラクタでdtype="category"を指定することで、構築中にこれを行うことができます。

In [17]: df = pd.DataFrame({"A": list("abca"), "B": list("bccd")}, dtype="category")

In [18]: df.dtypes
Out[18]: 
A    category
B    category
dtype: object

各列に存在するカテゴリが異なることに注意してください。変換は列ごとに行われるため、特定の列に存在するラベルのみがカテゴリになります。

In [19]: df["A"]
Out[19]: 
0    a
1    b
2    c
3    a
Name: A, dtype: category
Categories (3, object): ['a', 'b', 'c']

In [20]: df["B"]
Out[20]: 
0    b
1    c
2    c
3    d
Name: B, dtype: category
Categories (3, object): ['b', 'c', 'd']

同様に、既存のDataFrameのすべての列は、DataFrame.astype()を使用してバッチ変換できます。

In [21]: df = pd.DataFrame({"A": list("abca"), "B": list("bccd")})

In [22]: df_cat = df.astype("category")

In [23]: df_cat.dtypes
Out[23]: 
A    category
B    category
dtype: object

この変換も同様に列ごとに行われます。

In [24]: df_cat["A"]
Out[24]: 
0    a
1    b
2    c
3    a
Name: A, dtype: category
Categories (3, object): ['a', 'b', 'c']

In [25]: df_cat["B"]
Out[25]: 
0    b
1    c
2    c
3    d
Name: B, dtype: category
Categories (3, object): ['b', 'c', 'd']

動作の制御#

上記でdtype='category'を渡した例では、デフォルトの動作を使用しました。

  1. カテゴリはデータから推測されます。

  2. カテゴリは順序付けされていません。

これらの動作を制御するには、'category'を渡す代わりに、CategoricalDtypeのインスタンスを使用します。

In [26]: from pandas.api.types import CategoricalDtype

In [27]: s = pd.Series(["a", "b", "c", "a"])

In [28]: cat_type = CategoricalDtype(categories=["b", "c", "d"], ordered=True)

In [29]: s_cat = s.astype(cat_type)

In [30]: s_cat
Out[30]: 
0    NaN
1      b
2      c
3    NaN
dtype: category
Categories (3, object): ['b' < 'c' < 'd']

同様に、CategoricalDtypeDataFrameで使用して、カテゴリがすべての列で一貫していることを確認できます。

In [31]: from pandas.api.types import CategoricalDtype

In [32]: df = pd.DataFrame({"A": list("abca"), "B": list("bccd")})

In [33]: cat_type = CategoricalDtype(categories=list("abcd"), ordered=True)

In [34]: df_cat = df.astype(cat_type)

In [35]: df_cat["A"]
Out[35]: 
0    a
1    b
2    c
3    a
Name: A, dtype: category
Categories (4, object): ['a' < 'b' < 'c' < 'd']

In [36]: df_cat["B"]
Out[36]: 
0    b
1    c
2    c
3    d
Name: B, dtype: category
Categories (4, object): ['a' < 'b' < 'c' < 'd']

テーブル全体の変換を実行する場合、DataFrame全体のすべてのラベルが各列のカテゴリとして使用されます。categoriesパラメーターは、categories = pd.unique(df.to_numpy().ravel())によってプログラムで決定できます。

すでにcodescategoriesがある場合は、from_codes()コンストラクターを使用して、通常のコンストラクターモードでの因数分解ステップを節約できます。

In [37]: splitter = np.random.choice([0, 1], 5, p=[0.5, 0.5])

In [38]: s = pd.Series(pd.Categorical.from_codes(splitter, categories=["train", "test"]))

元のデータの復元#

元のSeriesまたはNumPy配列に戻すには、Series.astype(original_dtype)またはnp.asarray(categorical)を使用します。

In [39]: s = pd.Series(["a", "b", "c", "a"])

In [40]: s
Out[40]: 
0    a
1    b
2    c
3    a
dtype: object

In [41]: s2 = s.astype("category")

In [42]: s2
Out[42]: 
0    a
1    b
2    c
3    a
dtype: category
Categories (3, object): ['a', 'b', 'c']

In [43]: s2.astype(str)
Out[43]: 
0    a
1    b
2    c
3    a
dtype: object

In [44]: np.asarray(s2)
Out[44]: array(['a', 'b', 'c', 'a'], dtype=object)

Rのfactor関数とは対照的に、カテゴリカルデータは入力値を文字列に変換しません。カテゴリは、元の値と同じデータ型になります。

Rのfactor関数とは対照的に、現時点では作成時にラベルを割り当て/変更する方法はありません。作成後にカテゴリを変更するには、categoriesを使用してください。

CategoricalDtype#

カテゴリカルの型は、以下によって完全に記述されます。

  1. categories:一意の値のシーケンスであり、欠損値はありません

  2. ordered:ブール値

この情報はCategoricalDtypeに格納できます。categories引数はオプションであり、これは、pandas.Categoricalが作成されるときにデータに存在する内容から実際のカテゴリを推測する必要があることを意味します。カテゴリはデフォルトで順序付けされていないと見なされます。

In [45]: from pandas.api.types import CategoricalDtype

In [46]: CategoricalDtype(["a", "b", "c"])
Out[46]: CategoricalDtype(categories=['a', 'b', 'c'], ordered=False, categories_dtype=object)

In [47]: CategoricalDtype(["a", "b", "c"], ordered=True)
Out[47]: CategoricalDtype(categories=['a', 'b', 'c'], ordered=True, categories_dtype=object)

In [48]: CategoricalDtype()
Out[48]: CategoricalDtype(categories=None, ordered=False, categories_dtype=None)

CategoricalDtypeは、pandasがdtypeを予期する場所で使用できます。たとえば、pandas.read_csv()pandas.DataFrame.astype()、またはSeriesコンストラクターで使用できます。

便宜上、カテゴリが順序付けされておらず、配列に存在する設定値と等しくなるデフォルトの動作が必要な場合は、CategoricalDtypeの代わりに文字列'category'を使用できます。言い換えれば、dtype='category'dtype=CategoricalDtype()と同等です。

等価性の意味#

同じカテゴリと順序を持っている場合、CategoricalDtypeの2つのインスタンスは等しいとみなされます。順序付けされていない2つのカテゴリカルを比較する場合、categoriesの順序は考慮されません。

In [49]: c1 = CategoricalDtype(["a", "b", "c"], ordered=False)

# Equal, since order is not considered when ordered=False
In [50]: c1 == CategoricalDtype(["b", "c", "a"], ordered=False)
Out[50]: True

# Unequal, since the second CategoricalDtype is ordered
In [51]: c1 == CategoricalDtype(["a", "b", "c"], ordered=True)
Out[51]: False

CategoricalDtypeのすべてのインスタンスは、文字列'category'と等しいとみなされます。

In [52]: c1 == "category"
Out[52]: True

説明#

カテゴリカルデータでdescribe()を使用すると、型stringSeriesまたはDataFrameと同様の出力が生成されます。

In [53]: cat = pd.Categorical(["a", "c", "c", np.nan], categories=["b", "a", "c"])

In [54]: df = pd.DataFrame({"cat": cat, "s": ["a", "c", "c", np.nan]})

In [55]: df.describe()
Out[55]: 
       cat  s
count    3  3
unique   2  2
top      c  c
freq     2  2

In [56]: df["cat"].describe()
Out[56]: 
count     3
unique    2
top       c
freq      2
Name: cat, dtype: object

カテゴリの操作#

カテゴリカルデータには、可能な値をリストしたcategoriesプロパティと、順序が重要かどうかを示すorderedプロパティがあります。これらのプロパティは、s.cat.categoriesおよびs.cat.orderedとして公開されます。カテゴリと順序を手動で指定しない場合、それらは渡された引数から推測されます。

In [57]: s = pd.Series(["a", "b", "c", "a"], dtype="category")

In [58]: s.cat.categories
Out[58]: Index(['a', 'b', 'c'], dtype='object')

In [59]: s.cat.ordered
Out[59]: False

特定の順序でカテゴリを渡すことも可能です。

In [60]: s = pd.Series(pd.Categorical(["a", "b", "c", "a"], categories=["c", "b", "a"]))

In [61]: s.cat.categories
Out[61]: Index(['c', 'b', 'a'], dtype='object')

In [62]: s.cat.ordered
Out[62]: False

新しいカテゴリカルデータは、自動的に順序付けられ**ません**。順序付けられたCategoricalを示すには、明示的にordered=Trueを渡す必要があります。

unique()の結果は、Series.cat.categoriesと同じになるとは限りません。なぜなら、Series.unique()には、出現順にカテゴリを返し、実際に存在する値のみを含めるといういくつかの保証があるからです。

In [63]: s = pd.Series(list("babc")).astype(CategoricalDtype(list("abcd")))

In [64]: s
Out[64]: 
0    b
1    a
2    b
3    c
dtype: category
Categories (4, object): ['a', 'b', 'c', 'd']

# categories
In [65]: s.cat.categories
Out[65]: Index(['a', 'b', 'c', 'd'], dtype='object')

# uniques
In [66]: s.unique()
Out[66]: 
['b', 'a', 'c']
Categories (4, object): ['a', 'b', 'c', 'd']

カテゴリの名前変更#

カテゴリの名前変更は、rename_categories()メソッドを使用して行います。

In [67]: s = pd.Series(["a", "b", "c", "a"], dtype="category")

In [68]: s
Out[68]: 
0    a
1    b
2    c
3    a
dtype: category
Categories (3, object): ['a', 'b', 'c']

In [69]: new_categories = ["Group %s" % g for g in s.cat.categories]

In [70]: s = s.cat.rename_categories(new_categories)

In [71]: s
Out[71]: 
0    Group a
1    Group b
2    Group c
3    Group a
dtype: category
Categories (3, object): ['Group a', 'Group b', 'Group c']

# You can also pass a dict-like object to map the renaming
In [72]: s = s.cat.rename_categories({1: "x", 2: "y", 3: "z"})

In [73]: s
Out[73]: 
0    Group a
1    Group b
2    Group c
3    Group a
dtype: category
Categories (3, object): ['Group a', 'Group b', 'Group c']

Rのfactorとは対照的に、カテゴリカルデータは文字列以外の型のカテゴリを持つことができます。

カテゴリは一意である必要があります。そうでない場合、ValueErrorが発生します。

In [74]: try:
   ....:     s = s.cat.rename_categories([1, 1, 1])
   ....: except ValueError as e:
   ....:     print("ValueError:", str(e))
   ....: 
ValueError: Categorical categories must be unique

カテゴリはNaNであってはなりません。そうでない場合、ValueErrorが発生します。

In [75]: try:
   ....:     s = s.cat.rename_categories([1, 2, np.nan])
   ....: except ValueError as e:
   ....:     print("ValueError:", str(e))
   ....: 
ValueError: Categorical categories cannot be null

新しいカテゴリの追加#

カテゴリの追加は、add_categories()メソッドを使用して行うことができます。

In [76]: s = s.cat.add_categories([4])

In [77]: s.cat.categories
Out[77]: Index(['Group a', 'Group b', 'Group c', 4], dtype='object')

In [78]: s
Out[78]: 
0    Group a
1    Group b
2    Group c
3    Group a
dtype: category
Categories (4, object): ['Group a', 'Group b', 'Group c', 4]

カテゴリの削除#

カテゴリの削除は、remove_categories()メソッドを使用して行うことができます。削除された値はnp.nanに置き換えられます。

In [79]: s = s.cat.remove_categories([4])

In [80]: s
Out[80]: 
0    Group a
1    Group b
2    Group c
3    Group a
dtype: category
Categories (3, object): ['Group a', 'Group b', 'Group c']

未使用のカテゴリの削除#

未使用のカテゴリの削除も可能です。

In [81]: s = pd.Series(pd.Categorical(["a", "b", "a"], categories=["a", "b", "c", "d"]))

In [82]: s
Out[82]: 
0    a
1    b
2    a
dtype: category
Categories (4, object): ['a', 'b', 'c', 'd']

In [83]: s.cat.remove_unused_categories()
Out[83]: 
0    a
1    b
2    a
dtype: category
Categories (2, object): ['a', 'b']

カテゴリの設定#

1つのステップで新しいカテゴリの削除と追加を行いたい場合(これはいくらかの速度上の利点があります)、または単にカテゴリを事前に定義されたスケールに設定したい場合は、set_categories()を使用します。

In [84]: s = pd.Series(["one", "two", "four", "-"], dtype="category")

In [85]: s
Out[85]: 
0     one
1     two
2    four
3       -
dtype: category
Categories (4, object): ['-', 'four', 'one', 'two']

In [86]: s = s.cat.set_categories(["one", "two", "three", "four"])

In [87]: s
Out[87]: 
0     one
1     two
2    four
3     NaN
dtype: category
Categories (4, object): ['one', 'two', 'three', 'four']

Categorical.set_categories()は、一部のカテゴリが意図的に省略されたのか、それともスペルミスがあったのか、または(Python3の場合)型が異なる(例:NumPy S1 dtypeとPython文字列)ためなのかを判断できないことに注意してください。これにより、予期しない動作が発生する可能性があります。

ソートと順序#

カテゴリカルデータが順序付けられている場合(s.cat.ordered == True)、カテゴリの順序には意味があり、特定の操作が可能です。カテゴリカルが順序付けられていない場合、.min()/.max()TypeErrorを発生させます。

In [88]: s = pd.Series(pd.Categorical(["a", "b", "c", "a"], ordered=False))

In [89]: s = s.sort_values()

In [90]: s = pd.Series(["a", "b", "c", "a"]).astype(CategoricalDtype(ordered=True))

In [91]: s = s.sort_values()

In [92]: s
Out[92]: 
0    a
3    a
1    b
2    c
dtype: category
Categories (3, object): ['a' < 'b' < 'c']

In [93]: s.min(), s.max()
Out[93]: ('a', 'c')

カテゴリカルデータを順序付けられた状態にするにはas_ordered()を使用し、順序付けられていない状態にするにはas_unordered()を使用します。これらはデフォルトで新しいオブジェクトを返します。

In [94]: s.cat.as_ordered()
Out[94]: 
0    a
3    a
1    b
2    c
dtype: category
Categories (3, object): ['a' < 'b' < 'c']

In [95]: s.cat.as_unordered()
Out[95]: 
0    a
3    a
1    b
2    c
dtype: category
Categories (3, object): ['a', 'b', 'c']

ソートでは、データ型に存在する字句順ではなく、カテゴリによって定義された順序が使用されます。これは文字列および数値データの場合でも同様です。

In [96]: s = pd.Series([1, 2, 3, 1], dtype="category")

In [97]: s = s.cat.set_categories([2, 3, 1], ordered=True)

In [98]: s
Out[98]: 
0    1
1    2
2    3
3    1
dtype: category
Categories (3, int64): [2 < 3 < 1]

In [99]: s = s.sort_values()

In [100]: s
Out[100]: 
1    2
2    3
0    1
3    1
dtype: category
Categories (3, int64): [2 < 3 < 1]

In [101]: s.min(), s.max()
Out[101]: (2, 1)

並べ替え#

カテゴリの並べ替えは、Categorical.reorder_categories()およびCategorical.set_categories()メソッドを使用して行うことができます。Categorical.reorder_categories()の場合、古いカテゴリはすべて新しいカテゴリに含まれている必要があり、新しいカテゴリは許可されません。これにより、必然的にソート順はカテゴリ順と同じになります。

In [102]: s = pd.Series([1, 2, 3, 1], dtype="category")

In [103]: s = s.cat.reorder_categories([2, 3, 1], ordered=True)

In [104]: s
Out[104]: 
0    1
1    2
2    3
3    1
dtype: category
Categories (3, int64): [2 < 3 < 1]

In [105]: s = s.sort_values()

In [106]: s
Out[106]: 
1    2
2    3
0    1
3    1
dtype: category
Categories (3, int64): [2 < 3 < 1]

In [107]: s.min(), s.max()
Out[107]: (2, 1)

新しいカテゴリの割り当てとカテゴリの並べ替えの違いに注意してください。前者はカテゴリの名前を変更し、それによってSeries内の個々の値の名前を変更しますが、最初の位置が最後にソートされていた場合、名前が変更された値は引き続き最後にソートされます。並べ替えとは、値のソート方法が後で異なることを意味しますが、Series内の個々の値が変更されるわけではありません。

Categoricalが順序付けられていない場合、Series.min()およびSeries.max()TypeErrorを発生させます。+-*/のような数値演算、およびそれらに基づく操作(例:Series.median()。これは、配列の長さが偶数の場合、2つの値の間の平均を計算する必要がある)は機能せず、TypeErrorを発生させます。

複数列のソート#

カテゴリカルなdtyped列は、他の列と同様に複数列のソートに参加します。カテゴリカルの順序は、その列のcategoriesによって決定されます。

In [108]: dfs = pd.DataFrame(
   .....:     {
   .....:         "A": pd.Categorical(
   .....:             list("bbeebbaa"),
   .....:             categories=["e", "a", "b"],
   .....:             ordered=True,
   .....:         ),
   .....:         "B": [1, 2, 1, 2, 2, 1, 2, 1],
   .....:     }
   .....: )
   .....: 

In [109]: dfs.sort_values(by=["A", "B"])
Out[109]: 
   A  B
2  e  1
3  e  2
7  a  1
6  a  2
0  b  1
5  b  1
1  b  2
4  b  2

categoriesの並べ替えは、将来のソートを変更します。

In [110]: dfs["A"] = dfs["A"].cat.reorder_categories(["a", "b", "e"])

In [111]: dfs.sort_values(by=["A", "B"])
Out[111]: 
   A  B
7  a  1
6  a  2
0  b  1
5  b  1
1  b  2
4  b  2
2  e  1
3  e  2

比較#

カテゴリカルデータと他のオブジェクトの比較は、次の3つのケースで可能です。

  • カテゴリカルデータと同じ長さのリストのようなオブジェクト(リスト、Series、配列など)との等価性(==および!=)の比較。

  • ordered==Trueであり、categoriesが同じである場合、カテゴリカルデータと別のカテゴリカルSeriesのすべての比較(==!=>>=<、および<=)。

  • カテゴリカルデータとスカラーのすべての比較。

その他すべての比較、特にカテゴリが異なる2つのカテゴリカルまたはリストのようなオブジェクトとのカテゴリカルの「非等価」比較は、TypeErrorを発生させます。

カテゴリが異なるまたは順序が異なるカテゴリカルデータと、Seriesnp.arraylistとのカテゴリカルデータの「非等価」比較はすべて、カスタムカテゴリの順序を考慮する解釈と考慮しない解釈の2つの方法で解釈できるため、TypeErrorを発生させます。

In [112]: cat = pd.Series([1, 2, 3]).astype(CategoricalDtype([3, 2, 1], ordered=True))

In [113]: cat_base = pd.Series([2, 2, 2]).astype(CategoricalDtype([3, 2, 1], ordered=True))

In [114]: cat_base2 = pd.Series([2, 2, 2]).astype(CategoricalDtype(ordered=True))

In [115]: cat
Out[115]: 
0    1
1    2
2    3
dtype: category
Categories (3, int64): [3 < 2 < 1]

In [116]: cat_base
Out[116]: 
0    2
1    2
2    2
dtype: category
Categories (3, int64): [3 < 2 < 1]

In [117]: cat_base2
Out[117]: 
0    2
1    2
2    2
dtype: category
Categories (1, int64): [2]

同じカテゴリと順序を持つカテゴリカルとの比較、またはスカラーとの比較は機能します。

In [118]: cat > cat_base
Out[118]: 
0     True
1    False
2    False
dtype: bool

In [119]: cat > 2
Out[119]: 
0     True
1    False
2    False
dtype: bool

等価性の比較は、同じ長さのリストのようなオブジェクトとスカラーで機能します。

In [120]: cat == cat_base
Out[120]: 
0    False
1     True
2    False
dtype: bool

In [121]: cat == np.array([1, 2, 3])
Out[121]: 
0    True
1    True
2    True
dtype: bool

In [122]: cat == 2
Out[122]: 
0    False
1     True
2    False
dtype: bool

カテゴリが同じではないため、これは機能しません。

In [123]: try:
   .....:     cat > cat_base2
   .....: except TypeError as e:
   .....:     print("TypeError:", str(e))
   .....: 
TypeError: Categoricals can only be compared if 'categories' are the same.

カテゴリカルシリーズをカテゴリカルデータではないリストのようなオブジェクトと「非等価」比較する場合は、明示的にカテゴリカルデータを元の値に変換する必要があります。

In [124]: base = np.array([1, 2, 3])

In [125]: try:
   .....:     cat > base
   .....: except TypeError as e:
   .....:     print("TypeError:", str(e))
   .....: 
TypeError: Cannot compare a Categorical for op __gt__ with type <class 'numpy.ndarray'>.
If you want to compare values, use 'np.asarray(cat) <op> other'.

In [126]: np.asarray(cat) > base
Out[126]: array([False, False, False])

同じカテゴリを持つ2つの順序付けられていないカテゴリカルを比較する場合、順序は考慮されません。

In [127]: c1 = pd.Categorical(["a", "b"], categories=["a", "b"], ordered=False)

In [128]: c2 = pd.Categorical(["a", "b"], categories=["b", "a"], ordered=False)

In [129]: c1 == c2
Out[129]: array([ True,  True])

操作#

Series.min()Series.max()Series.mode()とは別に、カテゴリカルデータでは次の操作が可能です。

Series.value_counts()のようなSeriesメソッドは、一部のカテゴリがデータに存在しない場合でも、すべてのカテゴリを使用します。

In [130]: s = pd.Series(pd.Categorical(["a", "b", "c", "c"], categories=["c", "a", "b", "d"]))

In [131]: s.value_counts()
Out[131]: 
c    2
a    1
b    1
d    0
Name: count, dtype: int64

DataFrame.sum()のようなDataFrameメソッドも、observed=Falseの場合、「未使用の」カテゴリを表示します。

In [132]: columns = pd.Categorical(
   .....:     ["One", "One", "Two"], categories=["One", "Two", "Three"], ordered=True
   .....: )
   .....: 

In [133]: df = pd.DataFrame(
   .....:     data=[[1, 2, 3], [4, 5, 6]],
   .....:     columns=pd.MultiIndex.from_arrays([["A", "B", "B"], columns]),
   .....: ).T
   .....: 

In [134]: df.groupby(level=1, observed=False).sum()
Out[134]: 
       0  1
One    3  9
Two    3  6
Three  0  0

Groupbyも、observed=Falseの場合、「未使用の」カテゴリを表示します。

In [135]: cats = pd.Categorical(
   .....:     ["a", "b", "b", "b", "c", "c", "c"], categories=["a", "b", "c", "d"]
   .....: )
   .....: 

In [136]: df = pd.DataFrame({"cats": cats, "values": [1, 2, 2, 2, 3, 4, 5]})

In [137]: df.groupby("cats", observed=False).mean()
Out[137]: 
      values
cats        
a        1.0
b        2.0
c        4.0
d        NaN

In [138]: cats2 = pd.Categorical(["a", "a", "b", "b"], categories=["a", "b", "c"])

In [139]: df2 = pd.DataFrame(
   .....:     {
   .....:         "cats": cats2,
   .....:         "B": ["c", "d", "c", "d"],
   .....:         "values": [1, 2, 3, 4],
   .....:     }
   .....: )
   .....: 

In [140]: df2.groupby(["cats", "B"], observed=False).mean()
Out[140]: 
        values
cats B        
a    c     1.0
     d     2.0
b    c     3.0
     d     4.0
c    c     NaN
     d     NaN

ピボットテーブル

In [141]: raw_cat = pd.Categorical(["a", "a", "b", "b"], categories=["a", "b", "c"])

In [142]: df = pd.DataFrame({"A": raw_cat, "B": ["c", "d", "c", "d"], "values": [1, 2, 3, 4]})

In [143]: pd.pivot_table(df, values="values", index=["A", "B"], observed=False)
Out[143]: 
     values
A B        
a c     1.0
  d     2.0
b c     3.0
  d     4.0

データマングリング#

最適化されたpandasデータアクセスメソッド.loc.iloc.at、および.iatは、通常どおりに機能します。唯一の違いは(取得のための)戻り値の型と、categoriesにすでに存在する値のみを割り当てることができるという点です。

取得#

スライシング操作の結果が DataFrame または Series 型の列のいずれかである場合、category dtype は保持されます。

In [144]: idx = pd.Index(["h", "i", "j", "k", "l", "m", "n"])

In [145]: cats = pd.Series(["a", "b", "b", "b", "c", "c", "c"], dtype="category", index=idx)

In [146]: values = [1, 2, 2, 2, 3, 4, 5]

In [147]: df = pd.DataFrame({"cats": cats, "values": values}, index=idx)

In [148]: df.iloc[2:4, :]
Out[148]: 
  cats  values
j    b       2
k    b       2

In [149]: df.iloc[2:4, :].dtypes
Out[149]: 
cats      category
values       int64
dtype: object

In [150]: df.loc["h":"j", "cats"]
Out[150]: 
h    a
i    b
j    b
Name: cats, dtype: category
Categories (3, object): ['a', 'b', 'c']

In [151]: df[df["cats"] == "b"]
Out[151]: 
  cats  values
i    b       2
j    b       2
k    b       2

カテゴリ型が保持されない例としては、1行だけを取り出す場合が挙げられます。結果の Series は dtype が object になります。

# get the complete "h" row as a Series
In [152]: df.loc["h", :]
Out[152]: 
cats      a
values    1
Name: h, dtype: object

カテゴリデータから単一の項目を返す場合も、長さが「1」のカテゴリではなく、値が返されます。

In [153]: df.iat[0, 0]
Out[153]: 'a'

In [154]: df["cats"] = df["cats"].cat.rename_categories(["x", "y", "z"])

In [155]: df.at["h", "cats"]  # returns a string
Out[155]: 'x'

これは、R の factor 関数とは対照的です。factor(c(1,2,3))[1] は単一の値 factor を返します。

型が category の単一の値 Series を取得するには、単一の値を持つリストを渡します。

In [156]: df.loc[["h"], "cats"]
Out[156]: 
h    x
Name: cats, dtype: category
Categories (3, object): ['x', 'y', 'z']

文字列および日時アクセサ#

アクセサ .dt および .str は、s.cat.categories が適切な型である場合に機能します。

In [157]: str_s = pd.Series(list("aabb"))

In [158]: str_cat = str_s.astype("category")

In [159]: str_cat
Out[159]: 
0    a
1    a
2    b
3    b
dtype: category
Categories (2, object): ['a', 'b']

In [160]: str_cat.str.contains("a")
Out[160]: 
0     True
1     True
2    False
3    False
dtype: bool

In [161]: date_s = pd.Series(pd.date_range("1/1/2015", periods=5))

In [162]: date_cat = date_s.astype("category")

In [163]: date_cat
Out[163]: 
0   2015-01-01
1   2015-01-02
2   2015-01-03
3   2015-01-04
4   2015-01-05
dtype: category
Categories (5, datetime64[ns]): [2015-01-01, 2015-01-02, 2015-01-03, 2015-01-04, 2015-01-05]

In [164]: date_cat.dt.day
Out[164]: 
0    1
1    2
2    3
3    4
4    5
dtype: int32

返される Series(または DataFrame)は、.str.<method> / .dt.<method> をその型(category 型ではない!)の Series に使用した場合と同じ型になります。

つまり、Series のアクセサのメソッドおよびプロパティから返される値と、この Seriescategory 型に変換したもののアクセサのメソッドおよびプロパティから返される値は同じになります。

In [165]: ret_s = str_s.str.contains("a")

In [166]: ret_cat = str_cat.str.contains("a")

In [167]: ret_s.dtype == ret_cat.dtype
Out[167]: True

In [168]: ret_s == ret_cat
Out[168]: 
0    True
1    True
2    True
3    True
dtype: bool

処理は categories に対して行われ、新しい Series が構築されます。これは、要素が多数繰り返される文字列型の Series (つまり、Series 内の一意の要素の数が Series の長さよりも大幅に小さい場合)には、パフォーマンス上の影響があります。この場合、元の Seriescategory 型に変換し、そこで .str.<method> または .dt.<property> を使用する方が高速になる可能性があります。

設定#

カテゴリ列(または Series)に値を設定するには、その値が categories に含まれている必要があります。

In [169]: idx = pd.Index(["h", "i", "j", "k", "l", "m", "n"])

In [170]: cats = pd.Categorical(["a", "a", "a", "a", "a", "a", "a"], categories=["a", "b"])

In [171]: values = [1, 1, 1, 1, 1, 1, 1]

In [172]: df = pd.DataFrame({"cats": cats, "values": values}, index=idx)

In [173]: df.iloc[2:4, :] = [["b", 2], ["b", 2]]

In [174]: df
Out[174]: 
  cats  values
h    a       1
i    a       1
j    b       2
k    b       2
l    a       1
m    a       1
n    a       1

In [175]: try:
   .....:     df.iloc[2:4, :] = [["c", 3], ["c", 3]]
   .....: except TypeError as e:
   .....:     print("TypeError:", str(e))
   .....: 
TypeError: Cannot setitem on a Categorical with a new category, set the categories first

カテゴリデータを代入して値を設定する場合も、categories が一致することを確認します。

In [176]: df.loc["j":"k", "cats"] = pd.Categorical(["a", "a"], categories=["a", "b"])

In [177]: df
Out[177]: 
  cats  values
h    a       1
i    a       1
j    a       2
k    a       2
l    a       1
m    a       1
n    a       1

In [178]: try:
   .....:     df.loc["j":"k", "cats"] = pd.Categorical(["b", "b"], categories=["a", "b", "c"])
   .....: except TypeError as e:
   .....:     print("TypeError:", str(e))
   .....: 
TypeError: Cannot set a Categorical with another, without identical categories

他の型の列の一部に Categorical を代入すると、値が使用されます。

In [179]: df = pd.DataFrame({"a": [1, 1, 1, 1, 1], "b": ["a", "a", "a", "a", "a"]})

In [180]: df.loc[1:2, "a"] = pd.Categorical(["b", "b"], categories=["a", "b"])

In [181]: df.loc[2:3, "b"] = pd.Categorical(["b", "b"], categories=["a", "b"])

In [182]: df
Out[182]: 
   a  b
0  1  a
1  b  a
2  b  b
3  1  b
4  1  a

In [183]: df.dtypes
Out[183]: 
a    object
b    object
dtype: object

マージ / 連結#

デフォルトでは、同じカテゴリを含む Series または DataFrames を結合すると、category dtype になります。それ以外の場合、結果は基になるカテゴリの dtype に依存します。カテゴリ型ではない dtype になるマージは、メモリ使用量が増加する可能性があります。category の結果を保証するには、.astype または union_categoricals を使用してください。

In [184]: from pandas.api.types import union_categoricals

# same categories
In [185]: s1 = pd.Series(["a", "b"], dtype="category")

In [186]: s2 = pd.Series(["a", "b", "a"], dtype="category")

In [187]: pd.concat([s1, s2])
Out[187]: 
0    a
1    b
0    a
1    b
2    a
dtype: category
Categories (2, object): ['a', 'b']

# different categories
In [188]: s3 = pd.Series(["b", "c"], dtype="category")

In [189]: pd.concat([s1, s3])
Out[189]: 
0    a
1    b
0    b
1    c
dtype: object

# Output dtype is inferred based on categories values
In [190]: int_cats = pd.Series([1, 2], dtype="category")

In [191]: float_cats = pd.Series([3.0, 4.0], dtype="category")

In [192]: pd.concat([int_cats, float_cats])
Out[192]: 
0    1.0
1    2.0
0    3.0
1    4.0
dtype: float64

In [193]: pd.concat([s1, s3]).astype("category")
Out[193]: 
0    a
1    b
0    b
1    c
dtype: category
Categories (3, object): ['a', 'b', 'c']

In [194]: union_categoricals([s1.array, s3.array])
Out[194]: 
['a', 'b', 'b', 'c']
Categories (3, object): ['a', 'b', 'c']

次の表は、Categoricals のマージの結果をまとめたものです。

arg1

arg2

同一

結果

カテゴリ

カテゴリ

True

カテゴリ

カテゴリ (object)

カテゴリ (object)

False

object (dtype は推論されます)

カテゴリ (int)

カテゴリ (float)

False

float (dtype は推論されます)

結合#

必ずしも同じカテゴリを持たないカテゴリを結合したい場合は、union_categoricals() 関数がカテゴリのリストを結合します。新しいカテゴリは、結合されるカテゴリの結合になります。

In [195]: from pandas.api.types import union_categoricals

In [196]: a = pd.Categorical(["b", "c"])

In [197]: b = pd.Categorical(["a", "b"])

In [198]: union_categoricals([a, b])
Out[198]: 
['b', 'c', 'a', 'b']
Categories (3, object): ['b', 'c', 'a']

デフォルトでは、結果のカテゴリはデータに表示される順序で順序付けられます。カテゴリを辞書順にソートする場合は、sort_categories=True 引数を使用します。

In [199]: union_categoricals([a, b], sort_categories=True)
Out[199]: 
['b', 'c', 'a', 'b']
Categories (3, object): ['a', 'b', 'c']

union_categoricals は、同じカテゴリおよび順序情報を持つ2つのカテゴリを結合する「簡単な」ケースでも機能します(たとえば、append も可能です)。

In [200]: a = pd.Categorical(["a", "b"], ordered=True)

In [201]: b = pd.Categorical(["a", "b", "a"], ordered=True)

In [202]: union_categoricals([a, b])
Out[202]: 
['a', 'b', 'a', 'b', 'a']
Categories (2, object): ['a' < 'b']

カテゴリが順序付けられており、同一ではないため、以下は TypeError を発生させます。

In [203]: a = pd.Categorical(["a", "b"], ordered=True)

In [204]: b = pd.Categorical(["a", "b", "c"], ordered=True)

In [205]: union_categoricals([a, b])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[205], line 1
----> 1 union_categoricals([a, b])

File ~/work/pandas/pandas/pandas/core/dtypes/concat.py:341, in union_categoricals(to_union, sort_categories, ignore_order)
    339     if all(c.ordered for c in to_union):
    340         msg = "to union ordered Categoricals, all categories must be the same"
--> 341         raise TypeError(msg)
    342     raise TypeError("Categorical.ordered must be the same")
    344 if ignore_order:

TypeError: to union ordered Categoricals, all categories must be the same

カテゴリまたは順序が異なる順序付きカテゴリは、ignore_ordered=True 引数を使用することで結合できます。

In [206]: a = pd.Categorical(["a", "b", "c"], ordered=True)

In [207]: b = pd.Categorical(["c", "b", "a"], ordered=True)

In [208]: union_categoricals([a, b], ignore_order=True)
Out[208]: 
['a', 'b', 'c', 'c', 'b', 'a']
Categories (3, object): ['a', 'b', 'c']

union_categoricals() は、CategoricalIndex またはカテゴリデータを含む Series でも機能しますが、結果の配列は常にプレーンな Categorical になることに注意してください。

In [209]: a = pd.Series(["b", "c"], dtype="category")

In [210]: b = pd.Series(["a", "b"], dtype="category")

In [211]: union_categoricals([a, b])
Out[211]: 
['b', 'c', 'a', 'b']
Categories (3, object): ['b', 'c', 'a']

union_categoricals は、カテゴリを結合するときにカテゴリの整数コードを再コード化することがあります。これはおそらく望ましい動作ですが、カテゴリの正確な番号付けに依存している場合は注意してください。

In [212]: c1 = pd.Categorical(["b", "c"])

In [213]: c2 = pd.Categorical(["a", "b"])

In [214]: c1
Out[214]: 
['b', 'c']
Categories (2, object): ['b', 'c']

# "b" is coded to 0
In [215]: c1.codes
Out[215]: array([0, 1], dtype=int8)

In [216]: c2
Out[216]: 
['a', 'b']
Categories (2, object): ['a', 'b']

# "b" is coded to 1
In [217]: c2.codes
Out[217]: array([0, 1], dtype=int8)

In [218]: c = union_categoricals([c1, c2])

In [219]: c
Out[219]: 
['b', 'c', 'a', 'b']
Categories (3, object): ['b', 'c', 'a']

# "b" is coded to 0 throughout, same as c1, different from c2
In [220]: c.codes
Out[220]: array([0, 1, 2, 0], dtype=int8)

データの入出力#

category dtype を含むデータを HDFStore に書き込むことができます。例と注意点については、こちら を参照してください。

データを *Stata* 形式のファイルに書き込んだり、そこから読み込んだりすることも可能です。例と注意点については、こちら を参照してください。

CSV ファイルに書き込むと、データが変換され、カテゴリに関する情報(カテゴリと順序付け)が事実上削除されます。したがって、CSV ファイルを読み込み直す場合は、関連する列を category に変換し、正しいカテゴリとカテゴリの順序付けを代入する必要があります。

In [221]: import io

In [222]: s = pd.Series(pd.Categorical(["a", "b", "b", "a", "a", "d"]))

# rename the categories
In [223]: s = s.cat.rename_categories(["very good", "good", "bad"])

# reorder the categories and add missing categories
In [224]: s = s.cat.set_categories(["very bad", "bad", "medium", "good", "very good"])

In [225]: df = pd.DataFrame({"cats": s, "vals": [1, 2, 3, 4, 5, 6]})

In [226]: csv = io.StringIO()

In [227]: df.to_csv(csv)

In [228]: df2 = pd.read_csv(io.StringIO(csv.getvalue()))

In [229]: df2.dtypes
Out[229]: 
Unnamed: 0     int64
cats          object
vals           int64
dtype: object

In [230]: df2["cats"]
Out[230]: 
0    very good
1         good
2         good
3    very good
4    very good
5          bad
Name: cats, dtype: object

# Redo the category
In [231]: df2["cats"] = df2["cats"].astype("category")

In [232]: df2["cats"] = df2["cats"].cat.set_categories(
   .....:     ["very bad", "bad", "medium", "good", "very good"]
   .....: )
   .....: 

In [233]: df2.dtypes
Out[233]: 
Unnamed: 0       int64
cats          category
vals             int64
dtype: object

In [234]: df2["cats"]
Out[234]: 
0    very good
1         good
2         good
3    very good
4    very good
5          bad
Name: cats, dtype: category
Categories (5, object): ['very bad', 'bad', 'medium', 'good', 'very good']

to_sql を使用して SQL データベースに書き込む場合も同様です。

欠損データ#

pandas は主に np.nan の値を欠損データを表すために使用します。これはデフォルトでは計算に含まれません。欠損データのセクション を参照してください。

欠損値は、Categorical の categories には含めるべきではなく、values のみに含めるべきです。代わりに、NaN は異なり、常に可能性があると理解されています。Categorical の codes を操作する場合、欠損値のコードは常に -1 になります。

In [235]: s = pd.Series(["a", "b", np.nan, "a"], dtype="category")

# only two categories
In [236]: s
Out[236]: 
0      a
1      b
2    NaN
3      a
dtype: category
Categories (2, object): ['a', 'b']

In [237]: s.cat.codes
Out[237]: 
0    0
1    1
2   -1
3    0
dtype: int8

欠損データを処理するためのメソッド(例:isna()fillna()dropna())はすべて正常に動作します。

In [238]: s = pd.Series(["a", "b", np.nan], dtype="category")

In [239]: s
Out[239]: 
0      a
1      b
2    NaN
dtype: category
Categories (2, object): ['a', 'b']

In [240]: pd.isna(s)
Out[240]: 
0    False
1    False
2     True
dtype: bool

In [241]: s.fillna("a")
Out[241]: 
0    a
1    b
2    a
dtype: category
Categories (2, object): ['a', 'b']

R の factor との違い#

R の factor 関数との次の違いを観察できます。

  • R の levelscategories という名前です。

  • R の levels は常に文字列型ですが、pandas の categories は任意の dtype にできます。

  • 作成時にラベルを指定することはできません。後で s.cat.rename_categories(new_labels) を使用します。

  • R の factor 関数とは対照的に、カテゴリデータを単一の入力として使用して新しいカテゴリシリーズを作成しても、未使用のカテゴリは削除されず、渡されたものと同じ新しいカテゴリシリーズが作成されます。

  • Rでは、欠損値をlevels(pandasのcategories)に含めることができます。pandasでは、NaNカテゴリは許可されていませんが、欠損値はvaluesに含めることができます。

注意点#

メモリ使用量#

Categoricalのメモリ使用量は、カテゴリ数とデータ長に比例します。対照的に、object型はデータの長さに定数を掛けたものです。

In [242]: s = pd.Series(["foo", "bar"] * 1000)

# object dtype
In [243]: s.nbytes
Out[243]: 16000

# category dtype
In [244]: s.astype("category").nbytes
Out[244]: 2016

カテゴリ数がデータの長さに近づくと、Categoricalは、同等のobject型表現とほぼ同じか、それ以上のメモリを使用します。

In [245]: s = pd.Series(["foo%04d" % i for i in range(2000)])

# object dtype
In [246]: s.nbytes
Out[246]: 16000

# category dtype
In [247]: s.astype("category").nbytes
Out[247]: 20000

Categoricalnumpy配列ではない#

現在、カテゴリデータと基になるCategoricalは、低レベルのNumPy配列型としてではなく、Pythonオブジェクトとして実装されています。これにより、いくつかの問題が発生します。

NumPy自体は新しいdtypeについて認識していません

In [248]: try:
   .....:     np.dtype("category")
   .....: except TypeError as e:
   .....:     print("TypeError:", str(e))
   .....: 
TypeError: data type 'category' not understood

In [249]: dtype = pd.Categorical(["a"]).dtype

In [250]: try:
   .....:     np.dtype(dtype)
   .....: except TypeError as e:
   .....:     print("TypeError:", str(e))
   .....: 
TypeError: Cannot interpret 'CategoricalDtype(categories=['a'], ordered=False, categories_dtype=object)' as a data type

Dtypeの比較は機能します

In [251]: dtype == np.str_
Out[251]: False

In [252]: np.str_ == dtype
Out[252]: False

Seriesがカテゴリデータを格納しているかを確認するには、hasattr(s, 'cat')を使用します。

In [253]: hasattr(pd.Series(["a"], dtype="category"), "cat")
Out[253]: True

In [254]: hasattr(pd.Series(["a"]), "cat")
Out[254]: False

型がcategorySeriesに対してNumPy関数を使用することは、Categoricalsが数値データではないため(.categoriesが数値である場合でも)、機能するべきではありません。

In [255]: s = pd.Series(pd.Categorical([1, 2, 3, 4]))

In [256]: try:
   .....:     np.sum(s)
   .....: except TypeError as e:
   .....:     print("TypeError:", str(e))
   .....: 
TypeError: 'Categorical' with dtype category does not support reduction 'sum'

そのような関数が機能する場合は、pandas-dev/pandasにバグを報告してください!

applyでのdtype#

pandasは現在、apply関数でdtypeを保持しません。行に沿って適用すると、object dtypeSeriesが生成され(行を取得するのと同じで、1つの要素を取得すると基本型が返されます)、列に沿って適用するとobjectに変換されます。NaN値は影響を受けません。fillnaを使用すると、関数を適用する前に欠損値を処理できます。

In [257]: df = pd.DataFrame(
   .....:     {
   .....:         "a": [1, 2, 3, 4],
   .....:         "b": ["a", "b", "c", "d"],
   .....:         "cats": pd.Categorical([1, 2, 3, 2]),
   .....:     }
   .....: )
   .....: 

In [258]: df.apply(lambda row: type(row["cats"]), axis=1)
Out[258]: 
0    <class 'int'>
1    <class 'int'>
2    <class 'int'>
3    <class 'int'>
dtype: object

In [259]: df.apply(lambda col: col.dtype, axis=0)
Out[259]: 
a          int64
b         object
cats    category
dtype: object

カテゴリインデックス#

CategoricalIndexは、重複した要素によるインデックス付けをサポートするのに役立つインデックスの一種です。これはCategoricalのコンテナであり、重複した要素が多数あるインデックスの効率的なインデックス付けと保存を可能にします。詳細については、高度なインデックス付けのドキュメントを参照してください。

インデックスを設定すると、CategoricalIndexが作成されます

In [260]: cats = pd.Categorical([1, 2, 3, 4], categories=[4, 2, 3, 1])

In [261]: strings = ["a", "b", "c", "d"]

In [262]: values = [4, 2, 3, 1]

In [263]: df = pd.DataFrame({"strings": strings, "values": values}, index=cats)

In [264]: df.index
Out[264]: CategoricalIndex([1, 2, 3, 4], categories=[4, 2, 3, 1], ordered=False, dtype='category')

# This now sorts by the categories order
In [265]: df.sort_index()
Out[265]: 
  strings  values
4       d       1
2       b       2
3       c       3
1       a       4

副作用#

CategoricalからSeriesを作成しても、入力されたCategoricalはコピーされません。つまり、Seriesへの変更は、ほとんどの場合、元のCategoricalを変更します。

In [266]: cat = pd.Categorical([1, 2, 3, 10], categories=[1, 2, 3, 4, 10])

In [267]: s = pd.Series(cat, name="cat")

In [268]: cat
Out[268]: 
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]

In [269]: s.iloc[0:2] = 10

In [270]: cat
Out[270]: 
[10, 10, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]

そのような動作を防ぐには、copy=Trueを使用するか、単にCategoricalを再利用しないでください。

In [271]: cat = pd.Categorical([1, 2, 3, 10], categories=[1, 2, 3, 4, 10])

In [272]: s = pd.Series(cat, name="cat", copy=True)

In [273]: cat
Out[273]: 
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]

In [274]: s.iloc[0:2] = 10

In [275]: cat
Out[275]: 
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]

これは、Categoricalの代わりにNumPy配列を提供する場合にも発生します。int配列(例:np.array([1,2,3,4]))を使用すると同じ動作を示しますが、文字列配列(例:np.array(["a","b","c","a"]))を使用するとそうなりません。