使用-Altair-和人工智能的数据故事讲述指南-全-
使用 Altair 和人工智能的数据故事讲述指南(全)
原文:
zh.annas-archive.org/md5/5e84114267d6946828aa9cc0362743ab译者:飞龙
第一部分:介绍 Altair 和生成式 AI 到数据叙事
什么是数据叙事?你如何使用 Python Altair 实现数据驱动的叙事?生成式 AI 会为构建数据故事带来哪些好处?你将在本书的第一部分找到这些问题的答案。这一部分以渐进的方式介绍生成式 AI 在数据叙事中的应用:首先,我们将探讨 GitHub Copilot,然后在第四章,我们将讨论 ChatGPT 和 DALL-E。我选择遵循这种学习策略,因为先建立理论基础以理解主要概念,然后再利用生成式 AI 提供的各种工具进行自动化,这样做会更好。采用这种方法,你将成为生成式 AI 工具的全能大师,而不是它们的仆人。
在我们进入结合生成式 AI 的奇妙数据叙事世界之前,我想提醒你一件事:生成式 AI 是一个不断发展的领域,所以你在本书中读到的代码在你尝试运行它的时候可能已经过时了(尽管在写作时已经更新到最新版本)。然而,描述的原则始终有效,你可以查看生成式 AI 工具的官方文档来更新代码。实际上,本书中描述的代码位于一个 GitHub 仓库中,因此你可能甚至可以考虑在损坏的代码上提出问题,以保持 GitHub 仓库持续更新。这将是一种极好的合作方式,如果你这样做,我会非常感激。
在第一章中,你将了解数据叙事是什么,以及为什么你应该使用它来传达数据探索和分析中提取的见解。为了构建数据故事,你还将被介绍到数据、信息、知识、智慧(DIKW)金字塔,这是你将在整本书中使用的模型。尽管存在其他构建数据故事的模型(例如,叙事弧),但我选择 DIKW 金字塔作为本书的参考,因为我相信它更直接、更有效。它就像套娃一样,最外层的部分包含所有之前的部分,尽管每个单独的部分都可以作为一个独立对象存在。就像套娃(也称为嵌套娃娃)一样,DIKW 金字塔的每一步都可以作为一个独立(部分)故事存在;然而,只有最后一步,智慧,包含了一个完整的故事。在本章的最后部分,你将看到一个实际的应用案例,它应用了 DIKW 金字塔,这将揭示这个模型的可能性。我希望这个简单的例子能激发你的兴趣。然而,如果它没有达到你的期望,我请你耐心等待。在整个书中,你将遇到许多其他实际案例研究和例子,我希望你能将它们作为你自己在现实世界场景中的参考。
在第二章中,你将通过使用 Altair 和生成式 AI 编写你的第一个数据故事来亲自动手。目前,你将仅使用 Copilot,但请耐心等待。你将在本书的后面部分学习如何使用 ChatGPT 和 DALL-E。在这一章中,你将只看到 Copilot 在工作。你不仅将实现示例,而且将学习如何将 Copilot 作为工作工具使用,不仅限于 Altair,而且可以生成任何类型的代码。
第三章将回顾 Altair 的基本概念,以及 Altair 背后的数据可视化语法 Vega 和 Vega-Lite。你将实施实际练习和一个最终案例研究,使用 DIKW 金字塔。与之前章节中实现的案例研究相比,这个案例稍微复杂一些。在这一章的结尾,你将拥有足够的 Altair 技能,准备好使用 ChatGPT 和 DALL-E。
在第四章中,你将学习如何为 ChatGPT 和 DALL-E 构建提示,以及如何使用这些工具进行数据故事讲述。在使用它们之前,你将回顾一些与人工智能、机器学习、深度学习和生成式 AI 相关的一般概念。这将帮助你设定生成式 AI 工具的背景。在章节的最后部分,你将实施一个实际用例,展示生成式 AI 在数据故事讲述中的潜力。
如下列表所示,在每一章的结尾,你将实施一个实际案例研究,每个案例都有不同的目的:
-
第一章—这个案例研究仅是理论性的(没有代码)且关注与宠物相关的广告活动的一些统计数据。你可以在本书的 GitHub 仓库中找到这个例子的代码。
-
第二章—这个案例研究描述了一个与酒店建造新游泳池机会相关的简化决策过程。你将实现两个版本:一个使用 GitHub Copilot,另一个不使用。
-
第三章—这个案例研究聚焦于一个类似数据新闻学的例子,研究北美的人口增长。这个例子比前几章中实现的例子更高级。
-
第四章—这个案例研究再次涉及决策过程。这个案例增加了生成式 AI 到先前的案例研究中。
第一章:数据故事讲述简介
本章涵盖
-
什么是数据故事讲述
-
数据故事讲述的重要性
-
为什么您应该使用 Python Altair 和生成式 AI 工具进行数据故事讲述
-
当 Altair 和生成式 AI 工具对数据故事讲述没有帮助时
-
如何阅读这本书
-
数据、信息、知识、智慧(DIKW)金字塔
通过购买这本书,您已经决定踏上将数据故事讲述、Python 和生成式 AI 相结合的刺激之路。很可能,您已经对数据可视化有所了解,并希望学习新的技术来改进您的图表,以及如何在图表创建过程中使用生成式 AI。简单来说,通过阅读这本书,您希望学习如何使用生成式 AI 进行数据故事讲述。我马上告诉您,这本书不仅仅是那样——它不仅仅是数据故事讲述和生成式 AI 的背景知识。实际上,在整本书中,您还将学习一种方法论,这将使您能够系统地创建自己的基于数据的叙事。所以,这不会是一本又一本的代码和示例实现的书,但在这里和那里,我会为您提供实现您自己的数据驱动故事的理论基础。这本书是理论与实践的结合,这将使您能够在其他环境中应用所学的技术。
您将在整本书——以及本章——中遇到的示例将至关重要,因为它们旨在尽可能简单易懂地理解一种工作方法。在书的结尾,您将意识到,您将不仅仅实现了示例,您还将学会一种工作方法论。因此,我请求您不要因为示例的简单而感到失望,而要看看它们背后的方法论以及您如何在您的工作中应用它,这肯定比书中描述的示例要复杂得多。所以,让我们开始我们的旅程!
1.1 数据故事讲述的艺术
数据故事讲述是通过将数据转化为叙事故事来分享数据洞察的有力方式。这是一种您可以在任何行业中使用的艺术,例如政府、教育、金融、娱乐和医疗保健。数据故事讲述不仅针对数据科学家和分析师;它是针对任何曾经想要用数据讲故事的人。
要从数据可视化过渡到数据叙事,你需要改变视角。不是从你的角度看待数据,而是要从你将要告诉的人的角度看待数据——换句话说,就是观众。图 1.1 展示了通过熟练的数据科学家(左侧代表)和热切期待的观众(右侧代表)的眼睛看数据到预期观众的过程。流程包括三个主要阶段:数据探索、分析和展示。每个方框的大小反映了分配给其相应阶段的时间量。虽然数据科学家和观众有共同的目标(即真正掌握数据的本质),但他们实现这一目标的方式各不相同。数据科学家在数据探索阶段理解数据,而观众在数据展示阶段成为焦点。

图 1.1 从数据科学家的视角(左侧)和观众视角(右侧)看数据科学流程
数据叙事可以帮助数据科学家和分析师向观众展示和传达数据。你可以将数据叙事视为数据科学生命周期的压轴大戏。它包括将前几个阶段的结果转化为一个叙事,有效地将数据分析的结果传达给观众。而不是依赖于枯燥的图表,数据叙事使你能够让你的数据生动起来,并有力、有说服力地传达见解。数据叙事给观众提供了一个机会,在追求目标的过程中感受情绪,体验成功和挫折。
更正式地说,数据叙事构建引人入胜的故事,这些故事以数据为支撑,使得分析师和数据科学家能够有趣且互动地展示和分享他们的见解。数据叙事的最终目标是吸引观众并激发他们做出决策。在某些情况下,包括商业场景,数据并非第一步;首先,你心中有一个叙事或假设。然后,你寻找证实或否定它的数据。在这种情况下,你仍然可以进行数据叙事,但必须注意不要为了支持你的假设而篡改你的数据。知名的故事讲述培训顾问布伦特·戴克斯(Brent Dykes)提出了以下方法:“无论何时,如果你从叙事而不是数据开始,就需要自律和开放的心态。在这些场景中,风险的一个来源将是确认偏差。你可能会倾向于挑选证实你观点的数据,而忽略与你观点相冲突的数据。”(戴克斯,2023)请记住,在构建你的数据故事时,要基于准确和无偏见的数据分析。此外,始终考虑你正在分析的数据。
不久前,我有机会参与一个文化遗产项目,该项目要求我自动分析从大约 1700-1800 年间的名字登记册的转录文本中的实体。目标是计算登记册中人物的某些统计数据,例如最常见的名字、每年出生人数等。坐在我的电脑前,我计算并可视化了数据统计。该项目还涉及将这些人物与他们的坟墓联系起来,以构建一个交互式墓地地图。在项目进行到某个阶段时,我有机会参观了墓地。当我走过那里时,一排排的墓碑让我停下脚步。这让我感到震惊:那些刻在石头上的每一个名字都代表了一个生命。突然间,我一直在数据集中仔细研究的数据和统计数据不再仅仅是数据点——它们是真实人物的 stories。这是一个强大的认识,改变了我对待工作的方式。那时,我发现了数据故事讲述的真正力量。这不仅仅是创建花哨的图表和图表——这是让数据背后的人活过来。我们有一个使命,给这些人一个声音,确保他们的故事被听到。这正是数据故事讲述所做的事情;它给那些通常深埋在数字中的人一个声音。作为数据故事讲述者,我们的使命是将这些故事带到前台,确保它们被清晰地听到。
在这本书中,你将学习两种将数据转化为故事的技术:Python Vega-Altair(或简称 Altair)(altair-viz.github.io/)和生成式 AI 工具。Python Altair 是一个用于数据可视化的 Python 库。与最知名的 Python 库(如 Matplotlib 和 Seaborn)不同,Altair 是一个声明性库,其中你只需指定你想要在可视化中看到的内容。这个方面对于快速构建数据故事而无需关心如何构建可视化是有益的。Altair 还支持图表交互性,因此用户可以直接探索数据和与之交互。
生成式 AI 是你在本书中构建数据故事的第二种技术。我们将重点关注 ChatGPT 生成文本、DALL-E 生成图像和 GitHub Copilot 自动生成 Altair 代码。我选择使用 GitHub Copilot 生成代码,而不是 ChatGPT,因为 Copilot 是用特定领域的文本训练的,包括 GitHub 和 Stack Overflow 代码。相反,ChatGPT 更通用。在撰写本文时,生成式 AI 是一项非常新的技术,仍在发展中,它将规格说明或操作的描述翻译成文本。
数据叙事不仅仅是关于传达数据;它是关于激励你的听众并邀请他们采取行动。良好的数据叙事需要艺术和科学的结合。艺术在于找到合适的故事,而科学在于理解如何使用数据来支持这个故事。当做得好时,数据叙事可以成为变革的有力工具。在本节的剩余部分,我们将简要介绍关于数据叙事的三个基本问题。
1.1.1 为什么你应该使用数据叙事?
数据叙事使你能够通过易于被听众理解的叙事来传达你的数据分析过程的结果。在本书中,我们将看到许多示例和案例研究。例如,你将看到如何将图 1.2 中的原始图表转化为图 1.3 中所示的数据故事。

图 1.2 原始图表的一个示例

图 1.3 将图 1.2 中的原始图表转化为数据故事
数据叙事允许你填补仅仅可视化数据和向听众传达数据之间的差距。数据叙事可以提高你的沟通技巧,并使传达结果的过程标准化和简化,使人们更容易理解和记住信息。数据叙事还有助于你学会更有效地与他人沟通,改善个人和职业关系。
如果你想要做以下任何一项,请使用数据叙事:
-
专注于你想要传达的信息,并使数据更易于理解和相关。
-
以清晰和有说服力的方式将你的发现传达给他人。
-
在情感层面上与你的听众建立联系,这会使他们更有可能采取行动。
-
通过帮助他们更深入地理解你的数据,激励听众做出更好的决策。
1.1.2 数据叙事可以解决哪些问题?
如果你想要以撰写报告、做演示或构建仪表板的形式向听众传达某些信息,请使用数据叙事。
撰写报告
想象你必须为一家零售公司撰写一份销售报告。与其展示原始数据和图表,不如编织一个关于不同产品类别表现的数据故事。首先,确定数据中最关键的部分,例如最畅销的产品、新兴趋势或季节性波动。然后,结合可视化、轶事和逻辑叙事流程来呈现信息。
你可以围绕你的数据构建一个故事,例如通过介绍问题、建立悬念并以可操作的推荐作为结论。在撰写报告时,使用数据叙事来突出你数据中最重要的一部分,使你的报告更具吸引力和易于理解。
做演示
考虑一个市场营销演示,你必须展示各种营销活动的有效性。与其用众多图表和统计数据轰炸观众,不如专注于创建一个引人入胜的叙述,引导他们了解活动的展开过程及其对目标受众的影响。最后,展示可能的下一步行动。在演示中,使用数据叙事来吸引观众的注意力,并帮助他们更好地理解你的信息。
构建仪表板
让我们假设你正在为一家零售公司开发销售绩效仪表板。与其展示一个杂乱无章的界面和压倒性的数据,不如专注于引导用户通过一个叙述,突出关键洞察。在构建仪表板时,使用数据叙事来构建更用户友好和富有信息量的仪表板。
1.1.3 数据叙事的挑战有哪些?
构建引人入胜的数据故事并非易事。这需要时间来确保充满有价值信息的引人入胜的叙述。此外,它是一个团队努力,因为它涉及到将来自不同背景的个人聚集在一起,每个人都有自己的专业知识和观点,共同合作。这种合作可能具有挑战性,但对于编织一个统一且具有影响力的数据故事至关重要。
创建数据故事涉及两个关键挑战:时间和团队合作。在这些领域进行投资对于吸引观众和有效传达洞察力至关重要。
现在我们已经讨论了何时使用数据叙事,它可以解决的问题以及它独特之处,我们准备考虑与我们的两个工具相关的问题:Python Altair 和生成式 AI 工具。我们将在下一节中这样做。
1.2 为什么你应该使用 Python Altair 和生成式 AI 进行数据叙事?
Python 为您提供了许多数据可视化库。其中许多,包括 Matplotlib 和 Seaborn,都是命令式库,这意味着你必须精确地定义你想要如何构建可视化。Python Altair 相反,是一个声明式库,这意味着你只需指定要可视化的内容。使用 Python Altair 进行数据叙事而不是其他命令式库,可以使你快速构建可视化。
例如,要使用 Matplotlib 绘制折线图,你必须明确指定 x 和 y 坐标,设置图表标题和标签,并自定义外观。
列表 1.1 命令式库
import matplotlib.pyplot as plt
x = [1, 2, 3, 4, 5]
y = [1, 4, 9, 16, 25]
plt.plot(x, y)
plt.title('Square Numbers')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
注意:该图表在 Matplotlib 中构建折线图。您必须定义构建图表的单个步骤:(1)设置标题,(2)设置 x 轴,(3)设置 y 轴。
声明式可视化库,如 Altair,使您能够定义所需的输出,而不必指定实现它的确切步骤。例如,使用 Altair,您可以简单地定义数据,定义 x 和 y 变量,然后让库处理其余部分,包括轴、标签和样式,从而实现更简洁和直观的代码。
列表 1.2 声明式库
import altair as alt
import pandas as pd
df = pd.DataFrame({'x': [1, 2, 3, 4, 5], 'y': [1, 4, 9, 16, 25]})
chart = alt.Chart(df).mark_line().encode(
x='x',
y='y'
).properties(
title='Square Numbers'
)
chart.save('chart.png')
注意:该图表使用 Altair 构建折线图。您必须定义图表类型(mark_line)、变量和标题。
在命令式 Matplotlib 中,您必须以某种方式定义坐标轴,然后让库处理它;然而,在声明式库中,您使用带有坐标轴标签和样式的 x 和 y 变量来定义代码,这样,作为创作者,您可以在您打算创建的图表中更加具体和有方向性,并扩展您的图表。
生成式 AI 是人工智能技术的一个子集,涉及根据现有数据中的模式和示例创建新的、原创的内容。它使计算机能够生成逼真且有意义的结果,如文本、图像甚至代码。在这本书中,我们将重点关注 ChatGPT 生成文本,DALL-E 生成图像,以及 GitHub Copilot 在编码时的辅助:
-
ChatGPT—由 OpenAI 开发的高级语言模型。由 GPT-3.5 或 GPT-4 模型驱动,旨在进行类似人类的对话并提供智能响应。
-
DALL-E—由 OpenAI 创建的生成式 AI 模型。它结合了 GPT-3 的强大功能和图像生成能力,使其能够根据文本描述创建独特且逼真的图像。
-
GitHub Copilot—由 OpenAI Codex 驱动的全新工具,在您编写代码时提供辅助。在 GitHub Copilot 中,您描述软件必须运行的序列动作,GitHub Copilot 将其转换为您的首选编程语言的可运行代码。使用 GitHub Copilot 的能力包括学习如何描述动作序列。GitHub Copilot 是一个付费工具,但您可以将本书中描述的概念应用于其他流行的 AI 代码助手。
结合 Python Altair 和生成式 AI 工具将使您能够更快速、直接地在 Python 中编写引人入胜的数据故事。例如,我们可以使用 Copilot 帮助我们生成必要的代码片段,例如导入所需的库、设置绘图和标注坐标轴。此外,Copilot 的上下文理解能力有助于它提出相关的定制选项,例如添加图例或更改配色方案,从而节省在查找文档或示例上的时间和精力。
尽管生成式 AI 工具仍处于早期阶段,但一些有希望的统计数据表明它们提高了工人的生产力。Duolingo(最大的语言学习应用之一)的一项研究表明,他们公司使用 Copilot 后,开发者的速度提高了 25%(Duolingo 和 GitHub Enterprise,2022)。Compass UOL(一家数字媒体和技术公司)进行了一项另一项研究,要求经验丰富的开发者测量在三个不同时期完成用例任务(分析、设计、实施、测试和部署)所需的时间:在没有使用 AI 的情况下;在 AI 的可用性广泛之前;利用 2022 年可用的 AI 工具;以及使用现代生成式 AI 工具,如 ChatGPT。结果显示,开发者在使用 AI 之前需要 78 小时完成任务,使用 2022 年 AI 工具需要 56 小时,而使用新的生成式 AI 只需要 36 小时。与 AI 之前的时代相比,使用新的生成式 AI 的速度提高了 53.85%(图 1.4)。

图 1.4 Compass UOL 进行的测试结果
1.2.1 在数据科学项目生命周期的所有步骤中使用 Python 的好处
许多数据科学家和分析员使用 Python 来分析他们的数据。因此,在 Python 中构建基于分析数据的最终报告应该是自然而然的。然而,数据科学家和分析员通常只在数据科学项目生命周期的核心阶段使用 Python。然后,他们转向其他工具,如 Tableau 和 Power BI,来构建最终报告,如图 1.5 所示。这需要添加其他工作,包括从 Python 导出数据并将其导入外部应用程序。这种导出/导入操作本身并不昂贵,但如果在构建报告的过程中你意识到你犯了一个错误,你需要修改 Python 中的数据,然后再次导出数据。如果这个过程重复多次,可能会显著增加开销,直到变得难以管理。

图 1.5 在传统方法中,数据科学家在数据科学项目生命周期的不同阶段使用不同的技术。
本书使数据科学家和分析员能够在 Python 中运行数据科学项目生命周期的每个步骤,填补了在项目生命周期的最后阶段将数据导出到外部工具或框架的空白,如图 1.6 所示。仅使用 Python 的优势在于,程序员可以在实验的中间阶段构建他们的报告,而无需浪费时间将数据传输到其他工具,如 Tableau 或 Power BI。

图 1.6 在本书提出的方法中,数据科学家在整个数据科学项目生命周期的所有阶段使用相同的技术。
1.2.2 使用生成式 AI 进行数据故事讲述的好处
通常,您可以在数据科学项目的整个生命周期中使用生成式 AI 作为辅助工具。然而,在这本书中,我们将仅关注数据展示阶段的生成式 AI,这对应于数据故事讲述阶段。
将生成式 AI 工具引入数据展示阶段,可以帮助您将节省下来的努力和时间投入到数据展示阶段,从而获得更好的结果。多亏了生成式 AI 工具,您可以让观众理解您的数据(图 1.7)。现在我们已经讨论了本书中选择的工具的优点,接下来我们将简要讨论这些工具不太有效的情境。

图 1.7 在数据展示阶段引入生成式 AI 可以帮助您在更短的时间内构建更好的图表,使观众能够理解您的信息。
1.3 当 Altair 和生成式 AI 工具对数据故事讲述无益时
虽然 Python Altair 和生成式 AI 工具在快速构建数据故事方面很方便,但它们在分析大数据(如数 GB 的数据)时并不有用。您不应将它们用于以下任务:
-
复杂的数据探索性分析 — 数据探索性分析帮助数据分析师总结数据集的主要特征,识别变量之间的关系,并检测异常值。这种方法通常用于处理大型数据集或具有许多变量的数据集。
-
大数据分析 — 大数据分析通过分析大型数据集来揭示模式和趋势。为了有效,大数据分析需要访问大量数据、用于处理这些数据的强大计算机以及用于分析这些数据的专用软件。
-
复杂的大数据分析报告 — 生成详细报告需要强大的数据处理能力和先进的报告工具,尤其是在处理大数据时。
Altair 使您能够快速使用最多 5,000 行的数据集构建图表。如果行数超过 5,000,Altair 仍然会构建图表,但速度会变慢。对于复杂的数据分析,请使用更高级的分析平台,例如 Tableau 和 Power BI。尽管我们说这个组合不适合大数据,但如果通过数据预处理将数据量降至 5,000 行以下是可行的,那么 Altair 和生成式 AI 可以成为您讲述故事的不错组合。此外,考虑到您需要支付费用来使用生成式 AI 工具,所以如果您预算不足,请避免使用它们。
1.4 使用数据、信息、知识、智慧金字塔进行数据故事讲述
本书的主要焦点是一个重要的概念,称为数据、信息、知识、智慧(DIKW)金字塔(图 1.8),我们认为它为数据科学家和分析师提供了构建数据故事的宏观步骤。我们将在第五章更详细地介绍 DIKW 金字塔。我们在这里介绍 DIKW 金字塔,是因为使用它来构建数据故事是本文的基本概念。

图 1.8 DIKW 金字塔
DIKW 金字塔提供了将数据转化为智慧的宏观步骤,遵循其他中间步骤,包括信息和知识。它由以下元素组成:
-
数据——金字塔底部的基石。通常,我们从大量数据开始,或多或少已经清洗过。
-
信息——涉及从数据中提取见解。信息代表经过组织和处理、易于理解的数据。
-
知识——通过定义数据背景的上下文解释和理解的信息。
-
智慧——富含特定伦理知识的知识,邀请你以某种方式采取行动。智慧还提出了在理解数据之后的下一步行动。
本书描述了如何使用 DIKW 金字塔的元素作为渐进步骤,将你的数据转化为引人入胜的数据故事。这个想法在数据故事讲述中并不新颖;Berengueres 和 Sandell 在 2019 年提出了这种方法。本书的新颖之处在于 DIKW 金字塔、Python Altair 和生成式 AI 的结合使用。在本节中,我们将介绍本书将应用的基础 DIKW 知识,并探讨如何攀登金字塔的每一层。
1.4.1 从数据到信息
要将数据转化为信息,从数据中提取见解。考虑以下场景:一个专门针对宠物的活动的组织者正在收集将参与活动的宠物的类型。对于每个宠物类别,组织者在专门针对该类别的特定网站上宣传活动。组织者要求你构建一份关于当前情况的快速报告。表 1.1 显示了按类型划分的参与者和宣传网站的数量。首先,你关注参与者的数量,并构建以下条形图(图 1.9)。

图 1.9 展示参与活动宠物数量的条形图
表 1.1 活动中涉及的宠物相关数据
| 宠物 | 参与者数量 | 宣传网站数量 |
|---|---|---|
| 猫 | 1138 | 150 |
| 狗 | 130 | 28 |
| 其他 | 17 | 147 |
图 1.9 并没有为表 1.1 增加任何见解。它只是表格的视觉表示。对于某些数据,表格甚至比条形图更清晰。
要将数据转化为信息,首先要理解数据。提出以下问题:
-
我的故事的受众是谁?
-
他们需要什么信息?
-
所有数据都与回答上述问题相关吗?
让我们来回答这些问题。活动的组织者是故事的受众。他们想了解参与活动的动物当前的情况。我们可以假设他们想制定一个推广计划,以增加参与活动的宠物数量。
观察表中的原始数据,你会发现猫类别有最多的参与者,其次是相对较少的狗。根据几乎完全没有参与者的情况来看,其他宠物的所有者似乎对活动不感兴趣。你可以利用这个洞察力来关注以下选项之一:
-
删除“其他”类别,因为它与主题无关。
-
专注于“其他”类别,并提出增加其参与度的策略。
让我们关注第一种可能的行动方案:删除“其他”类别。首先计算每个类别的参与者百分比,如表 1.2 所示。
表 1.2 与活动相关的宠物数据,重点关注参与者数量的百分比
| 宠物 | 参与者数量 | 百分比 |
|---|---|---|
| 猫 | 1,138 | 88.56 |
| 犬 | 130 | 10.12 |
注意到 88.56%的宠物是猫,10.12%是狗。通常,最终观众不会对更详细的细节感兴趣,因此可以将猫近似为 90%,狗近似为 10%。提取的信息——你数据中的洞察力——是每 10 只宠物中有一只是狗,而有 9 只是猫。
考虑以下描述该情况的堆积柱状图(图 1.10)。该图是自我解释的,因为观众可以立即理解情况。你已经从数据中提取了信息(因此,洞察力)。

图 1.10 展示了当前参与活动的宠物百分比堆积柱状图
1.4.2 从信息到知识
要将信息转化为知识,添加上下文。上下文包括围绕数据的所有环境,如事件、情况和其他细节。添加上下文有助于观众更好地理解数据。
考虑之前关于狗和猫的例子。你已经知道猫的百分比大于狗的百分比。在此处添加上下文可能包括,例如,描述那些有动机的狗主人不参加的活动和情况。让我们关注数据集的第三列,这在表 1.1 中描述过,并在表 1.3 中回顾过:宣传网站的数量。对于猫,这个数字是 150,对于狗,这个数字是 28。这些信息被认为是上下文,因为它帮助观众理解为什么猫的数量高于狗的数量。
计算参与人数与宣传网站数量的比率,以了解参与率。表 1.3 显示了计算出的值。
表 1.3 与活动相关的宠物数据,重点关注参与率
| 宠物 | 参与者数量 | 百分比 | 宣传网站数量 | 参与者到网站比率(参与者/网站数量) |
|---|---|---|---|---|
| 猫 | 1138 | 88.56 | 150 | 7.59 |
| 犬 | 130 | 10.12 | 28 | 4.64 |
猫的参与率到网站的比例是 7.59,几乎接近 8。狗的参与率是 4.64,几乎接近 5。参与率有助于观众了解与活动参与人数相关的背景信息。
图 1.11 显示了图 1.10 的堆叠柱状图,并丰富了背景信息。背景信息包括以下要素:
-
标题——总结图表内容
-
标题——描述了广告网站的数量
-
两张图片——一张用于猫,另一张用于狗,以方便阅读

图 1.11 一个堆叠柱状图,显示了当前参与活动的宠物百分比,并丰富了背景信息
图 1.11 中的图表所描述的内容比图 1.9 中的图表更容易阅读和理解。向图表中添加背景信息使我们能够将信息转化为知识。
1.4.3 从知识到智慧
将知识转化为智慧意味着添加行动号召,邀请观众利用所发现的知识采取行动。行动号召是帮助推动转化的一种非常有效的方式。以下是一些例子:
-
我们可以做出哪些改变?
-
我们有哪些机会?
-
我们的项目有哪些优势?
-
我们可以概述哪些场景?
-
有哪些场景的例子?
或者,你可以提出对这些问题的可能解决方案,或者邀请观众讨论并听取他们对这些问题的建议和答案。讨论就发生在这里。
你的观众经常需要机会表达他们的意见和提出建议。有时,他们甚至可能有一个或多个问题。
再次考虑猫和狗的例子。为了添加行动号召,将标题从10 只宠物中有 9 只是猫改为增加与狗相关的网站上的广告活动! 图 1.12 显示了结果图表,其中在标题中添加了行动号召。

图 1.12 一个堆叠柱状图,显示了当前参与活动的宠物百分比,并添加了行动号召
宠物场景演示了如何将数据转化为智慧。首先,我们取了包含活动参与人数原始数据的资料。然后,我们提取了告诉我们 10 个参与者中有 1 个是狗,其余 9 个是猫的信息。接下来,我们添加了一个背景信息,解释了为什么狗主人对活动不感兴趣。动机是狗的广告活动做得不好。最后,我们添加了一个行动号召,邀请观众增加狗的广告活动。
在本章中,你已经学习了数据故事讲述背后的基本概念以及如何使用 DIKW 金字塔将数据转化为故事。在下一章中,你将看到如何使用 Python Altair 和生成式 AI 来实现 DIKW 金字塔方法。
摘要
-
数据叙事是一个强大的工具,帮助你更有效地传达你的数据。当正确使用时,数据叙事使复杂的数据更具相关性,更容易理解。
-
数据叙事需要改变你的视角,从你的观点转变为受众的观点。
-
Python Altair 和生成式 AI 是创建令人惊叹的数据故事的优秀工具。
-
如果你想进行大数据分析或编写总结大数据的复杂报告,不要使用 Python Altair 和生成式 AI。
-
使用 DIKW 金字塔将你的数据转化为智慧。从原始数据开始,通过给数据添加意义来提取信息。接下来,添加上下文来定义知识。最后,包括行动来提供智慧。
参考文献
-
Berengueres, J. 和 Sandell, M. (2019). 数据可视化与叙事入门:数据科学家的指南. 自出版。
-
Compass UOL (2023). 生成式 AI 加速软件开发:Compass UOL 研究. Compass.
blog.compass.uol/noticias/generative-ai-speeds-up-software-development-compass-uol-study/. -
Duolingo 和 GitHub Enterprise (2022). Duolingo 通过 GitHub Copilot 赋予工程师成为专业知识倍增器的力量. Codespaces.
github.com/customer-stories/duolingo. -
Dykes, B. (2023). 在 LinkedIn 上讨论数据故事是否可以从叙事而不是数据开始:
www.linkedin.com/feed/update/urn:li:activity:7061798908565336065/.
第二章:在 Altair 和 GitHub Copilot 中运行您的第一个数据故事
本章涵盖
-
介绍 Altair
-
相关用例:描述场景
-
使用 Altair
-
使用 Copilot
我的独特之处在于立即动手实践。如果我没有立即感受到,我就不会开心。要开始使用软件或任何其他东西,我立即会去“入门”部分。当我需要更多细节时,我会查阅文档。这一章节也是基于同样的想法产生的:立即了解事物的工作原理。我们将查看书中将要学习的大致但完整的草图。在本章中,我们将探讨 Altair 背后的基本概念,然后我们将实现一个实际的应用案例,这将使我们能够将原始数据集转化为故事。我们将逐步应用数据、信息、知识、智慧(DIKW)金字塔原则于 Altair,并逐步查看所取得的结果。在章节的第二部分,我们将使用 Copilot 自动化故事创建过程的一些步骤。我们将只关注 Copilot 作为生成式 AI 工具,以保持章节简单且流程易于理解。在接下来的章节中,我们将介绍 ChatGPT 和 DALL-E 到 DIKW 金字塔。
2.1 介绍 Altair
Vega-Altair 库(简称 Altair)是一个基于 Vega 和 Vega-Lite 可视化语法的声明式 Python 库,用于统计可视化。Vega 是一个用于创建表达性和交互式数据可视化的可视化语法。Vega-Lite 是建立在 Vega 之上的高级声明式语言,旨在通过简洁直观的语法简化常见数据可视化的创建。我们将在下一章中讨论 Vega 和 Vega-Lite。
Altair 是一个声明式库,这意味着您可以描述预期的图表结果,而不是需要手动编程每个步骤,定义一个简洁直观的语法来创建交互式可视化。我们使用声明式库来指定我们希望在图表中看到的内容。我们可以指定我们想要的数据和可视化类型,然后库会为我们自动创建可视化。声明式库与命令式库形成对比,后者更侧重于手动构建可视化(例如,指定所需的轴、大小、图例和标签)。Matplotlib 是命令式库的一个例子。
每个 Altair 图表至少包含三个主要元素:图表、标记和编码。我们将在接下来的页面中简要介绍这些内容。有关安装 Altair 和本章中描述的其他 Python 库的说明,请参阅附录 A。有关本章中使用到的 pandas DataFrame 方法的更多详细信息,请参阅附录 B。
2.1.1 图表
在 Altair 中,图表是一个对象,它作为在 Altair 中构建和自定义交互式可视化的起点。使用alt.Chart()方法输入您想要可视化的数据集,如列表 2.1 所示。您可以在本书的 GitHub 仓库中找到此示例(02/bar-chart.py)。首先,克隆本书的 GitHub 仓库:mng.bz/PZVP。要开始使用 GitHub,请遵循本书附录 A 中描述的程序或 GitHub 官方文档(mng.bz/1Geg)。
列表 2.1 Altair 中的图表
import altair as alt
import pandas as pd
data = {'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 35],
'City': ['New York', 'Paris', 'London']
} #1
df = pd.DataFrame(data) #2
print(df) #3
chart = alt.Chart(df) #4
1 为 DataFrame 创建数据
2 从数据创建 DataFrame
3 打印 DataFrame
4 创建图表
注意:首先,导入所需的库,然后构建包含您的数据的 DataFrame。最后,将 DataFrame 传递给alt.Chart()。此示例没有创建任何可视化,因为我们没有指定我们想要表示的内容。请注意,代码仍然不完整,所以如果您尝试运行它,它将不会工作。请耐心等待。在下一节中,您将添加其他部分以运行它!
2.1.2 标记
标记定义了如何表示数据。标记的示例包括柱状图、线状图、面积图等。要指定标记,将其附加到 Altair 图表。例如,在下面的列表中,我们将指定我们想要使用柱状图。
列表 2.2 在 Altair 中标记
chart = alt.Chart(df).mark_bar()
注意:使用mark_bar()在 Altair 中创建柱状图。
图表尚未准备好,因为您需要其他部分才能显示它。然而,为了有一个概念,图 2.1 显示了最终图表将如何看起来。

图 2.1 柱状图的示例
还有其他标记的示例,如mark_line(),用于绘制线状图(图 2.2)。此外,mark_circle()绘制散点图(图 2.3)。

图 2.2 线状图的示例

图 2.3 散点图的示例
2.1.3 编码
编码指定 DataFrame 列与其在 Altair 图表中的视觉表示之间的映射。在实践中,编码定义了数据在哪里在图表中表示,例如它们的位置、大小和颜色。在实践中,编码定义了数据属性如何映射到图表中的视觉属性。例如,您可以将数据集的x值编码为x轴上的位置,将y值编码为 y 轴上的位置。在 Altair 中,通道指的是一个视觉属性,如x、y、color、size和shape,可以用来在图表中表示数据属性。
每个通道都与一种数据类型相关联,它描述了属性包含的数据类型,例如对于数值数据使用定量,对于有序分类数据使用有序,或者对于无固有顺序的分类数据使用名义。例如,在下面的列表中,我们使用编码来指定 DataFrame 中必须用于 x 轴和 y 轴的列。
列表 2.3 Altair 中的编码
import pandas as pd
import altair as alt
data = {'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 35],
'City': ['New York', 'Paris', 'London']
} #1
df = pd.DataFrame(data) #2
# Create the chart
chart = alt.Chart(df).mark_bar(
).encode(
x = 'Age:Q',
y = 'Name:N'
)
chart.save('bar-chart.xhtml')
1 创建 DataFrame 的数据
2 从数据创建 DataFrame
注意:在 Altair 中使用encode()定义编码。将df的Age变量放在 x 轴上,将df的Name变量放在 y 轴上。将Age解释为数量(Q),将Name解释为名义值(N)。要显示图表,请使用save()方法将其保存为 HTML 文件。或者,如果你使用的是 Jupyter Notebook,只需写出图表变量的名称即可在行内显示图表。
图 2.4 显示了列表 2.4 中代码生成的图表。现在你已经学习了 Altair 背后的基本概念,我们将回顾在 Altair 和 Copilot 中实现实际场景的先决条件。

图 2.4 由列表 2.3 生成的图表
练习 1
使用列表 2.4 中的 DataFrame 绘制图 2.2(折线图)和图 2.3(散点图)。
列表 2.4
df = pd.DataFrame({
'X' : [2,3,5,6,7,9,4,5,5,6],
'Y' : [4,5,6,7,8,9,5,6,3,5]
})
练习的解答可以在本书的 GitHub 仓库下的 02/line-chart.py 和 02/scatter-plot.py 找到。
2.2 用例:描述场景
假设我们是在 2019 年底,在 COVID-19 大流行之前。你是一名葡萄牙酒店的数据分析师。你的老板经历了酒店游客到达人数的增加,并希望投资建造一个新的游泳池。在投资之前,他们要求你研究这一现象,以了解他们的投资是否会成功。你的任务是查看葡萄牙在近年来相对于全球趋势是否看到了游客到达人数的增加。
你开始搜索并找到一个关于旅游住宿设施到达情况的数据库(mng.bz/2KOX),这是由欧盟统计局发布的公开数据。为了简化,在本章中,你只考虑一个数据集,但在实际应用场景中,你应该考虑更多的数据,包括访问葡萄牙的游客中游泳池的受欢迎程度、在酒店新建游泳池的成本以及新游泳池可能为酒店带来的潜在收入。
该数据集包含了自 2019 年以来所有欧洲国家旅游住宿设施到达的数量。为了简化,只需关注以下国家:葡萄牙、法国、意大利、德国、西班牙和英国。本章中描述的代码可以在 GitHub 仓库的 CaseStudies/tourist-arrivals 下找到,数据集在 CaseStudies/tourist-arrivals/source 下。
2.2.1 数据集
表 2.1 展示了我们将要分析的样本数据集。该数据集包含了意大利(IT)、法国(FR)、德国(DE)、葡萄牙(PT)、西班牙(ES)和英国(UK)从 1994 年到 2019 年的旅游接待设施到达情况。
表 2.1 自 1994 年以来旅游接待设施到达情况
| 日期 | IT | FR | DE | PT | ES | UK |
|---|---|---|---|---|---|---|
| 1990-01-01 | 2,543,920 | 3,185,877 | 325,138 | 1723,786 | 1,776,000 | |
| 1990-02-01 | 2,871,632 | 3,588,879 | 381,539 | 1,885,718 | 2,250,000 | |
| 1990-03-01 | 3,774,702 | 4,272,437 | 493,957 | 2,337,847 | 2,662,000 | |
| … | … | … | … | … | … | … |
| 2019-08-01 | 11,649,500 | 13,692,822 | 14,570,339 | 2,531,809 | 12,893,366 | 8,889,049 |
| 2019-09-01 | 9,888,817 | 11,684,845 | 14,373,815 | 2,263,748 | 5,858,984 | |
| 2019-10-01 | 7,692,388 | 10,401,793 | 13,780,441 | 1,995,942 | 7,455,781 |
表格中某些国家和某些年份存在一些缺失值,因此如果我们想按年份合并数据,例如,我们应该注意,因为汇总不完整的年份可能会导致错误的结果。按照书中 GitHub 仓库中 from-data-to-information/raw-chart.py 文件中的描述,将数据集作为 pandas DataFrame 加载。
列表 2.5 将数据集作为 pandas DataFrame 加载
import pandas as pd
df = pd.read_csv('../source/tourist_arrivals_countries.csv', parse_dates=['Date'])
注意:使用 pandas 将数据集作为 DataFrame 加载。使用parse_dates参数将Date字段加载为日期。
现在,你已经准备好进行基本的数据探索。
2.2.2 数据探索
EDA(探索性数据分析)是提取数据洞察的必经阶段,但你不能使用其输出构建原始数据故事。实际上,EDA 的目的是分析和可视化数据以提取洞察并理解其潜在的模式、分布和关系。你甚至可以使用生成式 AI 进行 EDA。本书的范围不包括进行完整的数据探索。你可以参考本章末尾的参考文献,了解如何进行 EDA。然而,你可以使用现有的 EDA 库进行快速数据探索,例如 ydata-profiling (pypi.org/project/ydata-profiling/) 或 sweetviz (pypi.org/project/sweetviz/)。在本章中,我们将使用 ydata-profiling。导入库,创建一个ProfileReport()对象,并将 pandas DataFrame 作为输入参数传递。最后,通过调用to_file()方法生成报告。
列表 2.6 构建摘要报告
from ydata_profiling import ProfileReport
import pandas as pd
df = pd.read_csv('../source/tourist_arrivals_countries.csv', parse_dates=['Date'])
eda = ProfileReport(df)
eda.to_file(output_file='eda.xhtml')
注意:使用 ydata-profiling 构建数据集的摘要报告。
因此,to_file()方法生成一个包含报告的 HTML 文件。图 2.5 显示了生成的报告快照。

图 2.5 ydata-profiling 生成的报告快照
报告包含许多部分,如图 2.5 右上角的菜单所示。报告可以帮助你完成以下任务:
-
理解数据。 报告提供了数据集的概述,包括观测值数量、变量和缺失值。使用这些信息来了解数据质量。
-
识别数据类型。 报告确定了每个变量的数据类型,并提供了它们分布的摘要,包括均值、中位数、标准差和范围等指标。使用这些信息来识别可能需要进一步预处理的变量。
-
识别相关性。 报告提供了一个相关矩阵、热图和散点图矩阵,可以帮助识别高度相关或相关的变量。
-
识别分布。 报告提供了分布图,包括直方图和密度图,以帮助识别每个变量的分布形状和范围。使用这些信息来理解数据中的潜在模式、趋势和数据质量问题。
在你的情况下,缺失值部分描述了一些国家(法国、西班牙和联合王国)的缺失值,如图 2.6 所示。

图 2.6 报告突出了法国、西班牙和联合王国的缺失值。
现在我们已经快速探索了数据集,你准备好从该数据集中构建一个数据故事了。让我们从第一种方法开始:使用 Altair。
2.3 第一种方法:Altair
为了将数据转化为故事,我们将逐步进行。首先,我们将构建一个基本图表,然后我们将逐步丰富它,添加将图表转化为独立故事的必要元素。我们将使用 DIKW 金字塔的原则来实现这种渐进式丰富:
-
从数据到信息
-
从信息到知识
-
从知识到智慧
让我们从 DIKW 金字塔的第一步开始:将数据转化为信息。在本节中,我们将使用书中 GitHub 仓库 CaseStudies/tourist-arrivals 下的代码。
2.3.1 从数据到信息
将数据转化为信息意味着从数据中提取一些见解。首先,绘制以下代码中的原始图表和文件 from-data-to-information/ raw-chart.py。
列表 2.7 在 Altair 中构建原始图表
import altair as alt
import pandas as pd
from ydata_profiling import ProfileReport
df = pd.read_csv('../source/tourist_arrivals_countries.csv', parse_dates=['Date'])
df = pd.melt(df, id_vars='Date', value_name='Tourist Arrivals', var_name='Country')
chart = alt.Chart(df).mark_line().encode(
x = 'Date:T', #1
y = 'Tourist Arrivals:Q', #2
color=alt.Color('Country:N') #3
)
chart.save('raw-chart.xhtml')
1 使用符号::T 来指定数据类型是时间的
2 使用符号::Q 来指定数据类型是定量的
3 使用符号::N 来指定数据类型是名义的
注意:使用mark_line()属性在 Altair 中构建原始图表。在 y 轴上绘制游客到达数量,在 x 轴上绘制日期。
使用melt()函数将数据集进行转置(即,将数据行转换为列)。有关melt()函数的更多详细信息,请参阅附录 B。然后,构建图表。该图表使用三个通道:x和y描述轴,color按国家分组。对于每个通道,指定数据集中的列(例如,Date用于x通道)和数据类型(T用于时间数据,Q用于定量数据,N用于名义数据)。图 2.7 显示了生成的图表。

图 2.7 Altair 中生成的原始图表,没有任何操作
由于所有国家重叠,图表难以阅读。然而,你可以提取以下亮点:所有趋势线自 1990 年以来都在增加。这是一个可以向老板传达的积极信息。从 1990 年到 2019 年,游客数量的增加是渐进的。让我们更多地关注这个信息。
在所有情况下,你都不关心了解中间数据,因为没有异常值,只有时间序列的起点(1990)和终点(2019)。然而,我们注意到对于一些国家,1990 年没有数据,而 2019 年的数据不完整(数据截止到 10 月)。在这种情况下,问题是,在存在缺失值的情况下我们应该怎么办?答案是,这取决于。在你的情况下,你处理的是一个逐渐增长的时间序列(排除季节性),因此你可以将分析范围仅限于你有数据的日期。因此,你将搜索范围缩小到 1994–2018 年。
让我们在 Altair 中实施这个策略。首先,根据脚本 from-data-to-information/grouped-chart.py 和以下列表中的描述,从数据集中过滤掉缺失年份。
列表 2.8 从数据集中删除缺失年份
df.loc[:, 'Year'] = df['Date'].dt.year #1
df = df[(df['Year'] >= 1994) & (df['Year'] <= 2018)] #2
1 从日期中提取年份。
2 过滤掉 1994 年之前和 2018 年之后的年份。
注释:首先,从日期列中提取年份,然后过滤掉 1994 年之前和 2018 年之后的年份。使用loc通过标签访问一组行和列,使用iloc通过数值索引访问组行和列。
然后,按年份分组数据并计算总和。
列表 2.9 按年份分组并计算总和
df = df.groupby(['Year', 'Country'])['Tourist Arrivals'].sum().reset_index() #1
1 按年份和国家分组。
注释:使用group_by()按年份和国家分组数据,并通过sum()函数计算总和。最后,重置索引以检索年份。
使用reset_index()函数创建一个新的索引列,从 0 开始,并将当前索引值移动到新列。
最后,在 Altair 中构建图表。
列表 2.10 在 Altair 中构建图表
chart = alt.Chart(df).mark_line().encode(
x = 'Year:O',
y = 'Tourist Arrivals:Q',
color=alt.Color('Country:N')
)
chart.save(‘chart.xhtml’)
注释:使用 Altair 的mark_line()属性构建折线图。
图 2.8 显示了生成的图表。

图 2.8 计算每年总和并过滤边界年份后生成的图表
该图表比图 2.7 中的图表更清晰;你可以轻松地区分每个国家的趋势线。然而,过多的颜色和国家存在并不传达信息。你的目标是专注于葡萄牙,这是你老板要求你提供信息的国家。
其他国家随时间游客数量的增加大于葡萄牙。然而,你并不关心知道绝对值,而是关心每个国家随时间的百分比增加。如果我们使用绝对值,那么我们无法立即回答以下问题:
-
对于每个国家,自 1994 年以来百分比增长是多少?
-
两个国家中哪个国家的百分比增长更大?
如果你使用百分比增长,我们可以将两个国家置于更可比的水平,然后你可以回答之前的问题。让我们计算每个国家自 1994 年以来的百分比增长,然后绘制百分比增长的图表而不是绝对值的图表。
使用以下公式计算百分比增长:
百分比增长 = (最终值 – 初始值) / 初始值 * 100
在你的情况下,按照以下列表和from-data-to-information/chart.py脚本中的说明计算每个国家的百分比增长。
列表 2.11 计算每个国家的百分比增长
for country in df['Country'].unique():
current = df[df['Country'] == country]['Tourist Arrivals']
base = df[(df['Country'] == country) & (df['Year'] == 1994)]['Tourist Arrivals'].values[0]
df.loc[df['Country'] == country, 'PI'] = (current - base)/ base*100 #1
1 添加一个新列,包含每个国家当前年份和 1994 年游客到达人数之间的差异。
备注:对于每个国家,计算每年游客到达人数与 1994 年的差异。
现在,再次构建图表。
列表 2.12 绘制百分比增长的图表
base = alt.Chart(df).encode(
x = alt.X('Year:O', title=''),
y = alt.Y('PI:Q', title='Percentage Increase since 1994'),
color=alt.Color('Country:N',
scale=alt.Scale(scheme='set2'),
legend=None),
strokeWidth=alt.condition(alt.datum.Country == 'PT', alt.value(7), alt.value(0.5))
).properties(
title='Tourist Arrivals in Portugal (1994-2018)'
)
chart = base.mark_line()
chart.save('chart.xhtml')
备注:首先,创建一个新的包含所需值的 DataFrame。然后,绘制图表。使用title修改 y 轴的标题。使用scale属性选择颜色范围。在你的情况下,你使用了默认方案(set2)。对于方案列表的完整列表,你可以参考 D3 文档(observablehq.com/@d3/color-schemes)。该图表还使用了strokeWidth通道,这使得我们可以配置每条线的宽度。我们使用条件语句来设置宽度。要在条件语句内直接访问列值,使用alt.datum.<column>。要将值设置为特定的字符串或数字,使用alt.value()。
为了编码x通道,你使用了alt.X()对象——而不是像之前那样使用简单的字符串——与y和color通道相同。当你想要指定通道细节时,可以使用通道对象而不是简单的字符串。例如,你可以使用alt.Y()为y通道,alt.Color()为color通道,等等。在通道对象内,你可以设置依赖于通道的不同属性。例如,对于x和y通道,你可以设置轴属性。你可以在通道内为每个属性定义一个值或额外的对象。在实践中,相同的通道策略也适用于通道属性。例如,axis是x和y通道的属性。你可以使用alt.Axis()来设置轴的额外属性。在示例中,你设置了color通道的legend属性。
为了构建这个图表,你也使用了properties()函数,该函数配置了图表的一般属性,例如标题。图 2.9 显示了生成的图表。

图 2.9 计算百分比增长并突出显示葡萄牙后的图表
你自愿移除了图例,因为你将在每条线后添加注释。该图表显示葡萄牙的游客到达量百分比增长超过了其他国家。这是一个非凡的发现,并且回答了你老板的问题。你已经从数据中提取了信息。为了突出你的发现,向图表添加一个注释,精确描述与 2018 年对应的百分比增长值。
列表 2.13 向图表添加注释
annotation = base.mark_text(
dx=10, #1
align='left', #2
fontSize=12 #3
).encode(
text='label:N'
).transform_filter(
alt.datum.Year == 2018
).transform_calculate(
label='datum.Country + "(" + format(datum.PI, ".2f") + "%)"' #4
)
chart = (chart + annotation
)
chart.save('chart.xhtml')
1 设置从 x 坐标偏移的像素值
2 设置文本对齐方式
3 设置文本字体大小
4 格式化文本,仅显示两位小数并添加百分号
注意:使用 mark_text() 属性来绘制文本。接下来,通过 + 运算符将之前的图表和文本注释结合起来。使用 transform_filter() 来选择仅 2018 年(你想要放置注释的位置)和 transform_calculate() 来向数据集中添加一个包含格式化百分比的新的列。
mark_text() 函数(以及其他 mark_*() 函数)可以作为输入接收一些参数,这些参数定义了图表的一些静态属性。例如,如果你想为图表中的所有文本使用静态颜色,请在 mark_*() 函数中设置颜色。如果文本的颜色取决于 DataFrame 中的某一列,请在 encode() 函数中设置。图 2.10 显示了第一步结束时生成的图表,将数据转化为信息。

图 2.10 第一步结束时生成的图表,将数据转化为信息
该图表清晰易懂。添加信息意味着只提取相关的数据,并以一种便于观众理解的方式表示,无需进一步计算。现在你已经将数据转化为信息,是时候进入 DIKW 金字塔的下一步:将信息转化为知识。
练习 2
按以下方式修改图 2.10 的图表:
-
构建一个斜率图,它是一种仅显示第一和最后值(1994 年和 2018 年)的折线图。
-
在 y 轴上显示游客到达量,而不是百分比增长。
作为输出,你应该生成图 2.11 所示的图表。你可以在 CaseStudies/tourist-arrivals/exercises/exercise1.py 下找到练习的解决方案。

图 2.11 练习 1 的输出
练习 3
将图 2.11 的斜率图转换为堆积柱状图。该练习的解决方案在 CaseStudies/tourist-arrivals/exercises/exercise2.py 下。
2.3.2 从信息到知识
将信息转化为知识意味着为提取的信息添加上下文。通常,上下文回答以下问题:这种情况为什么会发生?如果可能,扩展你的搜索,或者查看相同的数据库来回答这个问题。在我们的案例中,问题是这样的:为什么葡萄牙在 25 年内经历了如此惊人的游客数量增长?为了回答这个问题,你进行了研究并发现葡萄牙(以及其他国家)在 20 世纪 90 年代初引入了许多低成本航班(Dobruszkes,2013)。这促进了旅游业的极大发展。
这个发现是图表的上下文;多亏了低成本航班的引入,葡萄牙在 25 年内游客数量增长了超过 200%,甚至超过了其他国家的增长。按照脚本 from-information-to-knowledge/chart.py 中的描述,将这个发现作为注释添加到图表中。
列表 2.14 向图表添加上下文
text_comm = f"""Thanks to the introduction
of low-cost flights,
Portugal has experienced
an increase
in tourist arrivals
of over 200% in 25 years,
even surpassing the increase
in the other countries."""
df_commentary = pd.DataFrame([{'text' : text_comm}])
commentary = alt.Chart(df_commentary
).mark_text(
lineBreak='\n',
align='left',
fontSize=20,
y=0,
x=0,
color='#81c01e'
).encode(
text='text:N'
).properties(
width=200
)
注意:首先,创建一个包含要添加到图表中的文本的 DataFrame。然后,使用mark_text()来绘制文本。还要指定换行符号(lineBreak='\n')、文本对齐方式(align='left')、字体大小(fontSize=20)、位置(y=100)和颜色(color='orange')。最后,通过加号运算符将文本与之前的图表结合。
为了加强图表中插入的文本,让我们添加一个额外的、非常简洁的图表,展示葡萄牙随时间变化的客流量。使用世界银行关于世界各国航空交通的数据集(mng.bz/RZRP,在 CC BY-4.0 许可下发布)。以下列表显示了如何将折线图添加到主图表中。
列表 2.15 向图表添加行动号召
df_airports = pd.read_csv('../source/airports.csv')
df_airports = df_airports.melt(id_vars='Country Name', var_name='Year', value_name='Value')
df_airports.dropna(inplace=True)
df_airports = df_airports[df_airports['Country Name'] == 'Portugal']
airports = alt.Chart(df_airports).mark_line(
color='#81c01e',
strokeWidth=6
).encode(
x=alt.X('Year', axis=alt.Axis(labels=False, ticks=False, grid=False), title='Year range: 1994-2018'),
y=alt.Y('Value', axis=alt.Axis(labels=False, ticks=False, grid=False), title='Air Passengers range: 4.3M-17.3M')
).properties(
title='Air passengers in Portugal',
width=200,
height=200
)
chart = ((commentary & airports) | (chart + annotation)
)
chart.save('chart.xhtml')
注意:首先,将机场数据集作为 pandas DataFrame 加载。接下来,格式化数据集以便 Altair 可以使用。然后,使用mark_line()方法构建趋势线。从 x 轴和 y 轴移除标签、刻度和网格。最后,通过&运算符将机场图表和文本结合,这对应于垂直对齐。同时,使用|运算符水平组合图表。
图 2.12 显示了第二步:将信息转化为知识后生成的图表。

图 2.12 第二步:将信息转化为知识后生成的图表
多亏了上下文,图表回答了为什么会发生这种情况。现在你已经将信息转化为知识,是时候进入 DIKW 金字塔的最终步骤:将知识转化为智慧。
2.3.3 从知识到智慧
将知识转化为智慧意味着在图表中添加行动号召并提出下一步行动。在本章中,我们只关注添加行动号召。我们将在接下来的章节中看到如何添加下一步行动。行动号召邀请观众采取行动。在你的情况下,行动号召应该回答你老板的原始问题:我应该建造一个新的游泳池吗?
根据先前分析的结果,制定以下行动号召:由于到达葡萄牙的游客数量持续增长,你可以考虑建造一个新的游泳池!将行动号召添加到主图表下方,如以下列表和 from-knowledge-to-wisdom/chart.py 脚本所示。
列表 2.16 将行动号召添加到图表中
text_cta = f"""Since the number of tourists arriving in Portugal is constantly expanding,
you can think about building a new swimming pool!"""
df_cta = pd.DataFrame([{'text' : text_cta}])
cta = alt.Chart(df_cta
).mark_text(
lineBreak='\n',
align='left',
fontSize=20,
y=0,
x=0,
color='#81c01e'
).encode(
text='text:N'
).properties(
width=200
)
final_chart = ((commentary & airports) | ((chart + annotation) & cta)
)
final_chart.save('chart.xhtml')
备注 在图表中添加新的文本注释以制定行动号召。
如果你的代码包含图像(我们将在下一章中看到),你可以在本地或 Web 服务器上运行它。如果你没有 Web 服务器,你可以从命令行运行一个本地和临时的 Web 服务器,在包含生成的 HTML 文件的目录中运行以下命令:python -m http.server. 服务器应该监听 8000 端口,并应该从它启动的目录中提供所有文件。将你的浏览器指向 http://localhost:8000/chart.xhtml 以访问 chart.xhtml 文件。图 2.13 显示了通过最终步骤,将知识转化为智慧所生成的图表。

图 2.13 最终步骤产生的图表:将知识转化为智慧
图表包含了你老板问题的答案。如果你将这个图表与图 2.5 进行比较,你可能会注意到很大的不同:图 2.9 和图 2.10 中的图表显示,观众不需要进行任何计算或处理。他们只需阅读结果即可。相反,如果他们查看图 2.5 中的图表,观众必须自己提取信息。
有些人可能会争论在过程中丢失了很多信息,比如其他欧洲国家发生了什么,以及 1994 年至 2018 年之间发生了什么。因此,老板不会仅仅基于图表中显示的数字做出重大投资。这个观察是正确的。然而,我们简化了这个例子,以展示将数据转化为故事的工作流程。在更复杂的情况下,你必须考虑额外的因素,并构建一个适当的故事,其中每个因素都是你数据故事的单个场景。
为了了解 Altair 和 Matplotlib 之间的差异,我们将在下一节中使用 Matplotlib 构建相同的图表。如果你不熟悉 Matplotlib,可以直接跳转到 2.4 节,该节涵盖了第二种方法:使用 Copilot。
2.3.4 比较 Altair 和 Matplotlib
你可能会想知道为什么在一本关于 Altair 的书中会有关于 Matplotlib 的章节。答案很简单:本书中描述的方法,包括使用生成式 AI 技术和 DIKW 金字塔的技术,可以应用于所有数据可视化库,而不仅仅是 Altair。实际上,你也可以考虑在其他语言中使用它们,例如 R。作为例子,在本节中,我们简要比较了 Altair 和 Matplotlib,突出了它们之间的差异和相似之处。
Matplotlib 是一个流行的 Python 数据可视化库。很可能,它是你用来快速绘制数据的第一个 Python 库。如果你将 Matplotlib 作为 Altair 的替代品,请考虑 Matplotlib 是一个命令式库,因此你必须定义所有生成图表的步骤。在 Altair 中,你只需关注期望的输出。无论如何,本书中描述的所有与数据叙事相关的概念也适用于 Matplotlib,所以如果你更喜欢命令式方法,可以使用它。
你可以在本书的 GitHub 仓库 CaseStudies/tourist-arrivals/matplotlib/chart.py 中找到 Matplotlib 的完整代码,以及在下述列表中。
列表 2.17 在 Matplotlib 中生成图表
import matplotlib.pyplot as plt
df = pd.read_csv('../source/tourist_arrivals_countries.csv', parse_dates=['Date'])
df = pd.melt(df, id_vars='Date', value_name='Tourist Arrivals', var_name='Country')
df.loc[:, 'Year'] = df['Date'].dt.year #1
df = df.groupby(['Year', 'Country'])['Tourist Arrivals'].sum().reset_index() #2
df = df[(df['Year'] >= 1994) & (df['Year'] <= 2018)] #3
for country in df['Country'].unique():
current = df[df['Country'] == country]['Tourist Arrivals']
base = df[(df['Country'] == country) & (df['Year'] == 1994)]['Tourist Arrivals'].values[0]
df.loc[df['Country'] == country, 'PI'] = (current - base)/ base*100 #4
fig, ax = plt.subplots(figsize=(10, 6)) #5
for country in df['Country'].unique():
current = df[df['Country'] == country]['Tourist Arrivals']
base = df[(df['Country'] == country) & (df['Year'] == 1994)]['Tourist Arrivals'].values[0]
pi = (current - base)/ base*100
ax.plot(df[df['Country'] == country]['Year'], pi, label=country) #6
ax.set_title('Tourist Arrivals in Portugal (1994-2018)') #7
ax.set_xlabel('') #7
ax.set_ylabel('Percentage Increase since 1994') #7
ax.legend() #8
plt.savefig('chart.png', dpi=300) #9
1 从日期中提取年份
2 按年份和国家分组
3 过滤出 1994 年之前和 2018 年之后的年份
4 添加一个新列,包含每个国家当前年份与 1994 年之间游客到达人数的差异
5 创建图形和轴
6 绘制数据
7 设置标题和轴标签
8 添加图例
9 保存图表
备注:为每个国家构建单独的行。
图 2.14 显示了 Matplotlib 生成的图表。通过添加图表线条、数据点或标签,可以使图表更易于阅读和展示。

图 2.14 Matplotlib 生成的图表
Altair 的代码比 Matplotlib 的代码更冗长。然而,在 Matplotlib 中,你必须单独绘制每条线或文本,而在 Altair 中,你只需传递 DataFrame,库就会为你绘制线条或标记。现在你已经通过手工将数据转化为智慧,并看到了 Altair 和 Matplotlib 之间的差异,我们将转向另一种方法:使用 GitHub Copilot 半自动地构建图表。
2.4 第二种方法:Copilot
Copilot 允许你编写代码来自动实现某些功能。我们将通过 Visual Studio Code (VSC)的扩展来启用 Copilot。要安装 VSC,请参考其官方文档(code.visualstudio.com/docs/introvideos/basics),并配置 Copilot,请参阅附录 A。
要使用 Copilot,只需有序地描述要实现的操作序列,Copilot 就会为我们完成剩余的工作,甚至提出不同的实现选项。在本节中,我们将使用 Copilot 构建图表的框架(即其主要部分)。然后,我们将手动添加细节,使用颜色和字体大小。使用 Copilot 的主要困难是用自然语言描述代码需要实现的内容。文本越清晰,Copilot 在实现上就越一致。
在本节中,我们将描述如何将前几节中编写的代码翻译成 Copilot 能理解的文本序列。我们将只关注将图表框架翻译成自然语言,而不考虑细节,如字体大小和颜色。在本书的仓库 CaseStudies/tourist-arrivals/copilot/chart.py 下找到 GitHub Copilot 生成的代码。
我们将把问题分解成步骤:
-
加载和清理数据集
-
计算百分比增加
-
在 Altair 中绘制基本图表
-
丰富图表
我们将在接下来的章节中详细讲解每个步骤。
2.4.1 加载和清理数据集
在使用数据集之前,你必须加载所需的库,以 pandas DataFrame 的形式打开数据集,并计算百分比增加。想法是为构建图 2.13 所示的图表准备数据。
打开 VSC,并写下以下注释。
列表 2.18 加载和过滤数据集
# Import the necessary libraries.
# Read the following dataset into a pandas DataFrame, '../source/tourist_
arrivals_countries.csv', and parse the Date field as a date.
# Filter out rows before 1994 and after 2018.
# Extract the year from the Date field and create a new column called Year.
# Group the data by Year and calculate the sum of tourist arrivals for each year.
注意:描述加载和过滤数据集的操作序列。
如果你按下最后一个注释的 Enter 键并开始输入import,Copilot 将建议完成该语句。只需按下 Enter 键接受生成的代码。或者,将鼠标悬停在建议的文本上,打开 Copilot,如图 2.15 所示。

图 2.15 如何在 VSC 中打开 Copilot
根据生成的代码,Copilot 可能会提出不同的解决方案,如图 2.16 所示。点击“接受解决方案”以接受一个解决方案。在我们的例子中,第二个解决方案实现了所描述的步骤。请注意,每次运行 Copilot 时,它可能会生成不同的解决方案,即使你重新运行相同的文本。

图 2.16 Copilot 在 VSC 中提出的不同解决方案
2.4.2 计算百分比增加
下一步涉及从之前清理过的数据集开始构建一个新的数据集。新的数据集将包含葡萄牙和德国的百分比增加值。写下以下操作。
列表 2.19 使用百分比增加构建新的 DataFrame
# Add a new column to the DataFrame called PI containing the difference for each country between the number of tourist arrivals in the year and 1994.
注意:描述如何计算百分比增加。
与前一个案例一样,Copilot 可以提出多个解决方案。打开 Copilot 检查可能的解决方案,并接受最能实现你操作的方案。如果你找不到任何实现你文本的解决方案,通过添加更多细节重写文本。如果在重写文本后,你仍然找不到实现你操作的解决方案,接受最接近你要求的解决方案并根据你的需要进行修改。
2.4.3 在 Altair 中绘制基本图表
现在,我们将指导 Copilot 构建一个类似于我们在图 2.12 中看到的图表(除了机场数据集图表),关于百分比增加。
列表 2.20 在 Altair 中构建图表
# Use the Altair library to plot the PI column for each country versus the Year.
备注 描述图表应该如何看起来。
Copilot 可以提出多个解决方案。打开 Copilot 检查可能的解决方案,并接受最能实现你操作的方案。
2.4.4 丰富图表
指导 Copilot 添加上下文。此外,将生成的图表保存为名为 output.xhtml 的 HTML 文件。
列表 2.21 丰富图表
# Create a new chart with the following text: 'Thanks to the introduction\n of low-cost flights,\nPortugal has experienced\\nan increase\\nin tourist arrivals\nof over 200% in 25 years,\\neven surpassing the increase\\nin the other countries.' Use the \n as a line break to format the text. Set the font size to 14\.
# Place the two graphs side by side. Set title to 'Tourist Arrivals in Portugal (1994-2018)'.
# Save the chart as an HTML. Name the file chart.xhtml.
备注 写上注释和标题,以便在图表中显示。
一旦代码完成,保存并运行脚本。图 2.17 显示了生成的图表。

图 2.17 Copilot 生成的图表
图表是一个好的起点,但仍需改进。为了改进图表,你可以手动运行以下步骤:
-
增加注释的字体大小。
-
增加 PT 线的笔画宽度。
使用 Copilot 可能看似没有意义,因为一些元素仍然需要手动管理。然而,随着实践,你会意识到 Copilot 对于加快你代码中最简单部分的编写速度是多么有价值。Copilot 会记住你之前的代码,并在你使用它时适应你的编程风格。随着实践,它很可能会成为你的编程伴侣,就像对我一样!
在本章中,你学习了如何使用 Altair 和 Copilot 将原始数据集转化为数据故事。在下一章,我们将回顾 Altair 的基本概念。
摘要
-
Altair 是一个用于数据操作和可视化的声明式库。它提供了三个主要元素来构建图表:图表、标记和编码。使用图表元素来指定要绘制的数据集。使用标记来指定要绘制的图表类型,并使用编码来设置通道,如 x 轴和 y 轴。
-
要将原始数据转化为数据故事,逐步使用 DIKW 原则。首先清理你的数据集,然后只提取有意义的信息。绘制提取的信息。然后,添加上下文和行动号召作为注释。
-
Copilot 是一个用于加快代码生成的强大工具。要使 Copilot 生成你的代码,将问题分解成小步骤,并使用自然语言描述每个步骤。尽管 Copilot 并不完美,但它可以帮助你构建代码的框架。
-
使用 Copilot 自动生成你代码的框架,然后手动改进代码。
参考文献
EDA
通用方法
-
Gupta, D., Bhattacharyya, S., Khanna, A., and Sagar, K. (Eds.). (2020). Intelligent Data Analysis: From Data Gathering to Data Comprehension. John Wiley & Sons.
-
Rasmussen, R., Gulati, H., Joseph, C., Stanier, C., and Umegbolu, O. (2019). Data Analyst: Careers in Data Analysis. BCS Publishing.
-
Khalil, M. (2024). Effective Data Analysis: Hard and Soft Skills. Manning Publications.
Python
-
Mukhiya, S. K. and Ahmed, U. (2020). Hands-On Exploratory Data Analysis with Python. Packt Publishing.
-
Oluleye, A. (2023). Exploratory Data Analysis with Python Cookbook. Packt Publishing.
工具和库
-
Altair: Declarative Visualization in Python. (n.d.).
altair-viz.github.io/ -
GitHub. (n.d.). GitHub Copilot: Your AI Pair Programmer.
github.com/features/copilot.
其他
- Dobruszkes, F. (2013). The Geography of European Low-Cost Airline Networks: A Contemporary Analysis. Journal of Transport Geography, 28, 75–88.
第三章:. 审查 Altair 的基本概念
本章涵盖
-
Vega 和 Vega-Lite
-
Altair 图表的基本组件
-
一个相关的案例研究
初看之下,似乎在介绍 Altair(第二章)如何运行数据故事之前,先讲解 Altair 的基础知识(第三章)是不合常理的。原因在于,现在你对 Altair 的工作原理有了基本的了解,你就可以开始深入了解所有细节了。如果你一开始就读了这一章,你可能会感到无聊并跳过它。相反,现在,你已经准备好平静地阅读它了。在本章中,我们将回顾 Vega 和 Vega-Lite 的基本概念,这些是 Altair 所构建的可视化语法的基础。然后,我们将重点关注 Altair 的主要组件:编码、标记、条件、复合图表和交互性。在章节的最后部分,我们将实现一个实际案例。
3.1 Vega 和 Vega-Lite
Vega 和 Vega-Lite 是 Altair 使用的可视化语法。可视化语法是一套规则和原则,用于定义如何以视觉方式表示数据,就像语法在口语中的作用一样。可视化语法包括一系列视觉元素,如点、线和条形,以及组合和排列这些元素以创建有意义的可视化规则。使用可视化语法可以使你创建清晰有效的数据可视化,传达洞察力并讲述故事。
Vega 和 Vega-Lite 提供了创建交互式可视化的声明性语言。在学习 Altair 之前先学习 Vega 和 Vega-Lite 非常重要,因为 Altair 是建立在这两个可视化库之上的。通过首先掌握这些基础工具,你可以更深入地理解 Altair 的工作原理,并充分利用其全部潜力。此外,学习 Vega 和 Vega-Lite 还使你能够创建仅使用 Altair 无法实现的自定义可视化,从而在数据探索和沟通中提供更多的灵活性和创造力。在下一节中,我们将探讨 Vega 和 Vega-Lite 规范的主要组件。
3.1.1 Vega
Vega是一种通过在 JSON 格式中编写代码来定义图表的视觉方面和交互功能的可视化语法。Vega 建立在 D3.js(d3js.org/)之上,这是一个非常流行的 JavaScript 库,用于数据可视化。使用 Vega,你可以生成基于 Web 的视图,利用 HTML5 Canvas 或 SVG 来显示生成的图表。HTML5 Canvas 是一种基于位图的绘图技术,它使你能够在 Web 上渲染动态图形和动画。SVG 是一种基于矢量的图形格式,用于构建可缩放和分辨率无关的图形。
Vega JSON 文件包含图表视觉外观和交互行为的规范。列表 3.1 展示了 JSON 规范的基本结构。为了测试,您可以使用 Vega 编辑器(vega.github.io/editor/#/) 来渲染 Vega 生成的图表。在本节中,我们将逐步生成一个基本的 Vega 线形图表。
列表 3.1 Vega JSON 文件的基本结构
{
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "A basic example in Vega",
"width": 600,
"height": 400,
"data": [],
"scales": [],
"axes": [],
"marks": [],
"signals": []
}
注意:首先,定义模式版本($schema)、description、图表的 width 和 height。然后,指定主要部分(signals、data 等)。列表 3.1 仅显示 Vega JSON 文件的结构,如果粘贴到 Vega 编辑器中,则不会生成任何图表。
Vega JSON 文件的主要部分包括 data、scales、axes、marks 和 signals。在本节的剩余部分,我们将描述 Vega 规范的主要部分的概述。有关更多详细信息,请参阅 Vega 官方文档。(vega.github.io/vega/docs/)。
数据
本节定义了要使用的数据源。它指定了数据格式、数据所在位置以及如何加载和转换数据。以下列表展示了 data 部分的示例。
列表 3.2 data 部分的示例
"data": [
{
"name": "myData",
"values": [
{"x": 10, "y": 20},
{"x": 40, "y": 60},
{"x": 70, "y": 40},
{"x": 90, "y": 80},
]
}
],
注意:指定用于可视化的数据列表。对于数据中的每个元素,指定名称和内容。我们将使用 myData 来引用规范中相关的数据。可以指定多个数据源。示例直接指定数据值。然而,您也可以通过指定 URL 和格式从 CSV 文件中检索数据,如下所示:"url": "/path/to/csv/myfile.csv", "format": {"type": "csv"}。或者,您可以从其他格式检索数据或将它们直接嵌入 JSON 中。
刻度
本节定义了将数据映射到视觉属性的刻度。刻度将数据域(输入范围)映射到视觉范围(输出范围)。以下列表展示了 scales 部分的示例。
列表 3.3 scales 部分的示例
"scales": [
{
"name": "xScale",
"type": "linear",
"domain": {"data": "myData", "field": "x"},
"range": "width"
},
{
"name": "yScale",
"type": "linear",
"domain": {"data": "myData", "field": "y"},
"range": "height"
}
],
注意:指定用于可视化的 scales 列表。对于每个刻度,指定至少名称、范围和域。示例定义了两个刻度,一个用于 x 轴,另一个用于 y 轴。指定作为域属性值的要使用的数据。
轴
本节定义了图表的轴。使用 axes 定义轴上的刻度和标签。以下列表展示了 axes 部分的示例。
列表 3.4 axes 部分的示例
"axes": [
{ "orient": "bottom", "scale": "xScale" },
{ "orient": "left", "scale": "yScale" }
]
注意:指定用于可视化的 axes 列表。对于每个轴,指定至少刻度和如何定位它。示例定义了两个轴,一个用于 x 轴,另一个用于 y 轴。
标记
本节定义了表示数据的视觉 marks。标记包括点、线、矩形、区域和其他形状。使用视觉属性,如大小、颜色、不透明度和形状,来设置标记样式。以下列表显示了一个 marks 部分的示例。
列表 3.5 marks 示例部分
"marks": [
{
"type": "line",
"from": {"data": "myData"},
"encode": {
"enter": {
"x": {"scale": "xScale", "field": "x"},
"y": {"scale": "yScale", "field": "y"},
"stroke": {"value": "red"}
}
}
}
],
注意:指定在可视化中使用的 marks 列表。对于每个 mark,指定至少 type(线、符号、矩形等)、数据源(from)以及如何编码数据(encode)。使用 enter 块定义可视化的初始属性。示例定义了一个表示 data 部分中 myData 变量包含的数据的折线图。它还定义了两个编码通道 x 和 y,以及线条的颜色。
信号
本节定义了可以用来修改可视化的交互式信号,例如滑块、下拉菜单和复选框。每个信号由两部分组成:监听器,由 signals 部分标识,和处理器,由 marks 部分的 signal 关键字定义。信号监听器响应信号变化,而信号处理器确定可视化如何响应。例如,信号处理器可能会根据信号值更改数据点的颜色或大小。以下列表提供了一个示例,当用户将鼠标悬停在条形上时(图 3.1),它会更改图表中选定元素的颜色。
列表 3.6 信号示例部分
"signals": [{
"name": "changeColor",
"on": [
{"events": "mouseover", "update": "datum"},
{"events": "mouseout", "update": "null"}
]
}]
注意:signals 用于捕获可视化中的事件或变化。对于每个信号,指定其名称(例如示例中的 changeColor),要修改的属性(例如示例中的 value),以及要监听的事件列表(例如示例中的 on)。
要使用信号,您必须在 mark 部分中指定一个 update 块。记住,您使用了 enter 块来指定图表的初始条件。现在,您可以使用 update 块来指定一个更改。以下列表指定了线条颜色的更改。
列表 3.7 更新示例部分
"marks": [{
"encode": {
"enter": {
...
},
"update": {
"stroke": {
"signal": "changeColor === datum ? 'red' : 'blue'"
}
}
}
}],
注意:在 marks 部分下,指定与信号关联的处理程序。当与信号关联的指定事件(如鼠标悬停)发生时,将触发此处理程序。在 marks 部分的 update 属性中定义了一个处理程序。此处理程序检查信号是否对应于鼠标悬停事件(例如,changeColor === datum)。如果条件满足(即鼠标位于线上),则处理程序执行操作,例如相应地更改线条颜色。有关更多详细信息,请参阅 Vega 文档(vega.github.io/vega/docs/signals/)。
图 3.1 展示了生成的折线图。您可以在书籍的 GitHub 仓库 03/vega/json/line-chart.json 下找到完整的示例。

图 3.1 使用 Vega 编写的折线图
练习 1
按照以下方式在 Vega 中绘制折线图:
-
从以下 URL 加载数据:
mng.bz/ZEyZ。 -
在线上添加点。为此,添加一个新的标记(除了线之外),使用
symbol作为类型。
您可以在书籍的 GitHub 仓库中找到这个练习的解决方案,位于 03/vega/json/spec.json。
练习 2
按照以下方式在 Vega 中绘制柱状图:
-
从以下 URL 加载数据:
mng.bz/Ad6W。 -
将
band作为X尺度的类型。 -
使用
rect作为mark类型。 -
可选地,设置一个在鼠标悬停时改变条形图颜色的信号。
您可以在书籍的 GitHub 仓库中找到这个练习的解决方案,位于 03/vega/json/spec2.json。现在您已经了解了 Vega 的基本概念,让我们继续下一步:Vega-Lite。
3.1.2 Vega-Lite
Vega-Lite 是 Vega 可视化的简洁 JSON。Vega-Lite 仍然保留了 Vega 中已定义的规范,但它通过更紧凑的语法来表示。与 Vega 相比,Vega-Lite 提供了更简洁的语法,适合快速生成简单可视化,代码复杂度更低,使得它对寻求快速创建常见图表的用户更加易于使用。虽然 Vega-Lite 为创建可视化提供了高级抽象,但它可能缺乏定制可视化每个方面的灵活性。它可能也比 Vega 的性能差。Vega-Lite JSON 规范的基本部分是 data、encoding、mark 和 layer。
data
本节指定了可视化的数据源。以下列表提供了 Vega-Lite 中 data 部分的示例。
列表 3.8 一个 data 部分的示例
"data": {
"values": [
{"x": 10, "y": 20},
{"x": 40, "y": 60},
{"x": 70, "y": 40},
{"x": 90, "y": 80}
]
},
注意:这指定了在可视化中使用的源数据。有关更多详细信息,请参阅 Vega-Lite 文档(vega.github.io/vega-lite/docs/data.xhtml)。
encoding
本节将数据字段映射到视觉属性,例如位置、颜色和大小。以下列表展示了 Vega-Lite 中 encoding 部分的示例。使用 Vega 编辑器在 Vega-Lite 中测试您的代码,确保您从左上角的下拉菜单中选择 Vega-Lite。
列表 3.9 一个 encoding 部分的示例
"encoding": {
"x": {"field": "X", "type": "quantitative"},
"y": {"field": "Y", "type": "quantitative"}
},
注意:指定在可视化中使用的编码。示例定义了两个通道,x 和 y,并指定数据类型为 quantitative。Vega 和 Vega-Lite 还支持其他数据类型,例如 ordinal 用于顺序数据;nominal 用于文本;以及 time 用于时间数据。
mark
本节定义了视觉标记的类型,例如 bars、points 或 lines。以下列表展示了 Vega-Lite 中 mark 部分的示例。
列表 3.10 一个 mark 部分的示例
"mark": {"type": "line"},
注意:指定在可视化中使用的 mark。示例定义了一个折线图。
layer
本节在一个图表中组合多个标记。下一个列表展示了 Vega-Lite 中 layer 部分的示例。
列表 3.11 一个示例 layer 部分
"layer": [
{"mark": {"type": "line", }},
{"mark": {"type": "point", "shape": "circle", "size": 100}}
]
注意:指定要组合的标记列表。这组合了两个图表:一个折线图和一个点图。
交互性
Vega-Lite 通过使用 params 简化了管理信号的方式。你不需要定义一个信号,而是定义一个 param。
列表 3.12 如何在 Vega-Lite 中指定 params
"params": [
{
"name": "changeColor",
"select": {"type": "point", "on": "mouseover"}
},
{
"name" : "default",
"select": {"type": "point", "on": "mouseout"}
}
],
注意:使用 params 关键字指定每个信号的名字。对于每个 param,指定名字和如何触发它。类型可以设置为 point,以在图表或区间中选择一个单独的点。
在定义了 params 之后,你可以在标记中使用它们,如以下列表中指定。你可以在本书的 GitHub 仓库 03/vega-lite/json/line-chart.json 下找到完整的示例。
列表 3.13 在 Vega-Lite 中如何使用 params 在 mark 中
"stroke": {
"condition": [
{
"param": "changeColor",
"empty": false,
"value": "red"
},
{
"param": "default",
"empty": false,
"value": "blue"
}
],
"value" : "blue"
}
注意:列表 3.13 中的示例使用列表 3.12 中定义的 params 来根据条件设置线条颜色。如果触发 changeColor param,则线条颜色设置为 red;如果触发 default param,则线条颜色设置为 blue。默认线条颜色是 blue。
练习 3
将练习 1 的结果转换为 Vega-Lite。该练习的解决方案在本书的 GitHub 仓库的 03/vega-lite/json/spec.json 下提供。
现在你已经了解了 Vega 和 Vega-Lite 的基本概念,让我们看看如何使用 HTML 和 JavaScript 渲染 Vega/Vega-Lite 可视化。如果你不熟悉 HTML 和 JavaScript,你可以跳过下一节,直接进入 3.2 节。
3.1.3 如何渲染 Vega 或 Vega-Lite 可视化
Vega 和 Vega-Lite 都是 JSON 对象,需要渲染器才能显示,例如外部 JavaScript 库,以便从 HTML 页面渲染可视化。Vega-Lite 提供了一个 JavaScript API,可以自动构建 Vega 和 Vega-Lite 可视化。
要渲染 Vega 或 Vega-Lite JSON,请将以下 JavaScript 库导入到你的 HTML 文件中:
库的版本可能不同。在我们的例子中,我们导入 Vega 的 5.22.1 版本,Vega-Lite 的 5.6.0 版本,以及 Vega-Embed 的 6.21.0 版本,就像我们通常导入 JavaScript 库一样。然后,你可以将列表 3.11 中描述的代码包装起来,以渲染你的 Vega/Vega-Lite JSON 文件。
渲染 Vega 或 Vega-Lite JSON 规范的 JavaScript 代码
例如,要渲染列表 3.7 中描述的图表,请使用以下 HTML 规范。
列表 3.14 完整的 HTML 文件
<!DOCTYPE html>
<html>
<head> #1
<script src="https://cdn.jsdelivr.net/npm/vega@5.22.1"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-lite@5.6.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6.21.0"></script>
</head>
<body>
<div id="vis"></div> #2
<script type="text/javascript"> #3
var request = new XMLHttpRequest();
request.open(‘GET’, ‘/path/to/json/file’, false);
request.send(null)
var data = JSON.parse(request.responseText);
vegaEmbed('#vis', data);
</script>
</body>
</html>
1 导入 Vega 和 Vega-Lite 库
2 创建一个 div 来包含图表
3 构建图表
注意:首先,在头部部分导入 Vega 和 Vega-Lite 库。然后,创建一个将包含图表的 div。最后,构建图表。使用外部 JSON 文件来定义图表规范。在 script 主体中,指定导入您的 Vega JSON 文件的 JavaScript 代码。打开一个新的 XMLHttpRequest() 来加载 JSON 文件,然后使用 vegaEmbed() 函数来渲染 JSON。请注意,您必须向请求对象提供一个有效的外部 URL。您不能使用本地 JSON 文件。
您可以在本书 GitHub 仓库的 03/vega (mng.bz/x2NB) 和 03/vega-lite (mng.bz/Vxj5) 部分找到完整的示例和其他示例。现在您已经了解了 Vega 和 Vega-Lite 的基本概念,让我们转向下一个主题:Altair 图表的基本组件。
3.2 Altair 图表的基本组件
在本书中,我们主要使用 即时教学 方法论,该方法将理论与实际相结合,在您需要时立即教授概念。然而,在本章中,我们将不使用这种方法。这是因为我们希望在深入探讨书中后面章节的更高级主题之前,先建立一个关于 Altair 的基本概念坚实基础。通过偏离本章的即时教学方法,我们可以提供一个全面的概述和深入理解基础原理,这些原理将作为后续材料的构建块。Altair 建立在 Vega-Lite 之上,并提供一个用户友好的 API 来构建图表。您在第二章中学习了如何在 Altair 中构建基本图表,在本章中,我们将深入探讨 Altair 图表的基本组件。
基本的 Altair 组件包括以下内容:
-
编码 — 这些定义了数据如何映射到视觉属性,例如颜色、形状和大小。
-
标记 — 这些指的是代表数据的视觉元素,例如条形、点和线条。
-
条件 — 这些使我们能够通过指定何时使用某些编码或标记的规则来创建更复杂的可视化。
-
复合图表 — 这些将多个视觉元素组合起来,创建更复杂且信息丰富的可视化。
-
交互性 — 这使我们能够与可视化交互并探索数据。
-
配置 — 这些定义了复合图表的一般属性。
让我们分别分析每个组件,从第一个开始:编码。您可以在本书 GitHub 仓库的 03/altair 目录中找到一些示例代码(mng.bz/rVXX)。
3.2.1 编码
Altair 使用 编码 来指示数据在哪里显示。编码定义了数据集中某一列与编码通道之间的映射。每个编码通道对应于 DataFrame 的一个特定列,可以映射到图表的视觉特征。编码过程确定根据所选通道类型在哪里显示数据。Altair 识别了以下几种关键类型的通道,包括以下内容:
-
位置 — 这指定了图表中数据的位置。
-
标记属性 — 这决定了图表的外观,如颜色、大小和形状。
-
文本和工具提示 — 这些为图形提供补充注释。
表 3.1 提供了主要编码的简要描述。更多详细信息,请参阅 Altair 文档 (mng.bz/d6RQ).
请参阅第二章使用编码构建图表。
表 3.1 Altair 中的主要编码
| 类型 | 通道 | 描述 |
|---|---|---|
| 位置 | x, y |
水平和垂直位置 |
longitude, latitude |
地理坐标 | |
xError, xError |
x 和 y 的误差值 |
|
x2,y2, longitude2, latitude2 |
范围的第二个位置 | |
theta, theta2 |
起始和结束弧角度 | |
| 标记属性 | angle, radius |
标记的角度和半径 |
color, fill |
标记的颜色和填充 | |
opacity, fillOpacity |
标记的不透明度和填充不透明度 | |
shape, size |
标记的形状和大小 | |
stroke, strokeDash, strokeOpacity, strokeWidth |
标记的描边属性 | |
| 文本和工具提示 | text |
标记的文本 |
tooltip |
标记的工具提示 |
数据集中的每一列都与一个数据类型相关联,这定义了变量可以存储的数据类型。数据类型包括原始类型,如整数和字符,以及复杂类型,如数组和对象。Altair 支持以下数据类型:
-
名义 — 可以分为不同类别,但没有特定顺序的数据,例如数据集中不同水果的名称
-
有序 — 可以分为具有内在顺序的不同类别,例如一周中的日子,其中日子的顺序至关重要
-
定量 — 数值和连续数据,例如产品销售数量
-
时间 — 带有时间(即日期或时间)成分的数据,例如销售发生的日期
-
Geojson — 带有地理成分的数据,例如经纬度坐标
当我们在 Altair 中创建图表时,必须将图表中涉及的每一列与数据类型关联起来。Altair 定义了两种指定数据类型的方式:
-
通道的类型属性,例如
x=alt.X('category',type= 'quantitative'),其中category` 是列名 -
在列名之后,冒号
:符号后面的缩写代码,x=alt.X ('category:Q')
现在您已经学习了编码的概念,让我们继续学习下一个概念,标记。
3.2.2 标记
标记 使您能够在可视化中表示数据。标记的例子包括条形图、折线图、热图和箱线图等。要指定标记类型,请使用 mark_<type>() 函数,其中 <type> 代表特定的标记类型。例如,要创建一个条形图,请使用 mark_bar()。此外,您还可以为表 3.1 中定义的每个标记属性提供属性列表作为输入参数。当在 mark_<type>() 函数中使用标记属性时,该属性的值固定为常量值。例如,mark_circle(size=5) 将绘制大小为 5 的圆形。然而,如果您想根据 DataFrame 的特定列来改变圆形的大小,请使用编码函数中的 size 通道。表 3.2 展示了 Altair 提供的主要标记类型。
表 3.2 Altair 提供的主要标记类型
| 方法 | 描述 |
|---|---|
mark_arc() |
饼图 |
mark_area() |
填充区域图 |
mark_bar() |
条形图 |
mark_circle() |
填充点的散点图 |
mark_geoshape() |
地理形状 |
mark_line() |
折线图 |
mark_point() |
可配置点的散点图 |
mark_rect() |
热图中的填充矩形 |
mark_rule() |
横跨轴的垂直或水平线 |
mark_text() |
文本 |
请参考第二章使用标记构建图表。
3.2.3 条件
Altair 提供了多种在可视化中创建 条件 的方式,这使您可以根据特定标准自定义和控制图表的外观。在 Altair 中创建条件的一种常见方法是在图表的编码通道中使用 if-else 语句。要在 Altair 中定义一个条件,请使用 alt.condition() 方法,该方法接受三个参数:
-
条件
-
当条件为真时使用的值
-
当条件为假时使用的值
例如,如果我们想根据特定条件改变条形图的颜色,请编写以下列表中的代码和书中 03/altair/condition.py 的 GitHub 仓库。最终的图表在 03/altair/condition.xhtml 中渲染。
列表 3.15 使用 alt.condition() 方法
import altair as alt
import pandas as pd
df = pd.DataFrame(
{"Country": ["Japan", "USA", "Germany", "Spain", "France", "Italy"],
"Medals": [4, 6, 10, 3, 7, 8],
"Region":["Asia","Americas","Europe","Europe","Europe","Europe"]})
chart = alt.Chart(df).mark_bar().encode(
x='Medals',
y='Country',
color=alt.condition(
alt.datum.Region == 'Europe',
alt.value('red'), #1
alt.value('blue') #2
)
)
chart.save('chart.xhtml')
1 条件为真时使用的颜色
2 条件为假时使用的颜色
注意:示例中的条件检查区域是否为欧洲。如果条件为真,条形图将变为红色,如果条件为假,条形图将变为蓝色。使用 datum 变量可以直接在条件中访问 DataFrame 列名。
除了使用 alt.condition() 方法中的 if-else 语句和类型检查函数外,Altair 还提供了两种在可视化中创建条件的方法:转换 和 选择。您将在第五章中看到转换,在第六章中看到选择。您可以使用转换和选择来构建相互关联的图表,这些图表可以同步更新。
3.2.4 复合图表
复合图表 允许您在单个图表中组合可视化。Altair 支持以下类型的复合图表:分层、水平连接、垂直连接和重复图表。为了解释每种类型的复合图表如何工作,请考虑以下列表和 03/altair/layering.py 中描述的两个图表。
列表 3.16 在 Altair 中构建两个图表
base = alt.Chart(df
).encode(
x='Country',
y='Medals'
).properties(
width=300
)
chart1 = base.mark_bar(
color='#636466'
).properties(
title='A bar chart'
)
chart2 = base.mark_line(
color='#80C11E'
).properties(
title='A line chart'
)
注意:构建一个基础图表,其中包含所有图表共享的编码和属性,然后使用它来构建其他图表,一个条形图(chart1)和一个折线图(chart2)。使用 properties() 设置图表属性,例如宽度和标题。
分层
分层 允许您将图表堆叠在一起,如图 3.2 所示。在 Altair 中,使用 alt.layer(chart1,chart2) 函数分层两个图表,或者使用 + 操作符(chart1 + chart2)。如果每个图表都有一个标题,并且您没有为复合图表指定任何通用标题,则分层将设置分层图表的标题为第一个图表的标题。

图 3.2 堆叠条形图和折线图
水平连接
水平连接 允许您水平连接图表,如图 3.3 所示。在 Altair 中,使用 alt.hconcat(chart1,chart2) 函数分层两个图表,或者使用 | 操作符。请参阅 03/altair/hconcat.py 以获取完整示例。

图 3.3 水平连接的条形图和折线图
垂直连接
垂直连接 允许您垂直连接图表,如图 3.4 所示。在 Altair 中,使用 alt.vconcat(chart1,chart2) 函数分层两个图表,或者使用 & 操作符。请参阅 03/altair/vconcat.py 以获取完整示例。

图 3.4 垂直连接条形图和折线图
重复图表
重复图表 在单个可视化中显示类似的数据视图。使用重复图表,您可以通过重复使用不同的数据子集或更改视觉编码来创建一组图表,每个图表显示数据的一个子集。
使用 repeat() 函数在 Altair 中创建重复的图表。此函数接受重复的字段列表,如以下列表和 03/altair/repeat.py 中所示。
列表 3.17 在 Altair 中构建重复图表
import pandas as pd
import altair as alt
df = pd.DataFrame({
'X' : [1,2,3,4],
'Y' : [2,4,5,6],
'Z' : [3,4,5,6],
'H' : [5,6,8,9],
'M' : [3,4,5,3],
'Country' : ['USA', 'EU', 'EU', 'USA']
})
fields = df.columns.tolist()
fields.remove('Country')
chart = alt.Chart(df).mark_circle(color='#80C11E').encode(
alt.X(alt.repeat("column"), type='quantitative'),
alt.Y(alt.repeat("row"), type='quantitative')
).properties(
width=100,
height=100
).repeat(
row=fields,
column=fields
)
chart.save('repeat.xhtml')
注意:首先,创建 pandas DataFrame。然后,提取要重复的字段列表并将它们存储在 fields 变量中。接下来,定义 repeat() 方法,指定要重复的行和列。最后,使用刚刚定义的 row 和 column 变量作为图表编码的变量。
图 3.5 显示了列表 3.17 的输出。Altair 已构建了 25 个图表,每个图表对应于 fields 变量中指定的 DataFrame 列的每个组合。

图 3.5 通过编码变化的重复图表
3.2.5 交互性
交互性 指的是通过用户输入(如悬停在数据点上、点击图例或拖动滑块)来操作可视化的能力。在 Altair 中,使用 interactive() 方法来启用缩放、平移和数据点悬停以显示工具提示的功能。列表 3.18 展示了如何使图表交互的示例,图 3.6 显示了生成的图表。您也可以在 03/altair/interactive.py 下找到完整的示例。
列表 3.18 在 Altair 中构建交互式图表
import altair as alt
import pandas as pd
df = pd.DataFrame(
{"Country": ["Japan", "USA", "Germany", "Spain", "France", "Italy"],
"Medals": [4, 6, 10, 3, 7, 8],
"Region":["Asia","Americas","Europe","Europe","Europe","Europe"]})
chart = alt.Chart(df).mark_bar(color='#636466').encode(
x='Country',
y='Medals',
tooltip=['Country', 'Medals', 'Region']
).properties(
width=300,
title='A bar chart'
).interactive()
chart.save('interactive.xhtml')
注意:首先,创建 pandas DataFrame。然后,构建 Altair 图表,在末尾附加 interactive() 方法。另外,添加一个工具提示,它接收要显示的 DataFrame 列的列表。

图 3.6 启用交互性的条形图
3.2.6 配置
要配置单个图表的基本属性,请使用 properties() 方法。对于更复杂的属性和配置复合图表,您必须使用 全局配置。Altair 支持许多全局配置。在本节中,我们将描述轴、标题和视图。我们将等到书中稍后需要它们时再描述其他配置。
为了展示配置如何工作,我们将从图 3.7 中描述的图表开始,并通过以下列表中的代码生成。
列表 3.19 定义基本图表
import pandas as pd
import altair as alt
df = pd.DataFrame(
{"Country": ["Japan", "USA", "Germany", "Spain", "France", "Italy"],
"Medals": [4, 6, 10, 3, 7, 8],
"Region":["Asia","Americas","Europe","Europe","Europe","Europe"]})
chart = alt.Chart(df).mark_bar(color='#636466').encode(
x='Country',
y='Medals'
).properties(width=300, title='A bar chart')
chart.save('chart.xhtml')

图 3.7 默认配置构建的基本条形图
现在,让我们通过配置轴来开始修改基本图表。
配置轴
要在全局级别配置轴,Altair 支持三种方法:
-
configure_axis()—配置两个轴。 -
configure_axisX()—配置 x 轴。 -
configure_axisY()—配置 y 轴。
配置轴允许您自定义多个属性,例如标签、刻度和比例。以下列表显示了一个示例,您也可以在 03/altair/configure-axis.py 下找到。
列表 3.20 配置轴
chart = chart.configure_axis(
labelAngle=0, #1
titleFontSize=14, #2
labelFontSize=12 #3
)
chart.save('configure-axis.xhtml')
1 设置标签角度
2 设置轴标题的字体大小
3 设置轴标签的字体大小
注意:使用 configure_axis() 来配置轴的标签和标题。作为替代,使用 configure_axisX() 来配置 x 轴,而 configure_axisY() 来配置 y 轴。
图 3.8 显示了使用 configure_axis()(在左侧)和不使用 configure_axis()(在右侧)时的布局差异。

图 3.8 使用 configure_axis() 和不使用 configure_axis() 的布局之间的差异
配置标题
使用 configure_title() 方法自定义图表的标题属性,如颜色、字体大小和字体族。以下列表显示了如何使用 configure_title() 方法的示例,你还可以在 03/altair/configure-title.py 下找到。
列表 3.21 配置 title
chart = chart.configure_title(
fontSize=20, #1
color='#80C11E', #2
offset=30, #3
anchor='start' #4
)
chart.save('configure-title.xhtml')
1 设置标题字体大小
2 设置标题颜色
3 设置标题与图表之间的距离
4 设置标题锚点位置
注意:使用 configure_title() 配置标题属性。锚点必须是 None、start、middle 或 end。
图 3.9 显示了使用 configure_title()(左侧)和不使用 configure_title()(右侧)的布局之间的差异。

图 3.9 使用 configure_title() 和不使用 configure_title() 的布局之间的差异
配置视图
使用 configure_view() 方法自定义图表的一般视图。以下列表显示了 configure_view() 方法的使用示例,你还可以在 03/altair/configure-view.py 下找到。
列表 3.22 配置 view
chart = chart.configure_view(
strokeWidth=0, #1
stroke='#80C11E', #2
fill='#E0E0E0' #3
)
chart.save('configure-view.xhtml')
1 设置边框描边宽度
2 设置边框描边颜色
3 设置背景颜色
注意:使用 configure_view() 配置图表的一般视图。
图 3.10 显示了使用 configure_view()(左侧)和不使用 configure_view()(右侧)的布局之间的差异。

图 3.10 使用 configure_view() 和不使用 configure_view() 的布局之间的差异
现在你已经学习了 Altair 图表的基本组件,让我们继续下一步:实现一个案例研究。
3.3 案例研究
让我们假设你在全球人口分析中心(GDAC)工作,该中心希望研究北美的人口增长。为此研究,你将使用世界银行的 population 数据集(在 CC BY-4.0 许可下发布,data.worldbank.org/indicator/SP.POP.TOTL);你可以在本书的 GitHub 仓库的 CaseStudies/population/source/population.csv 中找到数据集的清洗版本。该数据集包含从 1960 年到 2021 年所有国家的数据,如表 3.3 所示。国家名称列还包含大洲。你可以在本书的 GitHub 仓库的 CaseStudies/population/population.ipynb 下找到此案例研究的完整代码。在这种情况下,只需执行包含图表变量的单元格即可可视化 Altair 图表。
表 3.3 人口数据集
| 国家名称 | 1960 | … | 2021 |
|---|---|---|---|
| 阿鲁巴 | 54,608 | 106,537 | |
| … | |||
| 津巴布韦 | 3,806,310 | 15,993,524 |
你首先在以下列表中绘制一个原始图表,显示从 1960 年到 2021 年所有国家和大陆的趋势线。国家名称列很脏,所以生成的图表非常混乱,如图 3.11 所示。
列表 3.23 绘制原始图表
import pandas as pd
import altair as alt
alt.data_transformers.disable_max_rows() #1
df = pd.read_csv('data/population.csv')
df = df.melt(id_vars='Country Name', #2
var_name='Year',
value_name='Population')
df['Year'] = df['Year'].astype('int')
chart = alt.Chart(df).mark_line().encode(
x = 'Year:Q',
y = 'Population:Q',
color = 'Country Name:N'
) #3
chart
1 使用此 Altair 函数处理超过 5,000 行的数据集
2 将数据集展开成非透视表形式
3 绘制图表
注意:使用 mark_line() 绘制图表。要绘制不同的国家,使用 color 通道。

图 3.11 原始数据的图表
该图表非常混乱,因为它提出了以下问题:国家太多,颜色太多,且没有专注于北美。为了解决这些问题,让我们使用 DIKW 金字塔,从第一步开始:将数据转化为信息。
3.3.1 从数据到信息
将数据转化为信息涉及从数据中提取洞察力。从图表的角度来看,这意味着使图表可读且易于理解。首先按大陆分组国家。数据集已经包含大陆的值。使用 pandas DataFrame 提供的 unique() 方法列出国家。有关 unique() 方法的更多详细信息,请参阅附录 B。请注意,国家列表中也包含大陆,因此我们可以构建一个大陆列表并基于该列表过滤 DataFrame。然后,绘制生成的图表。
列表 3.24 清理图表
continents = ['Africa Eastern and Southern',
'Africa Western and Central',
'Middle East & North Africa',
'Sub-Saharan Africa',
'Europe & Central Asia',
'Latin America & Caribbean',
'North America',
'Pacific island small states',
'East Asia & Pacific'] #1
df = df[df['Country Name'].isin(continents)] #2
chart = alt.Chart(df).mark_line().encode(
x = 'Year:Q',
y = 'Population:Q',
color = 'Country Name:N'
) #3
chart
1 定义大陆列表
2 根据列表过滤 DataFrame
3 绘制图表
注意:按大陆分组数据以减少线条数量。
图 3.12 显示了生成的图表,它更易于阅读。然而,它仍然存在一些问题:颜色太多,且没有专注于北美。在实践中,很难将北美与其他大陆进行比较。

图 3.12 专注于大陆的图表
通过专注于北美并将其他大陆按其平均值分组来更改图表,如下列表所示。
列表 3.25 专注于北美
mask = df['Country Name'].isin(['North America']) #1
df_mean = df[~mask].groupby(by='Year').mean().reset_index() #2
df_grouped = pd.DataFrame({
'Year' : df[mask]['Year'].values,
'North America' : df[mask]['Population'].values,
'World': df_mean['Population'].values
}) #3
df_melt = df_grouped.melt(id_vars='Year', var_name='Continent', value_name='Population')
1 定义一个仅选择北美的掩码
2 计算掩码外大陆的平均值
3 创建一个新的包含北美和其他大陆平均值的 DataFrame
注意:专注于北美,并计算其他大陆的平均值。
然后,绘制图表。
列表 3.26 绘制图表
colors=['#80C11E', 'grey']
chart = alt.Chart(df_melt).mark_line().encode(
x = alt.X('Year:Q',
title=None,
axis=alt.Axis(format='.0f',tickMinStep=10)), #1
y = alt.Y('Population:Q',
title='Difference of population from 1960',
axis=alt.Axis(format='.2s')), #2
color = alt.Color('Continent:N',
scale=alt.Scale(range=colors), #3
legend=None), #4
opacity = alt.condition(alt.datum['Continent'] == 'North America', alt.value(1), alt.value(0.3))
).properties(
title='Population in the North America over the last 50 years',
width=400,
height=250
).configure_axis(
grid=False, #5
titleFontSize=14,
labelFontSize=12
).configure_title(
fontSize=16,
color='#80C11E'
).configure_view(
strokeWidth=0
)
chart
1 格式化 x 标签
2 格式化 y 标签
3 设置颜色范围
4 不显示图例
5 禁用网格
注意:为了格式化 x 轴标签,使用 format 参数。使用 .0f 来仅显示整数部分。同时,使用 tickMinStep 参数来设置两个标签之间的距离。示例中将值设置为 10,因此标记了十年。为了格式化 y 轴标签,将格式参数设置为 .2s,使用两位有效数字的科学记数法。
图 3.13 显示了结果图表。我们目前省略了图例,稍后会添加。图表更清晰;然而,观众需要做一些计算才能理解两条线之间的差异。

图 3.13 突出显示北美的图表
当绘制图表时,为观众进行计算,为他们提供现成的菜肴。让我们计算两条线从起始年份(1960 年)的差异,从而使它们具有可比性。以下列表显示了如何计算差异。
列表 3.27 计算差异
baseline = df_melt[df_melt['Year'] == 1960]
continents = ['North America', 'World']
for continent in continents:
baseline_value = baseline[baseline['Continent'] == continent]['Population'].values[0]
m = df_melt['Continent'] == continent
df_melt.loc[m, 'Diff'] = df_melt.loc[m,'Population'] - baseline_value
注意:计算 1960 年的基线值,然后对每个大陆,计算每年与基线之间的差异。
计算完差异后,绘制图表。代码与列表 3.23 中的类似。只需更改 y 轴列到 Diff。图 3.14 显示了结果图表。

图 3.14 突出显示 1960 年人口差异的图表
图表清楚地显示了两条线之间的差异,尽管这不是很明确。为了完成图表,为每条线添加一个标签作为额外的文本标记。
列表 3.28 添加标签
mask = df_melt['Year'] == 2021
na = df_melt[mask]['Diff'].values[0]
oth = df_melt[mask]['Diff'].values[1]
df_text = pd.DataFrame({'text' : ['Rest of the world','North America'],
'x' : [2023,2023], #1
'y' : [oth,na]}) #2
text = alt.Chart(df_text).mark_text(fontSize=14, align='left').encode(
x = 'x',
y = 'y',
text = 'text',
color = alt.condition(alt.datum.text == 'North America', alt.value('#80C11E'), alt.value('grey'))
)
1 设置 x 位置
2 设置 y 位置
注意:使用 mark_text() 来设置线条标签。我们通过在每个线条旁边放置文本注释来标记线条。我们本来可以使用图例来标记线条,但正如 Cole Nussbaumer Knaflic 在她的书《用数据讲故事:商业专业人士的数据可视化指南》(John Wiley & Sons, 2015)中所说,使用文本注释可以使内容更清晰。要设置 y 文本位置,使用每条线条最后一个点的坐标。对于 x 轴,相反,稍微增加每条线条最后一个点的坐标(例如,示例中的 2023,而最后一个点是 2021)。
现在,将文本标记和原始图表结合起来。
列表 3.29 合并图表和文本标签
total = (chart + text).configure_axis(
grid=False,
titleFontSize=14,
labelFontSize=12
).configure_title(
fontSize=16,
color='#80C11E'
).configure_view(
strokeWidth=0
)
total
注意:使用 + 来合并两层。然后,使用 configure_axis()、configure_title() 和 configure_view() 来设置全局图表配置。
图 3.15 展示了信息步骤结束时的结果图表。

图 3.15 信息步骤结束时的图表
DIKW 金字塔的下一步是将信息转化为知识。让我们继续。
3.3.2 从信息到知识
将信息转化为知识涉及向图表添加上下文。我们添加了三种类型的上下文:显示两条线之间差距的注释、包含数据源的注释以及解释此差距原因的文本。
图表注释
图表注释旨在通过明确两条线之间的差距来简化观众的生活。图 3.16 显示了我们将添加到图表中的注释。该注释包含两个元素:一条显示两条线之间差距的垂直线和“大数字”(BAN),它包含差距值。

图 3.16 在北美与世界其他地区之间的差距上具有明确注释的图表
要绘制图表注释,使用mark_line()绘制垂直线,使用mark_text()绘制文本。以下列表描述了如何渲染前面的注释。
列表 3.30 添加注释
offset = 10000000 #1
mask = df_melt['Year'] == 2021
na = df_melt[mask]['Diff'].values[0] #2
oth = df_melt[mask]['Diff'].values[1] #3
df_vline = pd.DataFrame({'y' : [oth - offset,na + offset],
'x' : [2021,2021]})
line = alt.Chart(df_vline).mark_line(color='black').encode(
y = 'y',
x = 'x'
) #4
df_ann = pd.DataFrame({'text' : ['367M'],
'x' : [2022],
'y' : [na + (oth-na)/2]}) #5
ann = alt.Chart(df_ann).mark_text(fontSize=30, align='left').encode(
x = 'x',
y = 'y',
text = 'text'
) #6
total = chart + text + line + ann #7
total = total.configure_axis(
grid=False,
titleFontSize=14,
labelFontSize=12
).configure_title(
fontSize=16,
color='#80C11E'
).configure_view(
strokeWidth=0
)
total
1 定义了垂直线与国家线最后一个点之间的距离
2 计算北美线的最后一个值
3 计算其他线的最后一个值
4 绘制垂直线
5 将 BAN 放置在垂直线的中间
6 绘制 BAN
7 合并图表
注意:使用大陆线的最后数据点的 y 坐标作为垂直线和 BAN 的 y 坐标。为了简单起见,我们硬编码了 BAN 值。然而,在复杂场景中,您可以动态计算它。
数据源
将数据源作为文本注释立即放置在标题下方。
列表 3.31 添加数据源
df_subtitle= pd.DataFrame({'text' : ['source: World Bank'],
'href' : ' https://data.worldbank.org/indicator/SP.POP.TOTL' })
subtitle = alt.Chart(df_subtitle
).mark_text(
y=0
).encode(
text='text',
href='href'
)
注意:使用mark_text()标记向图表添加文本。同时,将href通道设置为数据源的链接。
文本注释
将文本注释放置在图表左侧,为观众准备图表。文本解释了北美与其他大陆之间差距的可能原因。以下列表描述了如何向图表添加文本注释。
列表 3.32 添加文本注释
df_context = pd.DataFrame({'text' : ['Why this gap?',
'1\. Lower Fertility Rate',
'2\. Lower Immigration Rate',
'3\. Higher Average Age'],
'y': [0,1,2,3]})
context = alt.Chart(df_context).mark_text(fontSize=14, align='left', dy=50).encode(
y = alt.Y('y:O', axis=None),
text = 'text',
stroke = alt.condition(alt.datum.y == 0, alt.value('#80C11E'), alt.value('black')),
strokeWidth = alt.condition(alt.datum.y == 0, alt.value(1), alt.value(0))
)
total = (context | (chart + text + line + ann + subtitle)).configure_axis(
grid=False,
titleFontSize=14,
labelFontSize=12
).configure_title(
fontSize=16,
color='#80C11E'
).configure_view(
strokeWidth=0
)
total.show()
注意:使用mark_text()绘制文本。然后,将文本注释与图表结合。
图 3.17 显示了知识步骤结束时的结果图表。

图 3.17 知识步骤结束时的图表
图表几乎准备好了。让我们进行最后一步:将知识转化为智慧。
3.3.3 从知识到智慧
将知识转化为智慧涉及添加行动号召。在前面的章节中,我们在标题中添加了一个简单的行动号召,邀请观众做某事。然而,在实际场景中,行动号召必须包括对可能下一步的提议,并辅以适当的资源进行论证。下一步邀请观众做具体的事情;因此,图表所描述的情况发生了变化。例如,你可以添加行动号召作为额外的图表,描述每个可能替代方案的成功率。在我们的例子中,我们可以添加一个条形图,描述提高北美人口增长的可能策略。示例中的百分比是虚构的,并不反映现实——我们只是添加它们来展示行动号召是如何工作的。以下列表展示了如何构建行动号召作为条形图并将其添加到主图表中。
列表 3.33 添加行动号召
df_cta = pd.DataFrame({
'Strategy': ['Immigration Development', 'Enhance Family-Friendly Policies', 'Revitalize Rural Areas'],
'Population Increase': [20, 30, 15] # Sample population increase percentages
})
cta = alt.Chart(df_cta).mark_bar(color='#80C11E').encode(
x='Population Increase:Q',
y=alt.Y('Strategy:N', sort='-x', title=None),
tooltip=['Strategy', 'Population Increase']
).properties(
title='Strategies for population growth in North America',
)
total = alt.vconcat((context | (chart + text + line + ann)), cta,center=True).configure_axis(
grid=False,
titleFontSize=14,
labelFontSize=12
).configure_title(
fontSize=20,
color='#80C11E',
offset=10
).configure_view(
strokeWidth=0
).configure_concat(
spacing=50
)
total.show()
备注:绘制一个图表,指定可能的替代方案及其成功率。
图 3.18 展示了最终图表。按照 DIKW 的步骤,我们将图 3.11 的原始图表转换成了数据故事。

图 3.18 智慧步骤结束时的最终图表
在本章中,你学习了 Altair 的基本概念。在下一章,你将复习生成式 AI 的基本概念。
摘要
-
Vega 和 Vega-Lite 是可视化语法,通过在 JSON 格式中编写代码来定义图表的视觉方面和交互功能。
-
Altair 建立在 Vega-Lite 之上,提供了一个用户友好的 API 来构建图表。Altair 图表的基本组件包括编码、标记、条件、复合图表、交互性和配置。
-
编码定义了数据属性(如 x 坐标、y 坐标、颜色和大小)如何在图表中视觉表示。
-
标记是图表中的视觉元素,如条形、点、线或区域。
-
条件指定了根据不同标准如何将数据属性映射到视觉属性。
-
复合图表涉及将多个图表组合成一个单一视图。
-
交互性指的是图表根据用户输入或交互做出响应或改变的能力。
-
配置包括各种设置,用于改变图表的外观,例如调整坐标轴、刻度或布局,以更好地传达预期的信息或美学。
参考文献
数据可视化
-
Belorkar, A., Guntuku, S. C., Hora, S., 和 Kumar, A. (2020). 使用 Python 进行交互式数据可视化. Packt Publishing.
-
Abbott, D. (2024). 日常数据可视化. Manning Publications.
Vega, Vega-Lite, 和 D3.js
-
Kats, P. 和 Katz, D. (2019). 通过构建数据科学应用学习 Python. Packt Publications.
-
Meeks, E. 和 Dufour, A.M. (2024). D3.js 实战. Manning Publications.
-
Vega:可视化语法. (n.d.).
vega.github.io/vega/. -
Vega-Lite:交互式图形语法. (n.d.).
vega.github.io/vega-lite/.
其他
- Knaflic, C. N. (2015). 数据讲故事:商业专业人士的数据可视化指南. 约翰·威利与儿子公司。
第四章:生成式 AI 工具用于数据叙事
本章涵盖
-
基本概念,包括人工智能、机器学习、深度学习和生成式 AI
-
ChatGPT 提示的基本结构
-
DALL-E 提示的基本结构
-
GitHub Copilot
我们终于到了这个阶段:如何使用 ChatGPT 和 DALL-E 进行数据叙事。你可能想知道为什么你直到现在才看到这些工具的实际应用。我将在本章中给出答案,其中我们将回顾生成式 AI 工具背后的基本概念以及如何在数据叙事的背景下使用它们。本章不描述生成式 AI 的基础。相反,第一部分快速概述了 AI、生成式 AI 和生成式 AI 工具。接下来,本章描述了如何将三个主要工具融入数据叙事:ChatGPT、DALL-E 和 GitHub Copilot。最后,它展示了一个实际用例,展示了如何快速结合这三个工具来构建数据故事。
4.1 生成式 AI 工具:站在巨人的肩膀上
之前,我参加了一个名为《关于 GPT 技术的考虑》(Considerazioni sulla tecnologia GPT,2023 年,www.youtube.com/watch?v=So48YMYyl58)的网络研讨会,与会者讨论了生成式 AI 带来的(可能的)好处。在讨论中,米歇尔·蒙特博士(Dr. Michele Monti)的一个特别干预引起了我的注意。他提出了一个有趣的类比,即使用 ChatGPT 在写作时帮助你,就像在智能手机上使用卫星导航器去目的地时帮助你一样。
在过去,当卫星导航器并不容易获得时,旅行者依赖纸质地图来达到目的地,值得注意的是,纸质地图不需要任何基础设施或能源即可运行。然而,随着卫星导航器的出现,它们提供的便利性和效率使得纸质地图几乎变得过时。如今,当人们可以选择使用卫星导航器时,很少有人会考虑使用纸质地图。
可以对生成式 AI 工具进行类似的分析,尤其是 ChatGPT。在生成式 AI 工具出现之前,人们需要手动编写文本,这可能会耗费大量时间和劳动力。随着生成式 AI 的进步,它可能会在写作和生成图像时越来越成为主要的辅助模式,尽管人们永远不应该完全依赖它。
在我的原始想法中,这本书本应只描述如何使用 Altair 和 DIKW 金字塔进行数据讲故事。是我在撰写本书时,鉴于生成式人工智能的快速出现,我的编辑(我真诚地感谢他)建议也包括生成式人工智能。最初,我只考虑了 Copilot,但随着我编写示例,我越来越多地使用了其他新出现的生成式人工智能工具来完成数据故事。这就是我决定在书中也描述如何使用生成式人工智能工具进行数据讲故事的原因。
你可能想知道为什么你必须等到第四章才能读到关于 ChatGPT 和 DALL-E 的内容。真实的原因是,为了正确应用生成式人工智能工具,首先必须了解你工作的背景。如果我立即介绍了 ChatGPT 和 DALL-E,我就会冒读者将一切委托给这些工具而不建立坚实基础的风险。对如何在 Python 中进行数据讲故事的了解必然是肤浅的。想象一下本书中描述的概念就像一座房子,其基础是数据讲故事的概念,楼层是 Altair,屋顶和墙壁是生成式人工智能。就像你不能从屋顶和墙壁开始建造房子一样,你也不能立即开始使用生成式人工智能。然而,为了不在前几章让你对生成式人工智能感到饥饿,我仍然想提到 GitHub Copilot,因为正如他们所说,“食欲伴随着进食”。
本书不描述如何自动化数据讲故事,而是描述如何使用生成式人工智能工具作为我们想法开发的助手。在了解如何使用这些工具进行数据讲故事之前,让我们先尝试理解生成式人工智能概念背后的内容。
生成式人工智能(AI)是人工智能的一个子领域;更确切地说,它是深度学习的一个子领域。描述生成式人工智能的基础超出了本书的范围,但在深入探讨其应用之前,对其快速概述是必要的。在撰写本书时,关于生成式人工智能工具的使用存在很多争议。一方面,有些人认为这些工具是危险的,因为它们可能会取代人类的工作,导致大规模失业。此外,它们常常产生有害的、有偏见的和幻觉的结果,以及其他问题。人们对人工智能决策的伦理影响以及这些工具被恶意滥用或操纵的担忧。另一方面,其他人强调它们的益处,认识到在负责任用户的监督下,这些工具引入的自动化水平。在本书中,我不采取任何立场,将使用这些工具的自觉责任留给你。
在这本书中,我们将尝试对生成式 AI 工具持保守和负责任的态度。我们将把它们用作助手,帮助我们更好地定义我们的内容,但它们永远不会取代我们的监督和想法。所谓“保守和负责任的态度”,意味着我们不会将生成式 AI 工具产生的输出作为绝对真理。相反,我们将采用人机协同策略,始终检查产生的输出。
本节简要概述了 AI、机器学习、深度学习和生成式 AI 是什么。如果你感到不耐烦,想直接进入主题——数据故事中的生成式 AI,可以跳过本节,直接跳到下一节。
4.1.1 什么是人工智能?
人工智能 是一个旨在构建能够进行独立推理、学习和决策的智能代理的学科。AI 专注于开发理论和方法论,以创建能够模仿人类思维和行为代理。图 4.1 显示了 AI 的主要子领域。

图 4.1 AI 的主要子领域,重点关注生成式 AI
该图还突出了生成式 AI 所属的子领域。生成式 AI 是深度学习的一个子领域,深度学习是机器学习的一个子领域,机器学习是人工智能的一个子领域。现在你已经了解了 AI 的主要子领域,让我们专注于机器学习。
4.1.2 什么是机器学习?
机器学习(ML)是 AI 的一个子领域,它使计算机能够通过学习示例来获取知识并提高性能。以评估一套服装是否时尚为例。如果没有 ML,你将不得不为每一条描述编写代码。相反,使用 ML 将需要收集和标记大量的服装描述和时尚元素数据集。然后,你将使用它来训练一个 ML 模型,以识别这套服装是否时尚。与必须逐行编写代码以提供指令的传统编程不同,ML 允许机器从数据中学习,并根据所学信息进行预测或采取行动。这种方法使机器能够发现可能对人类程序员来说并不立即明显的模式、关系和见解。ML 依赖于不同类型的学习。最受欢迎的是监督学习和无监督学习。
监督学习
监督学习从需要解决的问题和包含标签数据的集合开始,其中已经包含了一组样本的已解决问题的数据。首先,在标签数据中,必须提取输入特征。输入特征代表数据中模型将分析以学习模式和关系的独特属性。在监督学习中,有一个初步阶段,称为训练,在此期间算法从标签数据中学习。在训练过程中,训练数据集中的每个样本都与相应的标签或输出配对。算法通过从提供的标签数据中泛化来学习如何将输入特征映射到相应的输出。在训练阶段,算法优化其参数以最小化预测输出与真实标签之间的差异。一旦完成训练阶段,算法就可以根据从标签数据中学到的模式对新实例进行预测或分类。使用此技术进行图像分类、情感分析、垃圾邮件检测和语音识别等任务。
在监督学习中,主要有两种类型的任务:
-
分类——这涉及预测输入的类别或离散类标签。例如,给定一个输入(如图像或一组特征),算法预测它属于哪个类别或类。这些类别是不同的且预先定义的。例如包括垃圾邮件检测(将电子邮件分类为垃圾邮件或非垃圾邮件)和图像识别(在图像中识别像猫或狗这样的对象)。
-
回归——在这里,目标是预测一个连续的数值。与预测类标签不同,回归算法预测一个数量。例如,根据大小、位置和房间数量等特征预测房价是一个回归问题。输出是一个值范围,而不是不同的类别。
分类和回归都是监督学习的一部分,因为它们依赖于标签数据,其中算法在输入-输出对上进行训练以学习输入与其对应输出之间的关系。
挑战:在医疗保健中区分分类与回归
考虑以下场景。一个医疗系统旨在根据各种医疗检测结果预测患者是否患有特定疾病。该系统以血压、胆固醇水平和其他健康指标作为输入,并输出患者是否被诊断为该疾病。你能确定这个场景是分类还是回归的例子吗?
图 4.2 展示了分类模型的一个例子。首先,模型必须使用样本数据和类标签进行训练。训练过程的结果是,模型学会了如何区分两个类别:圆形和正方形。当模型接收到新的数据作为输入时,它预测每个样本的类别作为输出。

图 4.2 分类模型的一个示例
无监督学习
在无监督学习中,算法在未标记的数据中识别隐藏的结构和模式,在训练过程中没有提供特定的输出或标签。常见的无监督学习技术包括聚类、降维和异常检测。
图 4.3 展示了聚类模型的一个示例。该模型之前没有用数据进行过训练。相反,它应用一个内部公式来分组数据。模型接收一个包含正方形和圆形的数据集作为输入,并将它们分为两个簇,每个形状一个。

图 4.3 聚类模型的一个示例
你现在已经学习了机器学习的主要类型。接下来,让我们更进一步,专注于深度学习。
4.1.3 什么是深度学习?
深度学习(DL)是机器学习的一个子领域,专注于训练人工神经网络来处理和理解复杂数据。受人类大脑的启发,深度学习模型由多个相互连接的节点组成,这些节点被称为人工神经元或单元。层数越多,网络可以识别的数据结构就越复杂。深度学习非常资源密集,因为它需要高性能的 GPU 和大量的训练数据。你可以使用深度学习模型来解决图像和语音识别、自然语言处理,甚至游戏等任务。
图 4.4 展示了具有一个包含三个节点的输入层、三个隐藏层和两个输出的深度学习网络示例。

图 4.4 深度学习网络的一个示例
让我们分解一下:
-
输入层——这是深度学习网络的第一层,数据被输入到网络中。在这种情况下,有一个包含三个节点的输入层。每个节点代表一个特征或输入变量。
-
隐藏层——这些是输入层和输出层之间的中间层,网络在这里从输入数据中学习模式和表示。在这个网络中,有三个隐藏层,每个层有四个节点。每个隐藏层中的节点数量可以显著影响网络的学习能力和性能。
-
输出层——这是网络的最后一层,产生输出。在这种情况下,有两个输出节点,这意味着网络被设计成产生两个输出。
现在你已经看到了深度学习的概述,让我们继续下一步:定义生成式 AI。
4.1.4 什么是生成式 AI?
生成式 AI是深度学习的一个子领域,旨在根据从现有内容中学到的内容创建新的内容。从现有内容的学习过程的结果是一个统计模型(生成模型),用于生成新的内容。
生成模型试图理解数据分布。假设我们有一个包含圆形和方形图片的数据集。生成模型学习圆形和方形整体的模式和特征。然后,利用这种理解,它们生成看起来与它们所见相似的新的图片。
图 4.5 展示了一个生成模型的例子。该模型以一些文本作为输入,要求它生成一个新的圆形,并产生一个新的圆形作为输出。

图 4.5 生成模型的例子
图 4.5 中展示的从文本到图像/代码的模型是本书特定生成模型的例子。一般来说,生成意味着我们可以在特征空间中进行一些插值,并在标签空间中得到一些有意义的输出。
生成模型以提示作为输入。提示是一段用于指导输出质量的文本。它作为生成模型的指南,以生成与特定预期结果一致的响应或输出。基本的提示是要求工具执行特定操作的文本。在第二章中,你已经编写了一些基本的提示,让 Copilot 生成构建 Altair 图表的代码。
提示工程是一门旨在精心设计和完善提示以实现预期结果的学科。提示工程的目标是引发连贯、相关且与预期结果一致的响应,同时最大限度地减少任何未预期的偏见或不良行为。既然你已经了解了什么是生成式 AI,那么让我们继续描述生成式 AI 工具景观。
4.1.5 生成式 AI 工具景观
你可以在多个场景中使用生成式 AI,包括用于生成文本、图像、代码、语音等等。对于每个具体任务,你可以使用不同的工具。在所有情况下,你从一个文本提示开始,该工具根据其特定性质将其转换为所需的输出(文本、图像、代码等等)。对于大多数服务,你必须支付费用才能使用生成式 AI 工具。然而,一些生成式 AI 工具提供免费版本,但功能有所降低。
图 4.6 显示了生成式 AI 工具的主要应用领域(Madhvani,2023)。如果不正确使用,这些工具可能会产生错误或带有偏见的信息。因此,你必须仔细监控和调整生成的输出,以确保准确性和公平性。

图 4.6 生成式 AI 工具的主要应用领域。在这本书中,我们将重点关注代码、文本和图像。
该图还突出了本书中使用的应用领域:
-
文本到文本工具——使用它们来生成文本,例如用于内容创作、聊天机器人、虚拟助手、创意写作,以及自动化重复性任务,如撰写电子邮件或生成报告。文本到文本工具的例子包括 Open AI ChatGPT、Google Bard 和 Anthropic 开发的 Claude AI。在这本书中,我们将重点关注 ChatGPT。
-
文本到图像工具—使用它们根据文本输入生成视觉表示。在这本书中,我们将使用 Open AI DALL-E。如果你在 2023 年 4 月 6 日之前注册了 DALL-E,你将获得免费积分。新用户必须支付少量费用才能使用该服务。
-
文本到代码工具—使用它们来生成代码。在这本书中,我们将重点关注 GitHub Copilot,它需要一个有效的订阅。
你可能会问为什么你应该付费使用一个工具。主要答案是,高质量的生成式 AI 工具仅通过付费才能获得;免费工具要么质量不高,要么在付费或订阅之前非常有限。幸运的是,费用相当小。完全开源的工具是免费的,但它们所需的计算资源并不是免费的。另一方面,付费的生成式 AI 工具价格昂贵,但计算成本已包含在价格中。
我们可以使用 ChatGPT 生成代码。然而,我们更倾向于使用 Copilot,主要原因如下:
-
代码特定专业知识—Copilot 是专门为编码任务设计的,并在 GitHub 仓库的大量代码上进行训练。
-
VSC 集成—Copilot 直接集成到 Visual Studio Code (VSC) 中,允许开发者在他们的编程编辑器内处理项目时访问其建议和代码补全功能。
-
专注于代码生成—Copilot 主要专注于生成代码片段并协助开发者进行编码过程。它经过优化,以辅助编写函数、类以及整个代码块,根据正在编写的代码的上下文提供快速建议。
虽然 ChatGPT 可以在编程任务上提供帮助,但其主要功能并不是专门针对编码的。它是为通用用途设计的,可以协助广泛的任务,而不仅仅是编程。此外,根据我的个人经验,ChatGPT 有时会有发明一个虚构的 Python 方法或函数的倾向(这是一个幻觉的案例),如图 4.7 所示。

图 4.7 ChatGPT 生成代码时产生的一个不良输出示例
我们要求 ChatGPT 生成一个六边形图表,ChatGPT 使用了 mark_hex() 方法,但这个方法目前在 Altair 中尚未实现。这是一个 幻觉 的例子,我们将在第十章中更深入地讨论这个话题。目前,只需知道生成式 AI 模型可以产生幻觉,这意味着它可以生成令人信服地类似于真实数据的输出,尽管这些输出可能并没有在现实中明确地得到证实。
我们已经选择了特定的工具来生成用于数据讲述的文本、图像和代码。然而,你可以轻松地将描述的概念适应到其他工具上,几乎不需要任何努力。在所有情况下,提供准确和简洁的指令至关重要,以确保工具生成正确的输出。写具体且明确的指令,使用简单语言,避免复杂术语。此外,写关于语法、语言和格式的连贯指令。不一致的指令可能会使工具困惑,导致错误的输出。
现在我们已经快速定义了生成式 AI 工具的背景,我们可以继续下一步:使用生成式 AI 工具进行数据讲述。我们将从 ChatGPT 开始;然后描述 DALL-E;最后,涵盖 GitHub Copilot。
4.2 ChatGPT 提示的基本结构
要与 ChatGPT 互动,你必须编写一个输入文本(提示),以定义要执行的指令。你可以使用 ChatGPT 的网页界面或 Open AI API,如附录 A 所述。
有几种不同的方式来构建 ChatGPT 的提示。在这本书中,我们认为一个提示由三个主要连续的文本组成:
-
告诉 ChatGPT 扮演一个特定的角色。 例如,“你是一位正在审阅高中生英语论文的考官。”
-
告诉 ChatGPT 根据目标受众调整其输出。 例如,“用高中生能理解的方式解释你的评分。”
-
定义任务。 例如,“评估这篇文本,并解释你的理由。”
关于提示工程的更多细节,请参阅本章末尾参考文献中概述的资源。
尽管我们已经测试了这种提示结构,但有时它不会产生预期的输出,因此你可以通过不同的迭代来完善提示。在本节的剩余部分,我们将分别描述提示的所有部分。尽管提示的结构应该是角色、受众、任务,但我们将首先描述要执行的任务,以展示当角色和受众变化时产生的提示是如何变化的。
我们将展示一些生成的文本示例。如果你尝试自己的工具,你将获得不同的结果,因为每次使用 ChatGPT 时,它都会生成新的内容。
4.2.1 定义任务
任务是 ChatGPT 必须完成的特定目标。任务包括明确的指令,以引导 ChatGPT 生成与给定任务相关的响应。任务的例子包括回答问题、生成创意内容、翻译语言等。在数据讲述的背景下,我们将关注以下任务:
-
生成标题
-
生成副标题
-
总结文本
-
翻译文本
-
提出想法
我们将在下一章中更详细地分析每个任务。
想象一个场景,你必须吸引那些在个人和职业生活中追求成功的人群。你已经准备了以下文本,它展示了表现出强大和完美与展示脆弱之间的对比:
在一个赞美力量和完美的世界中,记住脆弱是我们人性的本质。我们的挣扎和不完美使我们成为人类,但我们常常因为恐惧和羞愧而隐藏它们。然而,那些勇敢而真诚地拥抱自己弱点的人培养了非凡的韧性。那些害怕自己脆弱性的人可能会变得僵化,被困在无法达到的完美假象中。因为正是在我们的脆弱中,我们成长并真正地生活。(改编自 Oliani,2023,“Ho Paura di Essere Debole [我害怕软弱],《圣安东尼奥使者》,第 6 期,第 53 页)。*
假设你的任务是为一篇关于脆弱和脆弱性的文本生成标题。为 ChatGPT 制定以下提示:为以下文本生成标题
-
拥抱脆弱:通往非凡韧性和真实生活的道路
-
在追求完美的世界中,脆弱是通往真实和韧性的门户
-
脆弱:拥抱不完美,以实现真实的韧性和成长
所有这些标题都是通用目的。下一步是向提示中添加一个角色,以便 ChatGPT 生成更具体的输出。
4.2.2 扮演一个角色
当传达信息时,你可能会有一个以下的目标:
-
娱乐—重点是提供乐趣、娱乐或放松。你可能想让人们发笑或唤起情感。
-
教育—重点是向观众提供知识、见解或技能,以拓宽观众的知识基础并鼓励学习。
-
信息—重点是让观众了解特定主题,如新闻更新或当前事件。
-
灵感—重点是激励观众,例如激发创造力或鼓励个人成长。励志信息通常分享成功故事或展示榜样。
-
行动邀请—重点是激励观众参与特定的行为或活动。
-
建立关系—重点是通过对个人轶事和共同经历的分享与观众建立联系。信息旨在建立信任、加强现有关系或培养社区感。
根据目标的不同,你将扮演不同的角色,这些角色使用特定的叙事技巧。在某些情况下,在相同的情况下,你可能扮演不同的角色,但通常,角色取决于目标受众。
再次考虑这样一个场景,你必须吸引那些在个人和职业生活中追求成功的受众。使用 ChatGPT 来调整文本以适应你的角色。使用以下语法来为 ChatGPT 制定提示:扮演一个[角色]。在尖括号
表 4.1 所有角色的提示结果
| 角色 | 输出 |
|---|---|
| 娱乐者 | 揭示我们的人性:拥抱脆弱,在不完美中蓬勃发展 |
| 教育者 | 培养韧性:拥抱脆弱和不完美,促进个人成长和福祉 |
| 通知者 | 拥抱脆弱的力量:庆祝不完美,培养韧性 |
| 激励者 | 拥抱你的脆弱力量:释放韧性,拥抱不完美,过上充实的生活 |
| 行动邀请者 | 拥抱你的脆弱人性:克服恐惧,拥抱不完美,释放你的韧性潜力 |
| 关系建立者 | 一起拥抱脆弱:培养真诚的连接和建立有弹性的关系 |
每个输出都有相同的结构([标题] [冒号] [副标题]),但它们的目标根据角色而变化。现在你已经学会了如何使用不同的角色来影响输出,让我们继续了解如何根据不同的目标受众来影响输出。
4.2.3 适应受众
根据你讲述数据故事时的上下文,你的受众会发生变化。例如,你可能有利益相关者、商人、儿童,甚至是哲学家等受众。更普遍地说,有以下类型的受众:
-
一般受众—一个具有多样化兴趣、背景和人口统计数据的广泛群体
-
目标受众—一个具有明确兴趣、背景和其他因素的特定群体
-
小众群体—一个相对较小的群体,他们有特定的兴趣、偏好或需求,例如特定爱好或特定领域的专业人士
-
大众群体—一个庞大且多样化的群体,他们消费吸引人的内容。与一般受众相比,大众群体是媒体和广告活动的目标。
再次考虑这样一个场景,你必须吸引那些在个人和职业生活中追求成功的受众。使用 ChatGPT 来调整文本以适应你的受众。使用以下语法来为 ChatGPT 制定提示:扮演一个[角色],针对[受众]。在尖括号
表 4.2 关系建立者角色所有受众类型的提示结果
| 受众 | 输出 |
|---|---|
| 一般受众 | 拥抱脆弱:通往弹性和真诚生活的道路 |
| 目标受众:商人 | 接受脆弱:在商业关系中的真诚与韧性之力 |
| 精准受众:足球爱好者 | 美丽的游戏:庆祝脆弱作为足球热情之核心 |
| 大众受众:年轻人 | 接受你的不完美:作为年轻人释放你内心的力量 |
所有输出都具有相同的结构([标题] [冒号] [副标题]),但它们的含义根据特定受众进行了调整。再次考虑仅指定角色的标题:共同接受脆弱:培养真诚的联系和建立韧性关系。这个标题与为普通受众生成的标题相似。当你添加特定受众时,标题会根据它进行定制。现在你已经学习了定义 ChatGPT 提示词的基本概念,让我们继续下一步:定义 DALL-E 的提示词。
4.3 DALL-E 提示的基本结构
要与 DALL-E 互动,你必须编写一个指定指令的提示词。你可以使用 DALL-E 的 Web 界面或附录 A 中描述的 Open AI API。
为 DALL-E 构建提示词有几种不同的方式。在这本书中,我们考虑由以下组成的提示词:
-
一个主题
-
一种风格
在本节的剩余部分,我们将分别描述提示词的所有部分,从主题开始。记住,如果输出不满意,你可以对其进行细化或使用本节后面描述的编辑图像工具。
4.3.1 主题
图像的主题是吸引观众注意力的主要焦点。主题的例子包括人物、物体或场景。在定义图像的主题时,尽可能具体。一个通用的主题将生成一个通用的图像。对于每个输入,DALL-E 生成四个输出图像。在本节的剩余部分,我们将展示一些生成的图像示例。如果你亲自尝试这个工具,你会得到不同的示例。
图 4.8 展示了 DALL-E 对于提示词“一个人”生成的输出。由于输入非常通用,输出也是通用的。四个生成的图像彼此不同。

图 4.8 提示词“一个人”的输出
为了提高图像的焦点,你可以添加以下元素:
-
一个形容词
-
一种情感
-
上下文
-
颜色
让我们从添加一个限定主题的形容词开始。我们将给出一个与“穷人”相关的图像生成的具体示例。我选择这个主题是为了展示将图像插入数据故事中激发观众情感的可能性。实际上,数据故事讲述也为数据背后的人发声。
图 4.9 展示了对于提示词“一个穷人”的输出,其中“穷人”是限定形容词。所有生成的图像都包含一个手放在头上的人,但不一定是穷人。然而,与通用输出相比,我们得到了更好的结果。

图 4.9 输入提示“穷人”的输出
更具体地说,添加一个描述人物情绪的情感。图 4.10 显示了由提示“一个悲伤、贫穷的人”产生的输出。

图 4.10 输入提示“穷人”的输出
图像仍需改进。让我们添加一个背景,为主题创造上下文。图 4.11 显示了由提示“一个悲伤、贫穷的人,背景有垃圾”产生的输出。

图 4.11 输入提示“一个悲伤、贫穷的人,背景有垃圾”的输出
最后,让我们添加颜色。图 4.12 显示了输入提示“一个悲伤、贫穷的人,穿着破旧的棕色衬衫,背景有垃圾”的输出(要查看彩色版本,请访问电子书版本)。

图 4.12 输入提示“一个悲伤、贫穷的人,穿着破旧的棕色衬衫,背景有垃圾”的输出
现在,输出看起来令人满意。我们可以进行下一步:添加风格。
4.3.2 风格
风格定义了输出的艺术类型,包括照片、数字艺术、铅笔素描等等。此外,您还可以指定特定艺术家的风格,例如梵高或卡拉瓦乔。您只需尽情发挥您的想象力!
图 4.13 显示了输入提示“一个悲伤、贫穷的人,背景有垃圾,数字艺术”的输出,而图 4.14 显示了输入提示“一个悲伤、贫穷的人,背景有垃圾,黑白照片”的输出。

图 4.13 输入提示“一个悲伤、贫穷的人,穿着破旧的棕色衬衫,背景有垃圾,数字艺术”的输出

图 4.14 输入提示“一个悲伤、贫穷的人,穿着破旧的棕色衬衫,背景有垃圾,黑白照片”的输出
本章中描述的提示结构仅是使用 DALL-E 绘制图像的入门点。您还可以定义环境(室内或室外)、一天中的时间、角度等等。您可以在《DALL·E 2 提示手册》中找到更多信息(mng.bz/0GNx)。
4.3.3 编辑图像工具
如果您对生成的图像完全不满意,可以使用 DALL-E 界面提供的编辑图像工具进行修改。要访问此工具,请选择图像,然后点击编辑。您可以擦除图像的一部分(如图 4.15 所示),添加新的生成框架或上传图像。

图 4.15 使用橡皮擦
然后,您可以针对所选部分编写新的提示。让我们使用以下提示将一只小狗添加到图 4.15 的图像中:“一只小黑狗正在吃一片面包,黑白照片”。图 4.16 显示了产生的输出。

图 4.16 使用橡皮擦修改后的图像输出
我们终于有了所有部件:ChatGPT、DALL-E 和 GitHub Copilot(来自上一章)。现在,是时候实施一个实际示例,使用所有这些工具。但在那之前,让我们先来激活 GitHub Copilot,定义一个更系统的方法,以便更好地利用它。
4.4 使用 Copilot 构建 Altair 图表的组件
当为 Copilot 编写指令时,将它们拆分为单个步骤。同时,提供您希望 Copilot 执行的上下文,并具体说明您正在编写的代码的要求。最后,请记住,Copilot 是一个旨在协助开发者的工具,而不是取代他们。Copilot 功能强大,但它不能取代您作为开发者的角色。始终使用您的专业知识来评估 Copilot 提供的代码建议,并确保它们适合您的特定需求。
在本节中,您将了解如何编写指令让 Copilot 构建 Altair 图表。我们将重点关注先决条件、标记、编码、条件、复合图表和交互性。
4.4.1 先决条件
在绘制图表之前,请让 Copilot 导入所需的库并将数据集作为 pandas DataFrame 加载。
列表 4.1 导入所需的库和加载数据集
# Import the required libraries.
# Load the file '/path/to/data.csv' into a pandas DataFrame.
注意:第一条指令指定导入所需的库。Copilot 将根据后续指令自动计算需要导入哪些库。第二条指令指定要加载的数据集的精确路径。
练习 1
编写指令让 Copilot 加载位于本书 GitHub 仓库 04/data/data.csv 下的 data.csv 文件。您可以在本节 04/copilot/example.py 下找到本练习和下一个练习的解决方案。
4.4.2 标记
要定义一个标记,只需指示 Copilot 在 Altair 中绘制它。
列表 4.2 定义标记
# Draw a <type of mark> named chart in Altair
注意:使用关键词 Draw 和 Altair 来指定您想要在 Altair 中构建图表。将 <type of mark> 替换为您想要构建的标记类型。例如,如果您想构建柱状图,请写 bar chart。
4.4.3 编码
要设置编码,请向列表 4.2 中定义的指令中添加关键词 with:。此外,为每个要定义的编码通道添加一个项目符号。
列表 4.3 设置编码
# Draw a bar chart named bar in Altair with:
# * The category column as the x-axis
# * The value column as the y-axis
# * The country column as the color.
注意:在定义标记后,使用关键词 with: 后跟一个编码通道列表。对于每个编码通道,指定要使用的数据集列和通道。
练习 2
编写指令让 Copilot 使用之前练习中加载的数据集在 Altair 中绘制折线图。指定使用 X 列作为 x 通道,Y 列作为 y 通道。
4.4.4 条件
要设置与特定编码通道相关的条件,请使用以下列表中指定的指令。
列表 4.4 定义条件
# * The country column as the color. Set the color to:
# - 'red' for 'IT'
# - 'green' for 'FR'
注意:将条件附加到选定的编码通道。使用列表指定条件的内容。示例中,如果国家是IT,则将颜色设置为red;如果国家是FR,则设置为green。
练习 3
为 Copilot 编写指令,执行以下操作:
-
加载数据集 data3.csv,位于 04/data/data3.csv 下。
-
在
x通道中绘制具有类别列的条形图,在y通道中绘制值列。 -
如果国家列等于
IT,则将条形图的颜色设置为red;否则,设置为green。
4.4.5 复合图表
分别构建每个图表。然后,要构建复合图表,请编写特定的指令,如以下列表所示。务必为每个图表赋予不同的名称。
列表 4.5 构建复合图表
# Build a compound chart named chart with the line and bar charts aligned vertically
注意:指定你想要绘制的组合类型。示例中垂直排列了图表。
练习 4
为 Copilot 编写指令,使其垂直对齐之前练习中绘制的折线图和条形图。
4.4.6 交互性
要使图表交互式,请按照以下列表中的简单指令编写。
列表 4.6 使图表交互式
# Make the chart interactive.
注意:编写简单明了的指令以使图表交互式。
练习 5
为 Copilot 编写指令,使其条形图交互式。注意,你可以在生成的代码上方或下方插入新的 Copilot 指令。
现在你已经学会了如何编写基本指令使 Copilot 在 Altair 中构建图表,让我们转向一个结合 Altair 和 Copilot 的实际案例研究。
4.5 案例研究:你的训练团队
让我们想象你在一个体育公司工作。你正在训练一支在各个项目中的年轻运动员团队。对于每个项目,你都已经记录了世界纪录以及你团队达到的最佳时间,以便进行比较。不幸的是,你的公司可用的投资资金有限。你的老板要求你区分哪些项目值得继续训练,以期望在即将到来的比赛中取得好成绩。你可以在本书的 GitHub 仓库 CaseStudies/competitions 目录下找到这个示例的代码。该目录包含不同的 Python 脚本,每个脚本对应本节剩余部分描述的每个步骤。
表 4.3 显示了每个项目,你的团队达到的记录和最佳时间。在你的情况下,目标是显示每个训练类型中你团队的时间和记录之间的差异,然后选择你应该继续投资的训练类型。
表 4.3 案例研究的数据集显示了每个训练类型的记录、记录保持者、记录时间和你团队的最好时间。
| 训练类型 | 打破记录 | 记录保持者 | 记录时间(秒) | 团队最佳时间 |
|---|---|---|---|---|
| 短跑 | 100 米 | 爆发力 | 9.58 | 15.07 |
| 长跑 | 马拉松 | 艾利乌德·基普乔格 | 7,299 | 13,261.76 |
| 游泳 | 100 m 自由泳 | César Cielo | 46.91 | 88.01 |
| 自行车 | 个人计时赛 (ITT) | Rohan Dennis | 1,963.49 | 2,795.22 |
| 划船 | 2,000 m | Josh Dunkley-Smith | 335.8 | 462.33 |
我们将使用 DIKW 金字塔将此数据集转化为智慧。让我们从金字塔的第一步开始:将数据转化为信息。
4.5.1 将数据转换为信息
用于此目的的最简单图形是堆叠条形图,它将两列显示为堆叠系列。让我们使用 GitHub Copilot 生成这个简单的图表。首先,编写生成图表的注释列表。
列表 4.7 生成堆叠条形图
# Import the required libraries.
# Load '../data/competitions.csv' as pandas dataframe.
# Select only the columns Training Type, Record Time (Seconds), Our Best Time.
# Use melt to transform the dataframe into a long format, with the parameter id_vars set to 'Training Type'.
# Draw a bar chart named chart in Altair with:
# * The Training Type column as the x-axis
# * The value column as the y-axis
# * The variable column as the color
# Save the chart as 'competitions.xhtml'.
注意:描述加载、操作和绘制数据集的操作顺序。
Copilot 将生成几个代码选项。选择以下列表中显示的代码。
列表 4.8 Copilot 生成的堆叠条形图
import pandas as pd
import altair as alt
df = pd.read_csv('data/competitions.csv')
df = df[['Training Type', 'Record Time (Seconds)', 'Our Best Time']]
df = pd.melt(df, id_vars='Training Type')
chart = alt.Chart(df).mark_bar().encode(
x='Training Type',
y='value',
color='variable'
)
chart.save('competitions.xhtml')
注意:首先,加载并操作数据集。然后,使用 mark_bar() 绘制堆叠条形图。
图 4.17 显示了生成的条形图。除了非常窄之外,生成的图表还呈现了一个基本问题:我们无法从图表中确定应该关注哪些学科。这可以归因于图表的清晰度不足,这使得我们难以有效地解释数据。在实践中,问题是变量之间的比例差异。短跑在 10 秒的范围内,而长跑在数千秒的范围内。因此,我们需要修改图表设计以易于传达信息。

图 4.17 Copilot 生成的堆叠条形图
通常,当我们想要在多个类别中比较两个变量时,计算每个类别的变量相对于另一个变量的百分比差异或百分比增加量,然后显示出来,这可能很有用。这种方法使我们能够比较两个变量,并识别数据中的任何模式或趋势。要生成此类图表,我们还可以再次利用 Copilot,它可以帮助我们编写必要的代码。编写以下列表中指定的指令列表。粗体指令显示了与列表 4.7 的不同之处。
列表 4.9 生成带有百分比差异的条形图
# Import the required libraries.
# Load '../data/competitions.csv' as pandas dataframe.
# Select only the columns Training Type, Record Time (Seconds), Our Best Time.
# Calculate the percentage difference between columns Our Best Time and Record Time (Seconds), and store it in a new column called Percentage Difference.
# Draw a bar chart with the following encodings:
# * The Percentage Difference on the y-axis with the following properties:
# - The domain of the Y scale to [0,100]
# * Training Type on the x-axis with the following properties:
# - The values sorted in descending order (-y)
# Set the following properties of the bar chart:
# * width to 300 pixels
# Save the chart as 'competitions.xhtml'.
注意:描述加载、操作和绘制数据集的操作顺序。还要指定按降序排序数据,并将 Y 轴的范围设置为 [0,100] 以使图表更清晰。由于图表比之前的更复杂,请在请求 Copilot 绘制图表时添加指令 with the following encodings。同样,对于标记属性也要这样做。还要注意嵌套列表,以将特定属性值添加到编码通道中。
Copilot 将生成几个代码选项。选择以下列表中显示的代码。
列表 4.10 Copilot 生成的条形图
import pandas as pd
import altair as alt
df = pd.read_csv('data/competitions.csv')
df = df[['Training Type', 'Record Time (Seconds)', 'Our Best Time']]
df['Percentage Difference'] = (df['Our Best Time'] - df['Record Time (Seconds)']) / df['Record Time (Seconds)'] * 100
chart = alt.Chart(df).mark_bar().encode(
x=alt.X('Training Type', sort='-y'),
y=alt.Y('Percentage Difference', scale=alt.Scale(domain=[0,100]))
).properties(
width=300
)
chart.save('competitions.xhtml')
注意:首先,加载并操作数据集。然后,向数据集中添加一个名为Percentage Difference(百分比差异)的列,包含百分比差异。最后,使用mark_bar()绘制堆叠条形图。Copilot 可能不会生成完整的代码,因此我们需要根据所需的输出进行调整。
图 4.18 显示了生成的图表。

图 4.18 Copilot 生成的带有百分比差异的条形图
该图表突出显示的是我们的最佳时间和记录时间之间百分比差异最大的运动是游泳和长跑。然而,该图表可能具有误导性,因为它不直观,因为最大的条形实际上是表现最差的。为了提高图表的可读性和直观性,并更好地传达每个运动中运动员的实际表现,我们可以计算百分比提高。
列表 4.11 计算百分比提高
df['Percentage Improvement'] = 100 -
(df['Our Best Time'] - df['Record Time (Seconds)']) / df['Record Time (Seconds)'] * 100
注意:计算相似度百分比:100 减去百分比差异。将列表 4.10 的第 5 行和第 6 行替换为列表 4.11 中显示的代码。
百分比提高表示我们的最佳时间与记录时间相比的性能提高的比例。百分比提高越高,表明运动员的表现越接近或优于记录保持者,而百分比提高较低则表明运动员的表现距离记录保持者的表现越远。这更直观,因为较大的条形现在表示最佳表现。图 4.19 显示了百分比提高。划船和自行车是表现更好的运动;因此,你可以选择那些运动进行进一步投资。

图 4.19 带有百分比提高的条形图
现在我们已经从数据中提取了信息,我们可以进入 DIKW 金字塔的下一步。在下一节中,我们将深入探讨如何将信息转化为知识。
4.5.2 将信息转化为知识
此步骤涉及向信息添加上下文。在这种情况下,上下文可能涉及三个方面:
-
突出显示你想要投资的两个运动
-
添加一个强调前两个运动的图片
-
例如,添加一个基准,比如提高 50%,显示为什么选择那些运动
让我们从第一个点开始,突出显示你想要投资的两个运动。
突出显示前两个运动
在之前生成的代码中,在y通道之后立即添加以下列表中显示的注释,以使用 Copilot 生成代码。
列表 4.12 突出显示表现最佳的运动
# Add the color encoding. Set the color to:
# - #80C11E if the Percentage Improvement is greater than 50,
# - lightgray otherwise
注意:描述如何突出显示表现更好的运动。
以下列表显示了生成的代码。
列表 4.13 突出显示表现最佳运动的生成代码
color=alt.condition(
alt.datum['Percentage Improvement'] > 50,
alt.value('#80C11E'),
alt.value('lightgray')
)
注意:Copilot 生成的代码使用条件语句来选择应用于每个条形的颜色。
图 4.20 显示了生成的图表。现在,我们已经突出显示了前两名运动,我们可以继续向图表中添加图像。

图 4.20 显示了突出显示前两名运动的条形图
添加执行图像
要为前两名运动中的每一项生成图像,向 DALL-E 提供以下提示:“一位热情的运动员在白色背景上练习[sport],卡通风格”。图 4.21 和 4.22 分别显示了 划船 和 自行车 的输出。

图 4.21 呈现的划船输出
我们选择图 4.21 中的第二幅图像和图 4.22 中的第一幅图像。如果你发现图像中存在一些不完美的地方,你可以使用编辑工具进行改进。要将图像包含在 Altair 图表中,你必须将其保存到远程仓库中,例如 GitHub,并向 Altair 提供远程 URL。

图 4.22 呈现的自行车骑行输出
现在,让我们让 Copilot 编写将图像添加到图表中的代码。下面的列表描述了 Copilot 的指令。
列表 4.14 向图表添加图像
# Add a new column to df called 'url' with the following value:
# * 'https://[..]/cycling.png' for Training Type = 'Cycling'
# * 'https://[..]/rowing.png' for Training Type = 'Rowing'
# * '' for all other Training Types
# Add the following image to the chart:
# * The image is a 35x35 pixel image.
# * The image is located at x='Training Type', y='Percentage Improvement'.
注意:首先,向 DataFrame 中添加一个包含图像 URL 的列。然后,指定如何将图像添加到图表中。
下面的列表显示了输出结果。
列表 4.15 添加图像的代码
df['url'] = ''
df.loc[df['Training Type'] == 'Cycling', 'url'] = 'https://[..]/cycling.png'
df.loc[df['Training Type'] == 'Rowing', 'url'] = 'https://[..]/rowing.png'
chart = chart + alt.Chart(df).mark_image(width=35, height=35).encode(
x=alt.X('Training Type', sort='-y'),
y=alt.Y('Percentage Improvement'),
url='url'
)
注意:首先,Copilot 建议通过添加新列来操作 DataFrame。然后,Copilot 建议使用 mark_image() 标记。
图 4.23 显示了添加到前两名运动列中的图像。现在,我们可以继续到下一步:生成基线。

图 4.23 添加图像后的条形图
添加基线
添加基线意味着在图表中添加一条水平红色线以进行比较。例如,我们可以将水平线的值设置为 50,这对应于 50%。下面的列表显示了 Copilot 的指令。
列表 4.16 生成基线
# Add a horizontal red line to the chart at y=50.
# Add the line to the chart.
注意:描述如何在 50% 改进处生成基线。
下面的列表显示了生成的代码。
列表 4.17 创建基线的生成代码
line = alt.Chart(pd.DataFrame({'y': [50]})).mark_rule(color='red').encode(y='y')
chart = chart + line
注意:Copilot 生成的代码使用 mark_rule() 生成水平线,并使用 + 运算符叠加两个图表。
图 4.24 显示了最终的图表。与图 4.19 相比,你添加了帮助读者立即了解应该投资哪些运动的上下文。

图 4.24 显示了表现优于基线的两种运动的条形图
我们可以通过调整坐标轴标题和应用整理来进一步改进图 4.24 中的图表。我们将在下一章中看到如何做到这一点。现在,我们已经为图表添加了上下文,我们可以继续到 DIKW 金字塔的下一步:将知识转化为智慧。
4.5.3 将知识转化为智慧
此步骤涉及向图表添加一个动作。让我们添加一个邀请受众投资划船和骑自行车的标题。我们可以使用 ChatGPT 来生成标题。写下以下提示:
-
扮演行动邀请者。
-
针对体育公司老板定制。
-
为以下文本生成一个引人注目的标题(在尖括号内:<您应该投资划船和骑自行车的资金,这些是最常练习的运动>)。
生成的一个标题示例是:解锁潜力:投资划船和骑自行车以获得最大回报!如果您对生成的标题不满意,可以要求 ChatGPT 生成另一个或一组n个标题。
一旦我们定义了标题,我们就可以为 Copilot 编写添加标题的说明,如下所示。在图表的properties()方法中添加注释。
列表 4.18 添加标题
# Add the following properties to the chart:
# * title to 'Unlock the Potential: Invest in Rowing and Cycling for Maximum Returns!'
注意:在图表的属性函数中添加此指令。
下面的列表显示了生成的代码。
列表 4.19 添加标题的生成代码
title='Unlock the Potential: Invest in Rowing and Cycling for Maximum Returns!'
注意:Copilot 生成的代码使用title属性。
图 4.25 显示了最终的图表。要渲染此图表,您应该从 Web 服务器运行生成的 HTML。如果您没有 Web 服务器,可以使用以下命令从您的目录中运行一个临时 Web 服务器:python -m http.server。

图 4.25 带有行动呼吁标题的条形图
现在,您可以最终确定图表。首先,旋转 x 轴标签并移除标题。通过添加axis属性和label角度(axis=alt .Axis(title=None, labelAngle=0)修改x通道。接下来,将所有图表的标题设置为Percentage Improvement(百分比改进)(主图表、图像和注释)。最后,如列表 4.20 所示,在红线上方添加注释文本。
列表 4.20 添加文本注释
annotation_df = pd.DataFrame({'text': ['Use 50% as the benchmark for sports selection']})
annotation = alt.Chart(annotation_df
).mark_text(
size=10,
align='left',
color='red',
x=170,
y=150,
dy=-10
).encode(
text='text'
)
chart = (chart + annotation)
注意:首先,定义注释 DataFrame,然后将其用作图表的输入。
您可以在本书的 GitHub 仓库下的 04/case-study/7-bar-chart.py 中找到此示例的完整代码。图 4.26 显示了生成的图表。

图 4.26 最终的条形图,带有旋转的标签、调整后的标题和注释
在本章中,您已经了解了生成式 AI 工具背后的基本概念,特别是专注于 ChatGPT、DALL-E 和 Copilot。在下一章中,您将看到如何结合 Altair 和生成式 AI 工具,使用 DIKW 金字塔构建引人入胜的数据故事。
摘要
-
生成式 AI 是人工智能的一个子领域,旨在根据训练数据生成新的内容。您可以使用生成式 AI 用于不同的目的,包括文本、图像、代码、视频和语音生成。
-
提示是一个包含生成式 AI 工具指令的输入文本。
-
ChatGPT 是一种文本到文本的生成式 AI 工具。为了为 ChatGPT 编写有效的提示,请将提示分为三个部分:角色、受众和主题。
-
DALL-E 是一个文本到图像的生成式 AI 工具。为了为 DALL-E 编写有效的提示,定义主题和风格。
-
Copilot 是一个文本到代码的生成式 AI 工具。为了使 Copilot 高效地编写代码建议,请使用具体且明确的指令,使用简单语言并避免复杂术语。
-
不要将 Copilot 作为你作为开发者的替代品。相反,使用它来辅助编程。
-
总要记住,生成式 AI 不能替代你的创造力和推理能力。
参考文献
提示工程
-
Barber, T. (2023). Dall-E 提示技巧与窍门。
torybarber.com/dall-e-prompting-tips-tricks/ -
DALL·E 2 提示手册。(2022)。https://mng.bz/0GNx。
-
Fulford, I. 和 Ng, A. (未注明)。为开发者设计的 ChatGPT 提示工程。
www.deeplearning.ai/short-courses/ChatGPT-prompt-engineering-for-developers/ -
Kemper, J. (2023). ChatGPT 指南:使用这些提示策略最大化你的结果。
the-decoder.com/ChatGPT-guide-prompt-strategies/ -
Madhvani, N. (2023).生成式 AI 工具在创意领域:权力与压力游戏正在进行! 2023 年 3 月。
www.rapidops.com/blog/generative-ai-tools/
其他
- 监督式机器学习。(未注明)。Javatpoint。
www.javatpoint.com/supervised-machine-learning
第二部分:使用 DIKW 金字塔进行数据讲故事
当你构建一个数据驱动的故事时,你应该考虑许多方面:从数据中提取的洞察力;数据背后的背景信息;以及与数据相关的下一步行动,邀请观众参与其中。数据、信息、知识、智慧(DIKW)金字塔帮助你考虑所有这些方面。在本部分,你将深化对 DIKW 金字塔所有步骤的理解。
在第五章中,你将巩固与 DIKW 金字塔相关的概念。本章的第一部分集中在无家可归的案例研究上,以及如何使用 DIKW 金字塔将数据从原始图表转化为数据故事。本章还介绍了一些与数据讲故事相关的一般概念,例如叙事结构和数据讲故事弧线。在章节的第二部分,你将实施另一个基于虚假新闻的案例研究。
第六章至第九章深化 DIKW 金字塔的每个步骤。它们实施一个单一案例研究:美国鲑鱼养殖中的安全问题。在每个章节的结尾,你将使用章节中描述的概念改进这个案例研究。
第六章从 DIKW 金字塔的底部开始,重点关注将数据转化为信息。你将学习一些基本技巧来提取和表示洞察力。本章还说明了如何构建最常见的图表系列:柱状图、折线图、饼图和地理地图。
在第七章和第八章中,你将通过将信息转化为知识来攀登金字塔。你将学习如何为你的数据故事添加背景信息。在第七章中,你将专注于文本背景,而在第八章中,则专注于视觉背景。你还将学习高级概念,如检索增强生成(RAG)和大型语言模型(LLM)微调。由于这些概念不断演变,有些可能在阅读这部分时可能已经过时。然而,整体理论仍然有效,尽管代码语法可能有所不同。
在第二部分的最后一章,第九章,你将通过将知识转化为智慧达到 DIKW 金字塔的顶端。你将学习智慧的主要三个要素:知识综合、经验和良好的判断。你还将使用 ChatGPT 作为额外的经验来源。
第五章:使用 DIKW 金字塔制作数据故事
本章涵盖
-
无家可归者的故事,本章的情景
-
数据故事是什么以及它与 DIKW 金字塔的关系
-
如何将生成式 AI 融入 DIKW 金字塔
创作故事是数据故事讲述中最激动人心的部分。这实际上是在跟随一个有开头、主要观点和结论的情节——就像在电影院或剧院的故事一样。然而,这里受到数据的约束。在本章中,我们将通过描述两个例子来关注使用 DIKW 金字塔。第一个例子将分析意大利无家可归者的问题,第二个将检查一个假设网站上的虚假新闻问题。我们还将描述数据故事的概念以及它与之前章节中已讨论的 DIKW 金字塔的关系。最后,我们将描述一些策略,基于如何为 ChatGPT 编写特定的提示来生成图表的上下文,以及为 DALL-E 生成用于图表的上下文图像,以将生成式 AI 融入 DIKW 金字塔。
5.1 打破僵局:无家可归者的故事
假设安吉丽卡为一家希望申请基金会资金以帮助减少意大利无家可归者人数的人道主义组织工作。人道主义干预可以应用于意大利多达四个地区。资金申请要求准备一个数据可视化图表,以激励选定的地区并详细说明资助提案的原因。
安吉丽卡的上司要求她完成一项关于投资哪些地区的研究,并激励她做出选择。安吉丽卡开始在网上搜索关于意大利无家可归者数据的可能数据集。经过几番搜索,她发现了 2021 年无家可归者数据的 ISTAT 数据集(mng.bz/Bd6J)。她下载了数据集并开始分析它。表 5.1 显示了无家可归者数据集的一个简化版本。原始数据集的列名是意大利语,为了提高可读性,它们被翻译成了英语。
表 5.1 2021 年 ISTAT 无家可归者数据集的摘录
| ITTER107 | 地区 | 性别 | 年龄 | 国籍 | 价值 |
|---|---|---|---|---|---|
| ITC1 | 皮埃蒙特 | M | 总计 | ITL | 4,218 |
| ITC1 | 皮埃蒙特 | F | 总计 | ITL | 1,496 |
| ITC2 | 瓦莱达奥斯塔 | M | 总计 | ITL | 41 |
| ITC2 | 瓦莱达奥斯塔 | F | 总计 | ITL | 17 |
ISTAT 无家可归者数据集
数据集有以下列:
-
ITTER107—地区 ID
-
地区—地区名称
-
性别—男性(M)、女性(F)或总计(T)
-
年龄—所有年龄之和(总计)、17 岁以下(Y_UN17)、18 至 34 岁(Y18-34)、35 至 54 岁(Y35-54)和 55 岁以上('Y_GE55)
-
国籍—意大利(ITL)、外国(FRGAPO)或总计(总计)
-
价值—无家可归者的实际人数
在分析结束时,安吉丽卡通过以下列表中的代码使用 Altair 编写代码,生成了图 5.1 所示的图表。您可以在 GitHub 仓库的 CaseStudies/homeless-people/raw-chart.py 中找到完整的代码。
列表 5.1 Altair 中的地图
import pandas as pd
import altair as alt
df = pd.read_csv('source/homeless.csv')
df['Territory'] = df['Territory'].str.replace('Trentino Alto Adige', 'Trentino-Alto Adige/Südtirol') #1
df = df[(df['Age'] == 'TOTAL') & (df['Sex'] == 'T') & (df['Citizenship'] == 'TOTAL')] #2
url = "https://raw.githubusercontent.com/openpolis/geojson-italy/master/topojson/limits_IT_regions.topo.json"
map = alt.topo_feature(url, "regions") #3
chart = alt.Chart(map).mark_geoshape().encode(
tooltip='properties.reg_name:N',
color=alt.Color('Value:Q')
).project('mercator').properties(
width=500,
height=500
).transform_lookup(
lookup='properties.reg_name',
from_=alt.LookupData(df, 'Territory', ['Territory', 'Value'])
).properties(title='Homeless in Italy in 2021')
chart.save('raw-chart.xhtml')
1 数据清洗
2 关注总年龄、总性别和总国籍。
3 通过 URL 加载 TopoJSON 文件。

图 5.1 2021 年意大利无家可归者的地图
备注:该图表使用包含意大利地图的底层topoJSON对象构建地图。要构建地图,请使用mark_geoshape()标记,并通过project()方法指定投影。此外,通过transform_lookup()方法将topoJSON文件的properties.reg_name变量与df DataFrame 的Territory变量进行映射。我们将在下一章更深入地讨论如何构建地理地图。
安吉丽卡向她的上司展示了图表,上司要求她回答以下问题:我们需要申请资金的四个地区是哪些? 安吉丽卡查看图表,并给出以下答案:颜色较深的地区。她的答案非常含糊,因为从她生成的图表来看,并不立即清楚哪些地区颜色最深。此外,选择这四个地区的原因也不明确。安吉丽卡不得不承认,她的图表未能完成其使命。
5.1.1 图表有什么问题?
图表的主要问题是安吉丽卡的上司(观众)期望在图表中看到的内容与图表实际显示的内容之间存在很大差异。在实践中,安吉丽卡的图表没有回答她请求的问题(图 5.2)。

图 5.2 安吉丽卡的上司(观众)要求回答一个具体的问题(在左侧)。实际上,图表回答了通用问题(在右侧)。
建立图表的第一条规则是:图表必须准确回答目标观众提出的问题。尽量不要让观众去解读答案。为他们做这项工作!
安吉丽卡的老板要求她重新绘制图表。她低头回到办公室,再次分析她的数据集。安吉丽卡明白问题不在于数据分析,而在于如何有效地表示数据。她尝试这样的推理:观众必须一眼就能理解这四个地区以申请资金。这四个地区可能是什么?可能是拥有最多无家可归者数量的那些地区。她之前图表中缺少的是可能的部分。她考虑制作一个条形图,按无家可归者数量降序显示地区。经过一番努力,她制作了图 5.3 中的图表,并在列表 5.2 中编写了代码。您也可以在 GitHub 仓库中找到该书的代码,位于 CaseStudies/homeless-people/bar-chart.py。

图 5.3 展示了 2021 年按地区划分的意大利无家可归者数量的条形图
列表 5.2 Altair 中的条形图
chart = alt.Chart(df).mark_bar(
color='#80C11E'
).encode(
y = alt.Y('Territory',
sort='-x',
axis=alt.Axis(title='')),
x = alt.X('Value',
axis=alt.Axis(tickCount=4,title='Number of homeless people'))
).properties(
width=500,
title='Homelessness in Italy in 2021'
)
).properties(title='Homeless in Italy in 2021')
chart = chart.configure_title(
fontSize=20,
offset=25
).configure_axis(
grid=False
).configure_view(
strokeWidth=0
)
chart.save('bar-chart.xhtml')
注释:该代码使用mark_bar()方法构建条形图。它还使用color参数设置条的颜色。为了按降序对条进行排序,在y通道中使用sort参数,并指定按x编码通道的降序排序。使用configure_title()配置标题参数,例如字体大小(fontSize)和标题与图表之间的offset。
该图表回答了老板的问题:这四个区域是拉齐奥、伦巴第、皮埃蒙特和坎帕尼亚。安吉丽卡注意到这四个区域中的三个也对应着意大利人口最多的地区。当安吉丽卡反思这一点时,她注意到她的图表中存在一个潜在的错误。人口最多的地区也拥有最多的无家可归者,这正是因为它们的人口更多。
因此,该图表没有以相关的方式展示情况。她需要根据人口对数据进行归一化——或者根据人口规模调整数字。如果没有任何归一化,数据可能会存在偏差。例如,考虑一个有 100 人口、其中 30 人是无家可归者的地区。再考虑一个有 10 人口、其中 8 人是无家可归者的地区。如果你表示绝对值,你可能会得出结论,在第一种情况下,无家可归者比第二种情况下多。但如果你更关注场景,在第二种情况下,80%的人口是无家可归的,而在第一种情况下,只有 30%的人口是无家可归的。
图 5.4 显示了经过归一化处理后的结果图表,重点关注拥有最多无家可归者人口的四个地区。列表 5.3 描述了绘制图表所编写的代码(脚本 CaseStudies/homeless-people/story-chart.py)。

图 5.4 展示了 2021 年人口为 1000 人的地区无家可归者数量的条形图
列表 5.3 列表 5.3 Altair 中的改进条形图
import pandas as pd
import altair as alt
df = pd.read_csv('source/homeless.csv')
df['Territory'] = df['Territory'].str.replace('Trentino Alto Adige',
↪ 'Trentino-Alto Adige/Südtirol') #1
df = df[(df['Age'] == 'TOTAL') & (df['Sex'] == 'T') & (df['Citizenship']
↪ == 'TOTAL')] #2
df = df[['Value', 'ITTER107']]
df_pop = pd.read_csv('source/population.csv')
df_pop = df_pop[(df_pop['Age'] == 'TOTAL') & (df_pop['Sex'] == 'T')] #3
df_pop = df_pop[['Value', 'ITTER107','Territory']]
df_tot = df_pop.set_index('ITTER107').join(df.set_index('ITTER107'),
↪ lsuffix='_pop', rsuffix='_hom').reset_index()
df_tot['Ratio'] = df_tot['Value_hom']/df_tot['Value_pop']*1000
chart = alt.Chart(df_tot).mark_bar().encode(
y = alt.Y('Territory',
sort='-x',
axis=alt.Axis(title='')),
x = alt.X('Ratio',
axis=alt.Axis(tickCount=4,title='')),
color=alt.condition(alt.datum.Ratio > 2,
alt.value('#80C11E'),
alt.value('lightgray'))
).properties(
width=500,
title='Number of homeless people in a population of 1,000'
)
1 数据清洗
2 关注总年龄、总性别和总公民身份。
3 按人口标准化值。
备注:代码加载了包含与无家可归者数据集相同字段的人口数据集,除了值列,该列表示实际人口。代码将两个数据集(人口df_pop和无家可归者df)合并,并计算无家可归者与人口之间的比率。然后,图表使用比率作为x编码通道构建条形图。
Angelica 的图表最终回答了她老板的问题:四个地区是拉齐奥、利古里亚、皮埃蒙特和萨丁岛。最后,Angelica 向她的老板展示了她的图表,她的老板对她结果非常满意。
为了申请资金,Angelica 必须将她的图表发送到一个委员会,该委员会将决定是否资助她组织的提案。然而,经过仔细评估后,基金委员会决定不资助她的项目,因为图表缺乏动力。
5.1.2 展示中有什么问题?
要了解为什么基金委员会拒绝资助 Angelica 的提案,让我们回顾她所遵循的路径。她从原始数据开始,进行分析,提取洞察,并通过图表来表示,在将图表发送给基金委员会之前(图 5.5)。

图 5.5 Angelica 的图表生成之旅。Angelica 直接向她的观众展示了数据探索的结果。
她的图表所缺乏的是没有上下文,也没有推动基金委员会接受她提案的动力。Angelica 应该在数据探索阶段之后添加一个解释阶段,在那里她可以用她的数据吸引观众,并激励他们接受她的提案。Angelica 可以使用 DIKW 模型将她数据的探索阶段转移到解释阶段(图 5.6)。

图 5.6 Angelica 应该将她的数据探索阶段转化为数据解释阶段。
换句话说,Angelica 还没有将数据转化为故事。在他的书籍《有效数据叙事》中,Brent Dykes 说:“数据故事的形成始于使用探索性数据可视化来发现洞察。一旦发现有意义的洞察,就使用解释性数据可视化来讲述故事。”(Dykes,2019)Angelica 构建的图表仅构成了数据故事的主要观点。然而,为了有一个完整的故事,Angelica 还应该包括背景和下一步行动(图 5.7)。基金委员会期望 Angelica 的图表能够回答这个问题:为什么我们应该资助你的项目?将图表组织成一个故事将增加她获得资助的机会。

图 5.7 基金委员会期望看到一个包含背景、主要观点和下一步行动的故事,邀请他们资助 Angelica 的提案。
现在,你理解了将数据转化为故事以有效地向观众传达信息的紧迫性。接下来,让我们进入下一步:数据故事是什么及其主要组成部分。
5.2 揭示叙事:数据故事是什么
根据《剑桥词典》,故事是“对一系列事件的真实或虚构描述。”故事是通过一个结构化、连贯的叙述来分享信息或娱乐,从而吸引观众的方式。故事的目标始终是吸引观众以传达信息。
在他的著作《诗学》中,古希腊哲学家亚里士多德提出了一种构建悲剧的框架,也称为“三幕结构”。该结构由三个主要部分组成:设置、对抗和解决(图 5.8)。

图 5.8 亚里士多德提出的三幕结构来构建悲剧
本节设置向观众介绍了故事的角色、背景和基本冲突。在对抗中,主要角色面临障碍并努力克服它们。最后,在解决阶段,故事达到冲突得以解决的点。亚里士多德的悲剧结构至今仍在文学、电影和其他叙事形式中得到广泛应用。其有效性在于其构建紧张和悬念的能力,从而为观众带来令人满意的结局。
在《有效数据叙事》一书中,Dykes 提出了一种三幕悲剧的更新版本,适用于数据叙事场景(图 5.9)。Dykes 称之为“数据故事结构弧”。

图 5.9 Dykes 提出的数据故事结构弧
数据故事首先通过(a)定义数据背后的背景和(b)激发观众的兴趣来开始。Dykes 还建议在此初始阶段加入一个钩子。钩子是“一个作为故事转折点的显著观察,开始揭示问题或机会”(Dykes,2019)。然后,数据故事通过(c)揭示洞察力、(d)给观众一些思考时间以及(e)通过提出下一步行动来结束。
图 4.9 中提出的结构在构建图表时可能看似抽象且难以应用。使用第一章中介绍的 DIKW 金字塔,你可以将简单的图表转变为故事。
5.2.1 使用 DIKW 金字塔简化数据故事
在第一章中,我们介绍了数据、信息、知识、智慧(DIKW)金字塔,并在宠物活动等实际案例中用它将数据转化为智慧。我们可以概括第二章中描述的例子,并使用 DIKW 来构建任何数据故事。图 5.10 显示了如何将数据故事结构弧映射到 DIKW 金字塔上:
-
从数据中提取洞察力对应于数据故事的主要点。
-
将背景添加到提取的信息中相当于定义背景并提高观众的兴趣。
-
添加行动号召相当于邀请观众思考和跟随下一步。

图 5.10 数据讲述弧与 DIKW 金字塔的映射
在数据之前拥有知识可能看起来很奇怪。然而,在这里,我们所说的“知识”是指理解故事并将其置于特定背景所需的所有元素。所描述的映射表明了构建数据故事时的自然流程(图 5.11)。首先,确定故事的主要观点;然后设置背景;最后,设置下一步。

图 5.11 构建数据故事的流程
5.2.2 DIKW 在行动:完成无家可归者故事
本章开头描述的无家可归者例子停留在 DIKW 金字塔的信息层面,从数据故事的角度来看,它只包括主要观点。向图表添加背景涉及吸引观众的注意力,并使他们对你所描述的问题产生兴趣。在第二章中,你学习了在 Altair 中,你可以向图表添加文本注释,将信息转化为知识。此外,还有其他方法可以向图表添加背景,包括标签、标题、来源、图像、描述和比较。我们将在第六章中更详细地描述这些内容。
在无家可归者问题的案例中,你可以添加描述这些人们情况的文本,以及可能给涉及的人一个面孔的照片。我们将在第六章中看到如何将英雄添加到故事中。现在,只需给数据背后的人一个面孔就足够了。
在列表 5.4 中,我们将描述背景的文本作为图表副标题添加。使用 TitleParams() 函数来指定标题属性,包括副标题。将副标题设置为
Homelessness is a heartbreaking reality that leaves individuals and families without a stable home, leading to devastating consequences such as poor health and social isolation.
此外,添加简短描述,帮助读者关注数据:
The chart describes the number of homeless people in Italy per 1,000 inhabitants, organized by region. The data is from 2021, the most recent year available (Source: ISTAT).
列表 5.4 将背景作为副标题添加
# Add context
chart = chart.properties(width=500,title=alt.TitleParams(
text=["Together, Let's Make a Difference:","Support Our Project to Help the Homeless!"],
subtitle=['Homelessness is a heartbreaking reality that leaves individuals and families without a stable home,','leading to devastating consequences such as poor health and social isolation.', 'The chart describes the number of homeless people in Italy per 1,000 inhabitants, organized by region. ', 'The data is from 2021, the most recent year available (Source: ISTAT).']],
subtitleFontSize=18,
))
注意:在 properties() 方法中,使用 title 参数来设置图表标题。使用 TitleParams() 函数定义标题,该函数可以接收多个参数作为输入。示例传递了以下参数:标题(text)、副标题(subtitle)、副标题字体大小(subTitleFontSize)、标题字体大小(titleFontSize)。
列表 5.5 展示了如何向图表添加两张照片,并将它们与原始图表结合来构建仪表板。你必须在一个网络服务器上运行代码才能使其工作。如果你没有网络服务器,你可以从命令行运行一个本地和临时的网络服务器,在包含生成的 HTML 文件的目录中运行以下命令:python -m http.server. 服务器应在端口 8000 上监听,并从启动它的目录中提供所有文件。将你的浏览器指向 http://localhost:8000/chart.xhtml 以访问文件 chart.xhtml。
列表 5.5 添加上下文作为图像
image1 = alt.Chart(pd.DataFrame({'image_url': ['source/homeless1.png']})).mark_image(
width=200,
height=200,
).encode(
url='image_url',
x=alt.value(0), # pixels from left
y=alt.value(50) # pixels from the top
) #1
image2 = alt.Chart(pd.DataFrame({'image_url': ['source/homeless2.png']})).mark_image(
width=200,
height=200,
).encode(
url='image_url',
x=alt.value(0), # pixels from left
y=alt.value(300)
) #2
image1 + image2 | chart
1 构建第一个图像。
2 构建第二个图像
备注:使用mark_image()向图表添加图像。在传递给图表的 DataFrame 中设置图像文件的路径。使用x和y通道设置图像在图表中的位置。使用相同的图表绘制image1和image2(+运算符),并将主图表相对于image1 + image2绘制在右侧。
为了完成无家可归的故事,添加一个行动号召,包括下一步。在这个例子中,我们首先修改标题:一起,让我们做出改变:支持我们的项目,帮助无家可归者! 我们将在第九章中看到如何向图表添加行动号召的不同策略。图 5.12 显示了结果图表。

图 5.12 无家可归图表通过上下文(图像和副标题)得到丰富。
然后,我们在图表中添加一个新的部分,描述我们将如何使用资金(图 5.13)。例如,我们将用 35%的资金用于庇护和住房,25%用于职业培训,等等。

图 5.13 解释我们将如何使用资金的下一个步骤
以下列表显示了我们在图 5.13 中实现图表的方法。我们假设我们有一个 DataFrame 存储每个类别的资金(分配)百分比。
列表 5.6 添加下一步
import pandas as pd
import altair as alt
donuts = None
for index, row in ns.iterrows():
curr_ns = pd.DataFrame(
{'Category': ['A', 'B'],
'Value': [row['Allocation'], 100-row['Allocation']]
} #1
)
donut = alt.Chart(curr_ns).mark_arc(outerRadius=30, innerRadius=20).encode(
theta=alt.Theta("Value:Q", stack=True),
color=alt.Color("Category:N", scale=alt.Scale(range=['green', 'lightgray']), legend=None) #2
)
title = alt.Chart(curr_ns).mark_text(text=row['Category'], y=0, size=16) #3
text = alt.Chart(curr_ns).mark_text(text=f"{row['Allocation']}%", color=iColor, size=16) #4
donut = donut.properties(
height=100,
width=100
) #5
#6
if index == 0:
#7
donuts = title + donut + text
else:
#8
donuts = alt.hconcat(donuts, title + donut + text)
#9
donuts = donuts.properties(title='Our visionary plan to harness the funds')
1 为当前行创建一个 DataFrame,用作图表的数据
2 使用 Altair 生成饼图
3 为饼图创建标题
4 为饼图创建文本注释
5 设置饼图的大小
6 结合标题、饼图和文本注释
7 如果是第一次迭代,则将组合图表分配给饼图
8 如果不是第一次迭代,则将组合图表水平连接到现有的饼图中
9 为最终的饼图设置标题
备注:使用mark_arc()将每个饼图实现为一个单独的图表。对于每个饼图,构建一个临时的 DataFrame(current_ns),其中包含实际分配值及其互补值(100 - 实际分配值)。然后,使用不同的颜色绘制当前分配值及其互补值。
图 5.14 显示了结果图表。如果 Angelica 将图 5.14 中的图表提交给委员会基金会,她可能会有更多获得资助的机会。现在你已经完成了无家可归的故事,让我们描述如何将生成式 AI 的力量融入 DIKW 金字塔。

图 5.14 无家可归故事的最终图表
5.3 将生成式 AI 融入 DIKW 金字塔
正如我们在第四章中讨论的,生成式 AI 可以帮助我们更进一步,通过使创建复杂和逼真的计算机生成内容成为可能,从而改变各个行业和领域。现在,有了这些知识,我们可以利用生成式 AI 的能力来增强人类创造力并自动化内容生成过程。
图 5.15 显示了我们可以将生成式 AI 工具融入 DIKW 金字塔的每个步骤:
-
洞察提取 — 使用生成算法自动化构建图表的代码生成。
-
添加背景 — 使用文本生成将基本文本转换为更吸引观众的文本。添加由 AI 生成的图像、声音和视频,为观众提供额外的背景信息。
-
行动号召 — 使用文本生成来提出新想法和吸引人的文本。

图 5.15 我们如何将生成式 AI 工具融入 DIKW 金字塔
重要的是要注意,列出的例子只是你可以将生成式 AI 融入 DIKW 金字塔的几种方式之一。还有无数其他方式可以利用这些工具,例如综合大量数据、开发个性化的预测模型,以及根据数据构建个性化的推荐。然而,在这本书中,我们将专注于将生成式 AI 融入 DIKW 金字塔的技术,因为它有助于构建数据故事。无论如何,我鼓励你跳出思维定式,探索将生成式 AI 应用于工作中的新方法和创新方式。随着如此多的潜力等待被解锁,可能性确实是无限的!现在,你已经学会了如何将生成式 AI 工具融入 DIKW 金字塔,让我们将这种策略应用于我们的案例研究:无家可归者的故事。
5.4 背后故事:无家可归者的故事
在前面的章节中,你看到了如何使用 GitHub Copilot 生成构建图表的代码。现在,是时候更进一步了。
再次考虑无家可归者的故事,特别是图 5.12。该图包含一个标题,定义了行动号召;一个副标题,设定背景;以及两张照片,增加了背景信息。虽然你可能认为我们提出了标题并创建或下载了图像,但实际上是 ChatGPT 和 DALL-E 共同合作生成内容!让我们看看我们是如何使用 ChatGPT 生成副标题以及 DALL-E 生成照片的。在接下来的章节中,我们将看到如何使用更深入的生成式 AI 工具进行数据叙事。
5.4.1 创建引人入胜的副标题
我们与 ChatGPT 进行了一次对话,以提取描述无家可归者生活状况的可能情境。这个情境应该是一个简短且引人入胜的句子。图 5.16 展示了对话中涉及的步骤。"Q"代表用户问题(这些将成为我们提示的基础),而"A"代表 ChatGPT 的回答。您可以在 05/genAI/SubtitleConversation.txt 中阅读完整的对话。

图 5.16 生成用于情境的文本的步骤
ChatGPT 在四个步骤后生成了文本。我们使用了以下策略来让 ChatGPT 生成所需的文本:
-
描述 — 要求 ChatGPT 一般性地描述您的问题。在这种情况下,要求 ChatGPT 一般性地描述无家可归的状况。作为回答,ChatGPT 生成了一段长文本。
-
缩短 — 要求 ChatGPT 对生成的文本进行摘要。
-
转换 — 要求 ChatGPT 使摘要对观众更具吸引力。
-
缩短 — 如果文本仍然很长,要求 ChatGPT 进一步缩减。
在没有意识到的情况下,我们将 DIKW 模型应用于 ChatGPT 的使用。从一段长文本(数据)开始,我们提取了信息(摘要),然后将其转化为知识和智慧(引人入胜的文本)。换句话说,当您与 ChatGPT 生成情境时,将对话组织成一个故事(图 5.17)。

图 5.17 数据故事结构与 ChatGPT 对话生成情境之间的映射
您可以将描述的过程推广到生成各种可能的副标题,然后选择适合您需求的那个。此外,您还可以添加特定的关键词,这有助于吸引目标受众并提高文本的可见性。我们将在第六章中看到如何将关键词添加到 ChatGPT 对话中。
5.4.2 生成图像
在您的图表中添加一张或多张图片或照片可以帮助给图表的主题赋予一个面孔。然而,图片常常受到版权保护,这意味着在没有获得所有者许可的情况下使用它们可能会导致法律后果。我们可以使用 DALL-E 和其他 AI 工具来创建独特且不受版权限制的图像。
在关于无家可归者的图表中,我们添加了两张代表无家可归者的照片。我们使用以下简单的提示来生成 DALL-E 的照片:"一张无家可归者的照片"。DALL-E 生成了四张图片,我们选择了其中两张(图 5.18)。在第六章中,我们将看到更多高级的图像生成技术。

图 5.18 当提示 DALL-E 展示无家可归者照片时生成的照片
现在您已经学会了如何使用生成式 AI 工具将您的原始数据可视化图表转换为数据故事,让我们来看另一个例子,以巩固这些概念。
5.5 另一个例子:假新闻
想象一下 LatestNews 是一个重要的网站,它发布来自不同贡献者的新闻。在某个时刻,主编收到了不同读者的投诉,因为他们阅读了大量的虚假新闻。主编联系你分析 LatestNews 网站上的虚假新闻数量,并建议网站编辑注意那些最有可能为虚假的新闻类别。你已经收集了数据,并且你有表 5.2 所示的数据集。
表 5.2 示例数据集的摘录
| 类别 | 虚假文章数量 | 文章总数 |
|---|---|---|
| 政治 | 1,235 | 1,300 |
| 经济 | 1,456 | 1,678 |
| 司法 | 300 | 570 |
| 宗教 | 30 | 100 |
数据集显示了每个新闻类别的虚假新闻故事数量和文章总数。你首先绘制一个初步图表,注意每个类别的虚假文章百分比。图 5.19 显示了初步图表,列表 5.7 显示了相关的代码。你还可以在 GitHub 仓库的 CaseStudies/fake-news/raw-chart.py 下找到该代码。

图 5.19 显示按类别划分的虚假文章数量的原始图表
列表 5.7 构建原始图表
import pandas as pd
import altair as alt
# Load the dataset data/fakenews.csv.
df = pd.read_csv('data/fakenews.csv')
# Create a column chart of the number of articles per category: #1
# - Use the `Category` column for x channel.
# - Use the `Number of Articles` for y channel.
chart = alt.Chart(df).mark_bar(
color='#81c01e'
).encode(
x=alt.X('Category:N',
sort='-y',
title=None,
axis=alt.Axis(labelFontSize=14)
),
y=alt.Y('Percentage of Fake Articles:Q',
axis=alt.Axis(labelFontSize=14, titleFontSize=14)
)
).properties(
width=400,
height=300
).configure_axis( #2
grid=False #3
).configure_view(
strokeWidth=0
).transform_calculate(
'Percentage of Fake Articles', alt.datum['Number of Fake Articles']/alt.datum['Number of Articles']*100
)
chart.save('raw-chart.xhtml')
1 使用 Copilot 注释来加速图表生成过程
2 使用 configure_axis()配置轴的一般属性
3 完全移除网格
注意:使用transform_calculate()动态向 DataFrame 添加新列。此方法接收新列名称作为第一个参数(例如,虚假文章百分比)和计算新列的表达式作为第二个参数。
在本节的剩余部分,我们将应用 DIKW 金字塔将图表转化为数据故事。让我们从第一步开始:将数据转化为信息。
5.5.1 从数据到信息
将数据转化为信息意味着从数据中提取一些洞察,这种洞察是重要的,有助于你的编辑听众理解哪种新闻最有可能为虚假。让我们尝试旋转策略。这种策略最初由 Berengueres 在他的先前引用的书中描述,涉及旋转图表以获得金字塔,并寻找一些金字塔框架,如道德、经济和其他类似框架。图 5.20 显示了旋转过程。

图 5.20 图 5.19 中图表的旋转过程
接下来,我们可以水平翻转图表,得到图 5.21 中的图表,并修正了标签。你还可以直接重写 Altair 代码来绘制柱状图而不是条形图(代码在 GitHub 仓库的 CaseStudies/fake-news/bar-chart.py 下)。我们更喜欢展示旋转过程而不是直接绘制图表,以展示旋转策略是如何工作的。

图 5.21 旋转过程,水平翻转图 5.19 中的图表
如果你仔细查看标签,可能会注意到在金字塔底部有与物质生活相关的类别(从教育到商业)。在金字塔的中间有与道德生活相关的类别(从人权到伦理)。在金字塔的顶部有与精神生活相关的类别(从神秘主义到哲学)。这意味着大多数假新闻都与物质生活(超过 70%)和道德生活(超过 30%的假新闻但不到 60%)相关。你可以在图表中根据新闻所属的不同宏类别使用不同的颜色来突出模型物质-道德-精神生活。
首先,使用 Copilot 生成宏类别列表,如列表 5.8 和 GitHub 仓库中书籍的 CaseStudies/fake-news/story-chart.py 所示。列表仅显示了如何生成物质生活宏类别,但你也可以应用相同的策略来处理其他宏类别。
列表 5.8 使用 Copilot 生成列表
# Build a Python list with the following categories and name it material_life:
# Technology
# Environment
# Health
# Science
# Education
# Business
# Lifestyle
# Travel
# Politics
# Economy
# Sport
material_life = ['Technology', 'Environment', 'Health', 'Science', 'Education', 'Business', 'Lifestyle', 'Travel', 'Politics', 'Economy', 'Sport'] #1
1 Copilot 生成的输出
注意:描述要包含在列表中的元素。
现在,让 Copilot 生成代码,向 DataFrame 添加一个包含宏类别的新的列。以下列表显示了 Copilot 的指令。
列表 5.9 使用 Copilot 向 DataFrame 添加新列
# Add a new column to the dataframe df called Macro Category that contains the following values:
# - If the Category is in material_life, then the value is Material Life.
# - If the Category is in moral_life, then the value is Moral Life.
# - If the Category is in spiritual_life, then the value is Spiritual Life.
df['Macro Category'] = df['Category'].apply(lambda x: 'Material Life' if x in material_life else ('Moral Life' if x in moral_life else 'Spiritual Life')) #1
1 Copilot 生成的输出
注意:描述如何构建 DataFrame 的新类别。
接下来,使用新的“宏类别”列来设置图表中条形的颜色。
列表 5.10 使用新列为条形颜色设置
chart = alt.Chart(df).mark_bar(
).encode(
y=alt.Y('Category:N',
sort='x',
title=None,
axis=alt.Axis(labelFontSize=14)
),
x=alt.X('Percentage of Fake Articles:Q',
axis=alt.Axis(labelFontSize=14,
titleFontSize=14),
),
color=alt.Color('Macro Category:N',
scale=alt.Scale(
range=['#991111', '#f38f8f','lightgray'],
domain=['Material Life', 'Moral Life', 'Spiritual Life']
),
legend=None
)
).properties(
width=400,
height=400
).transform_calculate(
'Percentage of Fake Articles', alt.datum['Number of Fake Articles']/alt.datum['Number of Articles']*100
)
注意:添加颜色通道以设置条形的颜色。
图 5.22 显示了生成的图表。我们使用了两种色调的红来突出关注物质生活和道德生活的紧迫性。

图 5.22 显示了带有宏类别高亮的图 5.21 图表
我们可以通过简化图表来改进它,例如,通过分组类别。然而,我们的受众是专家(编辑),他们需要非常详细的信息,因为他们必须确切知道他们必须深入分析的类别。因此,我们保持图表非常详细。我们已经从图表中移除了图例,因为我们想用一些图像来替换它,这些图像也充当我们故事的环境。让我们看看如何在下一步中添加这些图像:将信息转化为知识。
5.5.2 从信息到知识
想法是为每个宏类别添加一个图像,更具体地说,是一个图标。我们可以使用 DALL-E 生成三个图像。使用以下提示生成精神生活图标:黑白祈祷手势的图标。DALL-E 将生成四个图像,如图 5.23 所示。

图 5.23 与精神生活相关的 DALL-E 生成的图像
类似地,你可以生成与其它类别相关的图标。我们使用了以下提示:
-
一个黑白图标,代表道德生活的平衡符号
-
一个黑白图标,以圆形代表世界
如果你没有想法如何生成图像,可以使用 ChatGPT 来获取一些想法。例如,你可以在 ChatGPT 中写下以下提示:你将如何通过图标来代表物质生活? 在其他建议中,ChatGPT 建议以下:一个代表世界或宇宙的圆形,其中包含各种较小的图标或符号,以代表物质生活的不同方面。
一旦生成了图像,你就可以将其合并到图表中。以下列表描述了如何添加精神生活的图像。你也可以采用相同的策略来处理其他图像。同时,记得在 Web 服务器上加载生成的 HTML 图表。
列表 5.11 向图表添加图标
spiritual_image = alt.Chart(
pd.DataFrame({'image_url': ['media/spiritual-life.png']})
).mark_image(
width=80,
height=80,
).encode(
url='image_url',
x=alt.value(270), # pixels from left
y=alt.value(50)
) #1
1 向图表添加图像。
注意:根据图表显示情况,手动校准 x 和 y 位置。
在图标附近添加描述宏观类别的文本。
列表 5.12 向图表添加文本
spiritual_text = alt.Chart(
pd.DataFrame({'text': ['Spiritual Life']})
).mark_text(
fontSize=30,
color='black',
align='center',
baseline='middle',
font='Monotype',
fontStyle='italic'
).encode(
x=alt.value(420), # pixels from left
y=alt.value(50),
text='text'
)
注意:根据图表显示情况,手动校准 x 和 y 位置。
最后,合并所有图表。
列表 5.13 合并图像、文本和图表
chart = chart + spiritual_image + spiritual_text + moral_image + moral_text + material_image + material_text
chart = chart.configure_axis(
grid=False
).configure_view(
strokeWidth=0
)
chart.save('story-chart.xhtml')
注意:使用+运算符组合图表的所有元素
图 5.24 显示了生成的图表。图标和文本充当图例。此外,它们是我们故事的角色。

图 5.24 向图表添加与精神生活、道德生活和物质生活相关的图像
下一步涉及向我们的图表添加文本背景并设置上下文。我们可以将其作为图表的副标题。让我们使用 ChatGPT 来生成一些想法。写下以下提示:
扮演一个讲故事的人。描述一个视觉图表的上下文,显示在 LatestNews 网站上,虚假新闻数量最多的是与物质生活相关,其次是道德生活,最后是精神生活。请使用最多 150 个字符。
我们指定了三个主要元素:
-
ChatGPT 的角色(我们将在下一章中更详细地介绍)
-
场景
-
可使用的最大字符数
ChatGPT 可能会产生以下输出:在引人入胜的图表中,描绘 LatestNews 网站上虚假新闻的趋势,物质生活占据了最显眼的份额,其次是道德生活,最后是精神生活。我们可以直接使用它,也可以对其进行修改,甚至可以要求一个新的副标题。在我们的情况下,我们将使用这个输出作为图表的上下文。
让我们通过简单地在图表属性中设置副标题来将其添加到我们的图表中。
列表 5.14 向图表添加副标题
chart = chart.properties(width=500,title=alt.TitleParams(
subtitle=['The LatestNews website is a popular source of news and information, but it is also a source of fake news.'],
subtitleFontSize=18,
fontSize=30,
offset=40
))
注意:使用TitleParams()指定标题和副标题参数。
现在我们已经将信息转化为知识,接下来进行最后一步:将知识转化为智慧。
5.5.3 从知识到智慧
智慧在于在故事中添加一个行动号召。在我们的例子中,我们可以简单地告诉观众注意物质和道德新闻,因为它们有很高的虚假新闻比例。让我们使用 ChatGPT 生成一个吸引人的标题。使用以下提示:将以下文本转换为一个吸引人的标题:注意物质和道德新闻,因为它们有很高的虚假新闻比例。生成 5 个标题. 图 5.25 显示了 ChatGPT 可能生成的输出。

图 5.25 ChatGPT 生成的一个可能输出
生成的标题不包含行动号召。为了添加它,让我们通过为 ChatGPT 编写以下新提示来改进标题,在同一聊天中:将标题转换为行动号召,邀请观众关注与物质和道德生活相关的新闻。图 5.26 显示了 ChatGPT 可能生成的输出。

图 5.26 当被要求添加行动号召时,ChatGPT 生成的一个可能输出
例如,选择第三个标题,你的真理守护者:在物质和道德叙事中对抗虚假新闻,并将其添加到图表中,只需设置文本参数,如以下列表所示。图 5.27 显示了最终的图表。
列表 5.15 向图表添加标题
chart = chart.properties(width=500,title=alt.TitleParams(
text=['Your Truth Guardian:', 'Take a Stand Against Fake News in Material and
↪ Moral Narratives'],
subtitle=['The LatestNews website is a popular source of news and information, but it
↪ is also a source of fake news.'],
subtitleFontSize=18,
fontSize=30,
offset=40)
)

图 5.27 最终的图表
注意:使用text参数向图表添加标题。
在本章中,你已经学习了使用 DIKW 金字塔构建数据故事的主要概念以及如何将生成式 AI 工具融入流程中。我们已经对将 DIKW 金字塔作为我们数据故事的一部分以及融入生成式 AI 工具以进一步增强内容进行了高级概述。在接下来的几章中,我们将更详细地探讨穿越 DIKW 金字塔,首先从数据转向信息。
摘要
-
故事是一个吸引其观众以传达信息并促使他们采取行动的叙述。
-
数据故事讲述弧确定了故事中的五个时刻:背景、激发兴趣、主要观点、观众思考以及下一步行动。
-
使用 DIKW 金字塔来实际实施数据故事讲述弧。
-
使用生成式 AI 来协助你在扩展 DIKW 金字塔时,通过提示生成你想要的内容(文本或图像)。
参考文献
-
Dykes, B. (2019). 有效数据故事讲述:如何用数据、叙事和视觉驱动变革。John Wiley & Sons。
-
剑桥词典。 (n.d.). 故事。
dictionary.cambridge.org/dictionary/english/story。
第六章:从数据到信息:提取洞察
本章涵盖
-
介绍洞察
-
选择故事的角色
-
选择合适的图表
一个洞察是在数据中具有显著性的东西——一些你希望通过故事来传达的东西。从数据中提取洞察是每个数据分析师必须应对的最困难的任务。在前几章中,你学习了要将数据转化为信息,你必须从数据中提取洞察。你也学习了洞察是在你的数据中找到的相关内容。在这本书中,我们将不会专注于如何从数据中提取洞察。关于这个主题有大量的书籍,你可以参考它们以获得详细的描述(例如,参见 Guja 等,2024 年;De Mauro 等,2021 年)。在这一章中,我们将采用更基于直觉的方法来提取洞察,描述你在探索数据时的指导动机。我希望这种方法能帮助你拓宽数据分析旅程中的视野。因此,本章的目标是理解在 DIKW 金字塔的底层,是洞察的提取,将其转化为故事的角色,并通过图表来表示。首先,我们将关注如何使用直觉方法从数据中提取洞察。接下来,我们将描述如何选择数据故事的角色以及如何选择合适的图表。故事角色和合适的图表有助于将你的洞察传达给听众。最后,你将学习如何在 Altair 中实现一些最受欢迎的数据可视化图表。我们将利用 Copilot 的力量来加速图表构建过程,并提供实现整洁图表的代码,以便将其添加到数据故事中。
6.1 提取洞察的直觉方法
在儒勒·凡尔纳的《地球中心之旅》一书中,奥托·利登布鲁克教授(1864 年)通过运用他敏锐的智力和对探索的热情,仔细地从一份中世纪冰岛手稿中提取信息。凭借对语言、历史和地质的深刻理解,他解读了神秘的线索,交叉引用古代文本,并应用科学知识来揭示页面中隐藏的秘密。手稿提供了关于旅程路线和他们在途中可能遇到的地质标志的细节。换句话说,奥托·利登布鲁克教授将古老手稿中的数据转化为信息。
与儒勒·凡尔纳讲述的故事相似,你可以从将数据转化为信息开始,来讲述任何数据故事。在本节中,我们将描述一些可以帮助你将数据转化为信息的策略。
在他的书《看到别人看不到的东西》(Klein,2017 年)中,Gary Klein 确定了四种主要策略来帮助你识别洞察:
-
联系
-
偶然事件
-
好奇心
-
矛盾
让我们分别分析这四种提出的策略,首先是:连接。请记住,你可以同时应用多种策略。
6.1.1 连接策略
此策略涉及识别数据的主要点,然后将它们连接起来以识别一个故事。在书中,克莱因说,有时当你看到以新的方式组合不同数据的方式时,你会提取到一个洞见。有时,你只需以不同的方式查看你已有的数据,就可以提取到一个洞见。为了解释连接策略是如何工作的,考虑以下场景。安吉丽卡是一位为电子商务网站工作的数据分析师。有一天,安吉丽卡收到了一份关于网站上每个产品的客户评论和评分的报告。每个产品都是按 1 到 5 的比例评分的,并且客户可以留下文本评论。表 6.1 显示了安吉丽卡必须分析的销售数据集。
表 6.1 安吉丽卡分析的销售数据集
product_id |
number_of_orders |
product_rating |
product_category |
returns |
number_of_reviews |
|---|---|---|---|---|---|
| P1001 | 30 | 4.5 | 电子产品 | 0 | 3 |
| P1002 | 12 | 3.2 | 家居与厨房 | 6 | 6 |
| … | … | … | … | … | |
| P1006 | 24 | 4.2 | 电子产品 | 0 | 4 |
| P1006 | 22 | 2.1 | 电子产品 | 18 | 20 |
这里是对数据集列的解释:
-
product_id—数据集中每个产品的唯一标识符。 -
number_of_orders—特定产品被订购的次数。 -
product_rating—客户或用户为每个产品分配的评分。评分可以是按比例(例如,1 到 5 星)的,并反映产品的满意度或感知质量。 -
product_category—此列将产品分类到不同的组或类型。 -
returns—客户退货产品的次数。这提供了对客户不满率或导致退货的产品问题的洞察。 -
number_of_reviews—每个产品收到的评论数量。
安吉丽卡首先绘制了产品评分与评论数量以及产品评分与退货百分比的关系图(图 6.1)。你可以在这个书的 GitHub 仓库下的 06/connections 找到这个例子。

图 6.1 安吉丽卡绘制的图表
安吉丽卡发现了以下主要点:
-
一些产品评分高但评论相对较少。
-
其他产品评分较低但评论数量较多。
安吉丽卡通过她的直觉将点连接起来,发现负面评论与高退货率相关。这可能会损害整体客户体验和品牌声誉。
之前的例子表明,一旦我们确定了“点”,提取洞察力只是将它们连接起来。然而,这个策略的主要问题在于识别“非点”,或者说无关的信息。在之前的场景中,一个非点的例子是产品类别与产品评分。
6.1.2 巧合策略
巧合是指由于随机性同时发生的事件,尽管它们似乎有一些联系——两个或多个表面上看似重要的情况的意外汇聚,即使它们可能是不相关的。巧合的范围可以从日常事件,比如在意想不到的地方遇到某人,到看似极不可能的非凡事件。然而,虽然巧合很吸引人,但它们并不一定表明超出了偶然。它们可能是由于它们奇特的对齐而吸引我们注意的罕见或时间安排不寻常的事件。
在他的书《看到别人看不到的东西》中,Gary Klein 说:“观察到一个巧合意味着我们发现了某些事件,它们似乎彼此相关,尽管它们之间似乎没有明显的因果关系。”当你遇到一个孤立的事件,且数据点很少,无法证明两个变量之间关系时,你应该采取怀疑的态度,将其视为巧合(因此忽略它)。另一方面,当事件或趋势在多次场合重复出现时,它们不太可能是巧合,因此值得进一步调查。
要确定一个重复事件是巧合还是不是,你可以用一种故意怀疑的心态来处理数据,忽略孤立的事件,同时寻找长期模式,这些模式可能会表明存在某种关系。相关因素的存在可能表明存在潜在的因果关系。例如,通过分析时间序列数据,你可能会发现某些变量先于重复事件出现,这暗示着因果关系。通过因果关系,你可以区分纯粹的巧合和有意义的联系,解释支配观察现象的机制。
让我们考虑以下例子来解释如何应用巧合策略。想象一下,Angelica 在一家销售电子设备配件的商店工作,例如耳机、USB 线、智能手机壳等等。同时,想象一下 Angelica 想要监控她的订单。她得到了表 6.2 中所示的数据库。
表 6.2 电子商店的销售数据集
| 日期 | 耳机 | USB 线 | 智能手机壳 |
|---|---|---|---|
| 2023-01-01 | 14 | 34 | 32 |
| 2023-01-02 | 17 | 54 | 45 |
| … | … | … | … |
| 2023-01-30 | 16 | 34 | 34 |
| 2023-01-31 | 20 | 23 | 5 |
为了简化,数据集包含了 2023 年 1 月份三种产品的订单数量:耳机、USB 线和智能手机壳。图 6.2 展示了数据集的视觉表示。您可以在书的 GitHub 仓库下的 06/coincidences 找到完整的代码(https://mng.bz/lMmy)。

图 6.2 电子商店订单,重点关注耳机
该图突出了耳机订单。值得注意的是,耳机订单每周五达到峰值。这可能是一个巧合,但事件的重复性应该被考虑。例如,安吉丽卡可能会发现每周五都有一个令人兴奋的广播节目,导致人们争相购买新的耳机。或者她可能会发现她所在的城市周五有一条法律,不允许打开扬声器。无论如何,这种事件的重复性需要进一步分析。
观察图 6.2 的 x 轴,您会发现一个奇怪的标签策略:首先,年份;然后是工作日;最后是月份。这是 Altair 的默认日期标签。您可以在 Altair 官方文档中了解更多关于如何格式化日期的详细信息(https://mng.bz/lMmy)。
碰巧洞察与联系洞察在发现方式上有所不同。在联系洞察中,我们关注重要细节,而在碰巧洞察中,我们关注重复事件。此外,当您有聚合度量时,联系是有帮助的,而巧合最好应用于像随时间变化的原始销售数字这样的东西。
6.1.3 好奇心
在 1928 年,苏格兰细菌学家亚历山大·弗莱明爵士在研究葡萄球菌细菌时发现了青霉素。有一天,当他仔细检查装满这些微生物的平板时,他注意到一些奇怪的现象。一个不小心被留下的平板被霉菌污染了。弗莱明对这种意想不到的情况感到好奇,他仔细检查了平板。令他惊讶的是,他观察到霉菌周围的细菌正在逐渐恶化。霉菌释放出的某种物质正在击败它们。出于好奇心,弗莱明开始了一系列实验来探索这一现象。他小心地隔离了霉菌并提取了它产生的物质,他将其命名为青霉素。好奇心促使弗莱明进一步调查这个问题,最终导致了青霉素的发现,这是世界上第一种抗生素,拯救了无数生命,并永远改变了医学。
好奇心是一种推动力,它能激发惊奇,推动发现,并激发创新,推动我们质疑、寻求答案,并扩展知识的边界。好奇心是我们可以应用来提取洞察力的一种策略。对数据的兴趣可能由各种现象触发,包括异常值、缺失数据、时间或空间上的数据缺口、突然的变动或趋势、意外的模式等等。当我们注意到数据中的奇怪之处时,我们会深入挖掘,提出问题,并探索导致观察到的模式的潜在因素。这种好奇心驱使我们从不同的角度分析数据,揭示隐藏的洞察力,并最终对现象有更深入的理解。
让我们考虑以下例子来解释我们如何应用好奇心策略。再次考虑前一小节中电子设备的例子。现在,想象一下你有 3 月份的不同趋势线,如图 6.3 所示。

图 6.3 3 月份的电子产品订单,重点关注耳机
该图显示了 3 月 25 日的销售高峰。这一事件可能会引起一些好奇心,促使我们进行进一步调查。好奇心可以帮助我们在头脑风暴过程中。例如,它可能引导我们寻找可能促成这一波峰的各种因素,如营销努力、特别促销或外部事件。随着我们继续研究,好奇心可能会促使我们添加其他因素,如这一时期的客户人口统计。这项调查不仅会满足我们的好奇心,还会帮助我们提取洞察力。
6.1.4 矛盾
矛盾是指两种或更多信念或证据冲突的情况,从而产生不一致性。根据加里·克莱因的说法,要从矛盾中提取洞察力,你必须以怀疑的心态处理数据。与基于巧合的方法不同,我们寻找重复的事件,而在基于矛盾的战略中,我们寻找看似冲突的事件。从矛盾中提取的洞察力可以颠覆传统思维,挑战既定观念,并提供促进创造性问题解决的替代视角。通过接受矛盾中的内在张力,你可以获得宝贵的洞察力,从而实现突破、创新和增长。
考虑图 6.4,展示了随着时间的推移产品销售和价格。您可以在本书的 GitHub 仓库下的 06/contradictions 找到生成的图表代码。

图 6.4 展示产品销售和价格的图表
图表显示,价格上涨与销售额增加相对应。这一现象相当矛盾,因为普遍的假设是价格上涨应该与销售额下降相对应。然而,为了理解这一现象,你可以戴上怀疑的透镜,更深入地分析这一矛盾现象。你可能发现的一个可能动机是感知价值。当价格上涨时,顾客可能会认为你的产品更独特、高质量或更受欢迎。这种增加价值的感知可以使顾客愿意支付更多,从而导致销售额增加。另一种解释可能是消费者的紧迫感。消费者可能担心错过产品,认为更高的价格意味着稀缺或供应有限。另一个原因可能是通货膨胀。在通货膨胀的背景下,当价格上涨时,人们可能会因为担心价格会进一步上涨而购买更多,从而使产品变得难以获得。
所有的策略、联系、巧合、好奇心和矛盾都定义了一些可能的技巧来提取意义。无论你采取哪种方法,都要始终依靠你的经验和深入的探索性分析阶段来从数据中提取意义。现在你已经学会了如何从数据中提取洞察力,让我们继续探讨如何选择故事的角色。
6.2 选择故事的角色
在电影《印第安纳·琼斯与圣战奇兵》中,印第安纳·琼斯,一位杰出的考古学家,踏上了一场惊心动魄的冒险之旅,追寻着一件古老的圣物——约柜。故事背景设定在 20 世纪 30 年代,印第安纳与时间赛跑,试图在纳粹分子将其据为己用之前找到这个神圣的遗物。这部电影以及印第安纳·琼斯系列的其他电影都是引人入胜的故事的例子,其中英雄渴望得到某物。
每个故事都是从英雄渴望得到某物开始的。没有英雄和要实现的目标,就没有故事。在数据故事中也应该是这样。虽然“英雄”这个词可能过于夸张,用来定义我们故事的主题,然而,在我们的情况下,我们确实想强调数据故事与真实故事之间的相似性。在任何故事中,总有一个英雄与主要角色相对应。我们的想法是将要被代表的主题看作是我们故事中的英雄,即使它只是一个简单的产品。采用这种观点,构建故事也会更容易:
-
每个数据故事都应该有一个 英雄。这可能是一个产品、一个客户,或者任何其他东西。
-
英雄必须 渴望得到某物。这可能是一个销售增长、满意度提高,或者其他任何东西。
请记住,故事中的英雄不是你自己。英雄是你的数据故事的主要主题。它取决于你提取的洞察力。英雄的例子包括人、物和地方。
一旦你确定了故事中的英雄,下一步就是向观众提出以下问题:英雄会得到他们想要的吗? 在他的书中,《构建故事品牌:明确你的信息,让客户愿意倾听》(2017),唐纳德·米利根说:“在知道英雄想要什么之前,观众对她命运的兴趣很小。这就是为什么编剧必须在电影开始的前九分钟左右定义角色的抱负。”
除了有一个英雄之外,你的故事还应该至少有两个其他角色:向导和对手。向导通常是一位聪明而有经验的导师,帮助英雄实现他们的目标。对手可以是
-
一个试图阻碍或反对英雄进步的竞争者
-
一个比较角色,它允许你衡量英雄的进步
-
在不同的时间(例如,将英雄现在的状况与一年前进行比较)或不同的空间(例如,将这里的英雄与其他地点进行比较)的同一英雄
让我们回顾前几章中看到的所有例子,并确定每个故事中的英雄。为了方便起见,图 6.5 总结了分析的例子。尝试搜索每个例子中的英雄、英雄类型、英雄想要什么和对手。

图 6.5 总结了前几章中描述的所有例子
表 6.3 显示了图 6.5 中每个案例研究的英雄和对手。对于每个案例研究,表格还显示了英雄的类型和英雄想要什么。在第二章的例子——“是的,你可以建造一个新的游泳池!”中,存在这样的隐含假设:游客到访量的增加与建造新游泳池的可能性相对应。这显然是一个简化的假设,但我们简化了这个例子只是为了开始,并展示 DIKW 金字塔是如何工作的。
表 6.3 前几章分析的案例研究的英雄和对手
| 案例研究 | 英雄 | 英雄类型 | 英雄想要什么 | 对手 |
|---|---|---|---|---|
| 第一章:增加与狗相关的网站的广告活动 | 与狗相关的网站 | 事物 | 增加观看次数 | 与猫相关的网站 |
| 第二章:是的,你可以建造一个新的游泳池! | 葡萄牙游客到访流量 | 流程 | 游客到访量的增加 | 德国游客到访流量 |
| 第三章:过去 50 年北美的人口 | 北美人口 | 人们 | 解释趋势 | 世界其他地区的人口 |
| 第五章,示例 1:一起,让我们做出改变:支持我们的项目以帮助无家可归者 | 拉齐奥、皮埃蒙特、利古里亚和撒丁岛的无家可归者人数 | 人们 | 减少这个数字 | 其他意大利地区的无家可归者人数 |
| 第五章,示例 2:你的真理守护者:在物质和道德叙事中对抗假新闻 | 物质和道德生活的新闻 | 事物 | 确定它们是真实的还是假的 | 精神生活的新闻 |
在表 6.3 中展示的所有示例中,对手总是与英雄不同的角色,尽管它们与英雄属于同一类型。在某些情况下,对手可以是之前条件下的同一英雄,例如过去的产品销售或另一个地理区域内的销售。现在你已经学会了如何选择故事的角色,让我们继续选择适合你的数据故事的正确图表。
6.3 选择正确的图表
最佳图表取决于你想展示的洞察力、你想传达的信息以及你针对的受众。图 6.6 展示了洞察力、信息和受众在图表中的相互关系。如果你只考虑洞察力,你的图表效果就是提供信息。

图 6.6 洞察力、受众和信息之间的关系
在讲述故事时,有三个层次:
-
提供信息—在这个层面,你描述你在数据中发现的洞察力。与受众没有接触。
-
传达—你通过提供关于你的数据的详细信息来涉及受众,但他们在听完你的故事后并没有被激励去做任何事情。
-
激励—基于你的数据,你提出一个信息,激励受众去做某事。受众高度参与,并将在其他情境中重述你的故事。
换句话说,如果你只考虑洞察力,你的图表效果就是提供信息。如果你将你的洞察力校准到你的受众,你可以有效地传达它。最后,如果你在你的图表中添加信息,你可以激励你的受众。我们将在本章的剩余部分看到如何展示洞察力,如何在第七章中将图表校准到目标受众,以及如何在第九章中向图表添加信息。
让我们关注如何在图表中展示洞察力。图表的选择由你的具体目标驱动。不同类型的图表根据数据的性质和你要传达的信息有不同的用途。表 6.4 描述了根据你要传达的信息选择哪种图表。请注意,所描述的图表列表并不全面,你可以发挥你的想象力和专业知识来构建新的图表。此外,你可以遵循阿贝拉博士的图表选择器(2020 年;mng.bz/DdnE)来选择最适合你的图表。
表 6.4 根据要传达的信息建议的图表
| 要传达的信息 | 描述 | 建议的图表 |
|---|
| 一条信息 | 代表关键信息的单个数字 | • 大数字(BAN)• 饼图
• 100% 堆积柱状图
• 雪花图
|
| 整体的部分 | 贡献于系统、物体或概念整体性的组成部分。所有组成部分的总和必须是 100%。 | • 饼图 • 100%堆积车图
• 多维饼图
• 饼图
|
| 实体间的比较 | 多个实体之间的相似性和差异性,以建立关系和区分 | • 条形图 • 柱状图
• 斜率图
• 拉力图
• 表格
|
| 趋势 | 实体随时间的变化行为 | • 折线图 • 小多折线图
• 堆积面积图
• 堆积柱状图
|
| 调查或问卷的结果 | 调查或问卷中包含的问题的答案 | • 堆积条形图 • 柱状图
• 多维条形图
|
| 分布 | 数据集中值的分布,表示不同值出现的频率 | • 直方图 • 金字塔
• 箱线图
|
| 关系 | 不同实体之间的关联、连接或相关性,用于识别模式、趋势和依赖关系 | • 散点图 • 气泡图
• 热力图
|
| 空间信息 | 实体在空间中的行为 | • 彩色地图 • 点密度图
• 比例符号地图
• 热力图
|
| 流程 | 表示一个过程 | • 沙漏图(不支持 Altair 5.0.1)• 弦图(不支持 Altair 5.0.1)
|
如您从表中可以看到,您可以使用相同的图表用于不同的目的。此外,您可以使用许多不同的图表达到相同的目的。您选择的图表取决于受众,正如我们在第七章中将要看到的。在本节的剩余部分,我们将描述如何在 Altair 和 Copilot 中实现一些最重要的图表,按图表家族分组。
我们将关注以下图表家族:
-
烹饪图表
-
条形图
-
折线图
-
地理地图
-
点图
让我们从第一个家族开始:烹饪图表。您可以在本书的 GitHub 仓库下的第六部分中找到每个图表相关的代码。对于每个图表家族,我们将只展示一个代表性图表。您可以在附录 C 中找到更多图表。
6.3.1 烹饪图表家族
“烹饪图表”系列包括饼图和环形图。仅使用此类图表来表示数值。最近,我在社交媒体和网络上看到了对这些图表类型的不合理反对。主要论点是这些图表过于通用,不能正确传达结果。我曾经也是这种情绪的受害者,一度也避免使用这种图表。然后,在一个美好的日子里,在 LinkedIn 上,我读到了布伦特·戴克斯关于重新评估烹饪图表,特别是饼图的帖子(mng.bz/NRpD)。在他的帖子中,戴克斯指出,饼图在表示切片的精确尺寸方面并不出色,但它们确实表示了整体的部分。戴克斯明确表示:“我知道每个人都喜欢规则。‘永远不要使用这种图表类型 ...’,‘总是使用这种图表类型来...’,等等。然而,在数据故事讲述中并不总是这么简单。这不仅仅是如何显示某种类型的数据,而是如何最好地向您的受众传达一个特定的观点。”
按照戴克斯的建议,您可以在三种主要情况下使用烹饪图表:
-
您必须表示整体的一部分。所有切片的总和必须是 100%。
-
最多有两到三个块来表示(如果它们的大小不是非常相似的话;否则,很难确定哪个比其他的大)。
-
您不希望传达数据的细节,而只是提供一个概述。这可能适用于一般受众,不包括技术专家。
在本节的剩余部分,我们将了解如何实现饼图。对于环形图,请参阅附录 C。饼图是一种圆形数据可视化类型,通过将圆分割成若干块来显示数据,每一块代表整体的一个比例或百分比。
假设我们想使用饼图来表示 70%的值。首先,创建一个包含您的数字及其补充值(30%)的 pandas DataFrame。
列表 6.1 包含数字的 DataFrame
import pandas as pd
import altair as alt
data = {
'percentage': [0.7,0.3],
'label' : ['70%','30%'], #1
'color' : ['#81c01e','lightgray'] #2
}
df = pd.DataFrame(data)
1 要显示的标签
2 使用中性颜色(浅灰色)表示补充数字。
注意:创建一个包含要表示的数字及其补充值的 pandas DataFrame。
接下来,请 GitHub Copilot 为您绘制图表。饼图没有经典的x和y通道来编码数据。相反,饼图使用Theta和Color通道。Theta通道指定极坐标图中数据点的角度位置。Color通道指的是使用不同颜色来编码数据。
列表 6.2 显示了 GitHub Copilot 的起始提示。
列表 6.2 如何在 Copilot 中生成基本饼图
# Draw a pie chart in Altair with the following options:
# - Use the `percentage` column for theta channel
# - Use the `label` column for tooltip
# - Use the `color` column for color
# Save chart to `chart` variable.
# Save chart as 'pie-chart.xhtml'.
注意:Copilot 绘制饼图的基本指令。
因此,Copilot 将生成以下列表中显示的代码。图 6.7 显示了生成的图表。
列表 6.3 生成基本饼图的代码
chart = alt.Chart(df).mark_arc(
).encode(
theta=alt.Theta('percentage', stack=True), #1
color=alt.Color('color', scale=None),
tooltip='label'
).properties(
width=300,
height=300
)
chart.save('pie-chart.xhtml')
1 stack=True 不是由 Copilot 直接生成的。

图 6.7 Altair 中的基本饼图
注意:列表 6.3 是生成 Altair 基本饼图的代码。使用mark_arc()方法绘制饼图。我们手动添加stack=True属性以进行下一步。这个属性意味着图表内的各个切片将堆叠在一起,而不是并排显示。
我们可以通过在每个切片旁边添加标签来改进图表。要求 Copilot 绘制包含标签的文本,如以下列表所示。
列表 6.4 在 Copilot 中如何给图表添加标签
# Add text near to each slice of the pie chart.
# - Use the `label` column for text channel.
# - Use the `color` column for color.
# Combine the pie chart and the text chart.
# - Use `+` operator to combine the charts.
# - Save the combined chart to `chart` variable.
注意:这是 Copilot 生成饼图旁边标签的提示。
因此,Copilot 将生成一些部分代码,这些代码没有显示期望的输出。按照以下列表中的描述进行修改。图 6.8 显示了最终的图表。
列表 6.5 生成标签的代码
text = chart.mark_text( #1
size = 20, #2
radius=180 #3
).encode(
text='label',
color=alt.Color('color', scale=None)
).properties(
width=300,
height=300
)
chart = (chart + text
).configure_view(
strokeWidth=0 #4
)
1 Copilot 使用 alt.Chart(df)来绘制图表。请使用 chart 代替。
2 添加 size 属性来设置字体大小。
3 添加 radius 来设置切片的距离。
4 使用 strokeWidth=0 从图表中移除边框。

图 6.8 Altair 中的带标签的饼图
注意:列表 6.5 是生成 Altair 基本饼图标签的代码。使用mark_text()方法写入文本。
现在你已经学会了如何实现饼图,让我们继续学习下一个图表系列。在接下来的部分,我们将讨论柱状图。
6.3.2 柱状图系列
柱状图系列包括各种类型的图表,其中每个数据系列都通过矩形条进行可视化。这些条可以是垂直的,称为柱状图,或者水平的,称为条形图。除了经典的柱状图和条形图之外,这个系列还包括堆叠柱状图/条形图、金字塔图和直方图。使用这些图表来表示分类数据或通过在水平或垂直轴上显示相应的值来比较不同的类别。
在某种程度上,与对饼图的反对类似,最近在网页和社交媒体上,我注意到条形图被过度滥用。虽然这些图表在传达信息方面具有简单和有效的优点,但反复使用相同的图表给同一受众可能会令受众感到厌烦,这可能是针对这些图表的困惑的原因。
你已经在之前的章节中学习了如何绘制柱状图和条形图。在本节中,你将学习如何通过 Copilot 提高柱状图的易读性。对于属于这个系列的其它图表,请参考附录 C。
柱状图是一种使用矩形条来表示数据值的图表,其中每根条的长度对应于它所代表的数量。使用此图表来表示不同类别中的数据。考虑表 6.5 中显示的数据集,描述了每种餐食类型的点赞数。
表 6.5 示例数据集
| 餐食类型 | 点赞数 |
|---|---|
| 披萨 | 120 |
| 汉堡 | 95 |
| 意面 | 80 |
| 寿司 | 60 |
| 沙拉 | 50 |
| 牛排 | 70 |
| 墨西哥卷饼 | 90 |
| 冰淇淋 | 110 |
| 咖喱 | 40 |
| 三明治 | 75 |
以下列表显示了在 Altair 中生成表示先前数据集的简单柱状图的代码,图 6.9 显示了生成的图表。
列表 6.6 创建柱状图的代码
import pandas as pd
import altair as alt
df = pd.read_csv('data/meals.csv')
chart = alt.Chart(df).mark_bar(
color='#81c01e'
).encode(
y=alt.Y('Meal Type', sort='-x'),
x='Number of Likes'
).properties(
width=300,
height=300
)
chart.save('bar-chart.xhtml')
注意:使用color属性设置柱子颜色,并设置y通道的sort属性,根据x的降序值对类别进行排序。

图 6.9 如果您开始编写,Copilot 将建议下一步。
图表需要简化,因此我们应该删除不必要的元素,例如 y 轴标题(餐食类型)、网格等。例如,我们可以在每根柱子的末尾添加其值,并完全删除 x 轴标签。使用 Copilot 来帮助您构建图表。当您使用 Copilot 时,您会发现它会根据您之前编写的代码提出更多适合您编码风格的建议。如果您开始编写某些内容,Copilot 将根据您之前编写的代码建议下一步。这意味着您不仅可以使用预定义的提示,还可以在提示中建议下一步。
要在每根柱子的末尾添加其值,开始为 Copilot 编写一个提示,按 Enter 键,Copilot 将建议如何实现,如图 6.10 所示。

图 6.10 Copilot 建议的一个示例
简单地按 Enter 键,Copilot 将继续提出如何添加文本。以下列表显示了 Copilot 提出的完整提示。
列表 6.7 Copilot 生成的提示
# Add text to the end of each bar.
# - Use the `Number of Likes` column for text channel.
# - Use the `Meal Type` column for y channel and sort by the number of likes.
# - Set the color of the text to '#81c01'.
# - Set the text alignment to 'left'.
# - Set the text baseline to 'middle'.
# - Set font size to 14.
注意:使用 Copilot 生成构建您图表的提示,然后按 Enter 键让 Copilot 生成代码。
在最终提示后,按 Enter 键,Copilot 将生成代码。
列表 6.8 生成标签的代码
text = chart.mark_text(
align='left',
baseline='middle',
dx=3,
color='#81c01',
fontSize=14
).encode(
text='Number of Likes',
y=alt.Y('Meal Type', sort='-x', title=None)
)
注意:将mark_text()方法应用于图表以生成标签。您可以使用为chart变量设置的列。
现在,向图表中添加五条垂直线以简化观众的比较过程。请 Copilot 为您生成代码。开始编写以下提示:# Add five vertical lines to the chart。Copilot 将生成以下列表中显示的提示。
列表 6.9 生成垂直线的提示
# Add five vertical lines to the chart.
# - Use alt.Chart(pd.DataFrame({'x': [20, 40, 60, 80, 100]})) to create a dataframe with six rows.
# - Use alt.Chart().mark_rule() to draw vertical lines.
# - Set the color of the lines to 'white'.
# - Set the line width to 1.
# - Set opacity to 0.5.
注意:Copilot 生成生成代码的提示。
按 Enter 键,Copilot 将生成以下列表中显示的代码。
列表 6.10 生成垂直线的代码
lines = alt.Chart(pd.DataFrame({'x': [20, 40, 60, 80, 100]})).mark_rule(
color='white',
strokeWidth=1,
opacity=0.5
).encode(
x='x:Q'
)
注意:使用mark_rule()标记向图表添加垂直线。
最后,从列表 6.6 中的原始图表中删除 x 轴(x=alt.X('Number of Likes',axis=None)),从 y 轴中删除标题(y=alt.Y('Meal Type', sort='-x', title=None)),并将三个图表合并,如下所示列表。图 6.11 显示了生成的图表。
列表 6.11 合并图表以生成最终图表
chart = (chart + text + lines
).configure_view(
strokeWidth=0
).configure_axis(
grid=False
)
chart.save('bar-chart.xhtml')

图 6.11 一个去杂的柱状图
注意:使用 + 操作符。
现在你已经知道如何在 Altair Copilot 中绘制一个去杂的柱状图。接下来,让我们继续到折线图系列。
6.3.3 折线图系列
折线图系列包括所有使用连续线来展示给定时间段内不同数据点之间关系或趋势的图表。常见的折线图类型包括基本折线图、面积图、斜率图和哑铃图。在本节中,我们将分析折线图。面积图、斜率图和哑铃图将在附录 C 中讨论。
折线图,也称为 线图 或 折线图,用于可视化时间序列数据。它们通过直线连接数据点,这使得很容易看到数据随时间的变化。折线图特别适用于显示数据中的趋势和模式,以及比较多个数据序列。
假设你有一个数据集,显示了每年每个月披萨和意大利面的订单数量。该数据集包含三列:月份、餐点类型和订单数量。在她的书《数据故事讲述》(Knaflic,2015)中,Cole Nussbaumer Knaflic 提议用每个线条附近的标签替换折线图的图例。为了遵循 Knaflic 的建议,我们可以在 Altair 中分三步构建一个折线图。首先,我们构建一个具有基本结构的基础图表。它包括一般的编码和属性,如下所示。
列表 6.12 如何构建折线图的基础图表
base = alt.Chart(df).encode(
x=alt.X('Month',
axis=alt.Axis(title=None,
labelAngle=0,
),
sort=months
),
y=alt.Y('Number of Orders'),
color=alt.Color('Meal Type',scale=alt.Scale(range=['#81c01e','gray']),legend=None)
).properties(
width=600,
height=300
)
注意:在构建基础图表时,不要指定标记。
接下来,我们通过将 mark_line() 方法应用于基础图表来绘制折线图。最后,我们通过将 mark_text() 方法应用于基础图表来绘制标签。
列表 6.13 如何构建折线图和标签
chart = base.mark_line()
text = base.mark_text(
fontSize=14,
baseline='middle',
align='left',
dx=10
).encode(
text=alt.Text('Meal Type:N'),
).transform_filter(
alt.datum['Month'] == 'December'
)
chart = chart + text #1
chart = chart.configure_view(
strokeWidth=0
).configure_axis(
grid=False
)
chart.save('line-chart.xhtml')
1 将折线图和文本标记合并为单个图表
注意:在定义基础图表后,通过仅指定 mark_line() 方法来构建折线图。使用 transform_filter() 方法来选择仅部分数据。在我们的例子中,选择数据集的最后值(alt.datum['Month'] == 'December')来将标签放置在线的末端。
图 6.12 显示了生成的图表。现在,是时候转向下一个图表系列:地理地图。

图 6.12 一个折线图
6.3.4 地理地图系列
地理地图显示了地球表面上不同数据点的位置,如国家、大陆、海洋和河流。地理地图还显示了感兴趣区域的资源和数据分布。
最受欢迎的地理地图包括以下几种:
-
渐变地图——这些使用颜色来显示不同区域之间值的差异(图 6.13)。
-
点密度地图——这些使用点来显示一个区域内某个值的浓度(图 6.14)。
-
比例符号地图——这些地图使用根据它们所代表的值来定制的符号(图 6.15)。
你可以在本书的 GitHub 仓库中找到每种类型地理地图的相关代码。

图 6.13 一个面状图

图 6.14 一个点密度图

图 6.15 一个比例符号地图
当你构建地理地图时,你必须设置要使用的投影。最受欢迎的投影地图之一是墨卡托投影地图。尽管这个地图非常流行,但它并不能正确地表示世界。例如,在地图上,格陵兰看起来几乎和非洲一样大,而实际上,非洲大约是 14 倍大!这种扭曲发生是因为在平面上很难准确地表示地球的曲面。现在你已经学习了最流行的地理地图,让我们继续学习点图系列。
6.3.5 点图系列
点图系列包括使用x和y轴上的点来表示数据点的图表,以显示两个变量之间的关系。最流行的点图系列包括散点图和气泡图。使用mark_point()标记来绘制点图。
散点图可视化两个数值变量之间的关系。使用它们来识别数据中的模式和趋势,并基于这些趋势进行预测。图 6.16 展示了散点图的一个示例。你可以在本书的 GitHub 仓库中找到相关的代码。

图 6.16 一个散点图
气泡图是散点图,它使用数据点的尺寸来编码一个额外的变量。在 Altair 中,你可以使用size通道来设置气泡大小:size=alt.Size('Ranking:Q',scale=alt.Scale(range=[1,200]),legend=None)。图 6.17 展示了气泡图的一个示例。现在你已经学习了基本的图表系列,让我们继续进行一个实际案例研究。

图 6.17 一个气泡图
6.4 案例研究:鲑鱼养殖业
想象一下,你想要研究美国鲑鱼养殖业的安全问题。你有美国农业部经济研究服务提供的养殖数据集(data.world/agriculture/aquaculture-data),该数据集描述了每个鱼类类别随时间出口的千克数和赚取的美元数,如表 6.6 所示。
表 6.6 水产养殖数据集快照
| SOURCE_ID | HS_CODE | COMMODITY_DESC | GEOGRAPHY_CODE | GEOGRAPHY_DESC | ATTRIBUTE_DESC | UNIT_DESC | YEAR_ID | TIMEPERIOD_ID | AMOUNT |
|---|---|---|---|---|---|---|---|---|---|
| 63 | 302110000 | 鲑鱼(鲑鱼、S. CLARKI 等)鲜、冷藏 | 1 | 世界 | 美国出口,数量 | 千克 | 1989 | 2 | 17,183 |
| 63 | 1604112000 | 鲑鱼,整条/块,油封罐装 | 2740 | 特立尼达和多巴哥 | 美国出口,VLU | 美元 | 1989 | 2 | 4,329 |
数据集包含以下列:
-
SOURCE_ID—数据来源 -
HS_CODE—商品的协调制度代码 -
COMMODITY_DESC—商品的描述 -
GEOGRAPHY_CODE—来源或目的地的国家或地区代码 -
GEOGRAPHY_DESC—来源或目的地的国家或地区名称 -
ATTRIBUTE_DESC—数据类型,无论是 US Export,QTY 还是 US Export,VLU -
UNIT_DESC—计量单位,无论是 KG 还是美元 -
YEAR_ID—数据的年份 -
TIMEPERIOD_ID—数据的月份 -
AMOUNT—数据的价值
你的研究目标是确定迄今为止在鲑鱼养殖业中采用的预防措施是否足够,或者是否应该改进。为了简化,你基于这个数据集进行研究。然而,在实际情况中,你也应该考虑其他方面,例如维持这些措施的成本和其他类似的分析。
你决定绘制鲑鱼养殖业销售趋势线与其他类型养殖业的对比图。你可以在本书的 GitHub 仓库 CaseStudies/aquaculture/下找到与此案例研究相关的代码。请 Copilot 为你生成图表。使用以下列表中描述的指令列表构建基本图表。
列表 6.14 Copilot 的说明
# import required libraries
# load the dataset '../source/Aquaculture_Exports.csv' as a pandas dataframe
# apply the following filters to the dataframe:
# - select only the rows where the 'GEOGRAPHY_DESC' column is 'World'
# - select only the rows where the 'UNIT_DESC' column is 'U.S.$'
# add a new column to the dataframe called 'DATE' which is a date object build as follows:
# - the year is the 'YEAR_ID' column
# - the month is the 'TIMEPERIOD_ID' column
# - the day is 1
# plot the dateframe using altair as follows:
# - the x axis is the 'DATE' column
# - the y axis is the 'AMOUNT' column
# - the color is the 'COMMODITY_DESC' column
# save the plot as 'chart.xhtml'
备注:在导入所需的库后,请 Copilot 导入数据集并应用一些过滤器,仅选择以美元计的世界出口。接下来,从YEAR_ID和TIMEPEROID_ID列计算日期。最后,在 Altair 中绘制趋势线。
之前的说明帮助你构建基本图表。在此基础上工作,以生成图 6.18 所示的图表。

图 6.18 水产养殖案例研究
你可以在本书的 GitHub 仓库 CaseStudies/aquaculture/from-data-to-information/chart.py 下找到生成的代码。作为一个洞见,你发现自 1998 年以来,销售额有所增加,这是在 1992 年至 1998 年销售额下降后的结果。受到对下降期的好奇心的驱使,你进行了一些研究,发现下降期部分是由于鲑鱼养殖业的一些健康问题。
在这一步骤结束时,你已经将数据转化为信息。在下一个图表中,我们将继续通过将提取的信息转化为知识来实施这个案例研究。
本章描述了如何将数据转化为信息。首先,我们描述了一些提取洞察力的技术。接下来,我们看到了如何选择故事的角色。最后,我们学习了如何使用 Copilot 在 Altair 中实现一些最受欢迎的图表。您可以在本书的 GitHub 仓库下的 06/other-charts 部分找到其他图表。在下一章中,您将学习如何将信息转化为知识。
摘要
-
将数据转化为信息意味着从数据中提取意义,这是一个对您有重要意义的洞察。
-
提取洞察力的技术包括连接、巧合、好奇心和矛盾。
-
连接涉及在您的数据中寻找细节并将它们联系起来。
-
巧合涉及寻找重复或随机事件,这些事件似乎有一些联系。
-
好奇心是寻找奇怪的事件,例如缺失值和异常值。
-
矛盾是寻找显然相互冲突的事件。
-
每个故事至少应该有三个角色:英雄、向导和对手。
-
用来讲述故事的正确图表取决于要传达的信息。
-
有不同的图表家族,例如柱状图、折线图、地理图表和烹饪图表。
参考文献
-
De Mauro, A., Marzoni, F., and Walter, A. J. (2021). 数据分析变得简单. Packt Ltd.
-
Dorfer, T. A. (2022). 反对饼图的理由.
towardsdatascience.com/the-case-against-the-pie-chart-43f4c3fccc6. -
Guja, A., Siwiak, M., and Siwiak, M. (2024). 数据分析中的生成式 AI. Manning Publications.
-
Khalil, M. (2024). 有效数据分析:硬技能和软技能. Manning Publications.
-
Klein, G. (2017). 看到别人看不到的东西:我们获得洞察力的非凡方式. Nicholas Brealey Publishing.
-
Miller, D. (2017). 构建故事品牌:清晰你的信息,让客户愿意倾听. Thomas Nelson.
-
Moses, B., Gavish, L., and Vorwerck, M. (2022). 数据质量基础:构建可信数据管道的实践指南. O’Reilly Media.
-
Nussbaumer Knaflic, C. (2015) 用数据讲故事:商业专业人士的数据可视化指南. Wiley.
第七章:从信息到知识:构建文本上下文
本章涵盖内容
-
介绍上下文
-
调整故事以适应受众
-
使用 ChatGPT 进行评论和注释
-
使用大型语言模型进行文本上下文处理
-
案例研究:从信息到知识(第一部分)
在计算机科学书籍中谈论知识可能显得完全不合适。单词“知识”可能会激发哲学概念,甚至让人感到畏惧。但在这章(以及下一章),我们不会谈论哲学知识,而是谈论帮助读者理解故事背景的知识。因此,这是应用于我们的数据故事背景的知识,而不是一般知识。在这些章节中,我们将回顾数据故事中上下文的基本概念以及如何根据受众进行调整。首先,我们将在本章中关注文本上下文,而在下一章中,我们将涵盖图像。我们将介绍大型语言模型(LLMs),并以 ChatGPT 作为 LLM 在数据故事中的应用实例。最后,我们将探讨一个实际案例。
7.1 介绍上下文
当我还是个孩子的时候,我经常听到我的父母讨论某个话题,却什么都不懂。他们的言语在我耳边回响,毫无意义,直到我渴望了解他们在谈论什么,我加入了对话并请求解释。然后,我的父亲或母亲,非常耐心地向我解释他们在谈论什么,调整他们的成人推理以适应我的孩子思维,这样我也能理解。多年以后,我发现我自己处于同样的情况,作为一个母亲。我的孩子们经常要求我用他们能理解的话来解释更复杂的言语。当我看到他们的脸上发光,理解我在说什么时,我感到无比的满足。
描述的例子告诉我们,根据我们面向的受众调整我们使用的语言是必要的。如果我们忽视了谁将接收我们的故事,我们可能会以对我们自己来说完全合理的方式说话,但这样可能会排除我们的受众,使他们无法理解我们想要传达的信息。
在上一章中,我们探讨了如何通过图表提取和表示洞察力。下一步是丰富图表的上下文(文本和图像),使参考受众的阅读更加容易。“上下文”指的是周围元素,使受众能够理解显示的信息,如文本、图像和符号。数据上下文应该为你的数据故事场景做好准备,并在受众中激发兴趣。在本章中,我们将主要处理文本上下文,而在下一章中,我们将更多地关注视觉上下文。
情境取决于您要面对的受众类型。例如,如果您与成年人谈论您为产品支付了多少钱,您不需要解释金钱是如何运作的。另一方面,如果您与您的孩子们谈论相同的话题,您可能需要解释不同钞票的面额以及货币体系是如何运作的。
您可以使用生成式 AI 工具,例如 ChatGPT 用于文本和 DALL-E 用于图像,以简化情境构建。您已经学习了使用生成式 AI 工具构建情境的基本技术。本章将重点介绍更高级的技术,以编写针对受众的具有影响力的情境。
我们将考虑以下类型的情境:
-
评论 — 在您的见解之前的文本。它包括帮助观众设定场景和理解见解的背景。在向孩子们解释产品成本示例中,评论包括钞票面额和货币体系是如何运作的。
-
注释 — 解释图表细节的简短文本,例如异常点或趋势线。仅在必要时添加注释。不要让图表因不必要的注释而负担过重。
-
图像 — 强调评论或注释的图片。在产品成本示例中,您可以添加钞票图片以帮助孩子们理解不同的面额。
-
符号 — 箭头、圆圈、线条等,结合注释。它们有助于观众关注图表的特定点。
在本章的剩余部分,我们将使用 ChatGPT 进行评论和注释。在下一章中,我们将专注于 DALL-E 用于图像和符号。此外,我们将介绍 LLMs 以及如何使用它们进行评论和注释。但首先,让我们描述如何调整故事以适应我们的受众。
7.2 调整故事以适应受众
几年前,我被邀请为研究生举办一次研讨会。研讨会的主题是关于构建数据新闻项目网络应用的实施。不幸的是,我发现我面临一个有些尴尬的情况。我的研讨会主题非常技术性,甚至评论了一些代码片段。当我开始讲话时,我意识到观众无法跟上我,因为他们没有理解所需的技术技能。我的演示在技术上正确,但由于对非技术受众讲得过于技术性,我的演讲结果是观众学到的很少。我从这次事件中获得的经验教会了我,在传达任何信息之前,一定要了解我将要面对的受众。
受众 是阅读您数据故事的个人或群体。理解目标受众对于构建能够有效传达信息的数据故事至关重要。在前一章中,我们了解到您可以使用多种类型的图表来传达信息(表 6.4)。一旦您选择了回答您问题的图表集,您就可以进一步细化选择,根据受众调整图表。
在第四章中,您了解到有不同类型的受众。为了简单起见,在本章中,我们将它们分为三种常见的受众类型:
-
普通公众
-
高管
-
专业人士
让我们分别调查每种类型的受众。为了解释您如何调整图表以适应目标受众,我们将使用第四章中描述的案例研究。这个案例研究的目的是了解我们的假设团队需要继续训练哪些运动项目才能在即将到来的比赛中取得最佳成绩。为了方便起见,图 7.1 显示了我们所实施的数据故事的完整内容:从数据到智慧。

图 7.1 第四章中描述的使用案例
7.2.1 普通公众
这个受众包括来自不同背景和知识水平的个人。他们可能对您的话题知之甚少或一无所知。在为普通公众制作数据故事时,使用精确的语言,避免用过多信息使他们感到不知所措,并专注于以视觉和吸引人的方式呈现最相关的见解。普通公众可能会发现图 7.1 中的图表复杂,有不必要的基线。作为图 7.1 中图表的替代方案,您可以考虑绘制图 7.2 中的图表,某些受众可能会觉得它更有吸引力。

图 7.2 适应普通公众的使用案例
这个图表被称为多层饼图。我们本可以将图像放置在相关的条形旁边,但在这个案例中,空间不足,所以我们把它们放在图表的中心。在其他情况下,您可能考虑将图像放在条形旁边。您可以在本书的 GitHub 仓库下的 07/general-public 找到生成此图表的完整代码。
7.2.2 高管
高管通常是组织中高级决策者,他们依赖数据驱动的洞察来做出关键的商业选择。他们通常时间有限,需要简洁且可操作的信息。在为高管创建数据故事时,提前展示关键发现、趋势和建议是至关重要的。
使用突出显示最关键数据点的可视化,并直接将数据与战略目标联系起来。提供额外的背景或行业标准以支持你的分析也可能很有帮助。图 7.1 所示的图表对于高管来说可能很好,因为它不包含很多细节,并描述了我们为什么选择某些运动,这得益于其 50%的基线。
7.2.3 专业人士
这个受众群体包括具有特定领域专业知识或专业背景的个人。他们对数据的理解更深入,需要更多分析信息。在为专业人士创建数据故事时,解释数据分析的方法、假设和局限性。考虑包括额外的支持数据和参考文献,使专业人士能够进一步探索数据。
作为 7.1 图中图表的替代方案,你可以绘制图 7.3 所示的图表,某些受众可能很容易理解。该图只显示了图表,没有任何注释或背景。你可以在本书的 GitHub 仓库下的 07/professionals 中找到生成此图表的完整代码。

图 7.3 适用于专业人士的使用案例
表 7.1 总结了根据受众类型在图表中应表示的内容。现在你已经学会了如何根据受众类型调整你的图表,让我们继续下一步:使用 ChatGPT 进行评论和注释。
表 7.1 根据受众类型在图表中应表示的内容
| 受众类型 | 需求 | 应表示的内容 |
|---|---|---|
| 普通公众 | 理解数据 | 吸引人的见解概述 |
| 高管 | 高层次的数据趋势概述,以辅助战略决策 | 突出关键指标和影响业务成果的趋势。 |
| 专业人士 | 深入理解数据背后的现象的详细见解 | 添加数字、统计和有用信息以深入理解见解。 |
7.3 使用 ChatGPT 进行评论和注释
在他的小说《变形记》中,弗朗茨·卡夫卡讲述了格里高尔·萨姆沙的故事,他是一位旅行推销员,一天早上醒来发现自己变成了一个巨大的昆虫。萨姆沙被困在这个昆虫的外壳中,无法与家人互动或传达他的想法。萨姆沙的家人努力接受他的转变,导致他们与萨姆沙的关系恶化,萨姆沙变得越来越孤立。这部小说揭示了当一个人的内心世界对他人不可接近时出现的根本孤立。数据分析师可能会发现自己处于与卡夫卡小说中格里高尔·萨姆沙相似的情况,当他们不得不向数据可视化图表添加文本时。数据分析师本质上是一名技术人员,可能会在撰写引人入胜的文本时遇到一些困难。
ChatGPT 可以帮助你在你的数据可视化图表中添加文本上下文。你已经了解到,ChatGPT 的提示的基本结构包括三个主要元素:角色、受众和任务。
例如,你可以写上扮演一个表演者(角色),为决策者写作(受众)。为<主题>写 5 个标题(任务)。主题可以是任何你想要的内容。主要问题是如何构建主题,以便 ChatGPT 产生正确的上下文。为了将主题包含在 ChatGPT 的提示中,我们将根据图 7.4 所示的方案生成上下文。

图 7.4 生成上下文所使用的方案
在提示中,我们指定以下四个主要元素:
-
角色 — 你希望 ChatGPT 扮演的角色。在第四章中,你已经学习了多种角色类型,包括表演者、教育者、信息提供者、激励者、行动邀请者和关系构建者。
-
受众 — 你图表的受众。有不同类型的受众,例如公众、高管和专业人士。
-
主题 — 你图表的主题。
-
类型 — 要生成的文本类型,包括注释和评论。
生成上下文的过程是迭代的,这意味着如果你对产生的结果不满意,你可以多次生成上下文。例如,你可以调整一个或多个元素,使 ChatGPT 收敛到期望的输出。
在本节的剩余部分,我们将关注如何编写方案的主题和类型元素,同时保持角色和受众简单。然而,你可以将描述主题和受众的策略适应到其他元素。
作为构建上下文的例子,我们将关注第四章中描述并在图 7.1 中展示的案例研究。以下文本为了方便起见总结了场景:想象你在一个体育公司工作。你正在训练一支在各个学科领域的年轻运动员队伍。对于每个学科,你都记录了世界纪录和你的队伍达到的最佳时间以供比较。不幸的是,你的公司可用的投资资金有限。你的老板要求你了解哪些学科值得训练,希望在下一次比赛中取得好成绩。
7.3.1 描述主题
描述主题意味着组成简单的词汇,精确地描述给 ChatGPT 你在图表中发现和展示的内容。你越精确,输出结果就越好。
描述主题时,关注三个方面的内容:场景、数据和洞察,如图 7.5 所示。让我们更详细地探讨这三个方面。

图 7.5 描述主题所使用的元素
场景
描述你场景的概述,包括分析的背景和目标。对于图 7.1 中的场景,我们可以为 ChatGPT 编写以下提示:我们正在训练一支在各个运动项目中都有参与的年轻运动员队伍。对于每个项目,我们计算了每个项目与该项目世界纪录相比的百分比提升。目标是寻找两个最佳项目进行资助。
数据
提供你数据的概述。这可能包括解释数据的类型、来源以及你进行的任何操作。
使用你自己的话描述数据,提供更个性化的描述。通过手动描述数据,你可以突出通过自动化方法本身可能不明显的重要模式、趋势或相关性。此外,通过手动描述,你可以融入领域专业知识观察。
对于图 7.1 中的场景,编写以下提示文本:有五种运动项目:划船(百分比提升=62.32%),自行车(57.64%),短跑(42.69%),长距离跑(18.31%),和游泳(12.38%)。现在你已经学会了如何描述数据,接下来让我们进入最后一步:描述见解。
见解
描述你从分析中得出的主要见解,例如你发现的关键模式、趋势、相关性或关系。对于图 7.2 中的场景,包括以下文本:*划船和自行车的百分比比 50%的基线更为重要。在我们展示如何构建观众描述之前,让我们在 ChatGPT 上测试到目前为止构建的提示。
测试
我们编写以下提示:扮演一个启发者。为以下主题编写 5 个标题。有五种运动项目:划船(百分比提升=62.32%),自行车(57.64%),短跑(42.69%),长距离跑(18.31%),和游泳(12.38%)。划船和自行车的百分比高于 50%的基线。图 7.6 显示了 ChatGPT 可能生成的输出。

图 7.6 ChatGPT 生成的五个标题
作为一种替代方案,你可以要求 ChatGPT 生成以下内容:扮演一个启发者。使用 30 个词为以下主题编写图表的上下文。有五种运动项目:划船(百分比提升=62.32%),自行车(57.64%),短跑(42.69%),长距离跑(18.31%),和游泳(12.38%)。划船和自行车的百分比高于 50%的基线。图 7.7 显示了 ChatGPT 可能生成的输出。

图 7.7 ChatGPT 生成的上下文
现在您已经学会了如何描述主题,尝试为第一章中描述的案例研究生成上下文:宠物场景。例如,您可以扮演信息提供者的角色。为了方便,我们总结了场景:一个专门为宠物举办的活动组织者正在收集将参加活动的宠物类型。对于每个宠物类别,组织者在专门针对该类别的网站上宣传活动。组织者要求您构建一份关于当前情况的快速报告。
更多详情请参阅第一章。您可以在此处找到生成的上下文的提示:mng.bz/EZvJ。为了进一步练习,请为前几章中描述的其他场景编写主题。现在您已经学会了如何描述主题,让我们继续到下一个元素:描述类型。
7.3.2 描述类型
我们考虑以下类型:评论和注释。在前几节中,您已经看到了不同的方法来指导 ChatGPT 生成上下文类型,例如使用 30 个单词编写图表的上下文。
在描述类型时,尽可能精确,指定以下方面:
-
类型(评论、注释或普通文本)
-
生成单词的最大数量
我的建议是尝试不同的类型,并根据您的需求评估结果。
7.3.3 设置自定义指令
ChatGPT 使您可以为所有新聊天配置自定义指令。例如,如果我们为同一受众类型构建图表并扮演相同的角色,我们可以将此属性用作 ChatGPT 的默认配置。
要启用自定义指令,访问 ChatGPT 网页界面,点击您个人资料附近的三个点,然后点击“自定义指令”。在新窗口中,编写自定义指令。例如,您可以使用第一个框来配置您的角色以及目标受众,第二个框用于更多详细信息,例如要生成的单词数、语气和风格,如图 7.8 所示。

图 7.8 自定义指令对话框
点击“保存”按钮以启用新聊天的自定义指令。如果您想禁用此属性,请在对话框中取消选中“为新聊天启用”属性。然后,点击“保存”按钮。
让我们尝试使用前面的文本(我们已移除要生成的单词数,因为我们已在自定义指令中进行了配置):为以下主题编写图表的上下文。有五种运动项目:划船(百分比提升=62.32%),自行车(57.64%),短跑(42.69%),长跑(18.31%)和游泳(12.38%)。划船和自行车的百分比高于 50%的基线。
图 7.9 展示了可能的一个输出。注意,例如,我们在自定义指令中设定的非正式语气。

图 7.9 配置自定义指令时可能产生的输出
为了在数据叙事中最佳地使用自定义指令,我建议使用它们来配置角色和受众。此外,你可以配置与你的工作或数据相关的其他特定细节,如 ChatGPT 文档中所述(mng.bz/8wAP)。
现在你已经学会了如何使用 ChatGPT 进行评论和注释,让我们继续下一步:以不同的方式使用大型语言模型。到目前为止,你已经使用了 OpenAI 提供的 Web 界面来为 ChatGPT 编写提示。现在,我们将介绍基于 API 调用的 LLM 的高级使用。
7.4 使用大型语言模型进行上下文处理
到目前为止,你已经使用了三个生成式人工智能工具:Copilot 用于代码生成,ChatGPT 用于文本生成,DALL-E 用于图像生成。所有这些工具都是大型语言模型(LLM)应用使用的例子。LLM 是一种旨在预测合理语言的机器学习(ML)模型。自 2017 年谷歌研究人员引入了 transformers 的概念以来,LLM 的受欢迎程度急剧上升,这是一种革命性的架构,它允许在大型语言模型上进行训练,例如基于生成预训练的 transformers(GPTs),ChatGPT 就是基于此,以及双向编码器表示的 transformers(BERT)。Transformers 使得在大量数据集上训练 LLM 成为可能,从而产生了具有令人难以置信的语言生成能力的模型。
在这本书中,我们不会关注 LLM 是如何工作的。相反,我们旨在展示你如何有效地使用它们进行数据叙事。然而,如果你对深入了解技术方面感兴趣,关于这个主题有大量的参考文献可供查阅(Vaswani,2017;Koenigstein,2024)。
在你开始使用大型语言模型(LLM)构建你的数据驱动故事之前,问自己模型是否需要了解与你工作领域相关的特定信息,如图 7.10 所示。如果答案是“否”,那么你可以安全地继续使用 ChatGPT。然而,如果你的答案是“是”,那么你可以应用以下技术之一:
-
微调 — 这种技术通过在特定任务数据上更新其参数,将预训练的 LLM 适应特定领域,优化其在该领域的性能。
-
检索增强生成 — 这种技术结合了信息检索和语言生成,使大型语言模型(LLMs)能够在生成过程中整合外部知识源。

图 7.10 建立是否扩展 LLM 的标准
在本节的剩余部分,我们假设你的答案是肯定的,并且你必须根据你的特定领域定制你的大型语言模型。微调有用的实际案例可能包括当你必须为同一类型的受众或甚至同一受众生成不同的数据故事时。在这种情况下,你可以使用相同的注释结构构建你的数据库,以便所有新的注释都与之前的注释具有相同的结构。当你的受众阅读你的数据故事时,这可能会产生一些熟悉感。在其他情况下,你可能需要使用 RAG,例如,当你有一个长文档,你想要基于它为你的数据故事构建一个简短的注释时。使用 RAG 可以帮助你构建文本摘要。现在你已经了解了扩展 LLM 的潜在好处,让我们首先分析第一种策略:微调。
7.4.1 微调
GPT-3 在 17GB 的数据上进行了训练,而 OpenAI 的最新模型 GPT-4 有 45GB 的训练数据。这意味着它们包含几乎在所有情况下都可以使用的信息。然而,在某些情况下,微调你的模型可能会提供更好的结果。
微调是在特定数据集上进一步训练预训练语言模型的过程,该数据集与你的特定领域更相关。在微调过程中,你使用一个较小的数据集,该数据集通常包含与你的任务相关的示例和特定的输入-输出对。在实践中,数据集是一组样本的集合,每个样本包含提示和建议的完成内容。
当你将微调应用于数据叙事时,你可以为每种受众类型构建不同的数据集,从而获得更好的结果。例如,你可以为公众构建一个数据集,为专业人士构建一个,为决策者构建另一个。你甚至可以为每个你工作的场景(产品、主题等)以及你想要生成的每种文本类型(标题、注释和评论)创建不同的数据集。你的数据集越具体,你的结果就会越好。
准备数据集是微调过程中的最大努力。在本节的剩余部分,我们将描述两种准备数据集的策略:手动构建和从来源构建。在两种情况下,我们都会使用 OpenAI API。有关安装的更多详细信息,请参阅附录 A。
手动构建
手动构建涉及手动定义每个对(提示,完成)。这种解决方案使你能够获得最佳结果,因为你指定了模型在特定输入下的确切行为。例如,考虑以下对:
-
提示 — 为关于主题 X 的公众生成一个标题。
-
完成 — X 向你揭示了!
现在,假设您已经使用这对数据对模型进行了微调,并想使用您的模型为公众生成标题。如果您将相对论主题作为输入提供给您的微调模型,它可能会生成以下类似的标题:相对论揭秘给你! 这种策略的缺点是它耗时,因为您必须手动编写每一对。
首先,您可以定义一个最小数量的精选样本,涵盖所有可能的案例。OpenAI 模型要求您至少表示 10 个样本。接下来,训练模型。然后,通过将原始模型(即,没有微调)作为参考,进行模型评估。向原始模型和微调模型提供相同的提示,并比较产生的输出。如果您的微调模型比原始模型表现更好,则使用您的新模型。相反,如果它表现不如或与原始模型表现相同,则尝试添加新的样本或改进现有的样本。重复此过程,直到达到良好的结果。
为了展示手动构建的工作原理,我们将构建一个针对公众的定制数据集,并生成一个输出作为评论。在前一章中,您看到应根据您想要传达的信息使用不同的图表。在这里,我们将根据需要传达的信息构建不同的输出。我们将为每种类型的信息构建一个或多个(提示-完成)对。表 7.2 显示了表示所述场景的可能数据集。您可以在本书的 GitHub 仓库下的 07/manual-ChatGPT-fine-tuning 部分找到本节中描述的代码。
表 7.2 基于需要传达的信息的样本
| 需要传达的信息 | 提示 | 完成 ^a |
|---|---|---|
| 整体部分 | 会议参与者类型百分比(研究人员 5%,学生 30%,教授 65%) | 参与会议的教授比例较高(65%)。研究人员对此次活动不感兴趣(5%)。 |
| 实体之间的比较 | 红色(80%)、黄色和绿色性能的比较 | 与黄色和绿色相比,红色在性能上提高了 80%。 |
| 趋势 | 上一个 12 个月内的销售变化(–12%),由于订阅者和视频观看次数减少。 | 在过去 12 个月内,基于各种指标(包括订阅者和视频观看次数),销售下降了 12%。 |
| 调查或问卷的结果 | 问卷:3 个正面回答,7 个负面回答 | 在 10 个人中,有 3 个人对问卷给出了正面回答。 |
| 分布 | 产品 A(+30%)和产品 B 在过去 12 个月的销售 | 与产品 B 相比,产品 A 在过去 12 个月的销售增长了 30%。 |
| 空间信息 | 与南欧相比,北欧的销售(+23%) | 与南欧相比,北欧的销售增长了 23%。 |
| 关系 | 1990 年至 2020 年的销售趋势线增长了 120%。 | 在 1990 年至 2020 年间,销售额增长了 120%。 |
| 实体比较 | 我们食谱的主要成分:糖和盐 | 我们食谱所选的成分是糖和盐。 |
| 实体比较 | 金(30)、银(20)和铜(40)之间的比较 | 铜以 40% 的比例击败了银和金。 |
| 分布 | 家庭事务分配(烹饪 35%,清洁 30%,洗衣 20%,和园艺工作 15%) | 烹饪占据了最大的比例,达到 35%。清洁紧随其后,为 30%,而洗衣和园艺工作分别占 20% 和 15%。 |
| a. 单词 completion 可能会令人困惑,但这是 OpenAI API 使用的术语。Completion 指的是模型产生的输出。 |
一旦构建了数据集,你必须将其格式化为 JSONL 文件。此文件包含一条消息列表。将每条消息视为一个单独的聊天,你可以指定一个通用配置、用户提示和助手(模型)输出,如下所示。
列表 7.1 JSONL 文件的结构
{
"messages": [
{
"role": "system",
"content": "You are a data analyst showing data to the general public."
},
{
"role": "user",
"content": "Distribution of household chores
(Cooking 35% Cleaning 30% Laundry 20%, Yard work 15%)"
},
{
"role": "assistant",
"content": "Cooking takes up the largest portion at 35%.
Cleaning follows at 30% while laundry
and yard work accounts for 20% and 15% respectively."
}
]
}
注意 使用关键字 messages 定义样本列表。想象每个样本都是一个单独的聊天,你可以指定模型角色:system 用于通用配置;user 用于用户提示;assistant 用于模型输出。
如果你的数据集以 CSV 文件保存,请使用以下列表中显示的代码进行转换,该代码也包含在 prepare-data.py 中,以将其转换为 JSONL。
列表 7.2 如何将 CSV 文件转换为 JSONL
import pandas as pd
import json
df = pd.read_csv('general-public.csv')
json_list = []
for index, row in df.iterrows():
json_object = {
"messages": [
{
"role": "system",
"content": "You are a data analyst showing data to the general public."
},
{
"role": "user",
"content": row['prompt']
},
{
"role": "assistant",
"content": row['completion']
}
]
}
json_list.append(json_object)
with open('general-public.jsonl', 'w') as outfile:
for json_object in json_list:
json.dump(json_object, outfile)
outfile.write('\n')
注意 首先,将数据集作为 pandas DataFrame 加载。接下来,按照列表 7.1 中描述的格式将其格式化为 JSONL 格式,最后保存生成的 JSONL 文件。
现在,我们已经准备好微调我们的模型。我们需要一个 OPENAI_API_KEY,如附录 A 中所述。如果你从免费计划转换到付费计划,你可能需要生成一个新的 API 密钥,因为初始密钥在切换到付费计划后不再有效。打开终端,并将你的 OPENAI_API_KEY 作为环境变量导出(export OPENAI_API_KEY='my key')。接下来,将生成的文件上传到 OpenAI 服务器,上传过程完成后,创建一个用于微调的任务。以下列表显示了执行这些操作的代码。或者,阅读 GitHub 仓库中的 tune-model.py 脚本。请记住,此选项仅适用于付费版本。
列表 7.3 如何微调模型
import os
import openai
import time
openai.api_key = os.getenv('OPENAI_API_KEY') #1
dataset = openai.File.create(file=open('general-public.jsonl',
[CA]'rb'), purpose='fine-tune') #2
print('Uploaded file id', dataset.id)
while True: #3
print('Waiting while file is processed...')
file_handle = openai.File.retrieve(id=dataset.id)
if len(file_handle) and file_handle.status == 'processed':
print('File processed')
break
time.sleep(3)
job = openai.FineTuningJob.create(training_file=dataset.id, model="gpt-3.5-turbo") #4
while True:
print('Waiting while fine-tuning is completed...')
job_handle = openai.FineTuningJob.retrieve(id=job.id)
if job_handle.status == 'succeeded':
print('Fine-tuning complete')
print('Fine-tuned model info', job_handle)
print('Model id', job_handle.fine_tuned_model) #5
break
time.sleep(3)
1 获取密钥的另一种方法:openai.api_key = 'MY_KEY'
2 创建一个新的数据集并将其上传到 OpenAI 服务器
3 进入循环,直到模型微调完成
4 创建一个新的微调任务
5 打印模型 ID
注意:首先,使用openai.File.create()方法创建一个新的数据集并将其上传到 OpenAI 服务器。接下来,使用openai.FineTuningJob.create()方法创建一个使用 GPT-3.5 Turbo 的微调作业。等待作业完成。这可能会花费很长时间,具体取决于数据集的大小。一旦模型训练完成,使用fine_tuned_model变量来打印与微调模型相关的信息。
以下列表显示了在执行 fine-tune-model.py 脚本后的输出示例。这次微调大约需要 0.05 美元。
列表 7.4 与微调模型相关的信息示例
fine-tuned model info {
"object": "fine_tuning.job",
"id": "your model id",
"model": "gpt-3.5-turbo-0613",
"created_at": 1693347869,
"finished_at": 1693348340,
"fine_tuned_model": "ft:gpt-3.5-turbo-0613:personal::7t1Xuct5",
"organization_id": "org-jWkYw8hPpaNwkesXezsWOwK8",
"result_files": [
"file-ro0BoeariIjOl7NSGRC80v8r"
],
"status": "succeeded",
"validation_file": null,
"training_file": "file-InGnigMTto3YLrsiLuIUr7ty",
"hyperparameters": {
"n_epochs": 10
},
"trained_tokens": 5930
}
注意:提供了一些有用的信息,包括模型类型、模型 ID、使用的超参数等。
现在,我们可以使用微调模型来生成针对普通公众的新评论。使用前一个列表中fine_tuned_model键对应的值来引用你的模型(例如,示例中的"ft:gpt-3.5-turbo-0613:personal::7t1Xuct5")。
要生成新的评论,请使用openai.ChatCompletion.create()方法开始一个新的聊天会话,如以下列表和 GitHub 仓库中该书的 generate-description.py 脚本所示。作为一个用例,再次考虑图 7.1 的例子。
列表 7.5 如何生成新的评论
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
model_id = "ft:gpt-3.5-turbo-0613:personal::7t1Xuct5"
completion = openai.ChatCompletion.create(
model=model_id,
messages=[
{
'role': 'system',
'content': 'You are a data analyst showing data to the general public.',
},
{
'role': 'user',
'content': 'Top sports: rowing (62%) and cycling (58%)'
},
],
)
print(completion.choices[0].message)
注意:通过指定模型 ID 和消息列表创建一个新的ChatCompletion实例。示例中定义了一个消息,其系统角色与微调数据集相同,用户角色有一个简短的情景描述。
以下列表显示了一个示例输出。
列表 7.6 一个示例输出
{
"role": "assistant",
"content": " \"The most popular sports are rowing and cycling with 62% and 58% of people practicing them respectively.\""
}
注意:输出包含角色(助手)和内容。
将生成的内文作为评论纳入你的图表中。这个例子展示了如何使用手动数据集进行模型微调。在例子中,输出是直接的。如果你想让你的模型产生更复杂的输出,你必须使你的微调数据集更复杂——例如,通过添加专门为你的受众或主题设计的新对(提示-完成)。
为了与微调模型进行比较,图 7.11 显示了 ChatGPT(未微调)在类似输入下的输出。现在你已经学会了如何手动构建你的数据集,让我们继续到下一个策略,从来源构建。

图 7.11 ChatGPT 未微调产生的输出
从来源构建
这种策略涉及从外部来源构建您的数据集,例如您的公司网站或特定领域的博客。例如,如果您在医疗领域工作,您可以下载关于健康的科学论文的标题和摘要。这使您能够构建一个具有非常特定领域语言的数据集。或者,如果您在 ICT 领域工作,您可以从馈送中下载博客文章的标题和副标题来构建您的技术数据集。无论如何,您必须在所有情况下注意数据许可。如果许可证明确禁止使用,则不能使用这些来源,并且您必须寻找其他数据来源。在某些情况下,直接联系数据作者可能就足够了——例如,如果您想下载他们的数据。
在本节的剩余部分,我们将通过从我的 Medium 博客中提取内容来构建一个针对技术受众的数据集。目标是构建与博客标题相对应的图表标题,并将博客副标题作为输入。您可以在本书的 GitHub 仓库下的 07/from-source-ChatGPT-fine-tuning 目录中找到示例。
首先,下载数据。要求 Copilot 为您生成代码。以下列表显示了 Copilot 的指令。
列表 7.7 Copilot 的指令
# import required libraries
# extract the title and link from the following rss/feed url: https://alod83.medium.com/feed
# for each extracted link, extract the subheading from the article
# create a dataframe with the following columns: 'prompt', ‘completion’
# save the dataframe to a csv file called 'medium-articles.csv'
注意:指定每个条目的馈送 URL 和要提取的信息。还要求 Copilot 生成代码以将提取的项目保存到 CSV 文件中。
Copilot 将生成类似于列表 7.8 中所示的结果。将提示设置为副标题,将完成设置为标题。保存脚本,并运行它。您可以在本书的 GitHub 仓库中的 download-raw-data.py 脚本中找到 Copilot 生成的代码。您应该在您的当前工作目录中看到 medium-articles.csv 文件。
列表 7.8 如何从馈送中提取数据
import feedparser
import pandas as pd
import requests
from bs4 import BeautifulSoup
url = 'https://alod83.medium.com/feed'
feed = feedparser.parse(url)
titles = []
links = []
subheadings = []
for entry in feed.entries:
titles.append(entry.title)
links.append(entry.link)
print(entry.link)
response = requests.get(entry.link)
soup = BeautifulSoup(response.content, 'html.parser')
subheading = soup.find('h2', attrs={'class': 'pw-subtitle-paragraph'}).text
subheadings.append(subheading)
df = pd.DataFrame({'prompt': subheadings,'completion': titles})
df.to_csv('medium-articles.csv', index=False)
注意:使用feedparser、requests和bs4库。如果您在环境中没有它们,请使用 pip 包管理器进行安装。
一旦构建了数据集,请按照 7.4.1 节中描述的程序微调数据集(列表 7.2–7.6)。您可以在本书的 GitHub 仓库中找到完整的示例。
要测试微调后的模型,请提供以下提示作为输入:选择最佳资助运动项目的图表。模型生成的输出类似于以下内容:如何选择最佳资助运动项目:一种数据驱动的方法。尝试使用 ChatGPT 进行类似的提示。图 7.12 显示了可能的输出。由于 ChatGPT 未进行微调,您必须在提示中指定更多细节,如之前所见。相反,对于您的微调模型,在提示中描述内容就足够了。现在您已经学会了如何进行数据故事讲述的微调,让我们继续学习将模型适应特定上下文的下一个策略:增强检索生成。

图 7.12 ChatGPT 生成的输出
7.4.2 增强检索生成
到目前为止,你已经看到了如何通过构建一个专用的数据集来适应一个 LLM 的上下文。在这种情况下,努力的工作在于准备数据集。想象一下,如果能够直接将文本传递给 LLM 而不需要将其转换为特定格式,那会多么方便。好消息是,这确实可能,多亏了检索增强生成(RAG)。
RAG 是一种高级自然语言处理(NLP)技术,它结合了信息检索和文本生成的元素。首先,RAG 执行一个检索步骤,查询外部知识源,如庞大的文本语料库或结构化数据库。接下来,RAG 使用这个知识源来增强其响应生成。RAG 将检索到的事实整合到其生成的文本中。
在数据故事讲述领域,你可以使用 RAG 来适应你的 LLM 到你的主题,例如产品、实时数据、客户评论或其他相关信息。例如,通过查询知识库以获取特定产品的详细信息,你可以生成专用的评论和注释。
假设你想构建一个基于 RAG 的系统,从你的公司网站上检索有关产品的信息。图 7.13 展示了我们将要实现的 RAG 系统的架构。

图 7.13 基于 RAG 的系统
首先,我们将从指定的 URL 下载文本,将其分割,并以我们存储在向量数据库中的向量形式表示。我们将向量数据库作为 LLM 应用程序的输入,该应用程序可以通过查询向量数据库来回答查询。我们将实现一个示例,根据包含在 HTML 页面中的描述生成特定智能手机的评论。在实践中,我们将加载 HTML 页面到向量数据库中,然后我们将实现一个 LLM 应用程序来查询它。我们将使用 LangChain 来实现 LLM 应用程序,使用 Chroma 作为向量数据库,使用 OpenAI 作为 LLM,以便一切都能正常工作。有关如何安装这些工具的更多详细信息,请参阅附录 A。
在本节的剩余部分,你将学习如何实现所描述的系统。我们将首先介绍 LangChain。接下来,我们将看到如何将数据存储在 Chroma 中。最后,你将学习如何查询构建的系统。
LangChain 介绍
LangChain (www.langchain.com/) 是一个框架,它使你能够创建将 LLM 连接到其他来源的应用程序。LangChain 支持多个提供商,包括 OpenAI、Google、Microsoft、Hugging Face 以及更多。在这本书中,我们将专注于 OpenAI 提供的数据模型。
LangChain 背后的核心思想是链的概念,它由来自不同模块的几个组件组成。有三个主要组件:
-
LLM 包装器 — 由外部提供商提供的 LLM 包装器,例如 OpenAI 和 Hugging Face
-
提示模板 — 不同提示的模板,例如聊天机器人和问答
-
索引 — 你可以使用的外部结构,为 LLM 提供额外的上下文
基于 LangChain 的应用程序是 上下文感知 的,因为它们将 LLM 连接到外部来源。此外,此类应用程序很有用,因为它们可以根据提供的上下文、采取的行动等问题进行回答。
最直接的链仅由一个 LLM 和一个允许你查询模型的提示组成。在本节的剩余部分,我们将实现一个由图 7.14 中显示的组件组成的 LangChain:向量数据库(Chroma)、提示模板、LLM 模型(OpenAI 的 GPT-3.5 Turbo)和检索接口。

图 7.14 实现的架构
你可以在本书的 GitHub 仓库中找到本例中描述的完整代码,位于 07/rag 目录下。我们需要一个 OPENAI_API_KEY,如附录 A 中指定。打开终端,并将你的 OPENAI_API_KEY 作为环境变量导出(export OPENAI_API_KEY='my key')。
Chroma (www.trychroma.com/) 是一个嵌入数据库,你可以将其用作 LangChain 的索引器。要安装和配置 Chroma,请参阅附录 A。嵌入 是数据的数值表示,易于索引和检索,通常用于实时任务(Lane 和 Dyshel,2024)。在将文本存储到 Chroma 之前,我们必须将其转换为向量嵌入。有关嵌入的更多详细信息,请参阅本章的参考文献部分。
考虑一个假设网站上的产品描述,如图 7.15 所示。我们任务的目标是将图 7.15 中显示的产品描述存储在 Chroma 中。

图 7.15 包含产品描述的 HTML 页面
第一步涉及从 URL 加载数据,如下所示列表。由于 Chroma 完全集成于 LangChain,我们将使用它来完成我们的任务。LangChain 支持多种格式,包括 PDF、URL 等。
列表 7.9 在 LangChain 中如何加载 HTML 文档
from langchain_community.document_loaders.xhtml import UnstructuredHTMLLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
loader = UnstructuredHTMLLoader('product.xhtml') #1
data = loader.load()
1 加载数据
注意 — 要在 LangChain 中加载 HTML 文档,请构建一个 UnstructuredHTMLLoader() 对象。
接下来,将数据分成 20 个块,如下所示列表。我们本可以选择小于总文本大小的任何数字作为块大小。
列表 7.10 如何将文本分割成块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 100,
chunk_overlap = 20,
length_function = len,
is_separator_regex = False,
)
splitted_data = text_splitter.split_documents(data)
注意 — 创建一个 RecursiveCharacterTextSplitter() 对象来将文本分割成块。
然后,将分割的文本转换为嵌入并将它们存储在 Chroma 中。
列表 7.11 如何在 Chroma 中生成嵌入
embeddings = OpenAIEmbeddings()
store = Chroma.from_documents(
splitted_data,
embeddings,
ids = [f"{item.metadata['source']}-{index}" for index, item in enumerate(splitted_data)],
collection_name='Product-Info',
persist_directory='db',
)
store.persist()
注意 — 首先,创建一个新的 OpenAIEmbeddings() 对象。然后,创建一个包含分割数据和嵌入的 Chroma 存储,并将其与集合 Product-Info 关联。最后,使用 persist() 方法将 Chroma 存储在文件系统中。
现在,我们的向量存储已准备就绪,因此我们可以继续下一步:定义一个提示模板。
定义提示模板
提示模板是用于生成 LLM 提示的预定义文本。提示模板可能包括适合您任务的说明、示例、上下文和问题。以下列表显示了一个我们可以作为系统输入的提示示例。
列表 7.12 如何构建提示模板
template = """You are a bot that answers questions about the product New SmartX 2023, using only the context provided.
If you don't know the answer, simply state that you don't know.
{context}
Question: {question}"""
prompt = PromptTemplate(
template=template, input_variables=['context', 'question']
)
注意:首先,定义你的模板结构。使用括号定义输入变量。在示例中,有两个变量:context 和 question。接下来,创建一个新的 PromptTemplate() 对象,并将模板和输入变量作为参数传递给它。
一旦我们构建了提示模板,我们就准备好进行最后一步:检索和查询。
检索接口
检索接口 是一个使我们能够结合存储在 Chroma 数据库和 OpenAI LLM 中的数据的接口。我们可以使用检索来查询我们的系统,并生成评论和注释以包含在我们的图表中。以下列表显示了一个检索使用示例。
列表 7.13 如何构建检索
llm = ChatOpenAI(temperature=0, model='gpt-3.5-turbo')
qa = RetrievalQA.from_chain_type(
llm=llm,
chain_type='stuff',
retriever=store.as_retriever(),
chain_type_kwargs={'prompt': prompt, },
return_source_documents=True,
)
print(
qa.invoke({"query": 'Describe the product New SmartX 2023 using 30 words'})
)
注意:首先,使用 ChatOpenAI() 创建一个 LLM 实例。将温度设置为 0 以获得保守的输出。温度范围从 0(低创造力)到 1(高创造力)。将模型设置为 GPT-3.5-turbo。接下来,通过指定 LLM、向量存储(retriever)、提示和其他参数,使用 RetrievalQA() 创建一个检索接口。将 chain_type 设置为 stuff,这是一个预包装的文档链,它将文档列表插入到提示中,然后传递给 LLM。最后,提出问题。
下面的列表显示了生成的输出。您可以将生成的文本(粗体)插入到您的图表中。
列表 7.14 生成的输出
{'query': 'Describe the product New SmartX 2023 using 30 words',
'result': 'The New SmartX 2023 is a cutting-edge smartphone with a 5.7-inch Super AMOLED display and a high-quality camera that captures breathtaking landscapes and detailed close-ups.',
'source_documents':
[Document(page_content='© 2023 SmartX Technologies. All rights reserved.',
metadata={'source': 'product.xhtml'}),
Document(page_content='Get ready to experience the future with the all-new SmartX 2023\. This cutting-edge smartphone',
metadata={'source': 'product.xhtml'}),
Document(page_content='Introducing the New SmartX 2023\n\\nKey Features:\n\\n5.7-inch Super AMOLED Display',
metadata={'source': 'product.xhtml'}),
Document(page_content="you're taking breathtaking landscapes or detailed close-ups, the SmartX 2023's camera delivers",
metadata={'source': 'product.xhtml'})
]
}
注意:输出包含要插入图表中的文本(粗体)和其他有用信息,例如原始查询和源文档。
现在您已经学会了如何将 LLM 应用于构建数据故事中的上下文,让我们继续到一个实际例子。
7.5 案例研究:从信息到知识(第一部分)
在上一章中,我们分析了如何在水产养殖案例研究中将数据转化为信息。作为一个快速提醒,该案例研究涉及围绕美国鲑鱼养殖安全问题构建故事。我们决定将鲑鱼养殖的销售趋势线与其他类型的养殖进行比较。作为一个洞察,我们发现自 1998 年以来,销售有所增加,而 1992 年至 1998 年期间销售下降。我们发现下降期部分是由于鲑鱼养殖中的一些健康问题。图 7.16 显示了 DIKW 金字塔第一步结束时生成的图表:从数据到信息。

图 7.16 数据到信息阶段结束时生成的图表
要将图表转化为数据故事,下一步涉及将信息转化为知识。我们将通过以下方式完成:
-
调整图表以适应受众的一些设计考虑因素
-
添加描述水产养殖安全一般情况的注释
-
添加注释和符号以突出销售下降的时期
让我们从第一个要点开始:根据受众调整图表。
7.5.1 根据受众调整图表
该场景要求我们向高管受众展示数据故事,这意味着我们需要一个图表足够简单,他们可以快速根据其信息做出决策。一般来说,高管熟悉趋势线,因此我们不需要修改图表。此外,图表既不太详细也不太简单。它包含适当的信息量,使受众不会被信息淹没。此外,图表没有显得稀疏。因此,我们可以得出结论,图表类型非常适合我们的受众。
我们还假设我们的受众熟悉 y 轴上的$符号和 x 轴上的“年份”标签,因此我们不需要添加任何进一步的说明。我们可以保留鲑鱼趋势线与其他趋势线的比较,因为这有助于我们的受众了解鲑鱼销售与其他类别相比的行为。不需要添加更多细节。
挑战:你如何调整图表以适应普通公众或专业人士的受众?
对于普通公众,您可以考虑简化图表——例如,通过减少年份的数量。您可能还需要更好地解释 y 轴的含义。对于专业人士,您可以添加更多细节,例如每年都有值的点,或者甚至可以展示其他水产养殖类别。
现在我们已经讨论了一些设计考虑因素,以根据受众调整图表,让我们继续下一步:添加注释。我们将使用 RAG 生成注释。
7.5.2 使用 RAG 添加注释
我们将在标题下方立即添加注释。我们的注释应该解释美国水产养殖中的安全机制。我们将基于“养殖海鲜”(mng.bz/WEW0),一份官方 FDA 事实说明书。这份文件描述了包括水产养殖海鲜的安全水平在内的多个主题。
您可以实施一个基于 RAG 的系统,使用第 7.4.2 节中实现的代码构建所需的注释。您只需要提供以下提示:“描述美国水产养殖海鲜的安全性”。实现的 RAG 系统代码也可在本书的 GitHub 仓库中找到,位于 CaseStudies/aquaculture/from-information-to-knowledge/rag.py。以下列表显示了生成的输出,包含所需的注释。
列表 7.15 生成的输出
{'query': 'Describe Safety of Aquaculture Seafood in the U.S.', 'result': 'Aquaculture seafood in the U.S. is regulated by the FDA to ensure safety. Strict standards are in place to monitor water quality, feed, and disease control. Regular inspections and testing are conducted to minimize risks and protect consumers.', 'source_documents': [Document(page_content='Safety of Aquaculture Seafood', metadata={'source': 'aquaculture.xhtml'}), Document(page_content='Regulatory Requirements for Aquacultured Seafood', metadata={'source': 'aquaculture.xhtml'}), Document(page_content='Domestic Aquaculture Seafood', metadata={'source': 'aquaculture.xhtml'}), Document(page_content='for additional information on how the FDA ensures the safety of imported seafood products.', metadata={'source': 'aquaculture.xhtml'})]}
注意:使用生成的输出作为图表的注释。
现在,我们可以将此文本作为图表的注释添加。列表 7.16 仅显示了包括注释在内的原始图表的修改。您可以在 GitHub 仓库中找到完整的代码,位于 CaseStudies/aquaculture/ 从信息到知识/chart.py。
列表 7.16 注释
commentary = ['Aquaculture seafood in the U.S. is regulated by the FDA to ensure safety. Strict standards are in place to monitor water quality, feed, and disease control.',
'Regular inspections and testing are conducted to minimize risks and protect consumers. (Source: U.S. Food and Drug Administration)'
]
base = alt.Chart(df).encode(
x=alt.X('YEAR_ID:O', title=''),
y=alt.Y('AMOUNT', title='$',axis=alt.Axis(format='.2s')),
color=alt.Color('CATEGORY',
legend=None,
scale=alt.Scale(range=range, domain=domain)
)
).properties(
width=800,
height=400,
title=alt.TitleParams(
text='Aquaculture Exports of Salmon in the U.S.',
subtitle=commentary,
fontSize=20,
subtitleFontSize=14,
align='left',
anchor='start',
offset=20,
color=color,
subtitleColor='black'
)
)
注意:使用 title 属性在标题之前添加注释,并为图表添加一个临时标题。
下一步是突出显示销售额下降的时期。让我们继续。
7.5.3 突出显示销售额下降的时期
销售额下降的时期从 1992 年到 1998 年。我们想突出显示它,让观众知道在这个时期,鲑鱼养殖业存在健康问题。这将让观众考虑尊重安全规则,以避免未来出现同样的问题。我们将添加两个元素来突出这个下降时期:
-
覆盖下降时期的浅灰色矩形
-
描述健康问题的文本注释
以下列表显示了构建矩形的代码。
列表 7.17 矩形
N = 100000000 #1
y = df['AMOUNT'].max() + N
rect_df = pd.DataFrame({'x': [1992],
'x2': [1998],
'y' : [0],
'y2': [y]
})
rect = alt.Chart(rect_df).mark_rect(
color='lightgrey',
opacity=0.5
).encode(
x='x:O',
x2='x2:O',
y= 'y:Q',
y2= 'y2:Q'
)
1 一个用于设置图表上部的魔法数字
注意:首先,使用矩形的坐标构建一个 DataFrame。接下来,使用 mark_rect() 绘制矩形。
挑战:您可以为 Copilot 编写哪些指令来加快编码过程?
您可以尝试添加以下指令来生成矩形:# 添加 一个 从 1993 到 2000 的 矩形。您会得到什么输出?
以下列表显示了添加注释的代码。销售额下降的部分原因是鱼类健康问题。
列表 7.18 注释
ann_df = pd.DataFrame({'x': [1992, 1992, 1992],
'y': [y, y-N/3*2, y-N/3*4],
'text': ['The decline in sales was',
'partially due to fish',
'health issues']
})
annotation = alt.Chart(ann_df
).mark_text(
align='left',
baseline='middle',
fontSize=14,
dx=5,
dy=10
).encode(
x='x:O',
y='y:Q',
text='text:N'
)
chart = (chart + text + rect + annotation
).configure_axis(
labelFontSize=14,
titleFontSize=16,
grid=False
).configure_view(
strokeWidth=0
)
chart.save('chart.xhtml')
注意:首先,使用注释文本及其位置信息构建一个 DataFrame。接下来,使用 mark_text() 绘制注释。最后,绘制并保存图表。
图 7.17 展示了添加上下文后的最终图表,我们将信息转化为知识。在下一章中,我们将通过添加一些图片来进一步丰富上下文,而在第九章中,我们将通过添加下一步:智慧来完成故事。

图 7.17 信息到知识阶段结束时的图表
您现在已经实现了一个将信息转化为知识的实际例子。在进入下一章之前,让我们通过完成一个实际练习来进一步巩固这个概念。
7.5.4 练习
按照以下方式修改之前的图表:
-
将图表调整到专业人士的受众。
-
向鲑鱼折线图添加点。建议:将
point=True作为mark_line()的参数。 -
为每个点添加值。建议:使用
mark_text()为每个点添加值。
-
-
实现一个基于 RAG 的系统,从缅因州海洋水产养殖规划与发展委员会报告和建议(第 28-32 页,
mng.bz/jXqx)中提取下降期的注释。建议:使用PDFMinerLoader()从 PDF 中提取数据。你可能需要安装一些额外的 Python 包,包括 pdf2image、pdfminer 和 pdfminer.six。你可以在本书的 GitHub 仓库下的 CaseStudies/aquaculture/from-information-to-knowledge/rag-annotation.py 中找到解决方案。
在本章的第一部分,你学习了如何通过为你的数据可视化图表添加上下文来将信息转化为知识。你看到上下文取决于阅读你图表的受众。例如,如果你的图表将由公众阅读,请避免技术细节并使用吸引人的可视化。另一方面,如果你的图表将由技术专家阅读,请尽可能添加细节,同时保持图表易于阅读。在章节的第二部分,你看到了如何使用生成式 AI 工具作为助手来构建你的上下文。最后,你学习了在哪里放置你的图表中的文本上下文。在下一章中,你将看到如何向你的图表添加图像以丰富上下文。
摘要
-
在你的数据可视化中添加上下文对于将信息转化为知识至关重要。文本上下文包括所有对观众理解数据有用的相关事实和事件。
-
当你构建图表时,请根据受众进行定制。一般来说,有三种类型的受众:公众、专业人士和行政人员。
-
使用生成式 AI 工具作为助手来为你的数据创建上下文。特别是,使用 ChatGPT 来生成评论和注释。
-
如果 ChatGPT 需要了解自定义数据或主题,可以通过微调或 RAG 扩展你的 LLM。
-
微调使你能够根据提示-完成对的数据集优化预训练的 LLM。
-
检索增强生成使用一个称为向量数据库的外部数据库,通过特定领域的主题扩展 LLM 的知识。
参考文献
嵌入
-
Lane, H. 和 Dyshel, M. (2024). 《自然语言处理实战》(第 2 版)。Manning Publications.
-
OpenAI. (n.d.). 嵌入。
platform.openai.com/docs/guides/embeddings.
微调
-
Bantilan, N. (2023). 微调与提示工程大型语言模型比较。
mlops.community/fine-tuning-vs-prompt-engineering-llms/. -
Jolley, E. (2023). 检索增强生成简介。
arize.com/blog-course/introduction-to-retrieval-augmented-generation/. -
Marcelo, X. (2023). 如何微调 OpenAI GPT。
medium.com/@marceloax.br/how-to-fine-tune-openai-gpt-3-d06741f915f4. -
OpenAI. (n.d.). 微调。
platform.openai.com/docs/guides/fine-tuning.
LangChain
-
Biswas, A. (2023). 如何使用 LangChain Python 模块。
www.packtpub.com/article-hub/how-to-work-with-langchain-python-modules. -
Geeks for Geeks. (2024). LangChain 简介。
www.geeksforgeeks.org/introduction-to-langchain/. -
Pinecone. (n.d.). LangChain:简介和入门。
www.pinecone.io/learn/series/langchain/langchain-intro/.
LLM
-
De Angelis, L.,Baglivo, F.,Arzilli, G.,Privitera, G. P.,Ferragina, P.,Tozzi, A. E.,和 Rizzo, C. (2023). ChatGPT 和大型语言模型的兴起:公共卫生中的新 AI 驱动的信息流行病威胁。《公共卫生前沿》,11,1166120。
doi.org/10.3389/fpubh.2023.1166120. -
Google Developers. (n.d.). 大型语言模型简介。
developers.google.com/machine-learning/resources/intro-llms?hl=en.
RAG
-
Jolley, E. (2023). 检索增强生成简介。
arize.com/blog-course/introduction-to-retrieval-augmented-generation. -
Needham, M. (2023). 用 Mark 学习数据。
github.com/mneedham/LearnDataWithMark/tree/main. -
———. (2023). 使用 OpenAI/GPT 和 Chroma 进行检索增强生成。
www.youtube.com/watch?v=Cim1lNXvCzY. -
Routu, V. (2023). 使用 OpenAI 和 LangChain 回答问题:利用检索增强生成(RAG)的潜力。
www.linkedin.com/pulse/transforming-question-answering-openai-langchain-harnessing-routu/. -
Schwaber-Cohen, R. (2023). 什么是向量数据库?。
www.pinecone.io/learn/vector-database.
为观众思考
-
Bettes, S. (2019). 技术和专业写作体裁。
open.library.okstate.edu/technicalandprofessionalwriting/chapter/chapter-2/. -
Emery, A. K. (2021). 为什么“了解您的受众”是糟糕的数据可视化建议——以及您应该做什么代替.
depictdatastudio.com/why-know-your-audience-is-terrible-dataviz-advice-what-to-do-instead/. -
QuantHub. (2023). 如何确定您的受众以进行有影响力的数据故事讲述.
www.quanthub.com/how-to-identify-your-audience-for-impactful-data-storytelling/. -
LinkedIn AI 社区. (n.d.). 您如何吸引和培养您的技术受众,建立信任和权威?
www.linkedin.com/advice/0/how-do-you-engage-nurture-your-technical-audience. -
无线局域网专业人士. (n.d.). 如何向技术受众进行展示.
wlanprofessionals.com/how-to-present-to-a-technical-audience/.
变换器
-
Koenigstein, N. (2024). 变换器实战. Manning 出版公司.
-
Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, Ł., and Polosukhin, I. (2017). “Attention Is All You Need.” Advances in Neural Information Processing Systems, 30.
第八章:从信息到知识:构建视觉背景
本章涵盖
-
情感:视觉背景的基础
-
颜色、大小和交互
-
使用 DALL-E 制作图像
-
文本和视觉背景的战略定位
-
实施示例
通常,看到有趣的事物会唤起一种情感,一系列触动人心的感受。当然,书面文字可以唤起情感,而视觉元素产生情感的力量可能更大。那么,为什么不利用情感来使观众更多地参与到我们的数据故事中来呢?在本章中,我们将回顾数据故事中视觉背景的基本概念,重点关注情感。我们将描述如何在数据故事中设置颜色、大小和交互性。接下来,我们将深入探讨如何使用 DALL-E 制作图像。然后,我们将关注如何在图表中放置背景。最后,我们将关注一个实施所述概念的实践案例研究。
8.1 情感:视觉背景的基础
我和家人几年前去过自然历史博物馆。那里有许多动物、考古发掘的重建、一些原始人,还有更多。参观结束时,孩子们如此兴奋,以至于他们谈论的只有这件事。几天后,我最小的孩子告诉我,他想要在他祖母的花园里建造一个考古发掘!
我们的故事也应该发生同样的事情:一段时间后,我们的观众仍然在谈论它们。它们应该是难忘的。实现这一目标的一种方法,同时直接涉及我们的观众,是通过唤起情感。情感是感知、解释和记忆视觉信息的关键。情感在原始数据和人类理解之间架起桥梁。视觉刺激引发情感反应,影响我们对所看到的感知和参与。柯林斯词典(mng.bz/9dZl)将情感定义为“一种感觉,如快乐、爱、恐惧、愤怒或仇恨,可能是由你所处的情境或你所与的人引起的。”
情感在视觉传播的有效性中起着至关重要的作用。视觉背景,包括颜色、形状和布局,可以在观众中唤起特定的情感反应。例如,像红色和橙色这样的暖色调可能会唤起兴奋或紧迫感,而像蓝色和绿色这样的冷色调则可以传达平静或信任。有意在视觉元素中使用这些情感线索可以显著影响观众对信息的感知和保留。
在他们的书中,《沟通以影响:如何激发观众的行动》,本和凯利·德克解释说,我们可以通过能量和情绪的双重维度来理解情感,这有助于根据情感的正负程度对各种情感状态进行分类,如图 8.1 所示(德克,2015)。

图 8.1 情绪分类的宏观类别
情绪在这些维度上可能有很大的差异,个体可能会以独特的方式体验和表达情绪,这受到他们个人和文化背景的影响。
考虑到能量轴(x 轴),我们有两种类型的情绪:
-
高能量情绪—这些情绪通常与增强的生理唤醒度相关联。它们通常涉及激活身体的“战斗或逃跑”反应。高能量情绪的例子包括兴奋、愤怒和恐惧。这些情绪的特点是心率加快、警觉性提高和紧迫感。高能量情绪通常通过生动的肢体语言、热情的手势和快速的言语表达出来。
-
低能量情绪—这些情绪涉及生理唤醒度的降低,以平静和放松为特征。低能量情绪的例子包括满足、宁静和悲伤。经历低能量情绪的人可能会表现出动作缓慢、举止安静和身体紧张度降低。低能量情绪常常导致沉思和内省。
考虑到情绪轴(y 轴),我们有两种类型的情绪:
-
高落情绪—这些情绪与积极的价值观相关联,唤起快乐、喜悦和积极情绪。这些情绪营造出乐观的氛围,并常常导致微笑、笑声和普遍的幸福感。例如,快乐、兴奋和爱。
-
低落情绪—这些情绪以负面价值为特征,导致悲伤、愤怒或焦虑等感受。这些情绪会营造出更加忧郁的氛围。低落情绪可能导致悲伤的表情、皱眉以及普遍的痛苦感。例如,悲伤、恐惧和愤怒。
我们可以通过情绪和能量组合来生成不同的情绪,如图 8.1 中的表情符号所示。
在选择颜色时,还应考虑使内容对残疾人士可访问和可理解。更多详情请参考网络内容可访问性指南(WCAG,wcag.com/)网站和 WebAIM(webaim.org/articles/contrast/)。
在数据故事中融入情绪并非仅仅是一种审美选择,而是一种传达知识的战略方法。通过将情绪与预期的信息相匹配,我们可以将观众被动接收信息转变为主动寻求知识和理解的过程。例如,高能量情绪可以立即吸引注意力并激发好奇心,促使观众更深入地探索视觉内容。相反,低能量情绪可以鼓励反思,使观众能够更深刻地思考内容。
激发情感最快最有效的方式是通过视觉。正如我的孩子们在自然历史博物馆被他们所见所吸引一样,我们的观众应该被我们在视觉表现中所见所吸引。通过视觉吸引观众注意力的方式有很多,例如图表颜色、大小、交互性和图像。有大量关于所有这些视觉类型的书籍和资源;因此,本书将只提供一个概述,重点关注如何在 Altair 中配置它们。更多细节,请参考参考文献。此外,本章将重点介绍如何使用生成式 AI,特别是 DALL-E,来生成具有特定情感的图像。让我们从颜色的一般概述及其在 Altair 中的实现方法开始。
8.2 颜色
颜色通过设定场景和基调来传达信息。它建立了数据与观众之间的情感联系。颜色由三个主要组成部分构成:
-
色调——这指的是主导颜色的波长。它确定了颜色家族或颜色名称(如红色、绿色或紫色)。
-
饱和度——这个组件描述了该颜色的强度。它定义了颜色的纯度。
-
亮度——这衡量了颜色看起来有多亮或多暗。
在前面的章节中,我们了解到每个数据故事都有一个主要角色和一个对手。在绘制图表时,选择一种主色调来突出主要角色,并在提及他们时始终使用它。为对手使用另一种颜色,但如果你想让对手退居背景,则可以使用灰色调。你可以使用以下调色板之一来选择颜色(请参阅电子书中的全彩图片):
- 顺序色调板使用单一颜色在多种渐变或饱和度中组织从高到低的定量数据。使用此调色板来显示进展而不是对比(图 8.2)。

图 8.2 顺序色调板
- 对比色调板通过在两端使用两种对比色调以及较浅的混合色调来突出显示中间范围,从而强调定量数据的范围。对比色调板显示了数据在谱上的位置,例如冷到热(图 8.3)。冷可能代表较低的定量数据值,通常用较冷的色调表示,而热可能指较高的值,通常用较暖的色调表示。

图 8.3 对比色调板
- 定性色调板 强调定性类别。这种调色板类型为每个概念使用不同的色调(图 8.4)。

图 8.4 定性色调板
您可以使用颜色来集中注意力和突出图表想要展示的重要信息。此外,您可以使用颜色来建立关联,例如棕色或绿色代表自然,黄色调代表阳光或蜜蜂。您甚至可以使用颜色来唤起情感,例如红色代表能量,蓝色代表和平。不要使用重叠的颜色来表示不同的概念或使用过多的颜色。
挑战
考虑第五章关于无家可归主题的案例研究。我们在图表中用绿色调着色条形,这应该能在观众中唤起希望。颜色的目的是展示我们的项目将有助于解决无家可归问题。如果我们使用另一种颜色,比如 #6F4E37(咖啡色),传达的信息可能没有使用绿色时那么强烈。
8.2.1 在 Altair 中设置颜色
在前面的章节中,我们看到了如何使用颜色通道在 Altair 中设置颜色。在本节中,我们将提供更多细节。以下列表展示了如何在 Altair 中使用颜色的快速提醒。
列表 8.1 在 Altair 中设置颜色
import pandas as pd
import altair as alt
df = pd.DataFrame({
'Category' : ['A', 'A', 'B', 'B', 'C', 'C', 'C'],
'Cost' : [12,45,64,23,45,78,72],
'Sales' : [34,56,45,34,44,23,23],
'Product' : ['P1','P2','P3','P4','P5','P6','P7']
})
chart = alt.Chart(df).mark_bar().encode(
x=alt.X('Product:N'),
y=alt.Y('Cost:Q'),
color=alt.Color('Category:N')
)
chart.save('chart.xhtml')
注意:使用 alt.Color() 通道来设置颜色。
颜色通道可以使用 scheme 参数作为输入接收特定的颜色调色板。有关支持的调色板,请参阅 Altair 文档(vega.github.io/vega/docs/schemes/)。此外,您可以使用比例参数来指定颜色的范围和域:
-
范围参数指定了刻度的输出范围。 例如,当将定量变量映射到颜色时,使用从蓝色到红色的颜色刻度。指定范围为一个颜色列表或 Altair 的内置颜色方案之一。
-
域参数指定了刻度的输入域。 例如,如果你有一个介于 0 到 100 之间的定量变量,将颜色刻度的域设置为
[0,100]。
8.2.2 练习:设置颜色
考虑第三章关于过去 50 年北美人口的案例研究。你可以在本书的 GitHub 仓库中找到这个案例研究的完整代码,位于 CaseStudies/population/population.ipynb。最终的数据故事比较了北美与世界其他地区的人口,并显示有 3670 万人差距。我们使用绿色调来表示北美趋势线。
现在,按照以下步骤进行:
-
将主图表(即显示与 1960 年差异的图表)的颜色更改为 #963232(一种红色调),同时保持下一步图表(即标题为北美人口增长策略的图表)中的绿色调。
-
将获得的结果与第三章中实现的图表进行比较。使用不同的颜色是否以任何方式改变了信息?你可能注意到对北美人口增长速度慢于世界其他地区的负面强调。然而,下一步仍然保持乐观。
一种可能的解决方案
获得相同结果的方法有很多。一种可能的解决方案是为每个图表局部设置颜色。例如,你可以为下一步的图表设置颜色。
列表 8.2 在下一步图表中设置颜色
color='#80C11E'
df_cta = pd.DataFrame({
'Strategy': ['Immigration Development', 'Enhance Family-Friendly Policies', 'Revitalize Rural Areas'],
'Population Increase': [20, 30, 15] # Sample population increase percentages
})
# Creating the stacked column chart
cta = alt.Chart(df_cta).mark_bar(color=color).encode(
x='Population Increase:Q',
y=alt.Y('Strategy:N', sort='-x', title=None),
tooltip=['Strategy', 'Population Increase'],
).properties(
title=alt.TitleParams(
text='Strategies for population growth in North America',
color=color)
)
注意:向图表添加局部颜色。使用这种方法,当你配置所有分层图表时,此图表将不会受到全局配置的影响。
这个例子展示了使用颜色传达情感的基本示例。更多细节,请参考关于该主题的特定书籍,例如章节末尾参考文献部分中的那些。现在你已经学会了使用颜色传达情感的基本概念,让我们继续下一步:使用大小。
8.3 尺寸
在创建图表时,请考虑大小,因为它可以显著影响观众对数据的感知。大小影响你的视觉表示的可读性以及整体影响和效果。选择图表的适当大小取决于多个因素,例如你将在哪里展示它,你想要展示什么类型的内容,以及谁将查看它。确定正确大小的最佳方法是实验;尝试不同的尺寸,直到找到一个看起来足够好的为止。
此外,选择较小或较大的图表大小会影响多少信息可以放入一个框架中而不会杂乱无章或挤压文本。它还影响读者是否可以轻松阅读所有数据点,而不会使眼睛疲劳或迷失在过多的元素中。你可以在图表中配置两种主要类型的尺寸:字体大小和图表大小。
8.3.1 字体大小
字体大小直接影响数据的可读性和可理解性。字体大小应设置为使读者容易理解并解释图表中呈现的信息。
在设置图表时,你可以为不同的图表组件指定不同的字体大小,如标题、轴标签或刻度标签。一个好的做法是在整个绘图过程中保持它们的一致性,这样它们看起来既视觉上吸引人又井然有序。
默认情况下,Altair 将字体大小设置为 12 px。然而,你可以调整字体大小,使你的图表更易于阅读或与风格相匹配。
要在 Altair 中更改字体大小,确定您想要修改的可视化部分。一旦确定,使用 Altair 的configure_*()方法以及axis、title、text或legend属性,具体取决于您想要修改的组件。然后,您可以传递如fontSize或labelFontSize等参数及其期望的值。
在增加字体大小时,不要过度,因为较大的字体可能会导致图表杂乱无章,难以阅读。另一方面,较小的字体可能会导致难以阅读的图表。因此,请始终选择一个合理的字体大小,以很好地补充你的图表,同时不会负面影响可读性。
8.3.2 图表大小
设置你的 Altair 图表大小时要考虑的最后一个方面是实际的图表大小。你可以通过在属性中调整宽度和高度参数来调整它。这两个参数都是以像素为单位的。
在确定合适的图表大小时,没有一种适合所有情况的解决方案。它将取决于你计划在哪里显示图表、它代表的数据类型以及你想要包含多少细节等因素。
然而,你可以遵循以下一般性指南:
-
如果你想要创建一个简单的图表用于电子邮件或演示文稿幻灯片,请使用较小的尺寸。
-
如果你想要创建一个复杂的图表,请使用较大的尺寸。
要在 Altair 中设置图表大小,请使用以下方法之一:
-
properties(width=my_width,height=my_height)用于单图 -
configure_view(width=my_width,height=my_height)用于单图或复合图表。
8.3.3 练习:设置大小
考虑第一章中实现的案例研究——增加与狗相关的网站上的广告活动 (mng.bz/KZaK):
-
仅将 x 轴标签的大小更改为 9,如图 8.5 所示。
-
将获得的结果与原始图表进行比较。图表是否更清晰?

图 8.5 第一章案例研究,9 的值字体大小增加
解决方案
以下列表显示了如何将 x 通道编码以仅增加一个轴标签的字体大小。
列表 8.3 设置标签大小
x=alt.X('x:Q',
scale=alt.Scale(domain=[0, 10]),
axis=alt.Axis(tickMinStep = 1,
grid=False,
title=None,
orient='bottom',
labelFontSize=alt.condition(alt.datum.value == 9,
alt.value(25),
alt.value(12)))
),
注意:使用 alt.condition() 方法根据条件设置标签大小。
现在你已经学会了如何在 Altair 中配置大小,让我们继续下一步:交互性。
8.4 交互
到目前为止,我们已经实现了静态图表,这些图表向观众呈现一个静态的故事。这种图表类型的优点在于你可以决定故事,并引导观众通过它。换句话说,你不允许观众自主探索数据。
向图表添加交互元素,如按钮、菜单和缩放选项,将静态图表转换为动态图表。在动态图表中,你构建动态数据故事,观众构建自己的故事。在动态图表中,你失去了对观众的掌控和拥有一个集中且精确信息的可能性。观众可以自由探索数据并提取他们的见解,可能导致其他行动呼吁。因此,在设计时,重要的是预先考虑你希望观众根据要传达的信息浏览图表的哪些部分。
当您希望观众动态探索数据或处理大量数据集时,请使用交互式图表。例如,在金融数据或教育环境中,交互式图表允许观众放大特定时间段或悬停在数据点上以获取详细信息。相反,您可能更喜欢静态图表,用于简单的演示或报告,其中数据不需要探索。例如,静态图表可能非常适合展示趋势或比较的印刷年度报告,不需要用户交互。此外,在需要信息直接且一致的情况下,静态图表可能更有效。
例如,考虑第三章中描述的场景的变体。图表包含一个下拉菜单,允许您选择要在图表中显示的参考国家。这种图表类型在数据探索阶段是可行的,但它无法用于构建数据故事,因为它没有焦点。相反,如果您专注于北美,并给观众留下与其他国家进行比较的可能性,您仍然在讲述关于北美人口的故事,同时给观众提供了与其他国家进行比较的自由。
主要有两种交互类型:
-
被动交互——这指的是允许观众在不改变图表状态的情况下与图表交互的功能,例如工具提示或悬停效果。
-
主动交互——这涉及到根据用户输入更改图表的状态。
在本节的剩余部分,我们将了解如何向 Altair 图表添加工具提示、滑块和下拉菜单。
8.4.1 工具提示
工具提示是一个当您悬停在某些图表元素上时出现的迷你弹出框,例如数据点或条形。要将工具提示添加到您的 Altair 图表中,指定您希望它出现的元素。在 Altair 中,您可以通过工具提示编码通道使用方括号[]来实现,如下所示。以下示例使用第三章中的人口数据集,关注一个完全不同的洞察:2018 年按大陆的人口增长。
列表 8.4 设置工具提示
import pandas as pd
import altair as alt
df = pd.read_csv('data/population.csv')
# [...]
df = df[df['Country Name'].isin(continents)]
color = '#80C11E'
chart = alt.Chart(df).mark_bar(
color=color
).encode(
y=alt.Y('Country Name:O', sort='-x', title=''),
x=alt.X('Population:Q'),
tooltip=['Country Name', 'Population']
).transform_filter(
alt.datum.Year == 2018
) #1
# [...]
chart.save('tooltip.xhtml')
1 创建可视化
注意:使用tooltip通道在 Altair 中设置工具提示。
您可以在本书的 GitHub 存储库下的 08/tooltip.py 中找到示例的完整代码。图 8.6 显示了悬停在条上时产生的工具提示快照。现在您已经学会了如何在 Altair 中生成工具提示,让我们继续学习下一个交互元素:滑块。

图 8.6 工具提示示例
8.4.2 滑块
滑块是一个图形元素,允许用户通过在轨道或条上移动视觉指示器来在指定范围内输入或调整值。要在 Altair 中构建滑块,请按照以下步骤操作:
-
创建一个
binding_range(),一个定义滑块可以设置的值范围的对象。 -
使用
selection_point()方法创建一个选择。选择是一个对象,它定义了滑块如何与图表交互。 -
使用
add_params()方法将选择添加到图表中。 -
可选地,将
transform_filter()方法应用到图表上,以仅显示选择。
列表 8.5 展示了滑块使用示例。示例仍然使用第三章中的人口数据集,关注按大洲的人口增长。你可以在本书的 GitHub 仓库中的 08/slider.py 文件下找到完整的代码。列表中只显示了实现滑块的代码。更多细节,请参阅 GitHub 仓库中的代码。
列表 8.5 设置滑块
import pandas as pd
import altair as alt
df = pd.read_csv('data/population.csv')
# [...]
color = '#80C11E'
slider = alt.binding_range(min=1960, max=2021,step=1)
select_year = alt.selection_point(name="Select", fields=['Year'],
bind=slider)
chart = alt.Chart(df).mark_bar(
color=color
).encode(
y=alt.Y('Country Name:O', sort='-x', title=''),
x=alt.X('Population:Q'),
).add_params(
select_year
).transform_filter(
select_year
)
# [...]
chart.save('slider.xhtml')
注意:在导入所需的库后,使用 binding_range() 函数为滑块定义一个最小值为 1960、最大值为 2021、步长为 1 的绑定范围。接下来,使用 selection_point() 函数创建一个名为 Select 的选择,并指定它应该绑定到的字段(在这种情况下,为 Year)使用滑块。之后,使用 add_params() 方法将 Select_year 选择添加到图表中,并使用 transform_filter() 方法根据从滑块中选择的年份过滤数据。
图 8.7 显示了结果的静态图表表示。现在你已经学会了如何在 Altair 中生成工具提示,让我们继续到下一个交互元素:下拉菜单。

图 8.7 滑块示例
8.4.3 下拉菜单
下拉菜单是一个图形元素,通常以一个小矩形框的形式出现,带有箭头或三角形图标,表示有更多选项或选择可用。当用户点击或悬停在下拉菜单上时,一个可选择的项或选项列表“下拉”或展开在菜单下方或上方。用户可以通过点击来从可用选项中选择。要在 Altair 中创建下拉菜单,请按照以下顺序步骤进行:
-
首先生成一个
binding_select()对象。此对象定义了分配给下拉菜单的值列表。 -
使用
selection_point()方法创建一个选择。此选择对象控制下拉菜单与图表之间的交互。 -
使用
add_params()方法将选择集成到图表中。 -
应用
transform_filter()方法以仅显示所选数据。
列表 8.6 展示了下拉菜单使用示例。示例使用第三章的人口数据集,关注按大洲的人口增长。下拉菜单允许你选择特定的洲。你可以在本书的 GitHub 仓库中的 08/dropdown.py 文件下找到完整的代码。以下列表只显示了实现滑块的代码。更多细节,请参阅 GitHub 仓库中的代码。
列表 8.6 设置下拉菜单
import pandas as pd
import altair as alt
df = pd.read_csv('data/population.csv')
# [...]
color = '#80C11E'
input_dropdown = alt.binding_select(options=df['Country Name'].unique())
select_country = alt.selection_point(name='Select',fields=['Country Name'], bind=input_dropdown, value=[{'Country Name': 'Africa Eastern and Southern'}])
# Create visualization
chart = alt.Chart(df).mark_line(
color=color
).encode(
y=alt.Y('Population:Q', title=''),
x=alt.X('Year:O'),
).add_params(
select_country
).transform_filter(
select_country
)
# [...]
chart.save('dropdown.xhtml')
注意:在导入所需的库后,使用binding_select()函数定义下拉菜单的绑定选择。接下来,按照你为滑块所做的方式继续操作。
图 8.8 显示了结果的静态表示。

图 8.8 下拉菜单的示例
现在,是时候练习了。所以让我们继续进行一个练习。
8.4.4 练习:设置交互性
考虑第三章中实现的案例研究:过去 50 年北美的人口。您可以在本书的 GitHub 仓库中找到此案例研究的完整代码,位于 CaseStudies/population/population.ipynb:
-
删除“其他”行,并添加一个动态行,显示可从下拉菜单中选择的大陆,如图 8.9 所示。保持北美行静态。
-
将获得的结果与原始图表进行比较。这种动态图表最适合哪种类型的听众?
-
可选地,将所有图表元素适应到动画中。

图 8.9 第三章的案例研究,包含一个用于与北美比较的动态下拉菜单
你可以在本书的 GitHub 仓库中找到练习的解决方案,位于 08/population-exercise.py。现在你已经学会了如何在 Altair 中通过工具提示、滑块和下拉菜单引入交互性,让我们转向下一个元素,图像,专注于 DALL-E。
8.5 使用 DALL-E 生成图片
想象阅读一段描述以下场景的文字:一位有着黑色头发和深色眼睛的美丽小女孩,穿着黄色连衣裙,在满是黄色花朵的绿色草地上奔跑。你可以发挥你的创造力在脑海中设定这个场景,但这可能需要你大脑的一点点努力。现在,看看图 8.10(使用 DALL-E 实现),它以视觉方式展示了相同的场景。你看到了我们之前描述的内容,但肯定,你在脑海中描绘的场景是不同的。

图 8.10 一位穿着黄色连衣裙的小女孩的图片
当你描述你的数据故事主题时,也会出现相同的情况。如果你不给你的主题赋予一个面孔,只提供一般性的描述,你的听众可以根据他们的背景自由想象主题的各种方式。相反,如果你在你的数据故事中添加一个或多个图片,你将帮助听众对故事的主题有一个清晰的印象。通过图片,尤其是照片,你将把听众带到预期的情感水平,他们将会更愿意倾听你的数据故事。在数据故事中添加图片加强了上下文,并使听众为接受数据故事信息做好准备。
在第四章中,你学习了如何在 DALL-E 中绘制图像并将其插入到图表中。在本节中,你将看到如何使用 DALL-E 生成的图像来唤起情感。我们还将探讨如何保持我们在 DALL-E 中生成的图像的一致性。让我们从情感开始。
8.5.1 添加情感
DALL-E 提示词书建议一些关键词,以向图像添加情感。图 8.1 显示了与每个情感类别相关的一些可能的形容词:
-
高能量,积极情绪—明亮,充满活力,动态,精神饱满
-
高能量,消极情绪—黑暗,威胁,幽灵般,预兆
-
低能量,积极情绪—轻柔,平和,宁静,安详
-
低能量,消极情绪—柔和,阴郁,葬礼,忧郁
您可以在 DALL-E 提示词书的完整关键词列表中阅读(https:// mng.bz/0GNx)。
使用情感关键词可以使你获得不同的结果。图 8.11 显示了 DALL-E 对以下输入的可能输出:一位在花园中玩耍的宁静女士,照片。

图 8.11 一位在花园中宁静的女士的图像
图 8.12 显示了一个完全不同的结果,仅通过将关键词calm改为muted生成。

图 8.12 一位在花园中柔和的女士的图像
将此类图像融入数据故事有助于观众给你的主要角色赋予一个面孔。你的故事主角可以通过他们的情感和表现力直接与观众沟通。现在你已经学会了如何向 DALL-E 生成的图像添加情感,让我们继续下一步:生成一致图像。
8.5.2 生成一致图像
一致图像是一组具有共同特征的图像,例如颜色、风格等。当你想在数据故事中添加多于一张图像时,构建一致图像非常重要。在撰写本书时,DALL-E 没有提供任何明确的方式来构建一致策略。然而,在本节中,你将看到 Tyler Taggart 在他的 YouTube 视频中(Taggart,2023)提出的构建 DALL-E 中一致图像的小技巧。为了做到这一点,我们将使用 DALL-E 提供的编辑工具,该工具在第四章中已介绍。
为了展示所提出策略的工作原理,我们将重新绘制第四章案例研究中与划船和自行车相关的图像:解锁潜力:投资划船和自行车以获得最大回报!想法是在数据故事中插入定制给高管的真实照片,以更好地吸引他们的注意力。
首先,在 DALL-E 中写下以下提示词:一张展示划船运动员努力的图片。展示汗水与肌肉。运动员穿着绿色夹克。图 8.13 显示了 DALL-E 可能生成的输出。

图 8.13 DALL-E 可能生成的输出
在四个可用的照片中选择一张,然后点击编辑。现在,点击添加生成框架,并将其插入到图像附近,如图 8.14 所示。

图 8.14 生成框架放置位置
现在,请按照以下方式编辑你的提示:一张展示自行车运动员努力的照片。展示汗水与肌肉。运动员穿着绿色夹克。你只是将单词划船改为骑自行车。DALL-E 将生成四张图片。选择最适合你要求的一张,或者生成新的图片。图 8.15 显示了可能的输出。照片的两个部分(左部和右部)非常相似。例如,它们使用了相同的绿色色调。

图 8.15 DALL-E 生成的输出
如你所见,两张照片相互关联。结果看起来有点奇怪,但你可以通过下载并使用任何图像编辑工具将它们分开(生成两张看起来正常的照片)。
现在你已经获得了两张表现最好的运动项目的照片,你可以将它们替换为第四章案例研究中的图表,如图 8.16 所示。我们还添加了通过微调产生的副标题。故事中的照片突出了运动员为了打破记录所付出的努力,从而邀请高管继续为他们提供资金。

图 8.16 定制给高管观众的最终图表
现在,是时候练习了。让我们继续进行一个练习。
8.5.3 练习:生成图像
考虑第一章中的案例研究:增加与狗相关的网站上的广告活动:
-
为一只狗和一只猫生成两张一致的图片,并用生成的图片替换图表中的原始图片。
-
为一只狗(或一只猫)生成一张具有特定情绪(例如愤怒、平静、放松)的新图片。DALL-E 能否为动物生成情绪图片?
我们刚刚完成了使用 DALL-E 为数据故事生成图片的旅程。在本章的最后一步,我们将考虑在数据故事中定位你的图表的技巧。
8.6 战略性地放置上下文
在你的数据故事的主要观点之前,应该有上下文。因此,我建议你在你的数据可视化图表的以下三个主要位置之一放置你的上下文:顶部、左侧或图表内部。在本节的剩余部分,我们将分别研究每个上下文位置。作为使用每个上下文位置的例子,我们将关注第一章至第五章中描述的案例研究。为了方便,图 8.17 总结了分析的案例研究。

图 8.17 总结了第一章至第五章中描述的所有示例。
8.6.1 顶部放置
在图表顶部添加上下文意味着在标题下方立即添加文本描述,如图 8.18 所示。

图 8.18 图表顶部的上下文位置
使用图表的顶部来描述主题或数据源的概述。您还可以使用图表的顶部部分添加数据源。添加数据源意味着承认图表中使用的信源。这为呈现的信息提供了可信度。当观众看到我们已经正确地注明了数据源时,他们更有可能信任所呈现的信息。
考虑图 8.17 中的案例研究。表 8.1 显示了使用顶级上下文和上下文类型的案例研究。
表 8.1 使用顶部上下文位置的案例研究
| 名称 | 顶部上下文类型 | 文字 |
|---|---|---|
| 第三章: 过去 50 年北美的人口 | 数据源 | 来源:世界银行 |
| 第五章—示例 1: 一起,让我们做出改变:支持我们的项目,帮助无家可归者 | 文本描述 | 无家可归是一个令人心碎的现实,它使个人和家庭失去了稳定的家园,导致严重的后果,如健康状况恶化和社会孤立。 |
| 第五章—示例 2: 你的真理守护者:在物质和道德叙述中对抗假新闻 | 文本描述 | 最新新闻网站是一个流行的新闻和信息来源,但也是一个假新闻的来源。 |
8.6.2 左侧放置
在图表左侧添加上下文涉及在标题之前立即添加一个或多个注释,如图 8.19 所示。

图 8.19 图表左侧的上下文位置
使用图表的左侧部分添加初步图表或更深入的细节,例如文本描述、图像或照片,这些可能会影响观众的感受。再次考虑图 8.17 中的案例研究。表 8.2 显示了使用左侧上下文和显示所使用的上下文类型的案例研究。
表 8.2 使用左侧上下文位置的案例研究
| 名称 | 左侧上下文类型 | 文字 |
|---|---|---|
| 第一章: 在与狗相关的网站上增加广告活动 | 文本描述 | 在 150 个广告网站上,猫的参与率几乎是 8%,而在 30 个广告网站上,狗的参与率几乎是 5%。 |
| 第二章: 是的,你可以建造一个新的游泳池! | 次级图表文本描述
| 多亏了低成本航班的引入,葡萄牙在 25 年内游客到访量增长了超过 200%,甚至超过了其他国家。 |
|---|
| 第三章: 过去 50 年北美的人口 | 文本描述 | 为什么会有这个差距?1. 生育率下降 |
-
下降的移民率
-
较高的平均年龄
|
| 第五章—示例 1: 一起,让我们做出改变:支持我们的项目,帮助无家可归者 | 图片 | – |
|---|
与顶部位置所写的相比,左侧位置的文字包含额外的细节,有时是从其他来源提取的。
8.6.3 内部放置
图表中的上下文通过图表注释突出了某些有趣的观点或部分,如图 8.20 所示。

图 8.20 图表中的上下文位置
注释有助于观众关注图表的一些特定部分。注释的例子包括文本、圆圈、基线、图像和箭头。在使用注释时,确保它们不过度且不与图表本身重叠,以免干扰阅读。
再次考虑图 8.17 中的案例研究。表 8.3 显示了使用左侧上下文和上下文类型的案例研究。
表 8.3 使用上下文位置的案例研究
| 名称 | 上下文类型 | 文本 |
|---|---|---|
| 第一章:增加与狗相关的网站上的广告活动 | 图像 | – |
| 第三章:过去 50 年北美的人口 | 文本垂直线
| 367M |
|---|
| 第四章:释放潜力:投资划船和自行车以获得最大回报 | 文本基线
图像
| 使用 50%作为运动选择的标准。 |
|---|
| 第五章—示例 2:你的真理守护者:在物质和道德叙述中对抗假新闻 | 图像文本
| 物质生活 道德生活
精神生活
|
与位于顶部和左侧位置所写的文本相比,注释中的文本更加简洁,突出了数据细节。现在你已经学会了如何为你的数据故事添加视觉背景,让我们来实施一个实际案例研究。
8.7 案例研究:从信息到知识(第二部分)
在上一章中,我们分析了如何通过水产养殖案例研究中的文本上下文将信息转化为知识。只是为了快速提醒,这个案例研究涉及围绕美国鲑鱼水产养殖中的安全问题构建故事。我们决定绘制鲑鱼水产养殖销售趋势线与其他类型水产养殖的趋势线。作为一个洞见,我们发现自 1998 年以来,销售有所增加,这继 1992 年至 1998 年销售下降之后。我们发现下降期部分是由于鲑鱼水产养殖中的一些健康问题。图 8.21 显示了添加文本上下文将信息转化为知识时产生的图表。

图 8.21 添加文本上下文时产生的图表
下一步是向故事中添加视觉背景。我们将遵循两种不同的场景:
-
传达消极情绪,重点关注消极年份(1992-1998)
-
传达积极的情绪,重点关注趋势增加的年份(从 1998 年开始)。
在这两种情况下,我们将生成图像,设置颜色,并选择与特定情绪相关的尺寸。让我们从第一点开始:传达消极情绪。
8.7.1 设置消极情绪
在这个场景中,重点是负面的年份。目的是展示这个时期的恐怖,以在观众中提高确保它永远不会再次发生的紧迫感。我们将生成令人震惊的图片并使用深色。
图片
生成关于鲑鱼的令人震惊的图片(高能量,负面氛围)。让我们使用以下提示的 DALL-E:一张死鲑鱼的疾病震惊照片。图 8.22 显示了 DALL-E 生成的图像的可能输出。

图 8.22 DALL-E 根据以下输入产生的输出:一张死鲑鱼的疾病震惊照片
让我们选择照片编号 2 并将其添加到图表中。列表 8.7 显示了 Copilot 生成 Altair 图像的指令。
列表 8.7 Copilot 的指令
# build a DataFrame named img_df with the following columns:
# - 'url' with the values '../images/deadsalmon.png'
# - 'x' with the value 1993
# - 'x2' with the value 1997
# - 'y' with the value 0 #1
# draw a image with the img_df DataFrame:
# - set x to 'x'
# - set y to 'y'
# - set x2 to 'x2'
1 1 是一个象征性的数字。
注意:首先,指示 Copilot 生成包含所有相关通道的 DataFrame。然后,指定如何使用 Altair 绘制图像。我们已经要求 Copilot 将y设置为 0。这是一个初始值。你可以通过查看图像手动调整它。
你可以在 GitHub 仓库中找到这本书的完整代码,在 CaseStudies/from-information-to-knowledge-visual/chart-negative.py 下。
颜色和大小
除了照片,我们还可以使用深色来强调负面情景。例如,我们可以使用深红色调(#460805),这暗示了血液。接下来,我们可以根据 1992-1998 年的年份增加笔触宽度。我们将为这些年份生成另一条线,与原始图表重叠。以下列表显示了生成该线的代码。
列表 8.8 添加一个专注的线条
base = alt.Chart(df
).encode(...)
chart = base.mark_line()
chart_line = base.mark_line(
strokeWidth=8
).transform_filter(
(alt.datum.CATEGORY == 'Salmon') &
(alt.datum.YEAR_ID >= 1992) &
(alt.datum.YEAR_ID <= 1998)
)
注意:首先,构建一个包含所有编码选项的基础图表。在绘制主要图表之后,绘制另一个专注于选择的图表。使用transform_filter()方法仅选择一些数据。
再次,你可以在 GitHub 仓库中找到这本书的完整代码,在 CaseStudies/from-information-to-knowledge-visual/chart-negative.py 下。图 8.23 显示了生成的图表。

图 8.23 添加负面视觉背景后的图表
现在我们已经实现了负面氛围的场景,让我们转向相反的场景:积极的氛围。
8.7.2 设置积极的氛围
在这个场景中,重点是正趋势线,始于 1998 年。目标是鼓励观众为了鲑鱼种群继续采用当前策略。在这里,我们不是关注负面影响,而是关注积极情景,仅将负面方面作为可能性提及。我们将使用放松的图片和颜色。
图片
生成放松图像的程序与负面场景类似,不同之处在于使用落在图 8.1 顶部左边的时钟面上的文字(低能量,积极情绪)。以下是对 DALL-E 生成所需图像的提示:一个宁静的、安全的鲑鱼养殖场。图 8.24 显示了可能的输出。让我们选择照片编号 1。

图 8.24 DALL-E 根据以下输入生成的输出:一个宁静的、安全的鲑鱼养殖场
颜色和大小
使用和平的颜色(例如,#105473)来表示鲑鱼线。此外,突出显示自 1998 年以来的趋势线。图 8.25 显示了生成的图表。你可以在 GitHub 仓库中找到该书的完整代码,位于 CaseStudies/from-information-to-knowledge-visual/chart-positive.py。

图 8.25 添加积极视觉背景后的图表
我们已经生成了两种类型的图表——负面和正面——但不是同时(比如,在同一张图表中包含两张图片)。这是因为我们不希望出现相互冲突的情感提示,这可能会使观众困惑或削弱我们旨在传达的意图。现在你已经学会了如何向数据故事添加积极或消极的情绪,让我们继续进行一个额外的练习:添加交互性。
8.7.3 练习:使图表交互式
修改之前的图表以使其交互式,如下所示:定义一个下拉菜单,包含两个值:正面和负面。当用户选择负面时,图表突出显示 1992 年至 1998 年之间的鲑鱼趋势线。当用户选择正面时,图表突出显示自 1998 年以来的趋势线。你可以在 GitHub 仓库中找到该书的练习解决方案,位于 CaseStudies/from-information-to-knowledge-visual/chart-dropdown.py。
在本章的第一部分,你学习了如何通过向你的数据可视化图表添加视觉背景将信息转化为知识。你看到了如何通过颜色、大小和交互性向图表添加情感。你还学习了如何在 DALL-E 中生成基于情绪且一致的图像。接下来,你学习了在哪里放置图表中的背景。最后,你实施了一个实际案例研究。在下一章中,你将看到如何通过添加行动号召和可能的下一步到图表中,将知识转化为智慧。
摘要
-
在你的数据可视化中添加视觉背景对于提升观众的情感至关重要。
-
结合颜色、大小和交互性可以帮助你更好地聚焦你的故事,并针对你的观众进行调整。
-
当你在 DALL-E 中绘制图像时,请使用编辑工具保持一致性。
-
考虑在图表中战略性地定位背景以增强其效果。
参考文献
使用情感进行沟通
-
Decker, B. 和 Decker, K. (2015). 通过沟通影响他人:如何激发听众采取行动. McGraw Hill.
-
Abbott, D. (2023). 日常数据可视化. Manning Publications.
-
Christiansen, J. (2022). Building Science Graphics: An Illustrated Guide to Communicating Science through Diagrams and Visualizations. A K Peters/CRC Press.
-
Strachnyi, K. (2022). ColorWise: A Data Storyteller’s Guide to the Intentional Use of Color. O’Reilly Media.
图片
-
QuantHub. (2023). Photos and Illustrations in a Visual Narrative.
www.quanthub.com/photos-and-illustrations-in-a-visual-narrative/. -
The DALL·E 2 Prompt Book. (n.d.).
dallery.gallery/wp-content/uploads/2022/07/The-DALL%C2%B7E-2-prompt-book-v1.02.pdf. -
Gemignani, Z. (2021). Tips for Using Photos in Data Storytelling.
www.juiceanalytics.com/writing/tips-for-using-photos-in-data-stories. -
Hang, I. (2023). How to Create Consistent Characters in Midjourney: EASY Step by Step AI Tutorial for Amazon KDP.
www.youtube.com/watch?v=zdSIPkbvsek. -
Nussbaumer Knaflic, C. (2018). Using Images.
www.storytellingwithdata.com/blog/2018/3/26/using-images. -
Taggart, T. (2023). DALL-E 2 Tutorial: How to Get Image Consistency.
www.youtube.com/watch?v=MU_yXYsfBR0.
第九章:从知识到智慧:添加下一步
本章涵盖
-
介绍智慧和下一步
-
回顾所有案例研究
-
下一步的战略放置
如果你正在阅读本章,你几乎已经爬到了 DIKW 金字塔的顶部,并建立了一个精彩的图表。你应该对你的工作感到满意,可能已经向同事甚至朋友和亲戚展示了它。然而,我必须告诉你,你的工作还不完整。最后一块缺失了,那就是将你的图表变成真实故事的那一块:智慧。智慧是非常深刻的东西,不能简单地从数据中提取出来。智慧也来自个人的文化背景、经验和良好的判断力。在本章中,我们不会对智慧进行哲学上的探讨。然而,我们将考虑智慧作为在观众与我们的故事互动后提出下一步行动的能力。我们还将利用生成式 AI 作为文化背景和共享经验来帮助我们定义下一步。
在本章中,我们将从智慧及其下一步的基本概念开始(在数据叙事的 DIKW 金字塔的顶部)。接下来,我们将根据定义的智慧概念回顾本书中描述的所有案例研究。最后,我们将描述如何在故事中战略性地放置下一步。
9.1 介绍智慧
《柯林斯词典》将“智慧”定义为“运用经验和知识做出明智决策或判断的能力。”应用于数据叙事,智慧使我们能够根据我们的数据做出更好的决策。智慧帮助我们继续在故事结束后继续讲述。
智慧不仅仅是知识转换的结果。它还需要其他依赖于我们生活经历方面的因素。智慧是几个不同成分的结果:
-
知识 — 这为智慧构建的基础。没有对我们数据的广泛理解,我们无法做出明智的决策。
-
经验 — 这为我们应用知识提供了背景。它加深了我们对于世界的理解,并帮助我们看到书籍或讲座中无法学到的细微差别和复杂性。经验包括我们经历过的所有时刻、我们面临的挑战以及我们学到的教训。
-
良好的判断力 — 这是权衡情况利弊、分析风险和收益,并做出明智决策的能力。它需要从多个角度评估情况并理解每个选择可能产生的潜在结果的能力。
图 9.1 展示了如何修改 DIKW 金字塔以正确地包含智慧。

图 9.1 增加了智慧成分的 DIKW 金字塔
总结来说,我们可以这样说,智慧由三个主要元素组成:知识综合、经验和良好的判断。让我们分别调查这三个元素,从知识综合开始。
9.1.1 将知识转化为智慧:下一步骤
每个值得尊重的故事都包含一个信息,这是在阅读故事后留在观众心中的东西。这是将智慧添加到故事中的体现。智慧使观众能够被故事所吸引,被打动,并在阅读故事后准备好采取行动。然而,信息本身是不够的。观众在阅读故事后必须采取行动。
将知识转化为智慧涉及将下一步骤添加到数据故事中。下一步骤是我们数据故事的解决方案,并定义听众在阅读故事后应该做什么。
每个故事都以一个结局结束,无论是好是坏,但必须有一个。即使在数据讲述故事的情况下,故事的结尾也被设想为开放式的。与小说不同,小说的结尾结束了故事,数据讲述故事应该是一个开放的故事,即在故事本身结束之后,还应该期待听众在阅读故事后有所行动。
为了更好地理解问题,让我们考虑一个例子。想象你开始阅读一本美丽的小说。当你读到主角的冒险时,突然,小说结束了,没有揭示主角是否克服了挑战或解决了问题。好吧,如果你只将你的图表限制在知识阶段,就像一本没有结尾的小说。你不应该只是展示你的数据发现;你应该告诉观众你认为他们应该如何根据这些发现采取行动。有句众所周知的话说,“知识不是力量;当用于行动时,它是潜在的力量。”那么,在你的图表中,你必须添加我们所说的下一步骤。这些作为行动呼吁被实施,邀请你的听众采取行动。
下一步骤并非来自天外,或者更糟糕的是,并非理论上的东西。它们是你研究和分析的结果。它们向你的听众提出在看到你的图表之后应该做什么——例如,关于可能的替代方案或前进的具体步骤。
下一步骤绝不能仅仅是通过数据获得的知识果实。它们必须与更广泛的东西挂钩,通常被称为智慧。智慧是从经验(从分析的数据中)和与他人分享的知识中得来的文化宝藏。
根据你生活的环境、观众类型和你的价值观,下一步必须基于一个道德框架。你不能仅仅基于你自己的信念或经验来提出一个通用的下一步方案。如果它们要得到广泛的应用,就必须将下一步方案锚定在你所生活的社会的价值观上。这些价值观因社区而异,但必须尊重基本的原则,并且下一步方案必须基于这些原则。
表 9.1 展示了最受欢迎的下一步分类。我们为每个分类提供了一个简短的描述、目的和示例。
表 9.1 最受欢迎的下一步分类
| 类别 | 描述 | 目的 | 示例 |
|---|---|---|---|
| 寻求支持 | 请求观众以某种方式支持故事。 | 利用观众的专业技能来解决故事中突出的问题。 | 邀请观众参与调查的文本 |
| 提供不同选项 | 为观众提供可能的替代方案。 | 帮助观众做出决策。 | 可能的替代下一步行动列表:A,B,C |
| 自由互动 | 允许观众自由与故事互动。 | 让观众分析数据并得出结论。 | 一个交互式图表 |
| 深入了解 | 鼓励观众深入探讨数据故事中呈现的主题或见解。 | 指导观众访问额外的资源、文章、研究或参考文献,以获得更全面的理解。 | 深度分析报告的链接 |
| 提出计划 | 提出一个概述将要采取的行动顺序的计划。 | 让观众在故事结束后继续工作。 | 可能的顺序下一步行动列表 |
| 分享 | 鼓励观众分享数据故事,例如,在他们的社交网络上。 | 利用观众的社交网络,促进讨论,并提高可见性,以扩大数据故事的影响力。 | 使用社交媒体按钮分享故事 |
现在,你已经学习了不同的下一步分类。接下来,让我们看看定义智慧、经验以及我们如何使用 ChatGPT 来帮助我们生成它的第二个要素。
9.1.2 将 ChatGPT 作为经验来源
传统上,经验来源于我们个人的背景,这有一定的局限性,特别是如果我们在一个特定领域没有太多经验的话。除了我们的个人经验外,我们可以使用生成式 AI 的知识来构建“集体背景”,以生成智慧。到目前为止,我们只使用生成式 AI 来生成内容。现在,是时候将其用作助手、队友,帮助我们思考和阐述概念了。在使用生成式 AI 的这种方式时,要小心它可能引发的一些伦理问题和其它问题,我们将在下一章讨论。在实践中,我们将使用 ChatGPT 作为经验来源。除了使用我们个人的经验来提出下一步行动外,我们还将使用 ChatGPT 作为另一个经验来源。
更具体地说,在本章中,我们将以两种方式使用生成式 AI:
-
作为内容生成者,正如我们在本书中已经做的那样
-
作为一名助手,帮助我们思考,将知识转化为智慧
在本节中,我们将只关注第 2 点,因为你在上一章已经学习了如何使用生成式 AI 来生成内容。特别是,我们将专注于 ChatGPT。
想象 ChatGPT 是你的个人思考助手。我们将使用 ChatGPT 作为助手,特别是它通过交互式聊天进行互动的能力,其中它记得之前的对话。我们可以将 ChatGPT 用作一个自然的对话者,帮助我们思考和制定策略。在本章中,我们只将使用 ChatGPT 来提出下一步行动,但一般来说,你可以使用 ChatGPT 的所有文化背景来生成新想法。为了使 ChatGPT 帮助我们解决问题,我们需要制定一个提示,以便 ChatGPT 能够理解它。
制定一个由三个部分组成的提示:
-
对场景的定义
-
一个问题
-
生成多个可能的答案
我们实际上可以将第 2 点和第 3 点合并为一个单独的点。在这个初始提示之后,我们可以与 ChatGPT 进行讨论,并要求它深化一个或多个生成的答案。
我们将在本章后面看到这个提示结构的许多应用。目前,记住这一点就足够了:场景、问题、答案数量。
一旦我们从 ChatGPT 中提取出一个想法,我们就可以通过查看额外的外部材料来进一步研究这个主题。ChatGPT 只是第一步。它作为想法的来源。接下来,我们需要通过咨询外部材料和来源来验证 ChatGPT 建议的可行性。现在,你已经从概念上学习了如何将 ChatGPT 的文化背景作为经验来源,接下来让我们进入下一步:良好的判断力。
9.1.3 良好的判断力:将行动锚定在伦理框架中
良好判断 是准确评估情况并做出符合个人价值观和目标的决定。在本章中,我们将通过将我们的信息锚定在伦理框架上来实施良好判断。伦理框架作为基于指导道德行为的原理的决策指南。它提供了一个框架来评估我们行为的潜在后果,并帮助我们根据正确和公正来确定最佳行动方案。
缺乏伦理考虑的行为可能导致意想不到的后果,甚至可能造成伤害。以下是一些最受欢迎的伦理框架(Berengueres,2019):
-
功利主义 — 这个框架基于最大化效用或幸福来做出伦理决策。根据这种观点,如果一个行为能带来最大多数人的最大幸福,那么这个行为就是正确的。
-
义务论 — 这个框架侧重于遵循道德规则或义务。它认为某些行为本身是善的或恶的,无论其后果如何。
-
美德伦理学 — 这个框架强调培养诸如勇气、诚实和同情等美德的重要性。它认为,善良的人比遵循规则或最大化幸福更重要。
-
关怀伦理学 — 这个框架强调关系和同理心的重要性。它基于对他人关怀和回应他们需求的伦理决策。
无论选择哪种伦理框架,如果你希望确保你的行动呼吁不被误解或滥用,下一步必须基于其中之一。这意味着考虑我们的行为对他人可能产生的影响,并确保它们与我们的价值观和原则一致。通过这样做,我们可以超越仅仅基于便利性做出决策,转向基于伦理考虑创造积极变化。
为了将下一步行动适应到特定的伦理框架中,我们可以实施以下两个阶段:
-
使用 ChatGPT 生成初步文本
-
修订产生的内容
在接下来的几节中,我们将看到如何实施这两个阶段。现在,你已经学习了与智慧三个组成部分(知识综合、经验和良好判断)相关的主要概念,让我们将这些概念应用到本书中实施的所有案例研究中。
9.2 案例研究
在本节中,我们将根据更具体的下一步行动,回顾本书中实施的所有案例研究。作为快速提醒,图 9.2 总结了前几章中实施的案例研究。让我们从第一个案例研究,即狗和猫活动开始。

图 9.2 前几章中描述的所有例子的总结
9.2.1 第一章:狗和猫活动
考虑我们在第一章中分析的案例研究,狗和猫活动。作为复习,该案例研究专注于一个针对宠物的活动(狗和猫)。对于每个宠物类别,组织者战略性地在该特定宠物类别的专业网站上宣传活动。结果,参加活动的 10 只宠物中有 9 只是猫。下一步包括改善与狗网站相关的活动,以增加狗参与者的数量。图 9.3 显示了与该案例研究相关的最终数据故事。

图 9.3 与狗和猫案例研究相关的最终数据故事
第一章中描述的案例研究包含一个通用的下一步,邀请观众增加与狗相关的网站活动。让我们实施一个更具体的下一步,使用共享类别。为了获得一些实际实施的思路,让我们询问 ChatGPT 如何实际改进这些共享机会。让我们制定以下提示:考虑以下场景:一个案例研究专注于一个针对宠物的活动(狗和猫)。对于每个宠物类别,组织者战略性地在该特定宠物类别的专业网站上宣传活动。结果,参加活动的 10 只宠物中有 9 只是猫。提出 5 种邀请观众分享与狗网站相关的活动信息的方法,以增加狗参与者的数量。 ChatGPT 将列出五个共享选项。以下是 ChatGPT 生成的一个可能的选项列表:
-
互动竞赛或挑战
-
互动社交媒体活动
-
博客推广和合作
-
定制的电子邮件活动
-
社区参与和论坛
在这里,我们只报告了选项列表。您可以在 ChatGPT 网站上找到完整的答案(ChatGPT.com/share/75475a1d-7dab-4487-858d-8288f7408737)。我们可以对前面的选项进行阐述,例如,将它们重新制定为三个选项:在线活动、影响者参与和社交媒体推广。
让我们实施前面的步骤。我们将实现共享选项,作为通过线条连接的三个矩形,如图 9.4 所示。

图 9.4 狗和猫案例研究可视化路线图
实现的代码可在本书的 GitHub 仓库中找到,位于 09/awareness-and-campaigns/pets-visualized-roadmap.py。我们将只关注下一步(图表的底部部分)。首先定义矩形的大小。我们使用参数值使代码尽可能通用。以下列表显示了生成位置为 0、15 和 30 的三个矩形的代码。
列表 9.1 设置矩形的定位
import pandas as pd
width = 10
space = 5
N = 3
x = [i*(width+space) for i in range(N)]
y = [0 for i in range(N)]
x2 = [(i+1)*width+i*space for i in range(N)]
y2 = [10 for i in range(N)]
text = ['Online Campaign', 'Influencers Engagement', 'Social Media Promotion']
df_rect = pd.DataFrame(
{ 'x': x,
'y': y,
'x2': x2,
'y2': y2,
'text' : text
}
)
注意:我们假设我们想要生成N(例如 3)个矩形,并且每个矩形都有一个大的width(10)和两个矩形之间的空间space(5)。然后,我们使用x、x2、y和y2作为顶点生成矩形坐标。我们还为每个矩形包含标签文本。最后,我们创建一个包含定义变量的 DataFrame。
接下来,在 Altair 中绘制矩形。
列表 9.2 绘制矩形
import altair as alt
rect = alt.Chart(df_rect).mark_rect(
color='#80C11E',
opacity=0.2
).encode(
x=alt.X('x:Q', axis=None),
y=alt.Y('y:Q', axis=None),
x2='x2:Q',
y2='y2:Q'
).properties(
width=700,
height=100,
title=alt.TitleParams(
text=['What can we do next?'],
fontSize=20,
offset=10
)
)
注意:使用mark_rect()标记绘制矩形。使用x、y、x2和y2通道并移除坐标轴(axis=None)。最后,设置图表大小和标题。使用alt.TitleParams()指定标题字体大小(fontSize)和标题与图表的偏移量。
现在,我们准备在矩形中绘制标签,如下所示。
列表 9.3 绘制标签
text = alt.Chart(df_rect).mark_text(
fontSize=14,
align='left',
dx=10,
).encode(
text='text:N',
x=alt.X('x:Q', axis=None),
y=alt.Y('y_half:Q', axis=None),
).transform_calculate(
y_half='datum.y2/2'
)
注意:使用mark_text()标记绘制标签。使用transform_calculate()方法设置每个标签的 y 坐标。
下一步涉及定义线条坐标。我们必须绘制N-1条线,第一条从位置 10 开始,到位置 15 结束,第二条从位置 25 开始,到位置 30 结束。使用公式来计算这些位置。
列表 9.4 定义线条坐标
x = [width*i+space*(i-1) for i in range(1,N)]
y = [5 for i in range(N-1)]
y2 = [5 for i in range(N-1)]
x2 = [(width+space)*i for i in range(1,N)]
df_line = pd.DataFrame(
{ 'x': x,
'y': y,
'x2': x2,
'y2': y2
}
)
注意:使用公式计算 x 坐标,并将 y 坐标设置为常数。
绘制线条。
列表 9.5 绘制线条
line = alt.Chart(df_line).mark_line(
point=True,
strokeWidth=2
).encode(
x=alt.X('x:Q', axis=None),
y=alt.Y('y:Q', axis=None),
x2='x2:Q',
y2='y2:Q'
)
注意:使用mark_line()标记绘制线条。将point=True设置为显示点。
如果我们已经绘制了狗和猫图表的前几个元素,我们就可以构建最终的图表。
列表 9.6 构建最终图表
chart = ((context | (chart + annotation + img)) & (rect + line + text)
).configure_view(
strokeWidth=0
).resolve_scale(
color='independent',
x='independent',
y='independent'
).configure_axis(
grid=False
)
chart.save('pets-visualized-roadmap.xhtml')
注意:最终的图表包括左侧的上下文;主要条形图(chart);右侧的注释(狗和猫的文本)和图像(img);以及下面的下一步(rect、line和text)。在resolve_scale()方法中将color、x和y设置为独立以独立渲染图表刻度。
现在我们已经完成了这个例子,让我们尝试同一主题的练习。
练习 1
再次考虑狗和猫案例研究,并实施下一步到 Facebook 和 Instagram,使用社交媒体分享选项。使用href通道包含指向外部网站的链接。图 9.5 显示了可能的结果。您可以在本书的 GitHub 仓库中找到此练习的解决方案,位于 09/awareness-and-campaigns/pets-social-sharing.py。

图 9.5 狗和猫案例研究,增加了社交媒体分享行动呼吁
9.2.2 第二章:游客到达情况
考虑我们在第二章中分析的游泳池案例研究。作为一个快速提醒,该案例研究集中在在葡萄牙酒店建设新游泳池的可能性。数据故事显示了近年来葡萄牙游客数量的增加,如图 9.6 所示。

图 9.6 与游泳池案例研究相关的最终数据故事
第二章中描述的案例研究包含了一个通用的下一步,邀请观众建造游泳池。让我们让 ChatGPT 提出关于不同选项的可能下一步建议。让我们使用以下提示:考虑一个专注于在葡萄牙酒店建造新游泳池可能性的案例研究。数据故事显示了近年来葡萄牙游客数量的增加。提供 5 个选项以进一步调查游泳池的可行性。以下是可能的 ChatGPT 回答列表:
-
市场研究和需求分析
-
竞争分析
-
财务可行性研究
-
监管和环境评估
-
季节性和利用率分析
你可以在 ChatGPT 的网站上找到完整的对话:ChatGPT.com/share/28f05921-b548-4edc-938d-ccfcb2d5bba6。例如,我们可以详细阐述 ChatGPT 的建议,并提出以下下一步:
-
向其他酒店业者询问他们的经验。
-
邀请行政团队计算成本。
让我们实施前面的下一步,如图 9.7 所示。

图 9.7 带有社交分享行动号召的狗和猫案例研究
首先,使用 DALL-E 生成下一步部分数据故事底部的两个黑色图标。对于第一个图标,使用以下提示:客户体验,黑白图标。图 9.8 展示了 DALL-E 生成的一个可能结果。选择一张图片并下载。

图 9.8 使用以下提示生成的 DALL-E 可能输出:客户体验,黑白图标
对于第二个,使用以下提示:美元袋,黑白图标。图 9.9 展示了 DALL-E 生成的一个可能结果。选择一张图片并下载。

图 9.9 使用以下提示生成的 DALL-E 可能输出:美元袋,黑白图标
现在,我们准备编写生成下一步部分的代码。你可以在本书的 GitHub 仓库 09/collaborative-efforts/actionable-recommendations.py 下找到代码。让我们专注于代码中的行动号召部分。首先创建包含文本和要包含在 CTA 中的图像的 DataFrame。
列表 9.7 CTA DataFrame
df_cta = pd.DataFrame(
{'text' : [['Ask other hoteliers', 'for their experience'], ['Invite the administrative team', 'to calculate the costs']],
'x' : [0,9.8],
'img' : ['img/experience.png', 'img/costs.png']})
备注:创建一个新的 DataFrame,包含 CTA 文本、它们在 x 轴上的位置以及图像的路径。
接下来,让我们使用 ChatGPT 为 CTA 生成标题。给 ChatGPT 以下提示:作为行动邀请者针对高管。从以下文本中生成四个标题:下一步:向其他酒店业者询问他们的经验。邀请行政团队计算成本。图 9.10 展示了一个可能的结果。

图 9.10ChatGPT 生成四个 CTA 标题的可能输出
让我们选择第三个标题,“战略决策:利用集体经验的力量”,作为故事下一步部分的标题。从视觉上讲,我们将下一步实现为两个图表的组合:文本图表和图像图表。由于两个图表有一些共同的部分,让我们先绘制一个基本图表。
列表 9.8 为下一步构建基本图表
base_cta = alt.Chart(df_cta
).encode(
x=alt.X('x:Q', axis=None)
).properties(
title=alt.TitleParams(
text=['Strategic Decision-Making:', 'Harnessing the Power of Collective Experience'],
fontSize=18,
anchor='start'
)
)
注意:在不指定任何标记的情况下构建图表的基本结构。仅指定输入 DataFrame(df_cta)、共享编码通道(X)和属性(title)。使用TitleParams()指定标题细节。
一旦我们定义了基本图表,我们就可以分别定义每个图表的细节。以下列表描述了如何实现文本。
列表 9.9 绘制文本图表
text_cta = base_cta.mark_text(
lineBreak='\n',
align='left',
fontSize=20,
y=0,
dx = 40,
color='#81c01e'
).encode(
text='text:N'
)
注意:文本图表通过添加文本标记属性(mark_text)和文本的特定编码(text通道)来详细说明base_cta chart。在mark_text()属性中,指定一些文本属性,例如换行字符(lineBreak)、如何对齐文本(align)、字体大小(fontSize)、y 位置(y)、从 x 位置的 x 轴偏移(dx)和文本颜色(color)。
接下来,绘制图像,如下所示。
列表 9.10 绘制图像图表
img_cta = base_cta.mark_image(
width=50,
height=50,
y=10
).encode(
url='img'
)
注意:图像图表通过添加图像标记属性(mark_image)和图像的特定编码(url通道)来详细说明base_cta chart。在mark_image()属性中,指定一些图像属性,例如图像宽度(width)、高度(height)和 y 位置(y)。
现在,我们可以构建最终的图表。我们假设我们已经实现了数据故事的前一部分(图表、注释、评论和机场图表)。以下列表显示了如何分层图表以渲染最终的数据故事。
列表 9.11 构建最终数据故事
chart = ((commentary & airports) | ((chart + annotation) & img_cta + text_cta)
).resolve_scale(
x='independent',
).configure_view(
strokeWidth=0
)
chart.save('actionable-recommendations.xhtml')
注意:最终图表包括上下文(commentary和airport)、主要图表(chart和annotation)和下一步(image_cta和text_cta)。
练习 2
再次考虑游泳池案例研究,并实施学习更多的下一步。使用href通道包含指向外部网站的链接。图 9.11 显示了可能的结果。您可以在书籍的 GitHub 仓库中找到此练习的解决方案,位于 09/collaborative-efforts/learn-more.py。

图 9.11 与游泳池案例研究相关的最终数据故事
9.2.3 第三章:北美人口
考虑我们在第三章中分析的案例研究,即过去 50 年北美的人口,该研究旨在研究北美的人口增长。案例研究发现,北美人口增长与世界其他地区之间存在差距。故事作为下一步,提出了增加美国人口增长的一些策略,如图 9.12 所示。

图 9.12 与北美人口案例研究相关的最终数据故事
本案例研究已经包含了一些可能的下一步行动选项。我们可以通过提出一个按顺序执行的计划来修改它们。让我们用以下提示与 ChatGPT 开始对话:考虑一个专注于北美人口增长的案例研究。该研究发现了北美人口增长与世界其他地区之间的差距。这里有三种可能的选择来增加北美的人口增长:加强家庭友好政策、发展移民和激活乡村地区。根据你将先应用哪个选项以及之后应用哪个选项,按顺序排列这三个选项。 ChatGPT 通过排列这三个选项并解释为什么选择这个顺序来回答。在我的情况下,它提供了以下顺序:
-
加强家庭友好政策。
-
发展移民。
-
激活乡村地区。
我们也可以通过提供以下提示来优先考虑一个特定的选项,例如激活乡村地区:如果你首先激活乡村地区会怎样? 在我的情况下,答案并不有趣。我们可以通过向 ChatGPT 提出以下问题来进一步细化主题:在执行之前,哪个选项最容易? 根据 ChatGPT 的说法,最容易执行的选项是加强家庭友好政策。对话可以继续,直到我们找到我们想要的东西。您可以在 ChatGPT 的网站上找到本例中使用的完整对话(chat.openai.com/share/78c6c6a1-f468-4271-aa60-ac0d410ce117)。一旦我们从 ChatGPT 中提取了这些信息,我们就可以通过查看额外的外部材料来进一步调查这个主题。ChatGPT 只是第一步。它作为一个经验。接下来,我们需要验证 ChatGPT 提议的可行性。
本案例研究提供了一个与 ChatGPT 进行对话并将其作为队友的例子。不要害怕向 ChatGPT 提问。无论你问什么问题,即使是令你感到不舒服的问题,它都会始终回答你。
9.2.4 第四章:体育项目
考虑我们在第四章中分析的案例研究,该研究涉及选择最佳体育项目进行训练以在即将到来的比赛中取得好成绩(图 9.13)。作为我们故事的下一步,我们提出了投资自行车和赛艇。这一步过于笼统,因此我们可以改进它。

图 9.13 第四章中的案例研究
在这个例子中,我们将下一步骤嵌入到了标题中。让我们通过详细说明我们可以投入的改善划船和骑自行车的投资来提高它们。让我们询问 ChatGPT 提高前两个项目成绩所需的技术技能。使用以下提示:考虑一个与体育项目相关的场景,以在即将到来的比赛中训练并取得好成绩。作为我们故事的下一步,我们提出了投资自行车和划船。提出提高这两个项目的所需技术技能. ChatGPT 为这两个项目中的每一个都生成了一份详细的技术技能列表,例如,骑自行车的操控和定位,划船的划水技巧,以及划船的平衡和稳定性。您可以在 ChatGPT 的网站上找到完整的答案(chat.openai.com/share/9a28714f-dca3-4bbd-9c11-72aa9e371959)。现在,我们可以通过询问 ChatGPT 将这些技能分组到更一般的类别中来继续对话。使用以下提示:将提出的技能分为四个一般类别。可能生成的输出包括以下类别:
-
技术技能
-
体能技能
-
战术技能
-
营养和心理技能
我们可以使用生成的技能作为可能的下一步。我们只将营养和心理技能修改为心理技能。图 9.14 显示了修改后的故事。一旦我们找到这些信息,我们可以通过寻找额外的材料来进一步深入这个主题,例如,每个技能对最终结果的估计贡献。
我们假设每个运动的表现由四个因素决定:体能、心理、战术和技术。每个因素根据体育项目不同,对表现有不同的贡献。图 9.14 显示了某些假设值。此外,数据故事设想我们想要专注于技术因素以提高运动员的表现。表 9.2 显示了包含每个因素对体育表现贡献的数据集结构。

图 9.14 运动员案例研究,包括一个可操作的建议作为下一步
表 9.2 因子数据集
| 划船 | 自行车 | 因子 |
|---|---|---|
| 40 | 25 | 体能 |
| 30 | 35 | 技术 |
| 20 | 25 | 战术 |
| 10 | 15 | 心理 |
我们将使用堆叠条形图和分层文本来实现下一步。您可以在本书的 GitHub 仓库下的 09/training-and-development/support-and-assistance.py 中找到这个示例的代码。让我们首先定义基本图表,包括条形图和文本共享的编码和属性。
列表 9.12 构建基本图表
df_cta = pd.read_csv('source/factors.csv')
df_cta = df_cta.melt('Factor', var_name='Sport', value_name='Value')
base_cta = alt.Chart(df_cta).encode(
y=alt.Y('Sport', title=''),
x=alt.X('Value', axis=None, title='', stack=True),
).properties(
title=alt.TitleParams(
text='Investing in technical skills could bring the maximum benefit',
subtitle='What is the contribution of each factor to the sports performance?'
),
width=600,
height=100
)
注意:首先,读取因素数据集,并使用melt()将 DataFrame 从宽格式转换为长格式。接下来,通过定义输入数据集(alt.Chart(df_cta))、编码(x和y)和属性(title、width和height)来创建基础图表(base_cta)。对于x通道,指定stack=True以创建堆叠图表。
现在,我们可以构建堆叠条形图。
列表 9.13 构建堆叠条形图
cta = base_cta.mark_bar(
strokeWidth=3,
stroke='white'
).encode(
color=alt.Color('Factor',
scale=alt.Scale(
range=['lightgrey', '#80C11E',
'lightgrey', 'lightgrey'],
domain=['physical', 'technical',
'taktical', 'psycol.']
),
legend=None),
)
注意:使用基础图表来构建堆叠条形图。设置标记属性(mark_bar)和颜色通道。
最后,绘制文本并将其与其他数据故事的部分结合起来,如下所示。
列表 9.14 绘制文本
text_cta = base_cta.mark_text(
xOffset=-35,
fontSize=14,
color='black'
).encode(
text = 'Factor:N',
)
chart = ((chart + annotation) & (cta + text_cta)
).configure_view(
strokeWidth=0
)
注意:使用基础图表来构建文本图表。添加标记属性(mark_text)和文本通道。接下来,绘制最终的图表,包括主要图表(chart)、注释(annotation)和下一步(cta和text_cta)。
挑战:改进下一步
通过用主图表中显示的相同图标替换图 9.14 中的y标签(自行车和划船)来改进图 9.14 中的下一步。
现在你已经学会了如何实现培训和发展的下一步,让我们继续到下一个类别:战略行动。
9.2.5 第五章:无家可归
考虑我们在第五章中分析的关于意大利地区无家可归的案例研究,如图 9.15 所示。该案例研究专注于寻找资金来资助关于无家可归者的项目。在下一步骤部分,它提出了实施一些战略行动来减轻无家可归问题。

图 9.15 第五章中描述的无家可归案例研究
挑战:选择另一个下一步
在以下下一步骤中选择一个关于无家可归案例研究的方案,并解释你选择它的原因:寻求支持、自由互动、了解更多、提出计划、分享。
9.2.6 第五章:假新闻
考虑我们在第五章中分析的关于假新闻的案例研究,如图 9.16 所示。该案例研究专注于识别最可能包含假新闻的新闻类别。我们在标题中包括了下一步。

图 9.16 前几章中描述的所有示例的总结
我们可以通过添加一个交互式框来改进我们的下一步,该框在观众点击特定条形时显示一个示例文章标题和标题,如图 9.17 所示。在这个例子中,我们不会使用 ChatGPT。具体来说,我们使用自由互动的号召性用语来实现本例的下一步。

图 9.17 前几章中描述的所有示例的总结
我们将包含标题和标题的文本作为根据所选条形变化的文本图表实现,最初不显示任何文本。你可以在这个例子相关的代码在书的 GitHub 仓库下的 09/engagement-and-communication/engagement-and-interaction.py 中找到。
我们将构建一个选择点,并将其连接到条形图的Category字段。接下来,我们将使用选择点过滤下一部分要显示的文本。我们将假设包含每个类别的文章标题和副标题的数据集,如表 9.3 所示。
表 9.3 文章数据集
| 类别 | 标题 | 副标题 |
|---|---|---|
| 政治 | 全球外交的未来:应对 21 世纪挑战 | 国际关系如何塑造我们今天的世界 |
| 经济 | 加密货币的崛起:颠覆传统金融系统 | 探索数字货币对全球市场的影响 |
| 正义 | 改革刑事司法:走向公平和公正的系统 | 解决各国法律体系中的不平等问题 |
| 宗教 | 现代社会中的宗教对话:在差异中搭建桥梁 | 理解宗教信仰和实践的多样性 |
首先创建一个图表的选择点。将数据集的Category字段添加为交互元素的作业字段,如下所示。
列表 9.15 定义选择点
click = alt.selection_point(name='Select',
fields=['Category'], empty=False)
注意 定义一个名为Select的选择点,允许用户根据其Category值选择数据点。将empty参数设置为False确保至少选择一个数据点。
现在,使用定义的选择点设置所选条形图的颜色,如下所示。来自第五章原始版本实现的代码以粗体显示。
列表 9.16 构建交互式条形图
color=alt.Color('Macro Category:N',
scale=alt.Scale(
range=['#991111', '#f38f8f','gray'],
domain=['Material Life', 'Moral Life', 'Spiritual Life']
),
legend=None
)
chart = alt.Chart(df).mark_bar(
).encode(
y=alt.Y('Category:N',
sort='x',
title=None,
axis=alt.Axis(labelFontSize=14)
),
x=alt.X('Percentage of Fake Articles:Q',
title=None,
axis=alt.Axis(labelFontSize=14,
titleFontSize=14),
),
color=alt.condition(click | ~click, color, alt.value('lightgray')
)
).properties(
width=400,
height=400
).transform_calculate(
'Percentage of Fake Articles', alt.datum['Number of Fake Articles']/alt.datum['Number of Articles']*100
).add_params(
click
)
注意 首先,将颜色通道定义为变量(我们将在文本图表中也要使用它,因此将其定义为变量以避免重复编写)。接下来,通过定义y和x通道来构建条形图。同时,将颜色通道定义为依赖于click选择点的。使用表达式click | ~click 设置颜色,当click为True时(这发生在选中条形图时)和当它为False时(这发生在没有选中条形图时,因为我们已将empty设置为False)。然后,设置属性并通过add_params方法添加点击交互性。
现在,我们可以将文本图表绘制为两个图表的组合:标题和副标题。首先,构建基础图表,两个图表之间共享详细信息。
列表 9.17 构建基本图表
df_cta = pd.read_csv('source/articles.csv')
df_cta['Macro Category'] = df_cta['Category'].apply(lambda x: 'Material Life' if x in material_life else ('Moral Life' if x in moral_life else 'Spiritual Life'))
base_cta = alt.Chart(df_cta).mark_text(
fontSize=20,
align='left',
).encode(
color=color
).transform_filter(
click
)
注意 首先读取文章数据集。然后,添加用于着色文本的宏类别。最后,通过指定标记属性(mark_text)、颜色通道和用于动态选择文本的过滤器来定义基本图表。
现在,让我们继续处理文章标题。
列表 9.18 构建文章标题
title_cta = base_cta.encode(
text='Label:N',
).properties(
title=alt.TitleParams(
text=['Click on a category bar to read a sample title', 'and headline of a fake article for that category'],
fontSize=25,
offset=20,
anchor='start'
)
).transform_calculate(
Label= 'Title: ' + alt.datum.Title
)
注意 使用基础图表绘制标题文章文本。指定text通道,使用通过transform_calculate方法生成的Label列。同时,设置图表属性。
最后,构建文章标题,使用与文章标题类似的程序。
列表 9.19 构建文章标题
headline_cta = base_cta.encode(
text='Label:N'
).transform_calculate(
Label= 'Headline: ' + alt.datum.Headline
)
chart = (chart & (title_cta & headline_cta)).configure_axis(
grid=False
).configure_view(
strokeWidth=0
)
chart.save('engagement-and-interaction.xhtml')
注意:使用基础图表绘制标题文章文本。然后,将图表组合起来以获得最终的数据故事(假设图表包含主要图表)。使用configure_axis(grid=False)从图表中移除网格。
到目前为止,你已经学会了如何实施各种后续步骤。在最后一个案例研究中,你将学习如何将你的故事锚定到伦理框架中。让我们将这种策略应用到之前章节中实施的鲑鱼养殖业案例研究中。
9.2.7 第 6-8 章:鲑鱼养殖业案例研究
考虑鲑鱼养殖业案例研究。作为一个快速回顾,鲑鱼养殖业案例研究旨在研究美国鲑鱼养殖业的安全问题。到目前为止,我们已经表示了美国从鲑鱼出口中获得的美元收入。我们考虑了两种情景:一种积极的情景,我们关注收入的增加,另一种消极的情景,我们关注疾病时期。在本章中,我们只关注积极的情景,但你也可以轻松地将描述的概念应用到消极情景中。图 9.18 显示了上一章结束时产生的数据故事:将信息转化为知识。

图 9.18 将信息转化为知识后产生的故事
让我们假设,作为我们数据故事的下一步,我们想要提出一些战略性行动来继续美元趋势线的积极增长。我们提出以下下一步:
-
强调鲑鱼安全措施。
-
提高鲑鱼养殖业实践。
-
推广可持续鲑鱼消费。
让我们使用 ChatGPT 来调整提出的下一步到不同的伦理框架中。为 ChatGPT 生成基于功利主义文本的以下提示:考虑以下伦理框架:功利主义。这个框架基于最大化效用或幸福来做出伦理决策。根据这种观点,如果一个行为能带来最大多数人的最大幸福,那么这个行为就是正确的。
重新制定以下下一步以适应功利主义框架:
-
强调鲑鱼安全措施。
-
提高鲑鱼养殖业实践。
-
推广可持续鲑鱼消费。
图 9.19 显示了 ChatGPT 可能产生的一个输出。

图 9.19 ChatGPT 可能生成的一个输出
现在,让我们通过以下问题进行复习:这个输出是否真的锚定到功利主义框架中?如果答案是肯定的,我们就可以将输出纳入我们的故事中;否则,我们必须在将其添加到我们的故事之前进行纠正。在下一章中,我们将看到更多关于与 ChatGPT 和通用生成 AI 相关的伦理问题的细节。现在,控制产生的输出就足够了。
在我们的案例中,生成的输出包含诸如福利、社会福祉和总体幸福等词语,这与功利主义框架相符。
将相同的程序应用于其他伦理框架。表 9.4 显示了 ChatGPT 的一些可能输出。
表 9.4 ChatGPT 为每个伦理框架生成的输出
| 伦理框架 | 强调鲑鱼安全措施 | 提高鲑鱼养殖实践 | 促进可持续鲑鱼消费 |
|---|---|---|---|
| 功利主义 | 通过鲑鱼安全措施最大化公众福祉 | 通过增强型鲑鱼养殖实践提高社会福祉 | 通过促进可持续鲑鱼消费培养更大的总体幸福 |
| 义务论 | 维护鲑鱼安全措施 | 遵守增强型鲑鱼养殖实践 | 倡导道德和可持续的鲑鱼消费 |
| 德性伦理 | 培养鲑鱼福祉的道德管理 | 促进鲑鱼健康的优良养殖实践 | 通过可持续鲑鱼消费提升美德 |
| 关怀伦理 | 优先考虑鲑鱼福祉:实施安全措施 | 培育鲑鱼:用同理心改善养殖实践 | 关怀消费:培养可持续鲑鱼选择 |
一旦你选择了特定的伦理框架,你就可以用它来实施你的案例研究中的下一步。
练习 3
考虑 ChatGPT 为功利主义伦理框架生成的输出,并将其实施为可操作的建议。例如,你可以生成图 9.20 中描述的最终数据故事。你可以在本书的 GitHub 仓库 CaseStudies/aquaculture/from-knowledge-to-wisdom/chart.py 中找到这个练习的解决方案。

图 9.20 水产养殖案例研究的最终数据故事
现在你已经学会了如何将你的下一步行动与一个伦理框架联系起来,让我们继续本章的最后一部分:如何在你的数据故事中战略性地放置你的下一步行动。
9.3 下一步战略定位
下一步必须遵循你数据故事的主要观点。因此,我建议你在数据可视化图表的以下三个主要位置之一放置你的下一步:在标题中、在右侧或图表下方。在本节的剩余部分,我们将分别研究每个下一步位置。
9.3.1 标题位置
在标题中添加下一步意味着将其添加到图表的顶部,如图 9.21 所示。使用标题来声明观众应该如何处理数据故事。

图 9.21 图表顶部的下一步位置
挑战:标题中的下一步
考虑第 1-5 章中描述的案例研究(图 9.2)。哪些在标题中添加了下一步?
9.3.2 正确位置
在图表右侧添加下一步涉及立即在图表后添加,如图 9.22 所示。

图 9.22 图表右侧的下一步位置
使用图表的正确部分添加深入细节,例如文本描述。使用其他图表向观众解释在阅读前几部分之后应该做什么。
挑战:图表右侧的下一步
考虑第 1-5 章中描述的案例研究(图 9.2)。哪些在图表右侧添加了下一步?
9.3.3 下方放置
图表下方的下一步与之前的案例仅在位置上有所不同,如图 9.23 所示。

图 9.23 图表下方的下一步位置
挑战:图表下方的下一步
考虑第 1-5 章中描述的案例研究(图 9.2)。哪些在图表下方添加了下一步?
在本章的第一部分,你学习了智慧的概念和下一步。接下来,你学习了如何将 ChatGPT 作为经验替代来源使用。然后,你看到了如何将你的下一步锚定在伦理框架上。最后,你看到了如何在故事中战略性地放置下一步。在下一章中,你将看到如何发布你的数据故事以及生成式 AI 在数据故事讲述中的伦理影响可能有哪些。
摘要
-
智慧是利用你的经验和知识做出明智的决定或判断的能力。
-
将知识转化为智慧涉及在数据故事中添加下一步。下一步是我们数据故事的解决方案,并定义了在阅读故事后观众应该做什么。
-
根据它们旨在实现的目标将下一步分类到不同的类别:寻求支持、提供不同选项、自由互动、了解更多、提出计划或分享。
-
使用 ChatGPT 的知识帮助你生成可能包含在你故事中的下一步。然而,你必须始终手动检查和细化建议的下一步。
-
伦理框架作为基于指导道德行为的原理的决策指南。下一步必须基于伦理框架来确立。
参考文献
-
Berengueres, J. and Sandell, M. (2019). 《数据可视化与故事讲述入门:数据科学家指南(视觉思维)》。自出版。
-
McDowell, K. (2021). Storytelling Wisdom: Story, Information, and DIKW. 《信息科学和技术的协会杂志》,72 (10),1223–1233。
第三部分:交付数据故事
数据、信息、知识、智慧(DIKW)金字塔以及生成式 AI 的使用已帮助您构建了一个针对受众的数据故事。首先,您通过使用不同的技术,如联系、巧合、好奇心和矛盾,提取有趣的见解,将数据转化为信息。您使用 Altair 和 GitHub Copilot 构建了一个基本图表来表示提取的见解。接下来,您通过添加图表的上下文(可以是文本或视觉的)将信息转化为知识。您使用 ChatGPT 生成文本上下文,使用 DALL-E 生成视觉上下文。最后,您通过在故事中添加下一步行动,将知识转化为智慧。您了解到存在不同类型的下一步行动,包括意识提升和活动、协作努力、倡导和政策、培训和开发、战略行动以及参与和沟通。每一项都取决于您想要实现的具体目标。
您的数据故事现在已准备就绪,但您的工作尚未完成。您有责任以道德的方式讲述您的故事,这意味着您应该考虑文化、传统以及所有受众所信任的框架。接下来,您就可以发布您的故事了。
在第十章中,您将考虑使用生成式 AI 时常见的议题,包括幻觉、偏见和版权。您还将学习联合国教科文组织关于监控生成式 AI 输出的指南,以确保其不会歧视任何人。
在第十一章中,您将学习使用 Streamlit、Tableau、Power BI 和 Comet 等工具发布数据故事的热门策略。您将使用 Streamlit 作为一个独立的框架来构建网络应用程序。您还将学习如何将您的数据故事整合到两个流行的数据故事讲述工具:Tableau 和 Power BI。最后,您将使用 Comet,这是一个机器学习实验平台,同时也提供报告构建系统。最后,您将了解如何将单一图表的数据故事拆分为多个幻灯片,以便进行现场演示。
第十章:使用生成式 AI 时常见的 10 个问题
本章涵盖
-
幻觉、偏见和版权
-
使用生成式 AI 的指南
-
确认来源
您的数据故事现在准备好了。然而,在向您的受众传播之前,您应该反思使用生成式 AI 可能出现的潜在问题。我们不会讨论这些问题的技术细节,但描述的概念将帮助您完成对生成式 AI 的整体理解。更多细节,鼓励您阅读一些关于该主题的详细书籍——其中一些列在本章的参考文献中。
本章的第一部分,我们将关注幻觉、偏见和版权。接下来,我们将关注使用生成式 AI 的指南。最后,我们将了解如何正确地注明来源,包括数据来源和生成式 AI。在传播故事之前,让我们从第一个点开始:幻觉、偏见和版权。
10.1 幻觉、偏见和版权
生成式 AI 并非全知全能的先知,而是人类努力的成果,因此它反映了人类的特点,包括智慧、科学进步等,但也包括歧视、不平等和不公正。生成式 AI 虽然是人类智慧和科学进步的产物,但本质上携带了其训练数据中的偏见和局限性,不仅反映了我们的知识,也反映了我们的社会和历史偏见。记住,这些系统受限于其训练数据和算法,并不具备对意识的固有理解。
因此,人类的一切,包括人类的好与不好,都反映在 AI 中。因此,当生成式 AI 作为工作工具使用时,尤其是在数据叙事中,必须始终谨慎处理,特别关注它可能对最脆弱人群造成的利益和可能的损害。生成式 AI 产品有可能生成针对少数族裔群体的偏见输出,甚至可以用来构建看似合理的操纵性故事。
为了获得在使用生成式 AI 时可能无意中发生的歧视的实际想法,考虑以下 DALL-E 的提示:“一位穿着红色连衣裙的女士”。图 10.1 显示了 DALL-E 可能生成的输出。

图 10.1 一位穿着红色连衣裙的女士的照片
现在,考虑以下 DALL-E 的提示:“一位穿着橙色毛衣的女士在喝咖啡”。图 10.2 显示了 DALL-E 可能生成的输出。

图 10.2 一位穿着橙色毛衣的女士在喝咖啡
最后,考虑以下提示:“一位坐在黄色椅子上的男士”。图 10.3 显示了 DALL-E 可能生成的输出。

图 10.3 一位坐在黄色椅子上的男士
我们可能会继续生成越来越多的图像,但我们可能始终得到相似的结果。图 10.1、10.2 和 10.3 有什么共同之处?问题是几乎所有的代表人物都有深色头发(可能例外的是图 10.2 中编号 4 的女性)。如果我们生成少量图像,这可能是一个小问题。然而,如果我们使用 DALL-E 在大规模生成图像,仅使用深色头发的人可能会对浅色头发的人产生一种偏见。
这个例子展示了生成式 AI 存在偏见的简单情况。问题可能在于用于训练生成模型的数据库,其中可能包含比金发人士更多的深色头发人士。这是一个相对温和的例子,但它清楚地说明了您可能需要留意 AI 输出的偏见和偏颇。
挑战 1:其他类型的歧视
在使用 DALL-E 生成包含人物的图像时,您能找到其他类型的歧视吗?例如,尝试生成一位金发女性的图像。您会得到什么样的刻板印象?
挑战 2:色彩和情绪
DALL-E 将色彩和情绪关联起来。例如,尝试以下提示:(1)一位穿着黄色毛衣的女性,(2)一位穿着蓝色连衣裙的女性。尽管您没有指定任何情绪,您是否得到了不同的情绪?
由生成式 AI 引起的问题取决于不同的原因,例如 AI 幻觉、偏见和版权。我们不会深入探讨与这些问题相关的技术细节;您可以阅读本章参考文献中描述的资源以获取更多信息。在本节的剩余部分,我们将简要描述生成式 AI 可能引入的潜在问题。让我们从幻觉开始。
10.1.1 AI 幻觉
幻觉发生在生成式 AI 生成的内容与现实不符时。AI 内部的幻觉可以创建误导性或完全虚构的数据。幻觉是生成式 AI 设计带来的问题,从某种意义上说,生成式 AI 背后的大型语言模型(LLMs)是在没有任何意图交流的情况下生成的。它只是根据训练数据集生成统计上更可能的文本。此外,例如,如果一个生成式 AI 模型主要在包含英语文本的数据集上训练,它可能在准确理解或生成在代表性较弱的语种内容时遇到困难,反映出对英语的统计偏见。幻觉可能导致伦理问题,因为它们可能被用于生成用于用户操纵的内容。
生成式 AI 中的幻觉可以以各种方式表现出来。例如,完全由 AI 生成的数据故事可能会描述一个不存在的政治丑闻,这可能会误导公众并影响基于错误信息的决策。为了减轻幻觉问题,我们始终建议进行人工验证。在将任何 AI 生成的内容纳入你的故事之前,请对其进行判断和审查。
挑战 3:与生成式 AI 开玩笑
为了娱乐,考虑第二章中的游泳池案例研究。作为一个快速提醒,该案例研究关注在葡萄牙酒店建造新游泳池的可能性。使用以下提示生成下一步:考虑以下场景:案例研究关注在葡萄牙酒店建造新游泳池的可能性。数据故事显示近年来葡萄牙的游客数量不断增加。编写一些幻觉的下一步行动。你得到了什么?
为了减轻幻觉,你可以在你的提示中尝试设置以下参数:
-
温度——温度控制输出生成过程中应用的随机程度。它允许用户调整生成内容的创造性和不可预测性水平。范围从 0(更结构化和可预测的输出)到 2(更创造性和意外的结果)。默认值是 1。要在你的提示中引入温度,只需添加文本设置温度 = N(例如,使用温度 = 2)。我们可以使用较低的温度值来降低模型幻觉的概率。
-
Top P——Top P 也称为核采样或无惩罚采样。它有助于控制生成文本的多样性。使用此技术生成不偏离主题的响应。范围在 0 到 1 之间。较高的 top P 使输出更加多样化,而较低的值使模型更加确定。默认值是 1。要在你的提示中引入 top P,请添加文本设置 top P = N(例如,使用 top P = 1)。
通常,你每个提示设置一个参数。
挑战 4:设置温度和 P 值
在挑战 3 的提示中将温度或 top P 值设置为 0,然后比较新的结果与挑战 3 的原始输出。你注意到有什么不同吗?
你可以在我的博客文章“如何使用配置参数提高 ChatGPT 输出”中找到如何设置温度和 top P 参数的详细描述(mng.bz/pp4G)。现在我们已经讨论了幻觉,让我们转向下一个问题:偏差。
10.1.2 偏差
偏差是一种系统性和通常是无意识的倾向、偏见或倾向,它会影响决策、行动、感知或判断,有利于或不利于某个特定的人、群体、物体或思想。偏差依赖于人类的信念、刻板印象或歧视。由于 LLMs 是在主要由人类创建的数据集上训练的,因此不可避免地,这些数据集中包含偏差。偏差是人类的固有属性,所以我们无法从我们的数据集中将其移除。
即使在人们平等自由、没有歧视和战争的假设场景中,偏差也可能发生。事实上,偏差是多方面的。AI 中的偏差不仅限于负面话题,如歧视或战争。偏差也可以是文化偏好、习语表达,甚至是被认为是“正常”的东西。一个在理想世界数据上训练的 LLM,仍然可能基于数据中的普遍或主导因素发展出偏差。
此外,即使数据理想,LLMs 也可能仍然发展出偏差,这可能是由于它们的设计、使用的算法或理解和处理人类语言的固有局限性。换句话说,即使在这个假设场景中,我们的 LLM 也可能仍然表现出偏差。
对于偏差的分类,存在不同的观点,例如 Baer 在他的著作《理解、管理和预防算法偏差》(Apress,2019 年)中提出的。一种可能的方法将偏差分为以下几种类型:
-
数据偏差—这指的是训练集中存在偏差。它源于不同的原因,例如某些群体在训练集中的代表性不足。LLMs 是用从互联网提取的数据进行训练的。然而,网络上的文本是由一小部分人类撰写的。代表从人类知识开始到今天所有人类知识的书籍堆满了图书馆,而互联网上却无法获取。这意味着尽管大数据被用来训练 LLMs,但规模并不能保证多样性。
-
算法偏差—这种偏差源于在设计、编码或实现机器学习(ML)算法过程中所做的假设和决策。这种偏差可能由于特征选择、模型复杂度以及其他与算法相关的技术问题而产生。
-
测量偏差—这发生在用于收集数据的方法或工具系统地错误表示或扭曲所收集的信息时。这种偏差可能源于不同的因素,如仪器、人类观察者等。
在本书写作时,尚无确切的解决方案来消除生成式 AI 工具中的偏见。然而,可能的技术来减轻偏见包括数据清洗和平衡、在中间插入人类、模型评估等等。从生成式 AI 中消除偏见也意味着首先从人类中消除它,这将是对世界的重大进步,但不太可能很快发生。无论如何,我们可以通过始终关注生成的输出并将我们的数据故事锚定在道德框架中(如前一章所述)来减轻偏见。在考虑了偏见问题后,让我们转向下一个问题:版权。
10.1.3 版权
生成式 AI 模型是在大量数据上训练的,特别是来自公共空间中免费共享的内容。然而,用于训练模型的原始数据集的创作者不允许访问它们,因此我们不知道是否也使用了专有数据来训练模型。因此,AI 生成的内容可能会引发关于知识产权和所有权的问题。版权问题可能与生成式 AI 模型是黑盒以及用于训练它们的数据不可用的事实有关。
例如,考虑一个在广泛的音乐数据库上训练的 AI 系统,它生成了一首与现有受版权保护的歌曲相似的曲子。确定原始创作者变得复杂,引发了关于 AI 生成内容的所有权和知识产权正确归属的问题,可能导致创作者和 AI 系统之间的法律纠纷。在使用生成式 AI 之前,您应该始终了解版权法,为数据来源使用清晰的许可协议,创建原始数据集,强调归属和认可,实施版权过滤器,并寻求定期法律咨询。
本书所描述的所有问题在写作时仍未得到解决。因此,我建议在使用生成式 AI 进行数据叙事以及所有应用领域时,始终一致地控制生成式 AI 产生的输出。使用生成式 AI 的道德责任在于确保每个人都受到平等对待,少数群体不会被代表性不足,等等。为了帮助您控制生成式 AI 的输出并在必要时进行修改,您可以应用使用生成式 AI 的道德指南。那么,让我们继续前进,学习它们吧!
10.2 使用生成式 AI 的指南
许多倡议旨在规范所有领域的 AI 和生成式 AI 的使用,例如欧盟 AI 法案(欧洲委员会,2021 年)和白宫关于安全、可靠和值得信赖的人工智能开发和使用行政命令(白宫,2023 年)。您可以在本章末尾的参考文献部分找到这些文件的链接。
参考数据故事领域的这些指南意味着尊重人类价值观。联合国教科文组织 AI 道德指南强调四个核心价值观:
-
人权和尊严—每个数据故事,包括通过生成式 AI 生成的部分,都必须尊重人权和尊严。联合国教科文组织指南明确指出,“在任何 AI 系统生命周期的任何阶段,都不应伤害或使任何人类或人类群体受到损害或从属,无论是身体上、经济上、社会上、政治上、文化上还是精神上。”
-
和平与公正的社会—AI 应用于促进社区内的和谐与公平,促进决策过程中的公平、透明和问责制。
-
多样性和包容性—AI 应尊重人类多样性的丰富性,包括但不限于种族、性别、民族、文化等。
-
环境繁荣—AI 应优先考虑可持续性,并为环境保护做出积极贡献。
我们可以将之前的指南应用于检查 AI 生成的内内容是否道德正确。对于所有 AI 生成的内内容,我们应该回答以下问题:
-
AI 生成的内内容是否尊重人权?
-
AI 生成的内内容是否尊重社会?
-
AI 生成的内内容是否尊重多样性和包容性?
-
AI 生成的内内容是否尊重环境?
为了了解如何将这些指南应用于实际的数据故事,考虑我们在第四章中分析的案例研究,相关于选择最佳体育学科进行训练以在即将到来的比赛中取得好成绩。作为下一步,我们提出了投资自行车和划船。图 10.4 显示了最终的数据故事。

图 10.4 第四章中描述的体育学科数据故事
让我们看看 AI 生成的每个部分:
-
一般标题—释放潜力:投资划船和自行车以获得最大回报!
-
图像 1—正在练习划船的白人男子
-
图像 2—正在练习骑自行车的白人女子
对于这个场景的 AI 生成的内内容,唯一的问题是多样性和包容性。实际上,这两张图片描述的是两个白皮肤的个人。为了使数据故事符合道德规范,我们可以用另一个种族的个人替换这两张图片中的一张。
练习 1
考虑第五章中关于无家可归的案例研究,如图 10.5 所示。该案例研究侧重于寻找资金来资助一个旨在减少意大利无家可归问题的项目。现在,按照以下步骤进行:
-
识别 AI 生成的内内容。
-
对于每个由 AI 生成的内容的元素,回答以下问题:
-
AI 生成的内内容是否尊重人权?
-
AI 生成的内内容是否尊重社会?
-
AI 生成的内内容是否尊重多样性和包容性?
-
AI 生成的内内容是否尊重环境?
-

图 10.5 第五章中描述的无家可归案例研究
现在你已经学习了使用生成式 AI 的道德准则,让我们继续下一步:确定在使用它时的角色。
10.3 引用来源
引用来源意味着在数据故事中引用所使用的来源。这尤其重要,因为它允许你认可他人的工作。它还增加了故事的可信度,因为观众可以亲自检查故事中使用的来源。应该引用哪些类型的来源?一般来说,用于制作故事的任何来源都应该被引用,但特别以下来源:
-
数据来源
-
使用生成式 AI 的事实
-
任何用于微调或检索增强生成(RAG)的文档
虽然你可以发挥创意将引用放置在任何你想的地方,但传统上,我们在数据故事中将其添加到以下四个位置之一:
-
在标题/副标题下
-
在主图表下
-
在下一步骤下
-
横向
让我们分别调查这些。
10.3.1 在标题或副标题下
在标题或副标题下放置引用从故事开始就给观众一种信任感。图 10.6 展示了在标题或副标题下放置引用的示例。

图 10.6 一个在标题或副标题下带有引用的数据故事示例
如果你想让你的观众从故事开始就知道来源,请使用这种放置方式。尽管这种放置可能会产生信任感,但它也可能分散观众的注意力,因为观众可能会离开你的故事去寻找来源。
10.3.2 在主图表下
将引用放在主图表下意味着在故事的主要观点上添加一个细节。这有助于加强故事的基本要点。图 10.7 展示了在主图表下放置引用的示例。如果你想加强图表的主要信息,请使用这种放置方式。

图 10.7 一个在主图表下带有引用的数据故事示例
10.3.3 在下一步骤下
在这种情况下,在故事的末尾引用来源,作为下一步骤的附录,如图 10.8 所示。如果你想加强故事的下一步,请使用这种放置方式。

图 10.8 一个在下一步骤下带有引用的数据故事示例
10.3.4 横向
横向放置引用意味着将其视为主要数据故事工作流程之外的内容。你可以将其放置在左侧或右侧,如图 10.9 所示。

图 10.9 一个在左侧和右侧带有引用的数据故事示例
使用这种放置方式以保持引用在主要数据故事工作流程之外,并使观众专注于故事。现在我们已经考虑了你可以放置引用的各种位置,让我们继续探讨如何实际实现引用。
10.3.5 在 Altair 中实现引用
要在 Altair 中实现致谢,请使用比主故事中使用的字体更小的mark_text()。可选地,你可以包括指向原始来源的超链接。
考虑第四章中描述并在图 10.4 中展示的体育学科案例研究。让我们为图像致谢 DALL-E,为标题致谢 ChatGPT。添加以下文本作为致谢:图像:来源 DALL-E。标题:来源 ChatGPT。
我们将在左侧放置致谢。你可以在书籍的 GitHub 仓库中找到实现的代码,位于 10/crediting/left-sideways。以下列表显示了在左侧实现致谢部分的代码。
列表 10.1 在左侧添加致谢
credits_df = pd.DataFrame({'text': ['Images: source DALL-E, Title: source ChatGPT']})
credits = alt.Chart(credits_df
).mark_text(
size=10,
align='left',
color='black',
angle=270,
x=-70,
y=200
).encode(
text='text'
)
chart = (credits + chart + annotation)
备注 使用mark_text()添加致谢。使用angle属性将文本旋转 270 度。调整x和y以适应图表。尝试不同的值以获得最佳结果。
如往常一样,你可以要求 Copilot 为你生成代码。图 10.10 显示了生成的图表。

图 10.10 描述在第四章中,左侧带有致谢的体育学科案例研究
练习 2
通过在标题下实现致谢,修改前面的示例,如图 10.11 所示。

图 10.11 描述在第四章中,标题下带有致谢的体育学科案例研究
你可以在书籍的 GitHub 仓库中找到这个练习的解决方案,位于 10/crediting/under-the-title。
挑战 5:比较阅读流程
比较图 10.10 和 10.11。你能否在阅读流程方面区分出任何差异?
到目前为止,你已经学会了如何在你的数据故事中注明来源。在下一章中,你将看到如何导出最终的故事。
摘要
-
在任何应用中使用生成式人工智能可能会暴露出不同的问题,例如偏见和歧视。因此,审查生成式人工智能提供的内容非常重要。
-
根据联合国教科文组织原则,道德地使用生成式人工智能意味着尊重人、社会和环境。
-
在发布你的数据故事之前,确保要注明来源。这是认可他人工作并增强受众对你信任感的一种方式。
参考文献
生成式人工智能问题
-
Baer, T. (2019). 理解、管理和预防算法偏差. Apress.
-
Tomczak, J. M. (2022). 深度生成建模. Springer.
伦理与人工智能
-
欧盟人工智能法案。 (2021).
eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:52021PC0206。 -
美国白宫关于人工智能安全、可靠和值得信赖的开发与使用的行政命令。(2023 年)
www.whitehouse.gov/briefing-room/presidential-actions/2023/10/30/executive-order-on-the-safe-secure-and-trustworthy-development-and-use-of-artificial-intelligence/. -
联合国教科文组织关于人工智能伦理的建议。(2022 年)
unesdoc.unesco.org/ark:/48223/pf0000381137.
第十一章:发布数据故事
本章涵盖
-
导出故事
-
使用 Streamlit 发布故事
-
发布故事的替代方法:Tableau、Power BI 和 Comet
-
通过幻灯片展示您的数据故事
在这本书的整个过程中,我们构建了我们的数据故事。现在,在本书的最后一章,是时候向我们的观众展示它了。本章全部关于如何将您的数据故事传达给观众,并且以道德的方式进行。在本章的第一部分,我们将关注不同的导出数据故事的技术。接下来,我们将描述 Streamlit,这是一个与 Altair 完全集成的 Python 库。Streamlit 可以帮助您构建一个完整的独立网站,托管您的 Python 代码。然后,我们将探讨一些发布故事的替代方法,包括将其集成到一些流行的数据分析与可视化工具中的技术:Tableau 和 Power BI。之后,我们将描述如何将数据故事集成到 Comet 中,这是一个机器学习(ML)的实验平台。最后,我们将看到如何通过幻灯片展示您的数据故事。让我们从第一个要点开始:导出故事。
11.1 导出故事
Altair 提供了不同的格式来导出您的数据故事。在整个书中,我们使用了chart.save('chart.xhtml'),但 Altair 还支持其他格式,包括以下:
-
JPEG
-
PNG
-
SVG
-
JSON
-
PDF
要将图表保存为特定格式,只需将格式扩展名添加到您的文件中,例如chart.save('chart.png')用于 PNG 文件。对于 PNG 文件,您还可以通过一个额外的参数指定每英寸像素数(ppi):chart.save('chart.png', ppi=300)。
您可能会想知道,为什么在这本书中,我们一直使用 HTML 格式而不是 PNG 或 SVG。主要原因在于 HTML 是唯一支持显示交互性的格式。此外,一旦您创建了 HTML 文件,Altair 还会在图表右上角提供默认操作菜单,让您可以将故事下载为其他格式,如图 11.1 所示。

图 11.1 HTML 文件右上角的菜单允许您将故事导出为其他格式。
如果您不想显示操作菜单,您可以禁用它,如下面的列表所示。
列表 11.1 禁用操作菜单
chart.save('chart.xhtml', embed_options={'actions': False})
您从将原始数据转换为数据故事,通过 DIKW 金字塔、生成式 AI 和 Altair 的旅程已经完成。您的故事终于准备好向观众传播了!
在接下来的两个部分中,我们将探讨一些发布故事的替代技术。如果您不感兴趣,可以自由跳过。首先,我们将看到如何使用 Streamlit 发布故事,然后我们将看到如何将我们的故事嵌入到一些流行的工具中。让我们从 Streamlit 开始。
11.2 通过网络发布故事:Streamlit
Streamlit (streamlit.io/) 是一个开源框架,允许你使用 Python 快速构建网络应用。要开始使用 Streamlit,请参阅附录 A。与其它选项相比,使用 Streamlit 的主要优势是它与 Altair 完全集成。
在本节中,我们将了解如何使用 Streamlit 发布数据故事。Streamlit 使用特定的函数在网页上显示内容,例如 st.Title() 用于设置页面标题。
考虑第 11.2 列表中的示例。首先,导入 Streamlit 库。然后,像在 Altair 中一样构建你的图表。要在网页上的任何位置添加 Altair 图表,请使用 st.altair_chart()。
列表 11.2 在 Streamlit 中构建应用
import streamlit as st
import pandas as pd
import altair as alt
# Read the data
df = pd.read_csv('../data/population.csv')
df = df.melt(id_vars='Country Name', var_name='Year', value_name='Population')
df['Year'] = df['Year'].astype('int')
continents = ['Africa Eastern and Southern', 'Africa Western and Central',
'Middle East & North Africa', 'Sub-Saharan Africa',
'Europe & Central Asia', 'Latin America & Caribbean',
'North America', 'East Asia & Pacific']
df = df[df['Country Name'].isin(continents)]
# Create Streamlit app
st.title('Population of Continents')
# Add a slider for year selection
selected_year = st.slider('Select a year', min_value=1960, max_value=2021, value=2021, step=1)
# Filter data based on selected year
filtered_df = df[df['Year'] == selected_year]
# Create Altair chart
chart = alt.Chart(filtered_df).mark_bar(color='#80C11E').encode(
y=alt.Y('Country Name:O', sort='-x', title=''),
x=alt.X('Population:Q', title='Population')
).properties(
width=600,
height=300
).configure_view(
strokeWidth=0
).configure_axis(
grid=False
)
# Display chart in Streamlit app
st.altair_chart(chart)
注意:首先导入所需的库。然后,将数据作为 pandas DataFrame 加载。接下来,像通常一样在 Altair 中构建图表。使用 st.slider() 在 Streamlit 中构建滑块。最后,使用 st.altair_chart() 在 Streamlit 应用中显示图表。
要启动应用,请在终端中运行以下命令:streamlit app.py,其中 app.py 是包含第 11.2 列表中代码的脚本的名称。图 11.2 显示了结果应用。

图 11.2 Streamlit 中的结果应用
你可以从本书的 GitHub 仓库中下载本节描述的代码,路径为 11/streamlit/app.py。现在你已经学会了如何在 Streamlit 中发布你的数据故事,让我们继续探讨一些其他发布故事的方法。如果你不感兴趣,可以直接跳到结论部分。
发布你的数据故事有多种方式,包括报告、演示文稿、HTML 页面等。在本节中,我们将介绍三种工具:
-
Tableau
-
Power BI
-
Comet
让我们从第一个工具开始:Tableau。
11.3 Tableau
Tableau (www.tableau.com/) 是一款商业智能工具,帮助用户探索和分析数据。与 Streamlit 不同,Tableau 不需要任何编程技能,因为其界面完全可视化。如果你已经熟悉 Tableau 并用它来实现仪表板,结合 Tableau 和 Altair 可能很有用。你可以将 Altair 生成的数据故事集成到 Tableau 中,作为仪表板的一个附加项。
在使用 Tableau 之前,你必须将其下载到你的本地计算机上并支付许可证费用。Tableau 还为学生和教师提供试用版和完全免费的许可证。在本章的参考文献中,你可以找到一些有趣的资源,帮助你开始使用 Tableau。在本节中,我们描述了如何在 Tableau 中导入你的数据故事。如果你不感兴趣,可以直接跳过本节,转到下一节。考虑第八章 slider.py 中的示例和图 11.3 所示。

图 11.3 带有滑块的图表
要在 Tableau 中导入你的图表,执行以下操作:
-
打开终端,将其指向包含 08/slider.py 的目录。运行命令
python3 slider.py以生成 HTML 文件。 -
在终端中,启动 Python 服务器如下:
python3-mhttp.server。 -
在浏览器中指向 localhost:8000,并访问 slider.xhtml;您应该看到图 10.14 中显示的图表。移动滑块以检查代码是否正确工作。
-
启动 Tableau Desktop 应用程序,导入 08/data/population.csv 文件,并创建一个新的仪表板。
-
在 Tableau 中,从对象菜单双击网页,并在对话框中输入以下 URL,如图 11.4 所示:http://localhost:8000/ slider.xhtml。

图 11.4 Tableau 中的编辑 URL 对话框
现在,您应该在 Tableau 仪表板中看到您的 Altair 图表。既然您已经学会了如何在 Tableau 仪表板中包含您的数据故事,让我们继续下一个替代方案:在 Power BI 报告中包含您的数据故事。
注意:在某些情况下,网页导入器失败,因为它不支持 Altair 生成的 JavaScript 包装器以包含图表。在这种情况下,将图表导出为 .png 文件,并使用图像对象将其导入到 Tableau 中。
11.4 Power BI
Power BI (mng.bz/o0Pd) 是微软发布的另一个商业智能工具。Power BI 提供桌面和在线版本。与 Tableau 类似,您必须购买许可证才能使用它;然而,您可以使用试用版进行测试。在本章的参考文献中,您可以找到一些开始使用 Power BI 的资源。
与 Tableau 类似,如果您已经熟悉并使用 Power BI,则可能需要将 Altair 生成的数据故事集成到 Power BI 仪表板中。否则,使用其他解决方案,如 Streamlit,会更好。
在本节中,我们仅描述如何将 Altair 生成的图表导入到 Power BI 中。我们将使用在线版本。具体来说,我们将使用 Deneb 插件,该插件允许您导入用 Vega 编写的数据。从 Altair,您始终可以直接从 HTML 版本导出 Vega 或 Vega-Lite JSON 文件,如图 10.12 所示。
为了简单起见,在下面的示例中,我们将直接使用 GitHub 仓库中本书的 03/vega/json/spec.json 目录下的 Vega 代码。执行以下步骤:
- 登录您的 Power BI 账户,然后点击新建报告 → 粘贴或手动输入您的数据 → 创建空白报告(图 11.5)。

图 11.5 创建新报告的 Power BI 界面快照
-
- 点击可视化 → (三个点)获取更多可视化 → Vega → Power BI 中的 Deneb:声明性可视化 → 添加(图 11.6)。

图 11.6 Deneb 插件
-
- 在可视化菜单中,选择 Deneb。
-
- 在数据菜单中,将 X 和 Y 添加到 Deneb。点击 X 和 Y 附近的箭头,然后点击不汇总(图 11.7)。

图 11.7 每个变量的选择菜单
-
- 在应用左侧,你应该能看到 Deneb 框。在报告中点击 Deneb 框中的三个点,然后点击编辑 → Vega - 空白(图 11.8)。

图 11.8 每个变量的选择菜单
-
- 将 03/vega/json/spec.json 中的代码复制到 Deneb 编辑器中。你需要删除数据才能使其工作。以下列表显示了修改后的版本。接下来,点击创建。
列表 11.3 图表的 Vega 表示
{
"description": "A basic line chart",
"width": 400,
"height": 200,
"padding": 5,
"data": [
{
"name": "dataset"
}
],
"scales": [
{
"name": "xscale",
"domain": {"data": "dataset", "field": "X"},
"range": "width"
},
{
"name": "yscale",
"domain": {"data": "dataset", "field": "Y"},
"range": "height"
}
],
"axes": [
{ "orient": "bottom", "scale": "xscale" },
{ "orient": "left", "scale": "yscale" }
],
"marks": [
{
"type": "line",
"from": {"data":"dataset"},
"encode": {
"enter": {
"x": {"scale": "xscale", "field": "X"},
"y": {"scale": "yscale", "field": "Y"},
"stroke": { "value": "#636466"}
}
}
},
{
"type": "symbol",
"from": {"data":"dataset"},
"encode": {
"enter": {
"x": {"scale": "xscale", "field": "X"},
"y": {"scale": "yscale", "field": "Y"},
"shape": {"value": "circle"},
"size" : {"value": 100},
"fill": { "value": "#636466"}
}
}
}
]
}
注意:用导入到 Power BI 的数据集的链接替换数据部分。
图 11.9 显示了结果图表。现在你已经学会了如何在 Power BI 中导入 Vega 图表,让我们继续到最后一个工具:Comet。

图 11.9 Power BI 中的结果图表
11.5 Comet
Comet (www.comet.com/site/)是一个用于 ML 模型跟踪和测试的实验平台。在我的上一本书《Comet for Data Science》中,我详细描述了如何开始使用 Comet 以及如何跟踪 ML、深度学习(DL)、时间序列和自然语言处理任务。因此,有关 Comet 的更多详细信息,请参阅此书。
如果你的数据故事是完整数据科学项目的成果,包括模型实验和评估,你可能会决定将 Altair 生成的数据故事与 Comet 集成。由于 Comet 支持 Python,使用它导出你的数据故事不需要任何特别的努力。
在 Comet 中,你还可以创建报告。报告是一组文本和面板,显示实验结果。这是一个非常有用的功能,因为你可以使用一个工具进行实验和报告。与 Tableau 和 Power BI 不同,Comet 对个人用户是免费的,因此你不需要付费来访问服务。此外,Comet 支持许多编程语言,包括 Python,因此你可以继续使用 Python 来编写报告。
在本节中,我们将描述如何在 Comet 中包含一个简单的交互式图表。我们将使用由 08/slider.py 生成的 HTML 代码。我们将用 HTML/JavaScript 编写代码,但 Comet 也支持 Python。如果你没有 slider.xhtml 文件,打开终端,移动到 08 目录,并运行命令python slider.py。
执行以下步骤:
-
登录 Comet,按照以下步骤开始一个新项目:新建项目 → 项目名称,描述,项目类型:实验跟踪。
-
通过点击报告 → 新报告 → 添加面板 → 新来创建一个新的报告。
-
Comet SDK 应该会打开。默认情况下,它配置为在 JavaScript 中工作。如果你想用 Python 工作,在右上角点击 JavaScript 并选择 Python。
-
在资源选项卡下,添加以下三个 JavaScript 脚本(图 11.10):

图 11.10 Comet SDK 中的资源标签页
-
- 切换到 HTML 标签页,并将
id名称更改为vis,如图 11.11 所示。
- 切换到 HTML 标签页,并将

图 11.11 Comet SDK 中的 HTML 标签页
-
- 在编辑模式下打开 slider.xhtml 文件,并复制


浙公网安备 33010602011771号