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

367
如何在pandas中实现这个需求: 我有一个名为extract_text_features的函数,用于处理单个文本列,并返回多个输出列。具体来说,该函数返回6个值。 该函数可以正常工作,但似乎没有正确的返回类型(pandas DataFrame/ numpy array/ Python list),以便将输出正确地分配给df.ix[: ,10:16] = df.textcol.map(extract_text_features) 因此,我认为我需要回到使用df.iterrows()迭代,就像这里所述一样? 更新: 使用df.iterrows()迭代至少比使用lambda表达式调用.map()慢20倍,因此我放弃了使用df.iterrows()方法,并将函数拆分成了六个独立的.map(lambda ...)调用。 更新2:此问题是在 v0.11.0 左右提出的,在那个版本之前,df.apply功能的可用性得到了改善,或者在 df.assign() 中添加了 在v0.16中添加。因此,该问题和答案的大部分内容不太相关。

1
我不认为你可以按照你所写的方式进行多重赋值:df.ix[:, 10:16]。我觉得你需要将特征与数据集进行合并。 - Zelazny7
2
对于那些想要更高性能解决方案的人,请查看下面的链接(https://dev59.com/8mQo5IYBdhLWcg3wMtC2#47097625),它不使用`apply`。 - Ted Petrou
大多数使用pandas的数字操作都可以进行向量化处理,这意味着它们比传统迭代要快得多。另一方面,某些操作(例如字符串和正则表达式)本质上很难进行向量化处理。在这种情况下,重要的是要了解如何循环遍历您的数据。有关何时以及如何循环遍历数据的更多信息,请阅读使用Pandas的For循环-我应该关心什么? - cs95
@coldspeed:主要问题不是在几个选项中选择哪一个性能更高,而是在v0.11.0左右,为了让它正常工作而与Pandas语法进行斗争。 - smci
实际上,这个注释是为了未来寻找迭代解决方案的读者而写的,无论他们是不知道更好的方法,还是已经知道自己在做什么。 - cs95
显示剩余2条评论
17个回答

11

我已经在两个相似的问题中发布了相同的答案。我更喜欢的方法是将函数的返回值打包成一系列:

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)

3
def extract_text_features(feature):
    ...
    ...
    return pd.Series((feature1, feature2)) 

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

这里将一个具有单个特征的数据框转换为两个新特征。 也可以尝试一下这个。


2
这对我有效:
import pandas as pd
import numpy as np
future = pd.DataFrame(
    pd.date_range('2022-09-01',periods=360),
    columns=['date']
)

def featurize(datetime):
    return pd.Series({
        'month':datetime.month,
        'year':datetime.year,
        'dayofweek':datetime.dayofweek,
        'dayofyear':datetime.dayofyear
    })
    
future.loc[
    :,['month','year','dayofweek','dayofyear']
    ] = future.date.apply(featurize)

future.head()

输出:

    date    month   year    dayofweek   dayofyear
0   2022-09-01  9   2022    3           244
1   2022-09-02  9   2022    4           245
2   2022-09-03  9   2022    5           246
3   2022-09-04  9   2022    6           247
4   2022-09-05  9   2022    0           248

很好。我曾在pandas 0.11上提出过这个问题,它能在哪个更早的版本上运行?它依赖于哪个版本的语法增强? - smci
我个人只在我的当前版本pandas==1.4.3上进行了测试,但我认为它应该与旧版本相当兼容。看起来'.loc'在0.11版本就已经存在了:https://pandas.pydata.org/pandas-docs/version/1.0/whatsnew/v0.11.0.html - meowmeow
我认为关键是从与列标签匹配的字典创建一个Series。 - meowmeow

1
你可以返回整个行而不是值:
df = df.apply(extract_text_features,axis = 1)

函数返回行的位置

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

不,我不想将 extract_text_features 应用于 df 的每一列,只想应用于文本列 df.textcol - smci

0
虽然问题指定函数应用于 Series,但大多数答案似乎将函数应用于 DataFrame,并从每行获取相关列。这似乎有些不优雅且潜在缓慢。
假设函数 f 获取列 df ["argument"] 中的值并返回两个值。我发现将其应用于列 Series 的最好方法是:
df[["value_1", "value_2"]] = df["argument"].apply(f).to_list()

DataFrame.apply 不同的是,不幸的是 Series.apply 没有 result_type 参数来将结果扩展为 DataFrame 并进行分配。但 Pandas 同样理解,如果你将其分配给一个元组的列表。

0

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

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列包含字典对象,因此我使用applyfrom_dictstack来提取每个字典的每一行:

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列,我尝试了三种方法:

  1. 修改 apply 函数以返回多列:

    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)
    

    输出结果为:

        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...
    

    但这不是我想要的,Summary 的结构被展平了。

  2. 使用 pd.concat

    df_tmp2 = pd.concat([df['TextID'], df2], axis=1)
    print(df_tmp2)
    

    输出结果为:

        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
    

    看起来很好,MultiIndex 列结构被保留为元组。但是检查一下列的类型:

    df_tmp2.columns
    

    输出结果为:

    Index(['TextID', ('Crisis_Level', 0), ('Special_Date', 0), ('Type', 0),
        ('Type', 1)],
        dtype='object')
    

    只是一个普通的 Index 类,而不是 MultiIndex 类。

  3. 使用 set_index

    将所有要保留的列转换为行索引,经过一些复杂的 apply 函数,然后使用 reset_index 恢复列:

    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)
    

    输出结果为:

        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
    

    检查一下列的类型:

    df_tmp3.columns
    

    输出结果为:

    MultiIndex(levels=[['Crisis_Level', 'Special_Date', 'Type', 'TextID'], [0, 1, '']],
            codes=[[3, 0, 1, 2, 2], [2, 0, 0, 0, 1]])
    

所以,如果你的apply函数将返回MultiIndex列,并且你想要保留它,那么你可能想尝试第三种方法。


0
只是为了补充一下,对我来说,在某些情况下使用unstack()方法是必要的,否则我只会得到一个包含字典的新列。
它的工作原理如下:
df.groupby('variable')['value'].apply(lambda grp: {
    'Min': grp.min(),
    'Median': grp.median(),
    'Max': grp.max()
}).unstack()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接