我一直在使用pandas进行库存分析,我对一个非常棘手的概念称为"实际封面",这是一个仅在临时分析中才有意义的概念由于"实际封面"是指(几天(的措施,即当前库存位置将持续多少,假设从那时起就不会进行任何补货。
。ex:
TIMESTAMP MATERIAL_GOODS STOCK_POS SALES
2017-03-29 PRODUCT A 47 2
2017-03-30 PRODUCT A 43 4
2017-03-31 PRODUCT A 38 5
2017-04-01 PRODUCT A 49 11
2017-04-02 PRODUCT A 49 0
2017-04-03 PRODUCT A 45 4
2017-04-04 PRODUCT A 38 7
2017-04-05 PRODUCT A 30 8
2017-04-06 PRODUCT A 44 6
2017-04-07 PRODUCT A 36 8
2017-04-08 PRODUCT A 47 10
2017-04-09 PRODUCT A 46 1
2017-04-11 PRODUCT A 31 8
2017-04-10 PRODUCT A 39 7
我提出了这个解决方案(正在工作...(
actual_cover = []
for i in DF.index:
z = 1
counter = 0
rest = DF['STOCK_POS'].iloc[i]
while (rest >= 0)&(i+z < DF.index.max()):
rest -= DF['SALES'].iloc[i+z]
counter += 1
z += 1
actual_cover.append(counter)
print('Progress: {}%'.format(round((i/len(DF.index))*100,2)), end="r", flush=True)
这是示例的输出,实际上是应该的样子:
TIMESTAMP MATERIAL_GOODS STOCK_POS SALES ACTUAL_COVER(days)
2017-03-29 PRODUCT A 47 2 9
2017-03-30 PRODUCT A 43 4 8
2017-03-31 PRODUCT A 38 5 7
2017-04-01 PRODUCT A 49 11 9
2017-04-02 PRODUCT A 49 0 8
2017-04-03 PRODUCT A 45 4 7
2017-04-04 PRODUCT A 38 7 6
2017-04-05 PRODUCT A 30 8 5
2017-04-06 PRODUCT A 44 6 7
2017-04-07 PRODUCT A 36 8 6
2017-04-08 PRODUCT A 47 10 12
2017-04-09 PRODUCT A 46 1 11
2017-04-11 PRODUCT A 31 8 8
2017-04-10 PRODUCT A 39 7 10
但是,使用此代码,大约需要1秒钟才能计算一家商店中一项的实际封面。由于我需要在2K商店的大约40k Itens进行此计算,因此并不是一个实用的解决方案。
我尝试使用滚动和其他熊猫工具来处理一些东西,但无法正确计算。
我的问题是:有更多的" Pythonic",快速,有效地进行相同的计算?
编辑
so ..@haleemur ali实际上给出了一个很好的问题,因为:
def actual_cover(rownum, frame):
mask = frame.SALES[rownum+1:].cumsum() > frame.STOCK_POS[rownum]
not_covered = np.where(mask.values)[0]
return np.nan if not_covered.size == 0 else not_covered[0]+1
如果您只有一个物品和一家商店的DataFrame
,则可以正常工作,但是我的原始问题看起来更像是这样:
TIMESTAMP ITEM STORE STOCK_POS SALES
2017-01-01 4251695 1216 0.0 0.0
2017-01-01 4251695 1269 1.0 0.0
2017-01-01 4264750 1269 0.0 0.0
2017-01-01 4264750 L101 0.0 0.0
2017-01-01 4252056 L836 308.0 0.0
2017-01-01 4252056 L856 158.0 1.0
2017-01-01 4255732 L101 360.0 0.0
2017-01-01 4255732 L110 101.0 0.0
2017-01-01 4262145 L715 8.0 0.0
2017-01-01 4262145 L794 0.0 0.0
当我使用一个项目(4252056(,一个商店(1001(应用actual_cover
函数时,请这样过滤数据框架:
DF = DF[(DF['ITEM'] == 4252056)&(DF['STORE'] == '1001')]
DF.reset_index(drop=True, inplace=True)
DF['ACTUAL_COVER'] = DF.apply(lambda x: actual_cover(x.name, DF), axis=1)
我得到了结果:
TIMESTAMP ITEM STORE STOCK_POS SALES ACTUAL_COVER
2017-01-01 4252056 1001 551 0 35.0
2017-01-02 4252056 1001 531 20 34.0
2017-01-03 4252056 1001 514 17 33.0
2017-01-04 4252056 1001 1146 28 64.0
2017-01-05 4252056 1001 1130 16 63.0
2017-01-06 4252056 1001 1865 15 76.0
2017-01-07 4252056 1001 1843 22 75.0
2017-01-08 4252056 1001 1833 10 74.0
2017-01-09 4252056 1001 1814 19 73.0
2017-01-10 4252056 1001 1808 6 72.0
这是完美的。但是由于我有许多商店(1300(像钥匙一样工作,所以我需要一种groupby
的解决方案。
使用当前功能:
def actual_cover_grouped(grp):
return grp.apply(lambda x: actual_cover(x.name, grp), axis=1)
这样(处理时间约50分钟(:
group_item_store = DF.groupby(by=[DF['ITEM'], DF['STORE']])
DF['ACTUAL_COVER'] = group_item_store.apply(actual_cover_grouped
).values.flatten()
这是同一段的结果(item-4252056/store-1001(:
TIMESTAMP ITEM STORE STOCK_POS SALES ACTUAL_COVER
2017-01-01 4252056 1001 551 0 NaN
2017-01-02 4252056 1001 531 20 NaN
2017-01-03 4252056 1001 514 17 NaN
2017-01-04 4252056 1001 1146 28 NaN
2017-01-05 4252056 1001 1130 16 NaN
2017-01-06 4252056 1001 1865 15 NaN
2017-01-07 4252056 1001 1843 22 NaN
2017-01-08 4252056 1001 1833 10 NaN
2017-01-09 4252056 1001 1814 19 NaN
2017-01-10 4252056 1001 1808 6 NaN
为什么分组版本不起作用?
这种类型的代码的第一个优化是用本机numpy/pandas函数替换循环并使用 pandas.DataFrame.apply
使用实际封面定义为
当前库存位置将持续多少
的量度(几天(
一个人可以等效地说,实际盖子是
the first day such that the cumulative sum of sales for all following days exceeds
the stock position on a given day
使用此实际封面的定义以下函数返回real_cover给定的行号
def actual_cover(rownum, frame):
mask = frame.SALES[rownum+1:].cumsum() > frame.STOCK_POS[rownum]
not_covered = np.where(mask.values)[0]
return np.nan if not_covered.size == 0 else not_covered[0]+1
然后,您可以将其应用于DataFrame并将值分配给新列
df['ACTUAL_COVER(days)'] = df.apply(lambda x: actual_cover(x.name, df), axis=1)
注意:
我使用了名称df
而不是DF
,因此您必须在数据集中尝试此代码
该函数使用行索引值来确定天数。因此,要使功能正常工作,每天都必须有一排,即使那天没有发生销售,并且必须按时间戳
订购行应用于上述数据框架的功能将返回累积总和永远不超过库存位置的行的np.nan
,即它输出以下内容:
df.apply(lambda x: actual_cover(x.name, df), axis=1)
# output
0 9.0
1 8.0
2 7.0
3 9.0
4 8.0
5 7.0
6 6.0
7 5.0
8 NaN
9 NaN
10 NaN
11 NaN
12 NaN
13 NaN
这与您提供的示例输出不同,因为您在示例
中的整个数据集截断了行 actual_cover
功能可以应用于分组的数据框架,但需要进一步按摩
def actual_cover_grouped(grp):
return grp.apply(lambda x: actual_cover(x.name, grp), axis=1)
grouped = df.groupby('MATERIAL_GOODS')
df['Actual Cover'] = grouped.apply(actual_cover_grouped).values.flatten()
我没有完全满足,但是我能够将3个循环转换为以下代码:
aux_dict = {}
counter = 0
begin = time.time()
for name, group in grouped_cob:
AUX_DF = group.copy()
AUX_DF.reset_index(drop=True, inplace=True)
AUX_DF["ACTUAL_COVER"] = AUX_DF.apply(lambda x: actual_cover(x.name, AUX_DF), axis=1)
aux_dict.update({name: AUX_DF})
final = time.time()
counter +=1
print('Progress: {}%'.format(round((counter/len(grouped_cob))*100,2)) +
' Parcial processing time: '+str(final-inicio), end="r", flush=True)
TESTE = pd.concat(aux_dict)
计算正确。