前言
之前去深圳慢病院搞研究,搞了一年多还欠着一篇论文…估计是很难还上了,因为寒假过后我也就半年的时间了,即将毕业成为社畜…
但是师兄还是给了一个 idea ,简单来说是将 vr 眼动的轨迹转化成热力图的形式,这样数据就从序列成为了图数据,提取特征理论上也确实更好提取一点,大概效果是这样的:
然后改成这样的形式:
这样确实可以清晰地描绘出眼动数据的注意区域~
分析这个研究不是本文要讨论的内容,本文主主要介绍如何实现出上两幅图片的转变。
核密度估计
理论
本人不太钻研理论,因此自己讲容易误人子弟,这里就引用的多一点:
https://www.bilibili.com/video/BV1ir4y1h7Pc/
如果你有一个柱状图,想用线来描绘出其趋势,那么首先需要解决的是:如何从点(离散的柱)转化成连续的线。
一个方法是,对于每一个点,我们都将其转化成一个正态分布的中心,再将这些正态分布联合起来
当然,也可以使用下述的别的分布,看诸位的需求。
这就是核密度估计的大概了,点转成线,理论上更高维也依然可以生效,二维的点可以转化成热力图,就如前言介绍的那样。
实现
我们使用的包是scipy.stats.gaussian_kde
,这玩意可以实现二维的高斯核密度估计,而且用法相当简单,直接上代码:
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
# 定义测量模型函数
def measure(n):
"""返回两个相关联的测量值."""
m1 = np.random.normal(size=n) # 生成标准正态分布数据
m2 = np.random.normal(scale=0.5, size=n) # 生成标准差为0.5的正态分布数据
return m1 + m2, m1 - m2 # 返回两个线性组合后的测量值
# 生成示例数据
m1, m2 = measure(2000)
# 计算数据的范围
xmin, xmax = m1.min(), m1.max()
ymin, ymax = m2.min(), m2.max()
# 执行核密度估计
X, Y = np.mgrid[xmin:xmax:100j, ymin:ymax:100j] # 创建网格
positions = np.vstack([X.ravel(), Y.ravel()]) # 将网格位置展平并堆叠
values = np.vstack([m1, m2])
kernel = stats.gaussian_kde(values) # 创建KDE对象
Z = np.reshape(kernel(positions).T, X.shape) # 计算密度并重塑形状
# 可视化结果
fig, ax = plt.subplots()
ax.imshow(np.rot90(Z), cmap=plt.cm.gist_earth_r, extent=[xmin, xmax, ymin, ymax]) # 显示密度图
ax.plot(m1, m2, 'k.', markersize=2) # 显示原始数据点
ax.set_xlim([xmin, xmax]) # 设置x轴范围
ax.set_ylim([ymin, ymax]) # 设置y轴范围
plt.show() # 显示图形
详细介绍一下上述的代码,首先measure
函数会为我们返回由两个正态分布产生的坐标点,对应二维图上的 x,y。这些点会被规范后直接进入我们KDE 类中进行运算:
values = np.vstack([m1, m2])
kernel = stats.gaussian_kde(values) # 创建KDE对象
这两部实际上就完成了高斯核密度估计的操作,但是我们还需要绘制热力图,这就需要我们绘制一个网格。
网格是什么?其实是一系列密集的空间上的点,举个例子:
import numpy as np
import matplotlib.pyplot as plt
# 使用 numpy.mgrid 生成二维网格
x, y = np.mgrid[-2:2:1, -2:2:1]
print(x)
print(y)
x 长这样
[[-2 -2 -2 -2]
[-1 -1 -1 -1]
[ 0 0 0 0]
[ 1 1 1 1]]
y 长这样
[[-2 -1 0 1]
[-2 -1 0 1]
[-2 -1 0 1]
[-2 -1 0 1]]
对应的就是这片矩形区域中所有整数的坐标点。
网格可以进行很多二维运算,产生密度图等效果:
import numpy as np
import matplotlib.pyplot as plt
# 使用 numpy.mgrid 生成二维网格
x, y = np.mgrid[-2:2:1, -2:2:1]
# 计算网格上的某个函数值,例如 z = x^2 + y^2
z = x**2 + y**2
# 使用 Matplotlib 进行可视化
plt.figure(figsize=(8, 6))
plt.contourf(x, y, z, levels=50, cmap='viridis') # 使用 contourf 填充等高线图
plt.colorbar(label='z value') # 添加颜色条
plt.title('2D Grid Visualization with numpy.mgrid')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
效果如下:
话说回来,上述示例代码中的这些代码为我们提供了网格:
X, Y = np.mgrid[xmin:xmax:100j, ymin:ymax:100j] # 创建网格,100j的意思是采样100个点
positions = np.vstack([X.ravel(), Y.ravel()]) # 将网格位置展平并堆叠
网格会进入运算完毕的 KDE 类中,为我们提供热力图:
Z = np.reshape(kernel(positions).T, X.shape) # 计算密度并重塑形状
这个 Z 即为拥有密度信息的向量,将会被用于制图。
上述代码运行完成后效果如下:
参考
scipy.stats.gaussian_kde的官方文档,有现成的参考代码可以直接用:
https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.gaussian_kde.html
绘制热力图:
https://www.zhihu.com/question/362018090
这篇文章适合入门核密度估计,但是使用的包是sklearn.neighbors.KernelDensity
,只适宜一维数组的处理:
https://zhuanlan.zhihu.com/p/671141343
文章评论