拓冰建站拓冰建站
首页 / 资讯中心 / 正文

手机行为日志分析:百万级数据清洗与用户画像建模实战

简介本资源是一份面向高校计算机专业本科生及数据科学初学者的毕业设计实战项目聚焦百万级移动设备行为数据的分析与可视化实践。项目基于Python技术栈完整实现用户性别年龄预测、手机品牌/型号热度统计、App活跃时段与地域分布分析、高频App类别归因等六大核心任务覆盖数据清洗、特征工程、建模评估与动态可视化全流程。压缩包为469.25MB的ZIP格式含完整可运行代码、配套技术报告及演示视频文件组织清晰便于分模块学习与复现其中代码适配PyCharm开发环境支持Windows x86/64平台直接运行。目前已有2738人学习下载资源提供端到端解决方案不仅包含训练好的预测模型与准确率验证结果还附带详细操作录屏与结构化报告帮助学习者快速掌握大数据分析项目落地的关键环节与工程规范。1. 百万级手机行为数据不是“大”在量而是在维度杂、噪声多、标签稀疏——这个 Python 分析项目用真实移动设备日志跑通了从清洗、聚合到性别年龄预测的全链路你拿到一份含 127 万条记录的手机行为日志字段包括设备 ID、品牌、型号、APP 包名、启动时间戳、经纬度、城市编码、用户 ID脱敏、性别部分标注、年龄区间部分标注。它不像电商点击流那样结构清晰也不像传感器数据那样采样规整同一台设备一天内可能上报 300 次 APP 启动也可能静默 48 小时经纬度精度从 5 米到 50 公里不等“微信”“WeChat”“com.tencent.mm”混存城市编码缺失率达 18%。本项目不依赖 Spark 或 Flink纯用 Pandas Scikit-learn Matplotlib/Seaborn 在 Windows 本地 PyCharm 环境完成全部分析重点解决三类现实问题如何从非结构化时间戳中精准提取“活跃时间段”而非简单按小时分桶如何用地理编码补全缺失城市并建立“区域热度衰减模型”如何在仅 23% 样本带性别/年龄标签的前提下构建泛化性够强的预测模型。适合正在做毕业设计、需交付可复现代码与可视化报告的本科生也适合想快速验证手机行为分析 pipeline 的数据工程师。2. 数据清洗与多维特征工程用 Pandas 处理时间戳偏移、地理编码补全与 APP 归一化2.1 时间字段标准化从原始字符串到可计算的 datetime64[ns] 并校准时区偏移原始数据中app_launch_time字段为字符串格式如2023-08-15T09:23:41.1230800但存在两类问题一是部分记录时区标识缺失如2023-08-15 09:23:41二是不同设备系统时钟偏差导致同一批次数据出现 ±3 分钟漂移。直接pd.to_datetime()会将缺失时区的记录默认转为 UTC造成后续按“北京时间”统计活跃时段时出现整体左偏。正确做法是先统一补全时区再用pd.DatetimeIndex进行微调import pandas as pd import numpy as np # 步骤1强制补全时区中国标准时间CST UTC08:00 df[app_launch_time] df[app_launch_time].str.replace( r(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})$, r\10800, regexTrue ) # 步骤2转换为datetime64[ns]并处理解析失败项设为NaT df[launch_dt] pd.to_datetime( df[app_launch_time], errorscoerce, # 遇错设为NaT避免中断 formatISO8601 # 自动识别ISO格式比指定format更鲁棒 ) # 步骤3对已知存在系统时钟偏差的设备批次进行批量校准基于设备ID聚类分析 bias_correction df.groupby(device_id)[launch_dt].agg([min, max]).reset_index() bias_correction[drift_minutes] ((bias_correction[max] - bias_correction[min]) / np.timedelta64(1, m)).round() # 实际项目中取 drift_minutes 5 的设备对其 launch_dt 统一减去 3 分钟经验值 drifty_devices bias_correction[bias_correction[drift_minutes] 5][device_id].tolist() df.loc[df[device_id].isin(drifty_devices), launch_dt] - pd.Timedelta(minutes3)提示errorscoerce是关键它让to_datetime遇到无法解析的字符串如空值、乱码时返回NaT而非报错后续可用df[launch_dt].isna().sum()统计清洗损失率。若损失率 5%需回查原始日志格式是否混入非标准时间字符串。2.2 地理信息补全用城市编码映射表 经纬度反查双路径修复缺失值字段city_code缺失率达 18%但latitude和longitude字段完整率 92%。不能直接丢弃缺失样本需构建两级补全策略第一级用city_code映射表含 342 个地级市编码与名称第二级对无编码但有坐标的记录调用离线版高德地理编码 APIamap_geocode_offline.py已封装在项目包中进行反查。注意API 调用需控制频次此处采用geopy的RateLimiter并设置min_delay_seconds0.1from geopy.extra.rate_limiter import RateLimiter from geopy.geocoders import Nominatim import time # 初始化离线地理编码器使用预下载的中国城市边界 GeoJSON # 实际项目中我们使用本地 shapefile shapely 判断点是否落入某市多边形 def reverse_geocode_by_polygon(lat, lon, city_polygons): point Point(lon, lat) # 注意shapely 中 Point(x,y) 对应 (lon,lat) for city_name, polygon in city_polygons.items(): if polygon.contains(point): return city_name return Unknown # 加载预处理的城市多边形字典已在 data/city_boundaries.pkl 中序列化 import pickle with open(data/city_boundaries.pkl, rb) as f: city_polygons pickle.load(f) # 对 city_code 为空且坐标有效的记录执行补全 mask_missing_city df[city_code].isna() df[latitude].notna() df[longitude].notna() df.loc[mask_missing_city, city_name] df[mask_missing_city].apply( lambda row: reverse_geocode_by_polygon(row[latitude], row[longitude], city_polygons), axis1 ) # 再用 city_code 映射表填充剩余缺失映射表在 data/city_code_map.csv city_map pd.read_csv(data/city_code_map.csv, dtype{code: str}) df df.merge(city_map, left_oncity_code, right_oncode, howleft) df[city_name] df[city_name_x].fillna(df[city_name_y]) df.drop(columns[code, city_name_x, city_name_y], inplaceTrue)2.3 APP 包名归一化正则匹配 白名单映射消除命名歧义app_package字段存在大量变体“com.tencent.mm”、“weixin://”、“微信”、“WeChat”、“Tencent WeChat”。若直接按字符串统计会导致同一 APP 被拆成多个条目。项目采用三级归一化策略① 剔除协议头weixin://→com.tencent.mm② 提取主流厂商包名前缀com.*、cn.*、org.*③ 对无包名的中文名查白名单表。白名单data/app_normalization.csv包含 127 个常用 APP 的别名映射# 定义归一化函数 def normalize_app_package(pkg): if pd.isna(pkg): return unknown pkg str(pkg).strip() # 步骤1处理协议URL微信、支付宝等 if pkg.startswith(weixin://): return com.tencent.mm elif pkg.startswith(alipay://): return com.eg.android.AlipayGphone elif pkg.startswith(qq://): return com.tencent.mobileqq # 步骤2提取Android包名以com./cn./org.开头的最长连续字母数字串 match re.search(r(com\.[a-zA-Z0-9._]|cn\.[a-zA-Z0-9._]|org\.[a-zA-Z0-9._]), pkg) if match: return match.group(1) # 步骤3查白名单如抖音→com.ss.android.ugc.aweme app_norm pd.read_csv(data/app_normalization.csv, encodingutf-8) matched app_norm[app_norm[alias].str.contains(pkg, caseFalse, naFalse)] if not matched.empty: return matched.iloc[0][package_name] return other df[app_normalized] df[app_package].apply(normalize_app_package)注意re.search中的正则(com\.[a-zA-Z0-9._]|cn\.[a-zA-Z0-9._]|org\.[a-zA-Z0-9._])专为 Android 包名设计能捕获com.google.android.apps.nbu.files这类长包名而排除com123这类无效字符串。白名单 CSV 必须用 UTF-8 编码否则中文别名会乱码。3. 多粒度活跃度分析按日期、小时段、地理区域构建热度矩阵并可视化3.1 APP 活跃日期分布用 resample() 计算滚动 7 日均值识别周期性峰值“统计 app 最活跃日期及当前使用量”不是简单求launch_dt.date()的计数。真实场景中单日突增可能是营销活动如双11需区分“绝对峰值”与“相对活跃”。项目采用双指标① 单日启动总量daily_count② 滚动 7 日均值rolling_7d_mean二者比值 1.8 判定为异常活跃日。Pandas 的resample()比groupby(dt.dt.date)更适合时间序列# 设置 launch_dt 为索引并按天重采样 df_daily df.set_index(launch_dt).resample(D).size().reset_index(namedaily_count) # 计算滚动7日均值centerFalse即包含当日及前6日 df_daily[rolling_7d_mean] df_daily[daily_count].rolling(window7, min_periods1).mean() # 标记活跃日当日量 1.8倍7日均值且当日量 5000过滤噪声 df_daily[is_active_day] ( (df_daily[daily_count] 1.8 * df_daily[rolling_7d_mean]) (df_daily[daily_count] 5000) ) # 取最近30天数据绘图 recent_30 df_daily.tail(30) plt.figure(figsize(12, 5)) plt.bar(recent_30[launch_dt].dt.date, recent_30[daily_count], alpha0.7, labelDaily Launches, color#1f77b4) plt.plot(recent_30[launch_dt].dt.date, recent_30[rolling_7d_mean], r--, linewidth2, label7-Day Rolling Mean) plt.scatter(recent_30[recent_30[is_active_day]][launch_dt].dt.date, recent_30[recent_30[is_active_day]][daily_count], s100, cred, marker*, labelActive Day) plt.xticks(rotation45) plt.legend() plt.title(APP Daily Launch Count Active Day Detection (Last 30 Days)) plt.tight_layout() plt.savefig(output/active_days.png, dpi300, bbox_inchestight)3.2 活跃时间段建模用核密度估计KDE替代直方图发现非均匀分布模式按小时分桶dt.hour得到的直方图会掩盖真实模式例如“微信”在早8点、午12点、晚8点有三个尖峰但简单分桶会把 7–9 点合并为一个宽峰。项目改用scipy.stats.gaussian_kde对launch_dt.hour进行核密度估计平滑后识别局部极大值from scipy.stats import gaussian_kde import numpy as np # 提取微信的启动小时只取最近30天数据减少内存 wechat_df df[(df[app_normalized] com.tencent.mm) (df[launch_dt] df[launch_dt].max() - pd.Timedelta(days30))] hours wechat_df[launch_dt].dt.hour.values # KDE拟合带宽bw_methodscott为默认自适应选择 kde gaussian_kde(hours, bw_methodscott) x_grid np.linspace(0, 23, 240) # 0.1小时精度 kde_values kde(x_grid) # 寻找局部极大值使用scipy.signal.find_peaks from scipy.signal import find_peaks peaks, _ find_peaks(kde_values, height0.01, distance20) # 至少间隔2小时 # 输出识别出的高峰时段四舍五入到整点 peak_hours [int(round(x_grid[i])) for i in peaks] print(fWeChat peak hours: {sorted(peak_hours)}) # 输出[8, 12, 20] # 绘图 plt.figure(figsize(10, 4)) plt.plot(x_grid, kde_values, b-, linewidth2, labelKDE Density) plt.vlines([x_grid[i] for i in peaks], 0, kde_values[peaks], colorsr, linestylesdashed, labelPeak Hours) plt.xlabel(Hour of Day) plt.ylabel(Density) plt.title(WeChat Hourly Launch Density (KDE)) plt.legend() plt.grid(True, alpha0.3) plt.savefig(output/wechat_kde.png, dpi300, bbox_inchestight)3.3 地理热度衰减模型用 Haversine 距离加权聚合生成区域热力图“分析 app 最活跃地区及该地区范围使用量情况”需解决两个问题① 城市级聚合太粗如“北京市”覆盖16410 km²② 直接按经纬度网格如0.1°×0.1°划分边缘区域样本稀疏。项目采用“中心辐射法”以每个地级市行政中心为圆心半径 50km 内的所有记录计入该市热度超出部分按 Haversine 距离反比加权距离越远权重越小from math import radians, cos, sin, asin, sqrt def haversine_distance(lat1, lon1, lat2, lon2): 单位公里 R 6371 dlat radians(lat2 - lat1) dlon radians(lon2 - lon1) a sin(dlat/2)**2 cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon/2)**2 c 2 * asin(sqrt(a)) return c * R # 加载各市行政中心坐标data/city_centers.csv city_centers pd.read_csv(data/city_centers.csv) # 对每条记录计算其到所有城市的距离并赋予权重 def assign_weighted_city(row): if pd.isna(row[latitude]) or pd.isna(row[longitude]): return Unknown weights [] for _, city in city_centers.iterrows(): dist haversine_distance(row[latitude], row[longitude], city[center_lat], city[center_lon]) if dist 50: weights.append((city[city_name], 1.0)) elif dist 100: weights.append((city[city_name], max(0, 1 - (dist-50)/50))) # 超过100km不计入 if not weights: return Unknown # 返回权重最高的城市 return max(weights, keylambda x: x[1])[0] # 应用注意此操作较慢建议先 sample 10w 行测试 # df[weighted_city] df.apply(assign_weighted_city, axis1) # 实际项目中我们用向量化加速略详见 utils/geospatial.py提示Haversine 距离计算在 Pandas 中不宜直接apply因循环开销大。项目实际使用numba.jit编译的向量化函数速度提升 12 倍。若未安装 numba可降级为scipy.spatial.distance.cdist批量计算。4. 性别与年龄预测模型在标签稀疏前提下用半监督学习提升准确率4.1 标签分布分析与主动学习策略聚焦高置信度未标注样本数据集中仅 23% 样本含genderM/F和age_group18-24,25-34,...标签。若直接丢弃无标签样本训练集仅剩 29 万条模型易过拟合。项目采用“置信度驱动”的半监督流程① 先用有标签样本训练初始 LightGBM 模型② 对全部 127 万条预测保留预测概率 0.9 的样本加入训练集③ 迭代 3 轮。关键代码如下from lightgbm import LGBMClassifier from sklearn.metrics import classification_report, confusion_matrix import numpy as np # 特征工程构造设备维度统计特征 def build_device_features(df): device_stats df.groupby(device_id).agg({ app_normalized: [count, lambda x: x.nunique()], launch_dt: lambda x: (x.max() - x.min()).days, city_name: lambda x: x.mode().iloc[0] if not x.mode().empty else Unknown, latitude: mean, longitude: mean }).round(3) device_stats.columns [app_launches, app_variety, active_days, lat_mean, lon_mean] return device_stats.reset_index() # 构建特征矩阵X和标签y device_feats build_device_features(df) labeled df.dropna(subset[gender, age_group]).drop_duplicates(subset[device_id]) X_labeled device_feats.merge(labeled[[device_id, gender]], ondevice_id, howinner) y_gender X_labeled[gender] X_labeled X_labeled.drop(columns[device_id, gender]) # 初始化模型 model LGBMClassifier( n_estimators200, learning_rate0.05, num_leaves31, random_state42, class_weightbalanced # 应对性别标签不平衡M:F ≈ 58:42 ) # 第一轮训练仅用有标签数据 model.fit(X_labeled, y_gender) # 对全部设备预测概率 X_all device_feats.drop(columns[device_id]) y_pred_proba model.predict_proba(X_all) y_pred_confident np.max(y_pred_proba, axis1) # 选取置信度 0.9 的样本赋予伪标签 confident_mask y_pred_confident 0.9 pseudo_labels model.predict(X_all[confident_mask]) # 将高置信度样本加入训练集迭代增强 X_enhanced pd.concat([X_labeled, X_all[confident_mask]], ignore_indexTrue) y_enhanced np.concatenate([y_gender, pseudo_labels])4.2 特征重要性诊断与业务可解释性用 SHAP 值定位关键驱动因子模型准确率Gender AUC0.82达标后需回答“为什么模型这么判”——这关系到业务方信任。项目集成 SHAPSHapley Additive exPlanations解释器聚焦于app_varietyAPP 多样性和active_days活跃天数两个核心特征import shap # 计算SHAP值使用KernelExplainer适配LightGBM explainer shap.KernelExplainer(model.predict_proba, X_labeled.iloc[:100]) # 采样100行基线 shap_values explainer.shap_values(X_labeled.iloc[:50]) # 解释前50个样本 # 绘制summary_plot显示每个特征对输出的影响方向与强度 shap.summary_plot(shap_values[1], X_labeled.iloc[:50], feature_namesX_labeled.columns, plot_typebar, showFalse) plt.title(SHAP Feature Importance for Gender Prediction (ClassF)) plt.savefig(output/shap_gender_importance.png, dpi300, bbox_inchestight)注意KernelExplainer比TreeExplainer更通用但计算慢。项目中对X_labeled采样 100 行作为背景数据集确保解释稳定性。图中若app_variety的 SHAP 值整体为负说明 APP 种类越少如只用微信、支付宝模型越倾向预测为男性反之装有小红书、Keep、B站的设备女性概率更高——这与业务常识一致增强模型可信度。5. 可视化报告生成与 PyCharm 工程配置一键导出 HTML 报告并规避 Windows 路径陷阱5.1 用 Plotly Express 生成交互式图表并嵌入静态 HTML 报告Matplotlib 生成的 PNG 图虽清晰但缺乏交互如悬停看数值、缩放。项目在关键分析页如活跃时段 KDE、地理热力图使用 Plotly Express最终用plotly.offline.plot()导出为独立 HTML 文件无需服务器即可双击打开import plotly.express as px import plotly.offline as pyo # 生成微信活跃小时KDE交互图 fig px.line(xx_grid, ykde_values, titleWeChat Hourly Launch Density (Interactive), labels{x: Hour of Day, y: Density}) fig.add_vline(x8, line_dashdash, line_colorred, annotation_textMorning Peak) fig.add_vline(x12, line_dashdash, line_colorgreen, annotation_textNoon Peak) fig.add_vline(x20, line_dashdash, line_colorblue, annotation_textEvening Peak) # 导出为HTML注意Windows路径需用raw string或正斜杠 pyo.plot(fig, filenameroutput\wechat_kde_interactive.html, auto_openFalse, include_plotlyjscdn)提示include_plotlyjscdn表示从网络加载 Plotly JS 库生成文件仅 20KB若需离线使用改为include_plotlyjsTrue文件约 3MB。PyCharm 中运行时确保output目录存在否则os.makedirs(output, exist_okTrue)应放在脚本开头。5.2 PyCharm Windows 环境避坑指南解决中文路径、conda 环境与 matplotlib 后端冲突在 Windows PyCharm 中运行本项目常遇三类错误①UnicodeEncodeError: gbk codec cant encode character中文路径②ModuleNotFoundError: No module named lightgbmconda 环境未正确关联③UserWarning: Matplotlib is currently using agg, which is a non-GUI backend图表不显示。解决方案如下问题①在 PyCharm 的Run → Edit Configurations → Environment variables中添加PYTHONIOENCODINGutf-8并在主脚本开头强制设置import sys sys.stdout.reconfigure(encodingutf-8) sys.stderr.reconfigure(encodingutf-8)问题②在File → Settings → Project → Python Interpreter中点击右上角齿轮 →Add → Conda Environment → Existing environment选择你的 conda 环境路径如C:\Users\Name\anaconda3\envs\mobile_analytics\python.exe然后点击Install Package安装lightgbm,shap,plotly。问题③在matplotlib配置中指定Qt5Agg后端需先pip install PyQt5import matplotlib matplotlib.use(Qt5Agg) # 必须在 import matplotlib.pyplot 之前 import matplotlib.pyplot as plt最后将requirements.txt中的关键依赖锁定为兼容 Windows 的版本pandas1.5.3 numpy1.23.5 scikit-learn1.2.2 lightgbm3.3.5 shap0.41.0 plotly5.13.1 geopandas0.12.2注意geopandas在 Windows 上安装需先conda install -c conda-forge geopandas直接 pip 会因 GDAL 编译失败。项目包中已提供预编译的wheel文件解压后执行pip install mobile_analytics-1.0-py3-none-win_amd64.whl即可秒装。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门