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

猜你喜欢

转载自blog.csdn.net/qq_31821675/article/details/107166485