Дерево материалов - поиск поздних элементов (внутри фрейма данных Pandas)

Хорошо, мне нужно помочь и / или посоветовать, как решить проблему своевременности / опоздания дерева материалов.

У меня есть фреймворк pandas, который содержит деревья материалов (['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)

Нормальный материальный поток состоит в том, что изделия поступают в следующую более высокую сборку. Например, элемент уровня 3 переходит в элемент уровня 2. Элемент уровня 2 питает элемент уровня 1 (уровень 1 - это верхняя / конечная сборка, которая ничего не питает). Может быть несколько уровней 2, которые питают один уровень 1. Несколько уровней 3, которые питают один уровень два и т. Д. Итак, в приведенном выше примере кода (для Дерева 1): 12B3 подает в 12B, 12A и 12B подает в 11.

Во всяком случае, мне нужно добавить еще один столбец со сравнительными данными с даты окончания элемента с датой начала его следующей более высокой сборки. Вернемся к нашему примеру выше. Часть 12B3 уровня 3 имеет дату окончания 11.02.2020- она ​​передает 12B с датой начала 11.01.2020: 12B3 - ПОЗЖЕ. Глядя на даты, 12В опоздает, 12А будет вовремя.

Нижние сборки всегда будут находиться под более высокими.

Ясно, как грязь, правда?

Что я пробовал:

Я сделал ужасную попытку создать цикл, повторяющий каждую строку. Он захватил значение уровня и затем перешел к следующей строке, если предыдущий уровень строки> текущий уровень строки, он с небольшим успехом сравнил текущую строку "Sched Fin" с предыдущей строкой "Sched Start". Конечно, все это взрывается, когда последовательно идут предметы одного уровня (например, два уровня 2).

Любая помощь будет принята с благодарностью.

** редактировать ** Деревья не зависят друг от друга. Не связаны между собой, как уровни.

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'].

Другие вопросы по тегам