Pandas:在数据框中创建两个新列,其值是从现有列计算得出的

115
我正在使用 pandas 库,并且想要向含有n列(n > 0)的数据框df中添加两个新列。
这两个新列是由将其中一列进行函数计算得到的。
所要应用的函数如下:
def calculate(x):
    ...operate...
    return z, y

创建一个返回单个值的函数的新列的一种方法是:
df['new_col']) = df['column_A'].map(a_function)

所以,我想要的是,我尝试过但没有成功的东西 (*):

(df['new_col_zetas'], df['new_col_ys']) = df['column_A'].map(calculate)

如何最好地完成这个任务呢?我扫了一遍文档,但毫无头绪。

**df['column_A'].map(calculate)返回一个Pandas Series,每个条目都由元组z、y组成。尝试将其分配给两个数据帧列会产生ValueError错误。

3个回答

136

我会使用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

谢谢,太棒了,它运作正常。在0.8.1的文档中我找不到这样的内容...我想我应该把Series看作元组列表... - joaquin
这样做与使用zip(*df["a"].map(calculate))有性能上的区别吗?前者是zip(*map(calculate,df["a"])),结果也是[(2, 4, 6), (3, 6, 9)]。 - ekta
1
当我像这样创建新列时,我会收到以下警告:“SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead.” 我应该担心吗? pandas v.0.15 - taras

55

我认为最佳回答存在缺陷。希望没有人使用from pandas import *将整个pandas导入其命名空间。此外,应该仅在需要传递字典或Series时才使用map方法。它可以接受一个函数,但这正是apply的用途。

因此,如果您必须使用上述方法,我会这样写:

df["A1"], df["A2"] = zip(*df["a"].apply(calculate))

这里实际上没有必要使用zip。你可以简单地这样做:

df["A1"], df["A2"] = calculate(df['a'])

这第二种方法在较大的数据框中也要快得多

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

创建了一个有300,000行的DataFrame

%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)

比zip快60倍


一般情况下,避免使用apply

使用apply与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)

所以这个速度是原来的两倍,虽然性能有所下降,但如果我们将上述内容进行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)

不需要使用apply来直接赋值

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

%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)

利用NumPy极快的向量化操作而不是循环,我们现在比原来快了30倍。


apply 的最简速度测试

上面的例子清楚地展示了apply有多慢,但为了更加明确,我们来看看最基本的例子。让我们对包含1000万个数字的Series进行平方运算并比较使用和不使用apply的速度。

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)

没有应用程序时,速度快了50倍。

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

1
这是一个非常好的答案。我想问一下:对于需要对数据框中的每个元素实现特定函数的情况,您认为applymap如何? - David
4
虽然这个回答提供了一些不错的建议,但我认为主要的建议使用func(series)而非series.apply(func)只适用于函数完全使用具有相似行为的操作定义在单个值和整个系列上的情况。这是第一个答案中的例子,但并不适用于原帖中的问题,原帖更普遍地询问将函数应用于列的情况。 - Graham Lea
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)返回的结果非常不同。 - Graham Lea
@Graham Lea 这是真的,但你总是可以(也可能不行)将函数向量化。我尝试过了,它可以正常工作:tdf['A1'], tdf['A2'] = np.vectorize(calc)(tdf.a) - KH Kim

0

您也可以使用管道

df['new_col1'], df['new_col2'] = df['column_A'].pipe(calculate)

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