Pandas进阶:分类数据处理

引言

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

本文将介绍:

  • 什么是分类数据?

  • 分类数据cat的处理方法

  • 为什么要使用分类数据?

  • 分类数据cat使用时的一些坑

什么是分类数据?

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

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

创建分类数据

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

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

自动创建分类数据

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

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方法转换即可,如:

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

自定义分类数据

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

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

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()修改分类的名称。

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']# 列表形式:修改分类类型为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()追加分类。

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

# 删除指定的分类r和t
s.cat.remove_categories(['r','t'])
# 自动删除未使用的分类
s.cat.remove_unused_categories()

顺序

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

# 有序设置
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作为超参数传给模型。

一个简单的例子。

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类型。

>> df1.memory_usage(deep=True)
Index          128
float_1     800000
species    6100448
dtype: int64

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

>> 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字符串上:

>> %timeit df1["species"].str.upper()
25.6 ms ± 2.07 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

在category字符串上:

>> %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次的处理。

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

>> df1_cat["species"].str.upper().memory_usage(deep=True)
6100576

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

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

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

%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

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列上

接着上面的例子。

>> 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列的合并

>> 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类别一模一样,让其中一个等于另外一个

>> df1_cat.astype({"species": df2_cat["species"].dtype}).merge(df2_cat, on="species").dtypesfloat_1     float64
species    category
habitat    category
dtype: object

3、category列的分组

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

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

一个例子来说明。

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,看下面分组结果。

>> 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调用中,这确保了我们仅获取数据中有值的组。

>> 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类型。

>> species_df = habitat_df.groupby(["habitat", "species"], observed=True)["float_1"].mean().unstack()
>> species_dfspecies       cat       ape       dog   gorilla
habitat                                        
house    0.501507       NaN  0.501023       NaN
jungle        NaN  0.501284       NaN  0.501108

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

>> 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类型的时候,注意是否可能与类别变量发生奇怪的交互作用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/189754.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue系列:页面中图片等静态资源引用

前言 近期在做项目时遇到一些图片、视频、动态图片等静态资源的使用&#xff0c;在vue页面jsx、tsx中使用的时候遇到些问题&#xff1b; 对静态资源的引用使用总结如下 引入方式说明 以下代码实例以图片、vue环境为例&#xff0c;不放视屏等引入实例&#xff0c;视频使用方式…

C++标准模板(STL)- 类型支持 (杂项变换,定义适于用作给定大小的类型的未初始化存储的类型,std::aligned_storage)

类型特性 类型特性定义一个编译时基于模板的结构&#xff0c;以查询或修改类型的属性。 试图特化定义于 <type_traits> 头文件的模板导致未定义行为&#xff0c;除了 std::common_type 可依照其所描述特化。 定义于<type_traits>头文件的模板可以用不完整类型实…

记录5款NodeJS后端框架

文章目录 前言一、Express二、Nest.js三、Meteor四、Koa.js五、Fastify 前言 https://xie.infoq.cn/article/d8c2cd9cb99a04cbbf0a45434 https://juejin.cn/post/6959583458779725860 Nodejs 框架分为三种类型&#xff1a; MVCREST APIFull-Stack 一、Express Express 是最…

uni-app 微信小程序 电子签名及签名图片翻转显示功能

文章目录 1. 需求背景2. 开始撸2.1 点击 重写 进入签名页面&#xff08;上图一&#xff09;2.2 书写签名&#xff0c;点击确认返回&#xff0c;及图片翻转显示&#xff08;上图二&#xff0c;三&#xff09; 3. 图片进行翻转&#xff0c;返回翻转后的图片 1. 需求背景 接的一个…

Hdoop学习笔记(HDP)-Part.6 安装OracleJDK

六、安装OracleJDK 下载jdk安装文件&#xff0c;放到/opt中&#xff0c;将文件解压到/usr/local下后&#xff0c;修改/etc/profile文件中环境参数&#xff0c;实现java的安装。 创建jdk.yml文件 ---- hosts: alltasks:- name: copy and unzip jdkunarchive:src: "/opt/j…

C++学习之路(十五)C++ 用Qt5实现一个工具箱(增加16进制颜色码转换和屏幕颜色提取功能)- 示例代码拆分讲解

上篇文章&#xff0c;我们用 Qt5 实现了在小工具箱中添加了《Base64图片编码预览功能》功能。为了继续丰富我们的工具箱&#xff0c;今天我们就再增加两个平时经常用到的功能吧&#xff0c;就是「 16进制颜色码转RGB文本 」和 「屏幕颜色提取」功能。下面我们就来看看如何来规划…

深入解析SpringBoot的请求响应机制

SpringBootWeb请求响应 前言1. 请求1.1 Postman介绍 1.2 简单参数1.2.1 原始方式1.2.2 SpringBoot方式1.2.3 参数名不一致 1.3 实体参数1.3.1 简单实体对象1.3.2 复杂实体对象 1.4 数组集合参数1.4.1 数组1.4.2 集合 1.5 日期参数1.6 JSON参数1.7 路径参数 2. 响应2.1 Response…

电子学会C/C++编程等级考试2021年06月(四级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:数字三角形问题 (图1) 图1给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。 注意:路径上的每一步只能从一个数走到下一层上和它…

【微服务 SpringCloudAlibaba】实用篇 · Gateway服务网关

微服务&#xff08;8&#xff09; 文章目录 微服务&#xff08;8&#xff09;1. 为什么需要网关2. gateway快速入门1&#xff09;创建gateway服务&#xff0c;引入依赖2&#xff09;编写启动类3&#xff09;编写基础配置和路由规则4&#xff09;重启测试5&#xff09;网关路由的…

LeetCode22. 括号生成

参考的题解 &#x1f517;:链接22. 括号生成 class Solution {public List<String> generateParenthesis(int n) {List<String> result new ArrayList<>();backtracking(n, result, 0, 0, "");return result;}private void backtracking(int n, …

python之logo编程

Logo标志是一种视觉符号&#xff0c;代表着一个品牌、企业或组织的形象。它通常采用图形、字母或字形来代表一个公司或品牌&#xff0c;起到对徽标拥有公司的识别和推广的作用。Logo的设计需要考虑多种因素&#xff0c;例如颜色搭配、字体选择和构图等&#xff0c;以创造出独特…

java餐饮刀削面快餐店点餐服务系统springboot+jsp

网上点餐省去了客户很多不必要的时间和麻烦&#xff0c;给商家带来更多利益。同时&#xff0c;网上点餐可以辅助餐饮企业营销。传统的点餐是需要配备一个专业的服务员负责菜品介绍并记录顾客点单&#xff0c;确认后上交至后台厨房&#xff0c;厨房根据菜品种类安排做菜顺序最终…

11.28 C++作业

提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数 要求使用C风格字符串完成 #include <iostream>using namespace std;int main() {string str;cout << "请输入一个字符串&#xff1a;" <<…

前端面试灵魂提问-计网(2)

1、websocket 为什么全双工? 1.1 WebSocket是什么 WebSocket 是一种通信协议&#xff0c;它在客户端和服务器之间建立持久的全双工连接。全双工意味着数据可以双向流动&#xff0c;即客户端可以向服务器发送消息&#xff0c;服务器也可以向客户端发送消息&#xff0c;而无需…

Hertz 整合swagger

文章目录 Swagger安装使用用法项目demoSwagger注释用法通用API信息 swag命令行参数swagger路由配置 Swagger 安装 go get 安装可执行文件需要配合 GOPATH 模式工作。 go get github.com/swaggo/swag/cmd/swag 因为从 Go 1.17 开始&#xff0c;在 go mod 模式下通过 go get 下…

Go 语言中 sync 包的近距离观察

让我们来看看负责提供同步原语的 Go 包&#xff1a;sync。 sync.Mutex sync.Mutex 可能是 sync 包中被广泛使用的原语。它允许对共享资源进行互斥操作&#xff08;即不允许同时访问&#xff09;&#xff1a; mutex : &sync.Mutex{}mutex.Lock() // Update shared variab…

Jinja2使用Layui报 “d is not defined“

问题出现场景在使用Jinja2渲染Layui的表格时候&#xff0c;要做自定义templte的传入 Jinja2这块本来就是支持 {{ }} 插值的模板语言&#xff0c;所以这块的第一种渲染方式会冲突 所以只能用函数返回代码块进行填充&#xff0c;不能使用插值&#xff0c;只能拼接字符串 templt…

Gradle windows下配置

1.Gradle下载 打开官网下载界面&#xff1a;https://gradle.org/releases/ 如果你使用的SpringBoot项目&#xff0c;建议使用6.8及以上的版本 2.下载后放到目录下 3.配置环境变量 配置gradle_home 配置Path 4.配置成功 5.配置国内源 新建一个init.gradle文件&#xff0c;配…

MySQL- CRUD-单表查询

一、INSERT 添加 公式 INSERT INTO table_name [(column [, column...])] VALUES (value [, value...]); 示例&#xff1a; CREATE TABLE goods (id INT ,good_name VARCHAR(10),price DOUBLE ); #添加数据 INSERT INTO goods (id,good_name,price ) VALUES (20,华为手机,…

虚假IP地址攻击的溯源方法

随着网络技术的迅速发展&#xff0c;网络攻击行为也日益猖獗。其中&#xff0c;虚假IP地址攻击是一种较为常见的网络攻击方式&#xff0c;它利用虚假的IP地址&#xff0c;通过互联网对目标进行攻击和入侵。这种攻击方式不仅难以追踪&#xff0c;而且往往会给企业和个人带来巨大…