4. 优雅的apply

引言

本文主要讲的是Pandas中第二好用的函数——apply。为什么说第二好用呢?做人嘛,最重要的就是谦虚,做函数也是一样的,而apply就是这样一个优雅而谦虚的函数。

我们单独用一篇来为apply树碑立传,原因有二,

  1. 一是因为apply函数极其灵活高效,甚至是重新定义了pandas的灵活,一旦熟练运用,在数据清洗和分析界可谓是“屠龙在手,天下我有”
  2. 二是apply概念相对晦涩,需要结合具体案例去咀嚼和实践。

Apply初体验

apply函数,因为她总是和分组函数一起出现,所以在江湖得了个·groupby伴侣·的称号。她的主要作用是做聚合运算,以及在分组基础上根据实际情况来自定义一些规则,常见用法和参数如下:

df.groupby(['列名']).apply(func,args)
  1. 参数func是最重要参数,它接受一个函数,会把分组后的数据根据函数来进行处理。
  2. args参数是我们可以给函数传入的参数,这个仅了解,用到的比较少。

如果把源数据比作面粉,groupby分组就是把面粉揉成一个个面团的过程,apply起到的作用,是根据数据需求来调馅并且把每一个面团包成我们喜欢的包子。接下来,我们通过两个场景,更深入的感受下apply函数的优雅迷人。

场景一

背景:我们拿到了一份4位同学三次模拟考试的成绩,想知道每位同学历次模拟中最好成绩最差成绩分别是多少。

思路:最好和最差,分别对应着max与min,我们先按姓名分组,再用apply函数返回对应的最大和最小值,最终将结果合并。
先导入源数据:
在这里插入图片描述
看一看每位同学最高成绩:

max_score = score.groupby('姓名')['综合成绩'].apply(max).reset_index()
max_score

在这里插入图片描述
我们指定综合成绩列,然后把max函数直接传入apply参数内,返回了对应分组内成绩的最大值。有一些常见函数,如max、min、len等函数可以直接传入apply

groupby分组默认会把分组依据列(姓名)变成索引,这里用reset_index方法重置或者说取消姓名索引,将它保留在列的位置,维持DataFrame格式,方便后续匹配。

再筛选出最低成绩:

min_score = score.groupby('姓名')['综合成绩'].apply(min).reset_index()
min_score 

在这里插入图片描述
两张表按姓名合并:

score_combine = pd.merge(max_score,min_score,left_on='姓名',right_on='姓名',how='inner')
score_combine.columns=['姓名', '最好成绩', '最差成绩']
score_combine

在这里插入图片描述
得到了我们预期的结果,只是列名略丑,可以用.columns方法来赋值更改。场景一比较死板和严肃,场景二我们换个更接地气的风格。

场景二

背景:Boss丢过来一份省市销售表,里面包含省份、城市、最近1个月销售额3个字段,想看看每个省份销售排名第3的都是哪些城市,以及他们的销售额情况。

思路:问题的关键是找到每个省份销售排名第3的城市,首先,应该对省份、城市按销售额进行降序排列,然后,找到对应排名第3的城市,Emmm,如果是排名第1的城市,我们可以通过排序后去重实现,
在这里插入图片描述
在这里插入图片描述
数据源有省份、城市、近1月销售额3个字段,一共210行(销售额)乱序排列,且都没有空值,整体比较规整。

要得到销售排名第3的城市,要先进行排序,这里我们用省份、近1月销售额两个关键字段进行降序排列,得到我们期待的顺序:

在这里插入图片描述
接着,在apply函数登场前,我们先详细剖析一下整个过程:
在这里插入图片描述
apply的精髓,在于揉面DIY(调馅)包子。我们需要把源数据(面粉)给揉成一个个面团,再把一个个面团DIY成我们想要口味的包子。其中,揉面的过程就是groupby分组,而DIY调馅做包子就是apply自定义函数和应用的过程。

结合我们的目标,揉面是按省份进行分组,得到每个省各个城市和对应销售额的面团;DIY包子是在每个面团中取其第三名的城市和销售额字段。

第一步分组非常简单,按省份分组即可。而取第3名的城市和销售,表明我们需要城市和销售两个字段,所以在分组后指明这两列:在这里插入图片描述
这一步,我们已经揉好了面,原始的面团也初步成型,虽然返回的结果有点晦涩,但是我们可以在脑海中构建一下这些面团,截图只展示了部分:

在这里插入图片描述
要把这些面团包成包子,就是要我们取出每一个面团中,排名第3的城市。有个问题需要注意,有一些直辖市是和省并列的,而作为城市只有单独的一行,这样的城市我们就默认返回其本身的数据;对于非直辖市省份来说,就需要定位筛选。

拿x2来举例,要找到这个面团中排名第三的城市和销售额,应该怎么做呢?答案是直接索引,把他看作是一个DataFrame格式的表,要选取第3行的所有值,包括城市和销售额,这里用iloc索引,很简单的一行代码:

x2.icol[2,:]
def get_third(x):
    # 如果分组长度<=1 说明是直辖市
    if len(x) <=1:
        return x.iloc[0,:]
        # 返回第0行所有值 也就是直辖市本身
    else:
        return x.iloc[2,:]

在这里插入图片描述

至此,每个省份,销售额排名第三的城市已经成功筛选出来。回顾整个操作流程,先排序后分组,最后通过定义函数传入apply,提取出我们的目标值。分组后数据的抽象形态,以及如何判断和取出我们需要的值,是解决问题的关键和难点。

数据

成绩表

姓名科目综合成绩
李华一模651
李华二模579
李华三模580
王雷一模475
王雷二模455
王雷三模432
张建国一模691
张建国二模582
张建国三模553
李子明一模490
李子明二模552
李子明三模577

省市销售数据

省份城市近1月销售额
重庆重庆市255343
浙江省金华市302624
浙江省台州市147853
浙江省舟山市136547
浙江省杭州市109073
浙江省宁波市67409
浙江省嘉兴市63883
浙江省衢州市34773
浙江省丽水市31285
浙江省绍兴市22184
浙江省湖州市20867
浙江省温州市15099
云南省临沧市666949
云南省大理白族自治州554325
云南省昆明市203210
云南省保山市164794
云南省昭通市123129
云南省德宏傣族景颇族自治州84815
云南省红河哈尼族彝族自治州31244
云南省玉溪市29546
云南省西双版纳傣族自治州16479
天津天津市510720
四川省南充市532493
四川省眉山市460836
四川省达州市427285
四川省德阳市392361
四川省成都市262325
四川省自贡市179184
四川省巴中市65975
四川省雅安市52585
四川省内江市30951
四川省乐山市24635
四川省凉山彝族自治州22985
四川省广元市21433
四川省绵阳市20530
四川省广安市5206
上海上海市139261
陕西省西安市450490
陕西省延安市120161
陕西省安康市60456
陕西省汉中市59391
陕西省咸阳市47411
陕西省榆林市36144
陕西省宝鸡市29488
山西省大同市354828
山西省晋中市252932
山西省临汾市186931
山西省运城市153765
山西省朔州市91025
山西省晋城市65112
山西省太原市49023
山西省忻州市36324
山西省吕梁市27027
山西省阳泉市23328
山西省长治市10753
山东省济南市355393
山东省济宁市329062
山东省菏泽市187375
山东省潍坊市136845
山东省临沂市83493
山东省日照市69346
山东省泰安市64255
山东省滨州市49878
山东省淄博市44215
山东省威海市41281
山东省东营市32000
山东省青岛市30497
山东省烟台市21219
山东省聊城市17802
山东省枣庄市1796
内蒙古自治区鄂尔多斯市651177
内蒙古自治区包头市289065
内蒙古自治区兴安盟258106
内蒙古自治区巴彦淖尔市141360
内蒙古自治区通辽市66463
内蒙古自治区呼伦贝尔市62450
内蒙古自治区乌海市53515
内蒙古自治区赤峰市26836
内蒙古自治区呼和浩特市16708
辽宁省鞍山市917327
辽宁省本溪市575838
辽宁省葫芦岛市392363
辽宁省盘锦市306124
辽宁省沈阳市202724
辽宁省抚顺市119776
辽宁省大连市115483
辽宁省铁岭市75596
辽宁省锦州市53737
辽宁省朝阳市44664
辽宁省营口市38969
辽宁省辽阳市20555
辽宁省丹东市18011
江西省吉安市587941
江西省抚州市207687
江西省南昌市141011
江西省赣州市126004
江西省上饶市100209
江西省萍乡市92271
江西省九江市89530
江西省景德镇市30243
江西省宜春市8674
江苏省宿迁市291918
江苏省无锡市274878
江苏省镇江市139625
江苏省常州市124904
江苏省南京市96092
江苏省淮安市50116
江苏省南通市45943
江苏省扬州市40711
江苏省徐州市31636
江苏省连云港市29047
江苏省苏州市21753
江苏省盐城市20877
江苏省泰州市17473
湖南省张家界市203990
湖南省永州市177242
湖南省长沙市163263
湖南省衡阳市143484
湖南省怀化市119420
湖南省株洲市89771
湖南省常德市78640
湖南省益阳市35275
湖南省娄底市25018
湖南省湘潭市19964
湖南省岳阳市16941
湖南省郴州市16597
湖南省邵阳市7408
湖北省潜江市839482
湖北省黄石市788623
湖北省襄阳市194910
湖北省孝感市128893
湖北省荆门市114273
湖北省鄂州市105010
湖北省黄冈市100447
湖北省随州市70228
湖北省宜昌市43783
湖北省十堰市29798
湖北省武汉市18516
湖北省荆州市13307
河南省信阳市303765
河南省郑州市190843
河南省焦作市151812
河南省周口市149732
河南省济源市119826
河南省鹤壁市104734
河南省三门峡市86982
河南省开封市74053
河南省新乡市52236
河南省南阳市30565
河南省驻马店市29491
河南省濮阳市26306
河南省漯河市23355
河南省许昌市20666
河南省平顶山市17923
河南省洛阳市11709
河南省商丘市6414
河北省廊坊市161929
河北省沧州市140578
河北省邯郸市127017
河北省邢台市119624
河北省秦皇岛市95955
河北省保定市69588
河北省承德市54630
河北省石家庄市38257
河北省张家口市27311
河北省唐山市24888
河北省衡水市11067
广东省广州市745366
广东省佛山市405419
广东省肇庆市321218
广东省梅州市148140
广东省惠州市137476
广东省阳江市95172
广东省清远市68248
广东省潮州市34674
广东省揭阳市26389
广东省韶关市19129
广东省深圳市16847
广东省汕头市11792
广东省中山市11343
广东省珠海市4324
广东省东莞市1866
甘肃省金昌市474105
甘肃省陇南市163465
甘肃省天水市119378
甘肃省庆阳市85120
甘肃省平凉市20565
甘肃省兰州市18208
甘肃省张掖市17878
甘肃省酒泉市14233
福建省厦门市495921
福建省福州市476299
福建省莆田市209084
福建省南平市141303
福建省漳州市138974
福建省泉州市74809
福建省三明市34839
福建省宁德市21116
北京北京市154682
安徽省宿州市657589
安徽省安庆市596127
安徽省合肥市179518
安徽省淮北市107184
安徽省阜阳市71129
安徽省淮南市69522
安徽省亳州市61254
安徽省滁州市30004
安徽省宣城市24571
安徽省蚌埠市16023
安徽省马鞍山市15212
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SoWhat1412

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值