分類資料#
這是 pandas 分類資料類型的介紹,包括與 R 的 factor
的簡短比較。
Categoricals
是 pandas 資料類型,對應於統計中的分類變數。分類變數會採用有限且通常固定的可能值數量(categories
;R 中的 levels
)。範例包括性別、社會階級、血型、國家歸屬、觀察時間或透過李克特量表評分。
與統計分類變數相反,分類資料可能會有順序(例如「非常同意」與「同意」或「第一次觀察」與「第二次觀察」),但數值運算(加法、除法等)無法進行。
分類資料的所有值都屬於 categories
或 np.nan
。順序由 categories
的順序定義,而非值的字彙順序。在內部,資料結構包含 categories
陣列和 codes
的整數陣列,指向 categories
陣列中的實際值。
分類資料類型在以下情況下很有用
僅由幾個不同值組成的字串變數。將此類字串變數轉換為類別變數將可節省一些記憶體,請參閱此處。
變數的字典順序與邏輯順序不同(「一」、「二」、「三」)。透過轉換為類別並指定類別順序,排序和最小值/最大值將使用邏輯順序,而不是字典順序,請參閱此處。
作為訊號傳達給其他 Python 函式庫,表示此欄應視為類別變數(例如,使用適當的統計方法或繪製類型)。
另請參閱類別的 API 文件。
物件建立#
序列建立#
類別 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'
的上述範例中,我們使用了預設行為
類別會從資料中推論出來。
類別是無序的。
若要控制這些行為,請使用 CategoricalDtype
的執行個體,而不是傳遞 'category'
。
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()
建構函式來儲存因式分解步驟
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)
可以在 pandas 預期 dtype
的任何地方使用 CategoricalDtype
。例如 pandas.read_csv()
、pandas.DataFrame.astype()
或 Series
建構函式。
注意
為了方便起見,當您希望類別的預設行為是無序,且等於陣列中存在的設定值時,可以使用字串 'category'
來取代 CategoricalDtype
。換句話說,dtype='category'
等於 dtype=CategoricalDtype()
。
相等語意#
當 CategoricalDtype
的兩個實例具有相同的類別和順序時,它們會相等。在比較兩個無序類別時,不會考慮 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()
會產生類似於 Series
或型別為 string
的 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
注意
新的分類資料不會自動排序。您必須明確傳遞 ordered=True
以指出已排序的 Categorical
。
注意
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 資料類型和 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()
,如果陣列長度為偶數,則需要計算兩個值之間的平均值)無法運作,並會引發 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
比較#
在三種情況下,可以將類別資料與其他物件進行比較
與長度與類別資料相同的類清單物件(清單、Series、陣列等)進行等值比較(
==
和!=
)。當
ordered==True
且categories
相同時,類別資料的所有比較(==
、!=
、>
、>=
、<
和<=
)與其他類別 Series。類別資料與純量的所有比較。
所有其他比較,特別是類別不同或類別與任何類清單物件的「不平等」比較,會引發 TypeError
。
注意
類別資料與 Series
、np.array
、list
或類別不同或順序不同的類別資料的任何「不平等」比較,會引發 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])
當您比較兩個具有相同類別的未排序類別時,不會考慮順序
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
方法(例如 Series.value_counts()
)會使用所有類別,即使資料中沒有某些類別
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
方法(例如 DataFrame.sum()
)在 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
在 observed=False
時,GroupBy 也會顯示「未使用的」類別
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
的資料型態會被保留。
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
如果取得單一列,則類別類型不會被保留的範例:結果的 Series
的資料型態為 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']
字串和日期時間存取器#
如果 s.cat.categories
為適當的類型,存取器 .dt
和 .str
會運作
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
存取器的 method 和 property 傳回的值,以及將此 Series
的存取器轉換為 category
類型的 method 和 property 傳回的值會相等
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
或 DataFrames
,結果會是 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
的結果
arg1 |
arg2 |
相同 |
結果 |
---|---|---|---|
category |
category |
True |
category |
category (object) |
category (object) |
False |
object (dtype 推論) |
category (int) |
category (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
也適用於將兩個類別和順序資訊相同的類別合併的「簡單」情況(例如,你也可以 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
資料類型的資料寫入 HDFStore
。請參閱 此處 以取得範例和注意事項。
也可以將資料寫入和從 Stata 格式檔案讀取資料。請參閱 此處 以取得範例和注意事項。
寫入 CSV 檔案會轉換資料,有效移除分類 (類別和順序) 的任何資訊。因此,如果您讀回 CSV 檔案,則必須將相關欄位轉換回 類別
,並指派正確的類別和類別順序。
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']
寫入 SQL 資料庫時,使用 to_sql
也是如此。
遺失資料#
pandas 主要使用值 np.nan
來表示遺失資料。它在預設情況下不包含在運算中。請參閱 遺失資料區段。
遺失值不應包含在類別的 類別
中,而只能包含在 值
中。相反地,NaN 被理解為不同,而且總是可能發生。在使用類別的 代碼
時,遺失值將永遠具有 -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
是以 Python 物件實作,而不是低階 NumPy 陣列 dtype。這會導致一些問題。
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
在 category
類型的 Series
上使用 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 目前不會在套用函式中保留資料型態:如果您沿著列套用,您會取得 Series
的 object
dtype
(與取得列相同 -> 取得一個元素會傳回一個基本型態),而沿著欄套用也會轉換為物件。 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]
注意
當您提供 NumPy 陣列而非 Categorical
時,也會在某些情況下發生:使用 int 陣列(例如 np.array([1,2,3,4])
)會表現出相同的行為,而使用字串陣列(例如 np.array(["a","b","c","a"])
)則不會。