
简介这是一份俄罗斯刑事犯罪数据集分析预测实战资源面向希望用 Python 完成完整数据挖掘流程的初学者和数据分析人员覆盖趋势分析、回归预测与聚类等典型任务。压缩包共 5 个文件含 3 个可直接运行的 Python 源代码、1 个 txt 说明文档和 1 个 CSV 数据集源代码合计约 35KBCSV 数据约 589.70KB压缩后整体约 107KB。代码手工整理、无语法错误以 2008—2023 年俄罗斯犯罪统计数据为基础分别实现犯罪趋势可视化、酒精消费与犯罪率回归分析、犯罪率变化规律与模式挖掘过程中使用 pandas、numpy、seaborn、matplotlib、scikit-learn、plotly 等主流库并包含 train_test_split、RandomForestRegressor、KMeans、curve_fit 等具体方法整体适合对照练习。包内附 readme 说明文件便于核对环境依赖与运行顺序减少上手阻力目前已有 55 人学习。下载后即可对照数据复现图表与模型结果也适合作为课程设计或机器学习入门练习的参考。1. 俄罗斯刑事犯罪数据集学AI预测不是找数据集而是找到能跑通的完整闭环很多人找AI实战项目眼睛只盯着“数据集”三个字以为数据够多就能学会预测。真上手才发现真正的门槛不在模型而在从解压zip到产出预测结果这条链路上——数据脏不脏、标签平不平衡、时间字段怎么解析、三个源代码和数据集对不对得上。这份“俄罗斯的刑事犯罪数据集分析预测实例”恰好是这么一条完整链路一份589.70 KB的犯罪记录数据配上3个源代码从数据清洗一路做到模型预测。它能帮你练的不是“调参玄学”而是拿到一份陌生数据集后怎么在最短时间内摸清结构、踩平坑、跑出可信的预测结果。适合正在学机器学习分类任务的初学者也适合想看看别人怎么组织数据分析和建模流程的从业者。下面我按自己拿到这类项目时的处理顺序把整条路走一遍。2. 拿到压缩包先做这三件事解压结构核对、数据清洗、标签分布体检2.1 先别急着训练目录结构与数据字典的核对顺序任何zip包到手第一步不是解压完就敲pd.read_csv()而是先看目录结构。刑事犯罪数据集几乎不可能只有一个CSV常见做法是包含原始数据、清洗后的数据、代码文件和一个README或数据字典。我一般会先在命令行里把目录树打出来确认文件数量和命名再决定后面代码里路径怎么写。unzip AI实战-俄罗斯的刑事犯罪数据集分析预测实例含3个源代码589.70 KB完整的数据集.zip -d russian_crime cd russian_crime find . -type f | sort这段命令把压缩包解压到russian_crime目录然后用find列出所有文件。-d参数指定解压目标目录避免文件散落到当前文件夹到处乱放。列出文件后重点看三样东西CSV文件的命名规律、代码文件是.py还是.ipynb、有没有说明文档。如果发现代码里的文件名和你实际解压出来的不一致后面跑起来一定会报FileNotFoundError这一眼就能排查掉。数据字典是这步更关键的产出。打开CSV看一眼列名和每列的前几行把字段含义记下来尤其是预测目标列通常是status或is_solved这类二值字段。俄罗斯刑事犯罪数据的字段一般围绕犯罪类型、发生地、时间、受害人或嫌疑人信息展开字段可能是俄语转写的英文也可能是缩写。我习惯把列名和推断含义写在一个文本文件里后续做特征工程随时回查。2.2 用pandas做第一轮数据质量体检缺失值、类型、时间字段目录结构核对完就要把数据真正读进来做体检。刑事犯罪记录这类人工录入的数据脏的程度往往超出预期日期字段混着多种格式、地区名称有拼写差异、年龄和武器字段可能留空。下面这段代码覆盖了最基础的三项体检缺失值比例、每列数据类型、时间字段的取值区间。import pandas as pd df pd.read_csv(russian_crime_data.csv, encodingutf-8) # 1. 缺失值概览按缺失比例从高到低排列 missing df.isnull().sum() missing missing[missing 0].sort_values(ascendingFalse) print(缺失字段及比例) print((missing / len(df)).round(4)) # 2. 每列数据类型重点看时间列是否被识别成object print(\n数据类型) print(df.dtypes) # 3. 检查日期列的范围是否合理 if date in df.columns: df[date] pd.to_datetime(df[date], errorscoerce) print(\n日期范围, df[date].min(), -, df[date].max()) print(解析失败行数, df[date].isnull().sum())这里errorscoerce是血泪经验的产物俄罗斯数据里日期格式可能同时存在2017-05-01和01.05.2017两种写法直接pd.to_datetime不指定格式会解析失败或产生歧义。先把解析不了的置成NaT统计出失败行数再回头查是哪些格式没覆盖到比一次性强转后报错更可控。缺失值比例的判断标准不固定但一般超过30%的字段就基本告别建模了缺失率低于5%的字段可以考虑填充。类型检查的重点在于预测标签列必须是int或bool时间列必须是datetime64数值列不能是object。这三项确认完数据才算过了第一关。2.3 标签分布不均衡是预测模型的第一道坎刑事犯罪数据里预测目标如果是“案件是否侦破”侦破率通常在20%到40%之间这意味着负样本未侦破可能占多数。这个分布不处理后面模型会学成“全都预测未侦破”也能拿到高准确率的假象。target_col status # 假设1表示已侦破0表示未侦破 value_counts df[target_col].value_counts(normalizeTrue) print(value_counts) # 若类别严重失衡记录一个比例备用 print(正样本占比, round(value_counts.get(1, 0), 4))一句话逻辑先看正样本占比如果低于20%后续建模就必须考虑类别不均衡的处理方式。常见的处理路径有两条——一是用class_weightbalanced让模型自动调整权重二是在评估指标上改成F1分数而不是准确率。这一步不做后面第5章的翻车现场就提前埋下了。3. 特征工程与训练集划分把俄语犯罪记录变成能喂给模型的数值特征3.1 犯罪类型、地区、时间三类特征的编码方式选择刑事犯罪数据集里文本类特征占大头犯罪类型偷窃、抢劫、诈骗等、地区名、街道名。这三类特征的编码方式选择直接决定模型能学到什么信息。犯罪类型通常是低基数的分类变量常见做法是直接用序号编码Label Encoding或独热编码。但如果类别数量在10个以内我更推荐独热编码因为犯罪类型之间没有天然顺序序号编码会给模型传递“类型A大于类型B”的错误信号。地区特征就复杂了——俄罗斯的地区名细分到区级可能上百个全做独热编码会让特征矩阵膨胀得很厉害。我一般会先看地区字段的基数再说# 检查文本特征基数决定编码策略 for col in [crime_type, district, street]: if col in df.columns: print(f{col} 的类别数量{df[col].nunique()}) # 低基数类别独热编码 crime_dummies pd.get_dummies(df[crime_type], prefixcrime) # 中高基数类别按频率编码出现次数映射为数值 district_freq df[district].value_counts() df[district_freq] df[district].map(district_freq)逻辑说明pd.get_dummies对犯罪类型这类低基数字段特别合适生成的是稀疏但可解释的0/1特征。而地区字段用频率编码该地区出现的次数来替代独热既能保留“这个地区案件多不多”的信息又不会把特征维度撑爆。街道名我直接丢弃因为街道级别的粒度太细模型学不透还容易过拟合。时间字段的处理比文本特征容易被忽视。把date拆成年、月、星期几把time拆成小时是预测任务的标准做法。犯罪预测里有个先验夜间街头犯罪和白天入室盗窃的规律完全不同所以hour这种周期性特征建议做环形编码保序的同时还能让模型理解“23点和0点很近”df[hour] df[time].dt.hour df[hour_sin] np.sin(2 * np.pi * df[hour] / 24) df[hour_cos] np.cos(2 * np.pi * df[hour] / 24) df[weekday] df[date].dt.weekday这段代码把小时拆成sin和cos两列代价是特征多了一列收益是模型不会再误以为“22点”和“2点”是两个远到天边的数值。刑事犯罪预测里时间特征是除了犯罪类型之外最重要的信号值得为它多花这点存储空间。3.2 特征相关性筛查与数值标准化特征工程做完之后拿着几十列特征直接扔进模型是一种常见的偷懒方式。特征之间高度相关比如hour和hour_sin在某些区间近乎共线会让部分模型的权重解释变形甚至影响收敛速度。这时候跑一轮相关性矩阵把相关系数超过0.8的特征对挑出来人工判断import numpy as np # 只对数值列做相关性计算 numeric_cols df.select_dtypes(include[np.number]).columns.tolist() corr df[numeric_cols].corr() # 找出高相关特征对 high_pairs [] for i in range(len(corr.columns)): for j in range(i1, len(corr.columns)): if abs(corr.iloc[i, j]) 0.8: high_pairs.append((corr.columns[i], corr.columns[j], round(corr.iloc[i, j], 3))) print(高相关特征对, high_pairs)逻辑说明select_dtypes限定数值列避免把犯罪类型这种编码后的列也拉进来算相关性那没有意义。corr()默认算皮尔逊相关系数适合线性关系判断。如果发现hour_sin和hour_cos没有高相关说明环形编码起作用了发现其他来自同一原始字段的分解列高度相关就考虑只保留其中一列。标准化这一步要不要做取决于你选的模型。逻辑回归和KNN这类基于距离的模型必须做标准化不然后果是量纲大的特征直接主导权重。随机森林和LightGBM这类树模型不用标准化因为它们只在特征值上做比较切分。所以先把要跑的三个模型定下来再决定是否调用StandardScaler。from sklearn.preprocessing import StandardScaler # 只对逻辑回归需要的特征做标准化 scaler StandardScaler() X_scaled scaler.fit_transform(X[[district_freq, victim_age, hour_sin, hour_cos]])参数说明StandardScaler把每列变成均值为0、方差为1的分布fit_transform在训练集上拟合同时完成转换。这里只选了四个量纲不一致的列做标准化像weekday这种取值范围本来就小的列不处理问题不大。注意scaler在训练集上fit完之后预测时要用同一个scaler.transform处理新数据不能重新拟合否则数据分布就错位了。3.3 按时间切分训练集模拟真实预测场景不做随机打乱刑事犯罪预测和电商点击率预测有个共同点数据自带时间属性而你真正要预测的是“未来”。如果把数据集随机打乱再切分模型会看到未来数据去推断过去样本测试集分数虚高上线就翻车。正确姿势是按时间排序后切分。df df.sort_values(date).reset_index(dropTrue) cutoff int(len(df) * 0.8) train df.iloc[:cutoff].copy() test df.iloc[cutoff:].copy() y_train train[target_col] X_train train.drop(columns[target_col, date, time]) y_test test[target_col] X_test test.drop(columns[target_col, date, time])切分逻辑先按日期升序排列前80%做训练集后20%做测试集。这样测试集在时间上永远晚于训练集最大限度模拟真实的上线预测场景。drop掉的date和time是原始时间字段它们已经被拆成了hour_sin、weekday等特征保留原始列只会让模型偷看时间本身。这里踩过真坑有次我图省事直接train_test_split随机切分本地交叉验证F1分数0.72换到按时间切分后直接跌到0.52。差距就是随机切分让同一个月的数据同时出现在训练和测试里模型等于开了上帝视角。刑事犯罪预测这种事关公共安全的场景宁可分数低但真实也不要自欺欺人的高指标。4. 三个源代码怎么用从逻辑回归到随机森林再到LightGBM的递进4.1 模型一逻辑回归基线先跑出及格线不管最终选什么模型第一个代码我都建议先跑逻辑回归。不是因为逻辑回归效果最好而是它训练快、可解释、能给出概率输出天然适合作为基线。刑事犯罪数据集的预测目标如果是“是否侦破”逻辑回归能告诉你每个特征的系数方向——比如“夜间发生”是正系数还是负系数这对业务理解极有帮助。from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, f1_score model_lr LogisticRegression( max_iter1000, class_weightbalanced, C1.0, solverliblinear, random_state42 ) model_lr.fit(X_train_scaled, y_train) y_pred_lr model_lr.predict(X_test_scaled) print(classification_report(y_test, y_pred_lr))参数说明class_weightbalanced是给正样本通常是侦破的案件更高权重自动抵消类别不均衡solverliblinear适合中小规模数据集和二分类场景收敛稳定max_iter1000是防止部分特征量纲差异大导致迭代次数不够——如果收敛警告出现优先调大这个值。C1.0是正则化强度的倒数越小正则化越强先保持默认后续可以按网格搜索再调。逻辑回归跑完看分类报告里三类指标精确率Precision、召回率Recall、F1分数。刑事犯罪预测场景里如果目标是“尽早发现可能侦破的案件”召回率比精确率重要——宁可多预测几个错的也不要漏掉真正能侦破的。基线模型的F1如果能有0.6以上后面两个模型才有提升的空间如果连0.5都不到先回头查特征工程哪里出了问题而不是急着换模型。4.2 模型二随机森林找出特征重要性第二个代码用随机森林目的不是拿它做最终预测而是借助它的feature_importances_属性做特征筛选。随机森林对特征交互和异常值都比较鲁棒即使特征没做标准化也能直接训练适合机器辅助审查特征质量。from sklearn.ensemble import RandomForestClassifier model_rf RandomForestClassifier( n_estimators200, max_depth8, min_samples_leaf10, class_weightbalanced, n_jobs-1, random_state42 ) model_rf.fit(X_train, y_train) # 输出特征重要性并用for循环打印 import numpy as np importances model_rf.feature_importances_ indices np.argsort(importances)[::-1][:15] for i in indices: print(f{X_train.columns[i]}: {importances[i]:.4f})逻辑说明随机森林在训练时会给每个特征计算“减少不纯度的贡献”归一化后就是feature_importances_。n_estimators200提供了足够的树数量去平均掉单棵树的噪声max_depth8限制树深避免对训练集过拟合min_samples_leaf10让每个叶子节点至少有10个样本才能被切分。这组参数适合几千行到几万行的数据集如果数据量更大可以适当加大深度。特征重要性打印出来后重点关注排名靠前的那几个并把重要性几乎为0的列记下来。这里有个常见误区不是直接把重要性低的特征全部删掉而是要看它是否和别的特征高度相关。比如hour_sin和hour_cos重要性都不高但它俩是时间周期的拆分删掉一个会失去完整性。特征筛选的正确姿势是“先删冗余再删无关”重要性只提供参考不做一刀切。4.3 模型三LightGBM做提升用早停控制过拟合第三个代码上LightGBM。它在中小规模数据集上训练速度快对类别型特征也友好是这类表格型预测任务的常见主力模型。LightGBM的直方图算法把特征值离散化成桶内存占用小训练速度比传统GBDT快一个量级缺点是树多了容易过拟合所以早停参数必开。import lightgbm as lgb from sklearn.metrics import roc_auc_score model_lgb lgb.LGBMClassifier( n_estimators500, learning_rate0.05, max_depth6, num_leaves31, subsample0.8, colsample_bytree0.8, class_weightbalanced, random_state42 ) model_lgb.fit( X_train, y_train, eval_set[(X_test, y_test)], eval_metricauc, callbacks[lgb.early_stopping(stopping_rounds50)] ) y_pred_proba_lgb model_lgb.predict_proba(X_test)[:, 1] print(LightGBM AUC:, round(roc_auc_score(y_test, y_pred_proba_lgb), 4))参数说明n_estimators500是树的最大数量但早停在50轮内AUC无提升时会把训练截断所以这个数设大点没关系learning_rate0.05是每棵树的步长调小能让训练更稳定代价是需要的树更多num_leaves31是LightGBM特有的叶子数参数过大容易过拟合一般从31开始调subsample0.8和colsample_bytree0.8分别让每棵树只用80%的样本和80%的特征列起到随机化防过拟合的作用。这段代码里eval_set传的是测试集这是为了早停做监控但要注意早停用的评估集不是用来调参的它更像一个“哨兵”一旦连续50轮验证AUC不再上涨就停。真正的模型效果评估还是要在没被早停看过的独立测试集上做。很多人忽略这一点导致模型多少沾上了测试集的信息泄露。5. 避坑指南刑事犯罪数据集预测的5个典型翻车现场5.1 现象预测准确率95%召回率却是0有次我拿一个犯罪数据集跑完看准确率0.95心里正美点开F1才傻眼——模型把所有案件都预测成“未侦破”因为未侦破案件本身就占95%。准确率被多数类抬起来了但模型对真正关心的正样本一个都抓不到。原因类别不均衡没有处理。训练时模型发现“全预测为多数类”的损失最小就选了这条偷懒路径。解决检查标签分布正样本占比低于20%时在模型里设置class_weightbalanced或者用SMOTE做少数类过采样更关键的是评估指标改成F1和AUC别再看准确率。刑事犯罪预测场景里准确率是典型的误导性指标F1才是跟业务对齐的度量。5.2 现象时间字段解析报错程序直接崩溃pd.to_datetime(df[date])跑出一堆ParserError原因多半是数据集里日期格式混着ISO写法和俄式日.月.年写法。俄罗斯数据里05.11.2017表示的到底是5月11日还是11月5日是个老坑。原因日期格式不统一pandas默认解析器无法自动区分日月的先后顺序。解决读入CSV时就指定parse_dates并带上格式参数。更稳妥的做法是用errorscoerce先解析一遍解析失败的行单独打印出来看格式再根据不同格式分段转换最后合并回原列。不要嫌麻烦时间字段是后续所有时间切分和周期特征的基础这里省事后面全是洞。5.3 现象独热编码后特征矩阵爆炸内存直接吃不消地区字段细到区级有上百个取值我图省事一把pd.get_dummies全转成01列数据量一大内存先告急训练速度也慢成幻灯片。原因高基数分类特征用独热编码维度随类别数线性膨胀。一百个地区就是一百列虽然每一列都是稀疏的但存储和计算成本都上去了。解决高位基数特征改用频率编码或者目标编码。频率编码把每个地区的案件数映射成该地区的“活跃度”一列解决战斗。目标编码用该地区正样本比例做特征信息量更大但容易过拟合需要配合交叉验证使用。我在这类数据上一般先用频率编码信息不够再尝试目标编码。5.4 现象测试集分数高得离谱上线后预测却失灵模型在验证集上AUC到0.85落地到新数据上直接掉回0.5基本等于瞎猜。排查到最后发现原因是数据按时间收集随机切分让训练和测试集混着同一时期的案件。原因时间泄露。模型见过了未来的数据在测试集上等于开卷考试一到真实预测未来是不可见的就原形毕露。解决切分数据时先按时间排序再切测试集必须时间上晚于训练集。如果数据集跨度大还可以按年份做分组交叉验证每次用过去年份预测未来年份这样评估出来的指标才是真实可上线的水平。5.5 现象三个源代码直接跑文件不存在报错解压完看到代码和数据集直接python train.py结果提示找不到CSV文件。查了半天原来代码里写的文件名是crime_clean.csv实际解压出来叫crime_data_clean.csv一个下划线的差异就能卡半天。原因代码里的文件名和实际解压的文件名不一致或者路径拼接时没有考虑解压目录的层级。解决拿到压缩包先ls -la或者find列全文件再打开代码文件逐个核对引用路径。遇到路径不一致不改代码直接把文件重命名成代码预期的名字或者改代码里的路径。这一步花五分钟能省下面排错两小时。顺带提一句代码里如果有/content/这类路径那是作者当时跑的环境本地要改成相对路径才能跑通。6. 最后一步用混淆矩阵和分类报告验证结果再做一次最小模型部署模型跑完不是终点“预测结果可信”才是。我习惯用混淆矩阵收尾——四个格子里藏着一半的模型诊断信息业务人员看着也直观。同时把训练好的模型存成本地文件写一个几行的预测函数这样“训练”和“使用”才算真正闭环。import pandas as pd import numpy as np from sklearn.metrics import confusion_matrix, classification_report, ConfusionMatrixDisplay import matplotlib.pyplot as plt import joblib # 1. 混淆矩阵和分类报告 cm confusion_matrix(y_test, y_pred_lgb) disp ConfusionMatrixDisplay(confusion_matrixcm) disp.plot(cmapBlues) plt.savefig(confusion_matrix.png, dpi150) print(classification_report(y_test, y_pred_lgb)) # 2. 把训练好的模型存成文件留作预测用 joblib.dump(model_lgb, crime_predict_model.joblib) joblib.dump(scaler, crime_scaler.joblib)这段代码前半段画混淆矩阵后半段用joblib把模型和标准化器打包落盘。joblib.dump是sklearn生态里序列化模型的标准方式比pickle对大对象的处理更高效。存下来的这两个文件就是后续做预测的最小资产。预测函数的最小闭环也很简单新数据进来走一遍和训练时完全相同的预处理——缺失值填充、时间字段拆分、特征编码映射然后加载模型直接出概率。def predict_crime_status(new_record): # 按训练时同样的路径转换时长拆字段、编码映射 X_new preprocess(new_record) # 假设preprocess复用训练时的特征工程 prob model_lgb.predict_proba(X_new)[:, 1] return float(prob[0]) # 调用时返回的就是“案件侦破概率”业务侧自行设定阈值 prob predict_crime_status(new_case)这里的阈值怎么定才是刑事犯罪场景真正见水平的地方。逻辑回归和LightGBM输出的是概率默认0.5的分类边界在类别不均衡时其实不适用。我的一般做法是找出测试集上“召回率达到0.7对应的概率阈值”用这个值做上线判定。比如0.3那概率超过0.3就标记为预测侦破宁可维持一定误报率也不漏掉真正可侦破的案件。整个项目做到这里你已经把一份陌生的犯罪数据集走成了“清洗→特征→建模→验证→部署”的完整闭环。这类AI实战项目的价值也正在于此数据集不是拿来收藏的源代码不是拿来跑通就扔的你把每一步的“为什么”想清楚下次换一份数据集、换个预测目标照样能做下来。我自己最深的体会是模型选哪个其实无所谓数据质量、标签定义和评估口径这三件事才是这类预测项目真正的胜负手。希望这份拆解能帮你在自己的数据集上少走两步弯路。本文还有配套的精品资源点击获取