🔖고객의 소리
💬제품의 부품을 보관하는 창고에서 생산 공장까지 운송 비용을 낮출 수 있을지 검토하고 싶다.
🔖전제 조건
- 창고 -> 생산 공장으로 부품 전달
- 운송 비용: 과거 데이터에서 정량적으로 계산되어 있음
- 집계 기간: 20190101 ~ 20191231
051.물류 데이터를 불러오자
데이터 불러오기
생산 공장 데이터(tbl_factory), 창고 데이터(tbl_warehouse), 창고와 공장 간의 운송 비용(rel_cost), 운송실적(tbl_transaction)
1
2
3
4
| import pandas as pd
factories = pd.read_csv("tbl_factory.csv", index_col = 0)
factories
|
1
2
3
4
| warehouses = pd.read_csv("tbl_warehouse.csv", index_col = 0)
# index_col을 0으로 설정하면 unnamed:0 을 없앨 수 있다.
cost = pd.read_csv("rel_cost.csv", index_col = 0)
cost.head()
|
1
2
| trans = pd.read_csv("tbl_transaction.csv", index_col = 0)
trans.head()
|
- 공장 데이터와 창고 데이터
- 비용 데이터나 운송 실적 데이터에도 있는 것으로 보아 키인 것을 알 수 있음.
- 비용 데이터
- 공장과 창고의 조합으로 관리되고 있음.
- 해당 공장에서 창고로 이동하는데 드는 비용
- 운송 실적 테이블
- 운송 날짜, 출발 공장(ToFC), 도착 공장(FromWH), 운송 개수
운송 실적 데이터와 비용 데이터 결합 결과
1
2
| join_data = pd.merge(trans, cost, left_on=["ToFC", "FromWH"], right_on=["FCID", "WHID"], how='left')
join_data.head()
|
공장 데이터 추가 결합 결과
1
2
| join_data = pd.merge(join_data, factories, left_on="ToFC", right_on="FCID", how="left")
join_data.head()
|
창고 데이터를 추가 결합하고 칼럼을 정리한 결과
1
2
3
| join_data = pd.merge(join_data, warehouses, left_on="FromWH", right_on="WHID", how="left")
join_data = join_data[["TransactionDate", "Quantity", "Cost", "ToFC", "FCName", "FCDemand", "FromWH", "WHName", "WHSupply", "WHRegion"]]
join_data.head()
|
북부지사의 데이터만 추출한 결과
1
2
| north = join_data.loc[join_data["WHRegion"] == "북부"]
north.head()
|
남부지사의 데이터만 추출한 결과
1
2
| south = join_data.loc[join_data["WHRegion"] == "남부"]
south.head()
|
052.현재 운송량과 비용을 확인해 보자
운송 실적 총비용 집계 결과
1
2
3
4
| print("북부지사 총비용: " + str(north["Cost"].sum())+"만원")
# 북부지사 총비용: 2189.3만원
print("남부지사 총비용: " + str(south["Cost"].sum())+"만원")
# 남부지사 총비용: 2062.0만원
|
운송 실적 총 운송 부품 개수 집계 결과
1
2
3
4
| print("북부지사의 총부품 운송개수: " + str(north["Quantity"].sum())+"개")
# 북부지사의 총부품 운송개수: 49146개
print("남부지사의 총부품 운송개수: " + str(south["Quantity"].sum())+"개")
# 남부지사의 총부품 운송개수: 50214개
|
운송 부품 1개당 운송 비용
1
2
3
4
5
6
| tmp = (north["Cost"].sum() / north["Quantity"].sum())*10000
print("북부지사의 부품 1개당 운송 비용: " + str(int(tmp)) + "원")
# 북부지사의 부품 1개당 운송 비용: 445원
tmp = (south["Cost"].sum() / south["Quantity"].sum())*10000
print("남부지사의 부품 1개당 운송 비용: " + str(int(tmp)) + "원")
# 남부지사의 부품 1개당 운송 비용: 410원
|
비용 데이터로부터 지사별 평균 운송 비용 계산
1
2
3
4
5
| cost_chk = pd.merge(cost, factories, on="FCID", how="left")
print("북부지사의 평균 운송 비용: " + str(cost_chk["Cost"].loc[cost_chk["FCRegion"] == "북부"].mean())+"원")
# 북부지사의 평균 운송 비용: 1.075원
print("남부지사의 평균 운송 비용: " + str(cost_chk["Cost"].loc[cost_chk["FCRegion"] == "남부"].mean())+"원")
# 남부지사의 평균 운송 비용: 1.05원
|
💡각 지사의 평균 운송 비용은 거의 같으므로 북부지사보다 남부지사 쪽이 “효율 높게” 부품을 운송하고 있다는 것을 알 수 있음!
053.네트워크를 가시화해 보자
❗최적화 프로그램이 도출한 계획이 올바른지, 그 계획을 선택할지 여부는 의사결정권자의 이해 여부에 달려 있음! -> 가시화 중요! 조건 만족하는지 확인 중요!
네트워크 가시화 결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| import networkx as nx
import matplotlib.pyplot as plt
# 그래프 객체 생성
G = nx.Graph()
# 노드 설정
G.add_node("nodeA")
G.add_node("nodeB")
G.add_node("nodeC")
# 엣지 설정
G.add_edge("nodeA", "nodeB")
G.add_edge("nodeA", "nodeC")
G.add_edge("nodeB", "nodeC")
# 좌표 설정
pos = {}
pos["nodeA"] = (0,0)
pos["nodeB"] = (1,1)
pos["nodeC"] = (0,1)
# 그리기
nx.draw(G, pos)
# 표시
plt.show()
|
054.네트워크에 노드를 추가해 보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| import networkx as nx
import matplotlib.pyplot as plt
# 그래프 객체 생성
G = nx.Graph()
# 노드 설정
G.add_node("nodeA")
G.add_node("nodeB")
G.add_node("nodeC")
G.add_node("nodeD")
# 엣지 설정
G.add_edge("nodeA", "nodeB")
G.add_edge("nodeA", "nodeC")
G.add_edge("nodeB", "nodeC")
G.add_edge("nodeA", "nodeD")
# 좌표 설정
pos = {}
pos["nodeA"] = (0,0)
pos["nodeB"] = (1,1)
pos["nodeC"] = (0,1)
pos["nodeD"] = (1,0)
# 그리기
nx.draw(G, pos, with_labels=True)
# 표시
plt.show()
|
055.경로에 가중치를 부여하자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
# 데이터 불러오기
df_w = pd.read_csv('network_weight.csv')
df_p = pd.read_csv('network_pos.csv')
# 엣지 가중치 리스트화
size = 10
edge_weights = []
for i in range(len(df_w)):
for j in range(len(df_w.columns)):
edge_weights.append(df_w.iloc[i][j]*size)
# 그래프 객체 생성
G = nx.Graph()
# 노드 설정
for i in range(len(df_w.columns)):
G.add_node(df_w.columns[i])
# 엣지 설정
for i in range(len(df_w.columns)):
for j in range(len(df_w.columns)):
G.add_edge(df_w.columns[i], df_w.columns[j])
# 좌표 설정
pos = {}
for i in range(len(df_w.columns)):
node = df_w.columns[i]
pos[node] = (df_p[node][0], df_p[node][1])
# 그리기
nx.draw(G, pos, with_labels = True, font_size=16, node_size=1000, node_color='k',font_color='w', width=edge_weights)
# 표시
plt.show()
|
056.운송 경로 정보를 불러오자
최소의 비용으로 부품을 배송하려면, 어느 창고에서 어느 공장으로 어느 정도 양을 운송해야 하는지를 검토해야함.
데이터 정보
경로 정보 불러오기
1
2
3
4
5
| import pandas as pd
# 데이터 불러오기
df_tr = pd.read_csv('trans_route.csv',index_col="공장")
# "공장"열을 index로!
df_tr.head()
|
057.운송 경로 정보로 네트워크를 가시화해 보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| import pandas as pd
import matplotlib.pyplot as plt
import networkx as nx
# 데이터 불러오기
df_tr = pd.read_csv('trans_route.csv', index_col="공장")
df_pos = pd.read_csv('trans_route_pos.csv')
# 왼쪽: 창고 / 오른쪽: 공장 -> 파악 쉬움
# 객체 생성
G = nx.Graph()
# 노드 설정
for i in range(len(df_pos.columns)):
G.add_node(df_pos.columns[i])
# 엣지 설정 및 가중치 리스트화(동시에! 이렇게 해야 엣지의 수와 가중치의 수가 달라지지 않음)
num_pre = 0
edge_weights = []
size = 0.1
for i in range(len(df_pos.columns)):
for j in range(len(df_pos.columns)):
if not (i==j):
# 엣지 추가
G.add_edge(df_pos.columns[i], df_pos.columns[j])
# 엣지 가중치 추가
if num_pre<len(G.edges):
num_pre = len(G.edges)
weight = 0
if (df_pos.columns[i] in df_tr.columns) and (df_pos.columns[j] in df_tr.index):
if df_tr[df_pos.columns[i]][df_pos.columns[j]]:
weight = df_tf[df_pos.columns[i]][df_pos.columns[j]]*size
elif(df_pos.columns[j] in df_tr.columns) and (df_pos.columns[i] in df_tr.index):
if df_tr[df_pos.columns[j]][df_pos.columns[i]]:
weight = df_tr[df_pos.columns[j]][df_pos.columns[i]]*size
edge_weights.append(weight)
# 좌표 설정
pos = {}
for i in range(len(df_pos.columns)):
node = df_pos.columns[i]
pos[node] = (df_pos[node][0], df_pos[node][1])
# 그리기
nx.draw(G, pos, with_labels=True, font_size=16, node_size=1000, node_color='k', font_color='w', width=edge_weights)
# 표시
plt.show()
|
058.운송 비용 함수를 작성하자
최적화 문제 푸는 패턴
- 최소화(또는 최대화)하고 싶은 것을 함수(목적 함수)로 정의
- 최소화(또는 최대화)를 함에 있어 지켜야 할 조건(제약 조건)을 정의
- 생각할 수 있는 여러 가지 조합 중에서 제약 조건을 만족시키면서 목적함수를 최소화(또는 최대화)하는 조합을 선택
운송 비용 계산
- 가설: “운송 비용을 낮출 효율적인 운송 경로가 존재한다”
- 목적함수: 운송 비용을 계산할 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import pandas as pd
# 데이터 불러오기
df_tr = pd.read_csv('trans_route.csv', index_col = "공장")
df_tc = pd.read_csv('trans_cost.csv', index_col = "공장")
# 운송 비용 함수
def trans_cost(df_tr, df_tc):
cost = 0
for i in range(len(df_tc.index)):
for j in range(len(df_tr.columns)):
cost += df_tr.iloc[i][j]*df_tc.iloc[i][j]
return cost
print("총 운송 비용: "+ str(trans_cost(df_tr, df_tc)))
# 총 운송 비용: 1493
|
059.제약 조건을 만들어보자
- 각 창고: 공급 가능한 부품 수에 제한있음
- 각 공장: 채워야 할 최소한의 제품 제조량이 있음
제약 조건 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| import pandas as pd
# 데이터 불러오기
df_tr = pd.read_csv('trans_route.csv', index_col="공장")
df_demand = pd.read_csv('demand.csv')
df_supply = pd.read_csv('supply.csv')
# 수요측 제약 조건
for i in range(len(df_demand.columns)):
temp_sum = sum(df_tr[df_demand.columns[i]])
print(str(df_demand.columns[i]) + "으로 운송량: "+str(temp_sum)+"(수요량:" + str(df_demand.iloc[0][i])+")")
if temp_sum >= df_demand.iloc[0][i]:
print("수요량을 만족시키고 있음")
else:
print("수요량을 만족시키지 못하고 있음. 운송경로 재계산 필요")
# 공급측 제약 조건
for i in range(len(df_supply.columns)):
temp_sum = sum(df_tr.loc[df_supply.columns[i]])
print(str(df_supply.columns[i])+" 부터의 운송량: "+str(temp_sum)+" (공급한 계:"+str(df_supply.iloc[0][i])+")")
if temp_sum <= df_supply.iloc[0][i]:
print("공급한계 범위내")
else:
print("공급한계 초과. 운송경로 재계산 필요")
|
060.운송 경로를 변경해서 운송 비용 함수의 변화를 확인하자
시험삼아 W1에서 F4로의 운송⬇️ 그만큼을 W2에서 F4로의 운송으로 보충! -> trans_route_new.csv
비용 개선 계산
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| import pandas as pd
import numpy as np
# 데이터 불러오기
df_tr_new = pd.read_csv('trans_route_new.csv', index_col = '공장')
print(df_tr_new)
# 총 운송 비용 재계산
print("총 운송 비용(변경 후) :"+str(trans_cost(df_tr_new, df_tc)))
# 제약 조건 계산 함수
# 수요측
def condition_demand(df_tr, df_demand):
flag = np.zeros(len(df_demand.columns))
for i in range(len(df_demand.columns)):
temp_sum = sum(df_tr[df_demand.columns[i]])
if (temp_sum>=df_demand.iloc[0][i]):
flag[i]=1
return flag
# 공급측
def condition_supply(df_tr, df_supply):
flag = np.zeros(len(df_supply.columns))
for i in range(len(df_supply.columns)):
temp_sum = sum(df_tr.loc[df_supply.columns[i]])
# 행 참조
if (temp_sum<=df_supply.iloc[0][i]):
flag[i]=1
# 그래프화 조건 만족하면 1, 아니면 0
return flag
print("수요조건 계산결과: "+str(condition_demand(df_tr_new, df_demand)))
print("공급조건 계산결과: "+str(condition_supply(df_tr_new, df_supply)))
|