(数据科学学习手札69)详解pandas中的map、apply、applymap、groupby、agg
*从本篇开始所有文章的数据和代码都已上传至我的github仓库:https://github.com/CNFeffery/DataScienceStudyNotes
一、简介
pandas提供了很多方便简洁的方法,用于对单列、多列数据进行批量运算或分组聚合运算,熟悉这些方法后可极大地提升数据分析的效率,也会使得你的代码更加地优雅简洁,本文就将针对pandas中的map()、apply()、applymap()、groupby()、agg()等方法展开详细介绍,并结合实际例子帮助大家更好地理解它们的使用技巧(本文使用到的所有代码及数据均保存在我的github仓库:https://github.com/CNFeffery/DataScienceStudyNotes对应本文的文件夹下)。
二、非聚合类方法
这里的非聚合指的是数据处理前后没有进行分组操作,数据列的长度没有发生改变,因此本章节中不涉及groupby(),首先读入数据,这里使用到的全美婴儿姓名数据,包含了1880-2018年全美每年对应每个姓名的新生儿数据,在jupyterlab中读入数据并打印数据集的一些基本信息以了解我们的数据集:
import pandas as pd #读入数据
data = pd.read_csv('data.csv')
data.head()
#查看各列数据类型、数据框行列数
print(data.dtypes)
print()
print(data.shape)
2.1 map()
类似Python内建的map()方法,pandas中的map()方法将函数、字典索引或是一些需要接受单个输入值的特别的对象与对应的单个列的每一个元素建立联系并串行得到结果,譬如这里我们想要得到gender列的F、M转换为女性、男性的新列,可以有以下几种实现方式:
● 字典映射
这里我们编写F、M与女性、男性之间一一映射的字典,再利用map()方法来得到映射列:
#定义F->女性,M->男性的映射字典
gender2xb = {'F': '女性', 'M': '男性'}
#利用map()方法得到对应gender列的映射列
data.gender.map(gender2xb)
● lambda函数
这里我们向map()中传入lambda函数来实现所需功能:
#因为已经知道数据gender列性别中只有F和M所以编写如下lambda函数
data.gender.map(lambda x:'女性' if x is 'F' else '男性')
● 常规函数
也可以传入def定义的常规函数:
def gender_to_xb(x): return '女性' if x is 'F' else '男性' data.gender.map(gender_to_xb)
map()可以传入的内容有时候可以很特殊,如下面的例子:
● 特殊对象
一些接收单个输入值且有输出的对象也可以用map()方法来处理:
data.gender.map("This kid's gender is {}".format)
map()还有一个参数na_action,类似R中的na.action,取值为'None'或'ingore',用于控制遇到缺失值的处理方式,设置为'ingore'时串行运算过程中将忽略Nan值原样返回。
2.2 apply()
apply()堪称pandas中最好用的方法,其使用方式跟map()很像,主要传入的主要参数都是接受输入返回输出,但相较于map()针对单列Series进行处理,一条apply()语句可以对单列或多列进行运算,覆盖非常多的使用场景,下面我们来分别介绍:
● 单列数据
这里我们参照2.1向apply()中传入lambda函数:
data.gender.apply(lambda x:'女性' if x is 'F' else '男性')
可以看到这里实现了跟map()一样的功能。
● 输入多列数据
apply()最特别的地方在于其可以同时处理多列数据,我们先来了解一下如何处理多列数据输入单列数据输出的情况,譬如这里我们编写一个使用到多列数据的函数用于拼成对于每一行描述性的话,并在apply()用lambda函数传递多个值进编写好的函数中(当调用DataFrame.apply()时,apply()在串行过程中实际处理的是每一行数据而不是Series.apply()那样每次处理单个值),注意在处理多个值时要给apply()添加参数axis=1:
def generate_descriptive_statement(year, name, gender, count):
year, count = str(year), str(count)
gender = '女性' if gender is 'F' else '男性' return '在{}年,叫做{}性别为{}的新生儿有{}个。'.format(year, name, gender, count) data.apply(lambda row:generate_descriptive_statement(row['year'],
row['name'],
row['gender'],
row['count']),
axis = 1)
● 输出多列数据
有些时候我们利用apply()会遇到希望同时输出多列数据的情况,在apply()中同时输出多列时实际上返回的是一个Series,这个Series中每个元素是与apply()中传入函数的返回值顺序对应的元组,比如下面我们利用apply()来提取name列中的首字母和剩余部分字母:
data.apply(lambda row: (row['name'][0], row['name'][1:]), axis=1)
可以看到,这里返回的是单列结果,每个元素是返回值组成的元组,这时若想直接得到各列分开的结果,需要用到zip(*zipped)来解开元组序列,从而得到分离的多列返回值:
a, b = zip(*data.apply(lambda row: (row['name'][0], row['name'][1:]), axis=1))
print(a[:10])
print(b[:10])
● 结合tqdm给apply()过程添加进度条
我们知道apply()在运算时实际上仍然是一行一行遍历的方式,因此在计算量很大时如果有一个进度条来监视运行进度就很舒服,在(数据科学学习手札53)Python中tqdm模块的用法中,我对基于tqdm为程序添加进度条做了介绍,而tqdm对pandas也是有着很好的支持,我们可以使用progress_apply()代替apply(),并在运行progress_apply()之前添加tqdm.tqdm.pandas(desc='')来启动对apply过程的监视,其中desc参数传入对进度进行说明的字符串,下面我们在上一小部分示例的基础上进行改造来添加进度条功能:
from tqdm import tqdm def generate_descriptive_statement(year, name, gender, count):
year, count = str(year), str(count)
gender = '女性' if gender is 'F' else '男性' return '在{}年,叫做{}性别为{}的新生儿有{}个。'.format(year, name, gender, count) #启动对紧跟着的apply过程的监视
tqdm.pandas(desc='apply')
data.progress_apply(lambda row:generate_descriptive_statement(row['year'],
row['name'],
row['gender'],
row['count']),
axis = 1)
可以看到在jupyter lab中运行程序的过程中,下方出现了监视过程的进度条,这样就可以实时了解apply过程跑到什么地方了。
● 结合tqdm_notebook()给apply()过程添加美观进度条
熟悉tqdm的朋友都知道其针对jupyter notebook开发了ui更加美观的tqdm_notebook(),而要想在jupyter notebook/jupyter lab平台上为pandas的apply过程添加美观进度条,可以参照如下示例:
from tqdm._tqdm_notebook import tqdm_notebook tqdm_notebook.pandas(desc='apply')
data.progress_apply(lambda row:generate_descriptive_statement(row['year'],
row['name'],
row['gender'],
row['count']),
axis = 1)
这时所添加的进度条就美观了不少。
2.3 applymap()
applymap()是与map()方法相对应的专属于DataFrame对象的方法,类似map()方法传入函数、字典等,传入对应的输出结果,不同的是applymap()将传入的函数等作用于整个数据框中每一个位置的元素,因此其返回结果的形状与原数据框一致,譬如下面的简单示例,我们把婴儿姓名数据中所有的字符型数据消息小写化处理,对其他类型则原样返回:
def lower_all_string(x):
if isinstance(x, str):
return x.lower()
else:
return x data.applymap(lower_all_string)
其形状没有变化:
配合applymap(),可以简洁地完成很多数据处理操作。
三、聚合类方法
有些时候我们需要像SQL里的聚合操作那样将原始数据按照某个或某些离散型的列进行分组再求和、平均数等聚合之后的值,在pandas中分组运算是一件非常优雅的事。
3.1 利用groupby()进行分组
要进行分组运算第一步当然就是分组,在pandas中对数据框进行分组使用到groupby()方法,其主要使用到的参数为by,这个参数用于传入分组依据的变量名称,当变量为1个时传入名称字符串即可,当为多个时传入这些变量名称列表,DataFrame对象通过groupby()之后返回一个生成器,需要将其列表化才能得到需要的分组后的子集,如下面的示例:
#按照年份和性别对婴儿姓名数据进行分组
groups = data.groupby(by=['year','gender'])
#查看groups类型
type(groups)
可以看到它此时是生成器,下面我们用列表解析的方式提取出所有分组后的结果:
#利用列表解析提取分组结果
groups = [group for group in groups]
查看其中的一个元素:
可以看到每一个结果都是一个二元组,元组的第一个元素是对应这个分组结果的分组组合方式,第二个元素是分组出的子集数据框,而对于DataFrame.groupby()得到的结果,主要可以进行以下几种操作:
● 直接调用聚合函数
譬如这里我们提取count列后直接调用max()方法:
#求每个分组中最高频次
data.groupby(by=['year','gender'])['count'].max()
注意这里的year、gender列是以索引的形式存在的,想要把它们还原回数据框,使用reset_index(drop=False)即可:
● 结合apply()
分组后的结果也可以直接调用apply(),这样可以编写更加自由的函数来完成需求,譬如下面我们通过自编函数来求得每年每种性别出现频次最高的名字及对应频次,要注意的是,这里的apply传入的对象是每个分组之后的子数据框,所以下面的自编函数中直接接收的df参数即为每个分组的子数据框:
import numpy as np def find_most_name(df):
return str(np.max(df['count']))+'-'+df['name'][np.argmax(df['count'])] data.groupby(['year','gender']).apply(find_most_name).reset_index(drop=False)
3.2 利用agg()进行更灵活的聚合
agg即aggregate,聚合,在pandas中可以利用agg()对Series、DataFrame以及groupby()后的结果进行聚合,其传入的参数为字典,键为变量名,值为对应的聚合函数字符串,譬如{'v1':['sum','mean'], 'v2':['median','max','min]}就代表对数据框中的v1列进行求和、均值操作,对v2列进行中位数、最大值、最小值操作,下面用几个简单的例子演示其具体使用方式:
● 聚合Series
在对Series进行聚合时,因为只有1列,所以可以不使用字典的形式传递参数,直接传入函数名列表即可:
#求count列的最小值、最大值以及中位数
data['count'].agg(['min','max','median'])
● 聚合数据框
对数据框进行聚合时因为有多列,所以要使用字典的方式传入聚合方案:
data.agg({'year': ['max','min'], 'count': ['mean','std']})
值得注意的是,因为上例中对于不同变量的聚合方案不统一,所以会出现NaN的情况。
● 聚合groupby()结果
data.groupby(['year','gender']).agg({'count':['min','max','median']}).reset_index(drop=False)
可以注意到虽然我们使用reset_index()将索引列还原回变量,但聚合结果的列名变成红色框中奇怪的样子,而在pandas 0.25.0以及之后的版本中,可以使用pd.NamedAgg()来为聚合后的每一列赋予新的名字:
data.groupby(['year','gender']).agg(
min_count=pd.NamedAgg(column='count', aggfunc='min'),
max_count=pd.NamedAgg(column='count', aggfunc='max'),
median=pd.NamedAgg(column='count', aggfunc='median')).reset_index(drop=False)
以上就是本文全部内容,如有笔误望指出!
* 2019.10.28更新:添加了使用apply同时返回分离的多列数据的方法
* 2019.11.26更新:添加了tqdm_notebook()版apply()进度条的使用方法
(数据科学学习手札69)详解pandas中的map、apply、applymap、groupby、agg的更多相关文章
- (数据科学学习手札97)掌握pandas中的transform
本文示例文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 开门见山,在pandas中,transform是 ...
- (数据科学学习手札99)掌握pandas中的时序数据分组运算
本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 我们在使用pandas分析处理时间序列数据 ...
- (数据科学学习手札44)在Keras中训练多层感知机
一.简介 Keras是有着自主的一套前端控制语法,后端基于tensorflow和theano的深度学习框架,因为其搭建神经网络简单快捷明了的语法风格,可以帮助使用者更快捷的搭建自己的神经网络,堪称深度 ...
- (数据科学学习手札91)在Python中妥善使用进度条
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在日常运行程序的过程中常常涉及到循环迭代过程,对 ...
- (数据科学学习手札63)利用pandas读写HDF5文件
一.简介 HDF5(Hierarchical Data Formal)是用于存储大规模数值数据的较为理想的存储格式,文件后缀名为h5,存储读取速度非常快,且可在文件内部按照明确的层次存储数据,同一个H ...
- (数据科学学习手札73)盘点pandas 1.0.0中的新特性
本文对应脚本及数据已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 毫无疑问pandas已经成为基于Pytho ...
- (数据科学学习手札127)在Python中使用icecream实现高效debug
本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 尽管有那么多花里胡哨的debug工具和方式 ...
- (数据科学学习手札71)在Python中制作个性化词云图
本文对应脚本及数据已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 一.简介 词云图是文本挖掘中用来表征词频的数据可视化 ...
- (数据科学学习手札107)在Python中利用funct实现链式风格编程
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 链式编程是一种非常高效的组织代码的方式,典型如p ...
随机推荐
- Java匹马行天下之JavaWeb核心技术——JSP(续一)
十二.JSP表单处理 我们在浏览网页的时候,经常需要向服务器提交信息,并让后台程序处理.浏览器中使用 GET 和 POST 方法向服务器提交数据. GET 方法 GET方法将请求的编码信息添加在网 ...
- 【Spring】org.springframework.web.context.ContextLoaderListen 报错
详细信息如下: org.apache.catalina.core.StandardContext.listenerStart Error configuring application listene ...
- 消息中间件——RabbitMQ(一)Windows/Linux环境搭建(完整版)
前言 最近在学习消息中间件--RabbitMQ,打算把这个学习过程记录下来.此章主要介绍环境搭建.此次主要是单机搭建(条件有限),包括在Windows.Linux环境下的搭建,以及RabbitMQ的监 ...
- 解释一下一门语言该有的东东(Javascript)
注释 Js中有两种注释 // 单行注释 /**/ 多行注释 变量 变量就像学校学习的 未知数 如 3 + x = 8 x: 类似变量,在改造一下 x + y = z 当 x=3, y=5, z=8, ...
- Selenium模拟登陆百度贴吧
Selenium模拟登陆百度贴吧 from selenium import webdriver from time import sleep from selenium.webdriver.commo ...
- Windbg程序调试系列-索引篇
最近整理了一下Windbg程序调试系列的文章,做个了索引贴,方便大家查询.搜索: Windbg程序调试系列1-常用命令说明&示例 Windbg程序调试系列1-Mex扩展使用总结 Windbg程 ...
- 内存泄漏排查之:Show me your Memory
java 语言有个神奇的地方,那就是你时不时会去关注下内存.(当然了,任何牛逼的同学都应该关注内存) 今天我们就来这么场景吧:某应用运行了一段时间后,ecs监控报警了,内存比较高了,怎么办?随着时间的 ...
- H5 Handlebars的简单使用
扫码关注公众号,不定期更新干活 web 开发中,js 解析JSON 是经常的事情.非常繁琐.handlebars 使用了模版,只要你定义一个模版,提供一个json对象,handlebars 就能吧js ...
- 使用appscan安全扫描问题以及解决办法
最近在做安全扫描,把遇到的一些问题以及一些解决方法记录下,以备后用. 扫描软件: IBM Security AppScan Standard 规则: 17441 1. 已解密的登录请求 (高) - ...
- 详解InheritableThreadLocal类的使用与原理
在Java并发编程中,InheritableThreadLocal 与 ThreadLocal 都可以用于线程间通信,不同的是 InheritableThreadLocal 继承了 ThreadLoc ...