如何使用极坐标绘制 scipy.hierarchy.dendrogram?
How to plot scipy.hierarchy.dendrogram using polar coordinates?
我正在尝试根据此问题调整以下资源:
Python conversion between coordinates
https://matplotlib.org/gallery/pie_and_polar_charts/polar_scatter.html
我似乎无法获得将树状图形状转换为极坐标的坐标。
有人知道怎么做吗?我知道 networkx 中有一个实现,但这需要构建一个图形,然后使用 pygraphviz 后端来获取位置。
有没有办法用 matplotlib
和 numpy
将树状图笛卡尔坐标转换为极坐标?
import requests
from ast import literal_eval
import matplotlib.pyplot as plt
import numpy as np
def read_url(url):
r = requests.get(url)
return r.text
def cartesian_to_polar(x, y):
rho = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
return(rho, phi)
def plot_dendrogram(icoord,dcoord,figsize, polar=False):
if polar:
icoord, dcoord = cartesian_to_polar(icoord, dcoord)
with plt.style.context("seaborn-white"):
fig = plt.figure(figsize=figsize)
ax = fig.add_subplot(111, polar=polar)
for xs, ys in zip(icoord, dcoord):
ax.plot(xs,ys, color="black")
ax.set_title(f"Polar= {polar}", fontsize=15)
# Load the dendrogram data
string_data = read_url("https://pastebin.com/raw/f953qgdr").replace("\r","").replace("\n","").replace("\u200b\u200b","")
# Convert it to a dictionary (a subset of the output from scipy.hierarchy.dendrogram)
dendrogram_data = literal_eval(string_data)
icoord = np.asarray(dendrogram_data["icoord"], dtype=float)
dcoord = np.asarray(dendrogram_data["dcoord"], dtype=float)
# Plot the cartesian version
plot_dendrogram(icoord,dcoord, figsize=(8,3), polar=False)
# Plot the polar version
plot_dendrogram(icoord,dcoord, figsize=(5,5), polar=True)
我刚试过这个,它更接近但仍然不正确:
import matplotlib.transforms as mtransforms
with plt.style.context("seaborn-white"):
fig, ax = plt.subplots(figsize=(5,5))
for xs, ys in zip(icoord, dcoord):
ax.plot(xs,ys, color="black",transform=trans_offset)
ax_polar = plt.subplot(111, projection='polar')
trans_offset = mtransforms.offset_copy(ax_polar.transData, fig=fig)
for xs, ys in zip(icoord, dcoord):
ax_polar.plot(xs,ys, color="black",transform=trans_offset)
你可以让树的 "root" 从中间开始,叶子在外面。您还必须向 "bar" 部分添加更多点,以使其看起来美观圆润。
我们注意到icoord和dcoord的每个元素(我就称这个seg
)有四个点:
seg[1] seg[2]
+-------------+
| |
+ seg[0] + seg[3]
两点之间的竖条可以作为直线,但我们需要在 seg[1]
和 seg[2]
之间添加更多点(横条,需要变成弧形)。
此函数将在这些位置添加更多点,并且可以在绘图函数中的 xs 和 ys 上调用:
def smoothsegment(seg, Nsmooth=100):
return np.concatenate([[seg[0]], np.linspace(seg[1], seg[2], Nsmooth), [seg[3]]])
现在我们必须修改绘图函数来计算径向坐标。一些实验导致了我正在使用的对数公式,基于另一个也使用对数刻度的答案。我在右侧为径向标签留了一个空隙,并完成了 "icoord" 坐标到径向坐标的非常基本的映射,以便标签与矩形图中的标签相对应。我不知道如何处理径向尺寸。日志中的数字是正确的,但我们可能也想映射它们。
def plot_dendrogram(icoord,dcoord,figsize, polar=False):
if polar:
dcoord = -np.log(dcoord+1)
# avoid a wedge over the radial labels
gap = 0.1
imax = icoord.max()
imin = icoord.min()
icoord = ((icoord - imin)/(imax - imin)*(1-gap) + gap/2)*2*numpy.pi
with plt.style.context("seaborn-white"):
fig = plt.figure(figsize=figsize)
ax = fig.add_subplot(111, polar=polar)
for xs, ys in zip(icoord, dcoord):
if polar:
xs = smoothsegment(xs)
ys = smoothsegment(ys)
ax.plot(xs,ys, color="black")
ax.set_title(f"Polar= {polar}", fontsize=15)
if polar:
ax.spines['polar'].set_visible(False)
ax.set_rlabel_position(0)
Nxticks = 10
xticks = np.linspace(gap/2, 1-gap/2, Nxticks)
ax.set_xticks(xticks*np.pi*2)
ax.set_xticklabels(np.round(np.linspace(imin, imax, Nxticks)).astype(int))
结果如下图:
首先,我认为您可能会受益于 this question。
然后,让我们分解一下objective:我不太清楚你想做什么,但我假设你想得到这样的东西
要渲染这样的东西,您需要能够渲染在极坐标中显示为半圆的水平线。然后,这是将水平线映射到极坐标图的问题。
首先,请注意您的半径未在此行中标准化:
if polar:
icoord, dcoord = cartesian_to_polar(icoord, dcoord)
您可以通过简单地将 icoord
重新映射到 [0;2pi] 来标准化它们。
现在,让我们尝试绘制一些更简单的东西,而不是复杂的图:
icoord, dcoord = np.meshgrid(np.r_[1:10], np.r_[1:4])
# Plot the cartesian version
plot_dendrogram(icoord, dcoord, figsize=(8, 3), polar=False)
# Plot the polar version
plot_dendrogram(icoord, dcoord, figsize=(5, 5), polar=True)
结果如下:
如您所见,极坐标代码不会将水平线映射到半圆,因此这是行不通的。让我们尝试使用 plt.polar
代替:
plt.polar(icoord.T, dcoord.T)
产生
这更符合我们的需要。我们需要先固定角度,然后我们将考虑 Y 坐标向内(虽然您可能希望它从中心到边界)。归结为这个
nic = (icoord.T - icoord.min()) / (icoord.max() - icoord.min())
plt.polar(2 * np.pi * nic, -dcoord.T)
产生以下结果
这与您需要的相似。请注意,直线保持直线,不会被圆弧取代,因此您可能希望在 for 循环中对它们重新采样。
此外,您可能会受益于单一颜色和对数刻度,以便于阅读
plt.subplots(figsize=(10, 10))
ico = (icoord.T - icoord.min()) / (icoord.max() - icoord.min())
plt.polar(2 * np.pi * ico, -np.log(dcoord.T), 'b')
我正在尝试根据此问题调整以下资源:
Python conversion between coordinates
https://matplotlib.org/gallery/pie_and_polar_charts/polar_scatter.html
我似乎无法获得将树状图形状转换为极坐标的坐标。
有人知道怎么做吗?我知道 networkx 中有一个实现,但这需要构建一个图形,然后使用 pygraphviz 后端来获取位置。
有没有办法用 matplotlib
和 numpy
将树状图笛卡尔坐标转换为极坐标?
import requests
from ast import literal_eval
import matplotlib.pyplot as plt
import numpy as np
def read_url(url):
r = requests.get(url)
return r.text
def cartesian_to_polar(x, y):
rho = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
return(rho, phi)
def plot_dendrogram(icoord,dcoord,figsize, polar=False):
if polar:
icoord, dcoord = cartesian_to_polar(icoord, dcoord)
with plt.style.context("seaborn-white"):
fig = plt.figure(figsize=figsize)
ax = fig.add_subplot(111, polar=polar)
for xs, ys in zip(icoord, dcoord):
ax.plot(xs,ys, color="black")
ax.set_title(f"Polar= {polar}", fontsize=15)
# Load the dendrogram data
string_data = read_url("https://pastebin.com/raw/f953qgdr").replace("\r","").replace("\n","").replace("\u200b\u200b","")
# Convert it to a dictionary (a subset of the output from scipy.hierarchy.dendrogram)
dendrogram_data = literal_eval(string_data)
icoord = np.asarray(dendrogram_data["icoord"], dtype=float)
dcoord = np.asarray(dendrogram_data["dcoord"], dtype=float)
# Plot the cartesian version
plot_dendrogram(icoord,dcoord, figsize=(8,3), polar=False)
# Plot the polar version
plot_dendrogram(icoord,dcoord, figsize=(5,5), polar=True)
我刚试过这个,它更接近但仍然不正确:
import matplotlib.transforms as mtransforms
with plt.style.context("seaborn-white"):
fig, ax = plt.subplots(figsize=(5,5))
for xs, ys in zip(icoord, dcoord):
ax.plot(xs,ys, color="black",transform=trans_offset)
ax_polar = plt.subplot(111, projection='polar')
trans_offset = mtransforms.offset_copy(ax_polar.transData, fig=fig)
for xs, ys in zip(icoord, dcoord):
ax_polar.plot(xs,ys, color="black",transform=trans_offset)
你可以让树的 "root" 从中间开始,叶子在外面。您还必须向 "bar" 部分添加更多点,以使其看起来美观圆润。
我们注意到icoord和dcoord的每个元素(我就称这个seg
)有四个点:
seg[1] seg[2]
+-------------+
| |
+ seg[0] + seg[3]
两点之间的竖条可以作为直线,但我们需要在 seg[1]
和 seg[2]
之间添加更多点(横条,需要变成弧形)。
此函数将在这些位置添加更多点,并且可以在绘图函数中的 xs 和 ys 上调用:
def smoothsegment(seg, Nsmooth=100):
return np.concatenate([[seg[0]], np.linspace(seg[1], seg[2], Nsmooth), [seg[3]]])
现在我们必须修改绘图函数来计算径向坐标。一些实验导致了我正在使用的对数公式,基于另一个也使用对数刻度的答案。我在右侧为径向标签留了一个空隙,并完成了 "icoord" 坐标到径向坐标的非常基本的映射,以便标签与矩形图中的标签相对应。我不知道如何处理径向尺寸。日志中的数字是正确的,但我们可能也想映射它们。
def plot_dendrogram(icoord,dcoord,figsize, polar=False):
if polar:
dcoord = -np.log(dcoord+1)
# avoid a wedge over the radial labels
gap = 0.1
imax = icoord.max()
imin = icoord.min()
icoord = ((icoord - imin)/(imax - imin)*(1-gap) + gap/2)*2*numpy.pi
with plt.style.context("seaborn-white"):
fig = plt.figure(figsize=figsize)
ax = fig.add_subplot(111, polar=polar)
for xs, ys in zip(icoord, dcoord):
if polar:
xs = smoothsegment(xs)
ys = smoothsegment(ys)
ax.plot(xs,ys, color="black")
ax.set_title(f"Polar= {polar}", fontsize=15)
if polar:
ax.spines['polar'].set_visible(False)
ax.set_rlabel_position(0)
Nxticks = 10
xticks = np.linspace(gap/2, 1-gap/2, Nxticks)
ax.set_xticks(xticks*np.pi*2)
ax.set_xticklabels(np.round(np.linspace(imin, imax, Nxticks)).astype(int))
结果如下图:
首先,我认为您可能会受益于 this question。
然后,让我们分解一下objective:我不太清楚你想做什么,但我假设你想得到这样的东西
要渲染这样的东西,您需要能够渲染在极坐标中显示为半圆的水平线。然后,这是将水平线映射到极坐标图的问题。
首先,请注意您的半径未在此行中标准化:
if polar:
icoord, dcoord = cartesian_to_polar(icoord, dcoord)
您可以通过简单地将 icoord
重新映射到 [0;2pi] 来标准化它们。
现在,让我们尝试绘制一些更简单的东西,而不是复杂的图:
icoord, dcoord = np.meshgrid(np.r_[1:10], np.r_[1:4])
# Plot the cartesian version
plot_dendrogram(icoord, dcoord, figsize=(8, 3), polar=False)
# Plot the polar version
plot_dendrogram(icoord, dcoord, figsize=(5, 5), polar=True)
结果如下:
如您所见,极坐标代码不会将水平线映射到半圆,因此这是行不通的。让我们尝试使用 plt.polar
代替:
plt.polar(icoord.T, dcoord.T)
产生
这更符合我们的需要。我们需要先固定角度,然后我们将考虑 Y 坐标向内(虽然您可能希望它从中心到边界)。归结为这个
nic = (icoord.T - icoord.min()) / (icoord.max() - icoord.min())
plt.polar(2 * np.pi * nic, -dcoord.T)
产生以下结果
这与您需要的相似。请注意,直线保持直线,不会被圆弧取代,因此您可能希望在 for 循环中对它们重新采样。
此外,您可能会受益于单一颜色和对数刻度,以便于阅读
plt.subplots(figsize=(10, 10))
ico = (icoord.T - icoord.min()) / (icoord.max() - icoord.min())
plt.polar(2 * np.pi * ico, -np.log(dcoord.T), 'b')