将pandas函数应用于列以创build多个新列?

pandas如何做到这一点:

我有一个函数extract_text_features单个文本列,返回多个输出列。 具体来说,该函数返回6个值。

该函数工作,但是似乎没有任何正确的返回types(pandas DataFrame / numpy数组/ Python列表),以便输出可以正确分配df.ix[: ,10:16] = df.textcol.map(extract_text_features)

所以我想我需要退回到与df.iterrows()迭代,按照这个 ?

更新:迭代与df.iterrows()是至less20倍慢,所以我投降,并将function拆分成六个不同的.map(lambda ...)调用。

根据user1827356的答案,你可以使用df.merge一次完成任务:

 df.merge(df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1})), left_index=True, right_index=True) textcol feature1 feature2 0 0.772692 1.772692 -0.227308 1 0.857210 1.857210 -0.142790 2 0.065639 1.065639 -0.934361 3 0.819160 1.819160 -0.180840 4 0.088212 1.088212 -0.911788 

我通常使用zip执行此操作:

 >>> df = pd.DataFrame([[i] for i in range(10)], columns=['num']) >>> df num 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 >>> def powers(x): >>> return x, x**2, x**3, x**4, x**5, x**6 >>> df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \ >>> zip(*df['num'].map(powers)) >>> df num p1 p2 p3 p4 p5 p6 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 4 8 16 32 64 3 3 3 9 27 81 243 729 4 4 4 16 64 256 1024 4096 5 5 5 25 125 625 3125 15625 6 6 6 36 216 1296 7776 46656 7 7 7 49 343 2401 16807 117649 8 8 8 64 512 4096 32768 262144 9 9 9 81 729 6561 59049 531441 

这是我过去所做的

 df = pd.DataFrame({'textcol' : np.random.rand(5)}) df textcol 0 0.626524 1 0.119967 2 0.803650 3 0.100880 4 0.017859 df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1})) feature1 feature2 0 1.626524 -0.373476 1 1.119967 -0.880033 2 1.803650 -0.196350 3 1.100880 -0.899120 4 1.017859 -0.982141 

编辑完整性

 pd.concat([df, df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))], axis=1) textcol feature1 feature2 0 0.626524 1.626524 -0.373476 1 0.119967 1.119967 -0.880033 2 0.803650 1.803650 -0.196350 3 0.100880 1.100880 -0.899120 4 0.017859 1.017859 -0.982141 

对于95%的用例来说,这是正确和最简单的方法:

 >>> df = pd.DataFrame(zip(*[range(10)]), columns=['num']) >>> df num 0 0 1 1 2 2 3 3 4 4 5 5 >>> def example(x): >>> x['p1'] = x['num']**2 >>> x['p2'] = x['num']**3 >>> x['p3'] = x['num']**4 >>> return x >>> df = df.apply(example, axis=1) >>> df num p1 p2 p3 0 0 0 0 0 1 1 1 1 1 2 2 4 8 16 3 3 9 27 81 4 4 16 64 256 

我已经看过几种做法,这里显示的方法(返回一个pandas系列)似乎不是最有效的。

如果我们从随机数据的大量数据框开始:

 # Setup a dataframe of random numbers and create a df = pd.DataFrame(np.random.randn(10000,3),columns=list('ABC')) df['D'] = df.apply(lambda r: ':'.join(map(str, (rA, rB, rC))), axis=1) columns = 'new_a', 'new_b', 'new_c' 

这里显示的例子:

 # Create the dataframe by returning a series def method_b(v): return pd.Series({k: v for k, v in zip(columns, v.split(':'))}) %timeit -n10 -r3 df.D.apply(method_b) 

10个循环,最好是每个循环2.77秒

另一种方法:

 # Create a dataframe from a series of tuples def method_a(v): return v.split(':') %timeit -n10 -r3 pd.DataFrame(df.D.apply(method_a).tolist(), columns=columns) 

10个循环,最好是3:每个循环8.85 ms

根据我的推算,采用一系列元组并将其转换为DataFrame会更有效率。 如果我的工作出现错误,我有兴趣听到别人的想法。

总结:使用df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)

注意:您创build的新列数必须等于用作.apply()函数input的数字列数。

细节假设你有两列数据框。 第一列是十岁时的一个人的身高; 二是20岁时说的人的身高。

我们进一步说,你需要计算每个人的高度的平均值,以及每个人的高度的总和。 换句话说,你正在计算每行的两个值。 您可以通过以下即将应用的函数执行此操作:

 def mean_and_sum(x): """ Calculates the mean and sum of two heights. Parameters: :x -- the values in the row this function is lambda-applied to. Could also work on a list or a tuple. """ sum=x[0]+x[1] mean=sum/2 return [mean,sum] 

你可以像这样使用这个函数:

  df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1) 

(要清楚的是:这个apply函数从子集数据框的每一行取得值并返回一个列表。)

但是,如果你这样做:

 df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1) 

你将创build一个包含[mean,sum]列表的新列,你可能想避免这个列表。

相反,你想打破每个值到它自己的专栏。 要做到这一点,你可以一次创build两列:

 df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']] .apply(mean_and_sum(x),axis=1) 

被接受的解决scheme对于大量的数据将会非常缓慢。 upvotes数量最多的解决scheme有点难以阅读,而且数字数据也很慢。 如果每个新列都可以独立计算,那么我就直接分配它们,而不使用apply

伪造字符数据的示例

在DataFrame中创build100,000个string

 df = pd.DataFrame(np.random.choice(['he jumped', 'she ran', 'they hiked'], size=100000, replace=True), columns=['words']) df.head() words 0 she ran 1 she ran 2 they hiked 3 they hiked 4 they hiked 

假设我们想要像原始问题那样提取一些文本特征。 例如,让我们提取第一个字符,计算字母“e”的出现并将该短语大写。

 df['first'] = df['words'].str[0] df['count_e'] = df['words'].str.count('e') df['cap'] = df['words'].str.capitalize() df.head() words first count_e cap 0 she ran s 1 She ran 1 she ran s 1 She ran 2 they hiked t 2 They hiked 3 they hiked t 2 They hiked 4 they hiked t 2 They hiked 

计时

 %%timeit df['first'] = df['words'].str[0] df['count_e'] = df['words'].str.count('e') df['cap'] = df['words'].str.capitalize() 127 ms ± 585 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) def extract_text_features(x): return x[0], x.count('e'), x.capitalize() %timeit df['first'], df['count_e'], df['cap'] = zip(*df['words'].apply(extract_text_features)) 101 ms ± 2.96 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 

令人惊讶的是,通过遍历每个值,您可以获得更好的性能

 %%timeit a,b,c = [], [], [] for s in df['words']: a.append(s[0]), b.append(s.count('e')), c.append(s.capitalize()) df['first'] = a df['count_e'] = b df['cap'] = c 79.1 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 

另一个假数字数据的例子

创build一百万个随机数并从上面testingpowers函数。

 df = pd.DataFrame(np.random.rand(1000000), columns=['num']) def powers(x): return x, x**2, x**3, x**4, x**5, x**6 %%timeit df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \ zip(*df['num'].map(powers)) 1.35 s ± 83.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) 

分配每一列的速度是25倍,非常可读:

 %%timeit df['p1'] = df['num'] ** 1 df['p2'] = df['num'] ** 2 df['p3'] = df['num'] ** 3 df['p4'] = df['num'] ** 4 df['p5'] = df['num'] ** 5 df['p6'] = df['num'] ** 6 51.6 ms ± 1.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) 

我在这里提供了更多的细节 ,为什么apply通常不是要走的路。