以最高效的方式比较两个pandas数据框架

4

让我们考虑两个Pandas数据帧:

import numpy as np
import pandas as pd

df = pd.DataFrame([1, 2, 3, 2, 5, 4, 3, 6, 7])

check_df = pd.DataFrame([3, 2, 5, 4, 3, 6, 4, 2, 1])

如果想要实现以下功能:
  1. 如果 df[1] > check_df[1] 或者 df[2] > check_df[1] 或者 df[3] > check_df[1],则将 df 的值赋为 1,否则为 0。
  2. 如果 df[2] > check_df[2] 或者 df[3] > check_df[2] 或者 df[4] > check_df[2],则将 df 的值赋为 1,否则为 0。
  3. 我们对 DataFrame 的末尾应用相同的算法。
我的原始代码如下:
df_copy = df.copy()
for i in range(len(df) - 3):
    moving_df = df.iloc[i:i+3]
    if (moving_df >check_df.iloc[i]).any()[0]:
        df_copy.iloc[i] = 1
    else:
        df_copy.iloc[i] = -1
df_copy


    0
0   -1
1   1
2   -1
3   1
4   1
5   -1
6   3
7   6
8   7

请问是否有不使用循环的可能性,您能给我一些建议吗?

2个回答

3

如果我理解正确的话,这可以轻松使用 rolling.min 完成:

df['out'] = np.where(df[0].rolling(N, min_periods=1).max().shift(1-N).gt(check_df[0]),
                     1, -1)

输出:

   0  out
0  1   -1
1  2    1
2  3   -1
3  2    1
4  5    1
5  4   -1
6  3    1
7  6   -1
8  7   -1

保持最后几个项目不变:

m = df[0].rolling(N).max().shift(1-N)
df['out'] = np.where(m.gt(check_df[0]),
                     1, -1)
df['out'] = df['out'].mask(m.isna(), df[0])

输出:

   0  out
0  1   -1
1  2    1
2  3   -1
3  2    1
4  5    1
5  4   -1
6  3    1
7  6    6
8  7    7

这与原始结果不匹配。 - Scott Hunter
@Scott 我正在更新那部分。 - mozway
1
N是什么? ;)) - Lucian
你快要完成了... :) - Scott Hunter
1
@Scott 如果你说的是第6行,我不确定这是否是原始结果所需的(比较是可能的),无论如何,对我来说这看起来像是一个实现细节,重要的逻辑是前面的部分,然后操作者可以根据需要掩盖任何值。 - mozway
显示剩余5条评论

0

虽然@mozway已经提供了一个非常聪明的解决方案,但我也想分享一下我的方法,这个方法是受到this post的启发。

你可以创建自己的对象来比较一个序列和一个滚动序列。比较可以通过典型的运算符进行,即>、<或==。如果至少有一个比较成立,该对象将返回预定义的值(在列表returns_tf中给出,如果比较为真,则返回第一个元素,如果为假,则返回第二个元素)。

可能的代码:

import numpy as np
import pandas as pd

df = pd.DataFrame([1, 2, 3, 2, 5, 4, 3, 6, 7])
check_df = pd.DataFrame([3, 2, 5, 4, 3, 6, 4, 2, 1])

class RollingComparison:
    
    def __init__(self, comparing_series: pd.Series, rolling_series: pd.Series, window: int):
        self.comparing_series = comparing_series.values[:-1*window]
        self.rolling_series = rolling_series.values
        self.window = window
        
    def rolling_window_mask(self, option: str = "smaller"):
        shape = self.rolling_series.shape[:-1] + (self.rolling_series.shape[-1] - self.window + 1, self.window)
        strides = self.rolling_series.strides + (self.rolling_series.strides[-1],)
        rolling_window = np.lib.stride_tricks.as_strided(self.rolling_series, shape=shape, strides=strides)[:-1]
        rolling_window_mask = (
            self.comparing_series.reshape(-1, 1) < rolling_window if option=="smaller" else (
                self.comparing_series.reshape(-1, 1) > rolling_window if option=="greater" else self.comparing_series.reshape(-1, 1) == rolling_window
            )
        )
        return rolling_window_mask.any(axis=1)
    
    def assign(self, option: str = "rolling", returns_tf: list = [1, -1]):
        mask = self.rolling_window_mask(option)
        return np.concatenate((np.where(mask, returns_tf[0], returns_tf[1]), self.rolling_series[-1*self.window:]))

可以按照以下方式完成作业:

roller = RollingComparison(check_df[0], df[0], 3)
check_df["rolling_smaller_checking"] = roller.assign(option="smaller")
check_df["rolling_greater_checking"] = roller.assign(option="greater")
check_df["rolling_equals_checking"] = roller.assign(option="equal")

输出(列rolling_smaller_checking等于您所需的输出):

    0   rolling_smaller_checking  rolling_greater_checking  rolling_equals_checking
0   3   -1                        1                         1
1   2    1                       -1                         1
2   5   -1                        1                         1
3   4    1                        1                         1
4   3    1                       -1                         1
5   6   -1                        1                         1
6   4    3                        3                         3
7   2    6                        6                         6
8   1    7                        7                         7

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