Дерево материалов - поиск поздних элементов (внутри фрейма данных 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']
.