用Matplotlib给‘大胃王’比赛数据画个柱形图,顺便聊聊怎么用颜色突出关键信息

当数据遇上故事,可视化就成了一场视觉盛宴。想象一下,你手头有一份1980-2010年间Nathan's热狗大胃王比赛的数据,如何让这些数字自己"开口说话"?本文将带你用Python的Matplotlib库,从零开始构建一个会讲故事的柱形图——不仅展示历年冠军的战绩,还要用颜色突出关键叙事点:美国选手的统治力。

1. 数据准备:读懂你的"食材"

在开始烹饪数据大餐前,先看看我们的原料。假设数据已整理成CSV格式,包含三个关键字段:

import pandas as pd
hot_dog = pd.read_csv("hot-dog-contest-winners.csv")
print(hot_dog.head(3))

典型的数据结构可能如下:

Year Dogs eaten Country
1980 9.1 United States
1981 11.0 Japan
1982 11.0 United States

关键检查点

  • 确认年份是否连续
  • 检查异常值(比如某年突然出现1000个热狗的离谱记录)
  • 注意国家字段的命名一致性

提示:实际项目中建议先用 hot_dog.info() 查看数据类型,用 hot_dog.describe() 快速了解数值分布。

2. 基础柱形图:搭建可视化"骨架"

让我们先用最简单的代码构建基础图形:

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(10,5))
ax.bar(x=hot_dog["Year"], height=hot_dog["Dogs eaten"])
ax.set_title("Nathan's Hot Dog Eating Contest Winners (1980-2010)")
ax.set_xlabel("Year")
ax.set_ylabel("Hot Dogs Consumed")
plt.show()

这段代码会产生一个朴素的柱形图,但存在几个明显问题:

  • 柱子太宽挤在一起
  • 没有视觉焦点
  • X轴年份显示不完整

优化方案

fig, ax = plt.subplots(figsize=(12,6))
ax.bar(
    x=hot_dog["Year"], 
    height=hot_dog["Dogs eaten"],
    width=0.6,  # 控制柱子宽度
    edgecolor="white",  # 添加边框
    linewidth=0.5
)
ax.set_xticks(hot_dog["Year"][::2])  # 隔年显示标签
ax.tick_params(axis="x", rotation=45)

3. 颜色叙事:让数据"开口说话"

现在进入最有趣的部分——用颜色传递信息。假设我们想突出显示美国选手夺冠的年份:

def assign_colors(df):
    return ["#DB7093" if country=="United States" else "#5F9F9F" 
            for country in df["Country"]]

colors = assign_colors(hot_dog)

fig, ax = plt.subplots(figsize=(12,6))
bars = ax.bar(
    x=hot_dog["Year"],
    height=hot_dog["Dogs eaten"],
    color=colors,
    width=0.6
)

颜色选择技巧

  • 主色调(美国)使用较鲜艳的粉红色(#DB7093)
  • 其他颜色使用低调的灰绿色(#5F9F9F)
  • 考虑色盲友好配色(可用ColorBrewer工具检查)

为了让颜色语义更清晰,建议添加图例:

from matplotlib.patches import Patch

legend_elements = [
    Patch(facecolor="#DB7093", label="US Champion"),
    Patch(facecolor="#5F9F9F", label="Other Countries")
]
ax.legend(handles=legend_elements, loc="upper left")

4. 高级修饰:从图表到故事

好的可视化应该引导观众发现故事。让我们添加几个增强元素:

标记破纪录年份

record_years = hot_dog[hot_dog["New record"] == True]["Year"]

for year in record_years:
    ax.annotate("★", 
                xy=(year, hot_dog.loc[hot_dog["Year"]==year, "Dogs eaten"].values[0]),
                xytext=(0,5), 
                textcoords="offset points",
                ha="center",
                fontsize=12,
                color="gold")

添加趋势线

import numpy as np

z = np.polyfit(hot_dog["Year"], hot_dog["Dogs eaten"], 1)
p = np.poly1d(z)
ax.plot(hot_dog["Year"], p(hot_dog["Year"]), 
        "--", 
        color="gray",
        alpha=0.5,
        label="Consumption Trend")

最终布局调整

ax.set_ylim(0, hot_dog["Dogs eaten"].max()*1.1)
ax.grid(axis="y", linestyle="--", alpha=0.3)
fig.tight_layout()

5. 专业技巧:让可视化更上一层楼

动态颜色映射

def color_by_value(df, threshold=10):
    """根据热狗数量设置颜色渐变"""
    norm = plt.Normalize(df["Dogs eaten"].min(), df["Dogs eaten"].max())
    cmap = plt.cm.get_cmap("RdYlGn")  # 红-黄-绿色谱
    return [cmap(norm(val)) for val in df["Dogs eaten"]]

colors = color_by_value(hot_dog)

交互式标注 (Jupyter环境适用):

from mpldatacursor import datacursor

fig, ax = plt.subplots(figsize=(12,6))
bars = ax.bar(hot_dog["Year"], hot_dog["Dogs eaten"], color=colors)
datacursor(bars, formatter=lambda **kwargs: f"{kwargs['x']}: {kwargs['y']:.1f} dogs")
plt.show()

输出高清图片

fig.savefig("hot_dog_chart.png", 
            dpi=300, 
            bbox_inches="tight",
            transparent=False)

6. 避免常见陷阱

在实际项目中,我遇到过几个典型问题:

  1. 颜色过载 :曾用5种颜色区分国家,结果变成"彩虹图"反而失去重点。现在坚持"1个焦点色+1个背景色"原则。

  2. 年份显示混乱 :当数据跨世纪时,建议使用 ax.xaxis.set_major_formatter(plt.FormatStrFormatter('%d')) 确保正确显示。

  3. 图例位置冲突 :通过 ax.legend(bbox_to_anchor=(1.05, 1)) 将图例移到图表右侧。

  4. 数值标签遮挡 :使用 ax.bar_label() 的padding参数控制标签位置:

ax.bar_label(bars, 
             labels=hot_dog["Dogs eaten"].round(1),
             padding=3,
             fontsize=8)

7. 扩展思考:可视化的叙事逻辑

同样的数据,换个颜色策略就能讲不同故事:

版本A:突出进步

  • 用颜色渐变表示成绩提升(浅色→深色)
  • 在X轴下方添加小箭头表示破纪录方向

版本B:强调国际竞争

  • 用不同颜色区分三大参赛国
  • 在顶部添加国旗图标注解

版本C:健康警示视角

  • 使用红色警示色
  • 在超过20个热狗的柱子上添加医疗十字图标
# 示例:添加警示图标
over_20 = hot_dog[hot_dog["Dogs eaten"] > 20]
for year in over_20["Year"]:
    ax.text(year, 20, "⚠️", 
            ha="center", 
            va="bottom",
            fontsize=14)

选择哪种叙事方式,取决于你想向观众传递什么信息——这就是数据可视化的艺术性所在。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐