数据挖掘入门项目二手交易车价格预测之数据分析

news2024/10/2 10:24:55

文章目录

  • 1. 相关库的引入
  • 2. 数据的加载
  • 3. 数据概况
    • 3.1 统计值查看
    • 3.2 查看数据类型
  • 4. 判断缺失值
    • 4.1 统计每一列空值的数量
    • 4.2 可视化缺失值数量
  • 5. 判断异常值
    • 5.1 异常值检测
  • 6. 了解预测值的分布
    • 6.1 统计各预测值的分布
    • 6.2 总体分布概况
    • 6.2 查看预测值的具体频数
    • 6.3 查看skewness(偏度) and kurtosis(峰度)
  • 7. 特征分析
    • 7.1 统计类别特征的值和数量
    • 7.2 类别特征箱形图可视化
    • 7.3数字特征分析
    • 7.4 每个数字特征得分布可视化
    • 7.5 数字特征相互之间的关系可视化
    • 7.6 多变量之间的关系可视化
  • 8. 用ydata_profiling生成数据报告
  • 9. 总结

数据集网址:https://tianchi.aliyun.com/competition/entrance/231784/information
参考了:Datawhale 零基础入门数据挖掘,更多详情在https://tianchi.aliyun.com/notebook/95457

1. 相关库的引入

import warnings # #导入warnings包,利用过滤器来实现忽略警告语句。
warnings.filterwarnings('ignore')
import pandas as pd # 
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import missingno as msno
  • pandas库:Pandas是数据分析三大剑客之一,是Python的核心数据分析库,它提供了快速、灵活、明确的数据结构,能够简单、直观、快速地处理各种类型的数据
  • numpy库:是Python科学计算中最重要的库之一,其主要目的是提供高效的数组和矩阵操作,为科学计算提供一个强大的基础工具库
  • matplotlib库:是一个用于绘制图表和可视化数据的 Python库。它提供了丰富的绘图工具,可以用于生成各种静态、交互式和动画图表
  • seaborn库:是一个基于matplotlib的Python数据可视化库,提供了高级的统计图形和美观的绘图风格,使得数据可视化更加简单和直观。它内置了许多常见的图表类型和颜色主题,能够帮助我们快速地创建漂亮且具有统计意义的图形
  • missingno库:用于可视化和分析数据中的缺失值。它提供了简单而直观的方法来理解数据集中缺失值的分布和模式

2. 数据的加载

train_data=pd.read_csv(train_path,sep=' ')
test_data=pd.read_csv(test_path,sep=' ') # 列与列之间是通过空格分隔的

查看数据形状:

train_data.shape

查看数据前几行和后几行,对数据大致有一个了解

train_data.head()
train_data.tail()

3. 数据概况

3.1 统计值查看

查看每一列的个数count、平均值mean、方差std、最小值min、中位数25% 50% 75% 、以及最大值。看这个信息主要是瞬间掌握数据的大概的范围以及每个值的异常值的判断

train_data.describe()

在这里插入图片描述

3.2 查看数据类型

train_data.info()

返回了列名,每一列非空值数量以及列的类型
在这里插入图片描述

4. 判断缺失值

4.1 统计每一列空值的数量

train_data.isnull().sum()

在这里插入图片描述

4.2 可视化缺失值数量

missing = Train_data.isnull().sum()  #统计缺失值
missing = missing[missing > 0]  # 找到有缺失值的列
missing.sort_values(inplace=True) # 按照缺失值数量排序
missing.plot.bar() # 柱状图显示

排序后的结果:
在这里插入图片描述
可视化:
在这里插入图片描述

分析: 通过以上两句可以很直观的了解哪些列存在 “nan”, 并可以把nan的个数打印,主要的目的在于 nan存在的个数是否真的很大,如果很小一般选择填充,如果使用lgb等树模型可以直接空缺,让树自己去优化,但如果nan存在的过多、可以考虑删掉

  • 用missingno库可视化缺失值

矩阵图展示:空白越多说明缺失越严重

msno.matrix(train_data)

在这里插入图片描述
柱状图展示:

msno.bar(train_data)

在这里插入图片描述

5. 判断异常值

5.1 异常值检测

train_data.info() # 查看结果发现发现除了notRepairedDamage 为object类型其他都为数字,猜测notRepairedDamage这一列中有异常值
train_data['notRepairedDamage'].value_counts() # 统计notRepairedDamage这一列的值及其对应的数量

在这里插入图片描述
notRepairedDamage的含义是汽车是否有损坏,-也是缺失值,鉴于很多模型对nan都有处理,这里先不做处理,转换成nan

train_data['notRepairedDamage'].replace('-',np.nan,inplace=True) # inplace=True表示在原始数据上直接操作
  • 对于其他的列,同理我们使用value_counts()查看其列值的分布,查看是否有异常,比如seller列:

可以看到seller列值这两个类别特征严重倾斜,一般不会对预测有什么帮助,故可以先删掉,当然你也可以继续挖掘,但是一般意义不大

train_data['seller'].value_counts()

在这里插入图片描述
还有offerType列,所有值都一样,故也不会对结果产生什么影响,可以删去

train_data['offerType'].value_counts()

在这里插入图片描述

删除列:

del train_data['seller']
del train_data['offerType']

6. 了解预测值的分布

6.1 统计各预测值的分布

train_data['price'].value_counts()

6.2 总体分布概况

分别用无界约翰逊分布,正态分布、对数正态分布来拟合我们的数据
无界约翰逊分布拟合:

# Scipy是一个用于科学计算的Python库,它提供了许多用于数值计算、优化、插值、统计和信号处理的功能
import scipy.stats as st 

plt.figure(1) # 创建一个新的图形窗口
plt.title('Johnson SU') # 设置图形的标题为 'Johnson SU'
# 函数绘制概率密度分布图
sns.distplot(y, kde=False, fit=st.johnsonsu)
# y: 这是要绘制分布的数据。
# kde=False: 禁用核密度估计曲线,只绘制直方图。
# fit=st.johnsonsu: 在这里,st.johnsonsu 表示使用 Johnson SU 分布进行拟合,下图中的曲线就是Johnson SU 分布拟合的

在这里插入图片描述

补充:无界约翰逊分布(Unbounded Johnson Distribution)是一种统计概率分布,它是有界约翰逊分布的一种扩展,用于建模不受限制的随机变量。这种分布的特点是能够适应多种分布形状,包括对称、右偏和左偏的形状。约翰逊分布是通过对原始随机变量进行适当的变换,使其服从正态分布。无界约翰逊分布是指这种变换没有对随机变量的范围(上下界)进行限制,因此是“无界”的

正态分布拟合:

plt.figure(1)
plt.title('Normal')
sns.distplot(y, kde=False, fit=st.norm)

可以看出正态分布拟合的效果不怎么好
在这里插入图片描述
对数正态分布拟合:

plt.figure(1)
plt.title('Normal')
sns.distplot(y, kde=False, fit=st.lognorm)

效果也还行
在这里插入图片描述

分析:价格不服从正态分布,所以在进行回归之前,它必须进行转换。虽然对数变换做得很好,但最佳拟合是无界约翰逊分布

6.2 查看预测值的具体频数

plt.hist(train_data['price'], orientation = 'vertical',histtype = 'bar', color ='orange')
plt.show()
# orientation='vertical': 这个参数指定了直方图的方向。在这里,设置为 'vertical' 表示垂直方向的直方图。
# histtype='bar': 这个参数指定了直方图的类型。在这里,设置为 'bar' 表示使用条形(bar)来表示直方图。
# color ='orange': 这个参数指定了直方图的颜色。在这里,设置为 'red' 表示直方图的颜色为红色。
# plt.show(): 这个函数用于显示图形。在 Matplotlib 中,你需要调用 plt.show() 才能实际显示图形。

在这里插入图片描述

  • 查看频数, 大于20000得值极少,其实这里也可以把这些当作特殊得值(异常值)直接用填充或者删掉,再前面进行
  • log变换 z之后的分布较均匀,可以进行log变换进行预测,这也是预测问题常用的trick
plt.hist(np.log(train_data['price']), orientation = 'vertical',histtype = 'bar', color ='orange')
plt.show()

在这里插入图片描述

6.3 查看skewness(偏度) and kurtosis(峰度)

■ 偏度(skewness),是描述数据分布形态的统计量,其描述的是某总体取值分布的对称性,简单来说就是数据的不对称程度,正态分布的偏度为0
■ 峰度又称峰态系数。偏度是描述某变量所有取值分布形态陡缓程度的统计量,简单来说就是数据分布顶的尖锐程度。峰度为0表示该总体数据分布与正态分布的陡缓程度相同;峰度 >0表示该总体数据分布与正态分布相比较为陡峭,为尖顶峰

train_data.skew()
train_data.kurt()

偏度可视化:

sns.distplot(train_data.skew(),color='blue',axlabel="skewness")

在这里插入图片描述
峰度可视化:

sns.distplot(train_data.kurt(),color='green',axlabel="kurtosis")

在这里插入图片描述

7. 特征分析

特征分为类别特征和数字特征,一般分开进行分析
这里需要根据实际意义人为来区分:

y_train=train_data['price'] # 把价格标签摘出来
# 数字特征
numeric_features = ['power', 'kilometer', 'v_0', 'v_1', 'v_2', 'v_3', 'v_4', 'v_5', 'v_6', 'v_7', 'v_8', 'v_9', 'v_10', 'v_11', 'v_12', 'v_13','v_14' ]
# 分类特征
categorical_features = ['name', 'model', 'brand', 'bodyType', 'fuelType', 'gearbox', 'notRepairedDamage', 'regionCode',]

7.1 统计类别特征的值和数量

nunique()可以计算每个列的类别数量

for cat in categorical_features:
    print("{}的特征分布如下:".format(cat))
    print("{}特征一共有{}个值,每个值的数量如下:".format(cat,train_data[cat].nunique()))
    print(train_data[cat].value_counts())

7.2 类别特征箱形图可视化

■ 箱型图:箱形图(也称盒图,箱线图等),因为形状长得像一个箱子而得名。它是用于显示一组数据分散情况的统计图,可以通过这种图直观的探索数据特征。
■ 箱线图判断异常值的标准以四分位数和四分位距为基础,四分位数具有一定的耐抗性,多达25%的数据可以变得任意远而不会很大地扰动四分位数,所以异常值不会影响箱形图的数据形状。箱线图识别异常值的结果比较客观,能够准确稳定地描绘出数据的离散分布情况,同时也利于数据的清洗。
■ 补充:
①箱线图是针对连续型变量的,解读时候重点关注平均水平、波动程度和异常值。
②当箱子被压得很扁,或者有很多异常的时候,试着做对数变换。
③当只有一个连续型变量时,并不适合画箱线图,直方图是更常见的选择。
④箱线图最有效的使用途径是作比较,配合一个或者多个定性数据,画分组箱线图

# 因为 name和 regionCode的类别太稀疏了,这里我们把不稀疏的几类画一下
categorical_features = ['model',
 'brand',
 'bodyType',
 'fuelType',
 'gearbox',
 'notRepairedDamage']
for c in categorical_features:
	# 将每个分类特征的数据类型转换为 Pandas 中的 'category' 类型
    train_data[c] = train_data[c].astype('category')
    if train_data[c].isnull().any():
    	# 如果包含缺失值,则新增一个类别'MISSING'作为缺失值
        train_data[c] = train_data[c].cat.add_categories(['MISSING'])
        # 用'MISSING'填充缺失值
        train_data[c] = train_data[c].fillna('MISSING')
# 自定义的箱线图绘制函数
def boxplot(x, y, **kwargs):
    sns.boxplot(x=x, y=y)
    x=plt.xticks(rotation=90) # 将 x 轴标签旋转90度,以便更好地显示分类特征的名称

f = pd.melt(train_data, id_vars=['price'], value_vars=categorical_features)
g = sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False)
g = g.map(boxplot, "value", "price")

在这里插入图片描述
还可以使用小提琴图、柱状图等多种图表来可视化,这里不再一一赘述。

7.3数字特征分析

  • 追加标签
numeric_features.append('price')
numeric_data=train_data[numeric_features] # 带标签的数字特征数据集
  • 相关性分析
correlation=numeric_data.corr()  # 计算各列之间的相关性
print(correlation['price'].sort_values(ascending=False),'\n') # 打印预测值price与各个特征之间的相关性,并降序排序

在这里插入图片描述

  • 利用热图可视化相关性
plt.title('Correlation of Numeric Features with Price')
sns.heatmap(correlation,square = True)

在这里插入图片描述

7.4 每个数字特征得分布可视化

f = pd.melt(train_data, value_vars=numeric_features) 
g = sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False)
g = g.map(sns.distplot, "value")
  • melt 函数的目的是将数据框从宽格式(wide format)转换为长格式(long format),转换后的数据框 f 包含三列,分别是 “variable”(变量,即原数据框的列名)、“value”(值,即原数据框中的数值)和 “ID”(如果有的话,用于标识原数据的行)
  • 注:宽格式一般包含多个列,长格式就是把多个列都放到一列显示
  • FacetGrid 函数创建一个多面板图。f 是转换后的数据框,col=“variable” 表示按照 “variable” 列的不同取值(即原数据框的列名)分割多个面板,col_wrap=2 表示每行最多显示两个面板,sharex=False 和 sharey=False 分别表示每个面板的 x 轴和 y 轴是独立的,即它们的刻度不共享。
  • 第三行使用 map 方法将 Seaborn 的 distplot 函数应用到每个面板上。distplot 用于绘制单变量的直方图,并可选地包含核密度估计曲线。这里,“value” 列中的数值将作为参数传递给 distplot,从而在每个面板上绘制对应变量的分布图
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

7.5 数字特征相互之间的关系可视化

sns.set() # 用于设置默认的图形样式和颜色主题
columns = ['price', 'v_12', 'v_8' , 'v_0', 'power', 'v_5',  'v_2', 'v_6', 'v_1', 'v_14']
# pairplot 函数,用于绘制一组变量之间的散点图矩阵
# size=2: 指定每个子图的大小为2。
# kind='scatter': 指定绘制散点图。
# diag_kind='kde': 对角线上的图形使用核密度图(Kernel Density Estimate)来表示单变量分布
sns.pairplot(Train_data[columns],size = 2 ,kind ='scatter',diag_kind='kde')
plt.show()

在这里插入图片描述

7.6 多变量之间的关系可视化

# 创建了一个包含多个子图的图形,按照5行2列的网格排列,figsize 参数指定了整个图形的大小
fig, ((ax1, ax2), (ax3, ax4), (ax5, ax6), (ax7, ax8), (ax9, ax10)) = plt.subplots(nrows=5, ncols=2, figsize=(24, 20))
# 这一行创建了一个新的数据框 v_12_scatter_plot,其中包含目标变量 'price' 和特征 'v_12'。pd.concat 函数用于在水平方向上连接两个数据框
v_12_scatter_plot = pd.concat([y_train,train_data['v_12']],axis = 1)
# 在第一个子图 ax1 上绘制了 'v_12' 与 'price' 的散点图和线性回归拟合线
sns.regplot(x='v_12',y = 'price', data = v_12_scatter_plot,scatter= True, fit_reg=True, ax=ax1)
# x='v_12', y='price': 指定 x 轴和 y 轴的变量。
# data=v_12_scatter_plot: 指定绘图数据。
# scatter=True: 表示显示散点图。
# fit_reg=True: 表示显示线性回归拟合线。
# ax=ax1: 指定要在哪个子图上进行绘制,这里是第一个子图 ax1
v_8_scatter_plot = pd.concat([Y_train,Train_data['v_8']],axis = 1)
sns.regplot(x='v_8',y = 'price',data = v_8_scatter_plot,scatter= True, fit_reg=True, ax=ax2)

这里仅展示两个:
在这里插入图片描述

8. 用ydata_profiling生成数据报告

import pandas as pd
from ydata_profiling import ProfileReport

train_path="..."
train_data=pd.read_csv(train_path,sep=' ')
pfr = ProfileReport(train_data)
pfr.to_file("./example.html")

9. 总结

数据探索有利于我们发现数据的一些特性,数据之间的关联性,对于后续的特征构建是很有帮助的。

  • 对于数据的初步分析(直接查看数据,或.sum(), .mean(),.descirbe()等统计函数)可以从:样本数量,训练集数量,是否有时间特征,是否是时许问题,特征所表示的含义(非匿名特征),特征类型(字符类似,int,float,time),特征的缺失情况(注意缺失的在数据中的表现形式,有些是空的有些是”NAN”符号等),特征的均值方差情况。

  • 分析记录某些特征值缺失占比30%以上样本的缺失处理,有助于后续的模型验证和调节,分析特征应该是填充(填充方式是什么,均值填充,0填充,众数填充等),还是舍去,还是先做样本分类用不同的特征模型去预测。

  • 对于异常值做专门的分析,分析特征异常的label是否为异常值(或者偏离均值较远或者事特殊符号),异常值是否应该剔除,还是用正常值填充,是记录异常,还是机器本身异常等。

  • 对于Label做专门的分析,分析标签的分布情况等。

  • 进步分析可以通过对特征作图,特征和label联合做图(统计图,离散图),直观了解特征的分布情况,通过这一步也可以发现数据之中的一些异常值等,通过箱型图分析一些特征值的偏离情况,对于特征和特征联合作图,对于特征和label联合作图,分析其中的一些关联性。

  • 分析的时候,训练集,测试集,标签我们都要做相应的处理和分析,上述只展示了训练集,望周知

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1484556.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTTPS的实现原理

图片来源:HTTPS 详解一:附带最精美详尽的 HTTPS 原理图 - 个人文章 - SegmentFault 思否 加密流程按图中的序号分为: 客户端请求 HTTPS 网址,然后连接到 server 的 443 端口 (HTTPS 默认端口,类似于 HTTP 的80端口)。…

双周回顾#006 - 这三个月

断更啦~~ 上次更新时间 2023/11/23, 断更近三个月的时间。 先狡辩下,因为忙、着实忙。因为忙,心安理得给断更找了个借口,批评下自己~~ 这三个月在做啥?跨部门援助,支援公司互联网的 ToC 项目,一言难尽。 …

【随记】分享第1期(2024.03.02)

记录这段时间,看到的有趣/有用/值得分享的东西 灵感来源:分类:周刊 - 阮一峰的网络日志 (ruanyifeng.com) 文章目录 大佬博客实用工具文章文摘 大佬博客 云风的 BLOG (codingnow.com) 美团技术团队 (meituan.com) 计算机科学 – 刘未鹏 | Mi…

可以用来测试的接口

实际开发过程中,我们可以通过postman工具来测试接口 get请求 https://api.github.com/events?id1&nameuser post请求 http://httpbin.org/post 参数1:key1value1 参数2:key2value2

springboot238光影视频

光影视频平台 摘 要 使用旧方法对光影视频平台的信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在光影视频平台的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误不能及时纠正等问题。这次开…

第二讲:用geth和以太坊交互

一:安装geth brew install ethereum geth github网址: https://github.com/ethereum/go-ethereum 二: 用geth连接以太坊 以太坊有主网络(Ethereum Mainnet),有测试网络(Sepolia、Goerli 等等…

打基础!张宇《30讲》vs 武忠祥《基础篇》

张宇老师和武忠祥老师的课程都很推荐,两个老师也都很有实力 我在考研的时候跟的是张宇老师,然后强化阶段跟的是武忠祥老师,我真实的听过两个老师的课程,所以我觉得我有一些发言权。因此对大家在考研数学备考选择老师方面&#xf…

单细胞Seurat - 降维与细胞标记(4)

本系列持续更新Seurat单细胞分析教程,欢迎关注! 非线形降维 Seurat 提供了几种非线性降维技术,例如 tSNE 和 UMAP,来可视化和探索这些数据集。这些算法的目标是学习数据集中的底层结构,以便将相似的细胞放在低维空间中…

JavaScript 设计模式之职责链模式

职责链 在日常开发中,我们一个函数(方法)应该是尽可能的单单只做一件事,比如 一个获取 name 的函数,他就用来返回 name 处理 name相关的数据就好了,这就是职责 function getName(){// todo sth.return na…

从零自制docker-1-【环境配置 docker go介绍与安装】

文章目录 docker简介举例docker安装go语言go安装go 配置 docker简介 Docker可以看作是一种极其轻巧的“虚拟机”,它允许你将一个或多个程序及其运行环境打包在一起,形成一个标准化的单元,这个单元可以在任何支持Docker的系统上运行&#xff…

ArmSoM Rockchip系列产品 通用教程 之 Camera 使用

Camera 使用 1. Camera 简介 ArmSoM系列产品使用的是mipi-csi接口的摄像头 ArmSoM-Sige7支持双摄同显: 2. RK3588硬件通路框图 rk3588支持2个isp硬件,每个isp设备可虚拟出多个虚拟节点,软件上通过回读的方式,依次从ddr读取每…

SQL无列名注入

SQL无列名注入 ​ 前段时间,队里某位大佬发了一个关于sql注入无列名的文章,感觉好像很有用,特地研究下。 关于 information_schema 数据库: ​ 对于这一个库,我所知晓的内容并不多,并且之前总结SQL注入的…

React多个echarts图表在一个页面的使用

前景 很多情况下图标都是一个,我们大概率会像下面代码一样的做法 大概流程就是获取到数据后执行初始化,因为先初始化后异步请求再设置state里面的数据回导致无法正常显示echarts(除非再次调用setOption)下面就记录下自己解决过程源码 https://github.com/superBiuBiuMan/react-…

《秦时明月》IP新高度:与陕西历史博物馆共同书写文化传承新篇章!

在IP产业风起云涌的今天,如何以创意和匠心为传统文化注入新的活力,成为了摆在每一位文化工作者面前的重要课题。近日,《秦时明月》作为一部深受观众喜爱的国产动画IP,在迎来其十七周年之际,联手陕西历史博物馆&#xf…

线性dp:P2679 子串

1.P2679 子串 传送门https://www.luogu.com.cn/problem/P2679这道题是公共子串问题的变种,但是我第一时间确实没想到转移方程(写少了) 一开始看了题解也没太看懂,直到自己模拟一遍(模拟数据便于理解原理)…

#WEB前端(HTML属性)

1.实验:a,img 2.IDE:VSCODE 3.记录: a: href插入超链接 默认情况下在本窗口打开链接, target可以设置打开的窗口,parent在父窗口打开,blank新开串口打开,top在顶层串口打开,self为默认在本窗口打开 img: 插入图片 可以插…

持续集成(CICD)- Git版本管理工具,Gitee线上仓库

文章目录 一、学习目标:二、什么是Git工具三 、Git环境搭建(windows系统)四、Gitee设置(私钥和公钥绑定)五、Git结合Gittee进行基本设置(重要)六、在Gitee上新建仓库私有仓库(非空仓库)七、Git拉取线上仓库代码,提交代码(重要)八、Git解决版本冲突问题(重要)场景一…

LeetCode 刷题 [C++] 第45题.跳跃游戏 II

题目描述 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i]i j < n 返回到达 nums[n …

YOLOv9独家原创改进|增加SPD-Conv无卷积步长或池化:用于低分辨率图像和小物体的新 CNN 模块

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、文章摘要 卷积神经网络(CNNs)在计算即使觉任务中如图像分类和目标检测等取得了显著的成功。然而&#xff0c;当图像分辨率较低或物体较小时&…

每日一题 — 复写零

1089. 复写零 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 首先找到最后一个复写的数&#xff1a; 双指针算法&#xff1a; 1、先判断 cur 位置上的值 2、然后决定 dest 移动一步还是两步 3、然后判断 dest 是否到终点了 4、最后 cur 处理越界的情况 arr[n-1] …