如何基于最大和分配组?

时间:2019-06-03 21:31:56

标签: python pandas dataframe

我有一个这样的数据框:

df = pd.DataFrame({'keys': list('aaaabbbbccccc'), 'values': [1, 5, 6, 8, 2, 4, 7, 7, 1, 1, 1, 1, 5]})

   keys  values
0     a       1
1     a       5
2     a       6
3     a       8
4     b       2
5     b       4
6     b       7
7     b       7
8     c       1
9     c       1
10    c       1
11    c       1
12    c       5

此外,我有一个变量max_sum = 10

我想根据keys中的值和(ii)max_sum中的值为每行分配一个组。

我的预期结果如下:

   keys  values  group
0     a       1      1
1     a       5      1
2     a       6      2
3     a       8      3
4     b       2      4
5     b       4      4
6     b       7      5
7     b       7      6
8     c       1      7
9     c       1      7
10    c       1      7
11    c       1      7
12    c       5      7

因此,a组中的前两个值(15)总计为6,小于10,因此它们在同一组中。如果现在还添加6,则将超出max_sum,因此该值将进入组2。我们无法将8添加到该组,因为将再次超出max_sum,因此我们定义了一个组3。然后与值bc相同。

一个人可以做

df['cumsum'] = df.groupby('keys')['values'].cumsum()

   keys  values  cumsum
0     a       1       1
1     a       5       6
2     a       6      12
3     a       8      20
4     b       2       2
5     b       4       6
6     b       7      13
7     b       7      20
8     c       1       1
9     c       1       2
10    c       1       3
11    c       1       4
12    c       5       9

但是我不知道如何从中获取组信息。

5 个答案:

答案 0 :(得分:6)

我们要基于行的累加总和对行进行分区,因此我们使用cumsum,取相对于max_sum的模数,然后找到差异以找到差异为负的点(以标记下一组)。我们还需要针对每个键执行此操作,因此上述整个操作都在GroupBy.apply调用内完成。

(df.groupby('keys')['values']
   .apply(lambda x: x.cumsum().mod(max_sum).diff())
   .fillna(-1)
   .lt(0)
   .cumsum())                 

0     1
1     1
2     2
3     3
4     4
5     4
6     5
7     6
8     7
9     7
10    7
11    7
12    7
Name: values, dtype: int64

在下面的评论中,我写道:

  

@Cleb似乎我的回答是错误的。对于4,4,9,2,输出   应该是1、1、2、3,但是我的代码会分配1、1、2、2,因为求和   折扣值。

因此,这是我解决这个极端情况的解决方案。定义一个分配组的函数:

grp = {'grp': 0}  # better than `global`, at least
def func(V):
    cumsum = 0
    grp['grp'] += 1
    grps = []
    for v in V.tolist():
        cumsum += v
        if cumsum > max_sum:
            cumsum = v
            grp['grp'] += 1
        grps.append(grp['grp'])

    return pd.Series(grps)

现在,致电apply

df.groupby('keys')['values'].apply(func).values
# array([1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 7, 7, 7])

答案 1 :(得分:4)

我们可以创建两个掩码,并在此基础上创建一个newArray / True数组。

  • m1 :所有大于False的值都标记为max_sum否则为True
  • m2 :前一行False中的值与当前行不同的行。

使用keys,我们在伪代码中基本上具有以下内容:

  

当m1 m2为True时,返回True,否则返回False

现在我们可以将np.whereTrue转换为1/0,因为它们是布尔值:

False

这是最后一行中True + True 2 的原因。

代码

cumsum

答案 2 :(得分:2)

我的逻辑是,首先获得每个组中的cumsum,然后我们需要获得先前组的最大最后组号cumsum分配给下一个组

s=(df.groupby('keys')['values'].cumsum()//10+1)
s+s.groupby(df['keys']).last().shift().fillna(0).cumsum().reindex(df['keys']).values

Out[24]: 
0     1.0
1     1.0
2     2.0
3     3.0
4     4.0
5     4.0
6     5.0
7     6.0
8     7.0
9     7.0
10    7.0
11    7.0
12    7.0
Name: values, dtype: float64

另一种方式

pd.factorize(list(zip(df['keys'],df.groupby('keys')['values'].cumsum()//10)))[0]+1
Out[51]: array([1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 7, 7, 7], dtype=int64)

方法3来自Pir的数据

s=df.groupby('keys')['values'].rolling(2,min_periods=1).sum().gt(10)
s.loc[s.groupby(level=0).head(1).index[1:]]=True
s.cumsum()+1
Out[79]: 
keys    
a     0      1
      1      1
      2      2
      3      3
b     4      4
      5      4
      6      5
      7      6
c     8      7
      9      7
      10     7
      11     7
      12     7
d     13     8
      14     8
      15     9
      16    10
Name: values, dtype: int32

答案 3 :(得分:2)

这不是向量化问题

至少据我所知

设置

考虑扩展示例

df = pd.DataFrame({
    'keys': [*'aaaabbbbcccccdddddddd'],
    'values': [*map(int, '156824771111544922252')]
})

使用发电机

def gen_groups(tups, max_sum=10):
    label = 0
    sums = {}
    for key, val in tups:
        if key not in sums:
            label += 1
            sums[key] = 0
        sums[key] += val
        if sums[key] > max_sum:
            # This resets the summation
            # to the first thing that exceeded the max
            sums[key] = val
            label += 1
        yield label

df.assign(group=[*gen_groups(zip(df['keys'], df['values']))])

输出

   keys  values  group
0     a       1      1
1     a       5      1
2     a       6      2
3     a       8      3
4     b       2      4
5     b       4      4
6     b       7      5
7     b       7      6
8     c       1      7
9     c       1      7
10    c       1      7
11    c       1      7
12    c       5      7
13    d       4      8  # First group for `key == d` 
14    d       4      8  # Still same group because `4 + 4 <= 10`
15    d       9      9  # New group because `4 + 4 + 9 > 10`
16    d       2     10  # New group because `9 + 2 > 10`
17    d       2     10  # Same group because `2 + 2 < = 10`
18    d       2     10  # Same group because `2 + 2 + 2 <= 10`
19    d       5     11  # New Group because `2 + 2 + 2 + 5 > 10`
20    d       2     11  # Same Group because `5 + 2 <= 10`

答案 4 :(得分:0)

我每个cumsum创建一个groupID,并使用它再次与keys一起分组以得出ngroup的{​​{1}}

keys-cumsum