分类数据#
这是对Pandas分类数据类型的介绍,包括与R的简短比较 factor 。
Categoricals 是与统计中的分类变量相对应的Pandas数据类型。类别变量具有有限且通常是固定数量的可能值 (categories ; levels 在R)中。例如,性别、社会阶层、血型、国家从属关系、观察时间或通过利克特量表进行评级。
与统计分类变量不同,分类数据可能有顺序(例如‘强烈同意’与‘同意’或‘第一次观察’与‘第二次观察’),但数值运算(加法、除法等)是不可能的。
分类数据的所有值都在 categories 或 np.nan 。顺序由以下顺序定义 categories ,而不是值的词汇顺序。在内部,数据结构由一个 categories 数组和一个整数数组 codes 这些值指向 categories 数组。
分类数据类型在以下情况下很有用:
仅由几个不同值组成的字符串变量。将这样的字符串变量转换为分类变量将节省一些内存,请参见 here 。
变量的词法顺序与逻辑顺序不同(“一”、“二”、“三”)。通过转换为类别并指定类别的顺序,排序和最小/最大将使用逻辑顺序而不是词法顺序,请参见 here 。
作为向其他Python库发出的信号,该列应被视为分类变量(例如,使用合适的统计方法或绘图类型)。
另请参阅 API docs on categoricals 。
对象创建#
系列剧创作#
直截了当的 Series 或列中的 DataFrame 可以通过以下几种方式创建:
通过指定 dtype="category" 在构造 Series :
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 数据类型:
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() ,它将数据分组到离散的存储箱中。请参阅 example on tiling 在文件里。
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 可以在构建过程中或构建后批量转换为类别。
这可以在构造期间通过指定 dtype="category" 在 DataFrame 构造函数:
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() 在正常构造函数模式下保存因式分解步骤的构造函数:
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)
In [47]: CategoricalDtype(["a", "b", "c"], ordered=True)
Out[47]: CategoricalDtype(categories=['a', 'b', 'c'], ordered=True)
In [48]: CategoricalDtype()
Out[48]: CategoricalDtype(categories=None, ordered=False)
A CategoricalDtype 可以在Pandas期望的任何地方使用 dtype 。例如 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
警告
因为 dtype='category' 本质上是 CategoricalDtype(None, False) ,并且由于所有实例 CategoricalDtype 比较等于 'category' ,所有实例 CategoricalDtype 比较等于一个 CategoricalDtype(None, False) ,不考虑 categories 或 ordered 。
描述#
使用 describe() 将产生类似于 Series 或 DataFrame 类型的 string 。
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
备注
新的分类数据是 not 自动订购的。您必须显式传递 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']
重命名类别#
重命名类别的方法是为 Series.cat.categories 属性或通过使用 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]: s.cat.categories = ["Group %s" % g for g in s.cat.categories]
In [70]: s
Out[70]:
0 Group a
1 Group b
2 Group c
3 Group a
dtype: category
Categories (3, object): ['Group a', 'Group b', 'Group c']
In [71]: s = s.cat.rename_categories([1, 2, 3])
In [72]: s
Out[72]:
0 1
1 2
2 3
3 1
dtype: category
Categories (3, int64): [1, 2, 3]
# You can also pass a dict-like object to map the renaming
In [73]: s = s.cat.rename_categories({1: "x", 2: "y", 3: "z"})
In [74]: s
Out[74]:
0 x
1 y
2 z
3 x
dtype: category
Categories (3, object): ['x', 'y', 'z']
备注
与R相反 factor ,分类数据可以具有除字符串之外的其他类型的类别。
备注
请注意,分配新类别是一项原地操作,而大多数其他操作在 Series.cat 默认情况下,返回一个新的 Series 数据类型的 category 。
类别必须是唯一的或 ValueError 已提出:
In [75]: try:
....: s.cat.categories = [1, 1, 1]
....: except ValueError as e:
....: print("ValueError:", str(e))
....:
ValueError: Categorical categories must be unique
类别也不能是 NaN 或者是 ValueError 已提出:
In [76]: try:
....: s.cat.categories = [1, 2, np.nan]
....: except ValueError as e:
....: print("ValueError:", str(e))
....:
ValueError: Categorical categories cannot be null
追加新类别#
追加类别可以通过使用 add_categories() 方法:
In [77]: s = s.cat.add_categories([4])
In [78]: s.cat.categories
Out[78]: Index(['x', 'y', 'z', 4], dtype='object')
In [79]: s
Out[79]:
0 x
1 y
2 z
3 x
dtype: category
Categories (4, object): ['x', 'y', 'z', 4]
正在删除类别#
删除类别可以使用 remove_categories() 方法。删除的值将替换为 np.nan .:
In [80]: s = s.cat.remove_categories([4])
In [81]: s
Out[81]:
0 x
1 y
2 z
3 x
dtype: category
Categories (3, object): ['x', 'y', 'z']
删除未使用的类别#
删除未使用的类别也可以执行以下操作:
In [82]: s = pd.Series(pd.Categorical(["a", "b", "a"], categories=["a", "b", "c", "d"]))
In [83]: s
Out[83]:
0 a
1 b
2 a
dtype: category
Categories (4, object): ['a', 'b', 'c', 'd']
In [84]: s.cat.remove_unused_categories()
Out[84]:
0 a
1 b
2 a
dtype: category
Categories (2, object): ['a', 'b']
设置类别#
如果您想在一个步骤中删除和添加新类别(这具有一定的速度优势),或者简单地将类别设置为预定义的比例,请使用 set_categories() 。
In [85]: s = pd.Series(["one", "two", "four", "-"], dtype="category")
In [86]: s
Out[86]:
0 one
1 two
2 four
3 -
dtype: category
Categories (4, object): ['-', 'four', 'one', 'two']
In [87]: s = s.cat.set_categories(["one", "two", "three", "four"])
In [88]: s
Out[88]:
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 [89]: s = pd.Series(pd.Categorical(["a", "b", "c", "a"], ordered=False))
In [90]: s.sort_values(inplace=True)
In [91]: s = pd.Series(["a", "b", "c", "a"]).astype(CategoricalDtype(ordered=True))
In [92]: s.sort_values(inplace=True)
In [93]: s
Out[93]:
0 a
3 a
1 b
2 c
dtype: category
Categories (3, object): ['a' < 'b' < 'c']
In [94]: s.min(), s.max()
Out[94]: ('a', 'c')
您可以使用以下命令设置要排序的分类数据 as_ordered() 或通过使用以下命令取消订购 as_unordered() 。默认情况下,这些函数将返回 new 对象。
In [95]: s.cat.as_ordered()
Out[95]:
0 a
3 a
1 b
2 c
dtype: category
Categories (3, object): ['a' < 'b' < 'c']
In [96]: s.cat.as_unordered()
Out[96]:
0 a
3 a
1 b
2 c
dtype: category
Categories (3, object): ['a', 'b', 'c']
排序将使用类别定义的顺序,而不是数据类型上存在的任何词汇顺序。这甚至适用于字符串和数字数据:
In [97]: s = pd.Series([1, 2, 3, 1], dtype="category")
In [98]: s = s.cat.set_categories([2, 3, 1], ordered=True)
In [99]: s
Out[99]:
0 1
1 2
2 3
3 1
dtype: category
Categories (3, int64): [2 < 3 < 1]
In [100]: s.sort_values(inplace=True)
In [101]: s
Out[101]:
1 2
2 3
0 1
3 1
dtype: category
Categories (3, int64): [2 < 3 < 1]
In [102]: s.min(), s.max()
Out[102]: (2, 1)
重新排序#
对类别进行重新排序可以通过 Categorical.reorder_categories() 以及 Categorical.set_categories() 方法:研究方法。为 Categorical.reorder_categories() ,所有旧类别必须包括在新类别中,不允许新类别。这必然会使排序顺序与类别顺序相同。
In [103]: s = pd.Series([1, 2, 3, 1], dtype="category")
In [104]: s = s.cat.reorder_categories([2, 3, 1], ordered=True)
In [105]: s
Out[105]:
0 1
1 2
2 3
3 1
dtype: category
Categories (3, int64): [2 < 3 < 1]
In [106]: s.sort_values(inplace=True)
In [107]: s
Out[107]:
1 2
2 3
0 1
3 1
dtype: category
Categories (3, int64): [2 < 3 < 1]
In [108]: s.min(), s.max()
Out[108]: (2, 1)
备注
请注意分配新类别和重新排序类别之间的区别:第一个重命名类别,因此重命名 Series ,但如果第一个位置排在最后,则重命名的值仍将排在最后。重新排序意味着以后对值的排序方式不同,但 Series 都变了。
备注
如果 Categorical is not ordered, Series.min() and Series.max() will raise TypeError. Numeric operations like +, -, *, / and operations based on them (e.g. Series.median(), which would need to compute the mean between two values if the length of an array is even) do not work and raise a `` 类型错误``。
多列排序#
分类dtype列将以与其他列类似的方式参与多列排序。定语的顺序由 categories 那个栏目的。
In [109]: dfs = pd.DataFrame(
.....: {
.....: "A": pd.Categorical(
.....: list("bbeebbaa"),
.....: categories=["e", "a", "b"],
.....: ordered=True,
.....: ),
.....: "B": [1, 2, 1, 2, 2, 1, 2, 1],
.....: }
.....: )
.....:
In [110]: dfs.sort_values(by=["A", "B"])
Out[110]:
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 [111]: dfs["A"] = dfs["A"].cat.reorder_categories(["a", "b", "e"])
In [112]: dfs.sort_values(by=["A", "B"])
Out[112]:
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
比较#
在三种情况下,可以将分类数据与其他对象进行比较:
比较平等 (
==和!=)到类似列表的对象(列表、系列、数组,...)与分类数据相同的长度。所有比较 (
==,!=,>,>=,<,以及<=)分类数据到另一个分类序列,当ordered==True以及categories都是一样的。分类数据与标量的所有比较。
所有其他比较,特别是具有不同类别的两个范畴或范畴与任何类似列表的对象的“不相等”比较,都将引发 TypeError 。
备注
分类数据的任何“不相等”比较 Series , np.array , list 或具有不同类别或排序的分类数据将引发 TypeError 因为自定义类别排序可以用两种方式解释:一种考虑了排序,另一种不考虑。
In [113]: cat = pd.Series([1, 2, 3]).astype(CategoricalDtype([3, 2, 1], ordered=True))
In [114]: cat_base = pd.Series([2, 2, 2]).astype(CategoricalDtype([3, 2, 1], ordered=True))
In [115]: cat_base2 = pd.Series([2, 2, 2]).astype(CategoricalDtype(ordered=True))
In [116]: cat
Out[116]:
0 1
1 2
2 3
dtype: category
Categories (3, int64): [3 < 2 < 1]
In [117]: cat_base
Out[117]:
0 2
1 2
2 2
dtype: category
Categories (3, int64): [3 < 2 < 1]
In [118]: cat_base2
Out[118]:
0 2
1 2
2 2
dtype: category
Categories (1, int64): [2]
与具有相同类别和排序的范畴词或标量词作品相比:
In [119]: cat > cat_base
Out[119]:
0 True
1 False
2 False
dtype: bool
In [120]: cat > 2
Out[120]:
0 True
1 False
2 False
dtype: bool
相等比较适用于长度和标量相同的任何类似列表的对象:
In [121]: cat == cat_base
Out[121]:
0 False
1 True
2 False
dtype: bool
In [122]: cat == np.array([1, 2, 3])
Out[122]:
0 True
1 True
2 True
dtype: bool
In [123]: cat == 2
Out[123]:
0 False
1 True
2 False
dtype: bool
这不起作用,因为类别不同:
In [124]: try:
.....: cat > cat_base2
.....: except TypeError as e:
.....: print("TypeError:", str(e))
.....:
TypeError: Categoricals can only be compared if 'categories' are the same.
如果要将分类序列与非分类数据的列表状对象进行“不相等”比较,则需要明确地将分类数据转换回原始值:
In [125]: base = np.array([1, 2, 3])
In [126]: 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 [127]: np.asarray(cat) > base
Out[127]: array([False, False, False])
当您比较具有相同类别的两个无序范畴时,不考虑顺序:
In [128]: c1 = pd.Categorical(["a", "b"], categories=["a", "b"], ordered=False)
In [129]: c2 = pd.Categorical(["a", "b"], categories=["b", "a"], ordered=False)
In [130]: c1 == c2
Out[130]: array([ True, True])
运营#
除了 Series.min() , Series.max() 和 Series.mode() ,可以对分类数据执行以下操作:
Series 方法,如 Series.value_counts() 将使用所有类别,即使某些类别不在数据中:
In [131]: s = pd.Series(pd.Categorical(["a", "b", "c", "c"], categories=["c", "a", "b", "d"]))
In [132]: s.value_counts()
Out[132]:
c 2
a 1
b 1
d 0
dtype: int64
DataFrame 方法,如 DataFrame.sum() 还会显示“未使用”类别。
In [133]: columns = pd.Categorical(
.....: ["One", "One", "Two"], categories=["One", "Two", "Three"], ordered=True
.....: )
.....:
In [134]: df = pd.DataFrame(
.....: data=[[1, 2, 3], [4, 5, 6]],
.....: columns=pd.MultiIndex.from_arrays([["A", "B", "B"], columns]),
.....: )
.....:
In [135]: df.groupby(axis=1, level=1).sum()
Out[135]:
One Two Three
0 3 3 0
1 9 6 0
Groupby还将显示“未使用”类别:
In [136]: cats = pd.Categorical(
.....: ["a", "b", "b", "b", "c", "c", "c"], categories=["a", "b", "c", "d"]
.....: )
.....:
In [137]: df = pd.DataFrame({"cats": cats, "values": [1, 2, 2, 2, 3, 4, 5]})
In [138]: df.groupby("cats").mean()
Out[138]:
values
cats
a 1.0
b 2.0
c 4.0
d NaN
In [139]: cats2 = pd.Categorical(["a", "a", "b", "b"], categories=["a", "b", "c"])
In [140]: df2 = pd.DataFrame(
.....: {
.....: "cats": cats2,
.....: "B": ["c", "d", "c", "d"],
.....: "values": [1, 2, 3, 4],
.....: }
.....: )
.....:
In [141]: df2.groupby(["cats", "B"]).mean()
Out[141]:
values
cats B
a c 1.0
d 2.0
b c 3.0
d 4.0
c c NaN
d NaN
透视表:
In [142]: raw_cat = pd.Categorical(["a", "a", "b", "b"], categories=["a", "b", "c"])
In [143]: df = pd.DataFrame({"A": raw_cat, "B": ["c", "d", "c", "d"], "values": [1, 2, 3, 4]})
In [144]: pd.pivot_table(df, values="values", index=["A", "B"])
Out[144]:
values
A B
a c 1
d 2
b c 3
d 4
数据吞吐#
优化的Pandas数据访问方法 .loc , .iloc , .at ,以及 .iat ,照常工作。唯一的区别是返回类型(用于获取),并且只有已存在的值 categories 可以被分配。
vbl.得到,得到#
如果切片操作返回 DataFrame 或以下类型的列 Series ,即 category 数据类型被保留。
In [145]: idx = pd.Index(["h", "i", "j", "k", "l", "m", "n"])
In [146]: cats = pd.Series(["a", "b", "b", "b", "c", "c", "c"], dtype="category", index=idx)
In [147]: values = [1, 2, 2, 2, 3, 4, 5]
In [148]: df = pd.DataFrame({"cats": cats, "values": values}, index=idx)
In [149]: df.iloc[2:4, :]
Out[149]:
cats values
j b 2
k b 2
In [150]: df.iloc[2:4, :].dtypes
Out[150]:
cats category
values int64
dtype: object
In [151]: df.loc["h":"j", "cats"]
Out[151]:
h a
i b
j b
Name: cats, dtype: category
Categories (3, object): ['a', 'b', 'c']
In [152]: df[df["cats"] == "b"]
Out[152]:
cats values
i b 2
j b 2
k b 2
不保留类别类型的一个示例是,如果您只取一行: Series 是数据类型 object :
# get the complete "h" row as a Series
In [153]: df.loc["h", :]
Out[153]:
cats a
values 1
Name: h, dtype: object
从分类数据返回单个条目也将返回值,而不是长度为“1”的分类数据。
In [154]: df.iat[0, 0]
Out[154]: 'a'
In [155]: df["cats"].cat.categories = ["x", "y", "z"]
In [156]: df.at["h", "cats"] # returns a string
Out[156]: 'x'
备注
这是与R形成对比的 factor 函数,其中 factor(c(1,2,3))[1] 返回单个值 factor 。
获取单一值的步骤 Series 类型的 category ,则传入一个只有一个值的列表:
In [157]: df.loc[["h"], "cats"]
Out[157]:
h x
Name: cats, dtype: category
Categories (3, object): ['x', 'y', 'z']
字符串和日期时间访问器#
访问者 .dt 和 .str 将在以下情况下工作 s.cat.categories 属于适当的类型:
In [158]: str_s = pd.Series(list("aabb"))
In [159]: str_cat = str_s.astype("category")
In [160]: str_cat
Out[160]:
0 a
1 a
2 b
3 b
dtype: category
Categories (2, object): ['a', 'b']
In [161]: str_cat.str.contains("a")
Out[161]:
0 True
1 True
2 False
3 False
dtype: bool
In [162]: date_s = pd.Series(pd.date_range("1/1/2015", periods=5))
In [163]: date_cat = date_s.astype("category")
In [164]: date_cat
Out[164]:
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 [165]: date_cat.dt.day
Out[165]:
0 1
1 2
2 3
3 4
4 5
dtype: int64
备注
归来的人 Series (或 DataFrame )的类型与您使用 .str.<method> / .dt.<method> 在一个 Series 属于该类型(并且不属于该类型 category !)。
这意味着,从方法和属性返回的值 Series 的方法和属性的返回值。 Series 转变为一种类型 category 将相等:
In [166]: ret_s = str_s.str.contains("a")
In [167]: ret_cat = str_cat.str.contains("a")
In [168]: ret_s.dtype == ret_cat.dtype
Out[168]: True
In [169]: ret_s == ret_cat
Out[169]:
0 True
1 True
2 True
3 True
dtype: bool
备注
这项工作是在 categories 然后是一个新的 Series 是被建造的。如果您有一个 Series 类型为字符串,其中大量元素重复(即 Series 的长度远远小于 Series )。在这种情况下,可以更快地转换原始 Series 到一种类型 category 并使用 .str.<method> 或 .dt.<property> 就在那上面。
设置#
设置分类列中的值(或 Series )有效,只要值包含在 categories :
In [170]: idx = pd.Index(["h", "i", "j", "k", "l", "m", "n"])
In [171]: cats = pd.Categorical(["a", "a", "a", "a", "a", "a", "a"], categories=["a", "b"])
In [172]: values = [1, 1, 1, 1, 1, 1, 1]
In [173]: df = pd.DataFrame({"cats": cats, "values": values}, index=idx)
In [174]: df.iloc[2:4, :] = [["b", 2], ["b", 2]]
In [175]: df
Out[175]:
cats values
h a 1
i a 1
j b 2
k b 2
l a 1
m a 1
n a 1
In [176]: 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 [177]: df.loc["j":"k", "cats"] = pd.Categorical(["a", "a"], categories=["a", "b"])
In [178]: df
Out[178]:
cats values
h a 1
i a 1
j a 2
k a 2
l a 1
m a 1
n a 1
In [179]: 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 [180]: df = pd.DataFrame({"a": [1, 1, 1, 1, 1], "b": ["a", "a", "a", "a", "a"]})
In [181]: df.loc[1:2, "a"] = pd.Categorical(["b", "b"], categories=["a", "b"])
In [182]: df.loc[2:3, "b"] = pd.Categorical(["b", "b"], categories=["a", "b"])
In [183]: df
Out[183]:
a b
0 1 a
1 b a
2 b b
3 1 b
4 1 a
In [184]: df.dtypes
Out[184]:
a object
b object
dtype: object
合并/串联#
默认情况下,组合 Series 或 DataFrames 它们包含相同的类别,导致 category 数据类型,否则结果将取决于基础类别的数据类型。导致非绝对数据类型的合并可能具有更高的内存使用率。使用 .astype 或 union_categoricals 确保 category 结果。
In [185]: from pandas.api.types import union_categoricals
# same categories
In [186]: s1 = pd.Series(["a", "b"], dtype="category")
In [187]: s2 = pd.Series(["a", "b", "a"], dtype="category")
In [188]: pd.concat([s1, s2])
Out[188]:
0 a
1 b
0 a
1 b
2 a
dtype: category
Categories (2, object): ['a', 'b']
# different categories
In [189]: s3 = pd.Series(["b", "c"], dtype="category")
In [190]: pd.concat([s1, s3])
Out[190]:
0 a
1 b
0 b
1 c
dtype: object
# Output dtype is inferred based on categories values
In [191]: int_cats = pd.Series([1, 2], dtype="category")
In [192]: float_cats = pd.Series([3.0, 4.0], dtype="category")
In [193]: pd.concat([int_cats, float_cats])
Out[193]:
0 1.0
1 2.0
0 3.0
1 4.0
dtype: float64
In [194]: pd.concat([s1, s3]).astype("category")
Out[194]:
0 a
1 b
0 b
1 c
dtype: category
Categories (3, object): ['a', 'b', 'c']
In [195]: union_categoricals([s1.array, s3.array])
Out[195]:
['a', 'b', 'b', 'c']
Categories (3, object): ['a', 'b', 'c']
下表总结了合并的结果 Categoricals :
Arg1 |
Arg2 |
完全相同 |
结果 |
|---|---|---|---|
范畴 |
范畴 |
真的 |
范畴 |
类别(对象) |
类别(对象) |
错误 |
对象(推断出数据类型) |
类别(INT) |
类别(浮动) |
错误 |
浮点型(推断出数据类型) |
另请参阅 merge dtypes 有关保留合并数据类型和性能的说明。
统一#
如果要组合不一定具有相同类别的范畴,请使用 union_categoricals() 函数将组合一个类似于类别词的列表。新的类别将是正在合并的类别的联合。
In [196]: from pandas.api.types import union_categoricals
In [197]: a = pd.Categorical(["b", "c"])
In [198]: b = pd.Categorical(["a", "b"])
In [199]: union_categoricals([a, b])
Out[199]:
['b', 'c', 'a', 'b']
Categories (3, object): ['b', 'c', 'a']
默认情况下,生成的类别将按照它们在数据中出现的顺序进行排序。如果希望对类别进行词法排序,请使用 sort_categories=True 论点。
In [200]: union_categoricals([a, b], sort_categories=True)
Out[200]:
['b', 'c', 'a', 'b']
Categories (3, object): ['a', 'b', 'c']
union_categoricals 也适用于将相同类别的两个类别和订单信息(例如,您还可以 append 用于)。
In [201]: a = pd.Categorical(["a", "b"], ordered=True)
In [202]: b = pd.Categorical(["a", "b", "a"], ordered=True)
In [203]: union_categoricals([a, b])
Out[203]:
['a', 'b', 'a', 'b', 'a']
Categories (2, object): ['a' < 'b']
以下是提高 TypeError 因为这些类别是有序的,不是完全相同的。
In [1]: a = pd.Categorical(["a", "b"], ordered=True)
In [2]: b = pd.Categorical(["a", "b", "c"], ordered=True)
In [3]: union_categoricals([a, b])
Out[3]:
TypeError: to union ordered Categoricals, all categories must be the same
具有不同类别或排序的已排序类别可以使用 ignore_ordered=True 论点。
In [204]: a = pd.Categorical(["a", "b", "c"], ordered=True)
In [205]: b = pd.Categorical(["c", "b", "a"], ordered=True)
In [206]: union_categoricals([a, b], ignore_order=True)
Out[206]:
['a', 'b', 'c', 'c', 'b', 'a']
Categories (3, object): ['a', 'b', 'c']
union_categoricals() 还可以与 CategoricalIndex ,或 Series 包含分类数据,但请注意,结果数组将始终是普通数组 Categorical :
In [207]: a = pd.Series(["b", "c"], dtype="category")
In [208]: b = pd.Series(["a", "b"], dtype="category")
In [209]: union_categoricals([a, b])
Out[209]:
['b', 'c', 'a', 'b']
Categories (3, object): ['b', 'c', 'a']
备注
union_categoricals 可能会在组合类别时重新编码类别的整数代码。这很可能是您想要的,但如果您依赖于类别的准确编号,请注意。
In [210]: c1 = pd.Categorical(["b", "c"])
In [211]: c2 = pd.Categorical(["a", "b"])
In [212]: c1
Out[212]:
['b', 'c']
Categories (2, object): ['b', 'c']
# "b" is coded to 0
In [213]: c1.codes
Out[213]: array([0, 1], dtype=int8)
In [214]: c2
Out[214]:
['a', 'b']
Categories (2, object): ['a', 'b']
# "b" is coded to 1
In [215]: c2.codes
Out[215]: array([0, 1], dtype=int8)
In [216]: c = union_categoricals([c1, c2])
In [217]: c
Out[217]:
['b', 'c', 'a', 'b']
Categories (3, object): ['b', 'c', 'a']
# "b" is coded to 0 throughout, same as c1, different from c2
In [218]: c.codes
Out[218]: array([0, 1, 2, 0], dtype=int8)
获取数据传入/传出#
您可以写入包含以下内容的数据 category 将数据类型转换为 HDFStore 。看见 here 作为一个例子和注意事项。
还可以向其中写入数据或从中读取数据 斯塔塔 格式化文件。看见 here 作为一个例子和注意事项。
写入CSV文件将转换数据,有效地删除有关类别(类别和顺序)的任何信息。因此,如果您回读CSV文件,则必须将相关列转换回 category 并分配正确的类别和类别排序。
In [219]: import io
In [220]: s = pd.Series(pd.Categorical(["a", "b", "b", "a", "a", "d"]))
# rename the categories
In [221]: s.cat.categories = ["very good", "good", "bad"]
# reorder the categories and add missing categories
In [222]: s = s.cat.set_categories(["very bad", "bad", "medium", "good", "very good"])
In [223]: df = pd.DataFrame({"cats": s, "vals": [1, 2, 3, 4, 5, 6]})
In [224]: csv = io.StringIO()
In [225]: df.to_csv(csv)
In [226]: df2 = pd.read_csv(io.StringIO(csv.getvalue()))
In [227]: df2.dtypes
Out[227]:
Unnamed: 0 int64
cats object
vals int64
dtype: object
In [228]: df2["cats"]
Out[228]:
0 very good
1 good
2 good
3 very good
4 very good
5 bad
Name: cats, dtype: object
# Redo the category
In [229]: df2["cats"] = df2["cats"].astype("category")
In [230]: df2["cats"].cat.set_categories(
.....: ["very bad", "bad", "medium", "good", "very good"], inplace=True
.....: )
.....:
In [231]: df2.dtypes
Out[231]:
Unnamed: 0 int64
cats category
vals int64
dtype: object
In [232]: df2["cats"]
Out[232]:
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 。
缺少数据#
Pandas主要使用的是价值 np.nan 来表示丢失的数据。默认情况下,它不包括在计算中。请参阅 Missing Data section 。
缺少的值应为 not 被包括在定语的 categories ,仅限于 values 。相反,人们理解NaN是不同的,并且永远是一种可能性。当使用分类的 codes ,则缺失值的代码将始终为 -1 。
In [233]: s = pd.Series(["a", "b", np.nan, "a"], dtype="category")
# only two categories
In [234]: s
Out[234]:
0 a
1 b
2 NaN
3 a
dtype: category
Categories (2, object): ['a', 'b']
In [235]: s.cat.codes
Out[235]:
0 0
1 1
2 -1
3 0
dtype: int8
处理丢失数据的方法,例如 isna() , fillna() , dropna() ,所有工作正常:
In [236]: s = pd.Series(["a", "b", np.nan], dtype="category")
In [237]: s
Out[237]:
0 a
1 b
2 NaN
dtype: category
Categories (2, object): ['a', 'b']
In [238]: pd.isna(s)
Out[238]:
0 False
1 False
2 True
dtype: bool
In [239]: s.fillna("a")
Out[239]:
0 a
1 b
2 a
dtype: category
Categories (2, object): ['a', 'b']
与R的差异 factor#
可以观察到R因子函数的以下不同之处:
R‘s
levels被命名为categories。R‘s
levels始终为字符串类型,而categories在Pandas中可以是任何d型。不能在创建时指定标签。使用
s.cat.rename_categories(new_labels)之后。与R相反
factor函数,使用分类数据作为唯一输入来创建新的分类序列将 not 删除未使用的类别,但创建一个新的类别系列,该系列与传入的类别系列相同!R允许将缺少的值包括在其
levels(Pandas的categories)。Pandas不允许NaN类别,但缺少的值仍可以位于values。
我明白了#
内存使用情况#
的内存使用情况 Categorical 与类别的数量加上数据的长度成正比。相比之下,一个 object 数据类型是数据长度的常量倍。
In [240]: s = pd.Series(["foo", "bar"] * 1000)
# object dtype
In [241]: s.nbytes
Out[241]: 16000
# category dtype
In [242]: s.astype("category").nbytes
Out[242]: 2016
备注
如果类别数接近数据的长度,则 Categorical 将使用几乎相同或更多的内存 object 数据类型表示法。
In [243]: s = pd.Series(["foo%04d" % i for i in range(2000)])
# object dtype
In [244]: s.nbytes
Out[244]: 16000
# category dtype
In [245]: s.astype("category").nbytes
Out[245]: 20000
Categorical 不是一个 numpy 阵列#
目前,分类数据和基础数据 Categorical 被实现为一个Python对象,而不是一个低级的NumPy数组数据类型。这导致了一些问题。
NumPy本身并不知道新的 dtype :
In [246]: try:
.....: np.dtype("category")
.....: except TypeError as e:
.....: print("TypeError:", str(e))
.....:
TypeError: data type 'category' not understood
In [247]: dtype = pd.Categorical(["a"]).dtype
In [248]: try:
.....: np.dtype(dtype)
.....: except TypeError as e:
.....: print("TypeError:", str(e))
.....:
TypeError: Cannot interpret 'CategoricalDtype(categories=['a'], ordered=False)' as a data type
数据类型比较的工作方式:
In [249]: dtype == np.str_
Out[249]: False
In [250]: np.str_ == dtype
Out[250]: False
要检查序列是否包含分类数据,请使用 hasattr(s, 'cat') :
In [251]: hasattr(pd.Series(["a"], dtype="category"), "cat")
Out[251]: True
In [252]: hasattr(pd.Series(["a"]), "cat")
Out[252]: False
在上使用NumPy函数 Series 类型的 category 不应该像 Categoricals 不是数值型数据(即使在 .categories 是数字)。
In [253]: s = pd.Series(pd.Categorical([1, 2, 3, 4]))
In [254]: try:
.....: np.sum(s)
.....: except TypeError as e:
.....: print("TypeError:", str(e))
.....:
TypeError: 'Categorical' with dtype category does not support reduction 'sum'
备注
如果这样的功能有效,请在https://github.com/pandas-dev/pandas!上提交错误
应用中的数据类型#
Pandas目前不在Apply函数中保留数据类型:如果沿行应用,则会得到一个 Series 的 object dtype (与获取行相同->获取一个元素将返回基本类型),沿列应用也将转换为Object。 NaN 值不受影响。您可以使用 fillna 在应用函数之前处理缺少的值。
In [255]: df = pd.DataFrame(
.....: {
.....: "a": [1, 2, 3, 4],
.....: "b": ["a", "b", "c", "d"],
.....: "cats": pd.Categorical([1, 2, 3, 2]),
.....: }
.....: )
.....:
In [256]: df.apply(lambda row: type(row["cats"]), axis=1)
Out[256]:
0 <class 'int'>
1 <class 'int'>
2 <class 'int'>
3 <class 'int'>
dtype: object
In [257]: df.apply(lambda col: col.dtype, axis=0)
Out[257]:
a int64
b object
cats category
dtype: object
分类索引#
CategoricalIndex 是一种索引类型,对于支持具有重复项的索引非常有用。这是一个容器,围绕着一个 Categorical 并且允许高效地索引和存储具有大量重复元素的索引。请参阅 advanced indexing docs 以获得更详细的解释。
设置索引将创建 CategoricalIndex :
In [258]: cats = pd.Categorical([1, 2, 3, 4], categories=[4, 2, 3, 1])
In [259]: strings = ["a", "b", "c", "d"]
In [260]: values = [4, 2, 3, 1]
In [261]: df = pd.DataFrame({"strings": strings, "values": values}, index=cats)
In [262]: df.index
Out[262]: CategoricalIndex([1, 2, 3, 4], categories=[4, 2, 3, 1], ordered=False, dtype='category')
# This now sorts by the categories order
In [263]: df.sort_index()
Out[263]:
strings values
4 d 1
2 b 2
3 c 3
1 a 4
副作用#
构建一个 Series 从一个 Categorical 不会复制输入 Categorical 。这意味着对 Series 在大多数情况下会改变原始的 Categorical :
In [264]: cat = pd.Categorical([1, 2, 3, 10], categories=[1, 2, 3, 4, 10])
In [265]: s = pd.Series(cat, name="cat")
In [266]: cat
Out[266]:
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]
In [267]: s.iloc[0:2] = 10
In [268]: cat
Out[268]:
[10, 10, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]
In [269]: df = pd.DataFrame(s)
In [270]: df["cat"].cat.categories = [1, 2, 3, 4, 5]
In [271]: cat
Out[271]:
[5, 5, 3, 5]
Categories (5, int64): [1, 2, 3, 4, 5]
使用 copy=True 为了防止这样的行为,或者干脆不重复使用 Categoricals :
In [272]: cat = pd.Categorical([1, 2, 3, 10], categories=[1, 2, 3, 4, 10])
In [273]: s = pd.Series(cat, name="cat", copy=True)
In [274]: cat
Out[274]:
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]
In [275]: s.iloc[0:2] = 10
In [276]: cat
Out[276]:
[1, 2, 3, 10]
Categories (5, int64): [1, 2, 3, 4, 10]
备注
在某些情况下,当您提供NumPy数组而不是 Categorical :使用整型数组(例如 np.array([1,2,3,4]) )将表现出相同的行为,而使用字符串数组(例如 np.array(["a","b","c","a"]) )不会。