首页
学习
活动
专区
圈层
工具
发布
50 篇文章
1
【原创佳作】介绍Pandas实战中一些高端玩法
2
pandas 如何实现 excel 中的汇总行?
3
pandas多级索引的骚操作!
4
40000字 Matplotlib 实操干货,真的全!
5
利用Python搞定女朋友的小情绪~
6
Python 绘制惊艳的瀑布图
7
6种方式创建多层索引
8
Python 进阶指南(编程轻松进阶):三、使用 Black 工具来格式化代码
9
数据科学 IPython 笔记本 9.6 聚合:最小、最大和之间的任何东西
10
精通 Pandas 探索性分析:1~4 全
11
高手系列!数据科学家私藏pandas高阶用法大全 ⛵
12
总结了67个pandas函数,完美解决数据处理,拿来即用!
13
PyAutoGUI,一个Python办公自动化利器!
14
解放双手|利用 PyAutoGUI 快速构建自动化操作脚本
15
Python中内置数据库!SQLite使用指南! ⛵
16
数据分析索引总结(中)Pandas多级索引
17
数据分析索引总结(下)Pandas索引技巧
18
数据分析索引总结(上)Pandas单级索引
19
网友需求系列01-Python-matplotlib定制化刻度(主副)绘制
20
用Python自动生成数据分析报告
21
手把手教你用Python操纵Word自动编写离职报告
22
pandas transform 数据转换的 4 个常用技巧!
23
30段极简Python代码:这些小技巧你都Get了么
24
数据处理遇到麻烦不要慌,5个优雅的Numpy函数助你走出困境
25
数据分析最有用的Top 50 Matplotlib图(带有完整的Python代码)(上)
26
数据分析最有用的Top 50 Matplotlib图(带有完整的Python代码)(下)
27
数据分析之Pandas变形操作总结
28
数据分析之Pandas缺失数据处理
29
数据分析之Pandas合并操作总结
30
数据分析之Pandas分组操作总结
31
学习用Pandas处理分类数据!
32
如何用Pandas处理文本数据?
33
Pandas处理时序数据(初学者必会)!
34
Python高阶函数使用总结!
35
机器学习在金融风控的经验总结!
36
你知道怎么用Pandas绘制带交互的可视化图表吗?
37
6个提升效率的pandas小技巧
38
Python数据分析库pandas高级接口dt和str的使用
39
pandas 拼接 concat 5 个常用技巧!
40
pandas分组8个常用技巧!
41
pandas 文本处理大全
42
pandas 筛选数据的 8 个骚操作
43
pandas 分类数据处理大全(附代码)
44
68 个Python内置函数,你用过几个?
45
太秀了!用 pandas 搞定 24 张 Excel 报表
46
用 Python 的 Template 类生成文件报告
47
码如其人,同学你能写一手漂亮的Python函数吗
48
Python处理图片九宫格,炫酷朋友圈
49
Python排序傻傻分不清?一文看透sorted与sort用法
50
python-docx操作word文件(
清单首页python文章详情

pandas 分类数据处理大全(附代码)

大家好,我是东哥。

继续更新pandas数据清洗,历史文章:

感兴趣可以关注这个话题pandas数据清洗,第一时间看到更新。

所有数据和代码可在我的GitHub获取:

https://github.com/xiaoyusmd/PythonDataScience


categorypandas的一种分类的定类数据类型。和文本数据.str.<methond>一样,它也有访问器功能.cat.<method>

本文将介绍:

  • 什么是分类数据?
  • 分类数据cat的处理方法
  • 为什么要使用分类数据?
  • 分类数据cat使用时的一些坑

什么是分类数据?

分类数据表达数值具有某种属性、类型和特征,也是我们理解的定类数据。比如,人口按性别分为男和女,按年龄分为老、中、少。

在计算机语言里,我们通常会用数字来表示,比如用1代表男,0代表女,但是0和1之间并没有大小关系,pandas中用category来表示分类数据。

创建分类数据

创建数据时可以用dtpye来指定类型,比如:

代码语言:javascript
复制
s = pd.Series(['a','b','c'],dtype='category')
s
------
0    a
1    b
2    c
dtype: category
Categories (3, object): ['a', 'b', 'c']

自动创建分类数据

在某些操作情况下会自动转变为分类类型,比如用cut进行分箱操作返回的分箱就是分类类型。

代码语言:javascript
复制
pd.Series(pd.cut(range(1,10,2),3))
-----------------
0    (0.992, 3.667]
1    (0.992, 3.667]
2    (3.667, 6.333]
3      (6.333, 9.0]
4      (6.333, 9.0]
dtype: category
Categories (3, interval[float64]): [(0.992, 3.667] < (3.667, 6.333] < (6.333, 9.0]]

分类数据类型转换

直接用astype方法转换即可,如:

代码语言:javascript
复制
s = pd.Series(['a','b','c'])
s
------
0    a
1    b
2    c
dtype: object

s.astype('category')
------
0    a
1    b
2    c
dtype: category
Categories (3, object): ['a', 'b', 'c']

自定义分类数据

除此之外,还可以通过CategoricalDtype自定义分类数据,自定义的类型适用于以上全部方法。

比如下面自定义了abc3个分类,并指定了顺序。然后就可以通过dtype指定自定义的数据类型了,d不在定义类型abc中,显示为空。

代码语言:javascript
复制
from pandas.api.types import CategoricalDtype
# 自定义分类数据,有序
c= CategoricalDtype(categories=['a','b','c'],ordered=True)
pd.Series(list('abcabd'),dtype=c)
--------
0      a
1      b
2      c
3      a
4      b
5    NaN
dtype: category
Categories (3, object): ['a' < 'b' < 'c']

分类数据的处理方法

修改分类

通过.cat.rename_categories()修改分类的名称。

代码语言:javascript
复制
s = pd.Series(['a','b','c'],dtype='category')
# 指定分类为x、y、z
s.cat.categories = ['x','y','z']
0    x
1    y
2    z
dtype: category
Categories (3, object): ['x', 'y', 'z']
代码语言:javascript
复制
# 列表形式:修改分类类型为mno
s.cat.rename_categories(['m','n','o'])
# 字典形式:
s.cat.rename_categories({'x':'m','y':'n','z':'o'})
0    m
1    n
2    o
dtype: category
Categories (3, object): ['m', 'n', 'o']

追加新分类

通过.cat.add_categories()追加分类。

代码语言:javascript
复制
s.cat.add_categories(['r','t'])
0    x
1    y
2    z
dtype: category
Categories (5, object): ['x', 'y', 'z', 'r', 't']

删除分类

同理,也可以删除分类。有两种方法remove_categoriesremove_unused_categories

代码语言:javascript
复制
# 删除指定的分类r和t
s.cat.remove_categories(['r','t'])
# 自动删除未使用的分类
s.cat.remove_unused_categories()

顺序

默认情况下分类数据不自动排序,可以通过前面CategoricalDtype设置顺序,或者通过.cat.as_ordered设置。

代码语言:javascript
复制
# 有序设置
s.cat.as_ordered()
0    x
1    y
2    z
dtype: category
Categories (3, object): ['x' < 'y' < 'z']
# 无序设置
s.cat.as_unordered()
# 重新排序
s.cat.reorder_categories(['y','x','z'], ordered=True)

为什么使用category数据类型?

总结一下,使用category有以下一些好处:

  • 内存使用情况:对于重复值很多的字符串列,category可以大大减少将数据存储在内存中所需的内存量;
  • 运行性能:进行了一些优化,可以提高某些操作的执行速度
  • 算法库的适用:在某些情况下,一些算法模型需要category这种类型。比如,我们知道lightgbm相对于xgboost优化的一个点就是可以处理分类变量,而在构建模型时我们需要指定哪些列是分类变量,并将它们调整为category作为超参数传给模型。

一个简单的例子。

代码语言:javascript
复制
df_size = 100_000
df1 = pd.DataFrame(
    {
        "float_1": np.random.rand(df_size),
        "species": np.random.choice(["cat", "dog", "ape", "gorilla"], size=df_size),
    }
)
df1_cat = df1.astype({"species": "category"})

创建了两个DataFrame,其中df1包含了species并且为object类型,df1_cat复制了df1,但指定了species为category类型。

代码语言:javascript
复制
>> df1.memory_usage(deep=True)
Index          128
float_1     800000
species    6100448
dtype: int64

就内存使用而言,我们可以直接看到包含字符串的列的成本是多高。species列的字符串大约占用了6MB,如果这些字符串较长,则将会更多。

代码语言:javascript
复制
>> df1_cat.memory_usage(deep=True)
Index         128
float_1    800000
species    100416
dtype: int64

再看转换为category类别后的内存使用情况。有了相当大的改进,使用的内存减少了大约60倍。没有对比,就没有伤害。

这就是使用category的其中一个好处。

使用category的一些坑!

但爱之深,责之切呀,category有很多坑要注意,这里东哥总结出以下几点,供大家参考。

1、category列的操作

好吧,这部分应该才是大家较为关心的,因为经常会遇到一些莫名其妙的报错或者感觉哪里不对,又不知道问题出在哪里。

首先,说明一下:使用category的时候需要格外小心,因为如果姿势不对,它就很可能变回object 。而变回object的结果就是,会降低代码的性能(因为强制转换类型成本很高),并会消耗内存。

日常面对category类型的数据,我们肯定是要对其进行操作的,比如做一些转换。下面看一个例子,我们要分别对categoryobject类型进行同样的字符串大写操作,使用accessor的.str方法。

在非category字符串上:

代码语言:javascript
复制
>> %timeit df1["species"].str.upper()
25.6 ms ± 2.07 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

在category字符串上:

代码语言:javascript
复制
>> %timeit df1_cat["species"].str.upper()
1.85 ms ± 41.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

结果很明显了。在这种情况下,速度提高了大约14倍(因为内部优化会让.str.upper()仅对分类的唯一类别值调用一次,然后根据结果构造一个seires,而不是对结果中的每个值都去调用一次)。

怎么理解?假设现有一个列叫animal,其类别有catdog两种,假设样本为10000个,4000个cat和6000个dog。那么如果我用对category本身处理,意味着我只分别对catdog两种类别处理一次,一共两次就解决。如果对每个值处理,那就需要样本数量10000次的处理。

尽管从时间上有了一些优化,然而这种方法的使用也是有一些问题的。。。看一下内存使用情况。

代码语言:javascript
复制
>> df1_cat["species"].str.upper().memory_usage(deep=True)
6100576

意外的发现category类型丢了。。结果竟是一个object类型,数据压缩的效果也没了,现在的结果再次回到刚才的6MB内存占用。

这是因为使用str会直接让原本的category类型强制转换为object,所以内存占用又回去了,这是我为什么最开始说要格外小心。

解决方法就是:直接对category本身操作而不是对它的值操作。 要直接使用cat的方法来完成转换操作,如下。

代码语言:javascript
复制
%timeit df1_cat["species"].cat.rename_categories(str.upper)
239 µs ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

可以看到,这个速度就更快了,因为省去了将category类别转换为object的时间,并且内存占用也非常少。因此,这才是最优的做法。

2、与category列的合并

还是上面那个例子,但是这次增加了habitat一列,并且species中增加了sanke

代码语言:javascript
复制
df2 = pd.DataFrame(
    {
        "species": ["cat", "dog", "ape", "gorilla", "snake"],
        "habitat": ["house", "house", "jungle", "jungle", "jungle"],
    }
)
df2_cat = df2.astype({"species": "category", "habitat": "category"})

和前面一样,创建该数据集的一个category版本,并创建了一个带有object字符串的版本。如果将两个object列合并在一起的,没什么意思,因为大家都知道会发生什么,object+ object= object而已。

把object列合并到category列上

接着上面的例子。

代码语言:javascript
复制
>> df1.merge(df2_cat, on="species").dtypes
float_1     float64
species      object
habitat    category
dtype: object

左边的df1species列为object,右边的df2_catspecies列为category。我们可以看到,当我们合并时,在结果中的合并列会得到category+ object= object

这显然不行了,又回到原来那样了。我们再试下其他情况。

两个category列的合并

代码语言:javascript
复制
>> df1_cat.merge(df2_cat, on="species").dtypes
float_1     float64
species      object
habitat    category
dtype: object

结果是:category+ category= object?

有点想打人了,但是别急,我们看看为啥。

在合并中,为了保存分类类型,两个category类型必须是完全相同的。 这个与pandas中的其他数据类型略有不同,例如所有float64列都具有相同的数据类型,就没有什么区分。

而当我们讨论category数据类型时,该数据类型实际上是由该特定类别中存在的一组值来描述的,因此一个类别包含["cat", "dog", "mouse"]与类别包含["cheese", "milk", "eggs"]是不一样的。上面的例子之所以没成功,是因为多加了一个snake

因此,我们可以得出结论:

  • category1+ category2=object
  • category1+ category1=category1

因此,解决办法就是:两个category类别一模一样,让其中一个等于另外一个

代码语言:javascript
复制
>> df1_cat.astype({"species": df2_cat["species"].dtype}).merge(
       df2_cat, on="species"
   ).dtypes

float_1     float64
species    category
habitat    category
dtype: object

3、category列的分组

用category类列分组时,一旦误操作就会发生意外,结果是Dataframe会被填成空值,还有可能直接跑死。。

当对category列分组时,默认情况下,即使category类别的各个类不存在值,也会对每个类进行分组。

一个例子来说明。

代码语言:javascript
复制
habitat_df = (
    df1_cat.astype({"species": df2_cat["species"].dtype})
           .merge(df2_cat, on="species")
)
house_animals_df = habitat_df.loc[habitat_df["habitat"] == "house"]

这里采用habitat_df,从上面例子得到的,筛选habitathouse的,只有dogcathouse,看下面分组结果。

代码语言:javascript
复制
>> house_animals_df.groupby("species")["float_1"].mean()
species
ape             NaN
cat        0.501507
dog        0.501023
gorilla         NaN
snake           NaN
Name: float_1, dtype: float64

groupby中得到了一堆空值。默认情况下,当按category列分组时,即使数据不存在,pandas也会为该类别中的每个值返回结果。略坑,如果数据类型包含很多不存在的,尤其是在多个不同的category列上进行分组,将会极其损害性能。

因此,解决办法是:可以传递observed=Truegroupby调用中,这确保了我们仅获取数据中有值的组。

代码语言:javascript
复制
>> house_animals_df.groupby("species", observed=True)["float_1"].mean()
species
cat    0.501507
dog    0.501023
Name: float_1, dtype: float64

4、category列的索引

仍以上面例子举例,使用groupby-unstack实现了一个交叉表,species作为列,habitat作为行,均为category类型。

代码语言:javascript
复制
>> species_df = habitat_df.groupby(["habitat", "species"], observed=True)["float_1"].mean().unstack()
>> species_df

species       cat       ape       dog   gorilla
habitat                                        
house    0.501507       NaN  0.501023       NaN
jungle        NaN  0.501284       NaN  0.501108

这好像看似也没什么毛病,我们继续往下看。为这个交叉表添加一个新列new_col,值为1。

代码语言:javascript
复制
>> species_df["new_col"] = 1
TypeError: 'fill_value=new_col' is not present in this Categorical's categories

正常情况下,上面这段代码是完全可以的,但这里报错了,为什么?

原因是specieshabitat现在均为category类型。使用.unstack()会把species索引移到列索引中(类似pivot交叉表的操作)。而当添加的新列不在species的分类索引中时,就会报错。

总结一下,pandascategory类型非常有用,可以带来一些良好的性能优势。但是它也很娇气,使用过程中要尤为小心,确保category类型在整个流程中保持不变,避免变回object。本文介绍的4个点注意点:

  • category列的变换操作:直接对category本身操作而不是对它的值操作。这样可以保留分类性质并提高性能。
  • category列的合并:合并时注意,要保留category类型,且每个dataframe的合并列中的分类类型必须完全匹配。
  • category列的分组:默认情况下,获得数据类型中每个值的结果,即使数据中不存在该结果。可以通过设置observed=True调整。
  • category列的索引:当索引为category类型的时候,注意是否可能与类别变量发生奇怪的交互作用。

以上就是本次分享内容。

所有数据和代码可在我的GitHub获取:

https://github.com/xiaoyusmd/PythonDataScience

下一篇
举报
领券