在直方图上用清晰的柱状条来可视化数据?

后端开发 2026-07-12

我正在做一个直方图,所有柱子都彼此重叠在一起。即使把柱子的透明度调低,仍然看起来一团糟。我在想如果把柱子的填充设为透明、边框颜色对不同的数据集使用不同的颜色,应该会更容易看清楚吗?

但我只能弄清楚把整体透明度设为0(这也包括边框颜色,所以根本看不到柱子),或者把填充改为白色(这仍然让后面堆叠的颜色难以看清——见附件)。

我该如何实现这种带彩色轮廓的透明柱子?这真的就是展示这种效果的最佳方式吗?

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import seaborn as sns
import pandas as pd

# import excel file
excel_file_path = '/content/Chondrule Sizes (comparison).xlsx'

# read excel file
df_excel = pd.read_excel(excel_file_path, sheet_name='All Diameters (raw)')

# select data
data_for_plot_8 = df_excel['MIL 15322 (EL3)']
data_for_plot_7 = df_excel['QUE 94594 (EL3)']
data_for_plot_6 = df_excel['ALH 85119 (EL3)']
data_for_plot_5 = df_excel['MAC 88180 (EL3)']
data_for_plot_4 = df_excel['PCA 91020 (EL3)']
data_for_plot_3 = df_excel['ALH 84170 (EH3)']
data_for_plot_2 = df_excel['PCA 91085 (EH3)']
data_for_plot_1 = df_excel['PCA 91238 (EH3)']


# create plot
plt.figure(figsize=(12, 7))
bins = np.linspace(0, 3300, 67)


# plot histograms - probability density
plt.hist(data_for_plot_1, bins, alpha=0.3,label='PCA 91238 (EH3)', linewidth=1.5, color='white', edgecolor='red', density=True)
plt.hist(data_for_plot_2, bins, alpha=0.3,label='PCA 91085 (EH3)', linewidth=1.5, color='white',edgecolor='darkorange', density=True)
plt.hist(data_for_plot_3, bins, alpha=0.3,label='ALH 84170 (EH3)', linewidth=1.5, color='white',edgecolor='gold', density=True)
plt.hist(data_for_plot_4, bins, alpha=0.3,label='PCA 91020 (EL3)', linewidth=1.5, color='white',edgecolor='yellow', density=True)
plt.hist(data_for_plot_5, bins, alpha=0.3,label='MAC 88180 (EL3)', linewidth=1.5, color='white',edgecolor='green', density=True)
Data with colored bars (original)plt.hist(data_for_plot_6, bins, alpha=0.3,label='ALH 85119 (EL3)', linewidth=1.5, color='white',edgecolor='lightseagreen', density=True)
plt.hist(data_for_plot_7, bins, alpha=0.3,label='QUE 94594 (EL3)', linewidth=1.5, color='white',edgecolor='deepskyblue', density=True)
plt.hist(data_for_plot_8, bins, alpha=0.3,label='MIL 15322 (EL3)', linewidth=1.5, color='white',edgecolor='indigo', density=True)

# change y-axis to percent instead of probability density
bin_width = bins[1] - bins[0]
def to_percent(y, position):
  return f"{y * bin_width * 100:.0f}%"
plt.gca().yaxis.set_major_formatter(mtick.FuncFormatter(to_percent))

# plot format
plt.legend(loc='upper right')
plt.xlabel('Diameter (µm)')
plt.ylabel('Percent %')
plt.title('Size Frequency Distribution of Chondrule Diameters (µm)')
plt.grid(True, linestyle='--', alpha=0.6)
plt.show()

彩色柱子(原始):

白色柱子,轮廓为彩色:

解决方案

我经常会遇到这种情况,通常会直接用numpy把直方图画成曲线:

Toy setup:

import numpy as np
import matplotlib.pyplot as plt


n_sets = 7
n_samples = 10**4

scales = np.linspace(0.8, 1.2, n_sets)
data = np.random.rand(n_sets, n_samples)**scales[:,None]

bins = np.linspace(0,1,10)

带有 plt.hist 的混乱直方图:

plt.figure(1).clf()
for d in data:
    plt.hist(d, bins, alpha=0.3)

enter image description here

现在使用 np.histogram 和常规的 plt.plot

plt.figure(2).clf()
for d in data:
    counts, bins = np.histogram(d, bins)
    plt.plot(bins[:-1], counts)
plt.ylim(0, plt.ylim()[1])

enter image description here

(如果你需要让人信服 plt.histnp.histogram 确实计算出相同的结果,可以通过把 plt.plot(bins_for_plot, counts) 替换为 plt.bar(bins_for_plot, counts, bar_width, alpha=0.3)bar_width = bins_for_plot[1] - bins_for_plot[0] 来获得完全相同的结果,或者你可以直接检查 all(all(x == y) for x,y in zip(plt.hist(d, bins), np.histogram(d, bins))) 是否相同)

备选方案

这确实是一团糟,你选择寻找替代方案是对的。

我的想法是使用 violinplot,它们非常适合同时显示多种分布——这正是你现在在做的事情。问题在于这类图并不会显示每个区间,但我认为你很少真正需要这些信息。

下面给出一个非常简单的示例。它很容易进行自定义,但在matplotlib的常规绘图自定义方式上并不完全适用,参阅这个链接了解自定义方法。

import matplotlib.pyplot as plt
import numpy as np
import itertools

# Generate random data for ploting
rng = np.random.default_rng()
names = (
    'MIL 15322 (EL3)',
    'QUE 94594 (EL3)',
    'ALH 85119 (EL3)',
    'MAC 88180 (EL3)',
    'PCA 91020 (EL3)',
    'ALH 84170 (EH3)',
    'PCA 91085 (EH3)',
    'PCA 91238 (EH3)',
)
N = 10_000
data = [
    rng.uniform(30, 60) * rng.chisquare(10, size=N) for name in names
]

# Display
colors = ('red', 'darkorange', 'gold', 'yellow', 'green', 'lightseagreen', 'deepskyblue', 'indigo')

plt.close('all')
plt.title('Size Frequency distribution of TLDR')

# Create violin plot
# /!\ don't use option vert=False, use orientation='horizontal' instead.
# /!\ I use it because I have an older version of matplotlib installed.
quantiles = [0.25, 0.5, 0.75]
parts = plt.violinplot(data, quantiles=[quantiles for name in names], vert=False)

# Color the bodies
for body, color in zip(parts['bodies'], colors, strict=True):
    body.set_color(color)

# Color the bars
for lines in (
    parts['cmaxes'],
    parts['cmins'],
    parts['cbars'],
):
    lines.set_colors(colors)

# Special case: the quantile bars need to be dealt with on their own
parts['cquantiles'].set_colors(
    list(itertools.chain.from_iterable([[c] * len(quantiles) for c in colors]))
)

# Pretty ticks
plt.yticks(list(range(1, len(names)+1)), names, rotation=45, va='top')
plt.xlabel('Diameter (µm)')

plt.grid()
plt.tight_layout()
plt.show()

输出: A very simple violin plot example with random generated data

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章