基于列值从数据框中插值。

3
假设我有以下问题:
import pandas as pd
import numpy as np

xp = [0.0, 0.5, 1.0]

np.random.seed(100)
df = pd.DataFrame(np.random.rand(10, 4), columns=['x0', 'y1', 'y2', 'y3'])

df
      x0     y1     y2     y3
0 0.5434 0.2784 0.4245 0.8448
1 0.0047 0.1216 0.6707 0.8259
2 0.1367 0.5751 0.8913 0.2092
3 0.1853 0.1084 0.2197 0.9786
4 0.8117 0.1719 0.8162 0.2741
5 0.4317 0.9400 0.8176 0.3361
6 0.1754 0.3728 0.0057 0.2524
7 0.7957 0.0153 0.5988 0.6038
8 0.1051 0.3819 0.0365 0.8904
9 0.9809 0.0599 0.8905 0.5769

我想要插值一个名为"interp"的列。需要插值的横坐标存储在"x0"列中,数据点的横坐标存储在"xp"列中,而数据点的纵坐标则分别存储在"y1"、"y2"和"y3"列中。以下是我目前的处理方式:
df['interp'] = df.apply(lambda x: np.interp(x.x0, xp, [x.y1, x.y2, x.y3]), axis=1)

df
      x0     y1     y2     y3  interp
0 0.5434 0.2784 0.4245 0.8448  0.4610
1 0.0047 0.1216 0.6707 0.8259  0.1268
2 0.1367 0.5751 0.8913 0.2092  0.6616
3 0.1853 0.1084 0.2197 0.9786  0.1496
4 0.8117 0.1719 0.8162 0.2741  0.4783
5 0.4317 0.9400 0.8176 0.3361  0.8344
6 0.1754 0.3728 0.0057 0.2524  0.2440
7 0.7957 0.0153 0.5988 0.6038  0.6018
8 0.1051 0.3819 0.0365 0.8904  0.3093
9 0.9809 0.0599 0.8905 0.5769  0.5889

然而,需要进行计算的数据框包含超过一百万行,因此我希望使用比apply更快的方法。有什么建议吗?np.interp似乎只接受1-D数组,这就是我选择apply的原因。
1个回答

2

一个加快速度的好方法是使用pandas.DataFrame.eval()

简而言之

Seconds per number of rows
Rows:     100   1000  10000    1E5    1E6    1E7
apply:  0.076  0.734  7.812
eval:   0.056  0.053  0.058  0.087  0.338  2.887

从这些时间可以看出,eval()有很多设置开销,而且在处理少于10000行的数据时基本上需要相同的时间。但是它比apply快两个数量级,因此对于大型数据集来说,它肯定值得开销。
那么,它是什么?
来自(DOCS)
pandas.eval(expr, parser='pandas', engine=None, truediv=True, 
            local_dict=None, global_dict=None, resolvers=(),
            level=0, target=None, inplace=None)

使用各种后端将Python表达式作为字符串进行评估。 支持以下算术运算符:+,-,*,/,**,%,//(仅限python引擎),以及以下布尔运算符:|(或),&(和)和~(非)。此外,“pandas”解析器允许使用and、or和not,其语义与相应的按位运算符相同。支持Series和DataFrame对象,并且它们的行为与普通的Python求值相同。 针对此问题执行的技巧: 下面的代码利用了插值始终仅在两个段中的事实。 它实际上计算了两个段的插值器,然后通过乘以布尔测试(即0、1)来丢弃未使用的段。 传递给eval的实际表达式为:
((y2-y1) / 0.5 * (x0-0.0) + y1) * (x0 < 0.5)+((y3-y2) / 0.5 * (x0-0.5) + y2) * (x0 >= 0.5)

代码:

import pandas as pd
import numpy as np

xp = [0.0, 0.5, 1.0]

np.random.seed(100)

def method1():
    df['interp'] = df.apply(
        lambda x: np.interp(x.x0, xp, [x.y1, x.y2, x.y3]), axis=1)

def method2():
    exp = '((y%d-y%d) / %s * (x0-%s) + y%d) * (x0 %s 0.5)'
    exp_1 = exp % (2, 1, xp[1] - xp[0], xp[0], 1, '<')
    exp_2 = exp % (3, 2, xp[2] - xp[1], xp[1], 2, '>=')

    df['interp2'] = df.eval(exp_1 + '+' + exp_2)

from timeit import timeit

def runit(stmt):
    print("%s: %.3f" % (
        stmt, timeit(stmt + '()', number=10,
                     setup='from __main__ import ' + stmt)))

def runit_size(size):
    global df
    df = pd.DataFrame(
        np.random.rand(size, 4), columns=['x0', 'y1', 'y2', 'y3'])

    print('Rows: %d' % size)
    if size <= 10000:
        runit('method1')
    runit('method2')

for i in (100, 1000, 10000, 100000, 1000000, 10000000):
    runit_size(i)

print(df.head())

结果:

         x0        y1        y2        y3    interp   interp2
0  0.060670  0.949837  0.608659  0.672003  0.908439  0.908439
1  0.462774  0.704273  0.181067  0.647582  0.220021  0.220021
2  0.568109  0.954138  0.796690  0.585310  0.767897  0.767897
3  0.455355  0.738452  0.812236  0.927291  0.805648  0.805648
4  0.826376  0.029957  0.772803  0.521777  0.608946  0.608946

1
@StevenG,如果你能将你的计算器压缩到它的模型中,eval() 会非常快。 :-) - Stephen Rauch
谢谢Stephen,我会尝试根据您的答案来解决我的问题。问题在于,我尝试提供了一个简化的问题,但我的插值方法需要在最后添加更多的列。我将尝试查看一下如何在不使用pandas(仅使用numpy)的情况下解决这个问题。无论如何,非常感谢您的回答,这可能是使用pandas的最佳见解! - Eric B
1
由于这种方法比apply快100倍,因此您可以轻松地将此解决方案扩展到5或10个段,并且它仍然会快得多。此外,如果您可以先验地限制一组特定行的可能性范围,那么这仍然会快得多。祝你好运。 - Stephen Rauch
我刚刚在一个更多列的数据框上测试了你的答案,它完美地运行了! - Eric B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接