Pandas应用、滚动、分组,具有多个输入和多个输出列

2024-09-30 12:12:00 发布

您现在位置:Python中文网/ 问答频道 /正文

在过去的一周里,我一直在努力使用apply在整个pandas数据帧上使用函数,包括rollingwindows、groupby,尤其是多输入列和多输出列。我发现了很多关于这个话题的问题,还有很多老生常谈的问题;过时的答案。因此,我开始为x输入的每个可能组合创建一个笔记本;输出、滚动、滚动和;groupby联合起来,我也把重点放在了性能上。因为我不是唯一一个在这些问题上苦苦挣扎的人,所以我想我会在这里提供我的解决方案,并提供工作示例,希望它能帮助任何现有/未来的熊猫用户


Tags: 数据函数答案用户重点示例pandas笔记本
1条回答
网友
1楼 · 发布于 2024-09-30 12:12:00

重要注意事项

  1. apply&;熊猫中的滚动具有非常高的输出要求。您必须返回一个值。不能返回pd.Series,不能返回列表,不能返回数组,不能返回数组中的数组,只能返回一个值,例如一个整数。当尝试为多个列返回多个输出时,这一要求使得很难获得有效的解决方案。我不明白为什么它对“申请&;“滚动”,因为没有滚动,“应用”没有此要求。一定是由于某些内部功能
  2. "应用及;“滚动”与多个输入列组合根本不起作用!假设一个数据框有2列6行,您希望应用一个滚动窗口为2的自定义函数。您的函数应该得到一个输入数组,其中包含2x2个值-每列2个值,对应2行。但是熊猫似乎不能同时处理滚动和多个输入列。我试图使用参数使其工作,但:
    • Axis=0,将调用每列的函数。在上面描述的dataframe中,它将调用您的函数10次(不是12次,因为rolling=2),因为它是每列的,所以它只提供该列的2个滚动值
    • Axis=1,将按行调用函数。这可能是您想要的,但pandas不会提供2x2输入。它实际上完全忽略了滚动,只提供一行2列的值
  3. 当对多个输入列使用“apply”时,可以提供一个名为raw(boolean)的参数。默认情况下为False,这意味着输入将是pd.Series,因此在值旁边包含索引。如果不需要索引,可以将raw设置为True以获得Numpy数组,这通常可以获得更好的性能
  4. 当结合“滚动和;groupby',它返回一个多索引系列,不能轻松地用作新列的输入。最简单的解决方案是附加一个reset_索引(drop=True),作为回答&;此处注释(Python - rolling functions for GroupBy object
  5. 你可能会问我,你什么时候会想要使用一个滚动的、groupby自定义的、具有多个输出的函数!?答:我最近不得不对一个包含500万条记录的数据集(速度/性能很重要)进行滑动窗口(滚动)傅里叶变换,数据集中有不同的批次(groupby)。我需要节省电力和能源;傅里叶变换在不同列中的相位(多个输出)。大多数人可能只需要下面的一些基本示例,但我相信,特别是在机器学习/数据科学领域,更复杂的示例可能会有用
  6. 请告诉我您是否有更好、更清晰或更快的方法来执行以下任何解决方案。我会更新我的答案,我们都会受益


代码示例

让我们首先创建一个dataframe,它将在下面的所有示例中使用,包括groupby示例的group列。 对于滚动窗口和多个输入/输出列,我在下面的所有代码示例中仅使用2,但显然,这可以是任意数字>;1.

df = pd.DataFrame(np.random.randint(0,5,size=(6, 2)), columns=list('ab'))
df['group'] = [0, 0, 0, 1, 1, 1]
df = df[['group', 'a', 'b']]

它将如下所示:

group   a   b
0   0   2   2
1   0   4   1
2   0   0   4
3   1   0   2
4   1   3   2
5   1   3   0


输入1列,输出1列

基础

def func_i1_o1(x):    
    return x+1

df['c'] = df['b'].apply(func_i1_o1)


滚动

def func_i1_o1_rolling(x):
    return (x[0] + x[1])

df['d'] = df['c'].rolling(2).apply(func_i1_o1_rolling, raw=True)


Roling&;Groupby

将重置索引解决方案(见上文注释)添加到滚动功能中

df['e'] = df.groupby('group')['c'].rolling(2).apply(func_i1_o1_rolling, raw=True).reset_index(drop=True)




输入2列,输出1列

基础

def func_i2_o1(x):
    return np.sum(x)

df['f'] = df[['b', 'c']].apply(func_i2_o1, axis=1, raw=True)


滚动

如上文注释第2点所述,2个输入没有“正常”解决方案。下面的解决方法使用“raw=False”来确保输入是pd.Series,这意味着我们还可以得到值旁边的索引。这使我们能够从其他列中以正确的索引获取要使用的值

def func_i2_o1_rolling(x):
    values_b = x
    values_c = df.loc[x.index, 'c'].to_numpy()
    return np.sum(values_b) + np.sum(values_c)

df['g'] = df['b'].rolling(2).apply(func_i2_o1_rolling, raw=False)


滚动&;Groupby

将重置索引解决方案(见上文注释)添加到滚动功能中

df['h'] = df.groupby('group')['b'].rolling(2).apply(func_i2_o1_rolling, raw=False).reset_index(drop=True)




输入1列,输出2列

基础

您可以通过返回pd系列来使用“正常”解决方案:

def func_i1_o2(x):
    return pd.Series((x+1, x+2))

df[['i', 'j']] = df['b'].apply(func_i1_o2)

或者你也可以使用zip/元组组合,这大约快8倍

def func_i1_o2_fast(x):
    return x+1, x+2

df['k'], df['l'] = zip(*df['b'].apply(func_i1_o2_fast))


滚动

正如上面注释中第1点所解释的,如果我们想在使用滚动&;时返回超过1个值,我们需要一种变通方法;组合应用。我找到了两个有效的解决方案

1

def func_i1_o2_rolling_solution1(x):
    output_1 = np.max(x)
    output_2 = np.min(x)
    # Last index is where to place the final values: x.index[-1]
    df.at[x.index[-1], ['m', 'n']] = output_1, output_2
    return 0

df['m'], df['n'] = (np.nan, np.nan)
df['b'].rolling(2).apply(func_i1_o2_rolling_solution1, raw=False)

优点:一切都在一个函数内完成。
Cons:您必须先创建列,因为它不使用raw输入,所以创建列的速度较慢

2

rolling_w = 2
nan_prefix = (rolling_w - 1) * [np.nan]
output_list_1 = nan_prefix.copy()
output_list_2 = nan_prefix.copy()

def func_i1_o2_rolling_solution2(x):
    output_list_1.append(np.max(x))
    output_list_2.append(np.min(x))
    return 0

df['b'].rolling(rolling_w).apply(func_i1_o2_rolling_solution2, raw=True)
df['o'] = output_list_1
df['p'] = output_list_2

优点:它使用原始输入,使速度提高一倍左右。由于它不使用索引来设置输出值,代码看起来更清晰(至少在我看来)。
缺点:您必须自己创建nan前缀,这需要更多的代码行。


滚动&;Groupby

通常,我会使用上面更快的第二种解决方案。但是,因为我们组合组和滚动这意味着您必须手动设置楠/零点(取决于组的数量)在数据集中的某个地方的右边的索引。在我看来,当组合滚动、groupby和多个输出列时,第一个解决方案更容易,并自动解决自动NAN/分组问题。最后,我再次使用reset_索引解决方案

def func_i1_o2_rolling_groupby(x):
    output_1 = np.max(x)
    output_2 = np.min(x)
    # Last index is where to place the final values: x.index[-1]
    df.at[x.index[-1], ['q', 'r']] = output_1, output_2
    return 0

df['q'], df['r'] = (np.nan, np.nan)
df.groupby('group')['b'].rolling(2).apply(func_i1_o2_rolling_groupby, raw=False).reset_index(drop=True)




输入2列,输出2列

基础

我建议使用与i1_o2相同的“快速”方式,唯一的区别是您可以使用两个输入值

def func_i2_o2(x):
    return np.mean(x), np.median(x)

df['s'], df['t'] = zip(*df[['b', 'c']].apply(func_i2_o2, axis=1))


滚动

由于我使用了一个变通方法来应用与多个输入的滚动,并且我使用了另一个变通方法来应用与多个输出的滚动,您可以猜到我需要将它们组合在一起进行此操作。
1.使用索引从其他列获取值(请参见func_i2_o1_滚动)
2.在正确的索引上设置最终的多个输出(请参见func_i1_o2_rolling_solution1)

def func_i2_o2_rolling(x):
    values_b = x.to_numpy()
    values_c = df.loc[x.index, 'c'].to_numpy()
    output_1 = np.min([np.sum(values_b), np.sum(values_c)])
    output_2 = np.max([np.sum(values_b), np.sum(values_c)])    
    # Last index is where to place the final values: x.index[-1]
    df.at[x.index[-1], ['u', 'v']] = output_1, output_2
    return 0

df['u'], df['v'] = (np.nan, np.nan)
df['b'].rolling(2).apply(func_i2_o2_rolling, raw=False)


滚动&;Groupby

将重置索引解决方案(见上文注释)添加到滚动功能中

def func_i2_o2_rolling_groupby(x):
    values_b = x.to_numpy()
    values_c = df.loc[x.index, 'c'].to_numpy()
    output_1 = np.min([np.sum(values_b), np.sum(values_c)])
    output_2 = np.max([np.sum(values_b), np.sum(values_c)])    
    # Last index is where to place the final values: x.index[-1]
    df.at[x.index[-1], ['w', 'x']] = output_1, output_2
    return 0

df['w'], df['x'] = (np.nan, np.nan)
df.groupby('group')['b'].rolling(2).apply(func_i2_o2_rolling_groupby, raw=False).reset_index(drop=True)

相关问题 更多 >

    热门问题