使用 groupby 重新采样非定时 df

Posted

技术标签:

【中文标题】使用 groupby 重新采样非定时 df【英文标题】:Resample non timed df with a groupby 【发布时间】:2021-11-19 04:45:48 【问题描述】:

我想在我的 df. 我必须使用 groupby 'name' 和 'grade' 是我尝试获取平均值的列。 'From' 和 'to' 是应用 'grade' 的垂直边界。

例子:

输入:

name   from   to   grade
 -       -     -     -
 A       0    1.5   1.0
 A      1.5   3.0   1.5
 A      3.0    4    1.7 
 B       0     3    1.6
 B       3    3.7   1.9
 B      3.7    5    2.0

想要的输出:

name   from   to    grade
 -       -     -      -
 A       0     1     1.0 
 A       1     2     1.25
 A       2     3     1.5
 A       3     4     1.7
 B       0     1     1.6
 B       1     2     1.6
 B       2     3     1.6
 B       3     4     1.93 (=1.9 x 0.7 + 2.0 x 0.3)
 B       4     5     2.0 

我正在使用 pandas 并尝试了“rolling_mean”,但效果不佳。

有什么想法吗?

编辑: 感谢 EBDS 的时间和回答,我尝试了您的代码,这里有更多详细信息:

    根据我对这部分代码的理解:
else:
  grade = r1['ni']*(r1['to'] - i) + r2['ni']*(to_val - r1['to'] )
  row = ['holeid': r1['holeid'],'from': i,'to': i+1,'ni': grade]

加权平均只取2个值,但1米内最多可以有10个值(但这只是我的理解,请确认)。例子: 输入:

name   from    to    grade
  -      -      -      -
  A     0.0   1.8     0.5
  A     1.8   2.15    1.0
  A     2.15  2.21    1.4
  A     2.21  2.5     1.6
  A     2.5   2.9     1.7
  A     2.9   3.2     1.0 

想要的输出:

name   from    to    grade
  -      -      -      -
  A     0      1.0    0.5
  A     1.0    2.0    0.6 (0.8 x 0.5 + 0.2 x 1.0)
  A     2.0    3.0    1.478 (0.15x1.0+0.06x1.4+0.29x1.6+0.4x1.7+0.1x1.0)
  A     3.0    4.0    1.0
    我用我的完整数据集进行了尝试,最后的长样本通常是不可分割的(如下):

EDIT2:第一个“来自”和最后一个“到”的行为 更多逻辑细节,其中 5.6 是第一个,7.9 是最后一个

输入:

name  from   to    grade
 -      -     -      -
 A     5.6   5.7    1.4
 A     5.7   5.9    1.0
 A     5.9   6.2    1.3
 A     6.2   6.9    1.6
 A     6.9   7.1    1.7
 A     7.1   7.6    1.0
 A     7.6   7.9    1.9

想要的输出:

name    from  to    grade
  -      -     -      -
  A     5.6   6.0   1.175 ((0.1x1.4 + 0.2x1.0 + 0.1x1.3)/0.4) 
  A     6.0   7.0   1.55 ((0.2x1.3 + 0.7x1.6 + 0.1x1.7)/1) 
  A     7.0   7.9   1.378 ((0.1x1.7 + 0.5x1.0 + 0.3x1.9)/0.9)

【问题讨论】:

嗨,你的意思是你想要weighted average 来自你的列fromto 吗? 我在下面提出了一个可能的解决方案。如果这是您想要的,请通过检查接受答案。谢谢。 哇!这比我想象的要复杂,非常感谢!我试了一下,然后回来检查它是否有效。 我不明白你的计算。对于 (0.8 x 0.5 + 0.2 x 1.0),它没有分数 from 和 to,为什么需要计算?我也不明白你是怎么得到这个 (0.15x1.0+0.06x1.4+0.29x1.6+0.4x1.7+0.1x1.0) 计算的?你的问题很有趣。无论如何要更好地理解这个问题? @VncJ 一些新的 /0.4, /0.9 .. 我可以说你除以 0.4 和 0.9 仅适用于非整数首先“从”和最后“到”吗?其余部分除以“1”,因此可以忽略。因为在前面给出的所有示例中,都不需要除以,我怀疑是因为它除以“1”。似乎我们有 12 小时的时差。 . 我在格林威治标准时间 +8 【参考方案1】:

这段代码非常原始。我刚开始工作。您可以在您的全套数据上进行尝试,看看是否是您想要的。然后可以修改代码。 我不知道如何以熊猫方式操纵它。我只知道编程循环/ifelse 方式。可能有人可以给你一些更优雅的东西。如果没有,你可以使用

def cal_grade(df):
    df1 = pd.DataFrame(columns = ['name','from','to','grade'])
    for r in range(len(df)-1):
        r1 = df.iloc[r,:]
        r2 = df.iloc[r+1,:]
        from_val = math.ceil(r1['from'])
        to_val = math.ceil(r1['to'])
        if to_val == (r1['to']):
            for i in range(from_val,to_val):
                row = ['name': r1['name'],'from':i,'to': i+1,'grade': r1['grade']]
                df1 = df1.append(row,ignore_index=True)
        else:
            for i in range(from_val,to_val):
                if i < to_val-1:
                    row = ['name': r1['name'],'from': i,'to': i+1,'grade': r1['grade']]
                else:
                    grade = r1['grade']*(r1['to'] - i) + r2['grade']*(to_val - r1['to'] )
                    row = ['name': r1['name'],'from': i,'to': i+1,'grade': grade]
                df1 = df1.append(row,ignore_index=True)
    df1 = df1.append(df.iloc[-1,:],ignore_index=True)
    from_val = df1['from'].iloc[-1]
    from_val1 = math.ceil(from_val)
    df1.replace(from_val,from_val1,inplace=True)
    return df1
df
df.groupby('name').apply(cal_grade).reset_index(drop=True)

输出

    name    from    to      grade
0   A       0.0     1.5     1.0
1   A       1.5     3.0     1.5
2   A       3.0     4.0     1.7
3   B       0.0     3.0     1.6
4   B       3.0     3.7     1.9
5   B       3.7     5.0     2.0
6   B       5.0     8.3     5.0
7   B       8.3     10.5    8.0
8   B       10.5    14.0    10.0

    name    from    to      grade
0   A       0       1.0     1.00
1   A       1       2.0     1.25
2   A       2       3.0     1.50
3   A       3       4.0     1.70
4   B       0       1.0     1.60
5   B       1       2.0     1.60
6   B       2       3.0     1.60
7   B       3       4.0     1.93
8   B       4       5.0     2.00
9   B       5       6.0     5.00
10  B       6       7.0     5.00
11  B       7       8.0     5.00
12  B       8       9.0     7.10
13  B       9       10.0    8.00
14  B       10      11.0    9.00
15  B       11      14.0    10.00

我添加了更多行来测试它。其中一些使用的变量可以被清理,但你先看看这是否是你想要的。

【讨论】:

谢谢!我有一个问题,此代码是否适用于 2 个以上的等级值?我最多可以在 1 米的深度从/深度到喜欢:name from to grade A 0 0.1 1.5 A 0.1 0.5 2.0 A 0.5 0.6 1.0 A 0.6 1.0 1.4 (抱歉格式不好,我现在甚至无法编辑,但基本上就像 1 米距离和距离之间的 10 个值) 也可以在任何地方剪切,例如:从 = 0.95,到 = 1.2 另一件事:例如,如果我有一行 from = 5 和 a to = 8,则输出应附加 3 行 1.我已经用 (5,8,3) 进行了测试,它能够“扩展”到多行。 2.你的初始问题数据显示1个单元的输出数据边界,即输出为(1,2),(2,3),(3,4),(4,5)....所以循环逻辑将数据拆分为 1 为单位(如在您的示例输出中)。您稍后在评论中提供的数据是子数据,即边界小于 1 的几个输入行。那么预期输出是多少?没有预期的输出,就无法知道是否可以完成。【参考方案2】:

代码不漂亮。它按原样工作。我希望有更好的方法来编码。这不是我可以在此编辑器上解释的单步逻辑。最好是使用 IDE 来单步执行。您必须修改您的网站。其中一些东西,比如我打印'bug',只需编辑出来。一些用于测试目的。祝你的网站好运!

from io import StringIO
from math import floor,ceil
import pandas as pd
import numpy as np

from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity='all'

df = pd.read_excel('Resample non timed df with a groupby (VNC J).xlsx',sheet_name='NW04',dtype='from':np.float64,'to':np.float64) \
        .rename(columns='from':'from1','to':'to1').reindex()
df['from2'] = df['from1'].apply(floor)
df['to2'] = df['to1'].apply(floor)
df

    name    from1   to1 grade   from2   to2
0   A   0.00    3.10    7.36    0   3
1   A   3.10    3.20    5.18    3   3
2   A   3.20    3.30    7.45    3   3
3   A   3.30    3.60    8.91    3   3
4   A   3.60    4.00    7.82    3   4
5   B   4.00    5.00    8.00    4   5
6   C   5.00    6.00    8.18    5   6
7   D   6.00    7.10    7.09    6   7
8   E   7.10    7.90    6.55    7   7
9   E   7.90    8.10    9.09    7   8
10  F   8.10    12.50   1.09    8   12
11  G   12.50   15.00   7.09    12  15
12  H   15.00   15.01   3.45    15  15
13  I   15.01   15.10   13.00   15  15

df1 = df.copy(deep=True)

lst = []
accum = 0
multi_row_to = -1
multi_row_last = 0

for r in range(len(df)):
    name = df.loc[r, 'name']
    from1 = df.loc[r, 'from1']
    to1 = df.loc[r, 'to1']
    from2 = df.loc[r, 'from2']
    to2 = df.loc[r, 'to2']
    grade = df.loc[r, 'grade']
    to_span = to2 - from2

    if ((multi_row_to != -1) and (to2 != multi_row_last)):
        multi_row_to = -1

    if (r == len(df)-1) and (floor(from2)==floor(to2)):  #  last row with small from/to
        multi_row_to = -1
        grade_out = ((to1 - from1) * grade + accum) / (to1 - floor(to1))
        accum = 0
        lst.append((name, r, from1, to1, grade, from2, to1, grade_out, accum))
    elif ((to1 % 1) == 0) and (to_span < 2):  # no multi-rows (wrong to1 == to2)
        multi_row_to = -1
        grade_out = (to2 - from1) * grade + accum
        accum = (to1 - to2) * grade
        lst.append((name, r, from1, to1, grade, from2, to2, grade_out, accum))
    elif to1 != to2 and to_span < 2:  # continue multi-rows
        if multi_row_to == to2:
            if to2 > from2:
                multi_row_to = to2 # (next value)
                grade_out = (to2 - from1) * grade + accum
                accum = (to1 - to2) * grade
                lst.append((name, r, from1, to1, grade, from2, to2, grade_out, accum))
            else:
                grade_out = '-'
                accum += (to1 - from1) * grade
                lst.append((name, r, from1, to1, grade, from2, to2, grade_out, accum))
        elif multi_row_to == -1: # entered multi_row
            multi_row_last = to2
            if to2 > from2:
                multi_row_to = to2 # (next value)
                grade_out = (to2 - from1) * grade + accum
                accum = (to1 - to2) * grade
                lst.append((name, r, from1, to1, grade, from2, to2, grade_out, accum))
            else:
                multi_row_to = to2
                grade_out = '-'
                accum += (to1 - from1) * grade ### Changed here bug
                lst.append((name, r, from1, to1, grade, from2, to2, grade_out, accum))
        else: # switch number in multi_row

            multi_row_to = to2
            grade_out = (to2 - from1) * grade + accum
            accum = (to1 - to2) * grade
            lst.append((name, r, from1, to1, grade, from2, to2, grade_out, accum))
    elif to_span >= 2:  # explod grade
        explode_first = True
        for i in range(from2, to2+1):
            if explode_first:  # i == from1:  # first
                explode_first = False
                grade_out = (i + 1 - from1) * grade + accum
                accum = 0
                lst.append((name, r, from1, to1, grade, i, i + 1, grade_out, accum))
            elif ((i == to2) and (floor(to1) != to1)):  # last decimal
                grade_out = '-'
                accum = (to1 - to2) * grade  # changed here
                lst.append((name, r, from1, to1, grade, i, to1, grade_out, accum))
                # if accum != 0:
                #     grade_out = accum
                #     accum = 0
                #     lst.append((name, r + 1, '-', '-', '-', to2, to1, grade_out, accum))
            elif ((i == (to2-1)) and (floor(to1) == to1)) : # whole number
                grade_out = grade
                accum = (to2 - to1) * grade
                lst.append((name, r, from1, to1, grade, i, i + 1, grade_out, accum))
                break   #  no choice, cater to last number decimal
            else:  # middle
                if accum != 0:
                    grade_out = (i + 1 - from1) * grade + accum
                    accum = 0
                    lst.append((name, r, from1, to1, grade, i, i + 1, grade_out, accum))
                elif (i + 1) <= to1:
                    grade_out = grade
                    accum = 0  # redundant but leave it
                    lst.append((name, r, from1, to1, grade, i, i + 1, grade_out, accum))
                # elif (i + 1) > to1:
                #     grade_out = (to1 - from2) * grade
                #     accum = 0
                #     lst.append((name, r, from1, to1, grade, i, to1, grade_out, accum))
                else:
                    print('Bug 1')
    else:
        print('Bug 2')
        break

if accum != 0:
    grade_out = accum / (to1-to2)
    accum = 0
    lst.append((name, r + 1, '-', '-', '-', to2, to1, grade_out, accum))


col = ['name', 'r', 'from1', 'to1', 'grade', 'from2', 'to2', 'grade_out', 'accum']
df2 = pd.DataFrame()
for i in lst:
    df2 = df2.append(pd.DataFrame(i).T)
df2.columns = col
df2.reset_index(drop=True, inplace=True)
df2

    name    r   from1   to1 grade   from2   to2 grade_out   accum
0   A   0   0.0 3.1 7.36    0   1   7.36    0
1   A   0   0.0 3.1 7.36    1   2   7.36    0
2   A   0   0.0 3.1 7.36    2   3   7.36    0
3   A   0   0.0 3.1 7.36    3   3.1 -   0.736
4   A   1   3.1 3.2 5.18    3   3   -   1.254
5   A   2   3.2 3.3 7.45    3   3   -   1.999
6   A   3   3.3 3.6 8.91    3   3   -   4.672
7   A   4   3.6 4.0 7.82    3   4   7.8 0.0
8   B   5   4.0 5.0 8.0 4   5   8.0 0.0
9   C   6   5.0 6.0 8.18    5   6   8.18    0.0
10  D   7   6.0 7.1 7.09    6   7   7.09    0.709
11  E   8   7.1 7.9 6.55    7   7   -   5.949
12  E   9   7.9 8.1 9.09    7   8   6.858   0.909
13  F   10  8.1 12.5    1.09    8   9   1.89    0
14  F   10  8.1 12.5    1.09    9   10  1.09    0
15  F   10  8.1 12.5    1.09    10  11  1.09    0
16  F   10  8.1 12.5    1.09    11  12  1.09    0
17  F   10  8.1 12.5    1.09    12  12.5    -   0.545
18  G   11  12.5    15.0    7.09    12  13  4.09    0
19  G   11  12.5    15.0    7.09    13  14  7.09    0
20  G   11  12.5    15.0    7.09    14  15  7.09    0.0
21  H   12  15.0    15.01   3.45    15  15  -   0.0345
22  I   13  15.01   15.1    13.0    15  15.1    12.045  0

df3 = pd.DataFrame()
newto = -1 # df2.loc[0,'to2']

for i in range(len(df2)):
#     if df2.loc[i,'to2'] != newto:
    if df2.loc[i,'grade_out'] != '-':
        df3 = df3.append(df2.loc[i,:])
        newto = df2.loc[i,'to2']
        
df3.drop(columns=['r','accum'],inplace=True)
df3

    name    from1   to1 grade   from2   to2 grade_out
0   A   0.00    3.1 7.36    0.0 1.0 7.360
1   A   0.00    3.1 7.36    1.0 2.0 7.360
2   A   0.00    3.1 7.36    2.0 3.0 7.360
7   A   3.60    4.0 7.82    3.0 4.0 7.800
8   B   4.00    5.0 8.00    4.0 5.0 8.000
9   C   5.00    6.0 8.18    5.0 6.0 8.180
10  D   6.00    7.1 7.09    6.0 7.0 7.090
12  E   7.90    8.1 9.09    7.0 8.0 6.858
13  F   8.10    12.5    1.09    8.0 9.0 1.890
14  F   8.10    12.5    1.09    9.0 10.0    1.090
15  F   8.10    12.5    1.09    10.0    11.0    1.090
16  F   8.10    12.5    1.09    11.0    12.0    1.090
18  G   12.50   15.0    7.09    12.0    13.0    4.090
19  G   12.50   15.0    7.09    13.0    14.0    7.090
20  G   12.50   15.0    7.09    14.0    15.0    7.090
22  I   15.01   15.1    13.00   15.0    15.1    12.045

【讨论】:

请接受答案。谢谢。 嘿EBDS,我接受了。我找到了一种更流畅、更简单的方法来完成它,我稍后会分享。 1) 每次从整数中重新采样以追加行 2) 使用双组名和 step_from 均值

以上是关于使用 groupby 重新采样非定时 df的主要内容,如果未能解决你的问题,请参考以下文章

使用特定的开始/结束日期以及 groupby 重新采样数据框

Pandas 0.18.1 groupby 和多级聚合错误重新采样

使用 xarray 重新采样非标准 CFTimeIndex 日历(360 天,无闰年)以供 pandas 使用的方法

将数据帧重新采样为具有任意期末月份的 n 个月期间

重新采样一个 numpy 数组

1.7 非平衡数据的处理方法大全