Python高级技术之:`Pandas`的`Categorical`类型:如何节省内存和提升处理效率。

各位观众,晚上好!我是你们的老朋友,数据魔法师,今晚咱们聊聊Pandas里的一个“省钱小能手”和“效率加速器”—— Categorical 类型。

开场白:数据瘦身术与性能提速包

想象一下,你有一张巨大的表格,里面塞满了各种各样的信息,比如客户的性别、所在城市、购买的产品类型等等。这些列的数据类型可能五花八门,有字符串、数字等等。但是仔细观察,你会发现其中一些列,比如“性别”,只有“男”和“女”两种取值;“城市”也只有有限的几个选择。

如果直接用字符串或者数字来存储这些列,那简直就是浪费资源!就像你明明只需要带两件衣服出门,却硬要拖一个装满杂物的行李箱。

Categorical 类型就是来帮你解决这个问题的。它可以把这些重复出现的字符串或者数字,用更节省空间的方式存储起来,并且在进行数据分析的时候,还能大幅提升处理速度。

第一幕:Categorical类型的“真面目”

Categorical 类型本质上是一种用数字编码来表示类别数据的类型。它由两部分组成:

  1. categories: 类别本身,也就是列中所有不同的值,可以想象成一个“词汇表”。
  2. codes: 每个值对应的类别在“词汇表”中的索引,也就是用数字来代表每个类别。

让我们来看一个简单的例子:

import pandas as pd

data = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
series = pd.Series(data)

print("原始Series:")
print(series)
print(f"n原始Series的内存占用:{series.memory_usage(deep=True)} 字节") # deep=True 计算字符串的真实内存占用

categorical_series = series.astype('category')

print("nCategorical Series:")
print(categorical_series)
print(f"nCategorical Series的内存占用:{categorical_series.memory_usage(deep=True)} 字节")

print("nCategorical Series的类别 (categories):")
print(categorical_series.cat.categories)

print("nCategorical Series的编码 (codes):")
print(categorical_series.cat.codes)

运行这段代码,你会看到以下结果(大致):

原始Series:
0     apple
1    banana
2     apple
3    orange
4    banana
5     apple
dtype: object

原始Series的内存占用:446 字节

Categorical Series:
0     apple
1    banana
2     apple
3    orange
4    banana
5     apple
dtype: category
Categories (3, object): ['apple', 'banana', 'orange']

Categorical Series的类别 (categories):
Index(['apple', 'banana', 'orange'], dtype='object')

Categorical Series的编码 (codes):
0    0
1    1
2    0
3    2
4    1
5    0
dtype: int8

从上面的例子可以看出:

  • 原始的 Series 存储的是字符串,内存占用比较大。
  • 转换成 Categorical 类型后,内存占用显著降低。因为只需要存储类别('apple', 'banana', 'orange')和每个值对应的索引(0, 1, 2)。
  • categories 属性包含了所有不同的类别。
  • codes 属性包含了每个值对应的类别索引,这些索引通常是整数类型,占用空间比字符串小得多。

第二幕:Categorical类型的“省钱秘籍”

Categorical 类型之所以能节省内存,主要是因为它使用了两种策略:

  1. 共享存储: 相同的字符串或者数字,只存储一次。 例如,’apple’这个字符串,在原始Series中出现了3次,会被存储3次。而Categorical类型,只会存储一次’apple’,然后用数字0来代表所有的’apple’。
  2. 更小的数据类型: codes 属性可以使用更小的数据类型来存储索引,例如 int8int16 等。 默认情况下,Pandas会选择足够容纳所有类别的最小整数类型。

让我们通过一个更大的例子来更清晰地展示 Categorical 类型的省钱能力:

import pandas as pd
import numpy as np

# 创建一个包含大量重复字符串的数据
num_rows = 100000
data = np.random.choice(['apple', 'banana', 'orange', 'grape', 'watermelon'], size=num_rows)
series = pd.Series(data)

print("原始Series的内存占用:", series.memory_usage(deep=True), "字节")

# 转换为 Categorical 类型
categorical_series = series.astype('category')

print("Categorical Series的内存占用:", categorical_series.memory_usage(deep=True), "字节")

运行结果(大致):

原始Series的内存占用: 6400160 字节
Categorical Series的内存占用: 500128 字节

可以看到,内存占用大幅度降低,效果非常明显。

第三幕:Categorical类型的“提速魔法”

除了节省内存,Categorical 类型还能提升数据处理速度。这是因为:

  1. 向量化操作: 很多 Pandas 的操作都针对 Categorical 类型进行了优化,可以利用向量化操作来加速计算。
  2. 减少比较次数: 在进行分组、排序等操作时,只需要比较整数类型的 codes,而不是比较字符串,大大减少了比较次数。

让我们看一个简单的例子:

import pandas as pd
import numpy as np
import time

# 创建一个包含大量重复字符串的数据
num_rows = 100000
data = np.random.choice(['apple', 'banana', 'orange', 'grape', 'watermelon'], size=num_rows)
series = pd.Series(data)
categorical_series = series.astype('category')

# 统计每个类别的数量(字符串类型)
start_time = time.time()
string_counts = series.value_counts()
string_time = time.time() - start_time

# 统计每个类别的数量(Categorical类型)
start_time = time.time()
categorical_counts = categorical_series.value_counts()
categorical_time = time.time() - start_time

print("字符串类型统计耗时:", string_time, "秒")
print("Categorical类型统计耗时:", categorical_time, "秒")

运行结果(大致):

字符串类型统计耗时: 0.00798177719116211 秒
Categorical类型统计耗时: 0.0019943714141845703 秒

可以看到,使用 Categorical 类型进行统计的速度明显快于字符串类型。

第四幕:Categorical类型的“高级用法”

Categorical 类型还有一些高级用法,可以让你更好地控制数据的存储和处理:

  1. 指定类别: 你可以手动指定 Categorical 类型的类别,即使某些类别在数据中没有出现。 这在处理缺失数据或者保证数据的一致性时非常有用。

    import pandas as pd
    
    data = ['apple', 'banana', 'apple', 'orange']
    categories = ['apple', 'banana', 'orange', 'grape'] # 指定类别,即使 grape 没有出现
    
    categorical_series = pd.Categorical(data, categories=categories)
    print(categorical_series)

    输出:

    ['apple', 'banana', 'apple', 'orange']
    Categories (4, object): ['apple', 'banana', 'orange', 'grape']

    注意,这里虽然数据中没有’grape’,但是’grape’仍然被包含在categories中。

  2. 有序类别: 你可以指定 Categorical 类型是有序的,这样可以进行大小比较。 例如,你可以把学历(小学、初中、高中、大学)定义为有序类别。

    import pandas as pd
    
    data = ['小学', '初中', '高中', '大学', '初中', '小学']
    categories = ['小学', '初中', '高中', '大学']
    categorical_series = pd.Categorical(data, categories=categories, ordered=True)
    
    print(categorical_series)
    print(categorical_series > '初中') # 可以进行大小比较

    输出:

    ['小学', '初中', '高中', '大学', '初中', '小学']
    Categories (4, object): ['小学' < '初中' < '高中' < '大学']
    [False False  True  True False False]
  3. 重命名类别: 你可以修改 Categorical 类型的类别名称。

    import pandas as pd
    
    data = ['apple', 'banana', 'orange']
    categorical_series = pd.Series(data).astype('category')
    
    # 重命名类别
    categorical_series = categorical_series.cat.rename_categories({'apple': '苹果', 'banana': '香蕉', 'orange': '橙子'})
    
    print(categorical_series)

    输出:

    0     苹果
    1     香蕉
    2     橙子
    dtype: category
    Categories (3, object): ['苹果', '香蕉', '橙子']

第五幕:Categorical类型的“注意事项”

在使用 Categorical 类型时,需要注意以下几点:

  1. 内存占用并非总是减少: 如果类别数量非常多,甚至接近于数据的总数量,那么使用 Categorical 类型可能不会减少内存占用,甚至会增加。 因为存储 categoriescodes 本身也需要空间。
  2. 数据类型转换:Categorical 类型转换回原始数据类型可能会比较慢。
  3. 兼容性: 某些 Pandas 的操作可能对 Categorical 类型的支持不够完善,需要进行额外的处理。
  4. 理解 ordered 参数: ordered=True 并不意味着 Pandas 会自动对类别进行排序。 它只是告诉 Pandas 这些类别之间存在一个顺序关系,可以进行大小比较。 真正的排序需要使用其他的排序方法。

第六幕:Categorical类型的“应用场景”

Categorical 类型在以下场景中特别有用:

  1. 处理大型数据集: 当数据集非常大,内存成为瓶颈时,可以使用 Categorical 类型来减少内存占用。
  2. 处理重复字符串数据: 当数据中包含大量重复的字符串或者数字时,可以使用 Categorical 类型来提升处理速度。
  3. 数据清洗和转换: 可以使用 Categorical 类型来统一数据的格式,例如将不同的字符串表示转换为统一的类别。
  4. 机器学习: 某些机器学习算法可以直接处理 Categorical 类型的数据,或者可以利用 Categorical 类型进行特征工程。

总结:Categorical类型,数据处理的瑞士军刀

Categorical 类型是 Pandas 中一个非常强大的工具,它可以帮助你节省内存、提升处理速度,并且提供了一些高级的数据处理功能。 掌握 Categorical 类型,就像拥有了一把数据处理的瑞士军刀,可以让你在面对各种数据挑战时更加得心应手。

希望今天的讲座能让你对 Categorical 类型有更深入的了解。 记住,数据处理不仅仅是写代码,更重要的是理解数据的本质,选择合适的数据类型和工具。 下次再遇到需要处理大量类别数据的情况,不妨试试 Categorical 类型,相信它会给你带来惊喜。

感谢大家的观看,我们下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注