WBlog

哀吾生之须臾,羡长江之无穷

0%

数据仓库与数据挖掘技术实验报告

数据仓库与数据挖掘技术

实验报告

24 秋


姓名王文海
班级计实验22
学号22101130107
成绩

1. 实验环境

1.1 硬件环境

  • 操作系统:macOS

1.2 系统环境

  • 开发工具:PyCharm
  • 编程语言:Python

1.3 开发工具及相关类库

  • Python 环境和相关类库

    • Python 版本:Python 3.9.19
    • Conda:用于管理Python环境和包的工具,支持跨平台的环境隔离和包管理
    • 相关类库
      • os, sys, time, datetime, dateutil:系统基础包,用于处理文件路径、系统操作、时间处理等。
      • pandas:用于数据表分析,提供高效的数据结构和数据分析工具。
      • numpy:用于多维数组及矩阵运算,提供强大的数值计算功能。
      • matplotlib:用于绘图,支持各种图表的绘制。
      • sympy.physics.control.control_plots:用于控制系统分析和绘图。
      • matplotlib.font_manager:用于管理字体,支持自定义字体。

2 数据仓库及多维分析

2.1 加载数据

image-20241005212202023

2.2 数据基本特性

image-20241005212424126

可以通过修改如图的alpha值调整

设置点的透明度为 0.5,值越小,点越透明,从而可以更好地展示数据密集区域的分布情况

修改前

image-20241005212739848

修改后

image-20241005213036878

2.3 增加维度

1
2
3
4
5
6
7
8
9
10
# 假设 df 是您的 DataFrame,并且已经包含了 timestamp 列
df["time"] = df.timestamp.apply(lambda ts: time.localtime(ts))

# 增加小时、分钟、15分钟间隔和5分钟间隔属性
df["hour"] = df.time.apply(lambda t: t.tm_hour)
df["minute"] = df.time.apply(lambda t: t.tm_min)
df["15_min_interval"] = df.time.apply(lambda t: t.tm_hour * 4 + t.tm_min // 15)
df["5_min_interval"] = df.time.apply(lambda t: t.tm_hour * 12 + t.tm_min // 5)

df.head()

增加几种间隔属性,用于之后绘制图片使用

2.4 按时间统计

绘制直方图的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 按小时统计数据量
hour_counts = df["hour"].value_counts().sort_index() # 统计每个小时的GPS采样量并按索引排序

# 按15分钟间隔统计数据量
interval_15_min_counts = df["15_min_interval"].value_counts().sort_index() # 统计每个15分钟间隔的GPS采样量并按索引排序

# 按5分钟间隔统计数据量
interval_5_min_counts = df["5_min_interval"].value_counts().sort_index() # 统计每个5分钟间隔的GPS采样量并按索引排序

# 绘图
plt.figure(figsize=(15, 12)) # 创建一个大小为15x12的图形

# 按小时统计数据量的图
plt.subplot(3, 1, 1) # 创建第一个子图,3行1列,第一个位置
plt.bar(hour_counts.index.values, hour_counts.values, width=0.8) # 绘制条形图,x轴为小时,y轴为GPS采样量
plt.xlabel('小时', fontproperties=font_prop) # 设置x轴标签
plt.ylabel('GPS采样量', fontproperties=font_prop) # 设置y轴标签
plt.title('GPS量按小时的分布', fontproperties=font_prop) # 设置图标题
plt.tight_layout() # 自动调整子图参数,使之填充整个图像区域
plt.grid() # 显示网格
plt.xticks(range(24)) # 设置x轴刻度,显示0到23的小时

# 按15分钟间隔统计数据量的图
plt.subplot(3, 1, 2) # 创建第二个子图,3行1列,第二个位置
plt.bar(interval_15_min_counts.index.values, interval_15_min_counts.values, width=0.8) # 绘制条形图,x轴为15分钟间隔,y轴为GPS采样量
plt.xlabel('15分钟间隔', fontproperties=font_prop) # 设置x轴标签
plt.ylabel('GPS采样量', fontproperties=font_prop) # 设置y轴标签
plt.title('GPS量按15分钟间隔的分布', fontproperties=font_prop) # 设置图标题
plt.tight_layout() # 自动调整子图参数,使之填充整个图像区域
plt.grid() # 显示网格
plt.xticks(range(0, 96, 4)) # 设置x轴刻度,每4个15分钟间隔显示一个刻度

# 按5分钟间隔统计数据量的图
plt.subplot(3, 1, 3) # 创建第三个子图,3行1列,第三个位置
plt.bar(interval_5_min_counts.index.values, interval_5_min_counts.values, width=0.8) # 绘制条形图,x轴为5分钟间隔,y轴为GPS采样量
plt.xlabel('5分钟间隔', fontproperties=font_prop) # 设置x轴标签
plt.ylabel('GPS采样量', fontproperties=font_prop) # 设置y轴标签
plt.title('GPS量按5分钟间隔的分布', fontproperties=font_prop) # 设置图标题
plt.tight_layout() # 自动调整子图参数,使之填充整个图像区域
plt.grid() # 显示网格
plt.xticks(range(0, 288, 12)) # 设置x轴刻度,每12个5分钟间隔显示一个刻度

plt.show() # 显示图形

绘制结果如下

如果实际按照5分钟和15分钟间隔绘制图片,可以考虑减少刻度标签的数量来保证整洁性。

image-20241005223855737

2.5 按路段统计

2.5.1 路段总量

image-20241006111855015

2.5.2 链接GPS计数

image-20241006112550862

2.5.3 获取路段数据

image-20241006112807780

2.6 按车辆统计

2.6.1 车辆总量

image-20241006113621757

2.6.2 链接GPS计数

image-20241006113701678

2.6.3 获取路段数据

image-20241006115253985

2.7 *按采样间隔统计

2.8 问题与总结

  1. 由于我使用的mac电脑,所以在一开始的绘制图片由于字体问题产生了许多问题,在查阅资料后通过替换字体路径解决了这个问题
  2. 之前所使用的conda环境缺少部分包,下载后解决
  3. jupyter的用法不太熟悉,经过上述的内容练习后现在可以初步使用

3 数据挖掘

3.1 线性回归

线性回归是一种用于预测连续值的监督学习算法。给定一组输入特征 x 和对应的输出值 y,线性回归的目标是找到一个函数 f(x),使得 f(x) 能够尽可能准确地预测 y。在本问题中,我们使用多项式回归来拟合给定的数据点。

3.1.1 数据描述

给定的数据集包含以下样本点:

  • x=[0,3,5,9,11]x=[0,3,5,9,11]
  • y=[1,5,3,2,9]y=[1,5,3,2,9]

我们希望使用一个 n次多项式来拟合这些数据点。

image-20241006121700476

绘制图如下

image-20241006122056319

为了分析不同超参数对线性回归模型性能的影响,我们可以设置不同的多项式次数 n 和正则化系数 λ进行实验。以下是具体的实验步骤和结果分析。

  1. 设置不同的多项式次数 n:

    • n=2
    • n=4
    • n=6
  2. 设置不同的正则化系数 λ:

    • λ=0 (普通最小二乘法)
    • λ=0.1
    • λ=1
    • λ=10

3.1.2 实验步骤

  1. 分别使用不同的 n 和 λ 进行拟合,并绘制拟合曲线。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # 定义绘制拟合曲线的函数
    def plot_fit(x, y, t, theta1, theta2, n, lambd):
    plt.title(f'{n-1} degree polynomial fitting') # 设置标题
    plt.plot(x,y,'o',label='samples')
    plt.plot(t, fit(t, theta1), label='OLS Fit') # 绘制普通最小二乘法拟合曲线
    plt.plot(t, fit(t, theta2), label='Ridge Fit (λ={})'.format(lambd)) # 绘制带正则化项的拟合曲线
    plt.grid() # 显示网格线
    plt.legend() # 显示图例
    plt.show() # 显示最终图表

    # 实验不同的多项式次数 n
    for n in [2, 4, 6]:
    A = np.vander(x, n, increasing=True)
    b = np.array(y)

    # 普通最小二乘法
    theta_ols = np.linalg.inv(A.T.dot(A)).dot(A.T).dot(b)

    # 带正则化项的最小二乘法
    for lambd in [0, 0.1, 1, 10]:
    theta_ridge = np.linalg.inv(A.T.dot(A) + lambd * np.eye(n)).dot(A.T).dot(b)
    t = np.linspace(min(x), max(x), 1000)
    plot_fit(x, y, t, theta_ols, theta_ridge, n, lambd)

    绘制图表如下

    image-20241006125157681

3.1.3 结论

  • 多项式次数 n:选择合适的多项式次数非常重要。过低的多项式次数会导致欠拟合,而太高的多项式次数会导致过拟合。通常可以通过交叉验证来选择最优的多项式次数。
  • 正则化系数 λ:正则化系数 λ 可以有效控制模型的复杂度,减少过拟合的风险。选择合适的 λ值可以通过交叉验证或网格搜索来确定。

3.2 k-means聚类

3.2.1 数据描述

为了分析不同超参数对K-means聚类算法性能的影响,我们可以设置不同的聚类数量 kk 和迭代次数 epochsepochs 进行实验。

  1. 设置不同的聚类数量 k:
    • k=2
    • k=3
    • k=4
  2. 设置不同的迭代次数 epochsepochs
    • epochs=3
    • epochs=6
    • epochs=9
  3. 分别使用不同的 k 和 epochs 进行聚类,并绘制聚类结果。

3.2.2 实验步骤

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def draw_kmeans(X, label, means, epoch):
plt.figure()
plt.gca().set_aspect(1)
plt.title('epoch %d' % epoch)
for k in range(len(means)):
ak = X[:, np.where(label == k)[0]]
plt.plot(ak[0], ak[1], 'o', label='cluster %i' % k)
plt.text(means[k][0], means[k][1], ' cluster %d' % k)
plt.plot(means[:, 0], means[:, 1], '^r', label='kmeans', markersize=9)
plt.legend()
return None


def kmeans(X, k):
# 初始化k个聚类中心 和 每个样本的类别标签
means = np.random.rand(k).reshape((k, -1)) * (X.max(1) - X.min(1)) + X.min(1) # k * 2 维
label = np.zeros(X.shape[1]).astype('i')
for i in range(6): # 开始迭代
for j in range(len(label)): # 对每个样本j
d = ((means - X[:, j]) ** 2).sum(1) # 计算当前样本到各聚类中心的距离
label[j] = d.argmin() # 距离样本最近的聚类中心,为样本的所在聚类簇
draw_kmeans(X, label, means, i + 1)
for kk in range(k): # 对每个聚类簇k
k_idx = np.where(label == kk)[0]
if len(k_idx) != 0:
means[kk] = X[:, k_idx].mean(1)
return label, means # 返回各样本的类别标签及各聚类的簇中心


label, _ = kmeans(a, 3)
label

绘制图片如下

image-20241006192845158

3.2.3 结论

  • 聚类数量 k:选择合适的聚类数量非常重要。过少的聚类数量会导致欠拟合,而太多的聚类数量会导致过拟合。通常可以通过肘部法则(Elbow Method)或轮廓系数(Silhouette Coefficient)来选择最优的聚类数量。
  • 迭代次数 epochs:迭代次数可以有效控制聚类算法的收敛速度。选择合适的迭代次数可以通过观察聚类中心的收敛情况来确定。

3.3 knn分类

3.3.1 数据描述

代码实现了一个简单的 k-近邻(k-NN)算法,并且包含了一个绘图函数 draw_knn 来可视化结果。

3.3.2 实验步骤

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def draw_knn(x, neighbor_list, X, label):
plt.figure()
plt.gca().set_aspect(1)
nn_idx = [nn[1] for nn in neighbor_list]
nn_data = X[:, nn_idx]
nn_label = label[nn_idx]
plt.scatter(nn_data[0], nn_data[1], 80, alpha=.5,
c='red', edgecolors='k', label='knn:%d' % len(neighbor_list))
for k in range(3):
ak = X[:, np.where(label == k)[0]]
plt.plot(ak[0], ak[1], '.')
plt.plot(x[0], x[1], 'X', markersize=7, label='x')
plt.legend()
return None


def knn(x, k, X, label):
neighbors = [] # 用于存放k个近邻,按近邻距离从小到大顺序存储
for i in range(len(label)): # 遍历每一个已知样本i
dis = ((x - X[:, i]) ** 2).sum() ** .5 # 计算x到已知样本i的距离
# 已遍历的样本小于k 或 当前距离小于k近邻中的最大距离时
if len(neighbors) < k or dis < neighbors[-1][0]:
# 按近邻距离从小到大的顺序插入buf
neighbor = [dis, i, label[i]] # 创建一个近邻数据
if len(neighbors) < k:
neighbors.append(neighbor) # 追加新创建的近邻
else:
neighbors[-1] = neighbor # 替换最后(距离最大)的一个近邻
# 对近邻列表排序
for j in range(len(neighbors) - 1, 0, -1):
if neighbors[j - 1] > neighbors[j]:
neighbors[j - 1], neighbors[j] = neighbors[j], neighbors[j - 1]
else:
break
# 遍历完成后,neighbors中已存放了k个近邻信息(距离,索引,标签)
draw_knn(x, neighbors, X, label)
return neighbors


knn([2, -2], 3, a, label) # k = 3
knn([2, -2], 5, a, label) # k = 5
knn([2, -2], 7, a, label) # k = 7
knn([2, -2], 9, a, label) # k = 9
# 求样本(2,-2)的类别,k为9,已知样本集与标签为a和label

绘制图片如下

image-20241006195311557

3.3.3 结论

通过设置不同的超参数(如 k 值),我们可以观察到 k-NN 算法在不同参数下的表现:

  • k 值的选择
    • 当 k 值较小时(如 k=3 或 k=5),模型对噪声敏感,容易受到局部数据的影响,分类结果可能不稳定。
    • 当 k 值较大时(如 k=7 或 k=9),模型对噪声的鲁棒性增强,分类结果更稳定,但可能引入偏差,导致模型对数据的整体分布过于平滑。
  • 距离度量
    • 欧几里得距离是最常用的距离度量,适用于大多数情况。但在某些特定场景下,可能需要使用其他距离度量(如曼哈顿距离、余弦相似度等)。

4 实验中遇到的问题

  1. 首先是标准库我当前主要使用的conda环境并没有,下载后正常运行。
  2. 字体问题,有很多字体mac上面没有,查阅资料的得知在win上运行正常,后来找到了其他方法替代。
  3. 代码读不懂,通过问国产代码助手CodeGeex了解代码并逐步修改调整参数,最终完成实验。
  4. 更深刻了解到python局限性,稍微代码多一点作为动态语言维护性十分差。通过类型注解可以缓解一部分但不能完全缓解。
  5. 好的开发工具是提高效率的利器,Pycharm的功能远超Vscode

5 实验总结

本次实验通过使用Python和相关类库,成功进行了数据仓库的多维分析和数据挖掘。我们加载并分析了GPS数据,通过调整透明度和增加时间维度,优化了数据展示。实验还涉及线性回归、k-means聚类和k-NN分类,通过设置不同超参数,观察了模型性能。实验过程中解决了字体和环境问题,提升了代码的可读性和维护性。