Optimizing Logistics Distribution Routes: A Graph Theory Approach
Learn By Building adalah metode pembelajaran dengan cara melakukannya dengan menggunakan data kondisi aktual
Sebuah perusahaan ingin menemukan jalur distribusi yang paling optimal untuk mengirimkan barang ke masing-masing tujuan.
Metode:
💡 Menggunakan bantuan NetworkX yang merupakan package Python yang digunakan untuk menganalisa jaringan kompleks
💡 Merepresentasikan records ke dalam Graf kemudian mencari rute-rute dengan weight terendah.
💡 weight bisa dalam bentuk total biaya (cost) yang dikeluarkan masing-masing rutenya.
💡 Total cost dapat diekstrak dari data records dengan informasi-informasi yang kita ketahui.
💬 Informasi apa saja yang dapat digunakan untuk mendapatkan biaya?
add_costdist (jarak), lge/100km (bahan bakar), dan harga bensin dapat digunakan untuk mencari biaya bahan bakarweight pada setiap edges grafKita adalah seorang data saintis di sebuah perusahaan produsen makanan dingin di Makassar. Kita diberikan data records_mks, dan lokasi_makassar yang berisi informasi pengiriman antar lokasinya.
Selain informasi dari data ini, kita juga mengetahui beberapa informasi antara lain:
Aturan
A (warehouse) adalah pusat penyimpanan. Setiap jalur distribusi berawal dari titik A
Kebutuhan produk perminggu setiap lokasi adalah 1 ton.
Truk dapat mengangkut produk dengan berat maksimal 2,2 ton.
Waktu drop di setiap lokasi adalah -+ 30 menit
Biaya:
Bahan bakar yang digunakan truk adalah Dexlite (Rp14,950/liter)
Biaya pengeluaran mesin pendingin truk adalah Rp9,000/jam
⚠ Disclaimer: Bisnis problem dan data bersifat dummy yang terinspirasi dari hasil penelitian Liang, Wu dan Sun [[2](https://doi.org/10.2991/asei-15.2015.145)] dimana mencari rute optimal pada kasus cold chain logistics menggunakan Particle Swarm Optimization (PSO). Inspirasi lain juga berasal dari artikel Samir Saci [[3](https://towardsdatascience.com/transportation-network-analysis-with-graph-theory-55eceb7e4de4)] yang melakukan analisis jaringan distribusi menggunakan Teori Graf.
import pandas as pd
records = pd.read_csv('records_mks.csv')
records.head()
| start | end | lokasi_start | latitude_start | longitude_start | lokasi_end | latitude_end | longitude_end | dist | lge/100km | add_cost | start_time | end_time | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | B | D | Nipah Mall | -5.138213 | 119.450386 | Mall Ratu Indah | -5.152877 | 119.417858 | 7.5 | 15.08 | 0.0 | 2023-05-03 15:20:00 | 2023-05-03 15:45:00 |
| 1 | A | B | Warehouse | -5.094272 | 119.491642 | Nipah Mall | -5.138213 | 119.450386 | 11.0 | 13.52 | 15.5 | 2023-05-03 14:19:00 | 2023-05-03 14:44:00 |
| 2 | F | G | Makassar Town Square | -5.139702 | 119.473848 | Phinisi Point | -5.151032 | 119.404404 | 12.3 | 16.03 | 0.0 | 2023-05-02 15:10:00 | 2023-05-02 15:48:00 |
| 3 | A | F | Warehouse | -5.094272 | 119.491642 | Makassar Town Square | -5.139702 | 119.473848 | 9.7 | 14.45 | 0.0 | 2023-05-02 14:13:00 | 2023-05-02 14:39:00 |
| 4 | E | C | Mall Panakkukang | -5.156903 | 119.446034 | TSM Makassar | -5.158039 | 119.394943 | 9.8 | 15.27 | 0.0 | 2023-05-01 15:02:00 | 2023-05-01 15:33:00 |
Deskripsi Data:
Data records merupakan kumpulan catatan pengiriman distribusi. Berikut informasi dari setiap kolom:
start: Lokasi berangkatend: Lokasi tujuanlokasi_start: Nama lokasi awallatitude_start: Titik latitude lokasi awallongitude_start: Titik longitude lokasi awallokasi_end: Nama lokasi tujuanlatitude_end: Titik latitude lokasi tujuanlongitude_end: Titik longitude lokasi tujuandist: Jarak tempuh (satuan km)lge/100km: Penggunaan liter bensin / 100 kmadd_cost: Tambahan biaya dalam satuan Ribu Rupiah (Biaya Tol)start_time: Waktu berangkatend_time: Waktu tibalokasi = pd.read_csv('lokasi_makassar.csv')
lokasi
| kode | lokasi | latitude | longitude | |
|---|---|---|---|---|
| 0 | A | Warehouse | -5.094272 | 119.491642 |
| 1 | B | Nipah Mall | -5.138213 | 119.450386 |
| 2 | C | TSM Makassar | -5.158039 | 119.394943 |
| 3 | D | Mall Ratu Indah | -5.152877 | 119.417858 |
| 4 | E | Mall Panakkukang | -5.156903 | 119.446034 |
| 5 | F | Makassar Town Square | -5.139702 | 119.473848 |
| 6 | G | Phinisi Point | -5.151032 | 119.404404 |
# Start dan end time menjadi data datetime
records[['start_time', 'end_time']] = records[['start_time', 'end_time']].astype('datetime64[ns]')
#Kolom berikut menjadi kolom kategory karena sifatnya yang berulang
kolom_cat = ['start', 'lokasi_start', 'end', 'lokasi_end']
records[kolom_cat] = records[kolom_cat].astype('category')
records.info()
<class 'pandas.core.frame.DataFrame'> Index: 22 entries, 0 to 21 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 start 22 non-null category 1 end 22 non-null category 2 lokasi_start 22 non-null category 3 latitude_start 22 non-null float64 4 longitude_start 22 non-null float64 5 lokasi_end 22 non-null category 6 latitude_end 22 non-null float64 7 longitude_end 22 non-null float64 8 dist 22 non-null float64 9 lge/100km 22 non-null float64 10 add_cost 22 non-null float64 11 start_time 22 non-null datetime64[ns] 12 end_time 22 non-null datetime64[ns] dtypes: category(4), datetime64[ns](2), float64(7) memory usage: 2.9 KB
# Mengambil komponen bulan,dan menyimpan ke kolom baru bernama month
records['month'] = records['start_time'].dt.month_name()
#Komponen Minggu
records['weekly'] = records['start_time'].dt.to_period('W')
#Komponen Hari
records['day'] = records['start_time'].dt.day_name()
# Week of year
records['weekofyear'] = records['start_time'].dt.isocalendar().week
records.head(10)
| start | end | lokasi_start | latitude_start | longitude_start | lokasi_end | latitude_end | longitude_end | dist | lge/100km | add_cost | start_time | end_time | month | weekly | day | weekofyear | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | B | D | Nipah Mall | -5.138213 | 119.450386 | Mall Ratu Indah | -5.152877 | 119.417858 | 7.5 | 15.08 | 0.0 | 2023-05-03 15:20:00 | 2023-05-03 15:45:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 |
| 1 | A | B | Warehouse | -5.094272 | 119.491642 | Nipah Mall | -5.138213 | 119.450386 | 11.0 | 13.52 | 15.5 | 2023-05-03 14:19:00 | 2023-05-03 14:44:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 |
| 2 | F | G | Makassar Town Square | -5.139702 | 119.473848 | Phinisi Point | -5.151032 | 119.404404 | 12.3 | 16.03 | 0.0 | 2023-05-02 15:10:00 | 2023-05-02 15:48:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 |
| 3 | A | F | Warehouse | -5.094272 | 119.491642 | Makassar Town Square | -5.139702 | 119.473848 | 9.7 | 14.45 | 0.0 | 2023-05-02 14:13:00 | 2023-05-02 14:39:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 |
| 4 | E | C | Mall Panakkukang | -5.156903 | 119.446034 | TSM Makassar | -5.158039 | 119.394943 | 9.8 | 15.27 | 0.0 | 2023-05-01 15:02:00 | 2023-05-01 15:33:00 | May | 2023-05-01/2023-05-07 | Monday | 18 |
| 5 | A | E | Warehouse | -5.094272 | 119.491642 | Mall Panakkukang | -5.156903 | 119.446034 | 14.2 | 12.72 | 15.5 | 2023-05-01 14:02:00 | 2023-05-01 14:28:00 | May | 2023-05-01/2023-05-07 | Monday | 18 |
| 6 | F | D | Makassar Town Square | -5.139702 | 119.473848 | Mall Ratu Indah | -5.152877 | 119.417858 | 9.6 | 15.78 | 0.0 | 2023-04-26 15:25:00 | 2023-04-26 15:57:00 | April | 2023-04-24/2023-04-30 | Wednesday | 17 |
| 7 | A | F | Warehouse | -5.094272 | 119.491642 | Makassar Town Square | -5.139702 | 119.473848 | 9.7 | 14.45 | 0.0 | 2023-04-26 14:15:00 | 2023-04-26 14:41:00 | April | 2023-04-24/2023-04-30 | Wednesday | 17 |
| 8 | C | B | TSM Makassar | -5.158039 | 119.394943 | Nipah Mall | -5.138213 | 119.450386 | 10.3 | 15.29 | 0.0 | 2023-04-25 15:20:00 | 2023-04-25 15:51:00 | April | 2023-04-24/2023-04-30 | Tuesday | 17 |
| 9 | A | C | Warehouse | -5.094272 | 119.491642 | TSM Makassar | -5.158039 | 119.394943 | 18.0 | 14.27 | 15.5 | 2023-04-25 14:01:00 | 2023-04-25 14:42:00 | April | 2023-04-24/2023-04-30 | Tuesday | 17 |
Informasi yang dapat digunakan:
dist, menyatakan jarak dalam satuan km.lge/100km, menyatakan penggunaan liter bensin / 100 km.💡 Langkah:
Hitung penggunaan liter bahan bakar dengan formula:
$ literneed = lge/100km \times \frac{dist}{100}$
Penggunaan liter akan dikalikan dengan biaya bensin/liter.
$ costfuel = literneed \times dexlitecost $
Additional Notes: Semakin besar nilai lge/100km maka semakin boros bensin untuk rute tersebut. Hal ini dapat dipengaruhi oleh tingkat kemacetan jalanan dimana penggunaan bahan bakar dapat meningkat 90-170% dari konsumsi normal bensin [[4](https://www.viamichelin.com/magazine/article/traffic-jams-our-tips-for-saving-fuel/#:~:text=An%20idling%20vehicle%20uses%20about,and%20175%25%20in%20urban%20areas)].
# Harga dexlite dalam satuan ribu
fuel = 14.95
# Mendapatkan total liter
records['literneed'] = records['lge/100km'] * (records['dist']/100)
records['cost_fuel'] = records['literneed'] * fuel
records.head()
| start | end | lokasi_start | latitude_start | longitude_start | lokasi_end | latitude_end | longitude_end | dist | lge/100km | add_cost | start_time | end_time | month | weekly | day | weekofyear | literneed | cost_fuel | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | B | D | Nipah Mall | -5.138213 | 119.450386 | Mall Ratu Indah | -5.152877 | 119.417858 | 7.5 | 15.08 | 0.0 | 2023-05-03 15:20:00 | 2023-05-03 15:45:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.13100 | 16.908450 |
| 1 | A | B | Warehouse | -5.094272 | 119.491642 | Nipah Mall | -5.138213 | 119.450386 | 11.0 | 13.52 | 15.5 | 2023-05-03 14:19:00 | 2023-05-03 14:44:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.48720 | 22.233640 |
| 2 | F | G | Makassar Town Square | -5.139702 | 119.473848 | Phinisi Point | -5.151032 | 119.404404 | 12.3 | 16.03 | 0.0 | 2023-05-02 15:10:00 | 2023-05-02 15:48:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 | 1.97169 | 29.476766 |
| 3 | A | F | Warehouse | -5.094272 | 119.491642 | Makassar Town Square | -5.139702 | 119.473848 | 9.7 | 14.45 | 0.0 | 2023-05-02 14:13:00 | 2023-05-02 14:39:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 | 1.40165 | 20.954667 |
| 4 | E | C | Mall Panakkukang | -5.156903 | 119.446034 | TSM Makassar | -5.158039 | 119.394943 | 9.8 | 15.27 | 0.0 | 2023-05-01 15:02:00 | 2023-05-01 15:33:00 | May | 2023-05-01/2023-05-07 | Monday | 18 | 1.49646 | 22.372077 |
Informasi yang dapat digunakan:
start_time dan end_time untuk mendapatkan waktu menit perjalanan💡 Langkah:
end_time - start_time.Kalikan total menit dengan biaya maintain pendingin.
$ costrefri = totalminutes \times \frac{refrigeratorcost}{60} $
Pada datetime dapat dilakukan subtraksi datetime seperti di bawah ini
# Menghitung waktu yang diperlukan
records['time'] = (records['end_time'] - records['start_time']).dt.total_seconds() / 60
# Biaya pendingin/jam dalam satuan ribu
refrigerator = 9
# total cost refri
records['cost_refri'] = records['time']/60 * refrigerator
records.head(2)
| start | end | lokasi_start | latitude_start | longitude_start | lokasi_end | latitude_end | longitude_end | dist | lge/100km | ... | start_time | end_time | month | weekly | day | weekofyear | literneed | cost_fuel | time | cost_refri | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | B | D | Nipah Mall | -5.138213 | 119.450386 | Mall Ratu Indah | -5.152877 | 119.417858 | 7.5 | 15.08 | ... | 2023-05-03 15:20:00 | 2023-05-03 15:45:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.1310 | 16.90845 | 25.0 | 3.75 |
| 1 | A | B | Warehouse | -5.094272 | 119.491642 | Nipah Mall | -5.138213 | 119.450386 | 11.0 | 13.52 | ... | 2023-05-03 14:19:00 | 2023-05-03 14:44:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.4872 | 22.23364 | 25.0 | 3.75 |
2 rows × 21 columns
Total cost akan kita jadikan sebagai weight dalam Graf kita nantinya. Sehingga kita akan mendapatkan kombinasi masing-masing rute beserta total biayanya.
Informasi yang digunakan:
cost_fuel: total biaya bahan bakarcost_refri: total biaya maintain pendinginadd_cost: additional_cost, dalam hal ini adalah biaya tol💡 Langkah:
Menjumlahkan masing-masing cost
$ total = costfuel + costrefri + addcost $
records['total'] = records['cost_fuel'] + records['cost_refri'] + records['add_cost']
records.head()
| start | end | lokasi_start | latitude_start | longitude_start | lokasi_end | latitude_end | longitude_end | dist | lge/100km | ... | end_time | month | weekly | day | weekofyear | literneed | cost_fuel | time | cost_refri | total | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | B | D | Nipah Mall | -5.138213 | 119.450386 | Mall Ratu Indah | -5.152877 | 119.417858 | 7.5 | 15.08 | ... | 2023-05-03 15:45:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.13100 | 16.908450 | 25.0 | 3.75 | 20.658450 |
| 1 | A | B | Warehouse | -5.094272 | 119.491642 | Nipah Mall | -5.138213 | 119.450386 | 11.0 | 13.52 | ... | 2023-05-03 14:44:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.48720 | 22.233640 | 25.0 | 3.75 | 41.483640 |
| 2 | F | G | Makassar Town Square | -5.139702 | 119.473848 | Phinisi Point | -5.151032 | 119.404404 | 12.3 | 16.03 | ... | 2023-05-02 15:48:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 | 1.97169 | 29.476766 | 38.0 | 5.70 | 35.176766 |
| 3 | A | F | Warehouse | -5.094272 | 119.491642 | Makassar Town Square | -5.139702 | 119.473848 | 9.7 | 14.45 | ... | 2023-05-02 14:39:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 | 1.40165 | 20.954667 | 26.0 | 3.90 | 24.854667 |
| 4 | E | C | Mall Panakkukang | -5.156903 | 119.446034 | TSM Makassar | -5.158039 | 119.394943 | 9.8 | 15.27 | ... | 2023-05-01 15:33:00 | May | 2023-05-01/2023-05-07 | Monday | 18 | 1.49646 | 22.372077 | 31.0 | 4.65 | 27.022077 |
5 rows × 22 columns
5 rute yang membutuhkan cost terbesar
# code here
records.sort_values(by = 'total', ascending = False).head(5)
| start | end | lokasi_start | latitude_start | longitude_start | lokasi_end | latitude_end | longitude_end | dist | lge/100km | ... | end_time | month | weekly | day | weekofyear | literneed | cost_fuel | time | cost_refri | total | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 9 | A | C | Warehouse | -5.094272 | 119.491642 | TSM Makassar | -5.158039 | 119.394943 | 18.0 | 14.27 | ... | 2023-04-25 14:42:00 | April | 2023-04-24/2023-04-30 | Tuesday | 17 | 2.56860 | 38.400570 | 41.0 | 6.15 | 60.050570 |
| 11 | A | G | Warehouse | -5.094272 | 119.491642 | Phinisi Point | -5.151032 | 119.404404 | 15.8 | 14.90 | ... | 2023-04-24 14:49:00 | April | 2023-04-24/2023-04-30 | Monday | 17 | 2.35420 | 35.195290 | 39.0 | 5.85 | 56.545290 |
| 15 | A | D | Warehouse | -5.094272 | 119.491642 | Mall Ratu Indah | -5.152877 | 119.417858 | 15.6 | 14.55 | ... | 2023-04-18 14:40:00 | April | 2023-04-17/2023-04-23 | Tuesday | 16 | 2.26980 | 33.933510 | 38.0 | 5.70 | 55.133510 |
| 5 | A | E | Warehouse | -5.094272 | 119.491642 | Mall Panakkukang | -5.156903 | 119.446034 | 14.2 | 12.72 | ... | 2023-05-01 14:28:00 | May | 2023-05-01/2023-05-07 | Monday | 18 | 1.80624 | 27.003288 | 26.0 | 3.90 | 46.403288 |
| 17 | A | E | Warehouse | -5.094272 | 119.491642 | Mall Panakkukang | -5.156903 | 119.446034 | 14.2 | 12.72 | ... | 2023-04-17 14:31:00 | April | 2023-04-17/2023-04-23 | Monday | 16 | 1.80624 | 27.003288 | 26.0 | 3.90 | 46.403288 |
5 rows × 22 columns
Memeriksa Frekuensi Tertinggi pengiriman barang berdasarkan minggu dalam setahun
records.weekofyear.value_counts()
weekofyear 18 6 17 6 16 6 15 2 14 2 Name: count, dtype: Int64
disini dapat kita lihat, dari minggu ke-14 sampai minggu ke-18, minggu ke-18 lah yang mengalami frekuensi pengiriman yang paling tinggi dengan 6 rute
records.day.value_counts()
day Wednesday 10 Tuesday 6 Monday 6 Name: count, dtype: int64
dapat kita lihat bahwa hari rabu lah yang mengalami frekuensi pengiriman yang paling tinggi
Mari kita lihat pengiriman pada minggu ke-18 karena mempunyai jadwal pengiriman yang paling tinggi (dengan asumsi pengiriman tinggi adalah pengiriman yang paling lengkap)
records[records['weekofyear'] == 18].head()
| start | end | lokasi_start | latitude_start | longitude_start | lokasi_end | latitude_end | longitude_end | dist | lge/100km | ... | end_time | month | weekly | day | weekofyear | literneed | cost_fuel | time | cost_refri | total | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | B | D | Nipah Mall | -5.138213 | 119.450386 | Mall Ratu Indah | -5.152877 | 119.417858 | 7.5 | 15.08 | ... | 2023-05-03 15:45:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.13100 | 16.908450 | 25.0 | 3.75 | 20.658450 |
| 1 | A | B | Warehouse | -5.094272 | 119.491642 | Nipah Mall | -5.138213 | 119.450386 | 11.0 | 13.52 | ... | 2023-05-03 14:44:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.48720 | 22.233640 | 25.0 | 3.75 | 41.483640 |
| 2 | F | G | Makassar Town Square | -5.139702 | 119.473848 | Phinisi Point | -5.151032 | 119.404404 | 12.3 | 16.03 | ... | 2023-05-02 15:48:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 | 1.97169 | 29.476766 | 38.0 | 5.70 | 35.176766 |
| 3 | A | F | Warehouse | -5.094272 | 119.491642 | Makassar Town Square | -5.139702 | 119.473848 | 9.7 | 14.45 | ... | 2023-05-02 14:39:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 | 1.40165 | 20.954667 | 26.0 | 3.90 | 24.854667 |
| 4 | E | C | Mall Panakkukang | -5.156903 | 119.446034 | TSM Makassar | -5.158039 | 119.394943 | 9.8 | 15.27 | ... | 2023-05-01 15:33:00 | May | 2023-05-01/2023-05-07 | Monday | 18 | 1.49646 | 22.372077 | 31.0 | 4.65 | 27.022077 |
5 rows × 22 columns
week18 = records[records['weekofyear'] == 18].sort_values(by = 'start_time')
week18
| start | end | lokasi_start | latitude_start | longitude_start | lokasi_end | latitude_end | longitude_end | dist | lge/100km | ... | end_time | month | weekly | day | weekofyear | literneed | cost_fuel | time | cost_refri | total | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 5 | A | E | Warehouse | -5.094272 | 119.491642 | Mall Panakkukang | -5.156903 | 119.446034 | 14.2 | 12.72 | ... | 2023-05-01 14:28:00 | May | 2023-05-01/2023-05-07 | Monday | 18 | 1.80624 | 27.003288 | 26.0 | 3.90 | 46.403288 |
| 4 | E | C | Mall Panakkukang | -5.156903 | 119.446034 | TSM Makassar | -5.158039 | 119.394943 | 9.8 | 15.27 | ... | 2023-05-01 15:33:00 | May | 2023-05-01/2023-05-07 | Monday | 18 | 1.49646 | 22.372077 | 31.0 | 4.65 | 27.022077 |
| 3 | A | F | Warehouse | -5.094272 | 119.491642 | Makassar Town Square | -5.139702 | 119.473848 | 9.7 | 14.45 | ... | 2023-05-02 14:39:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 | 1.40165 | 20.954667 | 26.0 | 3.90 | 24.854667 |
| 2 | F | G | Makassar Town Square | -5.139702 | 119.473848 | Phinisi Point | -5.151032 | 119.404404 | 12.3 | 16.03 | ... | 2023-05-02 15:48:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 | 1.97169 | 29.476766 | 38.0 | 5.70 | 35.176766 |
| 1 | A | B | Warehouse | -5.094272 | 119.491642 | Nipah Mall | -5.138213 | 119.450386 | 11.0 | 13.52 | ... | 2023-05-03 14:44:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.48720 | 22.233640 | 25.0 | 3.75 | 41.483640 |
| 0 | B | D | Nipah Mall | -5.138213 | 119.450386 | Mall Ratu Indah | -5.152877 | 119.417858 | 7.5 | 15.08 | ... | 2023-05-03 15:45:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.13100 | 16.908450 | 25.0 | 3.75 | 20.658450 |
6 rows × 22 columns
# import graph
from helper import graph
# # Membuat objek peta
map_object = graph.create_map_object('Makassar, Indonesia')
# # Visualisasi koordinat utk week 18
graph.visualize_route(map_object, week18)
Berikut adalah cara untuk mendapatkan rute dengan weight yang paling optimal (minimum weight)
Total Cost -> Total biaya per-rute.
Total Biaya = Biaya bensin + Biaya pendingin + Addition Cost (Tol)
Melihat apakah terdapat duplikat rute. Untuk melakukan pengecekan terhadap ada atau tidaknya data yang duplikat, kita dapat menggunakan method duplicated().
Syntax:
records.duplicated(subset = [kolom], keep = 'first')
Ket:
keep='first', melihat baris pertama (teratas) dari nilai yang duplicate.keep='last', melihat baris terakhir (terbawah) dari nilai yang duplicate.keep=False, melihat semua baris yang dulicate.# Melihat semua baris yang terduplikat
records[records.duplicated(subset = ['start','end'], keep = False)]
# Drop duplikat untuk data lama (pertahankan data terbaru) dengan menggunakan 'keep = "first"''
records_new = records.drop_duplicates(subset = ['start', 'end'], keep = 'first')
# cek duplikat
records_new[records_new.duplicated(subset = ['start', 'end'], keep = False)]
| start | end | lokasi_start | latitude_start | longitude_start | lokasi_end | latitude_end | longitude_end | dist | lge/100km | ... | end_time | month | weekly | day | weekofyear | literneed | cost_fuel | time | cost_refri | total |
|---|
0 rows × 22 columns
records_new.head()
| start | end | lokasi_start | latitude_start | longitude_start | lokasi_end | latitude_end | longitude_end | dist | lge/100km | ... | end_time | month | weekly | day | weekofyear | literneed | cost_fuel | time | cost_refri | total | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | B | D | Nipah Mall | -5.138213 | 119.450386 | Mall Ratu Indah | -5.152877 | 119.417858 | 7.5 | 15.08 | ... | 2023-05-03 15:45:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.13100 | 16.908450 | 25.0 | 3.75 | 20.658450 |
| 1 | A | B | Warehouse | -5.094272 | 119.491642 | Nipah Mall | -5.138213 | 119.450386 | 11.0 | 13.52 | ... | 2023-05-03 14:44:00 | May | 2023-05-01/2023-05-07 | Wednesday | 18 | 1.48720 | 22.233640 | 25.0 | 3.75 | 41.483640 |
| 2 | F | G | Makassar Town Square | -5.139702 | 119.473848 | Phinisi Point | -5.151032 | 119.404404 | 12.3 | 16.03 | ... | 2023-05-02 15:48:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 | 1.97169 | 29.476766 | 38.0 | 5.70 | 35.176766 |
| 3 | A | F | Warehouse | -5.094272 | 119.491642 | Makassar Town Square | -5.139702 | 119.473848 | 9.7 | 14.45 | ... | 2023-05-02 14:39:00 | May | 2023-05-01/2023-05-07 | Tuesday | 18 | 1.40165 | 20.954667 | 26.0 | 3.90 | 24.854667 |
| 4 | E | C | Mall Panakkukang | -5.156903 | 119.446034 | TSM Makassar | -5.158039 | 119.394943 | 9.8 | 15.27 | ... | 2023-05-01 15:33:00 | May | 2023-05-01/2023-05-07 | Monday | 18 | 1.49646 | 22.372077 | 31.0 | 4.65 | 27.022077 |
5 rows × 22 columns
Selanjutnya mari kita melakukan pembentukan graf berdasarkan data kita. Sebelum itu mari kita mengambil kolom2 yang dibutuhkan untuk membuat graf
import networkx as nx
data = records_new[['start', 'end', 'total']]
data['total'] = round(data['total'], 2)
data.head()
C:\Users\USER\AppData\Local\Temp\ipykernel_14636\2070334715.py:3: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy data['total'] = round(data['total'], 2)
| start | end | total | |
|---|---|---|---|
| 0 | B | D | 20.66 |
| 1 | A | B | 41.48 |
| 2 | F | G | 35.18 |
| 3 | A | F | 24.85 |
| 4 | E | C | 27.02 |
# Membuat objek graph
Graf = nx.Graph()
# Iterasi per baris dari dataframe
for ind in range(len(data)):
# Mengakses nilai setiap baris menjadi list
row = data.iloc[ind].tolist()
# Memasukkan edges tiap node ke dalam graf
Graf.add_edge(row[0], row[1], weight = row[2])
# Visualisasi graf
graph.visualize_graph(Graf)
Kita dapat menggunakan fungsi graph.get_total() dimana akan membuat dataframe dengan weight terurut dari masing-masing rute.
Sintaks
graph.get_total(G, source, target, cutoff)
keterangan:
Cutoff diset angka 2 karena kapasitas truk hanya dapat menampung 2.2 ton dimana kebutuhan tonase tiap alamat pengiriman adalah 1 ton.
Dilakukan beberapa kali pemeriksaan untuk menemukan variasi rute yang paling rendah totalnya.
graph.get_total(Graf, source = 'A', target = 'B', cutoff = 2)
| route | total | |
|---|---|---|
| 1 | [A, F, B] | 35.53 |
| 0 | [A, B] | 41.48 |
| 2 | [A, E, B] | 57.01 |
| 5 | [A, D, B] | 75.79 |
| 4 | [A, G, B] | 79.28 |
| 3 | [A, C, B] | 88.24 |
graph.get_total(Graf, source = 'A', target = 'C', cutoff = 2)
| route | total | |
|---|---|---|
| 2 | [A, C] | 60.05 |
| 3 | [A, D, C] | 67.06 |
| 0 | [A, B, C] | 69.67 |
| 1 | [A, E, C] | 73.42 |
graph.get_total(Graf, source = 'A', target = 'D', cutoff = 2)
| route | total | |
|---|---|---|
| 1 | [A, F, D] | 52.30 |
| 3 | [A, D] | 55.13 |
| 0 | [A, B, D] | 62.14 |
| 2 | [A, C, D] | 71.98 |
graph.get_total(Graf, source = 'A', target = 'E', cutoff = 2)
| route | total | |
|---|---|---|
| 1 | [A, F, E] | 45.34 |
| 2 | [A, E] | 46.40 |
| 0 | [A, B, E] | 52.09 |
| 4 | [A, G, E] | 74.78 |
| 3 | [A, C, E] | 87.07 |
graph.get_total(Graf, source = 'A', target = 'F', cutoff = 2)
| route | total | |
|---|---|---|
| 1 | [A, F] | 24.85 |
| 0 | [A, B, F] | 52.16 |
| 2 | [A, E, F] | 66.89 |
| 4 | [A, D, F] | 82.58 |
| 3 | [A, G, F] | 91.73 |
graph.get_total(Graf, source = 'A', target = 'G', cutoff = 2)
| route | total | |
|---|---|---|
| 3 | [A, G] | 56.55 |
| 1 | [A, F, G] | 60.03 |
| 0 | [A, B, G] | 64.21 |
| 2 | [A, E, G] | 64.63 |
Rekomendasi Rute Baru
Vehicle Routing Problem
Dari hasil yang didapatkan, terdapat 3 rekomendasi rute optimal yang telah mencakup keseluruhan lokasi, antara lain:
new_route = pd.DataFrame({'rute': ["[A F B]", "[A D C]", "[A E G]"],
'total': [35.53, 67.06, 64.63]})
new_route
| rute | total | |
|---|---|---|
| 0 | [A F B] | 35.53 |
| 1 | [A D C] | 67.06 |
| 2 | [A E G] | 64.63 |
Selanjutnya mari kita bandingkan total biaya rekomendasi rute baru dengan rute-rute lama.
Kita dapat melihat total biaya untuk setiap pekannya dengan melakukan agregasi tabel menggunakan .pivot_table().
Syntax:
{python}
data.pivot_table(
index=...,
columns=...,
values=...,
aggfunc=...
)
Kita dapat menggunakan pivot_table dengan beberapa parameter sebagai berikut.
data: dataframe yang kita gunakanindex: kolom yang akan menjadi index rowcolumns: kolom yang akan menjadi index kolomvalues: nilai yang digunakan untuk mengisi tabelaggfunc: fungsi agregasi# Membandingkan harga
aggcost = records.pivot_table(index = 'weekofyear',
values = 'total',
aggfunc= 'sum')
aggcost
| total | |
|---|---|
| weekofyear | |
| 14 | 57.014828 |
| 15 | 35.531884 |
| 16 | 199.368149 |
| 17 | 215.321762 |
| 18 | 195.598888 |
aggcost.plot(kind = 'bar');
Berhubung rute yang lengkap hanya terjadi pada 3 pekan terakhir
(pekan ke-16, 17 dan 18; diperiksa menggunakan perintah records.weekofyear.value_counts()) .
Maka dari itu kita hanya membandingkan rute rekomendasi dengan rute 3 pekan terakhir.
Biaya total untuk rute 3 pekan terakhir adalah Rp199.368, Rp215.321 dan Rp195.598 (Rerata Rp203.429). Mari kita bandingkan dengan total rute optimal yang kita dapatkan.
new_cost = new_route['total'].sum()
print(f"Total cost rute baru adalah Rp{new_cost},000")
Total cost rute baru adalah Rp167.22,000
Biaya rute baru ini dapat lebih hemat Rp 36.209 (Rerata Rp 203.429 - Rp 167.220)
Dalam pembuatan visualisasi peta, terdapat 2 step yang harus dilakukan.
graph.create_map_object() yang disimpan ke dalam map_object. Inputan fungsi ini adalah string nama daerah koordinat yang ingin kita visualisasikan, dalam hal ini adalah 'Jakarta, Indonesia'. graph.visualize_route(map_object, dataframe). Dimana map_object adalah object dari step 1 dan dataframe adalah kumpulan data koordinat yang ingin kita visualisasikan, dalam hal ini adalah week6.
Kolom wajib yang harus terdapat di dalam dataframe:daylokasi_startlongitude_startlatitude_startlokasi_endlongitude_endlatitude_endMari kita membuat dataframe yang berisi rute baru dan memiliki kolom-kolom yang diperlakukan.
(Pengisian hari diasumsikan pengiriman pada hari yang sama, pada contoh AFB dilakukan pada hari Senin, ADC pada hari selasa dan AEG pada hari rabu)
data = [['A', 'F', 'Monday'],
['F', 'B', 'Monday'],
['A', 'D', 'Tuesday'],
['D', 'C', 'Tuesday'],
['A', 'E', 'Wednesday'],
['E', 'G', 'Wednesday']]
optimize = pd.DataFrame(data, columns = ['start', 'end', 'day'])
optimize
| start | end | day | |
|---|---|---|---|
| 0 | A | F | Monday |
| 1 | F | B | Monday |
| 2 | A | D | Tuesday |
| 3 | D | C | Tuesday |
| 4 | A | E | Wednesday |
| 5 | E | G | Wednesday |
lokasi
| kode | lokasi | latitude | longitude | |
|---|---|---|---|---|
| 0 | A | Warehouse | -5.094272 | 119.491642 |
| 1 | B | Nipah Mall | -5.138213 | 119.450386 |
| 2 | C | TSM Makassar | -5.158039 | 119.394943 |
| 3 | D | Mall Ratu Indah | -5.152877 | 119.417858 |
| 4 | E | Mall Panakkukang | -5.156903 | 119.446034 |
| 5 | F | Makassar Town Square | -5.139702 | 119.473848 |
| 6 | G | Phinisi Point | -5.151032 | 119.404404 |
Dataframe optimize masih belum memiliki nama lokasi_start, lokasi_end serta koordinat titik start (long_start, lat_start) dan koordinat titik end (long_end, lat_end).
Mari kita mengambil informasi nama lokasi dan koordinat lokasi dari dataframe location dengan melakukan pd.merge().
Syntaks:
pd.merge(df_kiri, df_kanan, left_on='key', right_on='key', how='left|right|inner', suffixes=('_x', '_y'))
Keterangan:
df_kiri = dataframe yang ingin digabungkandf_kanan = dataframe yang ingin digabungkanleft_on = Kolom sebagai key untuk digabungkan pada dataframe kiri.right_on = Kolom sebagai key untuk digabungkan pada datagrame kanan.how = Cara melakukan merger, left|right|inner sama seperti join pada SQL. Lebih lengkap mengenai SQL Joinsuffixes = Penambahan nama kolom untuk nama kolom yang sama antara dataframe kiri dan kanan.# Merge detail untuk start
optimize_merge = pd.merge(optimize, lokasi, left_on='start', right_on='kode', how='left')
# Merge detail untuk end
optimize_merge = pd.merge(optimize_merge, lokasi, left_on='end', right_on='kode', how='left', suffixes=('_start', '_end'))
optimize_merge
| start | end | day | kode_start | lokasi_start | latitude_start | longitude_start | kode_end | lokasi_end | latitude_end | longitude_end | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | A | F | Monday | A | Warehouse | -5.094272 | 119.491642 | F | Makassar Town Square | -5.139702 | 119.473848 |
| 1 | F | B | Monday | F | Makassar Town Square | -5.139702 | 119.473848 | B | Nipah Mall | -5.138213 | 119.450386 |
| 2 | A | D | Tuesday | A | Warehouse | -5.094272 | 119.491642 | D | Mall Ratu Indah | -5.152877 | 119.417858 |
| 3 | D | C | Tuesday | D | Mall Ratu Indah | -5.152877 | 119.417858 | C | TSM Makassar | -5.158039 | 119.394943 |
| 4 | A | E | Wednesday | A | Warehouse | -5.094272 | 119.491642 | E | Mall Panakkukang | -5.156903 | 119.446034 |
| 5 | E | G | Wednesday | E | Mall Panakkukang | -5.156903 | 119.446034 | G | Phinisi Point | -5.151032 | 119.404404 |
# Visualisasi rute baru
graph.visualize_route(map_object, optimize_merge)
Perbandingan dengan rute sebelumnya
graph.visualize_route(map_object, week18)
Vehicle Routing Problem
Travelling Salesman Problem
Pada kasus sebelumnya, kita mencari rute optimal dengan maksimal mendatangi 2 lokasi dalam 1 perjalanan. Bagaimana jika kita ingin mendapatkan rute terbaik dengan mendatangi setiap lokasi dalam satu perjalanan? Urutan jalur manakah yang paling optimal? Dan bagaimana cara mencarinya?
Permasalahan ini disebut sebagai Travelling Salesman Problem (TSP) dimana seorang salesman memiliki daftar kota yang harus dikunjungi untuk menjual produk, namun mencari cara paling efisien untuk mengunjungi setiap kota tepat sekali dan kemudian dapat kembali ke titik awal. Cara paling efisien dalam kasus kita tentu saja berarti total cost yang paling rendah untuk mendatangi seluruh lokasi dalam 1 perjalanan.

Networkx telah menyediakan method untuk menyelesaikan permasalahan TSP ini, yaitu menggunakan nx.approximation.travelling_salesman_problem().
Syntax:
nx.approximation.travelling_salesman_problem(G, cycle = True, nodes = G.nodes)
Keterangan:
cycle atau node start di akhir list. Detail lebih lengkap dapat merujuk di dokumentasi
# Mendapatkan path optimal menggunakan tsp
path_tsp = nx.approximation.traveling_salesman_problem(Graf, cycle = False)
path_tsp
['G', 'E', 'B', 'F', 'A', 'F', 'D', 'C']
Kita telah mendapatkan jalur optimal untuk mengunjungi setiap lokasi dalam 1 kali perjalanan. Jalur optimal ini dihitung berdasarkan weight pada kasus ini adalah total_cost.
Selanjutnya mari kita melihat total weight jalur ini menggunakan nx.path_weight().
nx.path_weight(Graf, path_tsp, weight="weight")
128.6
Pada perhitungan ini, didapatkan bahwa jalur optimal untuk mengunjungi setiap lokasi dalam 1 kali perjalanan adalah 128.6rb rupiah.
Selanjutnya mari kita lakukan visualisasi rute hasil dari travelling salesman problem. Ingat kembali, untuk membuat visualisasi peta, terdapat 2 step yang harus dilakukan.
graph.create_map_object() yang disimpan ke dalam map_object. Inputan fungsi ini adalah string nama daerah koordinat yang ingin kita visualisasikan, dalam hal ini adalah 'Jakarta, Indonesia'. graph.visualize_route(map_object, dataframe). Dimana map_object adalah object dari step 1 dan dataframe adalah kumpulan data koordinat yang ingin kita visualisasikan, dalam hal ini adalah week6.
Kolom wajib yang harus terdapat di dalam dataframe:daylokasi_startlongitude_startlatitude_startlokasi_endlongitude_endlatitude_endMaka dari itu mari kita membuat dataframe awal seperti pada "Visualisasi Rute Baru" dimana terdapat kolom start, end, dan day. Pada kasus ini karena kita mengunjungi seluruh lokasi dalam 1 kali perjalanan, maka semua kita samakan saja harinya menjadi Monday.
data2 = [['G', 'E', 'Monday'],
['E', 'B', 'Monday'],
['B', 'F', 'Monday'],
['F', 'A', 'Monday'],
['A', 'F', 'Monday'],
['F', 'D', 'Monday'],
['D', 'C', 'Monday']]
optimize_tsp = pd.DataFrame(data2, columns = ['start', 'end', 'day'])
optimize_tsp
| start | end | day | |
|---|---|---|---|
| 0 | G | E | Monday |
| 1 | E | B | Monday |
| 2 | B | F | Monday |
| 3 | F | A | Monday |
| 4 | A | F | Monday |
| 5 | F | D | Monday |
| 6 | D | C | Monday |
lokasi
| kode | lokasi | latitude | longitude | |
|---|---|---|---|---|
| 0 | A | Warehouse | -5.094272 | 119.491642 |
| 1 | B | Nipah Mall | -5.138213 | 119.450386 |
| 2 | C | TSM Makassar | -5.158039 | 119.394943 |
| 3 | D | Mall Ratu Indah | -5.152877 | 119.417858 |
| 4 | E | Mall Panakkukang | -5.156903 | 119.446034 |
| 5 | F | Makassar Town Square | -5.139702 | 119.473848 |
| 6 | G | Phinisi Point | -5.151032 | 119.404404 |
Dataframe optimize_tsp masih belum memiliki nama lokasi_start, lokasi_end serta koordinat titik start (long_start, lat_start) dan koordinat titik end (long_end, lat_end).
Mari kita mengambil informasi nama lokasi dan koordinat lokasi dari dataframe lokasi dengan melakukan pd.merge().
tsp_merge = pd.merge(optimize_tsp, lokasi, left_on='start', right_on='kode', how='left')
tsp_merge = pd.merge(tsp_merge, lokasi, left_on='end', right_on='kode', how='left', suffixes=('_start', '_end'))
tsp_merge
| start | end | day | kode_start | lokasi_start | latitude_start | longitude_start | kode_end | lokasi_end | latitude_end | longitude_end | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | G | E | Monday | G | Phinisi Point | -5.151032 | 119.404404 | E | Mall Panakkukang | -5.156903 | 119.446034 |
| 1 | E | B | Monday | E | Mall Panakkukang | -5.156903 | 119.446034 | B | Nipah Mall | -5.138213 | 119.450386 |
| 2 | B | F | Monday | B | Nipah Mall | -5.138213 | 119.450386 | F | Makassar Town Square | -5.139702 | 119.473848 |
| 3 | F | A | Monday | F | Makassar Town Square | -5.139702 | 119.473848 | A | Warehouse | -5.094272 | 119.491642 |
| 4 | A | F | Monday | A | Warehouse | -5.094272 | 119.491642 | F | Makassar Town Square | -5.139702 | 119.473848 |
| 5 | F | D | Monday | F | Makassar Town Square | -5.139702 | 119.473848 | D | Mall Ratu Indah | -5.152877 | 119.417858 |
| 6 | D | C | Monday | D | Mall Ratu Indah | -5.152877 | 119.417858 | C | TSM Makassar | -5.158039 | 119.394943 |
Dataframe tsp_merge telah siap untuk dilakukan visualisasi, langsung kita lakukan visualisasi menggunakan graph.visualize_route().
graph.visualize_route(map_object, tsp_merge)
Dilakukan untuk memastikan titik start dan titik akhir berada di A (Warehouse)
tsp = nx.approximation.traveling_salesman_problem
G = nx.cycle_graph(Graf)
tsp(G, nodes = ['A','B','C','D', 'E','F','G'])
['A', 'F', 'G', 'E', 'C', 'B', 'D', 'A']
nx.path_weight(Graf, G, weight="weight")
181.07000000000002
data3 = [['A', 'F', 'Monday'],
['F', 'G', 'Monday'],
['G', 'E', 'Monday'],
['E', 'C', 'Monday'],
['C', 'B', 'Monday'],
['B', 'D', 'Monday'],
['D', 'A', 'Monday']]
optimize_tsp3 = pd.DataFrame(data3, columns = ['start', 'end', 'day'])
optimize_tsp3
| start | end | day | |
|---|---|---|---|
| 0 | A | F | Monday |
| 1 | F | G | Monday |
| 2 | G | E | Monday |
| 3 | E | C | Monday |
| 4 | C | B | Monday |
| 5 | B | D | Monday |
| 6 | D | A | Monday |
tsp_merge3 = pd.merge(optimize_tsp3, lokasi, left_on='start', right_on='kode', how='left')
tsp_merge3 = pd.merge(tsp_merge3, lokasi, left_on='end', right_on='kode', how='left', suffixes=('_start', '_end'))
tsp_merge3
| start | end | day | kode_start | lokasi_start | latitude_start | longitude_start | kode_end | lokasi_end | latitude_end | longitude_end | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | A | F | Monday | A | Warehouse | -5.094272 | 119.491642 | F | Makassar Town Square | -5.139702 | 119.473848 |
| 1 | F | G | Monday | F | Makassar Town Square | -5.139702 | 119.473848 | G | Phinisi Point | -5.151032 | 119.404404 |
| 2 | G | E | Monday | G | Phinisi Point | -5.151032 | 119.404404 | E | Mall Panakkukang | -5.156903 | 119.446034 |
| 3 | E | C | Monday | E | Mall Panakkukang | -5.156903 | 119.446034 | C | TSM Makassar | -5.158039 | 119.394943 |
| 4 | C | B | Monday | C | TSM Makassar | -5.158039 | 119.394943 | B | Nipah Mall | -5.138213 | 119.450386 |
| 5 | B | D | Monday | B | Nipah Mall | -5.138213 | 119.450386 | D | Mall Ratu Indah | -5.152877 | 119.417858 |
| 6 | D | A | Monday | D | Mall Ratu Indah | -5.152877 | 119.417858 | A | Warehouse | -5.094272 | 119.491642 |
graph.visualize_route(map_object, tsp_merge3)
Seiring dengan perkembangan komputasi, perusahaan dapat terbantu dengan adanya algoritma yang dapat membantu mengoptimalkan pendistribusian barang. Hal ini dapat dikembangkan secara luas berdasarkan banyaknya data yang tersedia.
References
[1] Wilson, R. J. (2010). Introduction to Graph Theory. New York: Prentice Hall/Pearson. ISBN: 027372889X 9780273728894
[2] Liang, Di & Wu, Shuang & Sun, Guizhi. (2015). Study on Optimization of Cold Chain Logistics Distribution Based on Improved Particle Swarm Optimization Algorithm. 10.2991/asei-15.2015.145.
[3] Saci, Samir. (2022). Transportation Network Analysis with Graph Theory. towardsdatascience.com
[4] Michelin. (2022). Traffic jams: our tips for saving fuel. viamichelin.com
[5] Taherdoost, Hamed, and Mitra Madanchian. 2023. "Multi-Criteria Decision Making (MCDM) Methods and Concepts" Encyclopedia 3, no. 1: 77-87. https://doi.org/10.3390/encyclopedia3010006