Pandas DataFrame 对多列应用函数并输出多列

9

我一直在搜索最佳的方法,以应用一个函数,该函数获取多个单独的Pandas数据框列,并在同一数据框中输出多个新列。假设我有以下内容:

def apply_func_to_df(df):
    df[['new_A', 'new_B']] = df.apply(lambda x: transform_func(x['A'], x['B'], x['C']), axis=1)

def transform_func(value_A, value_B, value_C):
    # do some processing and transformation and stuff
    return new_value_A, new_value_B

我试图将上述函数应用于整个DataFrame df,以输出2个新列。但是,这可以普遍适用于一个使用案例/函数,该函数接受n个DataFrame列,并将m个新列输出到同一DataFrame。
以下是我一直在尝试的内容(成功程度不同):
  • 创建一个Pandas系列以进行函数调用,然后将其附加到现有DataFrame,
  • 压缩输出列(但在我的当前实现中存在一些问题)
  • 重写基本函数transform_func,明确预期行(即字段)ABC,然后对df应用该函数:
def transform_func_mod(df_row):
    # do something with df_row['A'], df_row['B'], df_row['C]
    return new_value_A, new_value_B

我希望有一个通用的、符合Python风格的方法来完成这个任务,同时考虑到性能(内存和时间)。由于我不熟悉Pandas,所以我一直在努力解决这个问题,任何建议都将不胜感激。

1
当你写下“函数接受多个独立的Pandas DataFrame列,并在同一DataFrame中输出多个新列”时,你是说你的函数操作列并返回新列(即Series对象)吗?换句话说,你的函数已经向量化了吗? - NicholasM
1
不是这种情况。我正在尝试弄清楚不同方法的各种权衡(算法复杂度、内存、开销等),包括应用、使用zip、NumPy与Pandas向量化等,并希望选择最佳方法来解决我的问题。 - qxzsilver
2个回答

7
请按以下方式编写您的 transform_func
  • 它应该有一个参数 - 当前行,
  • 此函数可以从当前行中读取单个列并对其进行任何处理,
  • 返回的对象应该是一个带有以下内容的Series
    • 值 - 您想要返回的任何内容,
    • 索引 - 目标列名称。

例如:假设所有3列都是string类型,请将AB列连接起来,在C中添加"some string":

def transform_func(row):
    a = row.A; b = row.B; c = row.C;
    return pd.Series([ a + b, c + '_xx'], index=['new_A', 'new_B'])

只获取新值,请将此函数应用于每一行:

df.apply(transform_func, axis=1)

请注意,生成的DataFrame保留原始行的键(我们将在下一步中使用此功能)。
或者,如果您想要将这些新列添加到您的DataFrame中,请将您的df与上述应用程序的结果连接,在原始df下保存连接结果。
df = df.join(df.apply(transform_func, axis=1))

根据03:36:34Z的评论进行编辑

使用zip可能是最慢的选项。基于行的函数应该更快,而且更直观。 可能最快的方法是分别为每个列编写两个向量化表达式。在这种情况下,可以使用以下类似方式:

df['new_A'] = df.A + df.B
df['new_B'] = df.C + '_xx'

通常问题在于行级函数是否可以表示为矢量化表达式(如上所述)。如果是“否定”的情况,您可以应用行级函数。

要比较每个解决方案的速度,请使用 %timeit


我也想知道不同潜在方法处理问题的权衡是什么。例如,如果有的话,通过zip(*)的方式如何实现?与编写基于行的函数并使用.apply相比,这种方法的性能如何?是否有向量化的方法可以改善算法时间复杂度和内存使用情况?我知道我可以使用你提到的方法,但我想尝试选择最佳用例。 - qxzsilver
谢谢你的有用提示。我会看一下 Pandas apply 是如何实现的,以尝试了解算法复杂度,以及 zip 和 zip(*) 的确切工作方式(我仍然不完全确定它的工作原理)。 - qxzsilver

4

这个问题似乎与这个问题有些相关。我参考了@spen.smith在这个答案中的评论。

df = pd.DataFrame([[1,2,3], [2,3,4], [3,5,7]], columns = ['A', 'B', 'C'])
print(df)

   A  B  C
0  1  2  3
1  2  3  4
2  3  5  7

不要修改函数的返回值,只需像平常一样创建它。
def add_subtract(args):
    arg1, arg2 = args

    ret1 = arg1 + arg2
    ret2 = arg1 - arg2

    return ret1, ret2

查看使用apply的输出结果。选项result_type ='expand'将结果以数据帧形式返回,而不是元组序列。

print(df[['B', 'C']].apply(add_subtract, axis=1, result_type='expand'))

    0  1
0   5 -1
1   7 -1
2  12 -2

我们可以通过转置操作,将apply的输出列分配给两个新系列,然后访问这些值。转置是必要的,因为默认情况下调用values会将每一行视为列表,而我们希望将每一列作为一个列表。因此,最终表达式为:
df['D'], df['E'] = df[['B', 'C']].apply(add_subtract, axis=1, result_type='expand').transpose().values
print(df)

   A  B  C   D  E
0  1  2  3   5 -1
1  2  3  4   7 -1
2  3  5  7  12 -2

解决我的问题 - Yiling Liu

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