使用 Altair 和人工智能的数据故事讲述指南(二)
当你构建一个数据驱动的故事时,你应该考虑许多方面:从数据中提取的洞察力;数据背后的背景信息;以及与数据相关的下一步行动,邀请观众参与其中。数据、信息、知识、智慧(DIKW)金字塔帮助你考虑所有这些方面。在本部分,你将深化对 DIKW 金字塔所有步骤的理解。在第五章中,你将巩固与 DIKW 金字塔相关的概念。本章的第一部分集中在无家可归的案例研究上,以及如何使用 DIKW 金字塔将数据从原始图表转化
原文:
zh.annas-archive.org/md5/5e84114267d6946828aa9cc0362743ab译者:飞龙
第二部分:使用 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 文件。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-1.png
图 5.1 2021 年意大利无家可归者的地图
备注:该图表使用包含意大利地图的底层topoJSON对象构建地图。要构建地图,请使用mark_geoshape()标记,并通过project()方法指定投影。此外,通过transform_lookup()方法将topoJSON文件的properties.reg_name变量与df DataFrame 的Territory变量进行映射。我们将在下一章更深入地讨论如何构建地理地图。
安吉丽卡向她的上司展示了图表,上司要求她回答以下问题:我们需要申请资金的四个地区是哪些? 安吉丽卡查看图表,并给出以下答案:颜色较深的地区。她的答案非常含糊,因为从她生成的图表来看,并不立即清楚哪些地区颜色最深。此外,选择这四个地区的原因也不明确。安吉丽卡不得不承认,她的图表未能完成其使命。
5.1.1 图表有什么问题?
图表的主要问题是安吉丽卡的上司(观众)期望在图表中看到的内容与图表实际显示的内容之间存在很大差异。在实践中,安吉丽卡的图表没有回答她请求的问题(图 5.2)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-2.png
图 5.2 安吉丽卡的上司(观众)要求回答一个具体的问题(在左侧)。实际上,图表回答了通用问题(在右侧)。
建立图表的第一条规则是:图表必须准确回答目标观众提出的问题。尽量不要让观众去解读答案。为他们做这项工作!
安吉丽卡的老板要求她重新绘制图表。她低头回到办公室,再次分析她的数据集。安吉丽卡明白问题不在于数据分析,而在于如何有效地表示数据。她尝试这样的推理:观众必须一眼就能理解这四个地区以申请资金。这四个地区可能是什么?可能是拥有最多无家可归者数量的那些地区。她之前图表中缺少的是可能的部分。她考虑制作一个条形图,按无家可归者数量降序显示地区。经过一番努力,她制作了图 5.3 中的图表,并在列表 5.2 中编写了代码。您也可以在 GitHub 仓库中找到该书的代码,位于 CaseStudies/homeless-people/bar-chart.py。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-3.png
图 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)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-4.png
图 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)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-5.png
图 5.5 Angelica 的图表生成之旅。Angelica 直接向她的观众展示了数据探索的结果。
她的图表所缺乏的是没有上下文,也没有推动基金委员会接受她提案的动力。Angelica 应该在数据探索阶段之后添加一个解释阶段,在那里她可以用她的数据吸引观众,并激励他们接受她的提案。Angelica 可以使用 DIKW 模型将她数据的探索阶段转移到解释阶段(图 5.6)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-6.png
图 5.6 Angelica 应该将她的数据探索阶段转化为数据解释阶段。
换句话说,Angelica 还没有将数据转化为故事。在他的书籍《有效数据叙事》中,Brent Dykes 说:“数据故事的形成始于使用探索性数据可视化来发现洞察。一旦发现有意义的洞察,就使用解释性数据可视化来讲述故事。”(Dykes,2019)Angelica 构建的图表仅构成了数据故事的主要观点。然而,为了有一个完整的故事,Angelica 还应该包括背景和下一步行动(图 5.7)。基金委员会期望 Angelica 的图表能够回答这个问题:*为什么我们应该资助你的项目?*将图表组织成一个故事将增加她获得资助的机会。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-7.png
图 5.7 基金委员会期望看到一个包含背景、主要观点和下一步行动的故事,邀请他们资助 Angelica 的提案。
现在,你理解了将数据转化为故事以有效地向观众传达信息的紧迫性。接下来,让我们进入下一步:数据故事是什么及其主要组成部分。
5.2 揭示叙事:数据故事是什么
根据《剑桥词典》,故事是“对一系列事件的真实或虚构描述。”故事是通过一个结构化、连贯的叙述来分享信息或娱乐,从而吸引观众的方式。故事的目标始终是吸引观众以传达信息。
在他的著作《诗学》中,古希腊哲学家亚里士多德提出了一种构建悲剧的框架,也称为“三幕结构”。该结构由三个主要部分组成:设置、对抗和解决(图 5.8)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-8.png
图 5.8 亚里士多德提出的三幕结构来构建悲剧
本节设置向观众介绍了故事的角色、背景和基本冲突。在对抗中,主要角色面临障碍并努力克服它们。最后,在解决阶段,故事达到冲突得以解决的点。亚里士多德的悲剧结构至今仍在文学、电影和其他叙事形式中得到广泛应用。其有效性在于其构建紧张和悬念的能力,从而为观众带来令人满意的结局。
在《有效数据叙事》一书中,Dykes 提出了一种三幕悲剧的更新版本,适用于数据叙事场景(图 5.9)。Dykes 称之为“数据故事结构弧”。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-9.png
图 5.9 Dykes 提出的数据故事结构弧
数据故事首先通过(a)定义数据背后的背景和(b)激发观众的兴趣来开始。Dykes 还建议在此初始阶段加入一个钩子。钩子是“一个作为故事转折点的显著观察,开始揭示问题或机会”(Dykes,2019)。然后,数据故事通过(c)揭示洞察力、(d)给观众一些思考时间以及(e)通过提出下一步行动来结束。
图 4.9 中提出的结构在构建图表时可能看似抽象且难以应用。使用第一章中介绍的 DIKW 金字塔,你可以将简单的图表转变为故事。
5.2.1 使用 DIKW 金字塔简化数据故事
在第一章中,我们介绍了数据、信息、知识、智慧(DIKW)金字塔,并在宠物活动等实际案例中用它将数据转化为智慧。我们可以概括第二章中描述的例子,并使用 DIKW 来构建任何数据故事。图 5.10 显示了如何将数据故事结构弧映射到 DIKW 金字塔上:
-
从数据中提取洞察力对应于数据故事的主要点。
-
将背景添加到提取的信息中相当于定义背景并提高观众的兴趣。
-
添加行动号召相当于邀请观众思考和跟随下一步。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-10.png
图 5.10 数据讲述弧与 DIKW 金字塔的映射
在数据之前拥有知识可能看起来很奇怪。然而,在这里,我们所说的“知识”是指理解故事并将其置于特定背景所需的所有元素。所描述的映射表明了构建数据故事时的自然流程(图 5.11)。首先,确定故事的主要观点;然后设置背景;最后,设置下一步。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-11.png
图 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 显示了结果图表。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-12.png
图 5.12 无家可归图表通过上下文(图像和副标题)得到丰富。
然后,我们在图表中添加一个新的部分,描述我们将如何使用资金(图 5.13)。例如,我们将用 35%的资金用于庇护和住房,25%用于职业培训,等等。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-13.png
图 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 金字塔。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-14.png
图 5.14 无家可归故事的最终图表
5.3 将生成式 AI 融入 DIKW 金字塔
正如我们在第四章中讨论的,生成式 AI 可以帮助我们更进一步,通过使创建复杂和逼真的计算机生成内容成为可能,从而改变各个行业和领域。现在,有了这些知识,我们可以利用生成式 AI 的能力来增强人类创造力并自动化内容生成过程。
图 5.15 显示了我们可以将生成式 AI 工具融入 DIKW 金字塔的每个步骤:
-
洞察提取 — 使用生成算法自动化构建图表的代码生成。
-
添加背景 — 使用文本生成将基本文本转换为更吸引观众的文本。添加由 AI 生成的图像、声音和视频,为观众提供额外的背景信息。
-
行动号召 — 使用文本生成来提出新想法和吸引人的文本。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-15.png
图 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 中阅读完整的对话。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-16.png
图 5.16 生成用于情境的文本的步骤
ChatGPT 在四个步骤后生成了文本。我们使用了以下策略来让 ChatGPT 生成所需的文本:
-
描述 — 要求 ChatGPT 一般性地描述您的问题。在这种情况下,要求 ChatGPT 一般性地描述无家可归的状况。作为回答,ChatGPT 生成了一段长文本。
-
缩短 — 要求 ChatGPT 对生成的文本进行摘要。
-
转换 — 要求 ChatGPT 使摘要对观众更具吸引力。
-
缩短 — 如果文本仍然很长,要求 ChatGPT 进一步缩减。
在没有意识到的情况下,我们将 DIKW 模型应用于 ChatGPT 的使用。从一段长文本(数据)开始,我们提取了信息(摘要),然后将其转化为知识和智慧(引人入胜的文本)。换句话说,当您与 ChatGPT 生成情境时,将对话组织成一个故事(图 5.17)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-17.png
图 5.17 数据故事结构与 ChatGPT 对话生成情境之间的映射
您可以将描述的过程推广到生成各种可能的副标题,然后选择适合您需求的那个。此外,您还可以添加特定的关键词,这有助于吸引目标受众并提高文本的可见性。我们将在第六章中看到如何将关键词添加到 ChatGPT 对话中。
5.4.2 生成图像
在您的图表中添加一张或多张图片或照片可以帮助给图表的主题赋予一个面孔。然而,图片常常受到版权保护,这意味着在没有获得所有者许可的情况下使用它们可能会导致法律后果。我们可以使用 DALL-E 和其他 AI 工具来创建独特且不受版权限制的图像。
在关于无家可归者的图表中,我们添加了两张代表无家可归者的照片。我们使用以下简单的提示来生成 DALL-E 的照片:“一张无家可归者的照片”。DALL-E 生成了四张图片,我们选择了其中两张(图 5.18)。在第六章中,我们将看到更多高级的图像生成技术。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-18.png
图 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 下找到该代码。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-19.png
图 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 显示了旋转过程。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-20.png
图 5.20 图 5.19 中图表的旋转过程
接下来,我们可以水平翻转图表,得到图 5.21 中的图表,并修正了标签。你还可以直接重写 Altair 代码来绘制柱状图而不是条形图(代码在 GitHub 仓库的 CaseStudies/fake-news/bar-chart.py 下)。我们更喜欢展示旋转过程而不是直接绘制图表,以展示旋转策略是如何工作的。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-21.png
图 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 显示了生成的图表。我们使用了两种色调的红来突出关注物质生活和道德生活的紧迫性。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-22.png
图 5.22 显示了带有宏类别高亮的图 5.21 图表
我们可以通过简化图表来改进它,例如,通过分组类别。然而,我们的受众是专家(编辑),他们需要非常详细的信息,因为他们必须确切知道他们必须深入分析的类别。因此,我们保持图表非常详细。我们已经从图表中移除了图例,因为我们想用一些图像来替换它,这些图像也充当我们故事的环境。让我们看看如何在下一步中添加这些图像:将信息转化为知识。
5.5.2 从信息到知识
想法是为每个宏类别添加一个图像,更具体地说,是一个图标。我们可以使用 DALL-E 生成三个图像。使用以下提示生成精神生活图标:黑白祈祷手势的图标。DALL-E 将生成四个图像,如图 5.23 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-23.png
图 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 显示了生成的图表。图标和文本充当图例。此外,它们是我们故事的角色。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-24.png
图 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 可能生成的输出。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-25.png
图 5.25 ChatGPT 生成的一个可能输出
生成的标题不包含行动号召。为了添加它,让我们通过为 ChatGPT 编写以下新提示来改进标题,在同一聊天中:将标题转换为行动号召,邀请观众关注与物质和道德生活相关的新闻。图 5.26 显示了 ChatGPT 可能生成的输出。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-26.png
图 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)
)
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/5-27.png
图 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 找到这个例子。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-1.png
图 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)。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-2.png
图 6.2 电子商店订单,重点关注耳机
该图突出了耳机订单。值得注意的是,耳机订单每周五达到峰值。这可能是一个巧合,但事件的重复性应该被考虑。例如,安吉丽卡可能会发现每周五都有一个令人兴奋的广播节目,导致人们争相购买新的耳机。或者她可能会发现她所在的城市周五有一条法律,不允许打开扬声器。无论如何,这种事件的重复性需要进一步分析。
观察图 6.2 的 x 轴,您会发现一个奇怪的标签策略:首先,年份;然后是工作日;最后是月份。这是 Altair 的默认日期标签。您可以在 Altair 官方文档中了解更多关于如何格式化日期的详细信息(https://mng.bz/lMmy)。
碰巧洞察与联系洞察在发现方式上有所不同。在联系洞察中,我们关注重要细节,而在碰巧洞察中,我们关注重复事件。此外,当您有聚合度量时,联系是有帮助的,而巧合最好应用于像随时间变化的原始销售数字这样的东西。
6.1.3 好奇心
在 1928 年,苏格兰细菌学家亚历山大·弗莱明爵士在研究葡萄球菌细菌时发现了青霉素。有一天,当他仔细检查装满这些微生物的平板时,他注意到一些奇怪的现象。一个不小心被留下的平板被霉菌污染了。弗莱明对这种意想不到的情况感到好奇,他仔细检查了平板。令他惊讶的是,他观察到霉菌周围的细菌正在逐渐恶化。霉菌释放出的某种物质正在击败它们。出于好奇心,弗莱明开始了一系列实验来探索这一现象。他小心地隔离了霉菌并提取了它产生的物质,他将其命名为青霉素。好奇心促使弗莱明进一步调查这个问题,最终导致了青霉素的发现,这是世界上第一种抗生素,拯救了无数生命,并永远改变了医学。
好奇心是一种推动力,它能激发惊奇,推动发现,并激发创新,推动我们质疑、寻求答案,并扩展知识的边界。好奇心是我们可以应用来提取洞察力的一种策略。对数据的兴趣可能由各种现象触发,包括异常值、缺失数据、时间或空间上的数据缺口、突然的变动或趋势、意外的模式等等。当我们注意到数据中的奇怪之处时,我们会深入挖掘,提出问题,并探索导致观察到的模式的潜在因素。这种好奇心驱使我们从不同的角度分析数据,揭示隐藏的洞察力,并最终对现象有更深入的理解。
让我们考虑以下例子来解释我们如何应用好奇心策略。再次考虑前一小节中电子设备的例子。现在,想象一下你有 3 月份的不同趋势线,如图 6.3 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-3.png
图 6.3 3 月份的电子产品订单,重点关注耳机
该图显示了 3 月 25 日的销售高峰。这一事件可能会引起一些好奇心,促使我们进行进一步调查。好奇心可以帮助我们在头脑风暴过程中。例如,它可能引导我们寻找可能促成这一波峰的各种因素,如营销努力、特别促销或外部事件。随着我们继续研究,好奇心可能会促使我们添加其他因素,如这一时期的客户人口统计。这项调查不仅会满足我们的好奇心,还会帮助我们提取洞察力。
6.1.4 矛盾
矛盾是指两种或更多信念或证据冲突的情况,从而产生不一致性。根据加里·克莱因的说法,要从矛盾中提取洞察力,你必须以怀疑的心态处理数据。与基于巧合的方法不同,我们寻找重复的事件,而在基于矛盾的战略中,我们寻找看似冲突的事件。从矛盾中提取的洞察力可以颠覆传统思维,挑战既定观念,并提供促进创造性问题解决的替代视角。通过接受矛盾中的内在张力,你可以获得宝贵的洞察力,从而实现突破、创新和增长。
考虑图 6.4,展示了随着时间的推移产品销售和价格。您可以在本书的 GitHub 仓库下的 06/contradictions 找到生成的图表代码。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-4.png
图 6.4 展示产品销售和价格的图表
图表显示,价格上涨与销售额增加相对应。这一现象相当矛盾,因为普遍的假设是价格上涨应该与销售额下降相对应。然而,为了理解这一现象,你可以戴上怀疑的透镜,更深入地分析这一矛盾现象。你可能发现的一个可能动机是感知价值。当价格上涨时,顾客可能会认为你的产品更独特、高质量或更受欢迎。这种增加价值的感知可以使顾客愿意支付更多,从而导致销售额增加。另一种解释可能是消费者的紧迫感。消费者可能担心错过产品,认为更高的价格意味着稀缺或供应有限。另一个原因可能是通货膨胀。在通货膨胀的背景下,当价格上涨时,人们可能会因为担心价格会进一步上涨而购买更多,从而使产品变得难以获得。
所有的策略、联系、巧合、好奇心和矛盾都定义了一些可能的技巧来提取意义。无论你采取哪种方法,都要始终依靠你的经验和深入的探索性分析阶段来从数据中提取意义。现在你已经学会了如何从数据中提取洞察力,让我们继续探讨如何选择故事的角色。
6.2 选择故事的角色
在电影《印第安纳·琼斯与圣战奇兵》中,印第安纳·琼斯,一位杰出的考古学家,踏上了一场惊心动魄的冒险之旅,追寻着一件古老的圣物——约柜。故事背景设定在 20 世纪 30 年代,印第安纳与时间赛跑,试图在纳粹分子将其据为己用之前找到这个神圣的遗物。这部电影以及印第安纳·琼斯系列的其他电影都是引人入胜的故事的例子,其中英雄渴望得到某物。
每个故事都是从英雄渴望得到某物开始的。没有英雄和要实现的目标,就没有故事。在数据故事中也应该是这样。虽然“英雄”这个词可能过于夸张,用来定义我们故事的主题,然而,在我们的情况下,我们确实想强调数据故事与真实故事之间的相似性。在任何故事中,总有一个英雄与主要角色相对应。我们的想法是将要被代表的主题看作是我们故事中的英雄,即使它只是一个简单的产品。采用这种观点,构建故事也会更容易:
-
每个数据故事都应该有一个 英雄。这可能是一个产品、一个客户,或者任何其他东西。
-
英雄必须 渴望得到某物。这可能是一个销售增长、满意度提高,或者其他任何东西。
请记住,故事中的英雄不是你自己。英雄是你的数据故事的主要主题。它取决于你提取的洞察力。英雄的例子包括人、物和地方。
一旦你确定了故事中的英雄,下一步就是向观众提出以下问题:英雄会得到他们想要的吗? 在他的书中,《构建故事品牌:明确你的信息,让客户愿意倾听》(2017),唐纳德·米利根说:“在知道英雄想要什么之前,观众对她命运的兴趣很小。这就是为什么编剧必须在电影开始的前九分钟左右定义角色的抱负。”
除了有一个英雄之外,你的故事还应该至少有两个其他角色:向导和对手。向导通常是一位聪明而有经验的导师,帮助英雄实现他们的目标。对手可以是
-
一个试图阻碍或反对英雄进步的竞争者
-
一个比较角色,它允许你衡量英雄的进步
-
在不同的时间(例如,将英雄现在的状况与一年前进行比较)或不同的空间(例如,将这里的英雄与其他地点进行比较)的同一英雄
让我们回顾前几章中看到的所有例子,并确定每个故事中的英雄。为了方便起见,图 6.5 总结了分析的例子。尝试搜索每个例子中的英雄、英雄类型、英雄想要什么和对手。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-5.png
图 6.5 总结了前几章中描述的所有例子
表 6.3 显示了图 6.5 中每个案例研究的英雄和对手。对于每个案例研究,表格还显示了英雄的类型和英雄想要什么。在第二章的例子——“是的,你可以建造一个新的游泳池!”中,存在这样的隐含假设:游客到访量的增加与建造新游泳池的可能性相对应。这显然是一个简化的假设,但我们简化了这个例子只是为了开始,并展示 DIKW 金字塔是如何工作的。
表 6.3 前几章分析的案例研究的英雄和对手
| 案例研究 | 英雄 | 英雄类型 | 英雄想要什么 | 对手 |
|---|---|---|---|---|
| 第一章:增加与狗相关的网站的广告活动 | 与狗相关的网站 | 事物 | 增加观看次数 | 与猫相关的网站 |
| 第二章:是的,你可以建造一个新的游泳池! | 葡萄牙游客到访流量 | 流程 | 游客到访量的增加 | 德国游客到访流量 |
| 第三章:过去 50 年北美的人口 | 北美人口 | 人们 | 解释趋势 | 世界其他地区的人口 |
| 第五章,示例 1:一起,让我们做出改变:支持我们的项目以帮助无家可归者 | 拉齐奥、皮埃蒙特、利古里亚和撒丁岛的无家可归者人数 | 人们 | 减少这个数字 | 其他意大利地区的无家可归者人数 |
| 第五章,示例 2:你的真理守护者:在物质和道德叙事中对抗假新闻 | 物质和道德生活的新闻 | 事物 | 确定它们是真实的还是假的 | 精神生活的新闻 |
在表 6.3 中展示的所有示例中,对手总是与英雄不同的角色,尽管它们与英雄属于同一类型。在某些情况下,对手可以是之前条件下的同一英雄,例如过去的产品销售或另一个地理区域内的销售。现在你已经学会了如何选择故事的角色,让我们继续选择适合你的数据故事的正确图表。
6.3 选择正确的图表
最佳图表取决于你想展示的洞察力、你想传达的信息以及你针对的受众。图 6.6 展示了洞察力、信息和受众在图表中的相互关系。如果你只考虑洞察力,你的图表效果就是提供信息。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-6.png
图 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 直接生成的。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-7.png
图 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 从图表中移除边框。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-8.png
图 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的降序值对类别进行排序。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-9.png
图 6.9 如果您开始编写,Copilot 将建议下一步。
图表需要简化,因此我们应该删除不必要的元素,例如 y 轴标题(餐食类型)、网格等。例如,我们可以在每根柱子的末尾添加其值,并完全删除 x 轴标签。使用 Copilot 来帮助您构建图表。当您使用 Copilot 时,您会发现它会根据您之前编写的代码提出更多适合您编码风格的建议。如果您开始编写某些内容,Copilot 将根据您之前编写的代码建议下一步。这意味着您不仅可以使用预定义的提示,还可以在提示中建议下一步。
要在每根柱子的末尾添加其值,开始为 Copilot 编写一个提示,按 Enter 键,Copilot 将建议如何实现,如图 6.10 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-10.png
图 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')
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-11.png
图 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 显示了生成的图表。现在,是时候转向下一个图表系列:地理地图。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-12.png
图 6.12 一个折线图
6.3.4 地理地图系列
地理地图显示了地球表面上不同数据点的位置,如国家、大陆、海洋和河流。地理地图还显示了感兴趣区域的资源和数据分布。
最受欢迎的地理地图包括以下几种:
-
渐变地图——这些使用颜色来显示不同区域之间值的差异(图 6.13)。
-
点密度地图——这些使用点来显示一个区域内某个值的浓度(图 6.14)。
-
比例符号地图——这些地图使用根据它们所代表的值来定制的符号(图 6.15)。
你可以在本书的 GitHub 仓库中找到每种类型地理地图的相关代码。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-13.png
图 6.13 一个面状图
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-14.png
图 6.14 一个点密度图
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-15.png
图 6.15 一个比例符号地图
当你构建地理地图时,你必须设置要使用的投影。最受欢迎的投影地图之一是墨卡托投影地图。尽管这个地图非常流行,但它并不能正确地表示世界。例如,在地图上,格陵兰看起来几乎和非洲一样大,而实际上,非洲大约是 14 倍大!这种扭曲发生是因为在平面上很难准确地表示地球的曲面。现在你已经学习了最流行的地理地图,让我们继续学习点图系列。
6.3.5 点图系列
点图系列包括使用x和y轴上的点来表示数据点的图表,以显示两个变量之间的关系。最流行的点图系列包括散点图和气泡图。使用mark_point()标记来绘制点图。
散点图可视化两个数值变量之间的关系。使用它们来识别数据中的模式和趋势,并基于这些趋势进行预测。图 6.16 展示了散点图的一个示例。你可以在本书的 GitHub 仓库中找到相关的代码。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-16.png
图 6.16 一个散点图
气泡图是散点图,它使用数据点的尺寸来编码一个额外的变量。在 Altair 中,你可以使用size通道来设置气泡大小:size=alt.Size('Ranking:Q',scale=alt.Scale(range=[1,200]),legend=None)。图 6.17 展示了气泡图的一个示例。现在你已经学习了基本的图表系列,让我们继续进行一个实际案例研究。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-17.png
图 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 所示的图表。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/6-18.png
图 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 显示了我们所实施的数据故事的完整内容:从数据到智慧。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-1.png
图 7.1 第四章中描述的使用案例
7.2.1 普通公众
这个受众包括来自不同背景和知识水平的个人。他们可能对您的话题知之甚少或一无所知。在为普通公众制作数据故事时,使用精确的语言,避免用过多信息使他们感到不知所措,并专注于以视觉和吸引人的方式呈现最相关的见解。普通公众可能会发现图 7.1 中的图表复杂,有不必要的基线。作为图 7.1 中图表的替代方案,您可以考虑绘制图 7.2 中的图表,某些受众可能会觉得它更有吸引力。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-2.png
图 7.2 适应普通公众的使用案例
这个图表被称为多层饼图。我们本可以将图像放置在相关的条形旁边,但在这个案例中,空间不足,所以我们把它们放在图表的中心。在其他情况下,您可能考虑将图像放在条形旁边。您可以在本书的 GitHub 仓库下的 07/general-public 找到生成此图表的完整代码。
7.2.2 高管
高管通常是组织中高级决策者,他们依赖数据驱动的洞察来做出关键的商业选择。他们通常时间有限,需要简洁且可操作的信息。在为高管创建数据故事时,提前展示关键发现、趋势和建议是至关重要的。
使用突出显示最关键数据点的可视化,并直接将数据与战略目标联系起来。提供额外的背景或行业标准以支持你的分析也可能很有帮助。图 7.1 所示的图表对于高管来说可能很好,因为它不包含很多细节,并描述了我们为什么选择某些运动,这得益于其 50%的基线。
7.2.3 专业人士
这个受众群体包括具有特定领域专业知识或专业背景的个人。他们对数据的理解更深入,需要更多分析信息。在为专业人士创建数据故事时,解释数据分析的方法、假设和局限性。考虑包括额外的支持数据和参考文献,使专业人士能够进一步探索数据。
作为 7.1 图中图表的替代方案,你可以绘制图 7.3 所示的图表,某些受众可能很容易理解。该图只显示了图表,没有任何注释或背景。你可以在本书的 GitHub 仓库下的 07/professionals 中找到生成此图表的完整代码。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-3.png
图 7.3 适用于专业人士的使用案例
表 7.1 总结了根据受众类型在图表中应表示的内容。现在你已经学会了如何根据受众类型调整你的图表,让我们继续下一步:使用 ChatGPT 进行评论和注释。
表 7.1 根据受众类型在图表中应表示的内容
| 受众类型 | 需求 | 应表示的内容 |
|---|---|---|
| 普通公众 | 理解数据 | 吸引人的见解概述 |
| 高管 | 高层次的数据趋势概述,以辅助战略决策 | 突出关键指标和影响业务成果的趋势。 |
| 专业人士 | 深入理解数据背后的现象的详细见解 | 添加数字、统计和有用信息以深入理解见解。 |
7.3 使用 ChatGPT 进行评论和注释
在他的小说《变形记》中,弗朗茨·卡夫卡讲述了格里高尔·萨姆沙的故事,他是一位旅行推销员,一天早上醒来发现自己变成了一个巨大的昆虫。萨姆沙被困在这个昆虫的外壳中,无法与家人互动或传达他的想法。萨姆沙的家人努力接受他的转变,导致他们与萨姆沙的关系恶化,萨姆沙变得越来越孤立。这部小说揭示了当一个人的内心世界对他人不可接近时出现的根本孤立。数据分析师可能会发现自己处于与卡夫卡小说中格里高尔·萨姆沙相似的情况,当他们不得不向数据可视化图表添加文本时。数据分析师本质上是一名技术人员,可能会在撰写引人入胜的文本时遇到一些困难。
ChatGPT 可以帮助你在你的数据可视化图表中添加文本上下文。你已经了解到,ChatGPT 的提示的基本结构包括三个主要元素:角色、受众和任务。
例如,你可以写上扮演一个表演者(角色),为决策者写作(受众)。为<主题>写 5 个标题(任务)。主题可以是任何你想要的内容。主要问题是如何构建主题,以便 ChatGPT 产生正确的上下文。为了将主题包含在 ChatGPT 的提示中,我们将根据图 7.4 所示的方案生成上下文。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-4.png
图 7.4 生成上下文所使用的方案
在提示中,我们指定以下四个主要元素:
-
角色 — 你希望 ChatGPT 扮演的角色。在第四章中,你已经学习了多种角色类型,包括表演者、教育者、信息提供者、激励者、行动邀请者和关系构建者。
-
受众 — 你图表的受众。有不同类型的受众,例如公众、高管和专业人士。
-
主题 — 你图表的主题。
-
类型 — 要生成的文本类型,包括注释和评论。
生成上下文的过程是迭代的,这意味着如果你对产生的结果不满意,你可以多次生成上下文。例如,你可以调整一个或多个元素,使 ChatGPT 收敛到期望的输出。
在本节的剩余部分,我们将关注如何编写方案的主题和类型元素,同时保持角色和受众简单。然而,你可以将描述主题和受众的策略适应到其他元素。
作为构建上下文的例子,我们将关注第四章中描述并在图 7.1 中展示的案例研究。以下文本为了方便起见总结了场景:想象你在一个体育公司工作。你正在训练一支在各个学科领域的年轻运动员队伍。对于每个学科,你都记录了世界纪录和你的队伍达到的最佳时间以供比较。不幸的是,你的公司可用的投资资金有限。你的老板要求你了解哪些学科值得训练,希望在下一次比赛中取得好成绩。
7.3.1 描述主题
描述主题意味着组成简单的词汇,精确地描述给 ChatGPT 你在图表中发现和展示的内容。你越精确,输出结果就越好。
描述主题时,关注三个方面的内容:场景、数据和洞察,如图 7.5 所示。让我们更详细地探讨这三个方面。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-5.png
图 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 可能生成的输出。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-6.png
图 7.6 ChatGPT 生成的五个标题
作为一种替代方案,你可以要求 ChatGPT 生成以下内容:*扮演一个启发者。使用 30 个词为以下主题编写图表的上下文。有五种运动项目:划船(百分比提升=62.32%),自行车(57.64%),短跑(42.69%),*长距离跑(18.31%),和游泳(12.38%)。划船和自行车的百分比高于 50%的基线。图 7.7 显示了 ChatGPT 可能生成的输出。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-7.png
图 7.7 ChatGPT 生成的上下文
现在您已经学会了如何描述主题,尝试为第一章中描述的案例研究生成上下文:宠物场景。例如,您可以扮演信息提供者的角色。为了方便,我们总结了场景:一个专门为宠物举办的活动组织者正在收集将参加活动的宠物类型。对于每个宠物类别,组织者在专门针对该类别的网站上宣传活动。组织者要求您构建一份关于当前情况的快速报告。
更多详情请参阅第一章。您可以在此处找到生成的上下文的提示:mng.bz/EZvJ。为了进一步练习,请为前几章中描述的其他场景编写主题。现在您已经学会了如何描述主题,让我们继续到下一个元素:描述类型。
7.3.2 描述类型
我们考虑以下类型:评论和注释。在前几节中,您已经看到了不同的方法来指导 ChatGPT 生成上下文类型,例如使用 30 个单词编写图表的上下文。
在描述类型时,尽可能精确,指定以下方面:
-
类型(评论、注释或普通文本)
-
生成单词的最大数量
我的建议是尝试不同的类型,并根据您的需求评估结果。
7.3.3 设置自定义指令
ChatGPT 使您可以为所有新聊天配置自定义指令。例如,如果我们为同一受众类型构建图表并扮演相同的角色,我们可以将此属性用作 ChatGPT 的默认配置。
要启用自定义指令,访问 ChatGPT 网页界面,点击您个人资料附近的三个点,然后点击“自定义指令”。在新窗口中,编写自定义指令。例如,您可以使用第一个框来配置您的角色以及目标受众,第二个框用于更多详细信息,例如要生成的单词数、语气和风格,如图 7.8 所示。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-8.png
图 7.8 自定义指令对话框
点击“保存”按钮以启用新聊天的自定义指令。如果您想禁用此属性,请在对话框中取消选中“为新聊天启用”属性。然后,点击“保存”按钮。
让我们尝试使用前面的文本(我们已移除要生成的单词数,因为我们已在自定义指令中进行了配置):为以下主题编写图表的上下文。有五种运动项目:划船(百分比提升=62.32%),自行车(57.64%),短跑(42.69%),长跑(18.31%)和游泳(12.38%)。划船和自行车的百分比高于 50%的基线。
图 7.9 展示了可能的一个输出。注意,例如,我们在自定义指令中设定的非正式语气。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-9.png
图 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)能够在生成过程中整合外部知识源。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-10.png
图 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(未微调)在类似输入下的输出。现在你已经学会了如何手动构建你的数据集,让我们继续到下一个策略,从来源构建。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-11.png
图 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 未进行微调,您必须在提示中指定更多细节,如之前所见。相反,对于您的微调模型,在提示中描述内容就足够了。现在您已经学会了如何进行数据故事讲述的微调,让我们继续学习将模型适应特定上下文的下一个策略:增强检索生成。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-12.png
图 7.12 ChatGPT 生成的输出
7.4.2 增强检索生成
到目前为止,你已经看到了如何通过构建一个专用的数据集来适应一个 LLM 的上下文。在这种情况下,努力的工作在于准备数据集。想象一下,如果能够直接将文本传递给 LLM 而不需要将其转换为特定格式,那会多么方便。好消息是,这确实可能,多亏了检索增强生成(RAG)。
RAG 是一种高级自然语言处理(NLP)技术,它结合了信息检索和文本生成的元素。首先,RAG 执行一个检索步骤,查询外部知识源,如庞大的文本语料库或结构化数据库。接下来,RAG 使用这个知识源来增强其响应生成。RAG 将检索到的事实整合到其生成的文本中。
在数据故事讲述领域,你可以使用 RAG 来适应你的 LLM 到你的主题,例如产品、实时数据、客户评论或其他相关信息。例如,通过查询知识库以获取特定产品的详细信息,你可以生成专用的评论和注释。
假设你想构建一个基于 RAG 的系统,从你的公司网站上检索有关产品的信息。图 7.13 展示了我们将要实现的 RAG 系统的架构。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-13.png
图 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)和检索接口。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-14.png
图 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 中。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-15.png
图 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 金字塔第一步结束时生成的图表:从数据到信息。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-16.png
图 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 展示了添加上下文后的最终图表,我们将信息转化为知识。在下一章中,我们将通过添加一些图片来进一步丰富上下文,而在第九章中,我们将通过添加下一步:智慧来完成故事。
https://github.com/OpenDocCN/ibooker-dl-zh/raw/master/docs/dt-stytl-altair-ai/img/7-17.png
图 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.
更多推荐



所有评论(0)