我正在绘制分类数据的二维热图直方图,以帮助显示和解释大型数据集中的模式,而且我不知道一种有效地沿每个轴对值进行有效排序的方法,以便相关值彼此相邻出现。
例
使用PyPI下载日志中的示例(仅包含4个项目),我可以绘制项目名称与文件名称的直方图:
(请注意,我停在60个最常见的不同值,然后将其他所有值都合并到一个(其他)列中)
在这个极端的示例中,每个文件名都只与一个项目相关联,因此,如果对x值进行重新排序以使所有numpy文件彼此相邻,然后紧跟熊猫文件等...
在我的情况下,我不能保证x轴或y轴将包含更强的类别组,因此我希望找到可以沿两个轴排序的解决方案。 我还处理最大60x60的直方图,并且进行这种排序的时间预算小于500毫秒(理想情况下)。
关于正确解决方案的思考
我认为答案在于:
为所有(x,y)组合创建链接图
在链接图上运行分层聚类
基于集群的某种形式的排序。
我不清楚如何使用分层群集对每个轴重新排序。
使用相关矩阵的类似示例可以有效地处理一维数据,并将其转换为二维似乎很棘手。
当前的天真的实现
我当前的方法(某种程度上较差)是为表定义一个能量函数,该函数将各列与下一列之间的每个值的绝对差之和:
def get_energy(values):
np.abs(values[:, :-1] - values[:, 1:]).sum()
然后,对于每一行,我将计算必须交换哪些列以最终对该行中的值进行排序(相对于该行中的最高值)。 然后,对于每个交换,我都会计算交换前后的表能量。 如果能量较低,则使用交换的值更新我的表,然后继续。
最后,我转置表格,并重复以上步骤。
这种方法存在严重缺陷,因为天真的交换方法不会移动整个列,并且每个轴之间的订单之间也没有协作,因此您最终只能将我描述为零碎的内容当发生在第2列的交换发生时。破坏第1列中的值:
一个使用来自同一数据集的不同数据,比较发行版名称与发行版版本的示例(请注意,我在这里进行了一些轴标准化,否则Ubuntu将主导一切!):
未排序:
使用以上方法:
我当前的代码如下所示:(获取一个熊猫表,对numpy ndarrays进行操作):
class Reorderer:
def __init__(self, table):
self.table = table.values
self.rows = table.index.values
self.cols = table.columns.values
def get_frame(self):
return pd.DataFrame(data=self.table, index=self.rows, columns=self.cols)
def _energy(self, values):
return np.abs(values[:, :-1] - values[:, 1:]).sum()
def _get_proposals(self, row_num):
row = self.table[row_num]
swaps = row.argsort()[::-1]
basis = swaps[0]
for order, from_index in enumerate(swaps[1:], 1):
if row[from_index] == 0:
break
to_index = basis + order
if to_index >= len(self.cols):
to_index = basis - order
if from_index != to_index:
yield (from_index, to_index)
def reorder(self):
initial_swaps = self.table[0].argsort()[::-1]
self.table = self.table[:, initial_swaps]
self.cols = self.cols[initial_swaps]
for row_num in range(1, len(self.rows)):
current_energy = self._energy(self.table[:row_num+1])
for proposed_from, proposed_to in self._get_proposals(row_num):
new_order = np.arange(len(self.cols))
new_order[proposed_from] = proposed_to
new_order[proposed_to] = proposed_from
reordered = self.table[:, new_order]
new_energy = self._energy(reordered[:row_num+1])
if new_energy < current_energy:
self.table = reordered
self.cols = self.cols[new_order]
current_energy = new_energy
return self.get_frame()
必须有一个更好的方法。