Python的圆形直方图

问题描述 投票:28回答:2

我有周期性数据,并且它的分布最好围绕一个圆圈可视化。现在的问题是如何使用matplotlib进行这种可视化?如果没有,可以在Python中轻松完成吗?

我的代码将演示围绕圆圈分布的粗略近似值:

from matplotlib import pyplot as plt
import numpy as np

#generatin random data
a=np.random.uniform(low=0,high=2*np.pi,size=50)

#real circle
b=np.linspace(0,2*np.pi,1000)
a=sorted(a)
plt.plot(np.sin(a)*0.5,np.cos(a)*0.5)
plt.plot(np.sin(b),np.cos(b))
plt.show()

关于qzxswpoi的SX问题中有几个例子:

python matplotlib plot visualization histogram
2个回答
34
投票

从画廊建立Mathematica示例,你可以做到

this

当然,有许多变化和tweek,但这应该让你开始。

一般来说,浏览import numpy as np import matplotlib.pyplot as plt N = 80 bottom = 8 max_height = 4 theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False) radii = max_height*np.random.rand(N) width = (2*np.pi) / N ax = plt.subplot(111, polar=True) bars = ax.bar(theta, radii, width=width, bottom=bottom) # Use custom colors and opacity for r, bar in zip(radii, bars): bar.set_facecolor(plt.cm.jet(r / 10.)) bar.set_alpha(0.8) plt.show() 通常是一个很好的起点。

在这里,我使用matplotlib gallery关键字将中心留空,因为我认为我之前看到的问题更像是我的图形,所以我认为这就是你想要的。要获得上面显示的完整楔形,只需使用bottom(或保留它,因为bottom=0是默认值)。


7
投票

我对这个问题迟了5年,但无论如何......

我总是建议在使用圆形直方图时要小心,因为它们很容易误导读者。

特别是,我建议远离圆形直方图,其中频率和半径按比例绘制。我推荐这个,因为心脏受到箱子区域的影响很大,而不仅仅是它们的径向范围。这与我们习惯于解释饼图的方式类似:按区域划分。

因此,我建议不要使用bin的径向范围来显示它包含的数据点的数量,而是建议按区域显示点数。

问题

考虑给定直方图箱中数据点数量加倍的后果。在频率和半径成比例的圆形直方图中,该区间的半径将增加2倍(因为点数加倍)。但是,这个垃圾箱的面积将增加4倍!这是因为箱的面积与半径的平方成比例。

如果这听起来不是太多问题,让我们以图形方式看待它:

0

上述两个图都可视化相同的数据点。

在左侧的图中,很容易看出(0,pi / 4)箱中的数据点数比(-pi / 4,0)箱中的数据点多两倍。

但是,请看右手图(与半径成比例的频率)。乍一看,你的思想受到垃圾箱区域的影响很大。如果(0,pi / 4)bin中的点数比(-pi / 4,0)bin中的点多两倍,那么你会被原谅。但是,你被误导了。只有仔细检查图形(和径向轴),才能发现(0,pi / 4)箱中的数据点数比(-pi / 4,0)箱中的数据点数多两倍。不超过图表最初建议的数量的两倍。

可以使用以下代码重新创建上述图形:

frequency histograms

一个办法

由于我们受到圆形直方图中区域面积的极大影响,我发现确保每个区域的面积与其中的观察数量而不是半径成比例更有效。这与我们习惯于解释饼图的方式类似,其中面积是感兴趣的数量。

让我们使用前面例子中使用的数据集来重现基于区域的图形,而不是半径:

import numpy as np import matplotlib.pyplot as plt plt.style.use('seaborn') # Generate data with twice as many points in (0, np.pi/4) than (-np.pi/4, 0) angles = np.hstack([np.random.uniform(0, np.pi/4, size=100), np.random.uniform(-np.pi/4, 0, size=50)]) bins = 2 fig = plt.figure() ax = fig.add_subplot(1, 2, 1) polar_ax = fig.add_subplot(1, 2, 2, projection="polar") # Plot "standard" histogram ax.hist(angles, bins=bins) # Fiddle with labels and limits ax.set_xlim([-np.pi/4, np.pi/4]) ax.set_xticks([-np.pi/4, 0, np.pi/4]) ax.set_xticklabels([r'$-\pi/4$', r'$0$', r'$\pi/4$']) # bin data for our polar histogram count, bin = np.histogram(angles, bins=bins) # Plot polar histogram polar_ax.bar(bin[:-1], count, align='edge', color='C0') # Fiddle with labels and limits polar_ax.set_xticks([0, np.pi/4, 2*np.pi - np.pi/4]) polar_ax.set_xticklabels([r'$0$', r'$\pi/4$', r'$-\pi/4$']) polar_ax.set_rlabel_position(90) fig.tight_layout()

我假设读者在乍一看这个图形时被误导的可能性较小。

但是,当绘制一个面积与半径成比例的圆形直方图时,我们的缺点是你永远不会知道(0,pi / 4)bin中的点数比(-pi / 4)中的点数多两倍。 0)只是通过观察区域。虽然,你可以通过用相应的密度注释每个bin来解决这个问题。我认为这种劣势比误导读者更可取。

当然,我确保在这个图旁边放置一个信息性标题来解释这里我们用面积而不是半径来显示频率。

以上图表创建为:

density histograms

把它们放在一起

如果您创建了大量的圆形直方图,您最好创建一些可以轻松重复使用的绘图功能。下面我包括我编写并在我的工作中使用的函数。

默认情况下,该函数按区域可视化,正如我所推荐的那样。但是,如果你仍然想象半径与频率成比例的箱子,你可以通过传递fig = plt.figure() ax = fig.add_subplot(1, 2, 1) polar_ax = fig.add_subplot(1, 2, 2, projection="polar") # Plot "standard" histogram ax.hist(angles, bins=bins, density=True) # Fiddle with labels and limits ax.set_xlim([-np.pi/4, np.pi/4]) ax.set_xticks([-np.pi/4, 0, np.pi/4]) ax.set_xticklabels([r'$-\pi/4$', r'$0$', r'$\pi/4$']) # bin data for our polar histogram counts, bin = np.histogram(angles, bins=bins) # Normalise counts to compute areas area = counts / angles.size # Compute corresponding radii from areas radius = (area / np.pi)**.5 polar_ax.bar(bin[:-1], radius, align='edge', color='C0') # Label angles according to convention polar_ax.set_xticks([0, np.pi/4, 2*np.pi - np.pi/4]) polar_ax.set_xticklabels([r'$0$', r'$\pi/4$', r'$-\pi/4$']) fig.tight_layout() 来实现。此外,您可以使用参数density=False设置零角度的方向,使用offset设置标签应该是度数还是弧度。

lab_unit

这个功能非常简单易用。在这里,我演示它用于一些随机生成的方向:

def rose_plot(ax, angles, bins=16, density=None, offset=0, lab_unit="degrees",
              start_zero=False, **param_dict):
    """
    Plot polar histogram of angles on ax. ax must have been created using
    subplot_kw=dict(projection='polar'). Angles are expected in radians.
    """
    # Wrap angles to [-pi, pi)
    angles = (angles + np.pi) % (2*np.pi) - np.pi

    # Set bins symetrically around zero
    if start_zero:
        # To have a bin edge at zero use an even number of bins
        if bins % 2:
            bins += 1
        bins = np.linspace(-np.pi, np.pi, num=bins+1)

    # Bin data and record counts
    count, bin = np.histogram(angles, bins=bins)

    # Compute width of each bin
    widths = np.diff(bin)

    # By default plot density (frequency potentially misleading)
    if density is None or density is True:
        # Area to assign each bin
        area = count / angles.size
        # Calculate corresponding bin radius
        radius = (area / np.pi)**.5
    else:
        radius = count

    # Plot data on ax
    ax.bar(bin[:-1], radius, zorder=1, align='edge', width=widths,
           edgecolor='C0', fill=False, linewidth=1)

    # Set the direction of the zero angle
    ax.set_theta_offset(offset)

    # Remove ylabels, they are mostly obstructive and not informative
    ax.set_yticks([])

    if lab_unit == "radians":
        label = ['$0$', r'$\pi/4$', r'$\pi/2$', r'$3\pi/4$',
                  r'$\pi$', r'$5\pi/4$', r'$3\pi/2$', r'$7\pi/4$']
        ax.set_xticklabels(label)

angles0 = np.random.normal(loc=0, scale=1, size=10000) angles1 = np.random.uniform(0, 2*np.pi, size=1000) # Visualise with polar histogram fig, ax = plt.subplots(1, 2, subplot_kw=dict(projection='polar')) rose_plot(ax[0], angles0) rose_plot(ax[1], angles1, lab_unit="radians") fig.tight_layout()

© www.soinside.com 2019 - 2024. All rights reserved.