サプライ・チェイン最適化のためのデータ生成

顧客データのランダム生成関数 generate_cust

日本の郵便番号データからランダムに地点をサンプリングすることによって、仮想の顧客データを生成する。 社名はFakeパッケージを用いて生成する。 配送計画問題の例題を作成する場合は、あまり遠い地点を選ばないように、都道府県名を引数 prefecture で指定する。 例えば、ロジスティクス・ネットワーク設計モデルやサービスネットワーク設計モデルにおいては、日本全国からランダムに選択し、配送計画モデルにおいては1つの県から選択する。

引数:

  • num_locations: 地点数
  • random_seed: 乱数の種(同じ問題例が欲しい場合には、同じ種を与える。)
  • prefecture: 都道府県名(例えば"千葉県"のような文字列で与える。)省略するか空白の文字列の場合には、日本全国から選択する。

返値:

  • ランダムに生成された顧客データ

データの列は、名前 (name) と緯度・経度 (lat, lon; latitude, Longitudeの略) があれば良いが、郵便番号と住所付加されている。

generate_cust[source]

generate_cust(num_locations=10, random_seed=1, prefecture=None)

顧客データをランダムに生成する関数

generate_cust の使用例

cust_df = generate_cust(num_locations = 10, random_seed = 1, prefecture = None )
cust_df.head()
zip 都道府県 市区町村 大字 lat lon name
0 2995218 千葉県 勝浦市 小羽戸 35.182874 140.25605662 合同会社藤本建設
1 6308443 奈良県 奈良市 南永井町 34.651343 135.82379671 有限会社西之園運輸
2 4700133 愛知県 日進市 梅森台 35.134927 137.01172581 株式会社村山印刷
3 8240072 福岡県 行橋市 須磨園 33.740026 130.92690378 青田印刷合同会社
4 3212421 栃木県 日光市 栗原 36.774624 139.70691895 合同会社山田水産

顧客データの読み込み

顧客データをランダムに生成すると、リアリティがないデータになる危険性が高い。 日本全体にまんべんなく散りばめた顧客データが欲しい場合には、あらかじめ準備されたデータを用いる。 ここでは、各県の県庁所在地に顧客がいると仮定したデータを用いる。

ファイル名はCust.csvであり、データの列は、名前 (name) と緯度・経度 (lat, lon; latitude, Longitudeの略) である。

cust_df = pd.read_csv(folder+"Cust.csv", index_col="id")
cust_df.head()
name lat lon
id
1 札幌市 43.06417 141.34694
2 青森市 40.82444 140.74000
3 盛岡市 39.70361 141.15250
4 仙台市 38.26889 140.87194
5 秋田市 39.71861 140.10250

データベースへの保存

Mongodb Atlas server https://cloud.mongodb.com/ を用いる. 会社のメイルでログイン, 512Mまで無料.

Mongoengine (Object Document http://mongoengine.org/ で接続.

pip (pipenv) でインストールできる. dnspythonも同時に入れる必要がある.

以下のようにクラスでドキュメントを定義する.

class User(Document):
    email = EmailField(required=True, unique = True)
    first_name = StringField(max_length=50)
    last_name = StringField(max_length=50)
    password = StringField(min_length = 8, max_length=50)

もしくはpymongoで,データフレームをそのままクライアントに保存する.

これが一番簡単だが,プロジェクト形式で,複数のデータフレームをまとめておくことができない.

cust_df = pd.read_csv(folder+"Cust.csv", index_col=0)
prod_df = pd.read_csv(folder + "Prod.csv", index_col =0)
total_demand_df = pd.read_csv(folder + "total_demand.csv")
db.cust_df.drop()
db.cust_df.create_index("name", unique =True)
db.cust_df.insert_many(cust_df.to_dict("records")) #insert all data to db.cust_df collection
<pymongo.results.InsertManyResult at 0x7ff611f4b040>
new_cust_df = pd.DataFrame.from_records(db.cust_df.find() )

new_cust_df.tail()
_id name lat lon
42 5f5477c11b259e7bb840af7d 熊本市 32.78972 130.74167
43 5f5477c11b259e7bb840af7e 大分市 33.23806 131.61250
44 5f5477c11b259e7bb840af7f 宮崎市 31.91111 131.42389
45 5f5477c11b259e7bb840af80 鹿児島市 31.56028 130.55806
46 5f5477c11b259e7bb840af81 那覇市 26.21250 127.68111
cust_df = pd.read_csv(folder+"Cust.csv", index_col=0)
prod_df = pd.read_csv(folder + "Prod.csv", index_col =0)
total_demand_df = pd.read_csv(folder + "total_demand.csv", index_col=0)
total_demand_df
prod cust demand
0 A さいたま市 85.23
1 A 京都市 67.18
2 A 仙台市 75.21
3 A 佐賀市 84.23
4 A 前橋市 67.18
... ... ... ...
465 J 静岡市 172.47
466 J 高松市 205.56
467 J 高知市 109.30
468 J 鳥取市 129.35
469 J 鹿児島市 304.84

470 rows × 3 columns

prod_df.head()
name weight volume cust_value dc_value plnt_value fixed_cost average_demand standard_deviation inv_cost lot_size safety_inventory target_inventory initial_inventory
0 A 1 0 8 1 1 18 1015.153846 367.846749 0.005769 2516.855181 606.947136 3123.802316 1865.0
1 B 4 0 6 1 1 11 480.596154 169.345187 0.005769 1353.762658 279.419559 1633.182217 956.0
2 C 5 0 6 1 1 16 456.557692 162.361706 0.005769 1591.343248 267.896815 1859.240063 1063.0
3 D 2 0 7 1 1 16 165.230769 47.416591 0.005769 957.329619 78.237375 1035.566994 556.0
4 E 3 0 9 1 1 14 390.846154 134.714885 0.005769 1377.282348 222.279560 1599.561908 910.0
total_demand_df.head()
prod cust demand
0 A さいたま市 185.51
1 A 京都市 96.26
2 A 仙台市 310.85
3 A 佐賀市 157.43
4 A 前橋市 159.44
class Customer(EmbeddedDocument):
    name = StringField(max_length=200, required=True)
    lat  = FloatField()
    lon  = FloatField()
    
class Demand(EmbeddedDocument):
    cust = StringField(max_length=200, required=True)
    prod = StringField(max_length=200, required=True)
    demand  = FloatField()
    
class Product(EmbeddedDocument):
    name = StringField(max_length=200, required=True)
    weight =  FloatField()
    volume =  FloatField()
class CustomerSet(EmbeddedDocument):
    name      = StringField(max_length=200)
    customers = ListField(EmbeddedDocumentField(Customer))
    
class ProductSet(EmbeddedDocument):
    name      = StringField(max_length=200)
    products = ListField(EmbeddedDocumentField(Product))
    
class DemandSet(EmbeddedDocument):
    name      = StringField(max_length=200)
    demands = ListField(EmbeddedDocumentField(Demand))
total_demand_df
prod cust demand
0 A さいたま市 85.23
1 A 京都市 67.18
2 A 仙台市 75.21
3 A 佐賀市 84.23
4 A 前橋市 67.18
... ... ... ...
465 J 静岡市 172.47
466 J 高松市 205.56
467 J 高知市 109.30
468 J 鳥取市 129.35
469 J 鹿児島市 304.84

470 rows × 3 columns

cs1 = CustomerSet(customers= cust_df.to_dict("record"))
ps1 = ProductSet(products= prod_df[["name","weight","volume"]].to_dict("record"))
dem1 = DemandSet(demands= total_demand_df.to_dict("record"))
/Users/mikiokubo/.local/share/virtualenvs/scmopt-YRAFIXfq/lib/python3.8/site-packages/pandas/core/frame.py:1485: FutureWarning: Using short name for 'orient' is deprecated. Only the options: ('dict', list, 'series', 'split', 'records', 'index') will be used in a future version. Use one of the above to silence this warning.
  warnings.warn(
class LogisticNetworkDesign(Document):
    name = StringField(max_length=200, required=True)
    owner = StringField(max_length=200, required=True)
    
    customer_set = EmbeddedDocumentField(CustomerSet)
    product_set = EmbeddedDocumentField(ProductSet)
    demand_set = EmbeddedDocumentField(DemandSet)
lnd1 = LogisticNetworkDesign(name="sample lnd", owner="kubo@logopt.com", customer_set=cs1, product_set = ps1, demand_set = dem1)
lnd1.save();

データベースからの読み込み

name, lat, lon =[], [], [] 
for i in lnd1.customer_set.customers:
    #print(i.name, i.lat, i.lon)
    name.append(i.name)
    lat.append(i.lat)
    lon.append(i.lon)
new_cust_df = pd.DataFrame.from_dict({"name":name, "lat":lat,"lon":lon})
new_cust_df.head()
name lat lon
0 札幌市 43.06417 141.34694
1 青森市 40.82444 140.74000
2 盛岡市 39.70361 141.15250
3 仙台市 38.26889 140.87194
4 秋田市 39.71861 140.10250
name, weight, volume =[], [], []
for i in lnd1.product_set.products:
    name.append(i.name)
    weight.append(i.weight)
    volume.append(i.volume)
new_prod_df = pd.DataFrame.from_dict({"name":name, "weight":weight,"volume":volume})
new_prod_df.head()
name weight volume
0 A 1.0 0.0
1 B 4.0 0.0
2 C 5.0 0.0
3 D 2.0 0.0
4 E 3.0 0.0
prod, cust, demand =[], [], []
for i in lnd1.demand_set.demands:
    prod.append(i.prod)
    cust.append(i.cust)
    demand.append(i.demand)
new_demand_df = pd.DataFrame.from_dict({"prod":prod, "cust":cust,"volume":demand})
new_demand_df.head()
prod cust volume
0 A さいたま市 78.21
1 A 京都市 1314.60
2 A 仙台市 174.48
3 A 佐賀市 205.56
4 A 前橋市 73.20

中国の配送計画問題用の顧客データとトラックデータの生成

random_seed = 1 
n_cust = 100
n_vehicle = 25
lat_center = 35.486703
lon_center = 101.901875
std = 1.
np.random.seed(random_seed)

Faker.seed(random_seed)
fake = Faker(['zh_CN'])
fake_name = []
for _ in range(n_cust):
    fake_name.append(fake.company())
start = datetime.datetime(2020,1,5,6,0)
early, late = [], [] 
for i in range(n_cust):
    early_minutes = random.randint(0,11*60)
    early.append(start + datetime.timedelta(minutes=early_minutes))
    late.append(start+ + datetime.timedelta(minutes=random.randint(early_minutes + 5,12*60)))
for i in range(len(early)):
    if early[i]> late[i]:
        print(i)
lat_list = np.random.normal(lat_center,std,n_cust)
lon_list = np.random.normal(lon_center,std,n_cust)
wlb, wub = 100, 1000
vlb, vub = 5, 10
tlb, tub = 10, 20 
customer_df = pd.DataFrame({"name":fake_name, 
                        "weight":np.random.randint(wlb,wub,n_cust), 
                        "volume":np.random.randint(vlb,vub,n_cust), 
                        "time":np.random.randint(tlb,tub,n_cust), 
                        "maxVehicle":np.random.choice([4,11],n_cust),
                        "early": early,
                        "late": late,
                        "latitude":lat_list, "longitude":lon_list})
customer_df.to_csv(folder+"/metro/customer.csv")
vehicle_df = pd.DataFrame({"name":np.arange(n_vehicle),
                          "size":np.random.choice([4,11],n_vehicle),
                          "maxWeight": np.random.choice([4000,10000],n_vehicle),
                          "maxVolume": np.random.choice([40,50,60],n_vehicle),
                           "maxTime": np.random.choice([500,800],n_vehicle),
                            "start": start,
                            "costPerKm": np.random.choice([200,300,400],n_vehicle)})
vehicle_df.to_csv(folder+"/metro/vehicle.csv")

Plotlyによる可視化関数 plot_cust

可視化モジュールPlotlyを使うと、顧客を地図上に表示できる。

引数:

  • cust_df: 顧客データフレーム

返値:

  • fig: Plotlyの図オブジェクト

plot_cust[source]

plot_cust(cust_df)

顧客データフレームを入れると、PlotlyのFigureオブジェクトに地図を入れて返す関数

plot_cust 関数の使用例

上のplot_cust関数の適用例を示す。

# 図を表示すると、githubでエラーする。図が見たい場合には、以下を生かす。
fig = plot_cust(cust_df)
#fig.show()
#plotly.offline.plot(fig)
Image("../figure/plot_cust.png")

製品データの生成関数 generate_prod

製品名は仮想のデータとし、大文字のアルファベットとする。(製品名を具体的に入れると、すぐに企業名がバレてしまう。これは、守秘義務契約に引っかかる可能性が高いのだ。) したがって、26以下の整数を製品数num_prodに指定する。

基本となる製品は、以下のデータをもつ。

  • weight: 製品の重量を表す。これは引数 weight_bound (下限と上限のタプル) 間の一様乱数によって生成された整数とする。 (単位重量)
  • volume: 製品の容積を表す。これは引数 volume_bound (下限と上限のタプル) 間の一様乱数によって生成された整数とする。(単位容積)
  • cust_value : 顧客上での製品の価値を表す。これは引数 cust_value_bound (下限と上限のタプル) 間の一様乱数によって生成された整数とする。(円)
  • dc_value : 倉庫上での製品の価値を表す。これは引数 dc_value_bound (下限と上限のタプル) 間の一様乱数によって生成された整数とする。(円)
  • plnt_value : 工場における製品の価値を表す。これは引数 plnt_value_bound (下限と上限のタプル) 間の一様乱数によって生成された整数とする。(円)
  • fixed_cost: 製品の生産固定費用を表す。これは引数fc_bound (下限と上限のタプル) 間の一様乱数によって生成された整数とする。(円/回)

引数:

  • num_prod: 製品数(26以下の整数)
  • weight_bound: 製品の重量を生成するための下限と上限のタプル
  • volume_bound: 製品の容積を生成するための下限と上限のタプル
  • cust_value_bound: 顧客上での製品の価値を生成するための下限と上限のタプル
  • dc_value_bound: 倉庫上での製品の価値を生成するための下限と上限のタプル
  • plnt_value_bound: 工場における製品の価値を生成するための下限と上限のタプル
  • fc_bound: 製品の生産固定費用生成のための下限と上限のタプル

返値:

  • 製品データフレーム

generate_prod[source]

generate_prod(num_prod=10, weight_bound=(0, 0), volume_bound=(0, 0), cust_value_bound=(1, 1), dc_value_bound=(1, 1), plnt_value_bound=(1, 1), fc_bound=(1, 1))

仮想の製品のデータフレームを生成する関数

generate_prod関数の使用例

20個の製品をそれらの重量を1以上5以下の整数に、顧客上での価値は5以上10以下の整数に、容積と他の価値は既定値、生産固定費用を10以上20以下の整数になるように設定する。

prod_df = generate_prod(10, weight_bound=(1, 5), cust_value_bound =(5,10), fc_bound=(10,20))
#prod_df.to_csv(folder+"Prod.csv")
prod_df.head()
name weight volume cust_value dc_value plnt_value fixed_cost
0 A 2 0 9 1 1 13
1 B 2 0 10 1 1 20
2 C 3 0 7 1 1 15
3 D 4 0 10 1 1 18
4 E 3 0 6 1 1 18

需要データの生成関数 generate_demand

顧客データと製品データを与えると、仮想の需要データを生成する関数を準備しておく。 需要は、以下のパラメータを用いて生成される。基本的な分布はパレート分布とし、週次ならびに月次の波動と誤差項を加える。

引数:

  • cust_df: 顧客データフレーム
  • prod_df: 製品データフレーム
  • cust_shape: 顧客のパレート分布の形状を定める定数
  • prod_shape: 製品のパレート分布の形状を定める定数
  • weekly_ratio: 週次の波動を表すリスト(0が日曜日);長さは7
  • yearly_ratio: 年次の波動を表すリスト(0が1月);長さは12
  • start: 開始日
  • periods: 計画期間数
  • epsilon: 誤差項の標準偏差

返値:

  • demand_df: 需要データフレーム

generate_demand[source]

generate_demand(cust_df, prod_df, cust_shape=1.7, prod_shape=1.7, weekly_ratio=[1, 1, 1, 1, 1, 1, 1], yearly_ratio=[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], start='2019/01/01', periods=365, epsilon=0.0)

ランダムな需要を生成する関数

generate_demand関数の使用例

weekly_ratio = [0.0, 1.0, 1.2, 1.3, 0.9, 1.5, 0.2]  # 0 means Sunday
yearly_ratio = [1.0 + np.sin(i)*0.5 for i in range(13)]  # 0 means January
demand_df = generate_demand(cust_df, prod_df, cust_shape=1.7, prod_shape=1.6, weekly_ratio=weekly_ratio, yearly_ratio=yearly_ratio,
                            start="2019/01/01", periods=365, epsilon=1.)
demand_df.to_csv(folder + "demand.csv")
demand_df.head()
date cust prod demand
0 2019-01-01 札幌市 A 0
1 2019-01-01 札幌市 B 0
2 2019-01-01 札幌市 C 0
3 2019-01-01 札幌市 D 0
4 2019-01-01 札幌市 E 0

プロモーション情報の生成とプロモーションを加味した需要の生成関数 generate_demand_with_promo

引数:

  • cust_df: 顧客データフレーム
  • prod_df: 製品データフレーム
  • promo_df: プロモーション情報を入れたデータフレーム(promo_dfは、需要と同じ日付を入れた列dateと、プロモーション名を列名とし、その効果を列データとした列を持つものとする。)
  • cust_shape: 顧客のパレート分布の形状を定める定数
  • prod_shape: 製品のパレート分布の形状を定める定数
  • weekly_ratio: 週次の波動を表すリスト(0が日曜日);長さは7
  • yearly_ratio: 年次の波動を表すリスト(0が1月);長さは12
  • start: 開始日
  • periods: 計画期間数
  • epsilon: 誤差項の標準偏差

返値:

  • demand_df: プロモーション情報を加味した需要データフレーム

generate_demand_with_promo[source]

generate_demand_with_promo(cust_df, prod_df, promo_df, cust_shape=1.7, prod_shape=1.7, weekly_ratio=[1, 1, 1, 1, 1, 1, 1], yearly_ratio=[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], start='2019/01/01', periods=365, epsilon=0.0)

プロモーション情報を加味したランダムな需要を生成する関数

generate_demand_with_promo関数の使用例

プロモーション情報を加えた需要データを生成しておく。プロモーション情報は、promo_dfデータフレームに保管してあるものとする。

promo_dfは、需要と同じ日付を入れた列dateと、プロモーション名を列名とし、その効果を列データとした列を持つものとする。

start="2019/01/01"
periods =365*3
date_col = []
num_promo =2 
promo_cols = [ [] for promo_ in range(num_promo)]
for t in pd.date_range(start, periods=periods):
    date_col.append(t)
    if t.weekday() == 2:  # Tsuesday promotion 
        promo_cols[0].append(1)
    else:
        promo_cols[0].append(0)
    if t.day%10 ==0: # 10th, 20th 30th day promotion
        promo_cols[1].append(1)
    else:
        promo_cols[1].append(0)

promo_df = pd.DataFrame(data={"date": date_col, "promo_0": promo_cols[0], "promo_1": promo_cols[1]})
#promo_df.head(20)
promo_df.to_csv(folder + "promo.csv")
promo_df.head()
date promo_0 promo_1
0 2019-01-01 0 0
1 2019-01-02 1 0
2 2019-01-03 0 0
3 2019-01-04 0 0
4 2019-01-05 0 0
weekly_ratio = [0.0, 1.0, 1.2, 1.3, 0.9, 1.5, 0.2]  # 0 means Sunday
yearly_ratio = [1.0 + np.sin(i)*0.5 for i in range(13)]  # 0 means January
demand_with_promo_df = generate_demand_with_promo(cust_df, prod_df, promo_df, cust_shape=1.7, prod_shape=1.6, weekly_ratio=weekly_ratio, yearly_ratio=yearly_ratio,
                            start="2019/01/01", periods=365*2, epsilon=1.)
demand_with_promo_df.to_csv(folder + "demand_with_promo.csv")
demand_with_promo_df.tail()
date cust prod promo_0 promo_1 demand
343095 2020-12-30 那覇市 F 1 1 1
343096 2020-12-30 那覇市 G 1 1 1
343097 2020-12-30 那覇市 H 1 1 1
343098 2020-12-30 那覇市 I 1 1 1
343099 2020-12-30 那覇市 J 1 1 2
demand_with_promo_df.head()
date cust prod promo_0 promo_1 demand
0 2019-01-01 札幌市 A 0 0 0
1 2019-01-01 札幌市 B 0 0 0
2 2019-01-01 札幌市 C 0 0 0
3 2019-01-01 札幌市 D 0 0 0
4 2019-01-01 札幌市 E 0 0 0

予約需要の生成関数 generate_reservation_demand

収益管理で用いるランダムな予約需要データフレームを生成する。

引数:

  • class_df: 予約のクラスを表すデータフレーム; class_name列と基本需要(Possion分布に与える平均需要)を表す列 basic_demandをもつ。
  • weekly_ratio: 週次の波動を表すリスト(0が日曜日);長さは7
  • start: 開始日
  • periods: 計画期間数
  • max_lt: 予約可能な日が何日前かを表すパラメータ

返値:

  • reserve_demand_df: 予約需要データフレーム

generate_reservation_demand[source]

generate_reservation_demand(class_df, weekly_ratio=[1, 1, 1, 1, 1, 1, 1], start='2019/01/01', periods=7, max_lt=1)

ランダムな予約需要を生成する関数

generate_reservation_demand関数の使用例

class_df = pd.DataFrame({"class_name":["C","B","A"], "basic_demand": [20, 10, 5]})
weekly_ratio = [0.0, 1.0, 1.2, 1.3, 0.9, 1.5, 0.2]  # 0 means Sunday
start="2019/01/01"
periods = 60
max_lt = 30 # reservation in starts max_lt before check-in day 
reserve_demand_df = generate_reservation_demand(class_df, weekly_ratio=[1]*7, start="2019/01/01", periods=7, max_lt = 1)
reserve_demand_df.to_csv(folder+"reserve_demand.csv")
reserve_demand_df.head()
reserve checkin class_name demand
0 2019-01-01 2019-01-02 C 14
1 2019-01-01 2019-01-02 B 6
2 2019-01-01 2019-01-02 A 5
3 2019-01-02 2019-01-02 C 19
4 2019-01-02 2019-01-02 B 8

OD需要量の生成

サービスネットワーク設計問題に対しては、地点間の需要量を定義する必要がある。荷物の始点を発生地点(origin)、終点を集中地点(destination)と呼ぶ。 地点間の需要量は、ODフロー量と呼ばれる。

ここでは、重力法 (gravity method) を用いて需要量を算出する。 県別の人口を入れた顧客データを読み込み、以下の式によって地点 $i$ から地点 $j$ へのODフロー量を計算する。

記号:

  • $P_i$ : 地点 $i$ の人口
  • $d_{ij}$ : 地点 $i,j$ 間の距離(ここでは大圏距離とする。)
  • $D_{ij}$ : 地点 $i,j$ 間のODフロー量
  • $\alpha$ : 発生地点の人口がODフロー量に与える影響度
  • $\beta$ : 集中地点の人口がODフロー量に与える影響度

重力法: $$ D_{ij}= P_i^{\alpha} P_j^{\beta}/d_{ij} $$

都道府県の人口を入れたデータCust_with_population.csvを読み込む。

# #人口データの付加
# cust_df = pd.read_csv(folder+"Cust.csv", index_col=0)
# pop_df = pd.read_csv("population.csv")
# cust_df.reset_index(inplace=True)
# cust_df["population"] = pop_df["pop"].str.replace(",","").astype(float)
# cust_df.to_csv(folder + "Cust_with_population.csv")
cust_df = pd.read_csv(folder+"Cust_with_population.csv", index_col=0)
cust_df.head()
id name lat lon population
0 1 札幌市 43.06417 141.34694 5320.0
1 2 青森市 40.82444 140.74000 1278.0
2 3 盛岡市 39.70361 141.15250 1255.0
3 4 仙台市 38.26889 140.87194 2323.0
4 5 秋田市 39.71861 140.10250 996.0
n = len(cust_df)
D = np.zeros( (n,n) )
Distance = np.zeros( (n,n) )
alpha, beta = 1.,0.5
for i, row1 in enumerate(cust_df.itertuples()):
    for j, row2 in enumerate(cust_df.itertuples()):
        if i==j:
            D[i,j] = 0.
        else:
            D[i,j] = (row1.population**alpha) * (row2.population**beta) /distance( (row1.lat, row1.lon), (row2.lat,row2.lon)).km
            Distance[i,j] = distance( (row1.lat, row1.lon), (row2.lat,row2.lon)).km
od_df = pd.DataFrame(D, index=cust_df.name, columns=cust_df.name)
od_df.head()
name 札幌市 青森市 盛岡市 仙台市 秋田市 山形市 福島市 水戸市 宇都宮市 前橋市 ... 松山市 高知市 福岡市 佐賀市 長崎市 熊本市 大分市 宮崎市 鹿児島市 那覇市
name
札幌市 0.000000 748.604013 503.880787 479.530853 434.728978 325.400616 387.515654 380.707553 320.950984 306.975814 ... 155.037392 113.142207 268.320507 104.968908 128.494480 151.969403 130.564385 115.832932 134.674821 89.979170
青森市 366.911870 0.000000 349.739599 216.591680 300.250555 146.736232 161.797705 137.690614 117.949336 110.061161 ... 45.045703 33.190310 75.179922 29.333277 35.666270 42.628874 37.127441 32.597444 37.480032 24.019524
盛岡市 244.733739 346.578201 0.000000 374.849054 440.904081 236.165870 241.756337 178.105294 151.641357 135.078213 ... 46.792775 34.814853 76.531004 29.884567 36.305497 43.649604 38.222610 33.627525 38.454102 24.419156
仙台市 316.873079 292.012168 509.987066 0.000000 420.427689 1732.062426 1488.646715 574.094165 492.841131 390.859150 ... 97.623923 73.645927 154.638886 60.388289 73.150841 88.750941 78.475998 68.944247 78.125318 48.416035
秋田市 188.101580 265.062360 392.782315 275.293764 0.000000 199.309319 195.354280 142.173921 125.495887 115.713660 ... 39.834352 29.538809 64.824922 25.242764 30.545651 36.765353 32.327894 28.150575 32.123788 19.989013

5 rows × 47 columns

#heatmap
#fig = px.imshow(D)
#fig.show()
od_df.to_csv(folder + "od.csv")

実際問題のデータの読み込み

実際問題を解きたい場合には、上と同じ形式のデータを作成して読みこむ。

Plotly Expressによる需要の変化の図示

fig = px.line(demand_df, x="date",  y="demand", color ="prod")
#fig = px.line(demand_with_promo_df, x="date",  y="demand", color ="prod")
#plotly.offline.plot(fig)
Image("../figure/demand_series.png")

Plotly Expressによる需要のヒストグラムの図示

fig = px.histogram(demand_df, x="demand")
#fig = px.histogram(demand_with_promo_df, x="demand")
#plotly.offline.plot(fig)
Image("../figure/demand_hist.png")

需要の属性を生成する関数 demand_attribute_compute

需要量に応じて、以下のような諸量を計算し、需要データフレームに属性(列)として追加する関数

  • 製品の売り上げ: 需要と製品の顧客上での価値($=$価格)の積
  • 重量合計:需要量と製品重量の積
  • 容積合計:需要量と製品容積の積

引数:

  • demand_df : 需要データフレーム
  • prod_df : 製品データフレーム
  • attribute_col_name : 需要にかけ合わせる製品データの列名;例えば、売り上げを計算したい場合には "cust_value" とする。
  • new_col_name : 計算結果を格納するための列名;例えば、売り上げを保管したい場合には "sales" とする。

返値:

  • 新しい列を追加した需要データフレーム

demand_attribute_compute[source]

demand_attribute_compute(demand_df, prod_df, attribute_col_name, new_col_name)

需要データに新たな属性を追加する関数

demand_attribute_compute関数の使用例

demand_df = demand_attribute_compute(demand_df, prod_df, "cust_value", "sales")
demand_df.to_csv(folder + "demand.csv")
demand_df.head()
date cust prod demand sales
0 2019-01-01 札幌市 A 0 0
1 2019-01-01 札幌市 B 0 0
2 2019-01-01 札幌市 C 0 0
3 2019-01-01 札幌市 D 0 0
4 2019-01-01 札幌市 E 1 9

倉庫データの生成関数 generate_dc

倉庫の開設可能地点は、全ての顧客上と仮定する。 製品ごとの需要に製品の重量 weight を乗じたものの合計を計算し、それを倉庫の予定開設個数で割ることによって、倉庫の容量を設定する。 倉庫の容量の下限と上限を決める率 lb_ratio, ub_ratio を乗じて、下限と上限を決める。

返値のデータフレームの列名と意味は以下の通り.

  • name: 倉庫名称
  • lb: 容量下限
  • ub: 容量上限
  • fc: 固定費用
  • vc: 変動費用
  • lat: 緯度
  • lon: 経度

generate_dc[source]

generate_dc(cust_df, demand_df, prod_df, num_dc=5, lb_ratio=0.8, ub_ratio=1.2)

倉庫データの生成: 顧客データ、需要データ、製品データ、予定開設数、倉庫の容量を決めるための下限率、上限率を与えると、倉庫データを返す。

generate_dc関数の使用例

demand_df = pd.read_csv(folder+"demand.csv")
dc_df = generate_dc(cust_df, demand_df, prod_df, num_dc=5, lb_ratio=0.0, ub_ratio=10.3)
dc_df.to_csv(folder + "DC.csv")
dc_df.head()
name lb ub fc vc lat lon
0 札幌市 0.0 410047.12 100000.0 1.0 43.06417 141.34694
1 青森市 0.0 410047.12 100000.0 1.0 40.82444 140.74000
2 盛岡市 0.0 410047.12 100000.0 1.0 39.70361 141.15250
3 仙台市 0.0 410047.12 100000.0 1.0 38.26889 140.87194
4 秋田市 0.0 410047.12 100000.0 1.0 39.71861 140.10250

工場データと生産情報データの生成関数 generate_plnt

工場は3つで、小田原、大阪、千葉にあると仮定する。 大阪工場では約半分の製品を、千葉では大阪工場で製造していない約半分の製品を生産でき、小田原工場では全ての製品を生産できるものとする。 生産容量は、総需要量と設定しておく。

引数:

  • prod_df : 製品データフレーム
  • demand_df : 需要データフレーム
  • lead_time_bound: 工場での生産ロード時間を決めるためのパラメータ; タプルで与えた下限と上限の間の一様整数乱数とする。

返値:

  • plnt_df: 工場データフレーム
  • plnt_prod_df: 工場・製品データフレーム

generate_plnt[source]

generate_plnt(prod_df, demand_df, lead_time_bound=(1, 1))

工場データの生成

generate_plnt関数の使用例

plnt_df, plnt_prod_df = generate_plnt(prod_df, demand_df, lead_time_bound=(25,30))
plnt_df.to_csv(folder + "Plnt.csv")
plnt_df.head()
name lat lon
0 Odawara 35.284982 139.196133
1 Osaka 34.563101 135.415129
2 Chiba 35.543452 140.113908
plnt_prod_df.to_csv(folder + "Plnt-Prod.csv")
plnt_prod_df.head()
plnt prod ub lead_time
0 Osaka A 4086 29
1 Osaka B 4767 27
2 Osaka C 3695 26
3 Osaka D 7909 29
4 Osaka E 4978 26