Material 树——查找迟到的项目(在 Pandas 数据框内)

Material Tree--Finding Late Items (inside a Pandas Dataframe)

好的,所以我需要帮助 and/or 关于如何解决 material 树 on-time/late 问题的建议。

我有一个 pandas 数据框,其中包含 material 棵树 (['Tree'])、树内的不同层级 (['Level'])、部件号 ( ['Part #'])、计划开始日期 (['Sched Start']) 和计划完成日期 (['Sched Fin'])。

import pandas as pd


data = {'Tree': [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3],
        'Level': [1, 2, 2, 3, 1, 2, 3, 4, 1, 2, 3, 2, 3, 2],
        'Part #': ['11', '12A', '12B', '12B3',
                   '21', '22A', '22A3', '22A4',
                   '31', '32A', '32A3', '32B', '32B3', '32C'],
        'Sched Start': pd.to_datetime(['12/01/2020', '11/01/2020', '11/01/2020', '10/01/2020',
                        '12/01/2020', '11/01/2020', '10/01/2020', '09/01/2020',
                        '12/01/2020', '11/01/2020', '10/01/2020', '11/01/2020', '10/01/2020', '11/01/2020']),
        'Sched Fin': pd.to_datetime(['12/15/2020', '11/15/2020', '12/02/2020', '11/02/2020',
                        '12/15/2020', '11/15/2020', '11/02/2020', '09/15/2020',
                        '12/15/2020', '11/15/2020', '10/15/2020', '11/15/2020', '10/15/2020', '11/15/2020'])        
        }

df = pd. DataFrame(data)

正常的 material 流程是项目提供给下一个更高的组件。例如,一个 3 级项目馈入一个 2 级项目。 2 级项目提供 1 级项目(1 级是不提供任何内容的 top/final 程序集)。可能有多个 2 级馈送单个 1 级。多个 3 级馈送单个 2 级等等。所以在上面的示例代码中(对于树 1):12B3 馈入 12B,12A 和 12B 馈入 11。

无论如何,我需要添加另一列,其中包含项目完成日期与其下一个更高组件的开始日期的比较数据。回到我们上面的例子。 3 级部分 12B3 的完成日期为 11/02/2020——它为开始日期为 11/01/2020 的 12B 提供数据:12B3 迟到。看日期,12B会迟到,12A会准时

较低的组件将始终位于较高的组件之下。

清澈如泥,对吧?

我尝试过的:

好吧,我做了一个遍历每一行的循环的糟糕尝试。它获取级别值然后转到下一行,如果前一行级别 > 当前行级别它将当前行 'Sched Fin' 与前一行 'Sched Start' 进行比较并取得一点成功。当然,当有顺序相同级别的项目时,这一切都会爆炸(例如两个级别 2)。

如有任何帮助,我们将不胜感激。

** edit ** 树是相互独立的。不像关卡那样捆绑在一起。

就像评论中提到的above_c_level一样,通过一两个class来跟踪进食路径会更容易。

对于我的回答,我修改了你的 data 字典,在树级别和后续生产级别之间有分号,以便它们可以更容易地相互比较(即 '1;2B;3')。

首先,class 为您的各个零件提供一个 class 将有助于跟踪不同零件之间的进给。

class Part():
    def __init__(self, part, level, start, finish):
        self.part = part
        self.level = level
        self.start = pd.to_datetime(start)
        self.finish = pd.to_datetime(finish)
        self.feedsl = []
        self.isfedl = []
        self.status = None
    def __str__(self):
        return '({}, {}, {}, {})'.format(self.part, self.level, self.start, self.finish)
    def __repr__(self):
        return self.__str__()
    def feeds(self, parts):
        for p in parts:
            p.isfedl.append(self)
            self.feedsl.append(p)
    def isfed(self, parts):
        for p in parts:
            self.isfedl.append(p)
            p.feedsl.append(self)
    def late(self):
        deltas = []
        for feedp in self.feedsl:
            delta = feedp.start - self.finish
            deltas.append(delta)
        #lates should have self.finish > self.start, so negative delta
        lates = [t for t in deltas if t.days < 0]
        if len(lates) > 0:
            self.status = 'LATE'
            self.LATE = True
        elif len(lates) == 0:
            self.status = 'ONTIME'
            self.LATE = False
        return self.status

每个部分都会根据您输入的日期来跟踪它是准时还是迟到。您可以指定一个部件馈送到另一个部件(这会同时更新部件的 feedsl 属性和接收者的 isfedl 属性)或指定一个部件由一定数量的部件馈送(再次, 更新两个属性)。我将输入设置为假定一个列表,因此如果您逐一指定很多,您可以修改或将所有内容括在括号中。

执行此操作后,您必须根据数据生成零件列表:

parts = []
LEN = len(data['Tree'])
for i in range(LEN):
    treelev = data['Tree'][i]
    level = data['Level'][i]
    partnum = data['Part #'][i]
    start = data['Sched Start'][i]
    finish = data['Sched Fin'][i]
    parts.append(Part(partnum, level, start, finish))

因此,对于部件列表,您可以使用 Part.part 名称通过列表推导来分隔树(因为格式的第一个值始终是树编号)。

现在您需要一个 class 来接受零件列表(假设它们已正确分类到相应的树中),根据零件名称生成馈送路径。 (这是我想要分号的地方)。

class MaterialTree():
    def __init__(self, parts):
        self.parts = parts
    def setLevTree(self):
        self.levels = [p.level for p in self.parts]
        for ip in range(len(self.parts)-1):
            p = self.parts[ip]
            #all twos feed one:
            if p.level == 1:
                p2s = [p for p in self.parts if p.level == 2]
                p.isfed(p2s)
                continue
            #for each n >= 2, if adjacent is > n, adjacent feeds current
            for l in range(2, max(self.levels)+1):
                pnext = self.parts[ip+1]
                if p.level == pnext.level:
                    continue
                elif p.level == pnext.level-1:
                    p.isfed([pnext])
    def setTree(self):
        #number of production levels
        self.levels = range(max([p.level for p in self.parts]))
        #part names for each level
        self.levdct = {l+1:[p.part for p in self.parts if int(p.part.split(';')[-1][0]) == l+1] for l in self.levels}
        for ik in self.levels[:-1]: #exclude last level, only feeds second to last
            #get names for current base level
            namebase = self.levdct[ik+1]
            #get names for branches one level up
            namebranch = self.levdct[ik+2]
            #select parts with names in base
            base = [p for p in self.parts if p.part in namebase]
            #select parts with names in branch
            branch = [p for p in self.parts if p.part in namebranch]     
            #begin feed:
            for b in base:
                #if there is no letter in the name, all branches feed this
                if not b.part.upper().isupper():
                    for br in branch:
                        br.feeds([b])
                #if there is a letter in the name,
                if b.part.upper().isupper():
                    #select the letter and use it to compare branches
                    letts = [i for i in b.part if i.upper().isupper()][0]
                    #only branches with this letter feed this base
                    for fbr in [br for br in branch if letts in br.part]:
                        fbr.feeds([b])
    def status(self):
        lates = []
        for p in self.parts:
            lates.append(p.late())
        self.status = lates
        return self.status

各种 str.upper().isupper() 只是测试不同部分名称中是否存在任何字母。您可以使用它来为您的零件生成状态列表,以添加到数据框并根据需要导出到 Excel。

举个例子:

T1 = [p for p in parts if p.part[0] == '1']
m1 = MaterialTree(T1)
m1.setTree()
print(m1.status())

returns 对我来说 ['ONTIME', 'ONTIME', 'LATE', 'LATE'].

当然,如果您的零件名称具有不容易解析但应该可行的结构,它可能会变得更复杂。

** 编辑 **:如果馈送结构完全由顺序和级别决定(即,具有递增级别的相邻部分馈送当前部分),那么您可以改用 setLevTree。它采用这种顺序,但不依赖于零件名称。按照树 2 的相同示例,m.setLevTree() 给了我 ['ONTIME', 'ONTIME', 'LATE', 'ONTIME'].