使用 groupby 重新采样非定时 df

Resample non timed df with a groupby

我想在我的 df 中按米计算 'grade' 的加权平均值。 我必须使用 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 花时间和回答,我试过你的代码,这里有更多细节:

  1. 根据我对这部分代码的理解:
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
  1. 我用我的完整数据集试过了,最后的长样本通常是未分割的(如下):

EDIT2:第一个 'from' 和最后一个 'to' 的行为 逻辑的更多细节,其中 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)

此代码非常原始。我刚开始工作。你可以在你的全套数据上试一试,看看这是否是你想要的。然后可以修改代码。 我不知道如何以 pandas 的方式操作它。我只知道编程 loop/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

我添加了更多行来测试它。使用的一些变量可以清除,但您首先要看看这是否是您想要的。

代码不漂亮。它按原样工作。我希望有更好的编码方式。这不是我可以在此编辑器上解释的单步逻辑。最好是使用 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