材料树——查找后期项目(在 Pandas 数据框中)

Posted

技术标签:

【中文标题】材料树——查找后期项目(在 Pandas 数据框中)【英文标题】:Material Tree--Finding Late Items (inside a Pandas Dataframe) 【发布时间】:2020-07-21 00:28:14 【问题描述】:

好的,所以我需要帮助和/或建议如何解决材料树的准时/延迟问题。

我有一个 pandas 数据框,其中包含材料树(['Tree'])、该树内的不同级别(['Level'])、零件编号(['Part #'])、预定的开始日期(['计划开始']) 和计划完成日期 (['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)

正常的物料流是物料供给下一个更高的组件。例如,第 3 级项目馈入第 2 级项目。 2 级项目为 1 级项目提供食物(1 级是不提供任何东西的顶部/最终组件)。可能有多个级别 2 馈送单个级别 1。多个级别 3 馈送单个级别 2 等等。因此在上面的示例代码中(对于树 1):12B3 馈送到 12B,12A 和 12B 馈送到 11。

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

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

清如泥,对吧?

我尝试过的:

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

任何帮助将不胜感激。

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

【问题讨论】:

Pandas 非常适合矩阵运算,但可能不适合您的用例。如果您的生产流程是确定性的,那么您应该可以使用 2-3 个类(Tree、Level、Part)和一个循环。如果你一直以表格的形式思考这个问题,你就会让自己变得比必要的更困难。如果你想走这条路,我可以发布一个简单的例子。 任何事情都将不胜感激,我已经盯着这个问题看了一段时间了。我尝试过的每个循环都可能过于复杂。 【参考方案1】:

就像评论中提到的 above_c_level 一样,通过一两个班级跟踪喂养途径会更容易。

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

首先,为您的各个部分设置一个类将有助于跟踪不同部分之间的馈送。

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 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())

为我返回['ONTIME', 'ONTIME', 'LATE', 'LATE']

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

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

【讨论】:

非常感谢。这将为我解决很多问题。

以上是关于材料树——查找后期项目(在 Pandas 数据框中)的主要内容,如果未能解决你的问题,请参考以下文章

在 Pandas 数据框中高效、快速地查找和匹配唯一值

在 Pandas 数据框中查找唯一值,无论行或列位置如何

在 Pandas 数据框中查找每三列的平均值

在 Pandas 数据框中查找和替换子字符串忽略大小写

在熊猫数据框中查找重复行

如何在熊猫数据框中查找列的 ngram 频率?