副标题:厦门2020年共享单车数据深度清洗与分析

为了深入分析厦门2020年底的共享单车数据并确保数据质量,我们依据中国城市规划设计研究院(CAUPD)自2020年至2024年发布的《中国主要城市共享单车/电单车骑行报告》中关于厦门市的各项指标描述和分类方法进行了数据清洗。通过参考报告中的数据结论和标准分类体系,来确认清洗数据的限制条件,确保其标准化和可比性,从而揭示厦门特有的出行模式和发展特点。我们先来对齐一下信息颗粒度;

参考《2021年度中国主要城市共享单车、电单车骑行报告》的指标,我们来看看厦门市;

  •  厦门属于Ⅰ型大城市,也就是城区常住人口300万以上500万以下的城市;
  • 活跃用户日均骑行距离: 1.7公里;
  • 活跃用户日均骑行时长: 约12分钟;
  • 骑行时长分布: 15分钟以内的骑行占总骑行次数的90%以上;
  • 高峰时段平均骑行速度:Ⅰ型大城市速度约9.5km/h(2022年报告);
  • 对于轨道规模在50-100公里的城市,如厦门,其周边共享单车的平均骑行距离为1.3公里;

基于上述的统计背景,我们来对现有数据进行清洗;

订单有效性限制条件(这个判断条件具有一定个人主观性)

  • 单次订单的总骑行距离必须至少为50米;
  • 单次订单的持续时间必须至少为1分钟;
  • 对于每辆共享单车的单次订单,它会检查连续两个定位点之间的时间差和距离差(从一个定位点到下一个定位点的时间间隔不得超过1分钟,且从一个定位点到下一个定位点的距离不得超过100米(相当于24km/h)),以判断是否构成一个有效的订单;
  • 如果超过3分钟没有出现下一个坐标点,则认为是新的订单开始,并重新编号记录;
  • 单次订单的总骑行距离不得超过15公里;
  • 单次订单的持续时间不得超过1小时;
  • 订单的起止点之间的距离必须大于100米。

完整代码#运行环境 Python 3.11

import pandas as pd
from geopy.distance import geodesic
from tqdm import tqdm
from datetime import timedelta

# 定义文件路径
input_file_path = r'D:\data\gxdc_20201221.csv'  # 输入 CSV 文件路径
output_file_path = r'D:\data\gxdc_qx20201221.csv'  # 输出 CSV 文件路径

# 定义参数
min_order_distance = 50  # 单次订单最小骑行距离阈值(50米)
min_order_duration = timedelta(minutes=1)  # 单次订单最小骑行时间阈值(1分钟)
max_point_interval_time = timedelta(minutes=1)  # 定位点间隔最大时间(1分钟)
max_point_interval_distance = 100  # 定位点间隔最大距离(100米)
order_timeout = timedelta(minutes=3)  # 订单超时时间(3分钟)
max_order_distance = 15000  # 单次订单最大骑行距离(15公里)
max_order_duration = timedelta(hours=1)  # 单次订单最大骑行时间(1小时)

# 读取 CSV 文件
data = pd.read_csv(input_file_path)

# 确保数据按时间排序,并转换 LOCATING_TIME 列为 datetime 类型
data['LOCATING_TIME'] = pd.to_datetime(data['LOCATING_TIME'])
data = data.sort_values(by=['BICYCLE_ID', 'LOCATING_TIME'])

# 初始化结果 DataFrame
valid_data = []

def process_bicycle_id(group):
    order_num = 0
    current_order = []
    prev_time = None
    prev_lat, prev_lon = None, None
    total_distance = 0  # 用于记录当前订单的总距离
    start_time = None  # 用于记录订单开始时间
    end_time = None  # 用于记录订单结束时间

    for idx, row in group.iterrows():
        current_time = row['LOCATING_TIME']
        current_lat, current_lon = row['LATITUDE'], row['LONGITUDE']

        if prev_time is not None:
            time_diff = current_time - prev_time
            distance = geodesic((prev_lat, prev_lon), (current_lat, current_lon)).meters

            # 如果时间或距离超过了定位点间的最大允许值,或者超过了订单超时时间,则认为订单结束
            if time_diff > max_point_interval_time or distance > max_point_interval_distance or time_diff > order_timeout:
                # 检查当前订单是否有效
                if len(current_order) >= 2 and min_order_distance <= total_distance <= max_order_distance and start_time is not None and min_order_duration <= (end_time - start_time) <= max_order_duration:
                    total_duration_minutes = (end_time - start_time).total_seconds() / 60  # 总时间以分钟为单位
                    for order_idx, order_row in enumerate(current_order):
                        order_row_copy = order_row.copy()
                        order_row_copy['ORDER_NUM'] = order_num
                        order_row_copy['ORDER_SEQ'] = order_idx + 1
                        order_row_copy['TOTAL_DISTANCE'] = total_distance  # 添加总距离字段
                        order_row_copy['TOTAL_DURATION'] = round(total_duration_minutes, 2)  # 添加总时间字段(分钟)
                        valid_data.append(order_row_copy)
                    order_num += 1

                # 重置订单信息
                current_order = [row.copy()]
                total_distance = 0
                start_time = current_time
                end_time = current_time

            else:
                total_distance += distance
                end_time = current_time
                current_order.append(row.copy())

        else:  # 如果是订单的第一个数据点
            start_time = current_time
            end_time = current_time
            current_order = [row.copy()]

        prev_time = current_time
        prev_lat, prev_lon = current_lat, current_lon

        # 如果当前订单的距离或时间已经超过了最大限制,则直接放弃该订单
        if total_distance > max_order_distance or (end_time - start_time) > max_order_duration:
            current_order = []  # 清空当前订单
            total_distance = 0
            start_time = current_time
            end_time = current_time

    # 处理最后一组订单
    if current_order and len(current_order) >= 2 and min_order_distance <= total_distance <= max_order_distance and start_time is not None and min_order_duration <= (end_time - start_time) <= max_order_duration:
        total_duration_minutes = (end_time - start_time).total_seconds() / 60  # 总时间以分钟为单位
        for order_idx, order_row in enumerate(current_order):
            order_row_copy = order_row.copy()
            order_row_copy['ORDER_NUM'] = order_num
            order_row_copy['ORDER_SEQ'] = order_idx + 1
            order_row_copy['TOTAL_DISTANCE'] = total_distance  # 添加总距离字段
            order_row_copy['TOTAL_DURATION'] = round(total_duration_minutes, 2)  # 添加总时间字段(分钟)
            valid_data.append(order_row_copy)

# 获取唯一的 BICYCLE_ID 并处理每个ID的数据
unique_bicycle_ids = data['BICYCLE_ID'].unique()

# 使用 tqdm 包装 groupby 操作以显示进度条
for bicycle_id in tqdm(unique_bicycle_ids, desc="Processing BICYCLE_IDs"):
    group = data[data['BICYCLE_ID'] == bicycle_id]
    if not group.empty:
        process_bicycle_id(group)

# 将结果转换为 DataFrame 并保存到 CSV
if valid_data:
    valid_data_df = pd.DataFrame(valid_data)
    valid_data_df.to_csv(output_file_path, index=False)
    print(f"有效数据已保存到 {output_file_path}")
else:
    print("没有符合条件的有效数据。")

部分清洗结果如图;

以2020年12月21日为例,原始数据在250万条,根据当前条件下筛选下剩余220万左右,当然当前筛选条件仍然存在一些漂移数据或者在某个节点周边反复横跳的数据,后续继续优化限制条件;

反复横跳的点;

漂移点数据;

文章仅用于分享个人学习成果与个人存档之用,分享知识,如有侵权,请联系作者进行删除。所信息均基于作者的个人理解和经验,不代表任何官方立场或权威解读。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐