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

我在单个文本列上有一个函数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()。因此,很多问题和答案都不太相关。


当前回答

这是我过去所做的

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

其他回答

对我来说,这很有效:

输入df

df = pd.DataFrame({'col x': [1,2,3]})
   col x
0      1
1      2
2      3

函数

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

创建2个新列:

df[['square x', 'cube x']] = df['col x'].apply(f)

输出:

   col x  square x  cube x
0      1         1       1
1      2         4       8
2      3         9      27

公认的解决方案对于大量数据来说将会非常慢。获得最多赞数的解决方案读起来有点困难,而且处理数字数据也很慢。如果每个新列都可以独立于其他列计算,那么我将直接分配它们,而不使用apply。

假字符数据的例子

在DataFrame中创建100,000个字符串

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)

另一个假数字数据的例子

创建100万个随机数并从上面测试幂函数。

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)

我在这里也做了类似的回答,并详细说明了为什么申请通常不是正确的选择。

总结:如果您只想创建几个列,请使用df[['new_col1','new_col2']] = df[['data1','data2']]。Apply (function_of_your_selection (x), axis=1)

对于这个解决方案,创建的新列数必须等于用作.apply()函数输入的列数。如果你想做别的事情,看看其他答案。

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

假设你需要计算每个人身高的平均值和每个人身高的和。每一行有两个值。

你可以通过下面即将应用的函数来实现:

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]

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

 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)

您将创建一个包含[mean,sum]列表的新列,这可能是您希望避免的,因为这将需要另一个Lambda/Apply。

相反,您希望将每个值分解到它自己的列中。要做到这一点,你可以一次创建两个列:

df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']]
.apply(mean_and_sum(x),axis=1)
def extract_text_features(feature):
    ...
    ...
    return pd.Series((feature1, feature2)) 

df[['NewFeature1', 'NewFeature1']] = df[['feature']].apply(extract_text_features, axis=1)

在这里,具有单个特征的a数据帧被转换为两个新特征。 你也可以试试这个。

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

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列,并且希望保留它,则可能需要尝试第三种方法。