本記事では学習のアウトプットとして、『Python実践データ分析100本ノック』に書かれている各ノックのコードのうち、難解と思われる部分の意味を解説していきます。 「本に書かれている解説だけでは理解が難しい(;一_一)」と感じた方、この記事を読んで理解の一助となれば幸いです。
Python実戦データ分析100本ノックは、データの集計や分析のためのpandasやグラフ描画に使用するmatplotlib、機械学習を行うためのscikit-learnなど、データ分析に欠かせない要素を実際に自分で手を動かしながら学ぶことができる本です。
私自身はpython初心者で、pythonを触ったことがありませんでしたが、自ら手を動かしてpythonを学習したいと思いこちらの本で学習しました。よければ手に取っていただきたいです。
今回は第11回として、ノック51-55をやっていきます。
追記:現在第二版が2022年6月に出版されています。
本記事は第1版の内容の解説です。
第二版の解説記事もいずれ書かせていただこうと思っていますので今しばしお待ちください!
第6章 物流の最適ルートをコンサルティングする10本ノック
第6章では、物流のルートを最適化するために最適化問題に取り組みます。おおまかな流れは以下の通りです。
- 最小化または最大化したいを関数(目的関数)を定義する
- 目的関数の最小化または最大化を実現するために必要な制約条件を定義する
- 制約条件を満たしたうえで目的関数を最小化または最大化する組み合わせを選択する
以上が最適化問題の流れです 。
それでは始めましょう!
ノック51:物流に関するデータを読み込んでみよう
import pandas as pd
#これまでと同様にデータを読み込みます。
factories=pd.read_csv("tbl_factory.csv",index_col=0)
factories
warehouses=pd.read_csv("tbl_warehouse.csv",index_col=0)
warehouses
cost=pd.read_csv("rel_cost.csv",index_col=0)
cost.head()
trans=pd.read_csv("tbl_transaction.csv",index_col=0)
trans.head()
#mergeメソッドでtransとcostを結合します。
join_data=pd.merge(trans,cost,left_on=["ToFC","FromWH"],right_on=["FCID","WHID"],how="left")
join_data.head()
#join_dataとfactoriesを結合します。
join_data=pd.merge(join_data,factories,left_on="ToFC",right_on="FCID",how="left")
join_data.head()
#join_dataとwarehousesを結合します。
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()
#loc関数でWHRegionが関東となっているデータ(関東支社のデータ)を抽出します。
kanto=join_data.loc[join_data["WHRegion"]=="関東"]
kanto.head()
#loc関数でWHRegionが東北となっているデータ(東北支社のデータ)を抽出します。
tohoku=join_data.loc[join_data["WHRegion"]=="東北"]
tohoku.head()
mergeメソッドでtransとcostを結合する際、それぞれのテーブルには共通キーとなりそうな列があるものの、名前が異なります。そこで、left_on=で左側のテーブル(trans)のキー”ToFC”、right_on=で右側のテーブル(cost)のキー”FCID”,”WHID”を指定して結合させています。
これでデータの読み込みと整形ができました。
ノック52:現状の輸送量、コストを確認してみよう
#str関数でCostを文字列型に変換して、両脇の文字(~支社の総コストと万円)に接続できるようにしています。
print("関東支社の総コスト:"+str(kanto["Cost"].sum())+"万円")
print("東北支社の総コスト:"+str(tohoku["Cost"].sum())+"万円")
#こちらも、str関数でQuantityを文字列型に変換し、両脇の文字(~支社の総コストと万円)に接続できるようにしています。
print("関東支社の総部品輸送個数:"+str(kanto["Quantity"].sum())+"個")
print("東北支社の総部品輸送個数:"+str(tohoku["Quantity"].sum())+"個")
#sum()を使いながら総コストを総輸送個数で割り算し、tmpに格納します。
tmp=(kanto["Cost"].sum()/kanto["Quantity"].sum())*1000
print("関東支社の部品1つ当たりの輸送コスト:"+str(int(tmp))+"円")
tmp=(tohoku["Cost"].sum()/tohoku["Quantity"].sum())*1000
print("東北支社の部品1つ当たりの輸送コスト:"+str(int(tmp))+"円")
輸送した部品数、輸送にかかったコストを集計できました。
ノック53:ネットワークを可視化してみよう
#ネットワークの可視化に有用なNetworkXを使用します。まずはインポートします。
import networkx as nx
import matplotlib.pyplot as plt
#グラフオブジェクトの作成を行います。
G=nx.Graph()
#add_node(頂点)で頂点の追加を行います。
G.add_node("nodeA")
G.add_node("nodeB")
G.add_node("nodeC")
#add_edge(頂点1, 頂点2)で頂点を結び、辺の追加を行います。
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)
#draw()メソッドで描画します。
nx.draw(G,pos)
#show()メソッドを用いて表示します。
plt.show()
最適ルートを可視化するためのネットワーク可視化を行いました。
ノック54:ネットワークにノードを追加してみよう
#ノック53で作成したネットワークにさらにnodeDを追加します。
G.add_node("nodeD")
#nodeAとnodeDを結び、辺の追加を行います。
G.add_edge("nodeA","nodeD")
#頂点nodeDの座標位置を設定します。
pos["nodeD"]=(1,0)
#with_labells=Trueとすることで頂点に付けた名前を表示します。
nx.draw(G,pos, with_labels=True)
ノック53で作成したネットワークに頂点を追加できました。
ノック55:ルートの重みづけを実施しよう
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
#df_wはA~E各頂点同士のリンクの重みが記載された5行×5列のデータです。
df_w=pd.read_csv('network_weight.csv')
#df_pはA~E各頂点のリンクの位置が記載された2行×5列のデータです。
df_p=pd.read_csv('network_pos.csv')
size=10
#edge_weightsというリストを宣言します。
edge_weights=[]
#nodeの重みデータの要素数(5個)分繰り返し処理します。
for i in range(len(df_w)):
#len(.columns)で列数(横方向の要素数)を取得できます。
#今回は、列はA~Eまでの5つあります。
for j in range(len(df_w.columns)):
#edge_weightsリストにdf_wの全要素に10をかけたものを格納します。
edge_weights.append(df_w.iloc[i][j]*size)
# グラフオブジェクトの作成を行います。
G=nx.Graph()
#頂点の追加を行います。
#df_wの列(A~Eまでの5つ)数分繰り返し処理します。
for i in range(len(df_w.columns)):
#df_wテーブルの列の名前をそのまま頂点として追加します。
G.add_node(df_w.columns[i])
#頂点を結び、辺の追加を行います。
#df_wの列(A~Eまでの5つ)数分繰り返し処理します。
for i in range(len(df_w.columns)):
#iと同じfor文の繰り返し構造ですが、同じ頂点同士を結ぶことを含め、全頂点を結び、辺の追加を行います。
for j in range(len(df_w.columns)):
G.add_edge(df_w.columns[i],df_w.columns[j])
pos={}
#df_wの列(A~Eまでの5つ)数分繰り返し処理します。
for i in range(len(df_w.columns)):
#df_wの列(A~Eまでの5つ)名をnodeという変数に格納します。
node=df_w.columns[i]
#それぞれのnodeに対して、df_pの各行に記載されたデータを座標位置として設定します。
pos[node]=(df_p[node][0],df_p[node][1])
#draw()メソッドで描画します。
nx.draw(G,pos,with_labels=True,font_size=16,node_size=1000,node_color='k',font_color='w',width=edge_weights)
#show()メソッドを用いて表示します。
plt.show()
CSVファイルに記載された重み情報と位置情報を読み込み、重みづけしたリンクを描画できました。
ここまでで、ノック51-55は完了です。お疲れ様でした。 今回は、新しくNetworkXというライブラリを用いてネットワークを描画することができましたね。次回は最適化を本格的に行う第6章後半に入ります。引き続きよろしくお願いいたします(^^)/