python - graficas - ¿Cómo ajustar las longitudes de rama del dendrograma en matplotlib(como en astrodendro)?
plot title font size matplotlib (1)
No estoy seguro de que esto realmente constituya una respuesta práctica, pero le permite generar dendrogramas con líneas colgantes truncadas. El truco consiste en generar la trama de manera normal, luego manipular la trama matplotlib resultante para recrear las líneas.
No pude hacer que tu ejemplo funcione localmente, por lo que acabo de crear un conjunto de datos ficticios.
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)
El gráfico resultante es el dendrograma de brazo largo habitual.
Ahora lo más interesante. Un dendrograma se compone de una serie de objetos LineCollection
(uno para cada color). Para actualizar las líneas, iteramos a través de estas, extrayendo los detalles sobre sus rutas constituyentes, modificándolas para eliminar cualquier línea que llegue a una y
de cero, y luego LineCollection
una LineCollection
para estas rutas modificadas.
La ruta actualizada se agrega a los ejes y el original se elimina.
La única parte difícil es determinar qué altura dibujar en lugar de cero. Ya que estamos iterando sobre cada ruta de dendrogramas, no sabemos qué punto vino antes, básicamente no tenemos idea de dónde estamos. Sin embargo, podemos explotar el hecho de que las líneas colgantes cuelgan verticalmente. Suponiendo que no haya líneas en la misma x
, podemos buscar los otros valores de y
conocidos para una x
dada y usarlos como la base de nuestra nueva y
al calcular. El inconveniente es que para asegurarnos de que tengamos este número, debemos escanear previamente los datos.
Nota: Si puede obtener líneas colgantes de dendrogramas en la misma x
, deberá incluir la y
y buscar la y más cercana sobre esta x para hacer esto.
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''s 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
El dendrograma resultante se ve así:
Aquí está mi gráfico resultante a continuación, pero me gustaría que se pareciera a los dendrogramas truncados en astrodendro
como this :
También hay un dendrograma de aspecto realmente genial de este documento que me gustaría recrear en matplotlib
.
A continuación se muestra el código para generar un conjunto de datos de iris
con variables de ruido y trazar el dendrograma en matplotlib
.
¿Alguien sabe cómo: (1) truncar las ramas como en las figuras de ejemplo; y / o (2) ¿usar astrodendro
con una matriz de enlaces personalizados y etiquetas?
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
import astrodendro
from scipy.cluster.hierarchy import dendrogram, linkage
from scipy.spatial import distance
def iris_data(noise=None, palette="hls", desat=1):
# Iris dataset
X = pd.DataFrame(load_iris().data,
index = [*map(lambda x:f"iris_{x}", range(150))],
columns = [*map(lambda x: x.split(" (cm)")[0].replace(" ","_"), load_iris().feature_names)])
y = pd.Series(load_iris().target,
index = X.index,
name = "Species")
c = map_colors(y, mode=1, palette=palette, desat=desat)#y.map(lambda x:{0:"red",1:"green",2:"blue"}[x])
if noise is not None:
X_noise = pd.DataFrame(
np.random.RandomState(0).normal(size=(X.shape[0], noise)),
index=X_iris.index,
columns=[*map(lambda x:f"noise_{x}", range(noise))]
)
X = pd.concat([X, X_noise], axis=1)
return (X, y, c)
def dism2linkage(DF_dism, method="ward"):
"""
Input: A (m x m) dissimalrity Pandas DataFrame object where the diagonal is 0
Output: Hierarchical clustering encoded as a linkage matrix
Further reading:
http://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.cluster.hierarchy.linkage.html
https://pypi.python.org/pypi/fastcluster
"""
#Linkage Matrix
Ar_dist = distance.squareform(DF_dism.as_matrix())
return linkage(Ar_dist,method=method)
# Get data
X_iris_with_noise, y_iris, c_iris = iris_data(50)
# Get distance matrix
df_dism = 1- X_iris_with_noise.corr().abs()
# Get linkage matrix
Z = dism2linkage(df_dism)
#Create dendrogram
with plt.style.context("seaborn-white"):
fig, ax = plt.subplots(figsize=(13,3))
D_dendro = dendrogram(
Z,
labels=df_dism.index,
color_threshold=3.5,
count_sort = "ascending",
#link_color_func=lambda k: colors[k]
ax=ax
)
ax.set_ylabel("Distance")