项目介绍
对重庆市出租车GPS数据进行清洗和挖掘,通过提取出租车载客行程、交通小区划分、计算出行OD表等一系列操作量化城市不同区域流量,并挖掘出城市热点区域进行可视化。
数据介绍
本项目数据为重庆市2017年某两日内的出租车GPS记录数据,数据集为TXT文件,数据集压缩包可通过百度网盘自行获取
链接:https://pan.baidu.com/s/1ALzjE0De4g8CjhitmTCCBA
提取码:4vy0
获取数据集后将数据导入MySQL数据库中,然后对数据进行全面清洗,数据可能存在以下方面的问题:
1.记录重复
2.记录经纬度超出重庆市甚至中国经纬度范围
3.车辆时刻表在变而GPS坐标不变
4.车辆状态字段频繁跳动
上述问题几乎都可以用SQL语句解决,在此不做详细介绍。
已将出租车GPS数据存入到MySQL数据库中,并进行了全面的数据清洗,并按照车牌号进行排序后的数据如下:
字段有记录id、出租车id、日期、时间、经度、纬度、海拔、乘客状态(0表示出租车内无客,1表示有客)、GPS状态、时间日期组合字段datetime,原数据共有2700多万条记录,经过清洗后还剩1900多万条合理的记录数据。
1.提取出租车载客行程
python连接数据库,对表中相关字段进行读取,构造<车牌号id,上车时间,下车时间,上车经度,上车纬度,下车经度,下车纬度>
#连接数据库
import pymysql
import pandas as pd
db_config={
'host':'localhost',
'user':'root',
'password':'123456',
'db':'transdb',
'charset':'utf8mb4'
}
connection=pymysql.connect(**db_config)
cursor=connection.cursor()
sql='select id,datetime,longitude,latitude,passenger from taxiorder'
cursor.execute(sql)
rows=cursor.fetchall()
df=pd.DataFrame(rows)
df.columns=['id','datetime','longitude','latitude','passenger']
上面表已经是根据车牌号和时间进行排序后的数据,接下来根据passenger载客状态字段提取出租车载客行程,passenger由0-1表示上车,由1-0表示下车。
创建一个新字段passenger1,它的值是passenger整体往上移一行,再创建一列passengerchange,它的值是passenger1-passenger,记录载客状态的变化。passengerchange为1的记录是上车记录,为-1的记录是下车记录,提取上车记录和下车记录
#创建一列passenger1,它的值是passenger整体往上移一行
df['passenger1']=df['passenger'].shift(-1)
print(df[['passenger','passenger1']])
#创建一列passengerchange,它的值是passenger1减去pasenger,表示载客状态的变化
df['passengerchange']=df['passenger1']-df['passenger']
#print(df['passengerchange'])
#提取上车记录和下车记录(即上车记录:passengerchange=1;下车信息:passengerchange=-1)
oddata=df[(df['passengerchange']==1)|(df['passengerchange']==-1)&(df['id']==df['id'].shift(-1))]
提取上面表中的车牌号、时间、经纬度和乘客状态变化字段,分配新字段为<id,STime,SLng,SLat,passengerchange>,然后STime、SLng和SLat字段分别整体向上平移一行后作为ETime、ELng和ELat,S代表每条行程的开始量,E代表结束量,然后取车牌号相等且passengerchange值为1的所有记录,得到所有出租车的载客行程
#进一步提取OD信息
oddata=oddata[['id','datetime','longitude','latitude','passengerchange']]
oddata.columns=['id','Stime','SLng','SLat','passengerchange']
oddata['Etime']=oddata['Stime'].shift(-1)
oddata['ELng']=oddata['SLng'].shift(-1)
oddata['ELat']=oddata['SLat'].shift(-1)
oddata=oddata[(oddata['id']==oddata['id'].shift(-1))&(oddata['passengerchange']==1)]
oddata=oddata.drop('passengerchange',axis=1)
选取其中一天的数据对不同时间段出租车行程进行统计
#不同时间段出租车行程统计
df=oddata.copy()
# 筛选2017-03-01的数据
df_20170301 = df[df['Stime'].dt.date == pd.to_datetime('2017-03-01').date()]
date_str='2017-03-01 '
# 定义时间段
time_periods = {
'0:00-6:00': (pd.to_datetime(date_str+'00:00:00'), pd.to_datetime(date_str+'06:00:00')),
'6:00-9:00': (pd.to_datetime(date_str+'06:00:00'), pd.to_datetime(date_str+'09:00:00')),
'9:00-12:00': (pd.to_datetime(date_str+'09:00:00'), pd.to_datetime(date_str+'12:00:00')),
'12:00-15:00': (pd.to_datetime(date_str+'12:00:00'), pd.to_datetime(date_str+'15:00:00')),
'15:00-18:00': (pd.to_datetime(date_str+'15:00:00'), pd.to_datetime(date_str+'18:00:00')),
'18:00-21:00': (pd.to_datetime(date_str+'18:00:00'), pd.to_datetime(date_str+'21:00:00')),
'21:00-24:00': (pd.to_datetime(date_str+'21:00:00'), pd.to_datetime(date_str+'23:59:59'))
}
# 对每个时间段进行统计
for period_name, (start_time, end_time) in time_periods.items():
print(start_time,end_time)
# 筛选当前时间段的数据
period_df = df_20170301[(df_20170301['Stime'] >= start_time) &
(df_20170301['Stime'] < end_time)]
# 统计行程数量
trip_count = period_df.shape[0]
print(f"时间段 {period_name} 的行程数量: {trip_count}")
可以根据上面表格初步看出高峰出行时间段
随意选取其中十条行程进行百度地图可视化,蓝色代表出发点,红色代表目的地
2.网格划分交通小区
采用网格划分的方式对所有行程区域进行交通小区划分,在这里选择将交通小区的经度均分成6份,将纬度均分成5份(根据范围大小和实际情况综合考虑划分),然后对交通小区从01L01到06L05进行编号,前两位表示经度位置,后两位表示纬度位置。
max_start_lng = oddata['SLng'].max()
max_end_lng = oddata['ELng'].max()
# 比较两个最大值,找出真正的最大经度坐标
lngmax = max_start_lng if max_start_lng > max_end_lng else max_end_lng
max_start_lat = oddata['SLat'].max()
max_end_lat = oddata['ELat'].max()
# 比较两个最大值,找出真正的最大纬度坐标
latmax= max_start_lat if max_start_lat > max_end_lat else max_end_lat
min_start_lng=oddata['SLng'].min()
min_end_lng=oddata['ELng'].min()
lngmin=min_start_lng if min_start_lng<min_end_lng else min_end_lng
min_start_lat=oddata['SLat'].min()
min_end_lat=oddata['ELat'].min()
latmin=min_start_lat if min_start_lat<min_end_lat else min_end_lat
# 网格划分参数
grid_count_lat = 6 # 纬度方向网格数量
grid_count_lng = 5 # 经度方向网格数量
# 计算每个网格的纬度和经度大小
lat_interval = (latmax - latmin) / grid_count_lat
lng_interval = (lngmax - lngmin) / grid_count_lng
# 构建网格编号和边界信息的字典
zone_boundaries = {}
for lat_index in range(grid_count_lat):
for lng_index in range(grid_count_lng):
# 计算当前网格的边界
lat_start = latmin + lat_index * lat_interval
lat_end = lat_start + lat_interval
lng_start = lngmin + lng_index * lng_interval
lng_end = lng_start + lng_interval
# 创建网格编号,格式为 "XXLYY",其中XX是纬度索引,YY是经度索引
grid_id = f"{lat_index + 1:02d}L{lng_index + 1:02d}"
# 将网格的边界信息添加到zone_boundaries字典中
zone_boundaries[grid_id] = {
'lat_min': lat_start,
'lat_max': lat_end,
'lng_min': lng_start,
'lng_max': lng_end,
}
# 打印zone_boundaries字典以检查结果
for grid_id, boundaries in zone_boundaries.items():
print(f"Grid ID: {grid_id}, Boundaries: {boundaries}")
然后将出租车载客行程分配到各个交通小区中
#出租车行程分配到各交通小区
import pandas as pd
# 定义一个函数来检查一个点是否在某个小区内
def check_zone(lat, lng, zone_boundary):
return (zone_boundary['lat_min'] <= lat <= zone_boundary['lat_max']) and \
(zone_boundary['lng_min'] <= lng <= zone_boundary['lng_max'])
# 遍历oddata DataFrame,为每条记录分配交通小区
oddata['origin_zone'] = None
oddata['destination_zone'] = None
for index, row in oddata.iterrows():
origin_zone = next((zone for zone in zone_boundaries if check_zone(row['SLat'], row['SLng'], zone_boundaries[zone])), None)
destination_zone = next((zone for zone in zone_boundaries if check_zone(row['ELat'], row['ELng'], zone_boundaries[zone])), None)
oddata.at[index, 'origin_zone'] = origin_zone
oddata.at[index, 'destination_zone'] = destination_zone
3.计算出行OD表
这里选取一天进行OD表的计算
利用pandas中的DataFrame结构存储OD表,列名和行名分别是各交通小区的编号,每个单元格对应一个交通小区对。
初始化所有单元格值为0,每条出租车行程的上车点和下车点对应一个交通小区对,读取一条行程数据,并找到其所对应的交通小区对,在表中对应交通小区对的数值上加1,对所有行程执行该操作,得到一天内所有行程的OD表,将其存储为csv文件。
import pandas as pd
#提取交通小区的编码(即字典zone_boundaries的键)
zone_ids = list(zone_boundaries.keys())
# 初始化OD表,所有值都设为0
od_table = pd.DataFrame(0, index=zone_ids, columns=zone_ids, dtype=int)
# 筛选出 'Stime' 在三月二号的数据
date_filter = oddata['Stime'].dt.month == 3
date_filter &= oddata['Stime'].dt.day == 2
# 使用筛选条件来选择对应的行
oddata_march_2st = oddata[date_filter]
# 遍历oddata_march_2st DataFrame,更新OD表
for index, row in oddata_march_2st.iterrows():
origin_zone = row['origin_zone']
dest_zone = row['destination_zone']
od_table.at[origin_zone, dest_zone] += 1
# 将OD表保存为CSV文件
csv_filename = 'D:/交通大数据/taxiod/taxi_od_table02.csv'
od_table.to_csv(csv_filename)
至此,出租车载客OD表提取结束。可以看到很多单元格为空,所以推断出租车载客起始-目的地对十分集中。
4.城市热点区域挖掘
上面的出租车载客OD表已经从一方面展示了热点交通区域,数值集较大的单元格即为热点交通小区对。为了进一步挖掘城市热点区域,在这里选取出租车载客行程目的地作为研究目标,采用DBSCAN算法对目的地坐标进行聚类,得到目的热点簇
from sklearn.cluster import DBSCAN
import pandas as pd
# 假设taxi_df是您的DataFrame
coords = oddata[['ELat', 'ELng']].values # 提取上车点坐标
# 使用DBSCAN进行聚类
dbscan = DBSCAN(eps=0.01, min_samples=10) # 根据需要调整eps和min_samples参数
oddata['cluster_label'] = dbscan.fit_predict(coords)
df=oddata.copy()
# 计算每个标签的个数
label_counts = df['cluster_label'].value_counts().reset_index()
# 给标签计数DataFrame添加列名
label_counts.columns = ['cluster_label', 'count']
# 将标签计数合并回原始DataFrame
# 这里我们使用left join,基于'label'列将计数信息合并回去
df_with_counts= df.merge(label_counts, on='cluster_label', how='left')
df_with_counts
聚类结果如下
标签为-1的是噪声点,不被归到任意簇中。
接下来调用百度地图API进行热点区域可视化。提取出租车行程表中的下车点经纬度坐标和所属簇的下车点总数count,count值大的簇下车点密集,为高密度区,count值小的点为低密度区,设定点的颜色由高密度到低密度用红色-橙色-绿色颜色过渡显示在百度地图上。
#将每个点和它的count写入到js文件中,绘制热力图
import json
# 选择需要的列
selected_data = df_with_counts[['ELng', 'ELat', 'count']]
# 构造JavaScript对象格式
points = []
for index, row in selected_data.iterrows():
point = {
"lng": row['ELng'],
"lat": row['ELat'],
"count": row['count']
}
points.append(point)
# 将对象数组转换为JSON字符串
json_points = json.dumps(points, ensure_ascii=False)
# 写入到.js文件中
js_content = f"var points = {json_points}\n"
with open('D:/交通大数据/points.js', 'w', encoding='utf-8') as js_file:
js_file.write(js_content)
print("数据已写入points.js文件。")
编写html,调用百度地图API(可自行取百度地图控制台申请APK),引入上面的json文件对热点进行绘制
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=Jc3MjtqKpJpRNynmm5DnUR3wqxvQUwCj"></script>
<script type="text/javascript" src="http://api.map.baidu.com/library/Heatmap/2.0/src/Heatmap_min.js"></script>
<script type="text/javascript" src="D:/交通大数据/points.js"></script>
<title>热力图功能示例</title>
<style type="text/css">
ul,li{list-style: none;margin:0;padding:0;float:left;}
html{height:100%}
body{height:100%;margin:0px;padding:0px;font-family:"微软雅黑";}
#container{height:96%;width:100%;}
#r-result{width:100%;}
</style>
</head>
<body>
<div id="container"></div>
<div id="r-result">
<input type="button" onclick="openHeatmap();" value="显示热力图"/><input type="button" onclick="closeHeatmap();" value="关闭热力图"/>
</div>
</body>
</html>
<script type="text/javascript">
var map = new BMap.Map("container"); // 创建地图实例
var point = new BMap.Point(106.538768,29.586074);
map.centerAndZoom(point, 15); // 初始化地图,设置中心点坐标和地图级别
map.enableScrollWheelZoom(); // 允许滚轮缩放
if(!isSupportCanvas()){
alert('热力图目前只支持有canvas支持的浏览器,您所使用的浏览器不能使用热力图功能~')
}
//详细的参数,可以查看heatmap.js的文档 https://github.com/pa7/heatmap.js/blob/master/README.md
//参数说明如下:
/* visible 热力图是否显示,默认为true
* opacity 热力的透明度,1-100
* radius 势力图的每个点的半径大小
* gradient {JSON} 热力图的渐变区间 . gradient如下所示
* {
.2:'rgb(0, 255, 255)',
.5:'rgb(0, 110, 255)',
.8:'rgb(100, 0, 255)'
}
其中 key 表示插值的位置, 0~1.
value 为颜色值.
*/
heatmapOverlay = new BMapLib.HeatmapOverlay({"radius":5});
map.addOverlay(heatmapOverlay);
heatmapOverlay.setDataSet({data:points,max:184811});
//var myarray=new Array();
//myarray.push(new BMap.Point(106.542,29.590));
//myarray.push(new BMap.Point(106.529,29.590));
//myarray.push(new BMap.Point(106.529,29.577));
//myarray.push(new BMap.Point(106.542,29.577));
//myarray.push(new BMap.Point(106.542,29.590));
//var polyline = new BMap.Polyline( myarray, {strokeColor:"blue", strokeWeight:5, strokeOpacity:0.8}); //创建折线
//map.addOverlay(polyline);
//是否显示热力图
function openHeatmap(){
heatmapOverlay.show();
}
function closeHeatmap(){
heatmapOverlay.hide();
}
closeHeatmap();
function setGradient() {
// 定义渐变色对象
var gradient = {
0: 'rgb(0, 255, 0)', // 低密度区域为绿色
0.5: 'rgb(255, 165, 0)', // 中等密度区域为橙色
1: 'rgb(255, 0, 0)' // 高密度区域为红色
};
// 应用渐变色配置到热力图
heatmapOverlay.setOptions({"gradient": gradient});
}
//判断浏览区是否支持canvas
function isSupportCanvas(){
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
}
</script>
可视化效果图如下:
可以看到重庆市目的地热点区域主要集中在渝中区、渝北区、江北区、沙坪坝区、九龙坡区、南岸区和巴南区的部分沿江区域,北碚区的火车站和缙云山景区也较为密集。
总结
该项目涉及数据采集、清洗、提取转换、可视化、聚类挖掘等一系列数据处理和分析过程,是一个比较完整的大规模数据分析实例。
实验发现,城市中存在明显的出行热点区域,这些区域通常与商业中心、景点分布、交通枢纽等地理位置相吻合。通过聚类分析和百度地图可视化展示,能够识别出具有高出行到达密度的区域,为城市规划和交通管理提供了数据支持。
实际上,还可以以该数据为基础,对重庆市进行合理的路段划,采用合适的机器学习算法对全天24时隙不同路段的速度情况进行预测,对城市交通管制和交通规划也具有较强的实际意义。