类别数据#
这是 pandas 类别数据的介绍,包括与 R 的 factor 的简短比较。
Categoricals 是 pandas 的一种数据类型,对应于统计学中的类别变量。类别变量取有限且通常固定的可能值(categories;R 中的 levels)。例如性别、社会阶层、血型、国家归属、观察时间或李克特量表评分。
与统计学中的类别变量不同,类别数据可能具有顺序(例如,“非常同意” vs “同意” 或 “第一次观察” vs “第二次观察”),但不能进行数值运算(加法、除法等)。
类别数据的所有值都在 categories 中或为 np.nan。顺序由 categories 的顺序定义,而不是值的字母顺序。在内部,数据结构由一个 categories 数组和一个整数数组 codes 组成,codes 指向 categories 数组中的实际值。
在以下情况下,类别数据类型很有用:
一个只包含少量不同值的字符串变量。将此类字符串变量转换为类别变量将节省内存,参见 here 。
变量的字典顺序与其逻辑顺序(“一”、“二”、“三”)不同。通过转换为类别并指定类别的顺序,排序和 min/max 将使用逻辑顺序而不是字典顺序,参见 here 。
作为信号发送给其他 Python 库,表明此列应被视为类别变量(例如,使用合适的统计方法或绘图类型)。
另请参阅 API docs on categoricals 。
对象创建#
Series 创建#
可以通过多种方式创建类别的 Series 或 DataFrame 中的列:
在构造 Series 时指定 dtype="category":
通过将现有 Series 或列转换为 category dype:
通过使用特殊函数,例如 cut() ,它将数据分组到离散的箱子中。请参阅文档中的 example on tiling 。
通过将 pandas.Categorical 对象传递给 Series 或将其分配给 DataFrame。
类别数据具有特定的 category dtype :
DataFrame 创建#
与上一节将单个列转换为类别数据类似,可以通过在构造期间或之后进行批处理转换,将 DataFrame 中的所有列转换为类别数据。
这可以在构造期间通过在 DataFrame 构造函数中指定 dtype="category" 来完成:
请注意,每列中存在的类别不同;转换是逐列完成的,因此只有给定列中存在的标签才是类别:
同理,可以使用 DataFrame.astype() 批处理转换现有 DataFrame 中的所有列:
此转换同样是逐列进行的:
控制行为#
在上面我们传递 dtype=’category’ 的例子中,我们使用了默认行为:
类别是从数据中推断出来的。
类别是无序的。
要控制这些行为,请不要传递 ‘category’,而是使用 CategoricalDtype 的实例。
类似地,CategoricalDtype 可以与 DataFrame 一起使用,以确保类别在所有列之间保持一致。
备注
要执行表级转换,其中 DataFrame 中所有标签都用作每列的类别,可以通过 categories = pd.unique(df.to_numpy().ravel()) 以编程方式确定 categories 参数。
如果您已经有了 codes 和 categories,可以使用 from_codes() 构造函数来保存正常构造函数模式下的因子化步骤:
恢复原始数据#
要恢复到原始的 Series 或 NumPy 数组,请使用 Series.astype(original_dtype) 或 np.asarray(categorical):
备注
与 R 的 factor 函数相比,分类数据不会将输入值转换为字符串;类别最终将具有与原始值相同的数据类型。
备注
与 R 的 factor 函数相比,目前没有办法在创建时分配/更改标签。使用 categories 在创建后更改类别。
CategoricalDtype#
分类的类型由以下完全描述:
categories:一组唯一值且没有缺失值ordered:一个布尔值
这些信息可以存储在 CategoricalDtype 中。 categories 参数是可选的,这意味着实际的类别应该从创建 pandas.Categorical 时存在于数据中的内容中推断出来。默认情况下,类别假定为无序。
CategoricalDtype 可用于 pandas 期望 dtype 的任何地方。例如 pandas.read_csv() pandas.DataFrame.astype() ,或在 Series 构造函数中。
备注
为方便起见,当您想要类别无序且等于数组中存在的值集合的默认行为时,可以使用字符串 'category' 代替 CategoricalDtype 。换句话说,dtype='category' 等同于 dtype=CategoricalDtype()。
相等语义#
当 CategoricalDtype 的两个实例具有相同的类别和顺序时,它们会相等。比较两个无序的分类时,不考虑 categories 的顺序。
所有 CategoricalDtype 实例都与字符串 'category' 相等。
描述#
对分类数据使用 describe() 会产生与 string 类型的 Series 或 DataFrame 类似的输出。
使用类别#
分类数据具有 categories 和 ordered 属性,它们列出了可能的取值以及顺序是否重要。这些属性公开为 s.cat.categories 和 s.cat.ordered。如果您不手动指定类别和顺序,它们将从传递的参数中推断出来。
也可以按特定顺序传递类别:
备注
新的分类数据**不会**自动排序。您必须显式传递 ordered=True 来指示一个已排序的 Categorical。
备注
unique() 的结果不总是与 Series.cat.categories 相同,因为 Series.unique() 有几个保证,即它按出现顺序返回类别,并且只包含实际出现的值。
重命名类别#
通过使用 rename_categories() 方法可以重命名类别:
备注
与 R 的 factor 相反,分类数据可以具有字符串以外的其他类型的类别。
类别必须是唯一的,否则会引发 ValueError:
类别也不能是 NaN,否则会引发 ValueError:
追加新类别#
可以通过使用 add_categories() 方法追加类别:
移除类别#
可以通过使用 remove_categories() 方法移除类别。被移除的值将被替换为 np.nan。:
移除未使用的分类#
也可以通过以下方式移除未使用的分类:
设置分类#
如果您想一步完成移除和添加新分类(这在速度上有一些优势),或者只是将分类设置为预定义的大小,请使用 set_categories() 。
备注
请注意,Categorical.set_categories() 无法知道某个分类是被有意省略还是因为拼写错误,或者(在 Python3 下)由于类型差异(例如,NumPy S1 dtype 和 Python 字符串)而导致的。这可能会导致意想不到的行为!
排序和顺序#
如果分类数据是有序的(s.cat.ordered == True),那么分类的顺序是有意义的,并且可以进行某些操作。如果分类是无序的,.min()/.max() 将引发 TypeError。
您可以使用 as_ordered() 将分类数据设置为有序,或使用 as_unordered() 将其设置为无序。默认情况下,这些操作将返回一个*新*对象。
排序将使用分类定义的顺序,而不是数据类型存在的任何词典顺序。对于字符串和数字数据也是如此:
重新排序#
可以通过 Categorical.reorder_categories() 和 Categorical.set_categories() 方法重新排序分类。对于 Categorical.reorder_categories() ,所有旧分类必须包含在新分类中,并且不允许添加新分类。这将必然使排序顺序与分类顺序相同。
备注
请注意,分配新分类与重新排序分类的区别:前者重命名分类,因此会改变 Series 中各个值;但如果第一个位置的元素排序到了最后,重命名后的值仍然会排序到最后。重新排序意味着之后值的排序方式不同,但 Series 中各个值本身不会改变。
备注
如果 Categorical 是无序的,Series.min() 和 Series.max() 将引发 TypeError。数字运算如 +、-、*、/ 以及基于它们的运算(例如 Series.median() ,它在数组长度为偶数时需要计算两个值之间的平均值)将不起作用并引发 TypeError。
多列排序#
分类类型的列将以与其他列类似的方式参与多列排序。分类的顺序由该列的 categories 确定。
重新排序 categories 会改变未来的排序。
比较#
在三种情况下,分类数据可以与其他对象进行比较:
将相等性(
==和!=)与长度与分类数据相同的列表状对象(列表、Series、数组等)进行比较。分类数据与其他分类 Series 之间的所有比较(
==、!=、>、>=、<和<=),当ordered==True且categories相同时。分类数据与标量之间的所有比较。
所有其他比较,特别是具有不同分类的两个分类之间的“非相等”比较,或者分类与任何列表状对象之间的比较,将引发 TypeError。
备注
分类数据与具有不同分类或顺序的 Series、np.array、list 或分类数据之间的任何“非相等”比较将引发 TypeError,因为自定义的分类顺序可能被解释为两种方式:一种考虑顺序,一种不考虑。
与具有相同分类和顺序的分类或与标量进行比较是可行的:
与任何长度相同的列表状对象和标量进行相等性比较是可行的:
这行不通,因为分类不相同:
如果您想对分类 Series 与非分类的列表状对象进行“非相等”比较,您需要明确地将分类数据转换回原始值:
当比较两个具有相同分类但无序的分类时,不考虑顺序:
操作#
除了 Series.min() 、Series.max() 和 Series.mode() 之外,分类数据还可以进行以下操作:
Series 方法(如 Series.value_counts() )将使用所有分类,即使某些分类不在数据中:
DataFrame 方法(例如 DataFrame.sum() )在 observed=False 时也会显示“未使用的”类别。
Groupby 在 observed=False 时也会显示“未使用的”类别:
透视表:
数据整理#
经过优化的 pandas 数据访问方法 .loc, .iloc, .at 和 .iat 正常工作。唯一的区别是返回值(用于获取)以及只能分配已存在于 categories 中的值。
获取#
如果切片操作返回 DataFrame 或 Series 类型的列,则 category 数据类型会被保留。
一个类别类型未被保留的例子是,如果你只取一行:resulting Series 的数据类型为 object:
从分类数据中返回单个项目也会返回该值,而不是一个长度为“1”的分类。
备注
这与 R 的 factor 函数相反,在 R 中,factor(c(1,2,3))[1] 返回单个值 factor。
要获取类型为 category 的单个值 Series,请传入一个包含单个值的列表:
字符串和日期时间访问器#
当 s.cat.categories 是适当类型时,访问器 .dt 和 .str 将会工作:
备注
返回的 Series``(或 ``DataFrame)与你将 .str.<method> / .dt.<method> 应用于该类型(而不是 category 类型!)的 Series 时的类型相同。
这意味着,来自一个 Series 的访问器的属性和方法的返回值,以及将此 Series 转换为 category 类型后的访问器的属性和方法的返回值将是相等的:
备注
工作是在 categories 上完成的,然后构造一个新的 Series。如果你的 Series 是字符串类型,并且有很多重复的元素(即 Series 中唯一元素的数量远小于 Series 的长度),这会产生一些性能影响。在这种情况下,将原始 Series 转换为 category 类型,并对其使用 .str.<method> 或 .dt.<property> 可能会更快。
设置#
在分类列(或 Series)中设置值,只要该值包含在 categories 中即可工作:
通过分配分类数据设置值时,也会检查 categories 是否匹配:
将一个 Categorical 分配给其他类型的列的部分时,将使用其中包含的值:
合并/连接#
默认情况下,合并包含相同类别的 Series 或 DataFrames 会得到 category 数据类型,否则结果将取决于底层类别的数据类型。合并导致非分类数据类型的操作可能会产生更高的内存使用。使用 .astype 或 union_categoricals 来确保得到 category 结果。
下表总结了合并 Categoricals 的结果:
arg1 |
arg2 |
identical |
result |
|---|---|---|---|
category |
category |
True |
category |
category (object) |
category (object) |
False |
object (dtype is inferred) |
category (int) |
category (float) |
False |
float (dtype is inferred) |
联合#
如果你想合并不一定具有相同类别的分类数据, union_categoricals() 函数将合并一个类列表的分类数据。新的类别将是正在合并的类别的并集。
默认情况下,生成的类别将按照它们在数据中出现的顺序排列。如果你希望类别按字典顺序排序,请使用 sort_categories=True 参数。
union_categoricals 对于合并具有相同类别和顺序信息的两个分类的“简单”情况(例如,你也可以 append 的情况)也有效。
下面的代码会引发 TypeError,因为类别是有序的并且不相同。
具有不同类别或顺序的有序分类可以通过使用 ignore_ordered=True 参数来合并。
union_categoricals() 也适用于 CategoricalIndex 或包含分类数据的 Series,但请注意,结果数组始终是普通的 Categorical:
备注
union_categoricals 在组合分类数据时可能会重新编码类别的整数代码。这很可能就是你想要的,但如果你依赖于类别的确切编号,请注意。
输入/输出数据#
您可以将包含 category 数据类型的 dtypes 的数据写入 HDFStore。有关示例和注意事项,请参阅 here 。
还可以将数据写入和读取 Stata 格式的文件。有关示例和注意事项,请参阅 here 。
写入 CSV 文件会将数据进行转换,有效删除有关分类(类别和顺序)的任何信息。因此,如果您重新读取 CSV 文件,则必须将相关列转换回 category 并分配正确的类别和类别顺序。
将数据写入 SQL 数据库时,使用 to_sql 也会出现同样的情况。
缺失数据#
pandas 主要使用 np.nan 值来表示缺失数据。默认情况下,它不包含在计算中。请参阅 Missing Data section 。
缺失值**不**应包含在 Categorical 的 categories 中,而应仅包含在 values 中。相反,应该理解 NaN 是不同的,并且始终是一种可能性。在处理 Categorical 的 codes 时,缺失值将始终具有代码 -1。
与 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 是数据长度的常数倍。
备注
如果类别数接近数据长度,Categorical 将比等效的 object dtype 表示使用几乎相同或更多的内存。
Categorical 不是 numpy 数组#
目前,分类数据和底层 Categorical 是作为 Python 对象实现的,而不是作为低级 NumPy 数组 dtype 实现的。这会导致一些问题。
NumPy 本身不知道新的 dtype:
Dtype 比较有效:
要检查 Series 是否包含 Categorical 数据,请使用 hasattr(s, 'cat'):
在 category 类型的 Series 上使用 NumPy 函数不应奏效,因为 Categoricals 不是数值数据(即使 .categories 是数值型的)。
备注
如果此类函数有效,请在 pandas-dev/pandas 提交 bug!
apply 中的 dtype#
pandas 目前在 apply 函数中不保留 dtype:如果您沿行应用,您将得到一个 object dtype 的 Series``(与获取一行相同 -> 获取一个元素将返回基本类型),沿列应用也会转换为 object。``NaN 值不受影响。您可以在应用函数之前使用 fillna 来处理缺失值。
Categorical 索引#
CategoricalIndex 是一种索引类型,有助于支持带有重复项的索引。它是一个围绕 Categorical 的容器,可以对带有大量重复元素的索引进行高效的索引和存储。有关更详细的说明,请参阅 advanced indexing docs 。
设置索引将创建一个 CategoricalIndex:
副作用#
从 Categorical 构建 Series 不会复制输入的 Categorical。这意味着在大多数情况下,对 Series 的更改将更改原始 Categorical:
使用 copy=True 来防止此类行为,或者干脆不要重用 Categoricals:
备注
当您提供 NumPy 数组而不是 Categorical 时,也会在某些情况下发生这种情况:使用整数数组(例如 np.array([1,2,3,4]))将表现出相同的行为,而使用字符串数组(例如 np.array(["a","b","c","a"]))则不会。