Apply pandas function to column to create multiple new columns?
如何在熊猫中做到这一点:
我在单个文本列上有一个函数
该函数有效,但似乎没有任何正确的返回类型(pandas DataFrame / numpy数组/ Python列表),以便输出可以正确分配
所以我认为我需要回到使用
更新:
使用
更新2:这个问题是在v0.11.0左右回答的。 因此,大部分问题和答案都不太相关。
我通常使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | >>> 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 |
建立用户1827356的答案,你可以使用
1 2 3 4 5 6 7 8 9 | 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 |
编辑:
请注意巨大的内存消耗和低速:https://ys-l.github.io/posts/2015/08/28/how-not-to-use-pandas-apply/!
这就是我过去所做的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
编辑完整性
1 2 3 4 5 6 7 | 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%的用例,这是实现此目的的正确和最简单的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | >>> 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 |
摘要:如果您只想创建几列,请使用
对于此解决方案,您创建的新列的数量必须等于用作.apply()函数输入的列数。如果您想做其他事情,请查看其他答案。
细节
假设您有两列数据帧。第一列是10岁时人的身高;第二个是20岁时的人的身高。
假设你需要计算每个人身高的平均值和每个人身高的总和。这是每行两个值。
您可以通过以下即将应用的功能执行此操作:
1 2 3 4 5 6 7 8 9 10 | def mean_and_sum(x): """ Calculates the mean and sum of two heights. Parameters: :x -- the values in the row this function is applied to. Could also work on a list or a tuple. """ sum=x[0]+x[1] mean=sum/2 return [mean,sum] |
你可能会像这样使用这个函数:
1 | df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1) |
(要明确:此apply函数接受子集化数据框中每行的值并返回一个列表。)
但是,如果你这样做:
1 | df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1) |
您将创建一个包含[mean,sum]列表的新列,您可能希望避免这些列,因为这需要另一个Lambda / Apply。
相反,您希望将每个值分解为自己的列。为此,您可以一次创建两列:
1 2 | df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']] .apply(mean_and_sum(x),axis=1) |
在2018年,我使用带参数
1 2 | >>> appiled_df = df.apply(lambda row: fn(row.text), axis='columns', result_type='expand') >>> df = pd.concat([df, appiled_df], axis='columns') |
对我来说,这工作:
输入df
1 2 3 4 5 | df = pd.DataFrame({'col x': [1,2,3]}) col x 0 1 1 2 2 3 |
功能
1 2 | def f(x): return pd.Series([x*x, x*x*x]) |
创建2个新列:
1 | df[['square x', 'cube x']] = df['col x'].apply(f) |
输出:
1 2 3 4 | col x square x cube x 0 1 1 1 1 2 4 8 2 3 9 27 |
我看了几种方法,这里显示的方法(返回一个熊猫系列)似乎并不是最有效的。
如果我们从一个庞大的随机数据数据框开始:
1 2 3 4 | # 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, (r.A, r.B, r.C))), axis=1) columns = 'new_a', 'new_b', 'new_c' |
此处显示的示例:
1 2 3 4 | # 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 loops, best of 3: 2.77 s per loop
另一种方法:
1 2 3 4 | # 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 loops, best of 3: 8.85 ms per loop
通过我的计算,获取一系列元组然后将其转换为DataFrame效率更高。如果我的工作中出现错误,我会有兴趣听到别人的想法。
对于大量数据,接受的解决方案将非常缓慢。具有最多数量的upvotes的解决方案有点难以阅读,并且还因数字数据而变慢。如果每个新列可以独立于其他列进行计算,我只需直接分配它们而不使用
假字符数据的示例
在DataFrame中创建100,000个字符串
1 2 3 4 5 6 7 8 9 10 | 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"的出现次数并将该短语大写。
1 2 3 4 5 6 7 8 9 10 | 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 |
计时
1 2 3 4 5 6 7 8 9 10 11 | %%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) |
令人惊讶的是,您可以通过循环遍历每个值来获得更好的性能
1 2 3 4 5 6 7 8 9 | %%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) |
假数字数据的另一个例子
创建100万个随机数并从上面测试
1 2 3 4 5 6 7 8 9 10 | 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倍且非常易读:
1 2 3 4 5 6 7 8 | %%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) |
我在这里做了类似的回复,详细说明为什么
在其他两个类似问题中发布了相同的答案。我喜欢这样做的方法是将函数的返回值包装在一个系列中:
1 2 | def f(x): return pd.Series([x**2, x**3]) |
然后使用apply如下创建单独的列:
1 | df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1) |
只需使用
1 2 | df = pd.DataFrame(np.random.randint(0,10,(10,2)), columns=["random","a"]) df[["sq_a","cube_a"]] = df.apply(lambda x: [x.a**2, x.a**3], axis=1, result_type="expand") |
您可以返回整行而不是值:
1 | df = df.apply(extract_text_features,axis = 1) |
函数返回行的位置
1 2 3 4 | def extract_text_features(row): row['new_col1'] = value1 row['new_col2'] = value2 return row |