Pandas:在数据框中创建两个新列,其中的值是根据预先存在的列计算的

joa*_*uin 92 python pandas

我正在使用pandas库,我想在df具有n列(n> 0)的数据帧中添加两个新列.
这些新列是将函数应用于数据框中的一列的结果.

要应用的功能如下:

def calculate(x):
    ...operate...
    return z, y
Run Code Online (Sandbox Code Playgroud)

为仅返回值的函数创建新列的一种方法是:

df['new_col']) = df['column_A'].map(a_function)
Run Code Online (Sandbox Code Playgroud)

所以,我想要的,并尝试不成功(*),是这样的:

(df['new_col_zetas'], df['new_col_ys']) = df['column_A'].map(calculate)
Run Code Online (Sandbox Code Playgroud)

实现这一目标的最佳方法是什么?我没有任何线索扫描文档.

**df['column_A'].map(calculate)返回一个pandas系列,每个项目由一个元组z,y组成.并尝试将其分配给两个dataframe列会产生ValueError.*

DSM*_*DSM 107

我只是用zip:

In [1]: from pandas import *

In [2]: def calculate(x):
   ...:     return x*2, x*3
   ...: 

In [3]: df = DataFrame({'a': [1,2,3], 'b': [2,3,4]})

In [4]: df
Out[4]: 
   a  b
0  1  2
1  2  3
2  3  4

In [5]: df["A1"], df["A2"] = zip(*df["a"].map(calculate))

In [6]: df
Out[6]: 
   a  b  A1  A2
0  1  2   2   3
1  2  3   4   6
2  3  4   6   9
Run Code Online (Sandbox Code Playgroud)


Ted*_*rou 42

在我看来,最重要的答案是有缺陷的.希望没有人将所有的pandas大量导入其命名空间from pandas import *.此外,该map方法应保留给传递字典或系列的那些时间.它可以采取功能,但这apply是用于.

所以,如果你必须使用上面的方法,我会这样写

df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
Run Code Online (Sandbox Code Playgroud)

实际上没有理由在这里使用zip.你可以这样做:

df["A1"], df["A2"] = calculate(df['a'])
Run Code Online (Sandbox Code Playgroud)

第二种方法在较大的DataFrame上也快得多

df = pd.DataFrame({'a': [1,2,3] * 100000, 'b': [2,3,4] * 100000})
Run Code Online (Sandbox Code Playgroud)

DataFrame创建了300,000行

%timeit df["A1"], df["A2"] = calculate(df['a'])
2.65 ms ± 92.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
159 ms ± 5.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)

比拉链快60倍


一般来说,避免使用申请

应用通常不比迭代Python列表快得多.让我们测试一个for循环的性能来做同样的事情

%%timeit
A1, A2 = [], []
for val in df['a']:
    A1.append(val**2)
    A2.append(val**3)

df['A1'] = A1
df['A2'] = A2

298 ms ± 7.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Run Code Online (Sandbox Code Playgroud)

所以这是两倍慢,这不是一个糟糕的性能回归,但如果我们对上述进行cython化,我们会获得更好的性能.假设您正在使用ipython:

%load_ext cython

%%cython
cpdef power(vals):
    A1, A2 = [], []
    cdef double val
    for val in vals:
        A1.append(val**2)
        A2.append(val**3)

    return A1, A2

%timeit df['A1'], df['A2'] = power(df['a'])
72.7 ms ± 2.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)

直接分配,无需申请

如果使用直接矢量化操作,则可以获得更大的速度提升.

%timeit df['A1'], df['A2'] = df['a'] ** 2, df['a'] ** 3
5.13 ms ± 320 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Run Code Online (Sandbox Code Playgroud)

这利用了NumPy极快的矢量化操作而不是我们的循环.我们现在的速度比原来提高了30倍.


最简单的速度测试 apply

上面的例子应该清楚地显示出它的速度有多慢apply,但正好让它更加清晰让我们看看最基本的例子.让我们看看一系列1000万个数字,无论是否适用

s = pd.Series(np.random.rand(10000000))

%timeit s.apply(calc)
3.3 s ± 57.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Run Code Online (Sandbox Code Playgroud)

没有申请是快50倍

%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)

  • 虽然在这个答案中有一些很好的建议,但我认为使用`func(series)`而不是`series.apply(func)`的主要建议仅适用于func是使用在个人上表现相似的操作完全定义的价值和系列.在第一个答案中的示例中就是这种情况,但在OP的问题中并非如此,该问题更广泛地要求将函数应用于列.1/2 (2认同)
  • 例如,如果 df 是:`DataFrame({'a': ['Aaron', 'Bert', 'Christopher'], 'b': ['Bold', 'Courageous', 'Distrusted']})`并且 `calc` 是: `def calc(x): return x[0], len(x)` 然后 `tdf.a.apply(calc))` 和 `calc(tdf.a)` 返回非常不同的东西。 (2认同)