美文网首页
你在Pandas中使用Apply吗?有一个更快600倍的方法

你在Pandas中使用Apply吗?有一个更快600倍的方法

作者: Python_Camp | 来源:发表于2022-06-21 12:25 被阅读0次

你在Pandas中使用Apply吗?有一个更快600倍的方法
通过利用矢量化和数据类型,你可以大规模地加快Pandas中的复杂计算。
我最近又读了一篇文章,告诉你如何加快潘达斯中的apply函数的速度。这些文章通常会告诉你将apply函数并行化,使其速度提高2到4倍。

Apply: 11.8 seconds
Apply + Swifter: 6.71 seconds
Pandas vectorizatoin: 0.035 seconds
Pandas vectorization + data types: 0.019 seconds

在我向你展示如何让它快600倍之前,让我们先用vanilla apply()来说明一个用例。

让我们想象一下,你有一个pandas数据框架df,想对它进行一些操作。

我将使用一个有100万行和5列的数据框架(整数范围从0到10;我使用的是与本文类似的设置)。

df = pd.DataFrame(np.random.randint(0, 11, size=(1000000, 5)), columns=('a','b','c','d','e'))

def func(a,b,c,d,e):
    if e == 10:
        return c*d
    elif (e < 10) and (e>=5):
        return c+d
    elif e < 5:
        return a+b

我想在'e'的基础上应用一个逻辑判断,根据其他四列产生一个结果。

df['new'] = df.apply(lambda x: func(x['a'], x['b'], x['c'], x['d'], x['e']), axis=1)

我们得到的运行时间约为11.8秒(超过10次运行,最小运行时间为11.7秒)。

用Swifter并行化Pandas应用
你可以通过使用swifter轻松地并行化这个过程。

由于swifter并没有默认安装在anaconda中,你必须先安装它。

conda install -c conda-forge swifter

现在我们可以通过在应用前调用swifter来使用并行化应用

import swifter
df['new'] = df.swifter.apply(lambda x : func(x['a'],x['b'],x['c'],x['d'],x['e']),axis=1)

在MacBook Air上(使用M1 CPU),我得到的平均运行时间为6.71秒(超过10次运行,最小运行时间为6.45秒)。这比我们最初的应用实现快了近一倍。

Parallelization的效果比较呢?
Python中的并行化只能期待轻微的改进(如果有的话)。

Pandas矢量化
使用Pandas和Numpy的最快方法是对你的函数进行矢量化。另一方面,使用for循环、列表理解或apply(),沿着数组或系列逐个元素运行函数是一种不好的做法。

列表理解与for 循环。这不是你想的那样
Stack Overflow上的许多文章、帖子或问题都强调,列表理解比for循环更快。
towardsdatascience.com

让我们来为我们之前的函数创建一个向量实现。正如你所看到的,我正在使用两个掩码来识别相关案例,然后用.loc来更新数值。此外,默认情况下,没有使用任何掩码就被分配了。

df['new'] = df['c'] * df['d'] #default case e = =10
mask = df['e'] < 10
df.loc[mask,'new'] = df['c'] + df['d'] 。
mask = df['e'] < 5
df.loc[mask,'new'] = df['a'] + df['b'']

现在的运行时间是0.035秒(最小运行时间是0.027秒)。与swifter相比,这几乎是200倍的改进
矢量化将为你提供快如闪电的执行速度

你可以通过使用另一个技巧来加快执行速度:
通过使用更有效的数据类型使你的pandas数据帧更轻。

因为我们知道df只包含1到10的整数,所以我们可以将数据类型从64位减少到16位。

for col in ('a', 'b', 'c', 'd'):
    df[col] = df[col].astype(np.int16)

看看我们是如何将数据框架的大小从38MB减少到9.5MB的。显而易见,你的计算机将更容易处理近4倍小的对象。我们的函数的运行时间现在减少到0.019秒左右,这几乎是使用我们的初始数据框架(用np.int64)的两倍。

在现实生活中,你可能没有幸运到拥有一个只有小整数的数据集。尽管如此,你可以尝试通过使用np.float32而不是通常的np.float64或者使用pandas分类来加快你的进程。

通过利用数据类型来减少你的数据框架的大小
你用Python读取Excel文件吗?有一种更快的方法。
在这篇文章中,我将向你展示五种在Python中加载数据的方法。
实现了3个数量级的提速

NumPy矢量化
上面的代码是依靠pandas Series来进行检查和计算的。Pandas的Series是由NumPy数组(用于存储数据)和一些开销信息(如Series的索引和名称)组成。

我们可以通过使用.values直接访问Series "背后 "的NumPy数组,以使我们的矢量化速度稍快。这通常效果很好,除非你需要使用掩码和特定的列--如我们的例子。

为了向你展示numpy矢量化与pandas矢量化的力量,让我们创造另一个用例。

你想计算a、b、c和d列的总和,并将其乘以e。


df = pd.DataFrame(np.random.randint(0, 11, size=(100000000, 5), dtype=np.int16), columns=('a','b','c','d','e'))
我们的新数据框架大约需要900mb


df['new'] = df[['a','b','c','d']].sum(axis=1) * df['e']
在这个100%的pandas执行中,平均运行时间(超过10次试验)为2.92秒(最低为2.87秒)。


df[‘new’] = df[[‘a’,’b’,’c’,’d’]].values.sum(axis=1) * df[‘e’].values
使用.values,运行时间减少到2.65秒(最少为2.62秒),减少了10%。

apply() 11.8秒
apply +Swifter: 6.71秒
pandas矢量化:0.035秒
pandas矢量化+数据类型: 0.019秒

image.png

Pandas >> 如何在Pandas中使用Map() Apply() ApplyMap() 方法
在这篇文章中,我们将讨论如何使用map()、apply()和applymap(),以及何时使用其中一个。这些方法可以在DataFrame列上应用某些处理,并更新值或创建新的列。

三个方法与DataFrame、Series之间的关系
这三个方法与DataFrame、Series之间的关系组织如下。

image.png

如何使用map()方法
pandas.Series.map将根据一些规则或输入对应关系来映射一些系列的值。当传递一个字典或系列元素时,将根据字典或系列的键进行映射。缺少的值将被转换为NaN。
map()对Series来说是按元素排序的。例如,我们可以使用map()方法将分数映射到成绩,如下所示。

image.png

首先,我们准备数据。

import pandas as pd

df = pd.DataFrame({
    "name": ["Kevin", "Jack", "Mary", "Bob", "Robert", "Amy"],
    "score": [66, 75, 80, 93, 77, 81],
    "sex": ["M", "M", "F", "M", "M", "F"],
})
df
image.png

使用dict来对每个人的头衔说你好

例如,如果性别是'M',用Mr.,性别是'F',用Ms.。
我们指定一个dict,在其中定义映射以实现这一目标,如下所示。如果在dict中没有找到某些值,它们将被转换为NaN。

df["say-hello"] = df["sex"].map({"M": "Hello, Mr.", "F": "Hello, Ms."})
df
image.png

使用系列来添加昵称列

我们必须为昵称定义一个系列,并指定名称列作为其索引,然后在map()中使用这个系列。

代码块

使用可调用程序来添加等级列

我们可以在map()方法中使用一个函数或lambda表达式来将一个列映射到另一个列。

nick_name = pd.Series(['K', 'J', 'M', 'B', 'R', 'A'], index=df["name"])
df["nick-name"] = df["name"].map(nick_name)
df
image.png

当然,我们可以使用lambda表达式进行简单的映射。

如何使用apply()方法
pandas.DataFrame.apply可以用于DataFrame。它被用来沿着DataFrame的一个轴应用一个函数(一些转换或处理)。
apply()也可以沿元素方向工作,但更适合于复杂的操作和聚合。你也可以阅读另一篇关于向现有DataFrame添加列的文章。
apply()方法非常适用于对不能被矢量化的列进行处理。

Pandas " 如何向现有的DataFrame添加列
https://thats-it-code.com/pandas/pandas__how-to-add-columns-to-an-existing-dataframe/

例如,我们可以根据姓名和性别列来获取问候信息。

# The function that map score to grade
def get_grade(score):
    # Firstly convert score to float type
    try:
        num = float(score)
        if num < 60.0:
            return "D"
        if num < 80.0:
            return "C"
        if num < 90.0:
            return "B"
        else:
            return "A"
    except ValueError:
        # If converting failed, return empty string
        return ""

df["grade_func"] = df["score"].map(get_grade)
df

使用DataFrame df的apply()方法
可以指定lambda或函数,当使用lambda表达式时,行将是整个行的数据。
当根据多列获取列时,指定 axis=1。

df["greeting-msg"] = df.apply(lambda row: f"Welcome, {'Mr.' if row['sex'] == 'M' else 'Ms.'} {row['name']}", axis=1)

image.png

apply()方法也可以应用于 Series

def check_excellent(score):
    # Firstly convert score to float type
    try:
        num = float(score)
        if num > 90.0:
            return "Excellent"
        else:
            return ""
    except ValueError:
        # If converting failed, return empty string
        return ""

df["excellent"] = df["score"].apply(check_excellent)
df
image.png

如何使用applymap()方法
最后,我们将讨论pandas.DataFrame.applymap方法。
这个方法将一个接受并返回标量的函数应用于DataFrame的每个元素。它只能应用于pandas DataFrame。
有时它比apply()方法更快。

df[["name", "say-hello"]] = df[["name", "say-hello"]].applymap(lambda x: x.upper())
df

例如,我们可以将名字和say-hello列转换为大写字母。

image.png

结论
apply()方法是用来对多列或多行进行复杂的处理。
一列或多列:轴=1 一行或多行:轴=0
applymap()方法用于对整个DataFrame的元素进行操作。
apply()方法用于对系列的值进行复杂的处理。
map()方法用于根据一些规则或对应关系将系列的每个值映射到另一个值。

相关文章

网友评论

      本文标题:你在Pandas中使用Apply吗?有一个更快600倍的方法

      本文链接:https://www.haomeiwen.com/subject/iatuvrtx.html