使用pandas加快构建物料清单功能

cgh8pdjw  于 2023-06-20  发布在  其他
关注(0)|答案(2)|浏览(111)

我是一个Python新手,我正在编写一个构建物料清单(BOM)的代码,它可以从excel文件中的订单单中获取客户所需的物料id和采购数量,并使用另一个BOM表(也在同一个excel中)来计算所需的所有原材料的数量。从库存中减去原材料的库存后,将剩余的需求输入到字典输出中,如{id_material:数量},BOM表格式如下
| 项目ID|进程ID|进程号|输入/输出|材料ID|输入量|输出量||- -----|- -----|- -----|- -----|- -----|- -----|- -----||一个|Z420| 1| IN| 12125|一百|南||一个|Z420| 1| OUT| A-Z512-2|南|一百||一个|z512| 2| IN| A-Z512-2|一百|南||一个|z512| 2| OUT| A-Z600-3|南|一百二十||一个|z600| 3| IN| A-Z600-3|一百二十|南||一个|z600| 3| OUT|一四五五一|南|-20||一个|z600| 3| OUT|一个|南|一百|attr:processs_id:使用的进程ID
attr:processs_No.:处理路径中的处理顺序。不像自然数那样总是连续或规则的,例如(51,60,70,100)
attr:IN/OUT:指明物料是原材料还是输出
我使用的pandas.dataframe也是这样的,但是增加了两个attr列:'count_demand'表示需要的数量,'flag'表示需要执行的物料。我们称之为'df_demand'。
我能够完成将服务于我的目的的功能,但速度不令人满意。我用timeit这样的鼠标进行了测试,发现一些操作需要花费很多时间,但我想不出优化它们的方法,所以我来这里寻求帮助。
1.我发现的第一件不满意的事情,也是整个过程中最耗时的部分,是我的代码用来查找当前需求物料的原材料,并根据需求物料的数量计算原材料与BOM成比例的需求数量的一个函数,代码如下

def calculate_demand_raw(row, df_demand):
    try:
       if np.isnan(row['quantity_out']):
           raise ValueError('To avoid including those recycled materials with negative outputs')

       list_index = list(df_demand['item_id'].isin([row['item_id']]) &
                         df_demand['process_No.'].isin([row['process_No.']]) &
                         df_demand['IN/OUT'].isin(['IN']))
       index = [i for i, x in enumerate(list_index) if x==True]  
       # Search to find the index of the required generation process

       df_demand.loc[index, 'count_demand'] = row['count_demand']/row['quantity_out']*
                                               df_demand.loc[index, 'quantity_in']
       # calculate quantity of raw materials.
       df_demand.loc[index, 'flag'] = 1

    except ValueError:
        pass  # Prevent the query material is the base material, no process generation
    df_demand.loc[row.name, 'flag'] = 0

df_demand[df_demand['flag'].isin([1])].apply(lambda row: calculate_demand_raw(row, df_demand), axis=1)

timeit告诉我,在函数中,查找符合条件的行的索引所需的时间是计算原材料数量所需时间的三倍,并且calculate_demand_raw也是循环中最耗时的函数。
1.循环中的第二个耗时函数是将汇总的原材料需求填充到df_demand的属性'count_demand'中的函数,在该属性中为流程生成原材料需求。

def fill_demand(row, qty_sum_demand, df_demand):
    df_demand[row.name, 'count_demand'] += qty_sum_demand.loc[
                                qty_sum_demand['IN/OUT'].isin([row['IN/OUT']).tolist(),
                                'count_demand'].tolist()
    df_demand.loc[index, 'flag'] = 1

df_demand.loc[index_generated_process].apply(lambda row: 
                                fill_demand(row, qty_sum_demand, df_demand), axis=1)

是函数中的条件搜索使它花费这么长时间,就像calculate_demand_raw一样吗?有没有可能把这个操作变成一个更快的numpy矢量化操作?
谢谢任何帮助和建议

u0sqgete

u0sqgete1#

示例的修改版本,以显示一些功能:

df = pd.read_csv(io.StringIO(
"""
item_id,process_id,process_No.,IN/OUT,material_id,quantity_in,quantity_out,flag
A,z420,1,IN,12125,100.0,,0
A,z420,1,OUT,A-z512-2,,100.0,0
A,z512,2,IN,A-z512-2,100.0,,0
A,z512,2,OUT,A-z600-3,,120.0,0
A,z600,3,IN,A-z600-2,,400,1
A,z600,3,IN,A-z600-3,120.0,200,1
A,z600,3,IN,14551,,-20.0,0
A,z600,3,OUT,A,,100.0,0
""".strip()
))
item_id process_id  process_No. IN/OUT material_id  quantity_in  quantity_out  flag
0       A       z420            1     IN       12125        100.0           NaN     0
1       A       z420            1    OUT    A-z512-2          NaN         100.0     0
2       A       z512            2     IN    A-z512-2        100.0           NaN     0
3       A       z512            2    OUT    A-z600-3          NaN         120.0     0
4       A       z600            3     IN    A-z600-2          NaN         400.0     1
5       A       z600            3     IN    A-z600-3        120.0         200.0     1 # <- only valid flag (non-na quantity_in)
6       A       z600            3     IN       14551          NaN         -20.0     0
7       A       z600            3    OUT           A          NaN         100.0     0

这里有一种不使用.apply + .loc查找来实现calculate_demand_raw的方法。
一般来说,在这种情况下,您希望.merge,这样您就可以“并排”所有数据,从而允许您以“矢量化方式”工作。

flags = df[df['flag'] == 1].dropna(subset='quantity_in')

df_m = df.merge(flags, on=['item_id', 'process_No.'], how='left', suffixes=('', '_y'))

df_m.loc[ 
   (df_m['flag'] == 1) | (df_m['IN/OUT'] == 'OUT'), 
   df.columns.difference(['item_id', 'process_No.']) + '_y' 
] = float('nan')

rows = df_m['process_id_y'].notna()
df_m.loc[rows, 'quantity_out'] *= df_m.loc[rows, 'quantity_in_y']

df_m.loc[df_m['flag'] == 1, 'flag'] = 0
df_m.loc[rows, 'flag'] = 1

步骤分解:
查找所有标志行。

flags = df[df['flag'] == 1].dropna(subset='quantity_in')

.dropna()用于模拟代码中的if np.isnan(row['quantity_out'])行。

item_id process_id  process_No. IN/OUT material_id  quantity_in  quantity_out  flag
5       A       z600            3     IN    A-z600-3        120.0         200.0     1

带标志的左合并:

df_m = df.merge(flags, on=['item_id', 'process_No.'], how='left', suffixes=('', '_y'))
item_id process_id  process_No. IN/OUT material_id  quantity_in  quantity_out  flag process_id_y IN/OUT_y material_id_y  quantity_in_y  quantity_out_y  flag_y
0       A       z420            1     IN       12125        100.0           NaN     0          NaN      NaN           NaN            NaN             NaN     NaN
1       A       z420            1    OUT    A-z512-2          NaN         100.0     0          NaN      NaN           NaN            NaN             NaN     NaN
2       A       z512            2     IN    A-z512-2        100.0           NaN     0          NaN      NaN           NaN            NaN             NaN     NaN
3       A       z512            2    OUT    A-z600-3          NaN         120.0     0          NaN      NaN           NaN            NaN             NaN     NaN
4       A       z600            3     IN    A-z600-2          NaN         400.0     1         z600       IN      A-z600-3          120.0           200.0     1.0
5       A       z600            3     IN    A-z600-3        120.0         200.0     1         z600       IN      A-z600-3          120.0           200.0     1.0
6       A       z600            3     IN       14551          NaN         -20.0     0         z600       IN      A-z600-3          120.0           200.0     1.0
7       A       z600            3    OUT           A          NaN         100.0     0         z600       IN      A-z600-3          120.0           200.0     1.0

你想丢弃OUT行,不清楚你是否想将标志行与它们本身进行比较,所以我在这里丢弃了它们。
您可以将要丢弃的行上的_y列重置为NaN

df_m.loc[ 
   (df_m['flag'] == 1) | (df_m['IN/OUT'] == 'OUT'), 
   df.columns.difference(['item_id', 'process_No.']) + '_y' 
] = float('nan')
item_id process_id  process_No. IN/OUT material_id  quantity_in  quantity_out  flag process_id_y IN/OUT_y material_id_y  quantity_in_y  quantity_out_y  flag_y
0       A       z420            1     IN       12125        100.0           NaN     0          NaN      NaN           NaN            NaN             NaN     NaN
1       A       z420            1    OUT    A-z512-2          NaN         100.0     0          NaN      NaN           NaN            NaN             NaN     NaN
2       A       z512            2     IN    A-z512-2        100.0           NaN     0          NaN      NaN           NaN            NaN             NaN     NaN
3       A       z512            2    OUT    A-z600-3          NaN         120.0     0          NaN      NaN           NaN            NaN             NaN     NaN
4       A       z600            3     IN    A-z600-2          NaN         400.0     1          NaN      NaN           NaN            NaN             NaN     NaN
5       A       z600            3     IN    A-z600-3        120.0         200.0     1          NaN      NaN           NaN            NaN             NaN     NaN
6       A       z600            3     IN       14551          NaN         -20.0     0         z600       IN      A-z600-3          120.0           200.0     1.0
7       A       z600            3    OUT           A          NaN         100.0     0          NaN      NaN           NaN            NaN             NaN     NaN

然后,您可以对具有非na _y值的行执行计算:

item_id process_id  process_No. IN/OUT material_id  quantity_in  quantity_out  flag process_id_y IN/OUT_y material_id_y  quantity_in_y  quantity_out_y  flag_y
0       A       z420            1     IN       12125        100.0           NaN     0          NaN      NaN           NaN            NaN             NaN     NaN
1       A       z420            1    OUT    A-z512-2          NaN         100.0     0          NaN      NaN           NaN            NaN             NaN     NaN
2       A       z512            2     IN    A-z512-2        100.0           NaN     0          NaN      NaN           NaN            NaN             NaN     NaN
3       A       z512            2    OUT    A-z600-3          NaN         120.0     0          NaN      NaN           NaN            NaN             NaN     NaN
4       A       z600            3     IN    A-z600-2          NaN         400.0     1          NaN      NaN           NaN            NaN             NaN     NaN
5       A       z600            3     IN    A-z600-3        120.0         200.0     1          NaN      NaN           NaN            NaN             NaN     NaN
6       A       z600            3     IN       14551          NaN       -2400.0     0         z600       IN      A-z600-3          120.0           200.0     1.0
7       A       z600            3    OUT           A          NaN         100.0     0          NaN      NaN           NaN            NaN             NaN     NaN

切换标志值:

df_m.loc[df_m['flag'] == 1, 'flag'] = 0
df_m.loc[rows, 'flag'] = 1
item_id process_id  process_No. IN/OUT material_id  quantity_in  quantity_out  flag process_id_y IN/OUT_y material_id_y  quantity_in_y  quantity_out_y  flag_y
0       A       z420            1     IN       12125        100.0           NaN     0          NaN      NaN           NaN            NaN             NaN     NaN
1       A       z420            1    OUT    A-z512-2          NaN         100.0     0          NaN      NaN           NaN            NaN             NaN     NaN
2       A       z512            2     IN    A-z512-2        100.0           NaN     0          NaN      NaN           NaN            NaN             NaN     NaN
3       A       z512            2    OUT    A-z600-3          NaN         120.0     0          NaN      NaN           NaN            NaN             NaN     NaN
4       A       z600            3     IN    A-z600-2          NaN         400.0     0          NaN      NaN           NaN            NaN             NaN     NaN
5       A       z600            3     IN    A-z600-3        120.0         200.0     0          NaN      NaN           NaN            NaN             NaN     NaN
6       A       z600            3     IN       14551          NaN       -2400.0     1         z600       IN      A-z600-3          120.0           200.0     1.0
7       A       z600            3    OUT           A          NaN         100.0     0          NaN      NaN           NaN            NaN             NaN     NaN
3mpgtkmj

3mpgtkmj2#

我不知道为什么我在编辑模式下仔细编辑的数据表变成了这样一个狗屎哈哈。为了弥补这一点,我在这个回复

中包括了预览中的表和属性的屏幕截图

相关问题