熊猫:在数据框中创建两个新列,并使用从现有列中计算出的值


100

我正在使用pandas库,我想将两个新列添加到df具有n列(n> 0)的数据框中。
这些新列是由于将函数应用于数据框中的某一列而产生的。

要应用的功能如下:

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)返回一个熊猫系列,每个项目都由一个元组z,y组成。尝试将其分配给两个数据框列会产生ValueError。*

Answers:


119

我只用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 2012年

这样做的性能会有所不同吗?zip(* map(calculate,df [“ a”])))而不是zip(* df [“ a”]。map(calculate)),它也提供(如上所述)[(2,4,6),( 3,6,9)]?
ekta 2014年

1
在进行新的列创建时,我收到以下警告:“ SettingWithCopyWarning:试图在DataFrame的切片副本上设置一个值。尝试改用.loc [row_indexer,col_indexer] = value。” 我应该为此担心吗?熊猫v.0.15
塔拉斯2014年

46

我认为最佳答案是有缺陷的。希望没有人使用来将所有大熊猫大量导入其命名空间from pandas import *。同样,在将map方法传递给字典或系列时,应保留该方法的使用时间。它可以带一个函数,但这就是apply它的用途。

所以,如果您必须使用上述方法,我会这样写

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

实际上,这里没有理由使用zip。您可以简单地做到这一点:

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

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

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)

比拉链快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)

因此,这是缓慢的两倍,这并不是可怕的性能下降,但是如果我们对上述内容进行cythonize,我们将获得更好的性能。假设您正在使用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)

直接分配而不适用

如果使用直接矢量化操作,则可以进一步提高速度。

%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,但是正是如此,让我们来看一个最基本的示例。让我们平方一千万个带和不带数字的序列

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)

3
尽管此答案中有一些不错的建议,但我相信要使用func(series)代替的主要建议series.apply(func)仅适用于使用完全在单个值和系列上具有相似行为的操作完全定义功能的情况。在第一个答案的示例中就是这种情况,但在OP的问题中却不是这种情况,OP的问题是更普遍地询问将函数应用于列的问题。1/2
格雷厄姆·利亚

1
例如,如果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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.