파이썬

파이썬 데이터 분석 실무 테크닉 100 -최적화(2)

Leesemo 2021. 9. 14. 21:37

테크닉 056 : 운송 경로 정보를 불러오자

이번 글에는 본격적으로 물류 데이터를 이용해 최적화를 진행해 볼 것이다.

# 데이터 불러오기

df_tr = pd.read_csv('data/6장/trans_route.csv', index_col = '공장')
df_tr.head()

결과를 보면 어느 창고에서 어느 공장으로의 물류가 많은지는 알 수 있다.

그러면 네트워크 가시화를 이용해 운송 경로를 가시화해 보자.

df_pos = pd.read_csv('data/6장/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_tr[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()

위의 코드를 설명하자면 객체를 선언하고 노드를 설정한다. 노드 정보는 위치 정보를 포함하고 있는 trans_route_pos를 사용한다. 엣지 설정과 엣지의 가중치 작성을 동시에 진행한다. 이렇게 하면 엣지의 수와 엣지의 가중치 수는 달라지지 않는다. 그리고 읽어 들인 노드의 위치 정보를 pos에 저장하고 그래프를 그린다.

결과를 보면 어느 창고에서 어느 공장으로든 골고루 엣지가 보인다는 점이다. 운송 비용을 생각하면 운송 경로는 어느 정도 집중되는 편이 효율이 높을 것이다.


테크닉 058 : 운송 비용 함수를 작성하자.

df_tr = pd.read_csv('data/6장/trans_route.csv', index_col = '공장')
df_tc = pd.read_csv('data/6장/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 : 제약 조건을 만들어보자

df_tr = pd.read_csv('data/6장/trans_route.csv', index_col = '공장')
df_demand = pd.read_csv('data/6장/demand.csv')
df_supply = pd.read_csv('data/6장/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로의 운송으로 보충하게 변경한 경로가 저장된 CSV를 불러와 제약 조건을 만족시키는지, 그리고 어느 정도 비용 개선이 가능한지 계산한다.

import numpy as np

df_tr_new = pd.read_csv('data/6장/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
    return flag

print('수요조건 계산결과' + str(condition_demand(df_tr_new, df_demand)))
print('공급조건 계산결과' + str(condition_supply(df_tr_new, df_supply)))

W1에서 F4로의 운송을 줄이고, 그만큼을 W2에서 F4로의 운송으로 보충하는 것이다. 이것에 의해 운송 비용은 1428(만 원)로, 원래 운송 비용 1493(만 원)과 비교하면 약간의 비용 절감을 기대할 수 있다. 하지만 두 번째 공급 조건을 만족시키지 못해 공장 W2의 공급 한계를 넘어 버린 것을 알 수 있다.

 

지금까지 최적의 물류 계획을 세우기 위한 흐름을 배웠다. 다음으로는 최적 계산 방법을 배우고 물류 네트워크 전체의 최적화를 검토해 볼 것이다.