如何在熊猫身上做到这一点:

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

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

所以我认为我需要回落到迭代与df.iterrows(),按此?

更新: 使用df.iterrows()迭代至少要慢20倍,因此我放弃并将该函数分解为6个不同的.map(lambda…)调用。

更新2:这个问题是在v0.11.0版本被问到的,在可用性df之前。在v0.16中改进了Apply或添加了df.assign()。因此,很多问题和答案都不太相关。


当前回答

在另外两个类似的问题上也有相同的答案。我更喜欢这样做的方式是将函数的返回值打包成一个系列:

def f(x):
    return pd.Series([x**2, x**3])

然后使用apply创建单独的列,如下所示:

df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)

其他回答

我通常使用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 = df.apply(extract_text_features,axis = 1)

函数在哪里返回行

def extract_text_features(row):
      row['new_col1'] = value1
      row['new_col2'] = value2
      return row

我已经研究了几种方法,这里显示的方法(返回熊猫系列)似乎不是最有效的。

如果我们从一个较大的随机数据的数据框架开始:

# 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'

示例如下:

# 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圈,最好的3: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毫秒每循环

根据我的估算,采用一系列元组然后将其转换为DataFrame要有效得多。如果我的工作中出现了错误,我很想听听人们的想法。

我有一个更复杂的情况,数据集有一个嵌套结构:

import json
data = '{"TextID":{"0":"0038f0569e","1":"003eb6998d","2":"006da49ea0"},"Summary":{"0":{"Crisis_Level":["c"],"Type":["d"],"Special_Date":["a"]},"1":{"Crisis_Level":["d"],"Type":["a","d"],"Special_Date":["a"]},"2":{"Crisis_Level":["d"],"Type":["a"],"Special_Date":["a"]}}}'
df = pd.DataFrame.from_dict(json.loads(data))
print(df)

输出:

        TextID                                            Summary
0  0038f0569e  {'Crisis_Level': ['c'], 'Type': ['d'], 'Specia...
1  003eb6998d  {'Crisis_Level': ['d'], 'Type': ['a', 'd'], 'S...
2  006da49ea0  {'Crisis_Level': ['d'], 'Type': ['a'], 'Specia...

Summary列包含dict对象,所以我使用apply和from_dict和stack来提取每一行的dict:

df2 = df.apply(
    lambda x: pd.DataFrame.from_dict(x[1], orient='index').stack(), axis=1)
print(df2)

输出:

    Crisis_Level Special_Date Type     
                0            0    0    1
0            c            a    d  NaN
1            d            a    a    d
2            d            a    a  NaN

看起来不错,但缺少TextID列。为了得到TextID列回来,我尝试了三种方法:

Modify apply to return multiple columns: df_tmp = df.copy() df_tmp[['TextID', 'Summary']] = df.apply( lambda x: pd.Series([x[0], pd.DataFrame.from_dict(x[1], orient='index').stack()]), axis=1) print(df_tmp) output: TextID Summary 0 0038f0569e Crisis_Level 0 c Type 0 d Spec... 1 003eb6998d Crisis_Level 0 d Type 0 a ... 2 006da49ea0 Crisis_Level 0 d Type 0 a Spec... But this is not what I want, the Summary structure are flatten. Use pd.concat: df_tmp2 = pd.concat([df['TextID'], df2], axis=1) print(df_tmp2) output: TextID (Crisis_Level, 0) (Special_Date, 0) (Type, 0) (Type, 1) 0 0038f0569e c a d NaN 1 003eb6998d d a a d 2 006da49ea0 d a a NaN Looks fine, the MultiIndex column structure are preserved as tuple. But check columns type: df_tmp2.columns output: Index(['TextID', ('Crisis_Level', 0), ('Special_Date', 0), ('Type', 0), ('Type', 1)], dtype='object') Just as a regular Index class, not MultiIndex class. use set_index: Turn all columns you want to preserve into row index, after some complicated apply function and then reset_index to get columns back: df_tmp3 = df.set_index('TextID') df_tmp3 = df_tmp3.apply( lambda x: pd.DataFrame.from_dict(x[0], orient='index').stack(), axis=1) df_tmp3 = df_tmp3.reset_index(level=0) print(df_tmp3) output: TextID Crisis_Level Special_Date Type 0 0 0 1 0 0038f0569e c a d NaN 1 003eb6998d d a a d 2 006da49ea0 d a a NaN Check the type of columns df_tmp3.columns output: MultiIndex(levels=[['Crisis_Level', 'Special_Date', 'Type', 'TextID'], [0, 1, '']], codes=[[3, 0, 1, 2, 2], [2, 0, 0, 0, 1]])

因此,如果apply函数将返回MultiIndex列,并且希望保留它,则可能需要尝试第三种方法。

这是我过去所做的

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