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

开发 前端 开发工具
在本文中,笔者首先展示了一个“如何避免”的例子,然后展示了一个正确的“如何使用”pandas来计算统计数据的方法。改进后,代码更简洁、易读,执行更快。

Pandas在数据科学领域无需介绍,它提供高性能,易于使用的数据结构和数据分析工具。但是,在处理过多的数据时,单核上的Pandas就显得心有余而力不足了,大家不得不求助于不同的分布式系统来提高性能。然而,提高性能的权衡常常伴随着陡峭的学习曲线。

而大家都在尽可能地避免这种悬崖峭壁,结果可想而知,都转向了如何避免编写pandas代码。

[[311697]]

在过去4年里,笔者一直使用pandas作为数据分析的主要工具。必须承认,“如何避免编写pandas代码”的大部分内容来自于使用pandas编程的起步阶段。在进行代码审阅时,笔者仍然看到许多经验丰富的程序员在看一些热门“如何避免使用”的帖子。

在本文中,笔者首先展示了一个“如何避免”的例子,然后展示了一个正确的“如何使用”pandas来计算统计数据的方法。改进后,代码更简洁、易读,执行更快。报告时间的格式为: 831 ms ± 25.7 ms per loop,即平均831毫秒,标准偏差为25.7毫秒。每个代码示例执行多次,以计算准确的执行时间。

和往常一样,可以下载 JupyterNotebook并在电脑上试运行。

开始pandas游戏之旅,请阅读如下资源:

  • 5个鲜为人知的pandas技巧
  • 使用pandas进行探索性数据分析

[[311698]]

来源:Pexels

设置

  1. from platform importpython_versionimport numpy as np 
  2. import pandas as pdnp.random.seed(42) # set the seed tomake examples repeatable 

样本数据集

样本数据集包含各个城市的预订信息,是随机的,唯一目的是展示样本。

数据集有三列:

  • id表示唯一的标识
  • city表示预定的城市信息
  • booked perc表示特定时间预定的百分比

数据集有一万条,这使速度改进更加明显。注意,如果代码以正确的pandas方式编写,pandas可以利用DataFrames计算数百万(甚至数十亿)行的统计数据。

  1. size = 10000cities =["paris", "barcelona", "berlin", "newyork"]df = pd.DataFrame( 
  2.     {"city": np.random.choice(cities,sizesize=size), "booked_perc": np.random.rand(size)} 
  3. df["id"] = df.index.map(str) +"-" + df.city 
  4. dfdf = df[["id", "city", "booked_perc"]] 
  5. df.head() 

1. 如何避免对数据求和

[[311699]]

翻滚的熊猫/Reddit

来自Java世界的灵感,把“多行for循环”应用到了Python。

计算booked perc列的总和,把百分比加起来毫无意义,但无论如何,一起来试试吧,实践出真知。

  1. %%timeitsuma = 0 
  2. for _, row in df.iterrows(): 
  3.     suma += row.booked_perc766ms ± 20.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 

更符合Python风格的方式来对列求和如下:

  1. %%timeitsum(booked_perc forbooked_perc in df.booked_perc)989 µs ± 18.5 µs per loop (mean ±std. dev. of 7 runs, 1000 loops each)%%timeitdf.booked_perc.sum()92µs ± 2.21 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) 

正如预期的那样,第一个示例是最慢的——对一万项求和几乎需要1秒。第二个例子的速度之快令人惊讶。

正确的方法是使用pandas对数据进行求和(或对列使用任何其他操作),这是第三个示例——也是最快的!

2. 如何避免过滤数据

[[311700]]

玩耍的熊猫/Giphy

尽管在使用pandas之前,笔者已经很熟悉numpy,并使用for循环来过滤数据。求和时,还是可以观察到性能上的差异。

  1. %%timeitsuma = 0 
  2. for _, row in df.iterrows(): 
  3.     if row.booked_perc <=0.5: 
  4.         suma += row.booked_perc831ms ± 25.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)%%timeitdf[df.booked_perc<= 0.5].booked_perc.sum()724 µs ± 18.8 µs per loop(mean ± std. dev. of 7 runs, 1000 loops each) 

正如预期的一样,第二个例子比第一个例子快很多

如果加入更多的过滤器呢?只需把它们添加到括号里:

  1. %%timeitdf[(df.booked_perc <=0.5) & (df.city == 'new york')].booked_perc.sum()1.55ms ± 10.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 

3. 如何避免访问以前的值

[[311701]]

翻滚的熊猫/Giphy

你可能会说:好吧,但是如果需要访问先前某一列的值呢,还是需要一个for循环。你错了!

分别使用和不使用for循环来计算一行到另一行百分数的改变

  1. %%timeitfor i inrange(1, len(df)): 
  2.     df.loc[i,"perc_change"] =  (df.loc[i].booked_perc- df.loc[i - 1].booked_perc) / df.loc[i- 1].booked_perc7.02 s ± 24.4 ms per loop (mean ± std. dev. of 7runs, 1 loop each)%%timeitdf["perc_change"] = df.booked_perc.pct_change()586µs ± 17.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 

同样,第二个例子比第一个使用for循环的例子快得多。

pandas有许多函数可以根据以前的值计算统计数据(例如shift函数对值进行移位)。这些函数接受periods参数,可以在计算中包含以前值的数量。

4. 如何避免使用复杂的函数

[[311702]]

来源:坠落的熊猫(国家地理)Giphy

有时需要在DataFrame中使用复杂函数(有多个变量的函数)。让我们将从纽约的booking_perc两两相乘,其他设置为0并且把这列命名为sales_factor。

笔者首先想到的是使用iterrows的for循环。

  1. %%timeitfor i, row in df.iterrows(): 
  2.     if row.city =='new york': 
  3.         df.loc[i, 'sales_factor'] =row.booked_perc * 2 
  4.     else: 
  5.         df.loc[i, 'sales_factor'] =03.58 s ± 48.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 

一个更好的办法是直接在DataFrame上使用函数。

  1. %%timeitdef calculate_sales_factor(row): 
  2.     if row.city =='new york': 
  3.         return row.booked_perc* 2 
  4.     return 0df['sales_factor'] =df.apply(calculate_sales_factor, axis=1)165 ms ± 2.48 ms per loop(mean ± std. dev. of 7 runs, 10 loops each) 

最快的方法是使用pandas过滤器直接计算函数值。

  1. %%timeit df.loc[df.city== 'new york', 'sales_factor'] = df[df.city == 'newyork'].booked_perc * 2 
  2. df.sales_factor.fillna(0, inplace=True)3.03 ms ± 85.5 µsper loop (mean ± std. dev. of 7 runs, 100 loops each) 

可以看到从第一个例子到最后一个的加速过程。

当解决有3个及3个以上变量的函数时,可以把它分解为多个pandas表达式。这比运用函数更快。

  1. Eg: f(x, a, b) = (a + b) * x 
  2. df['a_plus_b'] = df['a'] +df['b'] 
  3. df['f'] = df['a_plus_b'] * df['x'] 

5. 如何避免对数据进行分组

[[311703]]

蹭痒熊猫/Giphy

现在可以看到,在开始使用pandas之前,笔者更多依赖于for循环。至于对数据进行分组,如果充分发挥pandas的优势,可以减少代码行数。

要计算如下数据:

  • 一个城市的平均sales factor
  • 一个城市的首次预定id
  1. %%timeit avg_by_city = {} 
  2. count_by_city = {} 
  3. first_booking_by_city = {}for i, row in df.iterrows(): 
  4.     city = row.city 
  5.     if city in avg_by_city: 
  6.         avg_by_city[city] += row.sales_factor 
  7.         count_by_city[city] += 1 
  8.     else: 
  9.         avg_by_city[city] = row.sales_factor 
  10.         count_by_city[city] = 1 
  11.         first_booking_by_city[city] =row['id']for city, _ in avg_by_city.items(): 
  12.     avg_by_city[city] /=count_by_city[city]878 ms ± 21.4 ms per loop (mean ± std. dev. of 7 runs, 1 loopeach) 

Pandas有分组操作所以不必在DataFrame上进行迭代,pandas的分组操作和SQL的GROUP BY语句一样的。

  1. %%timeitdf.groupby('city').sales_factor.mean() 
  2. df.groupby('city').sales_factor.count() 
  3. df.groupby('city').id.first()3.05 ms ± 65.3 µs per loop(mean ± std. dev. of 7 runs, 100 loops each)%%timeitdf.groupby("city").agg({"sales_factor":["mean", "count"], "id": "first"})4.5ms ± 131 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) 

惊奇的是,第三个例子不是最快的,但比第二个例子更简洁。笔者建议,如果需要加速的代码,请用第二种方法。

[[311704]]

快乐的熊猫/Giphy

最后,小芯的建议是:如果需要使用pandas编写for循环,那一定存在一种更好的编写方式。

会存在一些计算量很大的函数,即使上述的优化方法也会无效。那么我们就需要使用最后手段:Cython和Numba。

大家一起来试试这些方法吧,一定会有意想不到的收获~

 

责任编辑:赵宁宁 来源: 读芯术
相关推荐

2015-09-21 13:04:01

创业秘籍

2011-07-15 16:20:07

云计算云服务提供商

2010-12-03 11:27:47

IT职场

2023-01-09 15:16:17

2016-04-08 09:24:01

脆弱代码更新

2024-07-09 09:36:17

2011-06-07 15:34:15

2022-06-20 16:18:25

MySQL安全免密码输入

2020-02-28 09:26:54

PythonGo语言C语言

2022-12-15 10:52:26

代码开发

2022-06-27 06:23:23

代码编程

2021-06-08 09:35:11

Cleaner ReaReact开发React代码

2015-01-28 14:30:31

android代码

2012-07-11 10:51:37

编程

2010-02-05 16:49:05

编写Android 代

2012-03-15 13:36:51

云计算JavaSpring框架

2011-07-21 09:33:53

2022-06-07 09:30:35

JavaScript变量名参数

2024-06-24 14:19:48

2010-02-03 13:55:51

Python 代码
点赞
收藏

51CTO技术栈公众号