类别数据#

这是 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 创建#

可以通过多种方式创建类别的 SeriesDataFrame 中的列:

在构造 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’ 的例子中,我们使用了默认行为:

  1. 类别是从数据中推断出来的。

  2. 类别是无序的。

要控制这些行为,请不要传递 ‘category’,而是使用 CategoricalDtype 的实例。

类似地,CategoricalDtype 可以与 DataFrame 一起使用,以确保类别在所有列之间保持一致。

备注

要执行表级转换,其中 DataFrame 中所有标签都用作每列的类别,可以通过 categories = pd.unique(df.to_numpy().ravel()) 以编程方式确定 categories 参数。

如果您已经有了 codescategories,可以使用 from_codes() 构造函数来保存正常构造函数模式下的因子化步骤:

恢复原始数据#

要恢复到原始的 Series 或 NumPy 数组,请使用 Series.astype(original_dtype)np.asarray(categorical)

备注

与 R 的 factor 函数相比,分类数据不会将输入值转换为字符串;类别最终将具有与原始值相同的数据类型。

备注

与 R 的 factor 函数相比,目前没有办法在创建时分配/更改标签。使用 categories 在创建后更改类别。

CategoricalDtype#

分类的类型由以下完全描述:

  1. categories:一组唯一值且没有缺失值

  2. 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 类型的 SeriesDataFrame 类似的输出。

使用类别#

分类数据具有 categoriesordered 属性,它们列出了可能的取值以及顺序是否重要。这些属性公开为 s.cat.categoriess.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==Truecategories 相同时。

  • 分类数据与标量之间的所有比较。

所有其他比较,特别是具有不同分类的两个分类之间的“非相等”比较,或者分类与任何列表状对象之间的比较,将引发 TypeError

备注

分类数据与具有不同分类或顺序的 Seriesnp.arraylist 或分类数据之间的任何“非相等”比较将引发 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 中的值。

获取#

如果切片操作返回 DataFrameSeries 类型的列,则 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 分配给其他类型的列的部分时,将使用其中包含的值:

合并/连接#

默认情况下,合并包含相同类别的 SeriesDataFrames 会得到 category 数据类型,否则结果将取决于底层类别的数据类型。合并导致非分类数据类型的操作可能会产生更高的内存使用。使用 .astypeunion_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

处理缺失数据的方法,例如 isna()fillna()dropna() ,都可以正常工作:

与 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 dtypeSeries``(与获取一行相同 -> 获取一个元素将返回基本类型),沿列应用也会转换为 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"]))则不会。