天池最后一公里


title: "R Notebook"
author: "Harry Zhu"

output: html_notebook

天池最后一公里

library(readr)
library(plyr)
library(dplyr)
Site = readr::read_csv("1.csv") %>% mutate(type = "Site") %>% rename(c("Site_id"='ID'))

Spot = readr::read_csv("2.csv") %>% mutate(type = "Spot") %>% rename(c("Spot_id" = "ID"))

Shop = readr::read_csv("3.csv") %>% mutate(type = "Shop") %>% rename(c("Shop_id" = "ID"))

library(tidyr)
Geo = rbind(Site,Shop,Spot)

E_Order = readr::read_csv("4.csv")
O2O_Order = readr::read_csv("5.csv")
Courier = readr::read_csv("6.csv")

#plot(Site$Lng,Site$Lat)
plot(Geo$Lng,Geo$Lat)
library(ggplot2)
ggplot(data=Geo, aes(x=Lng, y=Lat,color=type))+geom_point(0.1)

天池最后一公里

# O2O订单时间统计与分布
O2O_ts = O2O_Order %>% tidyr::extract(col = Pickup_time,"Pickup_hour") %>% tidyr::extract(col = Delivery_time,"Delivery_hour")

#  这里可以看到11-13点和17-18点是一个O2O下单高峰时段

plot(table(O2O_ts$Pickup_hour))

天池最后一公里

# 而配送时间则稍有滞后,在15-点到18点为低谷期
plot(table(O2O_ts$Delivery_hour))

天池最后一公里

total_Order = rbind(E_Order,O2O_Order)

total_Spot_rank = total_Order %>% group_by(Spot_id) %>% summarise(Order_num = sum(Num)) %>% arrange(Order_num)
# 观察订单合并分布情况 # 配送点的总订单情况
plot(table(total_Spot_rank$Order_num))
# 我们观察到,绝大多数配送点(9202)都是符合合单配送的,只有12单是不满足合单配送,需要单独配送。

天池最后一公里

# 在早上8点-10点集中配送电商订单,快递员在11-13点集中配送O2O订单,在14-17点混合配送电商和O2O订单,17-19点集中配送O2O订单,19点-20点混合配送电商和O2O订单
时间段 包裹类型 持续时长 解决顺序
8-11点 电商 2小时 5
11-13点 O2O 2小时 1
13-17点 混合 4小时 4
17-19点 O2O 2小时 2
19-20点 混合 1小时 3

在实际商业场景中,O2O配送是赚钱的,而且客单价不低,不管是自营还是加盟,上海O2O一次配送可以有6-10元左右的提成。而电商配送需要区分是自营还是加盟,如果是加盟配送在送件上是不赚钱的,在取件上才赚钱。所以,电商快递员们其实每天都是非常辛苦的,上午需要集中取件配送,下午一般是集中收件,到是O2O配送在非高峰时段可以解放出配送的生产力。

O2O实际的配送情况是,对于加盟配送员(默认3公里范围内)在找到取餐地点可能就会花费10分钟,在中间行驶还会花费10分钟,最后10分钟还要花在找到送餐地点。所以,O2O配送对于配送员对地理情况的了解有比较高的要求,熟悉地理情况将大大缩短配送时长。

O2O在用餐高峰时期的时间约束是非常紧急的,对于送餐员是分秒必争,这一点和电商配送是有显著差别的,所以我们在算法设计上可以从这个边界条件入手(OR的基本原理:最优解总是发生在边界条件上)。

O2O_Pickup_time = ldply(strsplit(O2O_Order$Pickup_time, ":"), rbind) %>% plyr::rename(c("1"='P_hour',"2"="P_minute"))

O2O_Delivery_time = ldply(strsplit(O2O_Order$Delivery_time, ":"), rbind) %>% plyr::rename(c("1"='D_hour',"2"="D_minute"))

O2O_Order_ts <- O2O_Order %>% cbind(O2O_Pickup_time,O2O_Delivery_time) %>% select(-Pickup_time,-Delivery_time) %>% tbl_df()

# 首先筛选出高峰订单
rush_Order_1 <- O2O_Order_ts %>% dplyr::filter(P_hour == c(11,12))
library(DT)
DT::datatable(rush_Order_1 %>% arrange(Spot_id,Source_id))

天池最后一公里

# 我们观察到有一些配送点会从多家店铺分时下单(脑补一个公司大家各自订餐的场景)
# 有的场景是一个店家向多个配送点配送。
# 这样就有了一取多送和多取一送的两个基本问题。
# 根据题目写了一个距离计算公式
distLastMile <- function(lat1,lat2,lng1,lng2){
  # parameter type check
  if(class(lat1) != "numeric" ||class(lat2) != "numeric" ||class(lng1) != "numeric" ||class(lng2) != "numeric" ){
    stop("parameter must be numeric")
  }
  # reference https://img.alicdn.com/tps/TB1CiDVKXXXXXcpXFXXXXXXXXXX-866-255.png
  diff_lat = (lat1 - lat2)/2
  diff_lng = (lng1 - lng2)/2
  radius = 6378137
  temp = (sin((pi * diff_lat )/180))^2 + cos((pi * lat1)/180) * cos((pi * lat2)/180)*(sin((pi * diff_lng )/180))^2
  result = 2 * radius * asin (sqrt(temp))
  return(result)
}
# 以题中给定15km/h的移动速度,估算

# > Geo[Geo$ID=="S062",]
# # A tibble: 1 x 4
#      ID     Lng      Lat  type
#   <chr>   <dbl>    <dbl> <chr>
# 1  S062 121.217 31.04957  Shop
# > Geo[Geo$ID=="B1958",]
# # A tibble: 1 x 4
#      ID     Lng      Lat  type
#   <chr>   <dbl>    <dbl> <chr>
# 1 B1958 121.217 31.04973  Spot

# distance = distLastMile(lat1=31.04973,lat2=31.04957,lng1=121.217,lng2=121.217)
# [1] 17.81112 
#  distance * 4
# [1] 71.24448

这里计算出来的结果是至少71分钟左右的配送时长,17km左右的配送距离。由于不太确定O2O的具体业务,所以说不敢对这个配送时长的合理性做判断。

rush_info_1[rush_info_1$Order_id == "E0036",]
# A tibble: 1 x 11
#  Order_id Spot_id Source_id   Num P_hour P_minute D_hour D_minute     Lng      Lat  type
#     <chr>   <chr>     <chr> <int> <fctr>   <fctr> <fctr>   <fctr>   <dbl>    <dbl> <chr>
#1    E0036   B1958      S062     3     12       14     13       44 121.217 31.04973  Spot

# 配送时长最长可为 90分钟,看来就是怎么利用之间的 20分钟的时间来增加配送量。

接下来,我们继续分解任务,将O2O配送分为三种类型

  1. 一对多:同一家店铺取,顺序配送到多个目的地(旺铺型)

  2. 多对一:多家店铺顺序取件,一次配送到目的地(大客户型)

  3. 其他

# 一对多的情形
# reference https://www.youtube.com/watch?v=A1wsIFDKqBk