前言 最近有两位小伙伴跟我说,网上看到一篇文章说,在python中使用pandas连接两个表,别用merge,要使用join,因为在大量数据的情况下join比merge要快4到5倍。 其实这说法我一听就知道是错误的。不过当时没有具体证据支持,所以我也没有下具体结论。 今天,我就从源码的角度,给大家一个参考依据。 当然,本文你还会学到一些代码调试技巧,还会看到一些pandas的优化手段。 join比merge快很多? 那篇文章中的测试大概如下:importpandasaspdimportnumpyasnpfromtimeimporttimehigh1000rowslist〔(i1)1000000foriinrange(10)〕ncolumns4repeat5defcreatedf(nrows,ncolumns,colnames):datanp。random。randint(lowhigh,highhigh,size(nrows,ncolumns))indexcolnp。arange(0,nrows)np。random。shuffle(indexcol)datapd。DataFrame(data,columnscolnames,dtypenp。int16)data〔idx〕indexcolreturndatafornrowsin〔10000000〕:sumtimemerge10sumtimemerge20forinrange(repeat):df1createdf(nrows,ncolumns,〔fcol{i}foriinrange(ncolumns)〕)df2createdf(nrows,ncolumns,〔fCol{i}foriinrange(ncolumns)〕)mergestarttime()dfpd。merge(df1,df2,howleft,leftoncol0,rightonCol0)sumtimemerge1time()startjoinstarttime()df1。setindex(idx,inplaceTrue)df2。setindex(idx,inplaceTrue)dfdf1。join(df2)sumtimemerge2time()startresult。append(〔df1。shape〔0〕,sumtimemerge1repeat,sumtimemerge2repeat〕)print(pd。DataFrame(result,columns〔行数,merge耗时(秒),join耗时(秒)〕))跑一千万数据,5次,取个平均使用df。join有个前提,把2个表的关联key的列设置为行索引merge则使用普通的列作为关联key我这里生成的key是唯一的。足以复现原文的效果 看看结果: 嗯?还真快了这么多! 但是为什么我一开始听到这说法,不用做任何的实验,就觉得这观点有问题? 其实道理很简单。 假如今天你实现了一个功能函数: 功能很简单,把一个列表中的数值,先转成正数,然后求和 明天,你需要实现另一个功能很接近的函数,只不过输入的不是列表,而是2个具体的数值。显然你会想着调用之前的函数: 同样道理,join函数明显是merge函数的一个特例。pandas的设计者不会傻到用两套不一样的方式实现它们。 但是,别人给出来的实验结果确确实实反应了它们的差异。 接下来,我们就看看它们实现的源码。源码找答案 首先,新建一个python文件,把代码设置得简单一些。 打开调试窗口,点击创建python的调试配置。 这里最重要的是设置justMyCode为false。这样子我们才能进入pandas源码里面 接着,在merge函数那一行打开一个断点 执行调试 代码会停在断点的行,接着我们要点击控制菜单中的下一步(也可以用快捷键)。 可以看到,merge函数实际调用的是pandas。core。reshape。merge。merge,暂时不深入 如果你看过我之前关于类定义的文章,那么不用看里面的实现也知道,这里只不过实例化了一个对象,记录了一些相关数据而已,重要的是下方的getresult函数 同样道理,调试join函数 咦?它的实现与merge不一样?别急,继续执行,直到 进入一看,又跳回到之前merge函数的实现 从左侧的调用堆栈中可以看到调用顺序: 1是join调用2是joincompat3就是上图右边的代码 你可以点击调用堆栈中的一行,代码会跳回去,就连当时执行中的所有变量的值都可以查看 简单列一下大概的调用图: join函数绕了一圈才到真正执行的地方 所以现在我们知道,join函数其实比merge函数执行更多的代码。 但是,之前的实验数据很好地说明了join比merge快呀,为什么?不公平的对比 按调试流程,我们进入之前看到的op。getresult函数里面: 进入这个self。getjoininfo()里面: 可以看到许多关于leftindex和rightindex参数的判断。但是我们使用merge的时候根本没有设置这两个参数,它们都是False。 结果就会进入这段代码: 这是一个python的遍历代码,一个个去匹配key值 而join函数执行的却是: 直接调用行索引对象的函数 了解这些要点,相信聪明的你也知道要这样子修改实验代码: 把设置行索引的代码移除两个函数执行的范围外merge设置参数leftindex与rightindex 但是,结果却出乎意料!! 对比一下之前的时间: 解释一下差异:join的耗时短了很多,因为现在它没有设置行索引的操作merge耗时也短了很多,因为现在它内部用了行索引 但是,为什么merge耗时仍然比join要慢很多?pandas的优化 此时,我们把实验代码中执行merge和join的先后顺序调换一下: 注意,记录时间的变量的对应关系没有变,所以这不会影响结果表格的左右顺序 看看结果: 现在,结论截然相反! 为什么?显然,有什么东西在第二次运行的时候,得到了优化。 在之前的源码调试中,我们得知,其实两个表按行索引关联,最核心的计算就是行索引对象的join函数。 按这个原理以及之前的调试方式,可以找到一个属性。具体过程我就不再啰嗦了,直接给出验证结果: 在join的过程中,有一个判断逻辑,如果行索引的值都是唯一的,那么会进行一些操作。 直接看看它的源码 缓存了结果。 道理很简单,pandas怎么可以知道一个行索引的值是否唯一?显然要遍历一次数据。这个过程在大量数据的时候成本很高。由于索引对象是不可变的,所以可以缓存结果。 那么,现在我们修正一下测试实验的代码,让它公平对待: 现在的结果是: 很多小伙伴问我怎么学习pandas。正如我专栏里面的思路,集中学习少数核心常用的函数和原理,你的学习之路才能事半功倍。 不要忘记一键三连。你的点赞、收藏、关注,是我创作的动力。 推荐文章:python为什么需要函数、类这些概念懂Excel轻松入门Python数据分析pandas(18):pandas中的vlookuppandas每天一题题目19:炸列操作的多种方式pandas新版本增强功能,数据表多列频率统计