カテゴリカルデータ#
これは pandas のカテゴリカルデータ型の紹介です。R の factor との短い比較も含まれています。
Categoricals は、統計学におけるカテゴリカル変数に対応する pandas のデータ型です。カテゴリカル変数は、限られた、そして通常は固定された数の可能な値(categories; R では levels)を取ります。例としては、性別、社会階級、血液型、国籍、観測時間、リッカート尺度による評価などがあります。
統計的なカテゴリカル変数とは対照的に、カテゴリカルデータには順序がある場合があります(例: 「強く同意する」 vs 「同意する」または「最初の観測」 vs 「2番目の観測」)が、数値演算(加算、除算など)はできません。
カテゴリカルデータのすべての値は、categories または np.nan のいずれかです。順序は値の辞書順ではなく、categories の順序によって定義されます。内部的には、データ構造は categories 配列と、categories 配列内の実際の値を指す整数の codes 配列で構成されています。
カテゴリカルデータ型は、以下のケースで役立ちます。
少数の異なる値のみで構成される文字列変数。このような文字列変数をカテゴリカル変数に変換すると、メモリを節約できます。こちら を参照してください。
変数の辞書順が論理順(「one」、「two」、「three」)と同じではない場合。カテゴリカルに変換し、カテゴリに順序を指定することで、ソートと 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' を渡した例では、デフォルトの動作を使用しました。
カテゴリはデータから推論されます。
カテゴリは順序付けられていません。
これらの動作を制御するには、'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']
同様に、CategoricalDtype は DataFrame と一緒に使用して、すべての列間でカテゴリの一貫性を確保できます。
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()) によってプログラム的に決定できます。
すでに codes と categories を持っている場合は、from_codes() コンストラクタを使用して、通常のコンストラクタモードでの factorize ステップを節約できます。
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#
カテゴリカルの型は完全に記述されます。
categories: 一意の値のシーケンスで、欠損値を含まない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() と同等です。
等価セマンティクス#
2 つの CategoricalDtype のインスタンスは、同じカテゴリと順序を持つ場合に等しいと比較されます。順序付けされていない 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() を使用すると、string 型の Series または 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']
カテゴリの設定#
カテゴリを削除して新しいカテゴリを追加する操作を一度に行いたい場合(速度上の利点があります)、またはカテゴリを事前に定義されたスケールに設定するだけでよい場合は、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 を発生させます。+、-、*、/ のような数値演算やそれらに基づく演算(例: 配列の長さが偶数の場合に 2 つの値の間で平均を計算する必要がある Series.median())は機能せず、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 を発生させます。
注
カテゴリカルデータと異なるカテゴリまたは順序付けを持つ Series, np.array, list またはカテゴリカルデータとのあらゆる「非等価」比較は TypeError を発生させます。これは、カスタムカテゴリの順序付けが、順序付けを考慮する場合と考慮しない場合の 2 通りに解釈される可能性があるためです。
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 を返します。
カテゴリカル型の単一値 Series を取得するには、単一の値を持つリストを渡します。
In [156]: df.loc[["h"], "cats"]
Out[156]:
h x
Name: cats, dtype: category
Categories (3, object): ['x', 'y', 'z']
文字列および datetime アクセサ#
.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) は、その型の Series で .str.<method> / .dt.<method> を使用した場合と同じ型になります ( category 型ではありません!)。
これは、Series のアクセサのメソッドとプロパティから返される値と、category 型に変換されたこの Series のアクセサのメソッドとプロパティから返される値が等しくなることを意味します。
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 を持っている場合、パフォーマンスに影響を与えます。この場合、元の Series を category 型に変換し、その上で .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 または DataFrame を結合すると category dtype になりますが、そうでない場合は基になるカテゴリの dtype に応じて結果が異なります。カテゴリカルでない dtype になるマージは、メモリ使用量が増加する可能性があります。.astype または union_categoricals を使用して、category 結果を確実にします。
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 の結合結果をまとめたものです。
引数1 |
引数2 |
同一 |
結果 |
|---|---|---|---|
カテゴリ |
カテゴリ |
True |
カテゴリ |
カテゴリ(オブジェクト) |
カテゴリ(オブジェクト) |
False |
オブジェクト(dtypeは推論されます) |
カテゴリ (int) |
カテゴリ (float) |
False |
フロート (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 dtypes を含むデータを HDFStore に書き込むことができます。例と注意点については、こちら を参照してください。
Stata形式ファイルへのデータの書き込み、および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 の値で欠損データを表現します。デフォルトでは計算には含まれません。欠損データセクション を参照してください。
欠損値はカテゴリカルの categories には含めるべきではなく、values にのみ含めるべきです。代わりに、NaN は異なるものであり、常に可能性として存在すると理解されています。カテゴリカルの 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 の
levelsはcategoriesと名付けられています。R の
levelsは常に文字列型ですが、pandas のcategoriesは任意の dtype にできます。作成時にラベルを指定することはできません。後で
s.cat.rename_categories(new_labels)を使用してください。R の
factor関数とは対照的に、カテゴリカルデータを新しいカテゴリカル系列を作成するための唯一の入力として使用しても、未使用のカテゴリは削除されず、渡されたものと同じ新しいカテゴリカル系列が作成されます!R では、欠損値を
levels(pandas のcategories) に含めることができます。pandas はNaNカテゴリを許可しませんが、欠損値はvaluesに含まれる可能性があります。
落とし穴#
メモリ使用量#
Categorical のメモリ使用量は、カテゴリの数とデータの長さに比例します。対照的に、object dtype はデータの長さに定数を掛けたものです。
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 dtype 表現とほぼ同じか、より多くのメモリを使用します。
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
Categorical は numpy 配列ではありません#
現在、カテゴリカルデータとその基盤となる Categorical は、低レベルの NumPy 配列 dtype ではなく、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
Categoricals は数値データではないため(.categories が数値である場合でも)、category 型の Series に対して NumPy 関数を使用することは機能しません。
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 dtype の Series が得られ(1 行を取得するのと同じ -> 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 を使用するか、単に Categoricals を再利用しないようにしてください。
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"]))を使用するとそうではありません。