在pandas中根据约束条件生成一列

6
F_Date      B_Date      col   is_B
01/09/2019  02/08/2019  2200    1
01/09/2019  03/08/2019  672     1
02/09/2019  03/08/2019  1828    1
01/09/2019  04/08/2019  503     0
02/09/2019  04/08/2019  829     1
03/09/2019  04/08/2019  1367    0
02/09/2019  05/08/2019  559     1
03/09/2019  05/08/2019  922     1
04/09/2019  05/08/2019  1519    0
01/09/2019  06/08/2019  376     1

我希望生成一列c_a,使得对于航班日期的第一个条目,初始值为25000,并根据col值递减。例如:

期望输出:

F_Date      B_Date      col   is_B   c_a
01/09/2019  02/08/2019  2200    1    25000
01/09/2019  03/08/2019  672     1    25000 - 2200
02/09/2019  03/08/2019  1828    1    25000
01/09/2019  04/08/2019  503     0    25000 - 2200 - 672
02/09/2019  04/08/2019  829     1    25000 - 1828
03/09/2019  04/08/2019  1367    0    25000
02/09/2019  05/08/2019  559     1    25000 - 1828 - 829
03/09/2019  05/08/2019  922     1    25000 (since last value had is_B as 0)
04/09/2019  05/08/2019  1519    0    25000
01/09/2019  06/08/2019  376     1    25000 - 2200 - 672 (Since last appearance had is_B as 0)

有没有人能够指出一种使用pandas实现同样功能的方法?

你的值变化对我来说没有太多意义。 - Henry Yik
@HenryYik 所以这个想法是在 is_b = 1 之前计算 c_a 的值。对于每个 F_Date,都有一个可用的总值,即 25000。如果 is_b = 0,则不希望减去该行的 col 值。 - vp7
啊,好的,我错过了它在航班日期更改时重置的部分。 - Henry Yik
@HenryYik,你能帮忙吗? - vp7
@vp7 我仍然不太清楚你想做什么,特别是涉及 colis_B 部分。你能重新表述或详细说明一下吗? - AMC
4个回答

3

我认为我找到了一个非常简洁的解决方案:

df['c_a'] = df.groupby('F_Date').apply(lambda grp:
    25000 - grp.col.where(grp.is_B.eq(1), 0).shift(fill_value=0)
    .cumsum()).reset_index(level=0, drop=True)

结果如下:

       F_Date      B_Date   col  is_B    c_a
0  01/09/2019  02/08/2019  2200     1  25000
1  01/09/2019  03/08/2019   672     1  22800
2  02/09/2019  03/08/2019  1828     1  25000
3  01/09/2019  04/08/2019   503     0  22128
4  02/09/2019  04/08/2019   829     1  23172
5  03/09/2019  04/08/2019  1367     0  25000
6  02/09/2019  05/08/2019   559     1  22343
7  03/09/2019  05/08/2019   922     1  25000
8  04/09/2019  05/08/2019  1519     0  25000
9  01/09/2019  06/08/2019   376     1  22128

以下是基于组F_Date == '01/09/2019'的示例:

  1. grp.col.where(grp.is_B.eq(1), 0) - the value to subtract from the next row in group:

    0    2200
    1     672
    3       0
    9     376
    
  2. .shift(fill_value=0) - the value to subtract from the current row in group:

    0       0
    1    2200
    3     672
    9       0
    
  3. .cumsum() - cumulated values to subtract:

    0       0
    1    2200
    3    2872
    9    2872
    
  4. 25000 - ... - the target value:

    0    25000
    1    22800
    3    22128
    9    22128
    

谢谢您的回答,但是正如您所看到的,在第4行中isBooked的值为0时,我们不想将25000分配给它,相反,我们希望为该特定F_Date的最后一个条目值进行分配,例如在这种情况下,第4行应该有(25000-2200-672)= 22128。 - vp7
最初我错过了这个细节,但现在结果是好的。 - Valdi_Bo

1

好玩的熊猫游戏 :)

import pandas as pd
df = pd.DataFrame({'F_Date': [pd.to_datetime(_, format='%d/%m/%Y') for _ in
                              ['01/09/2019', '01/09/2019', '02/09/2019', '01/09/2019', '02/09/2019',
                               '03/09/2019', '02/09/2019', '03/09/2019', '04/09/2019', '01/09/2019']],
                   'B_Date': [pd.to_datetime(_, format='%d/%m/%Y') for _ in
                              ['02/08/2019', '03/08/2019', '03/08/2019', '04/08/2019', '04/08/2019',
                               '04/08/2019', '05/08/2019', '05/08/2019','05/08/2019', '06/08/2019']],
                   'col': [2200, 672, 1828, 503, 829, 1367, 559, 922, 1519, 376],
                   'is_B': [1, 1, 1, 0, 1, 0, 1, 1, 0, 1]
                   })

让我们一步一步来看:
# sort in the order that fits the semantics of your calculations
df.sort_values(['F_Date', 'B_Date'], inplace=True)

# initialize 'c_a' to 25000 if a new F_Date starts
df.loc[df['F_Date'].diff(1) != pd.Timedelta(0), 'c_a'] = 25000

# Step downwards from every 25000 and substract shifted 'col'
# if shifted 'is_B' == 1, otherwise replicate shifted 'c_a' to the next line
while pd.isna(df.c_a).any():
    df.c_a.where(
        pd.notna(df.c_a),   # set every not-NaN value to ...
        df.c_a.shift(1).where(       # ...the previous / shifted c_a...
            df.is_B.shift(1) == 0,   # ... if previous / shifted is_B == 0
            df.c_a.shift(1) - df.col.shift(1)   # ... otherwise substract shifted 'col'
        ), inplace=True
    )

# restore original order
df.sort_index(inplace=True)

这是我得到的结果

      F_Date     B_Date   col  is_B      c_a
0 2019-09-01 2019-08-02  2200     1  25000.0
1 2019-09-01 2019-08-03   672     1  22800.0
2 2019-09-02 2019-08-03  1828     1  25000.0
3 2019-09-01 2019-08-04   503     0  22128.0
4 2019-09-02 2019-08-04   829     1  23172.0
5 2019-09-03 2019-08-04  1367     0  25000.0
6 2019-09-02 2019-08-05   559     1  22343.0
7 2019-09-03 2019-08-05   922     1  25000.0
8 2019-09-04 2019-08-05  1519     0  25000.0
9 2019-09-01 2019-08-06   376     1  22128.0

0

尝试使用shiftcumsumffill进行分组

m = ~df.groupby('F_Date').is_B.diff().eq(1)
s = (-df.col).groupby(df.F_Date).apply(lambda x: x.shift(fill_value=25000).cumsum())

df['c_a'] = s.where(m).groupby(df.F_Date).ffill()


Out[98]:
       F_Date      B_Date   col  is_B      c_a
0  01/09/2019  02/08/2019  2200     1  25000.0
1  01/09/2019  03/08/2019   672     1  22800.0
2  02/09/2019  03/08/2019  1828     1  25000.0
3  01/09/2019  04/08/2019   503     0  22128.0
4  02/09/2019  04/08/2019   829     1  23172.0
5  03/09/2019  04/08/2019  1367     0  25000.0
6  02/09/2019  05/08/2019   559     1  22343.0
7  03/09/2019  05/08/2019   922     1  25000.0
8  04/09/2019  05/08/2019  1519     0  25000.0
9  01/09/2019  06/08/2019   376     1  22128.0

0

对于你的问题,答案分为两部分。首先,你需要按照 F_Date 对数据框进行分组。一旦完成,你可以使用 rolling() 对给定值之前的所有值执行操作。这里有一些问题:

  1. 滚动只能在单个列上执行
  2. 使用 expanding.apply 时,只能返回单个实数值

我们可以通过将分组数据框和初始数据框传递给 apply 上使用的方法,并在那里设置输出值来解决这个问题。这个解决方案可能不是完美的或者性能最佳的,但它确实有效。

In [1]: s = '''F_Date      B_Date      col   is_B
   ...: 01/09/2019  02/08/2019  2200    1
   ...: 01/09/2019  03/08/2019  672     1
   ...: 02/09/2019  03/08/2019  1828    1
   ...: 01/09/2019  04/08/2019  503     0
   ...: 02/09/2019  04/08/2019  829     1
   ...: 03/09/2019  04/08/2019  1367    0
   ...: 02/09/2019  05/08/2019  559     1
   ...: 03/09/2019  05/08/2019  922     1
   ...: 04/09/2019  05/08/2019  1519    0
   ...: 01/09/2019  06/08/2019  376     1'''

In [2]: import re

In [3]: sl = [re.split('\s+',x) for x in s.split('\n')]

In [4]: import pandas as pd

In [5]: df = pd.DataFrame(sl[1:], columns=sl[0])

In [6]: df['F_Date'] = df['F_Date'].astype('datetime64[ns]')

In [7]: df['B_Date'] = df['B_Date'].astype('datetime64[ns]')

In [8]: df['col'] = df['col'].astype(int)

In [9]: df['is_B'] = df['is_B'].astype(int)

In [10]: df['c_a'] = None

In [11]: def l(df, df_g, cols):
    ...:      is_Bs = df_g['is_B'].values[:len(cols)]
    ...:      values = [2500]+ [cols[i] for i in range(len(cols)-1) if is_Bs[i] ]
    ...:      df.at[df_g.index[len(cols)-1], 'c_a'] = values
    ...:      return 1

In [12]: for dt, df_g in df.groupby('F_Date'):
    ...:     df_g['col'].expanding().apply(lambda x: l(df, df_g, x),raw= True)
    ...: 
In [13]: df

Out[13]: 
      F_Date     B_Date   col  is_B                    c_a
0 2019-01-09 2019-02-08  2200     1                 [2500]
1 2019-01-09 2019-03-08   672     1         [2500, 2200.0]
2 2019-02-09 2019-03-08  1828     1                 [2500]
3 2019-01-09 2019-04-08   503     0  [2500, 2200.0, 672.0]
4 2019-02-09 2019-04-08   829     1         [2500, 1828.0]
5 2019-03-09 2019-04-08  1367     0                 [2500]
6 2019-02-09 2019-05-08   559     1  [2500, 1828.0, 829.0]
7 2019-03-09 2019-05-08   922     1                 [2500]
8 2019-04-09 2019-05-08  1519     0                 [2500]
9 2019-01-09 2019-06-08   376     1  [2500, 2200.0, 672.0]

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