当前位置: 首页 > 科技观察

懒人秘籍:教你如何避免编写Pandas代码

时间:2023-03-16 20:19:14 科技观察

Pandas在数据科学领域无需介绍,它提供了高性能、易用的数据结构和数据分析工具。然而,当处理过多的数据时,单核上的Pandas似乎已经绰绰有余,大家不得不求助于不同的分布式系统来提升性能。然而,提高性能的权衡通常伴随着陡峭的学习曲线。而每个人都在尽可能的避开这种悬崖。可以想象,每个人都转向了如何避免编写pandas代码。在过去的4年里,作者一直使用pandas作为数据分析的主要工具。诚然,大部分“如何避免编写pandas代码”都来自于使用pandas编程的初期阶段。在做代码审查时,我仍然看到很多有经验的程序员在看一些流行的“如何避免”的帖子。在这篇文章中,作者首先展示了一个“如何避免”的例子,然后展示了一个正确的“如何使用”pandas来计算统计数据。改进后的代码更加简洁易读,执行速度更快。报告时间的格式为:831ms±25.7msperloop,这意味着平均值为831ms,标准偏差为25.7ms。每个代码示例执行多次以计算准确的执行时间。与往常一样,您可以下载JupyterNotebook并在您的计算机上试用。要开始你的pandas之旅,请阅读这些资源:5个鲜为人知的pandas技巧,用于使用pandas进行探索性数据分析来源:Pexelssettingsfromplatformimportpython_versionimportnumpyasnpimportpandasaspdnp.random.seed(42)#settheseedtomakeexamplesrepeatable样本数据集样本数据集包含城市预订信息,是随机的唯一目的是展示样品。数据集有三列:id代表唯一标识city代表预订的预定城市信息perc代表在特定时间预订的百分比数据集有10000条条目,这使得速度提升更加明显。请注意,如果代码以正确的pandas方式编写,pandas可以利用DataFrame计算数百万(甚至数十亿)行的统计信息。size=10000cities=["paris","barcelona","berlin","newyork"]df=pd.DataFrame({"city":np.random.choice(cities,sizesize=size),"booked_perc":np.random.rand(size)})df["id"]=df.index.map(str)+"-"+df.citydfdf=df[["id","city","booked_perc"]]df.head()1。如何避免求和和翻转数据Pandas/Reddit受Java世界的启发,将“多行for循环”应用于Python。计算bookedperccolumn的总和,百分比相加是没有意义的,不过不管怎样,还是一起来试试吧,实践一下这个道理。%%timeitsuma=0for_,rowindf.iterrows():suma+=row.booked_perc766ms±20.9msperloop(mean±std.dev.of7runs,1loopeach)一个更加Python化的列求和方式如下:%%timeitsum(booked_percforbooked_percindf.booked_perc)989μs±18.5μsperloop(mean±std.dev.of7runs,1000loopseach)%%timeitdf.booked_perc.sum()92μs±2.21μsperloop(mean±std.dev.of7runs,10000loopseach)正如预期的那样,第一个示例是最慢的-采取几乎1秒来总结10,000个项目。第二个例子出奇地快。正确的方法是使用pandas对数据求和(或对列使用任何其他操作),这是第三个示例-也是最快的!2.如何避免过滤数据玩玩pandas/Giphy虽然在使用pandas之前,作者已经熟悉numpy并使用for循环过滤数据。求和时,仍然可以观察到性能差异。%%timeitsuma=0for_,rowindf.iterrows():ifrow.booked_perc<=0.5:suma+=row.booked_perc831ms±25.7msperloop(mean±std.dev.of7runs,1loopeach)%%timeitdf[df.booked_perc<=0.5]。booked_perc.sum()724μs±18.8μsperloop(mean±std.dev.of7runs,1000loopseach)正如预期的那样,第二个示例比第一个示例快得多如果添加更多过滤器会怎样?只需将它们添加到括号中:%%timeitdf[(df.booked_perc<=0.5)&(df.city=='newyork')].booked_perc.sum()1.55ms±10.7μsperloop(mean±std.dev.of7runs,1000loopseach)3.如何避免访问以前的值RollingPanda/Giphy你可能会说:好吧,但是如果你需要访问某列的以前的值,你还是需要一个for循环。您错了!使用和不使用for循环计算从一行到另一行的百分比变化%%timeitforiinrange(1,len(df)):df.loc[i,"perc_change"]=(df.loc[i].booked_perc-df.loc[i-1].booked_perc)/df.loc[i-1].booked_perc7.02s±24.4msperloop(mean±std.dev.of7runs,1loopeach)%%timeitdf["perc_change"]=df.booked_perc。pct_change()586μs±17.3μsperloop(mean±std.dev.of7runs,1000loopseach)同样,第二个示例比使用for循环的第一个示例快得多。pandas有很多函数可以根据以前的值计算统计数据(例如shift函数移动值)。这些函数接受一个周期参数,可以将之前值的个数计入计算。4.如何避免使用复杂函数来源:FallingPandas(NationalGeographic)Giphy有时需要在DataFrame中使用复杂函数(具有多个变量的函数)。让我们将纽约的booking_perc乘以2,将其他值设置为0并将列命名为sales_factor。我首先想到的是使用iterrows的for循环。%%timeitfori,rowindf.iterrows():ifrow.city=='newyork':df.loc[i,'sales_factor']=row.booked_perc*2else:df.loc[i,'sales_factor']=03.58s±48.2msperloop(mean±std.dev.of7runs,1loopeach)更好的方法是直接在DataFrame上使用该函数。%%timeitdefcalculate_sales_factor(row):ifrow.city=='newyork':returnrow.booked_perc*2return0df['sales_factor']=df.apply(calculate_sales_factor,axis=1)165ms±2.48msperloop(mean±std.dev.of7runs,10loopseach)最快的方法是使用pandasfilter直接计算函数值。%%timeitdf.loc[df.city=='newyork','sales_factor']=df[df.city=='newyork'].booked_perc*2df.sales_factor.fillna(0,inplace=True)3.03ms±85.5μsperloop(mean±std.dev.of7runs,100loopseach)可以看到从第一个例子到最后一个例子的加速比。在求解具有3个或更多变量的函数时,可以将其分解为多个pandas表达式。这比使用函数更快。例如:f(x,a,b)=(a+b)*xdf['a_plus_b']=df['a']+df['b']df['f']=df['a_plus_b']*df['x']5.如何避免分组数据ScratchPandas/Giphy现在你可以看到,在我开始使用pandas之前,我更多地依赖于循环。至于分组数据,如果充分发挥pandas的优势,可以减少代码行数。计算以下数据:一个城市的平均销售系数一个城市的首次预订id%%timeitavg_by_city={}count_by_city={}first_booking_by_city={}fori,rowindf.iterrows():city=row.cityifcityinavg_by_city:avg_by_city[city]+=row.sales_factorcount_by_city[city]+=1else:avg_by_city[city]=row.sales_factorcount_by_city[city]=1first_booking_by_city[city]=row['id']forcity,_inavg_by_city.items():avg_by_city[城市]/=count_by_city[city]878ms±21.4msperloop(mean±std.dev.of7runs,1loopeach)Pandas有分组操作,所以不需要在DataFrame上迭代。pandas的分组操作和SQL的GROUPBY语句是一样的。%%timeitdf.groupby('city').sales_factor.mean()df.groupby('city').sales_factor.count()df.groupby('city').id.first()3.05ms±65.3μsperloop(mean±std.dev.of7runs,100loopseach)%%timeitdf.groupby("city").agg({"sales_factor":["mean","count"],"id":"first"})4.5ms±131μsperloop(mean±std.dev.of7runs,100loopseach)令人惊讶的是,第三个例子并不是最快的,但它比第二个例子更简洁。作者建议,如果需要加速代码,请使用第二种方法。HappyPandas/Giphy最后,小新的建议是:如果需要用pandas来写for循环,一定要有更好的写法。会有一些计算量很大的函数,甚至上面描述的优化也不起作用。那么我们就需要使用不得已的手段:Cython和Numba。一起来试试这些方法吧,会有意想不到的收获哦~