我有一个这样的数据框:
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
组中的前两个值(1
和5
)总计为6
,小于10
,因此它们在同一组中。如果现在还添加6
,则将超出max_sum
,因此该值将进入组2
。我们无法将8
添加到该组,因为将再次超出max_sum
,因此我们定义了一个组3
。然后与值b
和c
相同。
一个人可以做
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
但是我不知道如何从中获取组信息。
答案 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
数组。
False
的值都标记为max_sum
否则为True
False
中的值与当前行不同的行。使用keys
,我们在伪代码中基本上具有以下内容:
当m1 或 m2为True时,返回True,否则返回False
现在我们可以将np.where
和True
转换为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