TZMCM 2021 比赛代码

数学建模挑战赛,今年跨校组队玩了一下

比赛时间

第一阶段: 北京时间 2021 年 4 月 8 日下午 20:00 时 —— 4 月 11 日下午 20:00 时

第二阶段: 北京时间 2021 年 5 月 13 日下午 20:00 时 —— 5 月 16 日下午 20:00 时

成绩

第一阶段

分数: 50

排名: 1608 / 2878

简短评语: 模型分析过程不错

第二阶段

没参加

Preface

第一次参加这个挑战赛时还是高二(见这篇博文),那时候写的代码挺垃圾的。不过当时一、二阶段的赛题算是勉强完成了。

2020 届挑战赛没去,因为要大家都要高考。

今年年初,以前组队的朋友问我要不要再来玩玩,我想着可以提高数据分析能力,于是就尝试了一下。

今年的这次挑战赛,第一阶段的分数没有第一次打的高,第二阶段的题目也没有思绪。跨校组队嘛,会有种种不便。不过模型分析的思路挺清晰,代码写得也很规整,学习到了很多知识,尤其是坐标型数据分析。

主要问题还是老样子,就是数学理论知识太少。

于是就干脆当数据分析玩玩算了,然后我就狂搓代码,能想到的特征图全做出来。

源码有三百行,贴出来也不太好,我讲些重点的代码吧。

数据处理

先来看看样本数据(CSV)

1
2
3
4
5
6
7
timestamp,latitude,longitude,total_cars,carsList
2019-01-10 11:45:55.070781 UTC,32.09995,34.78794,1,[182]
2019-01-10 11:45:55.070781 UTC,32.06567,34.79612,1,[268]
2019-01-10 11:45:55.070781 UTC,32.06465,34.80322,1,[106]
2019-01-10 11:45:55.070781 UTC,32.05978,34.81034,1,[180]
2019-01-10 11:45:55.070781 UTC,32.05133,34.75089,1,[16]
2019-01-10 11:45:55.070781 UTC,32.04223,34.7742,1,[72]

一共有 1048575 条数据,时间、经纬度都包含在内。

format 就很简单,把 list eval 转化一下,然后时间就 strptime,做一些 describe 看看数据有没有偏差,为预处理做准备。

预处理 包含了时间序列的预处理和经纬度区间的预处理,划定区间就行了,别的不合理数据暂时看不出来。

时间序列 Groupby

由于每一周为一个周期,因此把时间序列划分在以周为单位。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
groupby_hour = df[['timestamp', 'total_cars', 'latitude', 'longitude']].groupby(
    [
        df.timestamp.map(lambda x: x.year),
        df.timestamp.map(lambda x: x.month),
        df.timestamp.map(lambda x: x.day),
        df.timestamp.map(lambda x: x.hour)
    ]
).sum()
groupby_hour['datetime'] = list(map(lambda x: datetime(*groupby_hour.iloc[x].name), range(len(groupby_hour))))
groupby_hour.set_index(groupby_hour.datetime, inplace=True)
groupby_hour = groupby_hour.resample('H').first().fillna(0)

然后可以根据划出来的时间序列为接下来的工作做一些准备。比如:

1
2
3
4
5
time_gap = timedelta(days=6, hours=23, minutes=59)  # not days 7 because slice will select the end of time for unknown reason
d = {
    f'week{i + 1}': groupby_hour['total_cars'][datetime(2018, 12, 12) + i * time_gap : datetime(2018, 12, 19) + i * time_gap]
    for i in range(4)
}

时间序列归类描述

对 2018-12-12 到 2019-01-08 共 28 天三星期每间隔 7 天的流量进行相似分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fig, ax = plt.subplots(4, 1, sharex=False, sharey=True, figsize=(15, 40))
fig.set_facecolor('white')

for i in range(4):
    time_gap = timedelta(days=7)
    ax[i].set_title(f"{datetime(2018, 12, 12) + i * time_gap : %F} to {datetime(2018, 12, 19) + i * time_gap : %F} traffic analysis")
    ax[i].set_xlabel('hour')
    ax[i].set_ylabel('cars sum')
    ax[i].set_ylim([200, 1400])

x_array = list(range(24))
label_extra_list = ['Wed', 'Thur', 'Fri', 'Sat', 'Sun', 'Mon', 'Tue']

for i in range(4):
    week = d[f'week{i + 1}']
    for j in range(7):
        ax[i].plot(
            x_array,
            week.iloc[j * 24 : (j + 1) * 24].values,
            label=f'{week.iloc[[j * 24]].index[0] : %F}'
        )
        ax[i].legend()

fig.savefig('./2018-12-12|2018-12-19 7天的流量进行相似分析', dpi=200, bbox_inches='tight')

还有各种时间序列的分析,就不写了。

坐标型归类描述

也就是画热力图,方法得自己搓,有个专门做热力图的三方库不过我没用过。

这里实现原理就是把车辆作为权重,然后把经纬度根据设定的单位长度化为二维数据。

这是第一种算法实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fig, ax = plt.subplots(1, 1, sharex=False, sharey=False, figsize=(15, 10))
fig.set_facecolor('white')

ax.set_title('cutted position block')
ax.set_xlabel('longitude')
ax.set_ylabel('latitude')

step = 0.01
x_index = [f"{i + step / 2:.3f}" for i in cutinto_longitude[:-1]]
y_index = [f"{i + step / 2:.3f}" for i in cutinto_latitude[:-1]]

ax.scatter([i for i in x_index for _ in range(len(y_index))], y_index * len(x_index), s=groups_positions, alpha=0.5)

plt.savefig('总数据热力图', dpi=200, bbox_inches='tight')

坐标和时间关联描述

把坐标区域划分好,然后取一天内某个时间段,专门分析这段时间的车辆状况。

如果想提高数据可靠性,就把每周的数据合并统计绘图。(事实上这么做是必要的,因为一两周的数据量太少了)

可以分析出七天每天在某个固定时间段的流量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
label_extra_list = ['Wed', 'Thur', 'Fri', 'Sat', 'Sun', 'Mon', 'Tue']
highly_range1 = (4, 7)
groups_positions_common_sumweek = {
    v: pd.concat([
        df[(df.timestamp > datetime(2018, 12, 12, highly_range1[0]) + timedelta(days=i)) & (df.timestamp < datetime(2018, 12, 12, highly_range1[1]) + timedelta(days=i))][['latbin', 'lonbin', 'total_cars']],
        df[(df.timestamp > datetime(2018, 12, 19, highly_range1[0]) + timedelta(days=i)) & (df.timestamp < datetime(2018, 12, 19, highly_range1[1]) + timedelta(days=i))][['latbin', 'lonbin', 'total_cars']],
        df[(df.timestamp > datetime(2018, 12, 26, highly_range1[0]) + timedelta(days=i)) & (df.timestamp < datetime(2018, 12, 26, highly_range1[1]) + timedelta(days=i))][['latbin', 'lonbin', 'total_cars']],
        df[(df.timestamp > datetime(2019, 1, 2, highly_range1[0]) + timedelta(days=i)) & (df.timestamp < datetime(2019, 1, 2, highly_range1[1]) + timedelta(days=i))][['latbin', 'lonbin', 'total_cars']]
    ]).groupby(['lonbin', 'latbin']).sum()
    for i, v in enumerate(label_extra_list)
}
for i in groups_positions_common_sumweek:
    preprocess_groups_position(groups_positions_common_sumweek[i])
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
fig, ax = plt.subplots(7, 1, sharex=False, sharey=True, figsize=(15, 70))
fig.set_facecolor('white')

label_extra_list = ['Wed', 'Thur', 'Fri', 'Sat', 'Sun', 'Mon', 'Tue']
unsc_order = [5, 6, 0, 1, 2, 3, 4]

for i_, i in enumerate(unsc_order):
    ax[i_].set_title(f"{label_extra_list[i]} 4a.m.-7a.m. hot scatter plot")
    ax[i_].set_xlabel('longitude')
    ax[i_].set_ylabel('latitude')

step = 0.01
x_index = [f"{i + step / 2:.3f}" for i in cutinto_longitude[:-1]]
y_index = [f"{i + step / 2:.3f}" for i in cutinto_latitude[:-1]]

for i_, i in enumerate(unsc_order):
    ax[i_].scatter([i for i in x_index for _ in range(len(y_index))], y_index * len(x_index), s=groups_positions_common_sumweek[label_extra_list[i]], alpha=0.5)

fig.savefig('./7天数据早高峰4-7am集中热力图', dpi=200, bbox_inches='tight')

结语

不得不说,数学建模,一如既往地,刺激。

整个初赛就三天,第一天整思路,有可能第二天也在整思路,前期做一些数据 describe 不是很复杂。然后到后面有思路了,这个思路实现起来还不是那么容易,这种坐标型数据和时间序列摆一起的又特别复杂,写程序的还必须保证算法可靠性,非常辛苦。

总的来说,压力虽然大,也只拿了优秀奖,但是我还是挺乐在其中的。不管是在团队合作还是个人实力上都有提升。

写这篇文章写得真是太晚了!不过能补上就是好事。

近期应该会多写点博文。

CC BY-NC-SA 4.0 License
最后更新于 2021-11-24 11:43