根据 pandas 中另一个数据框中的某些条件将值从一个数据框拆分到另一个数据框

Splitting values from one data frame to another data frame based on certain conditions in another data frame in pandas

我有两个数据帧 df1 和 df2,我想根据 df1 中的条件将 df2 的值调整为 df1。条件是基于4个不同的列,不同的ID有不同的条件 在 df1 中,我需要将 df2 中一列的值放入 df2 中,以便将值从 df2 中拆分并在 df1 中进行调整,并且每个 ID 值的总和应该在两个数据框中匹配。

所以我有以下格式的数据:

我想将值从 df2 带到 df1 并根据 df1 本身的开始日期、结束日期、开始时间和结束时间拆分它,并且每个 ID 的 df1 和 df2 中的总和应该相等。

预期输出

这是在 pandas 中创建的相同数据框。这两个表是输入值,我想要上面的预期结果。

df1 = pd.DataFrame({'ID': ["Ch1","Ch1","Ch1","Ch1","Ch1","Ch1","Ch2","Ch2","Ch2"],
               'Start Day': [1,1,1,6,6,6,1,1,1], 
               'End Day': [5,5,5,7,7,7,7,7,7], 
               'Start Time': [600,1200,1700,600,1200,1700,700,1200,1700], 
               'End Time': [1200,1700,2500,1200,1700,2500,1200,1700,2400]})
print(df1)

df2 = pd.DataFrame({'ID': ["Ch1","Ch1","Ch1","Ch2","Ch2","Ch2","Ch2","Ch2","Ch2","Ch2","Ch2","Ch2","Ch2","Ch2"],
                    'Start Day': [1,1,1,1,1,1,1,1,1,1,1,1,6,1],
                    'End Day': [7,7,7,5,5,5,5,5,5,5,5,5,7,7],
                    'Start Time': [600,1200,1700,800,900,1000,1100,1200,1300,1900,2000,2200,700,700],
                    'End Time': [1200,1700,2500,900,1000,1100,1200,1300,1400,2000,2200,2300,2400,2400],
                    'Values':[1125,2250,1125,346.5,346.5,346.5,346.5,346.5,346.5,189,189,346.5,1795.5,346.5]})
print(df2)

有人可以帮我解决这个问题吗?

计算:

从 df2 到 df1 在 df2 中说我在第 1 天到第 7 天的值为 1125,在 ch1 的时间段为 600 到 1200 我想将该值拆分为 df1 中的第 1 到 5 天和第 6 到 7 天,所以将值 1125 在 5 到 7 天和同一时间段内,我将值除以 7,然后乘以 5,因为时间段相同,即两个数据帧中的 600 到 1200,我不会进一步划分并保留将值 (1125/7*5) 或 (1125/df2(End Day- Start Day+1)*df1(End Day- Start Day+1)) 写入 df1 其中 Start Day 和 End day 分别为 1 到 5 和 time频段是 600 到 1200,该值是 1125 中的 803.6,

同样,对于 df1 中的第 6 天到第 7 天,我们将以相同的方式拆分 df2 的值,从 df2 值列中,我们将进行以下计算: (1125/7*2) or (1125/df2(End Day- Start Day+1)*df1(End Day- Start Day+1)) into df1 where Start Day and End day is 6 to 7 time bands are 600 到 1200,该值将是 1125 中的 321.4。

如果 df1 中的时间段发生变化,我们将在值中添加计算, 在 df1 中说,我希望开始日和结束日为 1 到 5,时间段为 700 到 1100,然后我将按以下方式将 df2 的值放入 df1:

(1125/7*5)*6/4 or (1125/df2(结束日-开始日+1)*df1(结束日-开始日+1))*df2(结束时间-开始时间)/df1(结束时间 - 开始时间)

此外,如果从 df2 我们有开始日结束日 1 到 7 以及开始时间和结束时间 600 到 1200 为 1125,而在 df1 中我们有开始日和结束日期 1 到 5 以及开始时间 700 到 1100 只有并且任何行中都没有其他日期带或时间带,那么在这种情况下,将整个 1125 值保留在 df1 本身的那一行中。

请帮助我处理这段代码和逻辑,我会非常充实。 提前致谢。

更新答案,在 objective 进一步澄清后:

这些结果与您提供的示例相符,因此我确信我们现在在同一页面上。
如果将它应用于巨大的数据集,这可能会有点慢,因为它调用 DataFrame.apply() 函数两次,遍历 df1 的每一行,对于 df1 的每一行,它遍历 df1 的每一行df2.[​​=12=]

我试图捕捉 day/time 块之间重叠的每个场景,这些场景需要以不同方式确定返回值。您需要检查我没有遗漏任何其他场景/边缘情况。

解决方法如下:

def getDF2ValueForTimeBlock(df1row, df2row):
    if df2row["ID"] == df1row["ID"]:
        
        #Case 1: df2 window entirely contained within df1 window
        if (
            (df2row["Start Day"] >= df1row["Start Day"]) 
            & (df2row["End Day"] <= df1row["End Day"])
            & (df2row["Start Time"] >= df1row["Start Time"]) 
            & (df2row["End Time"] <= df1row["End Time"])
        ):    
            return df2row["Values"]
        
        #Case 2: df1 window entirely contained within df2 window
        elif (
            (df2row["Start Day"] <= df1row["Start Day"]) 
            & (df2row["End Day"] >= df1row["End Day"])
            & (df2row["Start Time"] <= df1row["Start Time"]) 
            & (df2row["End Time"] >= df1row["End Time"])
        ):    
            #Return only proportion of df2 values after scaling down to span of df1
            dayspanratio = (df1row["End Day"] - df1row["Start Day"] + 1) / (df2row["End Day"] - df2row["Start Day"] + 1)
            hourspanratio = (df1row["End Time"] - df1row["Start Time"]) / (df2row["End Time"] - df2row["Start Time"])
            return df2row["Values"] * dayspanratio * hourspanratio
        
        
        #Case 3: partial overlap on Days, df2 time completely within df1 time boundaries
        elif(
            (
                (df1row["Start Day"] <= df2row["Start Day"] <= df1row["End Day"])
                | (df1row["Start Day"] <= df2row["End Day"] <= df1row["End Day"])
            )
            &(
                (df1row["Start Time"] <= df2row["Start Time"] <= df2row["End Time"] <= df1row["End Time"])
            )
        ):
            #Find proportion of df2 values allocable to overlapping width of df1 window 
            maxStartDay = max([df1row["Start Day"], df2row["Start Day"]])
            minEndDay = min([df1row["End Day"], df2row["End Day"]])
            
            dayspanratio = (minEndDay - maxStartDay + 1) / (df2row["End Day"] - df2row["Start Day"] + 1)
            hourspanratio = 1
            return df2row["Values"] * dayspanratio * hourspanratio
        
        
        
        #Case 4: df2 window partially overlapping with df1 window on both Days and Time
        elif(
            (
                (df1row["Start Day"] <= df2row["Start Day"] <= df1row["End Day"])
                | (df1row["Start Day"] <= df2row["End Day"] <= df1row["End Day"])
            )
            &(
                (df1row["Start Time"] <= df2row["Start Time"] <= df1row["End Time"])
                | (df1row["Start Time"] <= df2row["End Time"] <= df1row["End Time"])
                #for df2 time extending beyond df1 time span on both boundaries:
                | (df2row["Start Time"] <= df1row["Start Time"] <= df1row["End Time"] <= df2row["End Time"]) 
            )
        ):
            #Find proportion of df2 values allocable to overlapping width of df1 window 
            maxStartDay = max([df1row["Start Day"], df2row["Start Day"]])
            minEndDay = min([df1row["End Day"], df2row["End Day"]])
            
            maxStartTime = max([df1row["Start Time"], df2row["Start Time"]])
            minEndTime = min([df1row["End Time"], df2row["End Time"]])
            
            dayspanratio = (minEndDay - maxStartDay + 1) / (df2row["End Day"] - df2row["Start Day"] + 1)
            hourspanratio = (minEndTime - maxStartTime) / (df2row["End Time"] - df2row["Start Time"])
            return df2row["Values"] * dayspanratio * hourspanratio
        
        
        #Case 5: Channel ID matches, but no overlap in both days and time windows
        else:
            return 0
        
    else:
        #Case Different Channel
        return 0


df1["Values"] = df1.apply(
    lambda d1row: df2.apply(lambda d2row: getDF2ValueForTimeBlock(d1row, d2row), axis=1).sum(), axis=1
)

print(df1)

输出:

    ID  Start Day  End Day  Start Time  End Time       Values
0  Ch1          1        5         600      1200   803.571429
1  Ch1          1        5        1200      1700  1607.142857
2  Ch1          1        5        1700      2500   803.571429
3  Ch1          6        7         600      1200   321.428571
4  Ch1          6        7        1200      1700   642.857143
5  Ch1          6        7        1700      2500   321.428571
6  Ch2          1        7         700      1200  2016.000000
7  Ch2          1        7        1200      1700  1323.000000
8  Ch2          1        7        1700      2400  1606.500000