pandas apply lambda_一分钟一个Pandas小技巧(二)
“ 在逛Kaggle的时候发现了一篇不错的Pandas技巧,我将挑选一些有用的并外加一些自己的想法分享给大家。本系列虽基础但带仍有一些奇怪操作,粗略扫一遍,您或将发现一些您需要的技巧。”
纸上得来终觉浅,绝知此事要躬行,所谓的熟练使用Pandas是建立在您大致了解每个函数功能上,希望本系列能给您带来些许收获。
本篇所涉及知识点:
- map、apply、applymap
- groupby
- MultiIndex DataFrame
- 统计函数、累计函数
- agg、transform、filter
map、apply和applymap
先总结一下:
- map()适用于Series,相当于将Series中的每个元素作为参数带入指定函数,注意是Series中的每个元素。
- apply()适用于DataFrame的行或者列,将整个行列作为参数带入指定函数,注意是DataFrame的行或列,即整个Series。
- applymap()适用于DataFrame中的每个元素,注意是DataFrame中的每个元素。
map
Series.map(self,arg,na_action)
作用于一个Series上,DataFrame的一行或者一列都是Series。
map()最主要的用途是实现以下两个功能:
- 字典替换
- 简单的有参函数(有且仅有一个参数,其实就是Series里的每个元素了)
df = pd.DataFrame([['小王', 'm', 80], ['小红', 'f', 90], ['小明', 'x', 85], [ '小林', np.nan, 83], ], columns=['student', 'sex', 'score'])df
student sex score0 小王 m 801 小红 f 902 小明 x 853 小林 NaN 83
使用dict对Series进行映射替换。如果Series中的值并没有在给定字典中出现,那该值会被替换为np.nan。
下面案例中我想把sex='m'替换为"男",sex='f'替换为"女",小明的sex='x'我并没有在字典中给出映射,所以map()后直接被替换为np.nan。
my_dict = {'m': '男', 'f': '女'}df['dict'] = df['sex'].map(my_dict)df
student sex score dict0 小王 m 80 男1 小红 f 90 女2 小明 x 85 NaN3 小林 NaN 83 NaN
如果对于未给定的键我们也想统一替换,那就要在字典中设置默认值,接下来教大家如何设置字典默认值。
from collections import defaultdict
def ret(): """ describe:因为defaultdict接受一个工厂函数,所以咱们直接创建一个函数,返回Unknown return :str """ return 'Unknown'
# 生成一个新的dict,使用defaultdict函数并传入我们创建的工厂函数# 当然你也可以填入其他的数据类型,具体类型及对应默认值如下:# int->0,set->set(),str->'',list->[]my_hasdefault_dict = defaultdict(ret)
# 将之前的dict再传入新的含有默认值的dictmy_dict = {'m': '男', 'f': '女'}for k, v in my_dict.items(): my_hasdefault_dict[k] = v
# 因为小林的sex=np.nan没有在给定字典中出现,所以也替换成了默认值df['hasdefault_dict'] = df['sex'].map(my_hasdefault_dict)# 在map中设置na_action='ignore'时就会忽略np.nan,不进行任何操作df['ignore_na'] = df['sex'].map(my_hasdefault_dict, na_action='ignore')df
student sex score dict hasdefault_dict ignore_na0 小王 m 80 男 男 男1 小红 f 90 女 女 女2 小明 x 85 NaN Unknown Unknown3 小林 NaN 83 NaN Unknown NaN
map()可以应用一些简单的自定义函数。
def my_cut(x): """ descrieb:对成绩分桶 return :str """ if x >= 90: return 'score>=90' if 85 <= x 90: return '85<=score<90' else: return 'score<85'
# map里面只要写入函数名就可以,不用括号df['my_cut'] = df['score'].map(my_cut)
# 上面的判断比较简单,所以我们可以改写成lambda的形式# lambda的格式是 lambda x:结果1 if 条件1 else 结果2 if 条件2 else 结果3df['cut_lambda'] = df['score'].map( lambda x: 'score>=90' if x >= 90 else '85<=score<90' if 85 <= x 90 else 'score<85')
# 当然上面只是为了演示map,其实用Pandas自带的cut更方便# right=True是右闭区间,right=False是左闭区间df['cut'] = pd.cut(df['score'], bins=[-np.inf, 85, 90, np.inf], labels=['score<85', '85<=score<90', 'score>=90'], right=False)df
student sex score my_cut cut_lambda cut0 小王 m 80 score<85 score<85 score<851 小红 f 90 score>=90 score>=90 score>=902 小明 x 85 85<=score<90 85<=score<90 85<=score<903 小林 NaN 83 score<85 score<85 score<85
apply
DataFrame.apply(self, func, axis=0, raw=False, result_type=None, args=(), **kwds)
Series.apply(self, func, convert_dtype=True, args=(), **kwds)
GroupBy.apply(self, func, args, *kwargs)
- ... apply()有好多,我们最主要的就是用到上面这三种结构下的apply()
apply()不可以像map()那样使用字典映射替换字符,但是可以通过字典调用函数(起码我还没用到过)。
df = pd.DataFrame([['小王', 'm', 80, '汉族'], ['小红', 'f', 90, '回族'], ['小明', 'm', 85, '汉族'], [ '小林', 'f', 83, '保安族'], ], columns=['student', 'sex', 'score', 'nationality'])df
student sex score nationality0 小王 m 80 汉族1 小红 f 90 回族2 小明 m 85 汉族3 小林 f 83 保安族
为了证明apply是真的可以字典调用函数,我们来做个没有实际意义的测试,结果是可行的。
# 如果sex=m则后面拼接ale,如果sex=f则后面拼接emalefunc_dict = {'m': lambda x: x+'ale', 'f': lambda x: x+'emale'}df['sex'].apply(func_dict)
m 0 male 1 fale 2 male 3 falef 0 memale 1 female 2 memale 3 female
是不是感觉和map出来的效果有点不一样?继续往下看。
# apply可以添加参数,如果用*args接收参数,那就使用元组的方式按位置索引,**kwargs就按照字典方式按key索引
def birth(x, **kwargs): return f'{x}身高{kwargs["height"]}cm'
# 调用随机函数,指定相同的范围df['apply_birth'] = df['student'].apply( birth, height=np.random.randint(160, 180))df['map_birth'] = df['student'].map( lambda x: f'{x}身高{np.random.randint(160,180)}cm')df
student sex score nationality apply_birth map_birth0 小王 m 80 汉族 小王身高164cm 小王身高171cm1 小红 f 90 回族 小红身高164cm 小红身高178cm2 小明 m 85 汉族 小明身高164cm 小明身高169cm3 小林 f 83 保安族 小林身高164cm 小林身高175cm
我们来对比一下map()和apply(),同样的想法却出现不一样的效果。apply()出来的身高都是一样的,而map()出来的身高是不一样的。所以就如之前的总结所说一般,map()作用于Series中的每个元素,apply()作用于整个Series。
我们看一下apply()是如何作用于多行/列的,下面案例中我们调取多列来实现对少数民族同学加5分的操作。
# 多列选择后使用apply,可以使用位置索引选择列,比如这里x[1]代表的是df['score']df['加分后成绩'] = df[['nationality', 'score']].apply( lambda x: x[1]+5 if x[0] != '汉族' else x[1], axis=1)df
student sex score nationality 加分后成绩0 小王 m 80 汉族 801 小红 f 90 回族 952 小明 m 85 汉族 853 小林 f 83 保安族 88
上面案例中为什么要用axis=1呢,下面的例子,请各位意会一下。
- axis=0是对行计算,返回的行数与原表列索引数相等。
- axis=1是对列进行计算,返回的行数应该与原表行索引数相等。
applymap
applymap()是DataFrame的方法,只能作用于整个DataFrame。
df = pd.DataFrame([[4, 9]] * 3, columns=['A', 'B'])df
A B0 4 91 4 92 4 9
df.applymap(lambda x: x+2)
A B0 6 111 6 112 6 11
# 错误示范df['A'].applymap(lambda x: x+2)
AttributeError: 'Series' object has no attribute 'applymap'
不得不会的groupby
敲黑板!pandas中相当重要的一环!
DataFrame.groupby(self, by=None, axis=0, level=None, as_index: bool = True, sort: bool = True, group_keys: bool = True, squeeze: bool = False, observed: bool = False)
返回的是DataFrameGroupBy对象,groupby的基本流程如下:
- 根据某些标准将数据分成组。
- 对每个组独立应用一个函数。
- 将结果组合到数据结构中。
import random# 以下所有案例皆基于这个DataFramecat = ['product1', 'product2', 'product3']df = pd.DataFrame(list(zip(pd.date_range(start='2020/1/1',end='2020/1/10'),random.choices('ABC', k=10), random.choices(cat, k=10), random.choices( range(1, 100), k=10))), columns=['date','salesman', 'product', 'volume'])df
date salesman product volume0 2020-01-01 C product3 491 2020-01-02 C product3 302 2020-01-03 B product1 353 2020-01-04 A product3 634 2020-01-05 B product2 265 2020-01-06 A product3 376 2020-01-07 B product1 507 2020-01-08 C product2 168 2020-01-09 A product2 459 2020-01-10 B product3 22
看到ABC,偷偷教大家一个Python快速打出26个英文字母的方法。
from string import ascii_uppercase # 26个大写字母
from string import ascii_lowercase # 26个小写字母
from string import ascii_uppercase # 52个字母,前26小写,后26大写
只要直接print(ascii_uppercase/ascii_lowercase/ascii_uppercase)就可以了。
我们想看一下销售员的总销量。
df.groupby(by='salesman').sum()
volumesalesman A 145B 133C 95
再看一下各销售员每类产品的总销量。
groupby可以根据多个标签分组,但是注意,默认情况下多标签分组后获得的DataFrameGroupBy是含有MultiIndex的。
# 默认情况下sort=True,Pandas会将分组标签自动排序,也可以设置为False,加快groupby速度,但大多数情况还是让它排序吧df_mulitindex = df.groupby(by=['salesman','product'],sort=True).sum()df_mulitindex
volumesalesman product A product2 45 product3 100B product1 85 product2 26 product3 22C product2 16 product3 79
即使是MultiIndex DataFrame也一样可以groupby的,你可以用level也可以用by来控制分组的标签。
- 如果原DataFrame是普通索引,那只能设置by参数
- 如果是MultiIndex DataFrame,那by和level都可以设置,但是更推荐用level,别问为什么,问就是不知道
df_mulitindex.groupby(level='product',sort=False).sum()
volumeproduct product2 87product3 201product1 85
我想先不进行聚合运算,看看分好组的样子可以吗?接下来几个函数可以帮到你。
- df.groupby().first()返回每个分组的第一行
- df.groupby().last()返回每个分组的最后一行
- df.groupby().nth(n)返回每个分组的第n行
- df.groupby().head(n)返回每个分组前n行
- df.groupby().tail(n)返回每个分组后n行
groupby的第一步操作,是按照指定字段分组,所以首先我将原DataFrame对列索引进行排序,方便大家理解。
# ascending=False降序/True升序df.sort_values(by=['salesman','product','volume'],ascending=[True,True,False])
上述函数我就不一一展示,就举一例子,查询各业务员各类产品销量最高的那行数据。
df.sort_values(by='volume',ascending=False).groupby(by=['salesman','product']).first()
MultiIndex
多了几个标签作为行索引而已,跟普通的Index没什么的,只是索引方式有那么一点点的小不同,看下去就知道。
# agg不知道是什么不要紧,下面会讲df_multi =df.groupby(by=['salesman','product']).agg(vol_sum=('volume','sum'),vol_max=('volume','max'))df_multi
vol_sum vol_maxsalesman product A product2 45 45 product3 100 63B product1 85 50 product2 26 26 product3 22 22C product2 16 16 product3 79 49
还记得loc吗!标签索引,忘记了回头看一下第一篇。
# 最基本的索引其实就是加上括号,把元组作为索引条件df_multi.loc[('B','product3')]# 这样返回的是Series
vol_sum 32vol_max 32Name: (B, product3), dtype: int64
df_multi.loc[[('B','product3')]]# 这样返回的是DataFrame
vol_sum vol_maxsalesman product B product3 32 32
# 切片,一样的df_multi.loc[('B','product3'):('C','product3'),'vol_max'] # 这样返回的是Series
salesman product B product3 32C product2 96 product3 72Name: vol_max, dtype: int64
df_multi.loc[('B','product3'):('C','product3'),['vol_max']] # 这样返回的是DataFrame
vol_maxsalesman product B product3 32C product2 96 product3 72
MultiIndex DataFrame怎么变为一维表?
- 分组时加入as_index=False使返回的DataFrame直接变为一维表
- 或使用reset_index()将含有MultiIndex的DataFrame变为一维表
df.groupby(by=['salesman','product'],as_index=False).sum()df.groupby(by=['salesman','product']).sum().reset_index()
出来的效果都是这样
salesman product volume0 A product2 451 A product3 1002 B product1 853 B product2 264 B product3 225 C product2 166 C product3 79
MultiIndex DataFrame怎么变成二维表?
- unstack()
df.groupby(by=['salesman','product']).sum().unstack()
volumeproduct product1 product2 product3salesman A NaN 45.0 100.0B 85.0 26.0 22.0C NaN 16.0 79.0
DataFrameGroupBy可以用的统计函数
Function | Description |
---|---|
mean() | 平均值 |
sum() | 求和 |
size() | 返回group的大小 |
count() | 计数 |
std() | 标准差 |
var() | 方差 |
sem() | 标准误 |
describe() | 超级方便的数据表述函数,包括了很多常见统计量 |
first() | 组中的第一个值 |
last() | 组中的最后一个值 |
nth() | 取n个值 |
min() | 最小值 |
max() | 最大值 |
如果确定要对某一列进行聚合运算,强烈建议先指出该列再使用聚合函数,可以提高效率。
df.groupby(by='salesman').describe()
volume count mean std min 25% 50% 75% maxsalesman A 1.0 3.000000 NaN 3.0 3.00 3.0 3.0 3.0B 6.0 46.166667 28.596620 2.0 32.75 47.5 66.0 80.0C 3.0 77.666667 16.258331 65.0 68.50 72.0 84.0 96.0
累计函数
累计函数,相信大家看一下案例就能理解了!
方法名 | 函数功能 |
---|---|
cumsum() | 依次给出前1、2、… 、n个数的和 |
cumprod() | 依次给出前1、2、… 、n个数的积 |
cummax() | 依次给出前1、2、… 、n个数的最大值 |
cummin() | 依次给出前1、2、… 、n个数的最小值 |
d = { 'salesperson': [ 'Nico', 'Carlos', 'Juan', 'Nico', 'Nico', 'Juan', 'Maria', 'Carlos', ], 'item': ['Car', 'Truck', 'Car', 'Truck', 'cAr', 'Car', 'Truck', 'Moto'],}df = pd.DataFrame(d)df
salesperson item0 Nico Car1 Carlos Truck2 Juan Car3 Nico Truck4 Nico cAr5 Juan Car6 Maria Truck7 Carlos Moto
df['count_by_person'] = df.groupby(by='salesperson').cumcount() + 1df['count_by_item'] = df.groupby(by='item').cumcount() + 1df['count_by_both'] = df.groupby(by=['salesperson', 'item']).cumcount() + 1df
agg、transfrom、filter
agg
agg()是aggregate()的别称,就像isna()是isnull()的别称一样。
agg()主要用在DataFrameGroupBy上,当然也可以用在DataFrame上,是用来在多列上计算不同函数的。
# 用法一# 直接使用列表列出所要使用的统计函数,具体可用函数已经在上面表中列出# 可以用字符串形式表示函数,也可以直接打函数名不加引号,如果报错就打np.函数名(毕竟pandas是基于numpy)df.groupby(by='salesman')['volume'].agg(['sum','max'])
sum maxsalesman A 145 63B 133 50C 95 49
# 用法二# 可以自定义列名,格式是agg(name=(column,func))df.groupby(by='salesman').agg(vol_max=('volume','max'),vol_sum=('volume','sum'))
vol_max vol_sumsalesman A 63 145B 50 133C 49 95
# 方法三# 您细细品一下效果df.groupby(by='salesman').agg({'volume':['max','mean'],'date':['min','max']})
volume date max mean min maxsalesman A 63 48.333333 2020-01-04 2020-01-09B 50 33.250000 2020-01-03 2020-01-10C 49 31.666667 2020-01-01 2020-01-08
当然,agg()也可以自定义函数。
# 每个人的销量乘以100再求和df.groupby(by='salesman').agg({"volume":lambda x:sum([i*100 for i in x])})
volumesales man A 14500B 13300C 9500
agg也是可以用在DataFrame或Series上,但个人感觉没样啥用,还不如直接用apply。
df[['volume']].agg(['max','min'])
volumemax 63min 16
transform
转换,有时候还是有点用的,我们看一下实例。
# 我们重新创建一个DataFrame,适用于transform和filter的df_temp = pd.DataFrame([['2020/1/1', 'A', 100], ['2020/2/1', 'A', 50], ['2020/3/1', 'A', np.nan], ['2020/1/1', 'B', 1000], ['2020/2/1', 'B', 3000], ['2020/3/1', 'B', 5000]], columns=['date', 'salesman', 'volume'])df_temp
date salesman volume0 2020/1/1 A 100.01 2020/2/1 A 50.02 2020/3/1 A NaN3 2020/1/1 B 1000.04 2020/2/1 B 3000.05 2020/3/1 B 5000.0
我们可以看到A业务员2020/3/1的销量为np.nan,没有记录,如果我们需要对数据进行清洗该怎么办呢?
直接删除吗,这不是最佳的选项。如果要填充,直接用volume整列的平均值?那这样对B业务员是不是很吃亏?(只是举例使用,真实情况下我们要和数据提供方确认数据质量)
所以我们可以尝试用A的均值来填充她的缺失值。
df_temp.volume = df_temp.groupby(by='salesman')['volume'].transform(lambda x:x.fillna(x.mean()))df_temp
date salesman volume0 2020/1/1 A 100.01 2020/2/1 A 50.02 2020/3/1 A 75.03 2020/1/1 B 1000.04 2020/2/1 B 3000.05 2020/3/1 B 5000.0
filter
看名字就知道是过滤,可以自定义函数。
def filter_loser(x): """ describe:筛选掉总销售量小于300的业务员 return """ return x['volume'].sum()>=300
df_temp.groupby(by='salesman').filter(filter_loser)
date salesman volume3 2020/1/1 B 1000.04 2020/2/1 B 3000.05 2020/3/1 B 5000.0
DataFrameGroupBy是可以使用apply()的,用法如之前所述一致。
往期回顾
一分钟一个Pandas小技巧(一)
臭不要脸环节
听说关注这个公众号的人都...都...都怎么样你自己说!
?分享、点赞、在看,给个三连呗!?
pandas apply lambda_一分钟一个Pandas小技巧(二)相关推荐
- pandas apply lambda_数据分析必备!Pandas实用手册(PART III)
这一系列的对应代码,大家可以在我共享的colab上把玩, ? https://colab.research.google.com/drive/1WhKCNkx6VnX1TS8uarTICIK2ViPz ...
- 3分钟学会python_3分钟学会一个Python小技巧
Python时间日期转换在开发中是非常高频的一个操作,你经常会遇到需要将字符串转换成 datetime 或者是反过来将 datetime 转换成字符串. datetime 分别提供了两个方法 strp ...
- 程序员的反击!每天一个离职小技巧
作者 | 梦想橡皮擦 来源 | 非本科程序员(ID:htmlhttp) 写在前面 俗话说的好,代码写的少,离职少不了. 最近畅游互联网,发现一些离职小技巧,读后,内心被深深的打动了,但是细细的品过之后 ...
- vob转mp4,每天一个实用小技巧
vob转mp4,vob的英文全称是Video Object,它是DVD视频媒体使用的容器格式,vob格式擅长将数字视频.音频.字幕.菜单等多个元素复用在流格式中.而且vob格式的文件可以被加密保护.经 ...
- 每天一个前端小技巧——生成gif动图下载
每天一个前端小技巧--生成gif动图下载 动态热图的展现,分别展现某个时间段的热图时间变化,例如:最近一周七天内,每天的热图分布变化图:这个动态变化的图生成一个gif图提供下载是否可行? 实现方案: ...
- 每天一个脱发小技巧 | Eclipse环境下spotbugs的安装配置和详细使用方法
每天一个脱发小技巧 | Eclipse环境下spotbugs的安装配置和详细使用方法 SpotBugs介绍 Eclipse环境下SpotBugs安装 SpotBugs的使用 其他 SpotBugs介绍 ...
- linux pandas教程_十分钟入门 Pandas
# 十分钟入门 Pandas 本节是帮助 Pandas 新手快速上手的简介.烹饪指南里介绍了更多实用案例. 本节以下列方式导入 Pandas 与 NumPy: In [1]: import numpy ...
- python unique函数_每30秒学会一个Python小技巧,GitHub星数4600+
作者 | xiaoyu,数据爱好者来源 | Python数据科学(ID:PyDataScience)很多学习Python的朋友在项目实战中会遇到不少功能实现上的问题,有些问题并不是很难的问题,或者已经 ...
- word关闭未响应_写了两小时的文档没保存?!一个word小技巧来拯救
不知道大家有没有遇到过这样的情况: 写了好久的word文档没来的及保存突然崩溃了 刚保存好的文档不小心被删除.被覆盖 老Y相信不管是哪种情况,一旦遇上想死的心都有了. 其实word中有一个小技巧,可以 ...
最新文章
- 10行代码实现目标检测
- 测试服务器最大链接数_JMeter压力测试集合点教程
- 十个不可不看的Matlab GUI
- mysql 监控 开源_强大的开源企业级数据库监控利器Lepus
- MySQL数据库MyISAM和InnoDB存储引擎的比较
- redis 学习笔记(6)-cluster集群搭建
- CAD迷你画图 for mac
- photoshop cs3 无法复制文字的解决方案
- xshell网站打不开
- C#语言实例源码系列-加密解密RAR文件
- 基于java+springboot+mysql的中小型超市进销存管理系统
- 我的世界学园都市java_我的世界学园都市地图整合包
- ionic start myApp 报错
- 如何用计算机学唱歌,男孩学唱歌教程 男生如何练习唱歌?
- linux图形加速驱动下载,Linux 安装emby 并开启nvidia nvenc 硬件加速转码
- 二字动词 复盘赋能_落地、赋能、共创、共建、复盘,互联网圈的漂亮词儿,你懂几个?...
- 30套最实用JAVA学习视频教程合集
- 上网部署(锐捷交换机)
- unity 输入框弹出输入法_国产输入法那么多,我为什么选择了「不接地气」的 Gboard?...
- 汇编语言知识点总结之五:第五章《[bx]和loop指令》