ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

俄罗斯刑事犯罪数据预测实战:从CSV到随机森林与KMeans的完整链路

俄罗斯刑事犯罪数据预测实战:从CSV到随机森林与KMeans的完整链路 简介面向机器学习与数据分析初学者的俄罗斯刑事犯罪数据集分析预测实战包覆盖犯罪趋势可视化、酒精与犯罪回归建模、犯罪率预测三个典型任务可帮助学习者完整走通数据清洗、特征工程、模型训练与评估流程。压缩包共5个文件包含3个Python源代码、1个readme说明与1个589.70 KB的CSV数据集整体约107KB数据为2008—2023年俄罗斯犯罪记录源码分别实现犯罪趋势分析、酒精消费与犯罪回归分析、随机森林预测及KMeans聚类并配有plotly交互可视化与回归诊断代码readme可作为快速上手指引。已有56人学习。借助该资源可掌握pandas、numpy、seaborn、sklearn、scipy等工具在真实数据中的综合运用学会将回归、聚类与可视化结合解决业务问题尤其适合课程设计、期末项目、竞赛练手或机器学习入门后的综合实战便于对照运行结果加深理解。1. 俄罗斯刑事犯罪数据集分析预测一张16年CSV串起四条机器学习链路把一份俄罗斯2008到2023年的犯罪统计表丢给刚学完机器学习的人大部分人第一步是画个折线图就收工。这份俄罗斯刑事犯罪数据集分析预测实例显然不想停留在这一步它把趋势可视化、酒精消费与犯罪回归、随机森林预测、KMeans聚类全部串进3个可运行的Python源代码里从pandas清洗一路走到sklearn建模和plotly交互出图。数据不花哨但正好覆盖了一条完整分析流水线的每个节点。适合两类人一是刚学完pandas和sklearn、手里缺真实数据练手的开发者二是要交作业或做数据分析汇报、希望代码开箱即用的技术人员。它解决的核心问题不是某个算法难题而是“拿到一张时间序列表格后怎么有条理地做出结论”。2. 数据集与脚本分工动手前先探清CSV结构和三个源码的边界这套资源最容易被低估的地方是那张CSV。很多人解压后第一件事是直接跑脚本跑通了就觉得完事却没弄明白数据长什么样、三个脚本为什么这么分工。这一章我把拿到资源后的完整探查过程写出来。2.1 先跑一段探查代码别急着画图解压后能看到一个readme.txt、三个py文件和一份名为“The number of crimes in Russia 2008-2023.csv”的数据文件。readme写得比较克制没有交代行列结构、编码方式这种时候我的习惯是先把表格的shape、列名、缺失情况摸清楚再决定怎么给脚本传参。import pandas as pd df pd.read_csv(The number of crimes in Russia 2008-2023.csv, encodingutf-8) print(shape:, df.shape) print(columns:, df.columns.tolist()) print(df.head(10)) print(df.info()) print(df.describe())这段代码做了四件事shape告诉你这张表有多少行多少列columns列出全部字段名head(10)让你直观看到头部记录长什么样info()则一次性暴露每列的数据类型和非空数量。describe()只对数值列生效输出均值、标准差、最小最大值是判断量级最直接的途径。这里的编码参数值得专门说一句。我遇到过一份东欧国家的时间序列表readme没写编码用默认utf-8读出来全是乱码换成cp1251之后一切正常。所以第一遍读取如果看到奇怪的字符优先怀疑的不是文件坏了而是编码不匹配把encoding换成latin1或cp1251再试一次即可。从文件大小推算这份数据大概率不是简单的16行年度汇总而是按地区拆分的细粒度表。俄罗斯联邦有85个左右联邦主体16年乘85个地区长表格式下大约有一千多行这个体量和文件大小吻合。你实际跑出来的shape以输出为准我在这里只是提示一个判断方向如果Year列有大量重复值那就必须做聚合再画趋势图否则折线图会被地区维度搅乱。2.2 三个源代码脚本的分工与依赖关系readme里三个脚本的命名已经说明了它们各自的定位我按执行顺序把功能和依赖拆成一张表脚本核心任务主要依赖1-Crime Trends Analysis in Russia 20082023.py绘制2008-2023年犯罪数量和犯罪率的趋势图静态图与交互图并出pandas、seaborn、matplotlib、plotly2-Alcohol and Crime Regression Analysis.py分析酒精消费量指标与犯罪数量之间的相关性做回归拟合与优度评估scipy.stats、scipy.optimize.curve_fit、sklearn.metrics.r2_score3-The trends in crime rates in Russia.py在犯罪率维度上做特征工程、随机森林预测并附带聚类分组sklearn整套、numpy、plotly、urllib、json三个脚本的递进关系很清晰脚本1回答“犯罪数量16年怎么变”脚本2回答“哪些因素和犯罪数量相关”脚本3回答“能不能用历史数据预测未来趋势”。很多初学者会跳过前两个直接跑第三个结果就是连Crime_Count和Crime_Rate两个字段的区别都没搞清楚就开始调参后面对模型输出自然无法解释。模块清单里出现了urllib.request.urlopen和json说明第3个脚本不只是读本地CSV还从外部公开接口抓过补充数据。最常见的做法是用年度人口数据把犯罪总数换算成每10万人犯罪率这样不同年份之间才可比——人口总量变化会直接影响犯罪绝对数不看人均只看总数容易得出错误结论。这个细节后面讲预测时还会提到。2.3 模块选型理由为什么是随机森林、curve_fit和KMeans组合看到一个项目同时用RandomForestRegressor、scipy.optimize.curve_fit和KMeans第一反应可能是“堆了一堆库”实际上这三个东西解决的问题完全不同。随机森林负责预测核心优势是表格数据上不需要做大量特征工程能自动捕捉非线性关系对这份只有十几个特征的小样本数据集非常稳。curve_fit负责解释性分析酒精消费和犯罪数量之间如果存在函数关系它能拟合出具体的表达式参数这是黑匣子模型给不了的。KMeans则做无监督分组把年份或地区按特征相似度聚成几类用聚类结果反过来验证监督模型的划分是否合理。选型逻辑里最值得学习的是“够用就好”。这份数据规模很小用深度学习纯属杀鸡用牛刀线性回归又难以刻画非线性波动随机森林处在两者之间训练快、可复现、带特征重要性还能扛住小样本下的过拟合风险。理解这个取舍比记住某个具体API重要得多。3. 犯罪趋势分析实战Seaborn静态图与Plotly交互图的配合方式脚本1是整个项目的入口它做的事情本质上只有一件把16年的犯罪数量和犯罪率变化讲清楚。但同一个结论静态图和交互图的表达侧重点完全不同脚本里两个都给了。3.1 年度聚合与Seaborn趋势线如果原始表是按地区拆分的第一步必须做年度聚合否则画出来的是85条纠缠在一起的细线什么结论都读不出来。import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import warnings warnings.filterwarnings(ignore) df pd.read_csv(The number of crimes in Russia 2008-2023.csv) df[Year] df[Year].astype(int) yearly df.groupby(Year, as_indexFalse)[Crime_Count].sum() sns.set_style(whitegrid) fig, ax plt.subplots(figsize(11, 5)) sns.lineplot(datayearly, xYear, yCrime_Count, markero, linewidth2.2, color#C62828, axax) ax.set_title(Crime Count in Russia 2008-2023) ax.set_xlabel(Year) ax.set_ylabel(Number of Crimes) plt.tight_layout() plt.show()groupby(Year, as_indexFalse)把相同年份的所有地区记录汇总as_indexFalse保证年份保留为列而不是变成索引方便后续传给seaborn。markero在每年数据点上画实心圆适合样本点只有16个的时间序列——点线结合能直接看出每年间的跳变。linewidth2.2控制线宽color#C62828是深红色深色的主趋势线在白色网格背景下辨识度最高。warnings.filterwarnings(ignore)是脚本里常见的一行作用是屏蔽seaborn和sklearn版本升级时抛出的DeprecationWarning避免控制台被无关信息刷屏。如果你跑脚本时发现某些警告信息指向sklearn的旧API别急着改代码先看警告级别DeprecationWarning不影响当前运行。这张图画完核心结论已经出来了俄罗斯犯罪绝对数量在这16年里整体下行中间存在明显波动。看到这样的趋势正常的下一步不是急着做预测而是问一句“为什么”这就把接力棒交到了脚本2手上。3.2 Plotly交互图与HTML导出静态图适合写进报告但数据密的地方想看具体某年数值就得靠交互图。脚本里用plotly.express实现的版本我一般会这样写import plotly.express as px fig px.line(yearly, xYear, yCrime_Count, titleCrime Count in Russia (Interactive), markersTrue) fig.update_traces(linedict(width3, color#C62828)) fig.write_html(crime_trend.html) fig.show()px.line一行就能生成带悬浮提示的交互折线图鼠标悬停后自动显示年份和数值。write_html会把整个图打包成一个独立HTML文件这个文件可以直接发给不会跑Python的同事或客户对方用浏览器打开就能看、能放大、能悬停读数这是静态图做不到的。这里有一个我在Jupyter环境里反复踩过的坑fig.show()在有些环境下只会输出一行文字图片不渲染。这不是代码问题而是plotly的renderer没配好。排查顺序是先确认你用的是Jupyter Notebook还是Jupyter Lab然后看plotly版本是否匹配不想折腾环境的话直接改成fig.write_html(crime_trend.html)然后浏览器打开绕开renderer问题。3.3 数量与率两套图别混着看脚本1画的是犯罪数量脚本3画的是犯罪率这两个维度在视觉趋势上可能差很多。犯罪数量下降可能只是因为人口减少人均犯罪率未必同步下降。所以我拿到这张表之后习惯把两张图放在一起对比如果数量下降但犯罪率上升说明社会安全状况实际在恶化只看总数会得出完全相反的结论。脚本1给的是数量层面的趋势打好底脚本3再在犯罪率维度上补一层两个脚本互为补充而不是重复建设。4. 酒精与犯罪回归分析相关、拟合和R²的解读边界脚本2把注意力从“随时间怎么变”转移到“和什么相关”。俄罗斯的禁酒政策与犯罪率之间的关系是社会学里被研究很多的话题这份数据集里带了酒精消费相关字段正好支撑这个分析。4.1 Pearson相关系数与显著性检验相关性分析最容易犯的错误是只算一个相关系数就下结论忽略p值。脚本里用scipy.stats计算相关性的部分我一般会写成这样from scipy.stats import pearsonr import pandas as pd df pd.read_csv(The number of crimes in Russia 2008-2023.csv) sub df[[Alcohol_Liters, Crime_Count]].dropna() r, p pearsonr(sub[Alcohol_Liters], sub[Crime_Count]) print(fPearson r {r:.4f}, p-value {p:.2e})pearsonr返回两个值相关系数r和p值。r的范围在-1到1之间正数表示正相关负数表示负相关绝对值越接近1线性关系越强。p值回答的是“这个相关性能不能排除随机性”行业里常用的判断线是0.05——p小于0.05才认为统计显著否则即使r算出来0.4也不能当真。sub df[[Alcohol_Liters, Crime_Count]].dropna()这一步非常关键pearsonr不接受含NaN的数组如果原始数据某个年份的酒精消费字段是空的不dropna直接跑会报错或者算出一个错误结果。dropna()按行删除缺失值删除前可以先看一眼sub.shape确认删掉了多少行如果删掉的比例超过10%就得回头检查原始数据采集是否有问题。俄罗斯的实际背景是2010年前后出台了一系列限制酒精销售的政策同期犯罪率出现明显变化。如果你跑出来的相关系数为正且p值很小只能说明两个变量在统计上存在正向关联不能直接写成“酒精导致了犯罪”——这个因果性判断需要政策干预节点、滞后效应等更多证据支撑脚本里只到相关性为止是合理的边界。4.2 curve_fit非线性拟合与R²评估相关分析回答“有没有关系”回归拟合回答“关系长什么样”。脚本2里用scipy.optimize.curve_fit拟合的代码我复现时这样处理import numpy as np from scipy.optimize import curve_fit from sklearn.metrics import r2_score x sub[Alcohol_Liters].values y sub[Crime_Count].values def linear_fit(x, a, b): return a * x b popt, pcov curve_fit(linear_fit, x, y) a, b popt y_pred linear_fit(x, a, b) print(fy {a:.2f} * x {b:.2f}) print(R2:, r2_score(y, y_pred))curve_fit的第一个参数是自定义函数第二个和第三个分别是自变量和因变量数组。popt是拟合得到的最优参数列表在这里对应a和bpcov是参数协方差矩阵对角线元素开根号就是参数的标准差标准差过大说明拟合不稳定。r2_score计算决定系数取值范围0到1代表模型解释了因变量百分之多少的方差。这里有一个新手很容易误解的点curve_fit本身不返回R²需要自己调用sklearn.metrics.r2_score来计算。脚本模块清单里同时出现scipy.optimize.curve_fit和sklearn.metrics.r2_score正是因为这个分工——前者做参数拟合后者做拟合优度评估。如果把R²高达0.9理解为“找到了因果机制”那就过度解读了R²只描述拟合程度不描述机制。4.3 回归诊断看一眼残差再信结论拟合跑完别急着截图我一般会再补一个残差检查把回归的残差算出来按年份画散点图。如果残差在某个时间段内系统性为正、另一个时间段内系统性为负说明模型存在结构性问题可能漏掉了关键变量或非线性关系。这个步骤脚本不一定写了但对解释“为什么酒精和犯罪的相关性在特定年份断开”很有帮助。残差分析的代码很简单residual y - y_pred然后按年份做个散点图观察是否随机分布在0轴两侧。5. 随机森林预测与常见问题排查时间序列切分、MSE量纲和可复现性脚本3是全项目技术含量最高的一块它把犯罪率预测当成一个标准的监督学习问题处理。但这里藏着一个几乎所有初学者都会踩的暗坑时间序列数据能不能直接用train_test_split随机切分。5.1 数据划分与随机森林建模先给出一版可以跑的建模代码再解释为什么划分方式在时间序列场景下要格外谨慎from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error, r2_score X sub[[Alcohol_Liters, Year]].values y sub[Crime_Count].values X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model RandomForestRegressor( n_estimators300, max_depth4, min_samples_leaf3, random_state42, n_jobs-1 ) model.fit(X_train, y_train) y_pred model.predict(X_test) mse mean_squared_error(y_test, y_pred) print(MSE:, mse) print(R2:, r2_score(y_test, y_pred)) print(Feature importance:, model.feature_importances_)几个参数的实际含义值得展开。n_estimators300表示建300棵决策树树越多预测越稳定但超过一定数量后收益递减300在这个样本量下是性价比比较高的值。max_depth4限制每棵树的最大深度防止单棵树记住训练集噪声——深度越大越容易过拟合4层对十几个特征来说已经够用。min_samples_leaf3强制叶子节点至少3个样本进一步抑制过拟合。random_state42同时出现在train_test_split和模型里是一个好习惯固定随机种子后每次运行结果完全一致否则同样的代码跑两次得到不同R²排查问题时会被随机性引入死胡同。n_jobs-1让模型用满所有CPU核心300棵树的训练时间能明显缩短。feature_importances_输出每个特征对预测的贡献度。这一行信息经常被忽略实际上它价值极高如果酒精消费量的重要性远高于年份说明犯罪率的年度变化主要由消费习惯驱动如果年份占主导说明趋势性因素才是关键。看重要性再决定后续特征工程方向比盲目堆特征靠谱得多。5.2 MSE量纲陷阱为什么数值大得吓人脚本第一次跑完MSE输出可能是几十万甚至上百万。很多初学者看到这个数字直接判定模型失败其实这是量纲问题因变量Crime_Count是绝对数量俄罗斯全国年度犯罪数量在几十万这个量级误差平方后自然变成百万级别。MSE不是百分比指标它的大小完全取决于因变量的单位。更直观的做法是看RMSE也就是mean_squared_error开根号单位回到原始量纲比如“平均偏差约8000起”马上能理解模型精度。另一个更稳的做法是把预测目标从犯罪总数换成每10万人犯罪率这样数值落在几百到几千的区间MSE就不会夸张到让人误判模型失效。这也是脚本3专门用urllib拉人口数据的原因——换成犯罪率后模型训练目标本身的数值分布合理得多。5.3 避坑清单五个复现时容易翻车的细节这一节把我在复现这套代码时实际遇到的坑按“现象、原因、解决”列出来照着排查能省不少时间。第一个坑是R²奇高但预测曲线完全不符合业务直觉。有一次我跑出来的R²是0.98但预测值在最后几年出现明显倒挂后来发现是train_test_split随机切分导致测试集里混进了“未来”的数据——随机森林记住了时间趋势测试集年份和训练集年份重叠等于开卷考试。解决方法是按年份顺序切分比如前80%的年份作训练集、后20%作测试集或者用sklearn.model_selection.TimeSeriesSplit做交叉验证。第二个坑是MSE输出几十万直接慌了神。原因上面已经说过绝对数量和平方误差双重放大导致数值无参考意义。解决方法是同时输出RMSE和R²如果还不放心就把目标字段换成犯罪率再训练一版对比。第三个坑是构造滞后特征后模型整体崩溃或早期预测全是空值。犯罪数据天然适合用前一年的值预测后一年但df[Lag_1] df[Crime_Count].shift(1)会让第一行变成NaN如果不处理整条特征列带着空值喂给随机森林结果不可控。解决方法是dropna()丢掉头部的空行或用fillna(methodffill)用后一值填充前者更干净。第四个坑是KMeans聚类结果两次运行不一致。KMeans的初始质心是随机选择的不固定种子每次出来的分组都不同。解决方法是固定random_state并把n_init设成10或更高n_init表示用多少个不同初始质心跑完取最优能明显降低落入局部最优的概率。第五个坑是plotly图形在Jupyter里不显示只看到一行文本。原因是renderer配置和当前环境不匹配排查顺序是先跑plotly.io.renderers.default看当前默认渲染器再确认你是不是在Jupyter Lab里用了不兼容的版本。不想折腾的话直接用fig.write_html(crime_trend.html)导出后用浏览器打开速度最快。6. KMeans聚类验证用无监督方法给预测结果兜底随机森林给出预测值之后这套流程似乎已经完整了。但其实还有一个值得做的验证步骤用KMeans对特征空间做无监督分组看聚类结果和监督模型的预测逻辑是否互相印证。如果随机森林说某一年犯罪率会高而聚类也把这一年归到“高风险”组结论的可信度就更高。6.1 标准化处理与肘部法则聚类正式跑之前必须先做标准化。Alcohol_Liters和Crime_Count两个字段的量级差可能超过百倍不标准化的话欧氏距离会被大数量字段完全主导聚类结果等于没做。from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans import matplotlib.pyplot as plt features sub[[Alcohol_Liters, Crime_Count]].values scaler StandardScaler() X_scaled scaler.fit_transform(features) kmeans KMeans(n_clusters3, n_init10, random_state42) sub[Cluster] kmeans.fit_predict(X_scaled) print(sub.groupby(Cluster).mean())StandardScaler先fit计算每列的均值和标准差再transform把数据映射成均值为0、标准差为1的分布。fit_predict一步完成训练和标签分配返回的Cluster列可以直接拼回DataFrame做分组统计。n_clusters3不是乱设的我习惯先用肘部法则确认inertia [] for k in range(1, 8): m KMeans(n_clustersk, n_init10, random_state42) m.fit(X_scaled) inertia.append(m.inertia_) plt.plot(range(1, 8), inertia, markero) plt.xlabel(k) plt.ylabel(inertia) plt.show()inertia_是簇内误差平方和k从1增加到7看曲线哪个位置出现明显的拐点那个k就是自然的分组数。拐点之后误差下降速度明显变缓继续增加k只是把数据切得更碎没有实际意义。这个方法不完美但配合业务背景——比如把俄罗斯的年份分成“高发期、过渡期、低位期”三段——基本够用。6.2 一个更稳的验证习惯聚类跑完之后我通常会把每个簇对应的年份打印出来和随机森林的特征重要性对照着看如果重要性最高的特征恰好是区分簇间差异最大的那个字段监督和无监督两条路就互相验证了。这一步代码很少但对结论的信心提升是实打实的。这套流程走过来最大的感受是真正增加价值的不是某个模型的精度而是每一步都问一句“这个结论能被另外一种方法验证吗”。从那以后我每次拿到没摸过的数据集都强制走一遍“探结构、画趋势、做相关、跑模型、聚类兜底”的完整流程遇到问题先查切分方式和量纲再怀疑模型本身。这套习惯帮我少走了很多弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表