当前位置: 首页 > 知识库问答 >
问题:

Pandas apply与NP.Vectorize从现有列创建新列性能

公孙宗清
2023-03-14

例如,假设我有以下带有n行的dataframe:

N = 10
A_list = np.random.randint(1, 100, N)
B_list = np.random.randint(1, 100, N)
df = pd.DataFrame({'A': A_list, 'B': B_list})
df.head()
#     A   B
# 0  78  50
# 1  23  91
# 2  55  62
# 3  82  64
# 4  99  80

进一步假设我希望创建一个新列,作为两列aB的函数。在下面的示例中,我将使用一个简单的函数divide()。要应用该函数,可以使用df.apply()np.vectorize():

def divide(a, b):
    if b == 0:
        return 0.0
    return float(a)/b

df['result'] = df.apply(lambda row: divide(row['A'], row['B']), axis=1)

df['result2'] = np.vectorize(divide)(df['A'], df['B'])

df.head()
#     A   B    result   result2
# 0  78  50  1.560000  1.560000
# 1  23  91  0.252747  0.252747
# 2  55  62  0.887097  0.887097
# 3  82  64  1.281250  1.281250
# 4  99  80  1.237500  1.237500

如果我将n的实际大小增加到100万或更多,那么我会发现np.vectorize()df.apply()快25倍或更多。

import pandas as pd
import numpy as np
import time

def divide(a, b):
    if b == 0:
        return 0.0
    return float(a)/b

for N in [1000, 10000, 100000, 1000000, 10000000]:    

    print ''
    A_list = np.random.randint(1, 100, N)
    B_list = np.random.randint(1, 100, N)
    df = pd.DataFrame({'A': A_list, 'B': B_list})

    start_epoch_sec = int(time.time())
    df['result'] = df.apply(lambda row: divide(row['A'], row['B']), axis=1)
    end_epoch_sec = int(time.time())
    result_apply = end_epoch_sec - start_epoch_sec

    start_epoch_sec = int(time.time())
    df['result2'] = np.vectorize(divide)(df['A'], df['B'])
    end_epoch_sec = int(time.time())
    result_vectorize = end_epoch_sec - start_epoch_sec


    print 'N=%d, df.apply: %d sec, np.vectorize: %d sec' % \
            (N, result_apply, result_vectorize)

    # Make sure results from df.apply and np.vectorize match.
    assert(df['result'].equals(df['result2']))
N=1000, df.apply: 0 sec, np.vectorize: 0 sec

N=10000, df.apply: 1 sec, np.vectorize: 0 sec

N=100000, df.apply: 2 sec, np.vectorize: 0 sec

N=1000000, df.apply: 24 sec, np.vectorize: 1 sec

N=10000000, df.apply: 262 sec, np.vectorize: 4 sec

我如何使用Pandas的‘应用’功能多列?

如何将函数应用于Pandas数据帧的两列

共有1个答案

龙智
2023-03-14

我首先要说的是,Pandas和NumPy数组的强大之处来自于对数值数组的html" target="_blank">高性能矢量化计算。1矢量化计算的全部意义是通过将计算移到高度优化的C代码和利用连续的内存块来避免Python级别的循环。2

现在我们可以看看一些计时。下面是所有Python级循环,它们生成包含相同值的pd.seriesnp.ndarraylist对象。为了分配给数据帧中的一个系列,结果是可比较的。

# Python 3.6.5, NumPy 1.14.3, Pandas 0.23.0

np.random.seed(0)
N = 10**5

%timeit list(map(divide, df['A'], df['B']))                                   # 43.9 ms
%timeit np.vectorize(divide)(df['A'], df['B'])                                # 48.1 ms
%timeit [divide(a, b) for a, b in zip(df['A'], df['B'])]                      # 49.4 ms
%timeit [divide(a, b) for a, b in df[['A', 'B']].itertuples(index=False)]     # 112 ms
%timeit df.apply(lambda row: divide(*row), axis=1, raw=True)                  # 760 ms
%timeit df.apply(lambda row: divide(row['A'], row['B']), axis=1)              # 4.83 s
%timeit [divide(row['A'], row['B']) for _, row in df[['A', 'B']].iterrows()]  # 11.6 s

一些外卖:

    null
def foo(row):
    print(type(row))
    assert False  # because you only need to see this once
df.apply(lambda row: foo(row), axis=1)

输出: 。创建、传递和查询Pandas series对象相对于NumPy数组会带来很大的开销。这并不奇怪:Pandas系列包含了相当多的支架来保存索引、值、属性等。

使用raw=true再次执行相同的练习,您将看到 。这一切在文档中都有描述,但看到它更有说服力。

np.vectorize的文档有以下注意事项:

vectorized函数对输入数组的连续元组求值pyfunc,与python map函数一样,不同的是它使用了numpy的广播规则。

%timeit np.where(df['B'] == 0, 0, df['A'] / df['B'])       # 1.17 ms
%timeit (df['A'] / df['B']).replace([np.inf, -np.inf], 0)  # 1.96 ms

当循环被认为是可行的时,它们通常通过numba进行优化,并使用底层NumPy数组尽可能多地移动到C。

确实,numba将性能提高到微秒级。如果没有一些繁琐的工作,就很难得到比这更有效率的多。

from numba import njit

@njit
def divide(a, b):
    res = np.empty(a.shape)
    for i in range(len(a)):
        if b[i] != 0:
            res[i] = a[i] / b[i]
        else:
            res[i] = 0
    return res

%timeit divide(df['A'].values, df['B'].values)  # 717 µs

使用@njit(parallel=true)可以进一步促进更大的数组。

    null
 类似资料:
  • 我有一个列表,我想创建一个名为的扩展。 我不想覆盖,因此不能使用append、extend或insert。我想知道是否有一个快速的方法来完成这项工作(比使用理解列表或Deep.copy更快)

  • 问题内容: 我建立了一个分析引擎,可以从数据库中提取50-100行原始数据(称为),在PHP上对其进行一堆统计测量,然后精确给出140个数据点,然后将它们存储在另一个表中(让我们称之为)。所有这些数据点都是非常小的整数(“ 40”,“ 2.23”,“-1024”是数据类型的很好的示例)。 我知道mysql的最大列数非常高(4000+),但是当性能真正开始下降时,似乎有很多灰色区域。 这里有一些关于

  • 问题内容: 有一个对象列表。 使用它创建一个unmodifiableList。 我了解不支持添加/删除/设置操作。同时,它不是不变的,因为它引用了现有的可修改列表,并且每当对该列表进行更改时,这些更改也会反映出来。 这样就创建了一个不可变的列表。 由于使用了转换构造函数,因此创建了一个不可变的列表。无法执行添加/删除/设置操作,原始列表中的任何更改都不会反映在中。让我们假设对象也是不可变的。 现在

  • 我正在尝试查看我们是否可以使用 spark/scala 从 dataFrame 中某个列中的值创建新列。我有一个数据帧,其中包含以下数据 在上面的数据中,col1/col2/col3是列名,后跟它的值。列名和值由< code >,分隔。每组由< code>|分隔。 现在,我想做到这一点 感谢任何帮助。

  • 我有一个很大的数据集,其中有许多带有状态的列。我想做一个新的专栏,有参与者的当前状态。我试图在dplyr中使用case_when,但我不确定如何跨列。数据集的列太多,我无法键入每一列。以下是数据示例: 对于代码,我想要一个新的列,列中说明参与者的最终状态;然而,如果他们的状态曾经是完成的,那么我希望它说完成,不管他们的最终状态是什么。对于该数据,答案如下所示: 还有,如果你能包括对你的代码的任何解

  • 问题内容: 我有一个应用程序,该应用程序可以获取制造过程的质量结果,并创建图形以显示不良品的帕累托图,并显示生产量。 为了自动化测试这些统计程序的任务,我希望确定性地能够将记录添加到数据库中,并使质量技术能够进入某些图表并与已知的良好图表进行比较。但是,我也想模拟结果,以便将它们像进入用户整个测试过程一样进入数据库。 我有一个想法是用i好的数字,j的坏数字1,k的坏数字2等填充一个列表,然后以某种