Я хочу выделить некоторое количество «единиц» для каждой группы DataFrame, которое выглядит примерно так:
limit allocation spaceLeft
Group
A 5.0 0.0 5.0
A 3.0 0.0 3.0
A 7.0 0.0 7.0
B 1.0 0.0 1.0
B 2.0 0.0 2.0
B 4.0 0.0 4.0
B 6.0 0.0 6.0
... которое может быть создано:
df = pd.DataFrame(data=[('A', 5.0, 0.0),
('A', 3.0, 0.0),
('A', 7.0, 0.0),
('B', 1.0, 0.0),
('B', 2.0, 0.0),
('B', 4.0, 0.0),
('B', 6.0, 0.0)],
columns=('Group', 'limit', 'allocation')).set_index('Group')
df['spaceLeft'] = df['limit'] - df['allocation']
Ограничение состоит в том, что распределение единиц должно быть как можно более равномерным в строках каждой группы, но не может превышать limit
для каждой строки.Так, например, если у нас есть 10 единиц, то окончательное, правильное распределение для группы A
будет:
limit allocation spaceLeft
Group
A 5.0 3.5 1.5
A 3.0 3.0 0.0
A 7.0 3.5 3.5
Я написал рекурсивную функцию для этого:
unitsToAllocate = 10.0
def f(g):
allocated = g['allocation'].sum()
unitsLeft = unitsToAllocate - allocated
if unitsLeft > 0:
g['spaceLeft'] = g['limit'] - g['allocation']
# "Quantum" is the space left in the smallest bin with space remaining
quantum = g[g['spaceLeft'] > 0]['spaceLeft'].min()
# Distribute only as much as will fill next bin to its limit
alloc = min(unitsLeft / g[g['spaceLeft'] > 0]['spaceLeft'].count(), quantum)
g.loc[g['spaceLeft'] > 0, 'allocation'] = g[g['spaceLeft'] > 0]['allocation'] + alloc
f(g)
else:
return g
Если я вручную, итеративно запускаю внутреннюю логику f
в одной группе, такой как group = df.groupby('Group').get_group('A')
, тогда она работает.(То есть, он выдает правильный результат для A
, показанного выше.)
Но если я вызову f
, как задумано с помощью df.groupby('Group').apply(f)
, он завершится неудачно с:
ValueError: cannot reindex from a duplicate axis
.
Что не так?
И есть ли более пандальный подход к этому алгоритму?