Ответ 1
Я не уверен, что это действительно представляет собой практический ответ, но он позволяет вам генерировать дендрограммы с усеченными подвесными линиями. Хитрость заключается в том, чтобы генерировать график как нормальный, а затем манипулировать полученным графиком matplotlib для воссоздания строк.
Я не мог заставить ваш пример работать локально, поэтому я только что создал фиктивный набор данных.
from matplotlib import pyplot as plt
from scipy.cluster.hierarchy import dendrogram, linkage
import numpy as np
a = np.random.multivariate_normal([0, 10], [[3, 1], [1, 4]], size=[5,])
b = np.random.multivariate_normal([0, 10], [[3, 1], [1, 4]], size=[5,])
X = np.concatenate((a, b),)
Z = linkage(X, 'ward')
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
dendrogram(Z, ax=ax)
Результирующий график - обычная длинношерстная дендрограмма.
Теперь за более интересный бит. Дендрограмма состоит из нескольких объектов LineCollection
(по одному для каждого цвета). Для обновления строки мы перебираем их, извлекая подробности об их составных путях, изменяющие их, чтобы удалить все строки, идущие к y
нуля, а затем воссоздает LineCollection
для этих модифицированных путей.
Обновленный путь затем добавляется к осям, а оригинал удаляется.
Одна сложная часть - определить, какую высоту нужно рисовать вместо нуля. Поскольку мы итерируем по каждому пути дендрограмм, мы не знаем, какой момент пришел раньше - мы в принципе понятия не имеем, где мы. Однако мы можем использовать тот факт, что висячие линии висят вертикально. Предполагая, что на одном и том же x
нет строк, мы можем искать известные другие значения y
для данного x
и использовать это в качестве основы для нашего нового y
при вычислении. Недостатком является то, что для того, чтобы убедиться, что у нас есть это число, мы должны предварительно сканировать данные.
Примечание. Если вы можете получить висячие линии dendrogram на одном и том же x
, вам нужно будет включить y
и выполнить поиск ближайшего y выше этого x для этого.
import numpy as np
from matplotlib.path import Path
from matplotlib.collections import LineCollection
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
dendrogram(Z, ax=ax);
for c in ax.collections[:]: # use [:] to get a copy, since we're adding to the same list
paths = []
for path in c.get_paths():
segments = []
y_at_x = {}
# Pre-pass over all elements, to find the lowest y value at each x value.
# we can use this to caculate where to cut our lines.
for n, seg in enumerate(path.iter_segments()):
x, y = seg[0]
# Don't store if the y is zero, or if it higher than the current low.
if y > 0 and y < y_at_x.get(x, np.inf):
y_at_x[x] = y
for n, seg in enumerate(path.iter_segments()):
x, y = seg[0]
if y == 0:
# If we know the last y at this x, use it - 0.5, limit > 0
y = max(0, y_at_x.get(x, 0) - 0.5)
segments.append([x,y])
paths.append(segments)
lc = LineCollection(paths, colors=c.get_colors()) # Recreate a LineCollection with the same params
ax.add_collection(lc)
ax.collections.remove(c) # Remove the original LineCollection
Результирующая дендрограмма выглядит так: