拿到数据后,数据处理是一个至关重要的步骤。数据处理的质量直接影响模型的效果和最终结果的准确性。
下面是数据处理的一般步骤和方法:

1. 初步检查和理解数据

1.1. 理解数据来源和背景

  • 了解数据是如何收集的,数据的背景、数据字段的含义、数据的单位等。避免误解数据并正确使用它们

1.2. 数据可视化

  • 使用直方图、散点图、箱线图等工具初步可视化数据,以快速了解数据的分布、趋势、异常值等信息

事实上数据可视化在各个阶段都有其必要性,将数据以图形化的形式展示出来,可以更容易理解和分析数据的内在结构和关系。

1.2.1. 在建模过程中的多个阶段会用到数据可视化:

  • 数据探索:
    • 分布分析:使用直方图、密度图等来查看数据的分布情况。
    • 散点图:查看变量之间的关系和相关性。
    • 箱线图:检测数据中的异常值和分布的集中趋势。
  • 特征工程:
    • 相关矩阵:热图展示变量之间的相关性,帮助选择特征。
    • 时间序列图:分析时间序列数据的趋势和周期性。
  • 模型诊断和评估:
    • 残差图:评估模型的拟合情况,查看残差的分布。
    • 学习曲线:展示模型在训练集和验证集上的表现,帮助判断是否过拟合或欠拟合。
    • ROC曲线和AUC:评估分类模型的性能。
  • 结果展示:
    • 决策树可视化:展示树模型的结构和决策路径。
    • 特征重要性图:展示不同特征对模型预测的重要性。

1.2.2. 以下是一些常用的可视化工具和库:

Python:

  • Matplotlib
  • Seaborn
  • Plotnine
  • Plotly
  • Bokeh

R:

  • ggplot2
  • Shiny

其他工具:

  • Tableau
  • Power BI
  • Excel

那什么时候选择Matplotlib、Seaborn或是Plotnine?Plotnine具有ggplot2的图层特性,但是由于开发时间较晚,目前这个工具包还有一些缺陷,其中最大的缺陷就是:没有实现除了直角坐标以外的坐标体系,如:极坐标。因此,Plotnine无法绘制类似于饼图、环图等图表。为了解决这个问题,在绘制直角坐标系的图表时,我们可以使用Plotnine进行绘制,当涉及极坐标图表时,我们使用Matplotlib和Seaborn进行绘制。

  • 有趣的是,Matplotlib具有ggplot风格,可以通过设置ggplot风格绘制具有ggplot风格的图表:(是看起来像ggplot表格,但是实际上Matplotlib还是不具备图层风格)
plt.style.use("ggplot")   #风格使用ggplot

1.2.3. 基本图表代码实现

  • 类别型图表

类别型图表用于表示分类数据,以便比较不同类别的数值。常见的类别型图表包括柱状图、条形图、饼图和环形图。柱状图和条形图通过长短不一的柱或条来表示各类别的数据量,适合展示类别之间的比较。饼图和环形图则通过分割圆形区域来展示各类别所占的比例,适合显示组成部分的百分比。选择合适的类别型图表能够清晰直观地呈现数据分布和比较。

## Matplotlib绘制单系列柱状图:不同城市的房价对比
data = pd.DataFrame({'city':['深圳', '上海', '北京', '广州', '成都'], 'house_price(w)':[3.5, 4.0, 4.2, 2.1, 1.5]})

fig = plt.figure(figsize=(10,6))
ax1 = fig.add_axes([0.15,0.15,0.7,0.7])  # [left, bottom, width, height], 它表示添加到画布中的矩形区域的左下角坐标(x, y),以及宽度和高度
plt.bar(data['city'], data['house_price(w)'], width=0.6, align='center', orientation='vertical', label='城市')
"""
x 表示x坐标,数据类型为int或float类型,也可以为str
height 表示柱状图的高度,也就是y坐标值,数据类型为int或float类型
width 表示柱状图的宽度,取值在0~1之间,默认为0.8
bottom 柱状图的起始位置,也就是y轴的起始坐标
align 柱状图的中心位置,"center","lege"边缘
color 柱状图颜色
edgecolor 边框颜色
linewidth 边框宽度
tick_label 下标标签
log 柱状图y周使用科学计算方法,bool类型
orientation 柱状图是竖直还是水平,竖直:"vertical",水平条:"horizontal"
"""
plt.title("不同城市的房价对比图")   # 在axes1设置标题
plt.xlabel("城市")    # 在axes1中设置x标签
plt.ylabel("房价/w")    # 在axes1中设置y标签
plt.grid(b=True, which='both')  # 在axes1中设置设置网格线
for i in range(len(data)):
    plt.text(i-0.05, data.iloc[i,]['house_price(w)']+0.01, data.iloc[i,]['house_price(w)'],fontsize=13)   # 添加数据注释
plt.legend()
plt.show()
## Plotnine绘制单系列柱状图:不同城市的房价对比
data = pd.DataFrame({'city':['深圳', '上海', '北京', '广州', '成都'], 'house_price(w)':[3.5, 4.0, 4.2, 2.1, 1.5]})

p_single_bar = (
    ggplot(data, aes(x='city', y='house_price(w)', fill='city', label='house_price(w)'))+
    geom_bar(stat='identity')+
    labs(x="城市", y="房价(w)", title="不同城市的房价对比图")+
    geom_text(nudge_y=0.08)+
    theme(text = element_text(family = "Songti SC"))
)
print(p_single_bar)
## Matplotlib绘制多系列柱状图:不同城市在不同年份的房价对比
data = pd.DataFrame({
    '城市':['深圳', '上海', '北京', '广州', '成都', '深圳', '上海', '北京', '广州', '成都'],
    '年份':[2021,2021,2021,2021,2021,2022,2022,2022,2022,2022],
    '房价(w)':[3.5, 4.0, 4.2, 2.1, 1.5, 4.0, 4.2, 4.3, 1.6, 1.9]
})

fig = plt.figure(figsize=(10,6))
ax1 = fig.add_axes([0.15,0.15,0.7,0.7])  # [left, bottom, width, height], 它表示添加到画布中的矩形区域的左下角坐标(x, y),以及宽度和高度
plt.bar(
    np.arange(len(np.unique(data['城市'])))-0.15, 
    data.loc[data['年份']==2021,'房价(w)'], 
    width=0.3, 
    align='center', 
    orientation='vertical', 
    label='年份:2021'
    )
plt.bar(
    np.arange(len(np.unique(data['城市'])))+0.15, 
    data.loc[data['年份']==2022,'房价(w)'], 
    width=0.3, 
    align='center', 
    orientation='vertical', 
    label='年份:2022'
    )
plt.title("不同城市的房价对比图")   # 在axes1设置标题
plt.xlabel("城市")    # 在axes1中设置x标签
plt.ylabel("房价/w")    # 在axes1中设置y标签
plt.xticks(np.arange(len(np.unique(data['城市']))), np.array(['深圳', '上海', '北京', '广州', '成都']))
plt.grid(b=True, which='both')  # 在axes1中设置设置网格线

data_2021 = data.loc[data['年份']==2021,:]
for i in range(len(data_2021)):
    plt.text(i-0.15-0.05, data_2021.iloc[i,2]+0.05, data_2021.iloc[i,2],fontsize=13)   # 添加数据注释

data_2022 = data.loc[data['年份']==2022,:]
for i in range(len(data_2022)):
    plt.text(i+0.15-0.05, data_2022.iloc[i,2]+0.05, data_2022.iloc[i,2],fontsize=13)   # 添加数据注释
plt.legend()
plt.show()
## Plotnine绘制多系列柱状图:不同城市在不同年份的房价对比
data = pd.DataFrame({
    '城市':['深圳', '上海', '北京', '广州', '成都', '深圳', '上海', '北京', '广州', '成都'],
    '年份':[2021,2021,2021,2021,2021,2022,2022,2022,2022,2022],
    '房价(w)':[3.5, 4.0, 4.2, 2.1, 1.5, 4.0, 4.2, 4.3, 1.6, 1.9]
})

data['年份'] = pd.Categorical(data['年份'], ordered=True, categories=data['年份'].unique())
p_mult_bar = (
    ggplot(data, aes(x='城市', y='房价(w)', fill='年份'))+
    geom_bar(stat='identity',width=0.6, position='dodge')+
    scale_fill_manual(values = ["#f6e8c3", "#5ab4ac"])+
    labs(x="城市", y="房价(w)", title="不同城市的房价对比图")+
    geom_text(aes(label='房价(w)'), position = position_dodge2(width = 0.6, preserve = 'single'))+
    theme(text = element_text(family = "Songti SC"))
)
print(p_mult_bar)
## Matplotlib绘制堆叠柱状图:不同城市在不同年份的房价对比
data = pd.DataFrame({
    '城市':['深圳', '上海', '北京', '广州', '成都', '深圳', '上海', '北京', '广州', '成都'],
    '年份':[2021,2021,2021,2021,2021,2022,2022,2022,2022,2022],
    '房价(w)':[3.5, 4.0, 4.2, 2.1, 1.5, 4.0, 4.2, 4.3, 1.6, 1.9]
})
tmp=data.set_index(['城市','年份'])['房价(w)'].unstack()
data=tmp.rename_axis(columns=None).reset_index()
data.columns = ['城市','2021房价','2022房价']
print(data)

plt.figure(figsize=(10,6))
plt.bar(
    data['城市'], 
    data['2021房价'], 
    width=0.6, 
    align='center', 
    orientation='vertical', 
    label='年份:2021'
    )
plt.bar(
    data['城市'], 
    data['2022房价'], 
    width=0.6, 
    align='center', 
    orientation='vertical', 
    bottom=data['2021房价'],
    label='年份:2022'
    )
plt.title("不同城市2121-2022年房价对比图")   # 在axes1设置标题
plt.xlabel("城市")    # 在axes1中设置x标签
plt.ylabel("房价/w")    # 在axes1中设置y标签
plt.legend()
plt.show()
## Matplotlib绘制百分比柱状图:不同城市在不同年份的房价对比
data = pd.DataFrame({
    '城市':['深圳', '上海', '北京', '广州', '成都', '深圳', '上海', '北京', '广州', '成都'],
    '年份':[2021,2021,2021,2021,2021,2022,2022,2022,2022,2022],
    '房价(w)':[3.5, 4.0, 4.2, 2.1, 1.5, 4.0, 4.2, 4.3, 1.6, 1.9]
})
tmp=data.set_index(['城市','年份'])['房价(w)'].unstack()
data=tmp.rename_axis(columns=None).reset_index()
data.columns = ['城市','2021房价','2022房价']
print(data)

plt.figure(figsize=(10,6))
plt.bar(
    data['城市'], 
    data['2021房价']/(data['2021房价']+data['2022房价']), 
    width=0.4, 
    align='center', 
    orientation='vertical', 
    label='年份:2021'
    )
plt.bar(
    data['城市'], 
    data['2022房价']/(data['2021房价']+data['2022房价']), 
    width=0.4, 
    align='center', 
    orientation='vertical', 
    bottom=data['2021房价']/(data['2021房价']+data['2022房价']),
    label='年份:2022'
    )
plt.title("不同城市2121-2022年房价对比图")   # 设置标题
plt.xlabel("城市")    # 在axes1中设置x标签
plt.ylabel("房价/w")    # 在axes1中设置y标签
plt.legend()
plt.show()
# 使用Matplotlib绘制雷达图:英雄联盟几位英雄的能力对比
data = pd.DataFrame({
    '属性': ['血量', '攻击力', '攻速', '物抗', '魔抗'],
    '艾希':[3, 7, 8, 2, 2],
    '诺手':[8, 6, 3, 6, 6]
})

plt.figure(figsize=(8,8))
theta = np.linspace(0, 2*np.pi, len(data), endpoint=False)   # 每个坐标点的位置
theta = np.append(theta, theta[0])  # 让数据封闭
aixi = np.append(data['艾希'].values,data['艾希'][0])  #让数据封闭
nuoshou = np.append(data['诺手'].values,data['诺手'][0])  # 让数据封闭
shuxing = np.append(data['属性'].values,data['属性'][0])  # 让数据封闭

plt.polar(theta, aixi, 'ro-', lw=2, label='艾希') # 画出雷达图的点和线
plt.fill(theta, aixi, facecolor='r', alpha=0.5) # 填充
plt.polar(theta, nuoshou, 'bo-', lw=2, label='诺手')  # 画出雷达图的点和线
plt.fill(theta, nuoshou, facecolor='b', alpha=0.5) # 填充
plt.thetagrids(theta/(2*np.pi)*360, shuxing)  # 为每个轴添加标签
plt.ylim(0,10)
plt.legend()
plt.show()
  • 关系型图表

关系型图表用于展示变量之间的关系,常见类型包括散点图、气泡图和折线图。散点图通过点的位置展示两个变量之间的关系,适合观察趋势和相关性。气泡图在散点图的基础上加入第三个变量,用气泡的大小来表示其数值。折线图通过连线展示数据的变化趋势,适合时间序列数据分析。选择合适的关系型图表可以帮助识别变量之间的模式和关系。

# 使用Matplotlib和四个图说明相关关系:
x = np.random.randn(100)*10
y1 = np.random.randn(100)*10
y2 = 2 * x + 1 + np.random.randn(100)
y3 = -2 * x + 1 + np.random.randn(100)
y4 = x**2 + 1 + np.random.randn(100)

plt.figure(figsize=(12, 12))

plt.subplot(2,2,1)  #创建两行两列的子图,并绘制第一个子图
plt.scatter(x, y1, c='dodgerblue', marker=".", s=50)
plt.xlabel("x")
plt.ylabel("y1")
plt.title("y1与x不存在关联关系")

plt.subplot(2,2,2)  #创建两行两列的子图,并绘制第二个子图
plt.scatter(x, y2, c='tomato', marker="o", s=10)
plt.xlabel("x")
plt.ylabel("y2")
plt.title("y2与x存在关联关系")

plt.subplot(2,2,3)  #创建两行两列的子图,并绘制第三个子图
plt.scatter(x, y3, c='magenta', marker="o", s=10)
plt.xlabel("x")
plt.ylabel("y3")
plt.title("y3与x存在关联关系")

plt.subplot(2,2,4)  #创建两行两列的子图,并绘制第四个子图
plt.scatter(x, y4, c='deeppink', marker="s", s=10)
plt.xlabel("x")
plt.ylabel("y3")
plt.title("y4与x存在关联关系")

plt.show()
# 使用Plotnine和四个图说明相关关系:
x = np.random.randn(100)*10
y1 = np.random.randn(100)*10
y2 = 10 * x + 1 + np.random.randn(100)
y3 = -10 * x + 1 + np.random.randn(100)
y4 = x**2 + 1 + np.random.randn(100)

df = pd.DataFrame({
    'x': np.concatenate([x,x,x,x]),
    'y': np.concatenate([y1, y2, y3, y4]),
    'class': ['y1']*100 + ['y2']*100 + ['y3']*100 + ['y4']*100
})

p1 = (
    ggplot(df)+
    geom_point(aes(x='x', y='y', fill='class', shape='class'), color='black', size=2)+
    scale_fill_manual(values=('#00AFBB', '#FC4E07', '#00589F', '#F68F00'))+
    theme(text = element_text(family = "Songti SC"))
)
print(p1)
# 使用Matplotlib绘制具备趋势线的散点图
from sklearn.linear_model import LinearRegression  #线性回归等参数回归
import statsmodels.api as sm

from sklearn.preprocessing import PolynomialFeatures  # 构造多项式
x = np.linspace(-10, 10, 100)
y = np.square(x) + np.random.randn(100)*100
x_poly2 = PolynomialFeatures(degree=2).fit_transform(x.reshape(-1, 1))
y_linear_pred = LinearRegression().fit(x.reshape(-1, 1), y).predict(x.reshape(-1, 1))
y_poly_pred = LinearRegression().fit(x_poly2, y).predict(x_poly2)
y_exp_pred = LinearRegression().fit(np.exp(x).reshape(-1, 1), y).predict(np.exp(x).reshape(-1, 1))
y_loess_pred = sm.nonparametric.lowess(x, y, frac=2/3)[:, 1]

plt.figure(figsize=(8, 8))
plt.subplot(2,2,1)
plt.scatter(x, y, c='tomato', marker="o", s=10)
plt.plot(x, y_linear_pred, c='dodgerblue')
plt.xlabel("x")
plt.ylabel("y")
plt.title("带线性趋势线的散点图")

plt.subplot(2,2,2)
plt.scatter(x, y, c='tomato', marker="o", s=10)
plt.plot(x, y_poly_pred, c='dodgerblue')
plt.xlabel("x")
plt.ylabel("y")
plt.title("带二次趋势线的散点图")

plt.subplot(2,2,3)
plt.scatter(x, y, c='tomato', marker="o", s=10)
plt.plot(x, y_exp_pred, c='dodgerblue')
plt.xlabel("x")
plt.ylabel("y")
plt.title("带指数趋势线的散点图")

plt.subplot(2,2,4)
plt.scatter(x, y, c='tomato', marker="o", s=10)
plt.plot(x, y_loess_pred, c='dodgerblue')
plt.xlabel("x")
plt.ylabel("y")
plt.title("带 loess平滑线的散点图")

plt.show()
# 使用Matplotlib绘制聚类散点图
from sklearn.datasets import load_iris  #家在鸢尾花数据集
iris = load_iris()
X = iris.data
label = iris.target
feature = iris.feature_names
df = pd.DataFrame(X, columns=feature)
df['label'] = label

label_unique = np.unique(df['label']).tolist()
plt.figure(figsize=(10, 6))
for i in label_unique:
    df_label = df.loc[df['label'] == i, :]
    plt.scatter(x=df_label['sepal length (cm)'], y=df_label['sepal width (cm)'], s=20, label=i)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('sepal width (cm)~sepal length (cm)')
plt.legend()
plt.show()
# 使用plotnine绘制相关系数矩阵图:
from plotnine.data import mtcars
corr_mat = np.round(mtcars.corr(), 1).reset_index() #计算相关系数矩阵
df = pd.melt(corr_mat, id_vars='index', var_name='variable', value_name='corr_xy') #将矩阵宽数据变成长数据
df['abs_corr'] = np.abs(df['corr_xy'])
p1 = (
    ggplot(df, aes(x='index', y='variable', fill='corr_xy', size='abs_corr'))+
    geom_point(shape='o', color='black')+
    scale_size_area(max_size=11, guide=False)+
    scale_fill_cmap(name='RdYIBu_r')+
    coord_equal()+
    labs(x="Variable", y="Variable")+
    theme(dpi=100, figure_size=(4.5,4.55))
)
p2 = (
    ggplot(df, aes(x='index', y='variable', fill='corr_xy', size='abs_corr'))+
    geom_point(shape='s', color='black')+
    scale_size_area(max_size=10, guide=False)+
    scale_fill_cmap(name='RdYIBu_r')+
    coord_equal()+
    labs(x="Variable", y="Variable")+
    theme(dpi=100, figure_size=(4.5,4.55))
)
p3 = (
    ggplot(df, aes(x='index', y='variable', fill='corr_xy', label='corr_xy'))+
    geom_tile(color='black')+
    geom_text(size=8, color='white')+
    scale_fill_cmap(name='RdYIBu_r')+
    coord_equal()+
    labs(x="Variable", y="Variable")+
    theme(dpi=100, figure_size=(4.5,4.55))
)
print([p1, p2, p3])
# 使用Matplotlib/Seaborn绘制相关系数矩阵图
uniform_data = np.random.rand(10, 12)
sns.heatmap(uniform_data)
  • 分布型图表

分布型图表用于展示数据的分布情况和统计特征。常见类型包括直方图、箱线图和核密度图。直方图通过分段柱状图展示数据的频率分布,适合观察数据的集中趋势和离散程度。箱线图显示数据的中位数、四分位数和异常值,帮助识别数据的分散性和对称性。核密度图平滑直方图,提供数据分布的连续曲线图。选择合适的分布型图表可以深入理解数据的整体形态和特征。

# 使用matplotlib绘制直方图:
plt.figure(figsize=(8, 6))
plt.hist(mtcars['mpg'], bins=20, alpha=0.85)
plt.xlabel("mpg")
plt.ylabel("count")
plt.show()
# 使用matplotlib绘制箱线图
import seaborn as sns 
from plotnine.data import mtcars

data = mtcars
data['carb'] = data['carb'].astype('category')
plt.figure(figsize=(8, 6))
sns.boxenplot(x='carb', y='mpg', data=mtcars, linewidth=0.2, palette=sns.husl_palette(6, s=0.9, l=0.65, h=0.0417))
plt.show()
# 使用Matplotlib绘制饼状图:
from matplotlib import cm, colors
df = pd.DataFrame({
    '己方': ['寒冰', '布隆', '发条', '盲僧', '青钢影'],
    '敌方': ['女警', '拉克丝', '辛德拉', '赵信', '剑姬'],
    '己方输出': [26000, 5000, 23000, 4396, 21000],
    '敌方输出': [25000, 12000, 21000, 10000, 18000]
})

df_our = df[['己方', '己方输出']].sort_values(by='己方输出', ascending=False).reset_index()
df_other = df[['敌方', '敌方输出']].sort_values(by='敌方输出', ascending=False).reset_index()
color_list = [cm.Set3(i) for i in range(len(df))]
plt.figure(figsize=(16, 10))
plt.subplot(1,2,1)
plt.pie(df_our['己方输出'].values, startangle=90, shadow=True, colors=color_list, labels=df_our['己方'].tolist(), explode=(0,0,0,0,0.3), autopct='%.2f%%')


plt.subplot(1,2,2)
plt.pie(df_other['敌方输出'].values, startangle=90, shadow=True, colors=color_list, labels=df_other['敌方'].tolist(), explode=(0,0,0,0,0.3), autopct='%.2f%%')

plt.show()
# 使用Matplotlib绘制环状图:
from matplotlib import cm, colors
df = pd.DataFrame({
    '己方': ['寒冰', '布隆', '发条', '盲僧', '青钢影'],
    '敌方': ['女警', '拉克丝', '辛德拉', '赵信', '剑姬'],
    '己方输出': [26000, 5000, 23000, 4396, 21000],
    '敌方输出': [25000, 12000, 21000, 10000, 18000]
})

df_our = df[['己方', '己方输出']].sort_values(by='己方输出', ascending=False).reset_index()
df_other = df[['敌方', '敌方输出']].sort_values(by='敌方输出', ascending=False).reset_index()
color_list = [cm.Set3(i) for i in range(len(df))]
wedgeprops = {'width':0.3, 'edgecolor':'black', 'linewidth':3}
plt.figure(figsize=(16, 10))
plt.subplot(1,2,1)
plt.pie(df_our['己方输出'].values, startangle=90, shadow=True, colors=color_list, wedgeprops=wedgeprops, labels=df_our['己方'].tolist(), explode=(0,0,0,0,0.3), autopct='%.2f%%')
plt.text(0, 0, '己方' , ha='center', va='center', fontsize=30)

plt.subplot(1,2,2)
plt.pie(df_other['敌方输出'].values, startangle=90, shadow=True, colors=color_list, wedgeprops=wedgeprops, labels=df_other['敌方'].tolist(), explode=(0,0,0,0,0.3), autopct='%.2f%%')
plt.text(0, 0, '敌方' , ha='center', va='center', fontsize=30)
plt.show()
  • 时间序列图表

时间序列图表用于展示数据随时间的变化趋势。常见类型包括折线图和面积图。折线图通过连接数据点的线条显示时间序列数据的变化,适合识别趋势和周期性。面积图则在折线图的基础上填充下面的区域,便于比较不同系列数据的累计值。选择合适的时间序列图表有助于深入理解数据的动态变化和预测未来趋势。

# 使用Plotnine绘制时间序列线图:
df = pd.read_csv(
    './data/AirPassengers.csv'
    )  # 航空数据1949-1960
df['date'] = pd.to_datetime(df['date'])
p1 = (
    ggplot(df, aes(x='date', y='value'))+
    geom_line(size=1, color='red')+
    scale_x_date(date_labels="%Y", date_breaks="1 year")+
    xlab('date')+
    ylab('value')
)
print(p1)
# 使用Matplotlib绘制时间序列折线图
plt.figure(figsize=(8,6))
plt.plot(df['date'], df['value'], color='red')
plt.xlabel("date")
plt.ylabel("value")
plt.show()
# Plotnine绘制多系列折线图
date_list = pd.date_range('2022-01-01', '2022-03-31').astype('str').tolist() * 2
value_list = np.random.randn(len(date_list))
Class = [1] * (len(date_list) // 2) + [2] * (len(date_list) // 2)
data = pd.DataFrame({
    'date_list': date_list,
    'value_list': value_list,
    'Class': Class
})

data['date_list'] = pd.to_datetime(data['date_list'])
p1 = (
    ggplot(data, aes(x='date_list', y='value_list', group='factor(Class)', color='factor(Class)'))+
    geom_line(size=1)+
    scale_x_date(date_labels="%D")+
    xlab('date')+
    ylab('value')
)
print(p1)
# Matplotlib 绘制多系列折线图
date_list = pd.date_range('2022-01-01', '2022-03-31').astype('str').tolist()
value_list1 = np.random.randn(len(date_list))
value_list2 = np.random.randn(len(date_list))
data = pd.DataFrame({
    'date_list': date_list,
    'value_list1': value_list1,
    'value_list2': value_list2
})
data['date_list'] = pd.to_datetime(data['date_list'])

plt.figure(figsize=(8, 6))
plt.plot(data['date_list'], data['value_list1'], color='red', alpha=0.86, label='value1')
plt.plot(data['date_list'], data['value_list2'], color='blue', alpha=0.86, label='value2')
plt.legend()
plt.xlabel('date')
plt.ylabel('value')
plt.show()

1.3. 检查数据的完整性

  • 确定数据集是否完整,是否有缺失值(NaN),以及这些缺失值的比例和分布(可使用热图(如 seaborn 库中的 heatmap )或缺失值矩阵图来直观地显示缺失值的分布情况 )
  • 识别重复数据,确定是否需要删除或处理(可使用 pandas 中的 duplicated() 函数查找重复行 )

2. 数据清洗

2.1. 处理缺失值

(每一列被称为一个属性,有多少属性就说数据有多少维。每一行被称为一个数据项。)

  • 观察缺失率,若存在缺失的数据项占比少(5%以内)时,若问题允许可以删除行
  • 若缺失率在5%-20%,可以使用填充(常数填充,均值填充等等),插值(使用其他数据点插值填充缺失值)的方法处理
  • 若缺失率在20%-40%,需要用预测方法例如机器学习去填充缺失数据
  • 若缺失率有50%以上,我们可以考虑把这一列都删掉

2.2. 处理异常值

  • 识别异常值: 使用箱线图、散点图、Z-Score等方法识别异常值
  • 处理方法
    • 删除异常值: 如果异常值确实不合理且没有参考意义,可以删除

    • 修正异常值: 如果能确定异常值是由于数据输入错误,可以修正

      • 使用中位数或均值填充替换异常值
      • 截断法(将异常值截断到一定范围内)
      • 变换法(对数据进行变换【如对数变换】以减小异常值的影响)
      • 箱线图法(使用IQR方法筛选和处理异常值)

2.3. 插值模型

插值模型用于估计已知数据点之间的值
线性插值通过连接相邻数据点的直线估计中间值,简单但精度有限。多项式插值使用高次多项式通过所有数据点,精度高但可能引入振荡。样条插值则用分段多项式平滑连接数据点,既保证精度又避免振荡。选择合适的插值模型有助于准确估计和预测数据。
下面介绍几种常见的插值模型:

2.3.1. 线性插值法

线性插值对两个点中的解析式是按照线性方程来建模。比如我们得到的原始数据列{y}和数据的下标{x},这里数据下标x可能并不是固定频率的连续取值而是和y一样存在缺失的。给定了数据点(xk,yk)和(xk+1,yk+1),需要对两个点之间构造直线进行填充。很显然,根据直线的点斜式方程,这个直线解析式为:​{L_1}(x) = {y_k} + \frac{{{y_{k + 1}} - {y_k}}}{{{x_{k + 1}} - {x_k}}}(x - {x_k})

按照这个方程形式对空缺的(x,y)处进行填充就可以完成插值过程。

import numpy as np
#数据准备
X = np.arange(-2*np.pi, 2*np.pi, 1) # 定义样本点X,从-pi到pi每次间隔1
Y = np.sin(X) # 定义样本点Y,形成sin函数
new_x = np.arange(-2*np.pi, 2*np.pi, 0.1) # 定义插值点
# 进行样条插值
import scipy.interpolate as spi
# 进行一阶样条插值
ipo1 = spi.splrep(X, Y, k=1)  # 样本点导入,生成参数
iy1 = spi.splev(new_x, ipo1)  # 根据观测点和样条参数,生成插值

2.3.2. 三次样条插值

三次样条插值是一种非常自然的插值方法。它将两个数据点之间的填充模式设置为三次多项式。它假设在数据点(xk,yk)和(xk+1,yk+1)之间的三次式叫做Ik,那么这一组三次式需要满足条件

​{a_i}x_i^3 + {b_i}x_i^2 + {c_i}{x_i} + {d_i} = {a_{i + 1}}x_{i + 1}^3 + {b_{i + 1}}x_{i + 1}^2 + {c_{i + 1}}{x_{i + 1}} + {d_{i + 1}}

​3{a_i}x_i^2 + 2{b_i}{x_i} + {c_i} = 3{a_{i + 1}}x_{i + 1}^2 + 2{b_{i + 1}}{x_{i + 1}} + {c_{i + 1}}

​6{a_i}{x_i} + 2{b_i} = 6{a_{i + 1}}{x_{i + 1}} + 2{b_{i + 1}}

通过解方程的形式可以解出每一只三次式。而简而言之,也就是说某个数据点前后两条三次函数不仅在当前的函数值相等,一次导数值和二次导数值也要保持相等

# 进行三次样条拟合
ipo3 = spi.splrep(X, Y, k=3)  # 样本点导入,生成参数
iy3 = spi.splev(new_x, ipo3)  # 根据观测点和样条参数,生成插值

2.3.3. 拉格朗日插值

对于一组数据{y}和下标{x},定义n个拉格朗日插值基函数: ​{l_k}(x) = \prod\limits_{i = 0,i \ne k}^n {\frac{{x - {x_i}}}{{{x_k} - {x_i}}}}
这本质上是一个分式,当x=xk时lk(x)=1,这一操作实现了离散数据的连续化。按照对应下标的函数值加权求和可以得到整体的拉格朗日插值函数: ​L(x) = \sum\limits_{k = 0}^n {{y_k}{l_k}(x)}
Python没有为我们提供拉格朗日插值函数的方法,我们可以自己写一个

#拉格朗日插值
def lagrange(x0,y0,x):
    y=[]
    for k in range(len(x)):
        s=0
        for i in range(len(y0)):
           t=y0[i]
           for j in range(len(y0)):
              if i!=j:
                t*=(x[k]-x0[j])/(x0[i]-x0[j])
           s+=t
        y.append(s)
    return y
#使用
from scipy.interpolate import interp1d
x0=[1,2,3,4,5]
y0=[1.6,1.8,3.2,5.9,6.8]
x=np.arange(1,5,1/30)
f1=interp1d(x0,y0,'linear')
y1=f1(x)
f2=interp1d(x0,y0,'cubic')
y2=f2(x)
y3=lagrange(x0,y0,x)
plt.plot(x0,y0,'r*')
plt.plot(x,y1,'b-',x,y2,'y-',x,y3,'r-')
plt.show()

2.4. 数据类型转换

  • 确保数据的类型正确,如将字符串型的数字转换为数值型,将类别型数据转换为哑变量等

3. 特征工程

3.1. 特征选择

  • 相关性分析: 使用相关系数、假设检验、互信息等方法评估特征与目标变量之间的相关性
  • 降维方法: 使用主成分分析、线性判别分析等方法降低数据维度,减少冗余信息(数据降维(data dimension reduction)-CSDN博客
  • 专家经验: 基于问题背景和领域知识选择重要特征

3.2. 特征构造

  • 衍生特征: 基于现有特征构造新特征,如计算差值、比率、累积和等
  • 非线性变换: 对特征进行非线性变换(如对数变换、平方变换等),以增强特征与目标变量的关系
  • 组合特征: 组合多个特征生成新的特征,例如特征的交互项、多项式特征等

4. 数据标准化和归一化

4.1. 标准化

  • 将数据转换为零均值和单位方差的形式(Z-Score标准化),常用于要求数据具有相同尺度的算法,如KNN、SVM等

4.2. 归一化

  • 将数据缩放到一个特定的范围(如[0,1]),适用于特征量级差异较大的情况,如神经网络中的输入数据(min-max规约)

4.3. 离散化

  • 将连续变量转换为离散变量或类别,如将年龄转换为年龄段

4.4. 下面介绍一些数据规约的方法

4.4.1. 属性规约

属性规约旨在减少数据集中属性(特征)的数量,通常通过以下方法实现:

  • 属性选择:从数据集中选择最相关的属性,去除冗余或不相关的属性。常见的方法有过滤法、包裹法和嵌入法
  • 属性提取:通过变换原始属性来创建新的属性,保留重要信息,同时减少属性数量。常见的方法包括主成分分析、线性判别分析等

4.4.2. 数值规约

数值规约旨在减少数值数据的精度或数量,通常通过以下方法实现:

  • 小波变换:使用小波变换将数据分解成不同的频率成分,可以在保留主要特征的情况下去除噪声
  • 聚类分析:将相似的数据点归为一类,用类中心点代表整个类,从而减少数据点数量
  • 直方图分析:将数据划分为不同的区间,用区间的代表值(如区间中值)代替区间内的所有值
  • 数据离散化:将连续数值数据转换为离散的类别数据,常用方法有等宽离散化和等频离散化

4.4.3. 数据压缩

数据压缩通过压缩算法减少数据的存储空间,常用的方法包括:

  • 无损压缩:在不丢失任何信息的前提下压缩数据,如霍夫曼编码、游程编码
  • 有损压缩:允许丢失部分数据,通常用于图像、音频和视频压缩,如JPEG、MP3和MPEG

4.4.4. 维度规约

维度规约通过变换或选择减少数据的维度,常用的方法包括:

  • 主成分分析:通过线性变换将高维数据映射到低维空间,最大化数据的方差
  • 线性判别分析:通过寻找能够最大化类间方差与类内方差比的投影方向,降低维度
  • t-SNE:一种非线性降维技术,适用于高维数据的可视化

4.4.5. 数据采样

数据采样通过选择数据子集来规约数据量,常用的方法包括:

  • 随机采样:从数据集中随机选择一部分数据作为样本
  • 系统采样:按照一定的间隔从数据集中选择数据
  • 分层采样:根据数据的某些特征将数据集划分为不同的层,然后从每一层中随机抽取样本

4.4.6. 聚合

数据聚合通过对数据进行汇总来减少数据量,常用的方法包括:

  • 分组聚合:对数据进行分组,并计算每组的统计值,如平均值、总和等
  • 滑动窗口:对数据进行滑动窗口处理,计算窗口内的数据统计值

4.4.7. 采样

采样是一种常见的数据规约技术,尤其在大数据处理中,采样可以有效降低数据量,提高计算效率。常用的采样方法有:

  • 简单随机采样:从数据集中随机抽取样本
  • 系统采样:按一定的间隔从数据集中抽取样本
  • 分层采样:根据某些特征将数据分层,然后从每一层中随机抽取样本

5. 数据分割

5.1. 训练集、验证集和测试集划分

  • 将数据集划分为训练集、验证集和测试集(通常比例为6:2:2或7:3),以便训练模型、调参和评估模型性能

5.2. 交叉验证

  • 使用k折交叉验证来评估模型,避免过拟合和验证集分割带来的偏差
    k折交叉验证一种常用的模型验证方法,用于评估机器学习模型的性能,尤其在数据量有限的情况下。
    它可以更好地利用数据,避免因训练集和验证集划分不同而导致的模型评估不稳定,减少过拟合风险。

5.2.1. 步骤

  1. 划分数据
    • 将整个数据集随机分成K个相等或接近相等的子集(折),这K个子集在交叉验证过程中分别用作训练集和验证集
  2. 训练和验证
    • 进行K次训练和验证。在每一次训练过程中,选择其中K-1个子集作为训练集,剩下的1个子集作为验证集
    • 重复这个过程K次,每次选择不同的子集作为验证集,其余的子集作为训练集
  3. 计算平均性能
    • 每次训练和验证后,记录模型的性能指标(如准确率、精确率、召回率、F1-score等)
    • K次验证完成后,计算这些指标的平均值,作为模型的最终性能评估结果

5.2.2. And

  • 常见的K值
    • 较小的K值(如K=3)会导致训练集较小,可能无法充分训练模型,但计算成本较低;较大的K值(如K=20)会使得每个训练集包含更多样本,有助于模型的训练,但计算成本较高。
    • 5折(K=5)和10折(K=10)是最常用的选择。它们在计算成本和模型评估稳定性之间取得了较好的平衡。
  • 极端情况
    • 留一法: 当K等于数据集的样本数量时,每次使用一个样本作为验证集,剩余的样本作为训练集。这种方法计算成本高,但能够最大化训练数据的利用率。

在 Python 中,可以使用 scikit-learn 库中的 KFold 或 cross_val_score 函数来方便地实现K折交叉验证。

5.2.3. Python实现K折交叉验证

from sklearn.model_selection import KFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris

# 加载数据集
data = load_iris()
X = data.data
y = data.target

# 定义模型
model = RandomForestClassifier()

# 定义K折交叉验证
kf = KFold(n_splits=5, shuffle=True, random_state=42)

# 使用cross_val_score进行交叉验证
scores = cross_val_score(model, X, y, cv=kf)

# 打印每折的分数和平均分数
print("Cross-validation scores:", scores)
print("Average cross-validation score:", scores.mean())

6. 数据平衡处理

  • 类别不平衡处理: 如果数据类别不平衡,可以通过欠采样、过采样、SMOTE等方法来平衡类别分布,提高模型性能

7. 数据扩充(如有必要)

  • 数据增强: 如果数据量不足,可以通过旋转、翻转、平移等方式对数据进行扩充,特别是在图像/音频处理领域

8. 数据保存与记录

8.1. 保存清洗后的数据

  • 将处理后的数据保存为CSV或其他格式,以备后续使用

8.2. 记录处理步骤

  • 记录所有数据处理的步骤、方法和原因,以便重现和解释

9. 总结

数据处理是数学建模中的重要环节,直接影响模型的性能和可靠性。在进行数据处理时,必须谨慎操作,并根据具体问题的特点和数据情况,运用各种技术手段,以确保处理后的数据能准确地反映问题的实质,并为模型提供可靠的支持。