DLAI-数据输入输出预处理笔记-全-

DLAI 数据输入输出预处理笔记(全)

001:欢迎学习数据输入/输出与预处理(Python 和 SQL)📊

在本课程中,我们将要学习如何通过网页抓取和API来收集数据,并掌握数据预处理的核心技能,为后续的数据分析打下坚实基础。

概述

欢迎来到《数据输入/输出与预处理(Python 和 SQL)》课程。这是Deep Learning.ai数据分析专业认证的第四门课程。到目前为止,在本系列课程中,你一直在使用已提供的、干净的数据集进行学习。然而,作为一名数据分析师,你经常需要自己收集数据,并将其处理成适合分析的形式。

数据质量的重要性

根据一份咨询报告(尽管准确性难以完全核实,但仍有参考价值),仅在美国,低质量数据每年就给企业造成约3万亿美元的损失。反之,这也意味着,通过你的数据收集和预处理技能,存在着为业务创造巨大价值的绝佳机会。

除了学习如何获取数据,你还将处理常见的数据处理任务,例如去重处理缺失值以及将数据归一化到统一尺度,以使数据为分析做好准备。

导师寄语

很高兴欢迎你的导师肖恩·博斯回来为大家讲解这些内容。正如你所知,数据分析师面临的一个核心挑战是找到高质量数据并将其转化为适用于特定场景的形式。预处理本身是一项独特的技能。

尽管预处理极其重要,但它不如分析过程那样引人注目,因此有时可能被低估。然而,它为你所有的分析洞察提供了基础。业内常说,数据分析师80%的工作是数据预处理。实际上,我的团队最近开发了一个预测电影制作时间线的模型,我们在预处理上就花费了数月时间。

预处理在AI与分析中的角色

在人工智能领域,无论是机器学习还是你将在本课程中执行的许多分析任务,高质量数据都能带来显著效益。这可能意味着复杂的预处理,也可能简单到仅仅是移除缺失值

如今,借助大语言模型,我甚至看到人们为特定领域进行非常巧妙的合成数据生成,尤其是在数据尚不存在或从外部收集成本过高的情况下。在许多现实世界的分析和AI系统中,执行核心机器学习任务的代码(例如工厂中的缺陷检测)只占整体代码的一小部分。而数据收集和预处理的代码量,有时确实会比分析和绘图的代码多得多。

因此,当你开始一个新的分析项目时,我鼓励你花时间零距离探索数据,真正理解它。沉浸于数据之中,你会学到很多东西。

本课程内容预览

在本课程中,你将立即开始使用Python进行网页抓取,从网站收集数据。在下一个模块中,你将通过API(一种允许程序相互通信的特殊网络服务)从网络收集数据。你将使用文本和数值处理技术,将这些数据转化为可分析的格式。

在最后两个模块中,你将学习数据库的基础知识,包括使用SQL编写查询语句来过滤、分组和连接数据。你将分析各种现实世界的数据,包括音乐曲目、乐高套装、食品安全检查、科技工作等。

你还将使用大语言模型来帮助规划SQL和Python中的任务与代码。数据收集和预处理需要大量上下文信息,而这些信息并不总是容易提供给大语言模型。例如,在电影行业,你会处理很多独特的媒体文件类型,如OCF、IMF和ProRes。无论你在哪个领域工作,都会有自己特定的“语言”,大语言模型可能并不深入了解。

AI在数据分析工具箱中的定位

在软件开发中,有一个“10倍效率开发者”的概念,指的是那些能产生十倍于平均开发者影响力的人。这并不意味着他们写代码的速度快十倍,而是他们的决策和优先级排序能带来更高效的产出和影响力。

我认为,随着越来越多的工作实现AI赋能,将会出现越来越多的“10倍效率专业人士”,数据分析师将是受益最大的职能之一。即使是现在,对于使用AI工具的数据分析师来说,其影响力可能轻松达到尚未熟练使用这些工具的分析师的两倍。然而,即使在使用这些出色的AI工具时,你仍然需要理解问题背景、设计输出等,才能有效地驱动技术。

因此,你在本课程中学到的技能,将使你能够借助AI加速产出并提高生产力。

总结

本节课中,我们一起学习了数据收集与预处理的重要性、它在实际工作与AI项目中的核心地位,以及本课程将涵盖的关键技能,包括网页抓取、API使用、数据清洗和SQL查询。这些技能将为你的数据分析之旅奠定坚实的基础,并帮助你在AI时代提升工作效率。

现在,让我们前往下一个视频,正式开始学习。

002:生成式AI在本课程中的角色 🧠

在本节课中,我们将要学习生成式人工智能(特别是大语言模型)在本课程中的核心作用、教学理念以及如何将其作为提升数据分析技能的辅助工具。

本课程的一个关键要素是学习如何与生成式人工智能,特别是大语言模型(LLMs)协同编程。有效使用LLMs将帮助你简化工作流程,并在职场中脱颖而出。

生成式AI作为技能补充 🔧

上一节我们提到了LLMs的重要性,本节中我们来看看团队关于在课程中使用生成式AI的核心哲学。

首先,LLMs是你作为数据分析师技能的补充,而非替代品。它们是一个优秀的编程伙伴,可以帮助你编写代码和修复错误。然而,只有在你自身懂得如何编码的前提下,你才能最大化利用LLM的潜力。

在本课程中,你将学习编码和与LLM协作的基础知识。你将建立代码解读能力,并培养对LLMs擅长何种任务以及可能在何处出错的直觉。你还将使用Coursera平台内置的实验聊天机器人来辅助你的代码编写。

课程的时效性与核心原则 🧭

本课程展示了截至2025年初的最新功能,我们预计在未来数月和数年内会有变化。本课程旨在传授经久不衰的原则,即如何在工作中思考和使用生成式AI,无论你使用何种具体产品。

你将培养一种迭代怀疑的思维模式。新的模型和功能在不断发布,以下是你近期可以预期的一些变化:

以下是未来LLMs可能的发展方向:

  • 更先进和专门化的功能:例如能够替你使用应用程序的生成式AI工具。
  • 更便宜、更快速、更高质量的工具和输出。

总体而言,跟上这个领域的快速发展是具有挑战性的,但无需担心。在本课程中,你将发展所需的元认知技能,以便在工作中驾驭这些技术进步。

工具选择与课程实践 💻

本课程也展示了一些LLMs的付费功能,但你无需购买任何额外产品即可完成作业。让你了解现有的选项(包括付费选项)非常重要,这样你才能建立信心,在工作中尝试并选择最佳工具。

本课程不推荐任何单一工具。你将在各个模块中看到多种工具。请记住,你将学到的核心原则将使你准备好与现在和未来的免费及付费LLMs协同工作。

你将在接下来的几个视频中遇到第一个LLM演示。现在,请与我一起进入下一个视频,了解本模块所有令人兴奋的主题。我们那里见。😊

003:网络爬虫与文本处理入门 🕸️📝

在本课程中,我们将学习如何从真实世界的网站中获取数据,并对其进行处理,为后续分析做好准备。我们将探索不同的数据来源、学习基础的网络爬虫技术,并掌握关键的文本预处理方法。

概述

本模块将为你提供处理从真实网站获取数据的基础工具,重点在于数据的收集与分析前的准备工作。

第一课:数据来源与预处理的重要性

在第一课中,你将了解各种数据来源,并看到不同的数据收集方法如何服务于独特的业务需求。你还将探索预处理、组织和清理原始数据在为有效分析准备数据集时所扮演的关键角色。

第二课:从网络抓取结构化数据

上一节我们介绍了数据来源,本节中我们来看看如何从网络获取数据。

在第二课中,你将学习如何从网络上抓取结构化数据。你将编写代码从网络收集有价值的数据,并将其转换为可分析的格式。

以下是本课将涉及的核心技能:

  • 学习如何在不同的数据类型之间进行转换。
  • 使用 containsreplacesplitstrip 等常用方法处理文本。

第三课:使用BeautifulSoup进行高级网页抓取

在掌握了基础抓取后,我们将面对更复杂的挑战。

在第三课,也是最后一课中,你将使用名为 BeautifulSoup 的出色Python模块来处理更高级的网络抓取挑战。你将探索网络上的数据是如何使用HTML构建的,以及如何将原始的HTML转化为可操作的见解。

总结

本节课中我们一起学习了从网络获取数据并对其进行处理的基础技能。到本模块结束时,你将掌握从网络获取数据并处理数据以为分析做好准备的基础技能。让我们在下一个视频中开始学习。

004:多样化的数据来源 📊

概述

在本节课中,我们将要学习数据分析师在日常工作中可能遇到的各种数据来源。你将了解到,数据不仅来自简单的CSV文件,还可能来自数据库、API接口以及网页。理解这些来源的特点和适用场景,是进行有效数据获取和预处理的第一步。


企业每天都在生成和收集大量信息。

根据你作为分析师需要解决的问题,你会遇到来自不同来源的数据。

假设你即将开始某项分析工作,并打开了你的Jupyter笔记本。

你的第一步通常是将数据加载到一个数据框中。到目前为止,你一直使用CSV文件来读取数据,这对于许多应用场景是可行的。但在实践中,你将处理各种各样的数据源,而不仅仅是CSV文件。

思考一下你可能用于分析的不同类型的信息。

首先,是你自己生成的信息,例如销售记录。CSV文件似乎不是存储这类信息的合适场所,因为一旦有人完成一笔销售,你的CSV文件就过时了。在接下来的模块中,你会看到这类数据通常是从数据库中加载的。

其次,是你实时收集的信息,例如监控竞争对手的价格或跟踪天气。这类信息可以通过网络爬虫获取(你将在本模块中学习),或者通过应用程序编程接口(API)获取(你将在下一个模块中遇到)。

最后,是历史信息,例如你去年的投资交易记录。这类信息很可能从一个平面文件(如CSV)中加载,因为它不会再被更新。平面文件指的是包含单行列表格的文件。

让我们来探讨几种常见场景,在这些场景中你可能会处理来自这些常见来源的数据:平面文件、数据库、API和网络爬虫。


平面文件 📄

如果你正在分析客户的购买趋势,你很可能会依赖销售数据。

这些数据是结构化的,意味着它们可以存储在行和列中。当你只处理一个较小的静态数据集时,CSV文件可能非常适合这项任务。你可以将它保存在笔记本电脑上,进行快速修改,并用几行代码将其加载到你的Python笔记本中。

但是,随着数据量的增长,将其存储在单个电子表格中可能会变得不可行或处理速度非常慢。随着数据复杂性的增加,你可能需要将数据存储在具有关系的多个表中。你可能还需要存储和处理非结构化数据。随着越来越多的人需要使用和编辑文件,你将需要管理访问权限。


数据库 🗃️

数据库解决了平面文件的大部分缺点。你可以将数据库视为许多通过关系连接起来的平面文件,它们针对速度和高效存储进行了优化。

例如,假设你仍在分析客户的购买趋势。你可能有一个客户信息的平面文件和一个购买记录的平面文件。数据库可以存储这些客户和购买记录,以及每个客户与其所做购买之间的关系。一个规模更大、更成熟的公司很可能拥有某种内部数据库系统。你可以直接从数据库访问数据并将其加载到你的Python笔记本中,以便进行分析。


应用程序编程接口(API) 🔌

现在,假设你想扩展你的购买趋势分析,以更好地了解客户对你品牌的情绪。这类客户情绪信息可能最好从你公司的在线评论中找到。这些评论混合了结构化和非结构化数据,例如评论者所在的城市(结构化)和他们评论的原始文本(非结构化)。在线评论网站通常会为你和其他愿意付费的人提供一种方式,通过应用程序编程接口(API)直接访问这些评论数据。

API允许你直接通过公司的服务器请求数据,这是他们为此目的提供的。API对你很有用,因为它提供了对另一个平台收集的、高质量且最新的信息的实时访问。


网络爬虫 🕸️

现在,假设你想将跟踪竞争对手价格作为你购买趋势分析的一部分。这些数据通常不会由你的竞争对手通过平面文件、数据库或API直接提供。他们不会让你轻易获取。如果其他方法都不可用,你可以尝试从他们的网站上抓取数据。

网络爬虫的过程包括编写代码来访问该网站,识别你需要的部分(如价格表),并将该数据保存为结构化格式。然后,你可以像处理任何其他数据一样,通过将其加载到Pandas数据框中来分析这些数据。


总结

本节课中,我们一起学习了数据分析中四种主要的数据来源:平面文件数据库API网络爬虫。来自所有这些来源的数据都可以加载到你的Python笔记本中进行处理和分析。根据其来源,数据将需要不同级别的预处理,即你在分析之前采取的步骤。请跟随我进入下一个视频,看看如何进行这些预处理步骤。

005:数据清洗与处理 🧹

在本节课中,我们将要学习如何将原始、混乱的数据转化为适合分析的形式。我们将了解数据预处理和数据清洗的核心概念、常见步骤以及它们之间的区别。

收集到的数据很少能直接用于分析。

为什么需要预处理?🤔

上一节我们介绍了数据分析的起点是数据。本节中我们来看看,为什么原始数据通常不能直接使用。

如果你学习过之前的数据分析基础课程,你探索过“Ask a Manager”薪资数据集。该数据集包含了数千份回复,每个人分享了其职位、行业和薪资的详细信息。虽然这个数据是一个极好的资源,但它也存在许多不一致之处。

以下是2019年“Ask a Manager”薪资调查结果的另一个视图。每一行代表一份调查回复,每一列是调查中的一个特征或问题。

想象一下,你想计算所有回复的平均薪资。这似乎是一个简单的问题。然而,如果你查看E列(你的年薪),会发现许多不同的格式。第一行没有逗号,而第二行和第三行有。第15行包含一个尾随的美元符号和空格,而不是逗号。从其在单元格左侧对齐的方式可以看出,Google Sheets实际上将其视为文本而非数字。需要一些工作来确保这些数据格式一致,以便正确分析所有结果。

假设你想分析参与调查者的所在地点。查看G列(“你位于哪里?”)。这是一个非常模糊的问题,自由文本回复导致了多种不同类型的答案。前三行是美国城市,格式良好。但第6行是英国的一个城市。下一行也在英国,但包含一个更广泛的区域,可能是因为回复者想保护其身份。分析“回复者来自哪里”这类看似简单的问题,实际上非常具有挑战性。

什么是数据预处理?🔧

上一节我们看到了原始数据中的不一致性。本节中,我们将正式定义数据预处理。

你需要对此数据执行一些预处理,为分析做准备。预处理总是从原始数据开始,原始数据是处于原始形式的未处理信息。然后,你将采取措施确保数据更适合分析。

以下是常见的预处理步骤:

  • 删除重复项:移除完全相同的数据行。
  • 处理缺失值:处理数据中的空白或缺失信息。
  • 处理异常值:识别和处理与大多数数据显著不同的极端值。
  • 修复不一致的格式:统一日期、货币、文本等格式。
  • 选择特征子集:仅保留与分析相关的列。
  • 将值缩放到共同范围:例如,将不同尺度的数值归一化。
  • 编码分类变量:将文本类别(如“男”、“女”)转换为数字形式(如0, 1)。

可以看到,预处理是一个广义的术语。有时你在转换数据,有时你只是在选择与当前业务问题最相关的内容。你采取的预处理步骤将始终取决于具体的业务问题。

预处理 vs. 数据清洗 🧽

上一节我们列出了预处理的常见步骤。本节中,我们来区分两个密切相关的概念:数据预处理和数据清洗。

你可能听过“数据清洗”这个术语,其含义与数据预处理相同。确实,许多专业人士交替使用这些术语,尽管它们的含义略有不同。

数据预处理 指的是你为准备原始数据进行分析而采取的所有步骤。这包括从过滤、转换和组织数据到使其与特定分析目标保持一致的一切。它也包括数据清洗。

数据清洗 是预处理的一个子集,专门侧重于修复数据中的问题,例如纠正错误、修复不一致性和删除重复项。例如,修复产品名称中的拼写错误就属于数据清洗,因为你明确地在纠正原始数据。

因此,虽然这些术语在实践中有所重叠,但数据清洗指的是更大预处理过程中的一个特定步骤。

预处理后的步骤:验证与分析 ✅

在完成预处理之后,还需要进行验证,才能进入最终的分析阶段。

预处理后,你需要验证数据集,以确保数据符合你的预期,并且预处理没有引入任何新问题。例如:所有产品名称都标准化了吗?所有缺失值都处理了吗?所有地点的销售数量都合理吗?

一旦你对数据的有效性有信心,就可以进入分析步骤,开始寻找一些有价值的见解。

总结 📝

本节课中我们一起学习了数据预处理的重要性。我们了解到原始数据通常包含不一致、错误和多种格式,无法直接分析。数据预处理是一个涵盖数据清洗、转换和组织的广泛过程,旨在使数据适合分析。数据清洗是其中的一个关键子步骤,专注于修复具体问题。预处理完成后,必须进行验证以确保数据质量,然后才能进行深入分析,获得可靠的洞察。

现在你已经了解了为什么数据预处理是分析前至关重要的一步,请跟随我进入下一个视频,在那里你将看到预处理可以在何时发生。希望你能继续学习。

006:ETL 与 ELT 🔄

在本节课中,我们将要学习数据在分析前通常需要经历的两种核心预处理流程:ETL 和 ELT。理解这两种模式的区别,能帮助你更好地与数据团队协作,并明确你手头数据的处理状态。

数据在准备好进行分析之前,通常会经历许多预处理步骤。

根据所需的预处理类型,这些步骤可能发生在数据生命周期的不同节点。如果你学习过之前的数据分析基础课程,可能会记得这张从数据收集、处理、存储、分析到交付的生态系统图。在本课程中你已经了解到,这里的“存储”通常指像 CSV 这样的平面文件或数据库,而“分析”通常在 Python Notebook 中进行。数据收集可能由他人完成,也可能根据你的用例,通过 API 或网络爬虫自行完成。请注意,此图在多个阶段包含了预处理。根据你的业务问题,你可能希望在存储之前或之后进行处理。

ETL:提取、转换、加载

在数据存储之前进行处理,称为 ETL(提取、转换、加载),即图中左侧的模块。

  • 提取 本质上是收集数据。
  • 转换 指的是预处理。
  • 加载 是指将数据保存为可供分析的格式,例如数据库或 CSV。

你可以将 ETL 中的转换视为每个人都需要的预处理。例如,如果你的公司有来自不同门店的销售数据,数据团队可能会标准化数据格式、清理客户姓名、移除明显的重复项等。

ELT:提取、加载、转换

如果在数据存储之后进行处理,则称为 ELT(提取、加载、转换),即图中右侧的模块。数据被收集后,先加载到你的 Python Notebook 中,然后再进行预处理和分析。这种方式相对不那么常见,数据团队会按原样加载数据,或将部分预处理留到后期进行。这通常在数据下游使用者有不同需求时采用。例如,如果团队定期收集在线评论,他们可能就按原样保存,以防不同的分析师(比如你自己)想对这些评论进行不同类型的分析。

上一节我们介绍了 ETL 和 ELT 的基本概念,本节中我们来详细看看 ETL 流程的每一步。

深入 ETL 步骤

以下是 ETL 流程的三个核心步骤:

提取
ETL 流程的第一步是提取,即从 CSV 文件、API 或网站等各种来源拉取数据。提取的目标是高效地检索数据,同时确保数据源的完整性。例如,当从 CSV 文件中提取数据以在 Python Notebook 中分析时,你需要确保文件在此过程中不会被覆盖或编辑,这样数据在流程中才能保持一致和可靠。

转换
提取之后,数据被转换。例如,你或数据团队的其他成员可能将所有数据格式转换为一致的结构(如 年-月-日),或移除重复行。这种转换确保了数据已准备好进行分析。

加载
最后,转换后的数据可以被加载到可供分析的格式中,如平面文件或数据库。然后,你可以使用像 Python Notebook 这样的工具读入数据进行分析。这一步将数据带入你已经熟悉的格式,让你无需执行大量预处理即可开始分析。

ELT 流程的特点

在 ELT 中,原始数据首先被加载到可供分析的格式(如 CSV 或数据库)中,转换可以稍后进行。企业通常在数据是非结构化或快速变化(如社交媒体内容)的情况下采用这种方法。但使用 ELT 流程也意味着,数据在使用前可能需要在 Python 或其他工具中进行额外的转换步骤。

在实践中,ETL 和 ELT 之间存在一个连续谱。


一些预处理步骤可能发生在将数据加载到数据库或 CSV 之前,而另一些步骤可能由你个人执行。正如你刚才看到的,在加载前发生的转换将确保数据完整性并支持一致的分析,且不应阻碍后续分析。例如,你的数据工程师可能会标准化来自不同地区的数据格式,但他们不太可能从时间戳中移除毫秒,即使这些数据对许多应用来说似乎不必要。这些数据通常会被保留,以防万一有用。

总结与回顾

本节课中我们一起学习了 ETL 和 ELT 这两种移动和准备数据以供分析的重要流程。虽然你可能不直接负责建立这些流程,但理解它们的工作原理将使你能够与数据工程师有效协作。更重要的是,了解你的数据是经过了 ETL 还是 ELT,意味着你能更好地理解其局限性,并有能力在你的分析中充分利用它。

在第一课中做得很好!接下来你将完成练习作业以测试所学知识。完成后,请加入下一课,我们将动手实践网络爬虫和数据清理。


007:网络爬虫介绍 🕷️

在本节课中,我们将要学习网络爬虫的基本概念、工作原理及其在数据分析中的价值。网络爬虫是一种从网站提取数据的技术,尤其适用于当其他数据源无法提供所需信息时。

什么是网络爬虫?

上一节我们介绍了数据收集的多种方法,本节中我们来看看网络爬虫的具体定义。网络爬虫是从网站提取数据的过程。在之前的课程中你了解到,当没有其他数据源能提供所需信息时,网络爬虫通常非常有用。

例如,假设你作为一名数据分析师,正在为一家在线宠物商店研究竞争对手的价格。你所需的数据可能分散在多个电子商务网站上,且没有简单的下载方式。在本课程中你已经遇到的数据收集方法,即下载所需数据的平面文件,在这里并不适用。

你可以手动访问不同的竞争对手网站,复制每个产品的价格,并将这些信息输入电子表格,但这将是一个耗时且容易出错的过程。为何不让计算机为你完成这项工作?这就是网络爬虫背后的理念:使用代码访问不同网站并收集你所需的数据。

如果你将网站想象成一个平面,那么网络爬虫本质上就是从该平面上“刮取”内容以进行处理和分析,因此得名“爬虫”。

网络爬虫如何工作?

网络爬虫本质上将设计供人类查看的信息转换为适合分析的数据。网站通常被格式化为人们提供视觉上吸引人的体验,包含菜单、图像等。虽然这对用户来说很好,但对分析师来说远非理想。事实上,这与数据框几乎相反,在数据框中你希望所有内容都整齐地排列成行和列,没有额外信息。

在网络爬虫中,你需要浏览构成网站的一大堆非结构化文本和代码,然后使用不同的编码技术仅提取你需要的数据,通常以行和列等结构化格式呈现。

以下是一个快速示例:假设你正在网上爬取寄居蟹入门套件的价格,以帮助你更好地为自己的产品定价。这是来自宠物用品商店的一个示例产品页面,显示了你感兴趣的寄居蟹入门套件。你感兴趣的是产品名称、价格,可能还有描述(此处称为“详细信息”)。

这里有很多不相关的信息。事实上,比表面上看到的要多。如果你右键单击此页面,然后选择“查看页面源代码”,你可能需要开启开发者工具,实际上可以看到这里有大量信息。

网络爬虫的挑战与优势

网络爬虫涉及编写计算机程序来解析所有这些非结构化的文本和代码以找到价格。价格大约在页面三分之一的位置。不用担心所有这些代码,你将看到使用Python,这项任务会变得更容易管理。

酷的是,一旦你编写了爬取此页面的程序,你很可能可以重复使用它。这是另一个水龟套件的产品页面。请注意,标题、价格和描述都位于相同的位置。一个常见的网络爬虫范式是拥有一个你想要收集信息的所有网站的列表,并使用相同的代码爬取每个网站。

网络爬虫为数据分析师带来了许多挑战。首先,并非所有网站都组织良好,这使得你的爬虫难以定位你需要的数据。例如,假设你正在爬取宠物用品的产品价格,如果产品正在促销,价格可能位于页面上与通常不同的位置,这可能导致你的数据中任何打折产品的价格缺失。

动态内容也给爬虫带来了问题。即使一个网站有清晰的结构,它也可能更新其布局或内容,这可能导致你的爬虫程序失效。网络爬虫通常需要维护,以确保它们能跟上这类变化。但是,如果网站结构保持一致,而内容是动态的,你的网络爬虫将真正发挥作用。

例如,你之前看到产品页面通常以一致的方式格式化。你可以编写一个爬虫来查找宠物网站上每个产品的价格。网络爬虫将非结构化信息转化为有价值的数据集,为你节省大量时间。

总结

本节课中我们一起学习了网络爬虫的基本概念。我们了解到网络爬虫是一种从网站提取数据的技术,尤其适用于没有现成数据源的情况。它通过解析网站的HTML代码,将非结构化的网页内容转换为结构化的数据,如行和列。虽然网络爬虫面临网站结构不一致、动态内容等挑战,但它能极大地提高数据收集的效率。在接下来的视频中,我们将学习如何使用Python编写代码来爬取表格数据。

008:使用Pandas抓取表格数据 📊

在本节课中,我们将学习如何使用Pandas库从网页中抓取表格数据,并将其转换为适合分析的结构化格式。我们将通过一个实际案例——为国际援助组织研究全球人口和宠物数量——来掌握这一技能。

概述

在上一节视频中,我们了解到网络爬虫可以将非结构化的网页内容转换为适合分析的数据。本节中,我们将动手实践,学习如何使用Pandas的read_html函数来抓取网页上的表格数据。

熟悉网页内容

在从任何网页抓取数据之前,需要先熟悉其内容。我们使用的网页改编自维基百科的“农村人口”页面。该页面以表格形式呈现数据,每一行代表一个国家或地区,每一列代表该国家或地区的某个特征,例如2022年或2023年的人口、狗的数量、所属区域、官方语言等。

这种表格格式与DataFrame非常相似。如果数据已经在DataFrame中,我们就可以计算人均狗的数量,进行排序,并执行其他分析。

需要注意的是,数据中存在大量缺失值。例如,我们有印度的狗数量,但没有猫或鸟的数量。在分析时需要留意这一点。

开始抓取数据

让我们打开一个新的Python笔记本来开始抓取这个页面。你可以使用练习实验室项目来跟随演示。

首先,导入Pandas库,这是我们在之前课程中已经熟悉的操作。

import pandas as pd

接下来,将目标网页的URL保存为一个字符串变量。

url = ‘网页URL地址‘

Pandas提供了一个名为read_html的函数,它可以将网页中存储的表格数据转换为DataFrame。这个过程比较复杂,因此并不总是完美运行,并且只适用于以特定方式存储的表格。

tables = pd.read_html(url)

运行这行代码后,Pandas会访问该网址,查找所有格式正确的表格,并将它们存储在一个列表tables中。

检查抓取结果

现在检查tables变量的类型,它是一个列表。

type(tables)

检查这个列表的长度,确认其中只有一个表格,这与网页上只有一个表格的情况相符。

len(tables)

要显示这个表格,需要获取列表中的第一个元素。

df = tables[0]

检查df的类型,确认它是一个DataFrame。

type(df)

因为是DataFrame,所以可以使用.head()方法查看前五行数据。

df.head()

这前五行数据与我们之前在网页上看到的表格完全一致。为了在本节课后续操作中更方便,我们将其保存在变量df中。

最后,使用.info()方法查看数据的摘要信息。

df.info()

结果显示有238行和12列。“非空计数”列显示,只有38行有狗的数量数据,这意味着我们只有大约15%的国家有相关数据。此外,Pandas自动检测到了许多数值列,但“变化百分比”这一列虽然应该是数值,却被表示为“object”类型。在Pandas中,“object”通常代表文本,当Pandas不确定如何安全地将数据转换为整数或浮点数时,会默认将其视为文本。

本节总结

在本节中,我们一起学习了如何使用pd.read_html()从网页抓取表格数据。

  • pd.read_html()函数提取网页中的表格,并将其存储在DataFrame列表中。
  • 该函数接受一个参数,即要抓取的网页URL(字符串形式)。
  • read_html是快速获取表格数据的方法,但要求表格结构正确。
  • 从函数返回的列表中提取出相关的DataFrame后,就可以使用.head().info()以及之前课程中学过的任何其他DataFrame函数和方法来检查数据。

做得很好!你已经看到,使用pd.read_html是提取网站所有表格的快捷方法。请跟随我进入下一个视频,在那里你将学习如何清理字符串数据以进行分析。

009:替换方法 📝

在本节课中,我们将学习如何使用Python中的字符串替换方法,来清理从网页抓取的数据。数据常常包含不需要的字符(例如百分号),这会影响后续的数值计算。我们将重点学习.replace()方法以及如何在Pandas数据框中正确应用它。


概述

上一节我们介绍了从网站抓取的数据可能存在混乱或不完整的情况。本节中,我们来看看如何使用文本处理方法进行数据清洗。具体来说,我们将学习如何移除字符串中不需要的字符,例如百分比符号,以便将文本数据转换为可用于计算的数值类型。

数据背景回顾

作为国际援助组织的数据分析师,你的任务是研究全球人口数据,以便更合理地分配援助人员。你抓取了一个包含世界各国及其人口规模的数据框。在分析过程中,你发现“年度人口变化百分比”这一列被存储为文本(对象类型),而不是数值类型(浮点数)。这给计算带来了不便。

例如,如果你尝试使用.mean()方法计算全球所有国家的平均增长率,会遇到错误。

# 尝试计算平均增长率会导致错误
df['Percent Change'].mean()

错误信息显示:“无法将字符串(如‘+0.88%’)转换为数值”。这表明,在自动将这些对象转换为浮点数之前,需要先移除百分号。

字符串替换方法介绍

.replace()方法允许你将字符串的一部分替换为另一部分。它还可以用于完全移除某些字符,例如百分号。

基本替换示例

假设你有一个包含四个国家名称的列表,并希望将“United States”替换为“USA”。

countries = ["China", "India", "United States", "Indonesia"]
countries_replaced = [country.replace("United States", "USA") for country in countries]
print(countries_replaced)
# 输出:['China', 'India', 'USA', 'Indonesia']

移除字符示例

要移除字符串中的百分号,你可以将百分号替换为空字符串。

value = "+0.88%"
clean_value = value.replace("%", "")
print(clean_value)
# 输出:'+0.88'

注意:空字符串不是空格,它不包含任何字符。

在Pandas数据框中应用替换

现在,我们希望在整个数据框的列上执行此操作,移除每个单元格中的百分号。

初次尝试(常见错误)

直接对列使用.replace()方法可能不会按预期工作。

df['Percent Change'].replace("%", "")

运行后,该列看起来与之前完全相同。这是因为Pandas的.replace()方法旨在替换整个单元格的值,而不是值的一部分。当列被当作一个整体字符串处理时,此方法无效。

正确方法:使用.str访问器

要解决这个问题,我们需要使用.str.replace()方法。.str是一个访问器,它允许我们将字符串方法应用于Series(列)中的每个元素。

以下是正确的操作步骤:

# 使用.str.replace()移除百分号
df['Percent Change'] = df['Percent Change'].str.replace("%", "")

现在,再次查看该列的前五个值:

df['Percent Change'].head()

你应该会看到正负浮点数,不再包含百分号。

关键概念总结

以下是本节课的核心概念:

  1. .replace()方法:用于替换字符串中的特定部分。
    • 公式/代码string.replace(old, new)
  2. .str访问器:在Pandas中,用于对Series中的每个元素应用字符串方法。
    • 公式/代码series.str.method()
  3. 向量化操作.str和之前学过的.dt(用于日期时间)都是向量化访问器。这意味着它们可以一次性对整个列应用更改,而无需编写循环,从而提高了效率。

总结

本节课中,我们一起学习了如何使用.replace()方法清理文本数据。我们了解到,直接对Pandas列使用.replace()可能无法处理单元格内的部分字符串,而需要通过.str.replace()来正确执行操作。数据并不总是干净且可以直接分析的,但借助.replace()等工具,我们可以将杂乱的字符串转换为可用的数据。

在下一节视频中,我们将更进一步,学习如何将这些清理后的字符串转换为数值类型,以便进行数学运算和分析。

010:类型转换 🧩

在本节课中,我们将学习如何在Python数据分析中,将数据从一种类型转换为另一种类型。你将了解到,信息的类型决定了可以对其执行哪些操作。有时数据以一种类型呈现,但你需要以另一种类型处理它。这时,你可以通过“类型转换”来改变数据类型。这是一个常见任务,尤其是在处理网络爬取或文本密集型数据集时。

回顾与情境

上一节我们介绍了用于数据清洗的第一个文本处理方法——replace。本节中,我们来看看另一个强大的工具:类型转换。

你实际上已经在Python中遇到过类型转换:pd.to_datetime函数将字符串转换为日期类型。尽管你完全没有改变信息本身,只是改变了它的类型,但这个函数开启了新的可能性,例如能够使用weekday方法来查找日期的星期几。

让我们看看类型转换的实际应用。

简单回顾一下,你作为国际援助组织的数据分析师,任务是研究国际人口和动物种群,以便在全球更好地分配援助人员。你爬取了一个包含世界各国及其人口规模的有用数据框来进行分析。

你一直在本笔记本中工作,爬取了一个世界人口表格并将其保存在变量df中。你还从“变化百分比”列中移除了百分号,为类型转换做好了准备。此时,该列中的数据仍然是对象类型(即文本),尝试计算平均百分比变化仍然会产生错误。


使用 astype 方法进行转换

但是,你可以使用astype方法转换这一列。astype接受一个参数:一个字符串,代表你想要转换成的目标类型。

对于百分比变化,最合适的类型是浮点数,你可以用字符串'float64'来指定。float64是pandas中一种特殊的浮点数类型,它也允许缺失值。这在处理网络爬取数据时非常有用,因为缺失值(NaN)非常常见,而float64可以妥善处理它们。

像你已经见过的许多方法一样,astype不会自动更改原始数据框,因此请确保将转换后的列保存回原始列中。

以下是转换步骤的代码:

df['change_percent'] = df['change_percent'].astype('float64')

运行df['change_percent'].head(),这些值看起来干净整洁,类型是float64(即浮点数)。现在,你可以计算平均增长率了。

average_growth = df['change_percent'].mean()

年增长率大约在1%左右。

再次检查数据类型,还有其他列可以转换吗?大部分其他列看起来都很好。唯一可以考虑的是将这些人口列转换为整数而不是浮点数。

核心概念总结

在本视频中,你学习了可以使用astype方法将pandas数据框中的列转换为另一种类型。该方法接受一个参数:你想要转换成的目标类型的字符串。

你成功地将一列转换为float64,这是一种特殊的pandas类型,能够妥善处理缺失值,而你在数据的初步检查中已经识别出了这些缺失值。

后续内容预告

在本视频中,你看到在数据预处理过程中规划缺失值至关重要。来自网络的数据通常有很多缺失值。在下一个视频中,你将看到处理缺失数据的常用技术。

本节课总结

本节课中,我们一起学习了:

  1. 数据类型的重要性:数据的类型决定了可对其执行的操作。
  2. 类型转换的概念:将数据从一种类型(如文本)转换为另一种更合适的类型(如数字)。
  3. astype方法的使用:通过df[‘column_name’].astype(‘target_type’)的语法进行转换。
  4. float64类型的优势:特别适合处理包含缺失值(NaN)的数据。
  5. 转换后的验证:转换后应检查数据类型并尝试进行计算,以确保转换成功。

通过掌握类型转换,你能够将原始、杂乱的文本数据转化为可以进行数学计算和深入分析的规整数值数据,这是数据清洗和预处理的关键一步。

011:缺失值处理 🧩

在本节课中,我们将要学习如何处理数据集中常见的缺失值问题。缺失值会影响分析的准确性,因此掌握有效的处理方法至关重要。

在之前的课程中,我们学习了如何从网页抓取的表格中提取信息。然而,提取的数据常常是不完整的。事实上,缺失值是处理真实世界数据集时的一个常见问题。

缺失值的影响

缺失数据指的是数据集中预期存在但实际缺失的值。数据缺失的原因可能包括人为错误、数据收集不完整或技术问题。

缺失数据对分析的影响取决于缺失数据的性质和范围。如果缺失的观测值比例很大,分析结果可能无法推广。例如,假设你正在收集城市居民的人口统计数据,但只有5%的受访者报告了他们的收入。即使使用推断统计,你也可能没有足够的数据将结果推广到整个城市。

其次,数据缺失通常是有系统性的,而非随机的。在人口统计数据中,高收入人群往往不太愿意透露自己的收入。因此,你的数据更有可能缺失较高范围的收入,这可能导致你的分析得出城市平均收入低于真实水平的错误结论。

处理缺失值的选项

为了处理缺失数据,你有几个选择。

选项一:删除缺失值

一种选择是删除包含缺失值的行或列。这个过程通常被称为删除行或列。

以下是考虑删除行或列的情况:

  • 删除行:如果只有一小部分数据缺失,可以考虑删除行。例如,如果你正在分析人口统计数据,只有1%的居民没有分享他们的年龄,你可以考虑在年龄分析中直接移除这些居民。
  • 删除列:当某一列有大量数据缺失时,可以考虑删除该列。在95%收入数据缺失的情况下,你可以选择从分析中移除这个特征。

选项二:填充缺失值

另一个选择是填充缺失值。你应该谨慎地填充值,并且只在你的假设合理的情况下进行。

例如,在你的人口数据中,假设你询问了就业状况和每周工作时间。如果一个人回答是全职工作但拒绝分享每周工作时间,你可以考虑用40小时来填充缺失值。一个无效的方法是用零来填充这些缺失值,因为这不符合数据本身的逻辑假设。

更复杂的方法是用描述性统计量来填充缺失值,例如均值、众数,甚至使用回归或机器学习来填充最佳值。

实战演练:在Notebook中处理缺失值

让我们通过一个Notebook示例来看看如何处理缺失值。

回顾一下,到目前为止,我们已经从网页上抓取了这个人口数据表,并在适当的地方将值转换为浮点数。假设你被要求估算每个县处理犬只数量所需的志愿者人数。

使用 df.info() 方法,你已经注意到“犬只数量”这一列有200个缺失值。

方法一:删除缺失行

如果你只想分析已有的确切数据,可以使用 dropna 方法来删除缺失“犬只数量”的行。你可以使用名为 subset 的参数,它应该是一个列名列表。

df_with_dogs = df.dropna(subset=['dog_population'])

现在,检查 df_with_dogs.info()。你只剩下38个县的数据,并且“犬只数量”列没有缺失值了(尽管其他动物的数量仍有缺失)。你可以从这里继续分析,估算这些县所需的援助人员数量。

这是一种方法。但是,有没有更复杂的方法可以让你为所有县创建估算值呢?

方法二:填充缺失值

一个想法是用合理的猜测来填充原始数据集中的这些缺失值。例如,你可以计算每个县的人均犬只数量,然后用该县的人口乘以人均比率来填充缺失值。让我们试一试。

首先,在原始数据框中创建一个新列 dogs_per_capita,即犬只数量除以2023年人口。

df['dogs_per_capita'] = df['dog_population'] / df['population_2023']

使用 .describe() 获取该列的基本统计信息,你会看到人均犬只数量的平均值是0.137。让我们用这个比例来填充缺失值。

df['dog_population'] = df['dog_population'].fillna(df['population_2023'] * 0.137)

fillna 在这里很有用,因为它会保留任何现有值,只填充缺失的值。

很好,再次查看 df.info(),现在“犬只数量”列有238个非空值。通过 df['dog_population'].describe(),你可以看到均值和标准差,这可能有助于你估算所需的援助人员数量。

总结

在本节课中,我们一起学习了如何处理缺失数据。

  • 你可以使用 dropna 方法删除包含缺失值的行,这确保了计算基于非缺失数据。你使用了名为 subset 的参数并传入列名列表,以删除在指定列中任何有缺失值的行。
  • 你也可以用统计量(如均值、中位数或众数)来填充缺失值,这在缺失数据更普遍时很有帮助。你看到 fillna 方法只会用你指定的值填充缺失值。

尽管缺失值会使你的分析变得复杂,但你现在已经掌握了几个处理它们的好方法。在下一节课中,你将学习一个新的文本处理方法:contains

012:字符串处理之包含检测 🔍

在本节课中,我们将学习一种常见的数据清洗方法:识别数据中的模式。具体来说,我们将学习如何在文本特征中搜索特定的单词或短语,并以此为基础创建新的数据列或筛选数据。

假设你正在为一个国际援助组织工作,目标是找出最容易部署援助人员的国家。你的援助人员主要会说英语或德语。因此,你希望首先将分析范围限定在那些官方语言中包含英语或德语的国家。我们将通过代码示例来演示如何实现这一目标。

回顾与准备

在开始之前,我们先简要回顾一下之前的工作。你已经成功地将一个人口数据表读入变量 df 中,适当地转换了数据类型,并填充了“狗数量”列中的缺失值。

上一节我们介绍了数据读取和基础清洗,本节中我们来看看如何基于文本内容进行更精细的筛选。

检测字符串包含关系

我们的目标是创建一个名为 English 的新列。如果“官方语言”列中包含“English”,则该列的值应为 True

一种简单的方法是检查“官方语言”是否完全等于“English”。让我们先尝试这种方法。

df['English'] = df['official_language'] == 'English'

执行这段代码没有报错。然而,我们需要手动验证一下数据。查看 df.head() 时,你会发现索引为1的行(对应印度)显示为 False,但这并不正确,因为印度的官方语言包含印地语和英语。

因此,我们需要一种策略来检查“English”是否出现在这个字符串中,而不仅仅是字符串是否完全等于“English”。

使用 str.contains() 方法

你可以使用 Pandas 的 Series.str.contains() 方法。其用法类似于之前学过的 Series.str.replace()

以下是创建 English 列的正确方法:

df['English'] = df['official_language'].str.contains('English')

现在再次检查这一列,你会发现印度对应的值变成了 True,这符合我们的预期。

接着,你可以用这个布尔列来筛选数据框:

english_df = df[df['English'] == True]

这个新数据框 english_df 的长度是56,意味着有56个国家的官方语言包含英语。你还可以进行其他有用的操作,例如计算这些国家的总人口:

total_population = english_df['population'].sum()

结果显示,超过30亿人生活在以英语为官方语言的国家。

关于大小写敏感性的重要说明

需要注意的是,str.contains() 默认是大小写敏感的。为了演示这一点,你可以分别检查包含大写“English”、小写“english”和全大写“ENGLISH”的国家数量:

# 检查大写 ‘English’
count_upper = df['official_language'].str.contains('English').sum()
# 检查小写 ‘english’
count_lower = df['official_language'].str.contains('english').sum()
# 检查全大写 ‘ENGLISH’
count_all_upper = df['official_language'].str.contains('ENGLISH').sum()

在这个数据集中,由于数据相对规整,所以只有大写“English”能匹配到56个国家,其他格式都匹配不到。但为了确保万无一失,你可以添加参数 case=False 来忽略大小写:

df['English'] = df['official_language'].str.contains('English', case=False)

设置 case=False 可以确保你捕获所有格式的“English”实例。再次对 English 列求和,结果仍然是56。

扩展应用:检测其他语言

使用相同的方法,你可以轻松地创建检测其他语言的列。例如,创建一个检测德语的列:

df['German'] = df['official_language'].str.contains('German', case=False)

结果显示,有6个国家的官方语言包含德语。

以下是本课中用到的主要方法总结:

  • Series.str.contains(substring):检查序列中的每个字符串是否包含指定的子字符串。
  • case=False 参数:使检测忽略大小写。

课程总结

本节课中我们一起学习了如何使用 Pandas 的 str.contains() 方法,这是一个基于文本数据创建新列的强大工具。它允许你识别包含特定子字符串的行。我们还了解到该方法默认区分大小写,但可以通过设置 case=False 参数来确保找到所有实例。

通过本课的学习,你为数据清洗工具箱添加了一个强大的文本过滤工具。接下来,我们将更进一步,探索清洗、转换文本数据以及从中提取洞察的更多技巧。

013:分割与修剪 📝

在本节课中,我们将学习如何使用Python的splitstrip方法来处理字符串数据。这些技巧在数据清洗中非常实用,能帮助我们清理文本中的多余字符和空白。


概述

上一节我们介绍了pandascontains方法及其在数据清洗中的应用。本节中,我们来看看如何分割字符串以及去除字符串首尾的空白字符。我们将继续使用一个国际援助组织的数据集,目标是清理国家名称列中的脚注引用和多余空格。

数据现状回顾

首先,回顾一下我们已经完成的工作。我们读取了人口数据表到变量df中,适当地转换了数据类型,并填充了“人口”列中的缺失值。我们还创建了列来标识以英语和德语为官方语言的国家。

现在,查看一下数据的前几行。你注意到索引为2的行(即“国家”列的第三行)有什么问题吗?




“国家”列中多出了一些额外文本,对吧?原始网站包含了一些脚注,用于解释特定国家的特殊情况(例如是否包含某些领土)。这些脚注引用对于我们的分析并非必需,反而会使数据变得杂乱。我们需要清理它们。

使用split方法分割字符串

你的第一反应可能是尝试使用str.replace方法来移除它们,就像在之前的视频中所做的那样。这是一个很好的直觉,但这次的问题更复杂,因为每个脚注引用都是不同的字母。

要清除这些脚注,可以使用split方法。该方法在指定的字符处将字符串分割成多个部分。

例如,假设你有一个字符串"China [a]"存储在变量text中。你可以使用以下命令在开括号字符[处进行分割:

substrs = text.split('[')

检查substrs,你会发现它实际上是一个包含两个元素的列表。字符串被分割了,现在你得到了两个部分。请注意,你用来分割的字符(开括号)已被移除。

那么,要如何访问第一部分“China”呢?你可以使用substrs[0]

split应用于整个列

与其他已学的字符串方法(如replacecontains)一样,你需要使用.str访问器。

在将结果保存回列之前,先看看它是什么样子。从df['country'].str.split('[')开始,这会为数据中的每一行生成一个列表。

但这并不是我们想要的结果。直接访问索引0处的项目,只会给你这一列的第一个值,而这个值本身仍然是一个列表。

这时,我们可以向LLM(大语言模型)寻求帮助。提示可以是:“我正在尝试通过像在开括号处分割'China [a]'这样的字符串来移除脚注引用。以下是我的代码。我该如何访问每个列表的第一个值并将其保存回‘country’列?”

LLM的回复指出,你需要再次使用.str访问器,并提供了一个代码片段。我们来尝试一下。

清理空白字符:strip方法

很好,现在让我们先用.str.contains检查是否还有括号。尝试检查闭括号],你会发现该列中已经不存在了,这很棒。

最后还有一点收尾工作。再次查看“国家”列,你注意到“China”有什么问题吗?

它多出了一些空白字符,这是在分割字符串时遗留下来的。为了清理这些空白,可以使用str.strip()方法,它会移除字符串首尾的空白字符。请务必将结果重新赋值给“国家”列。

现在看起来好多了!你已经从“国家”列中移除了所有的引用脚注。

总结

本节课我们一起学习了:

  1. 如何使用split方法将字符串拆分为子字符串列表。我们使用开括号字符将字符串分割成两部分,然后使用索引0从该列表中选取第一个字符串。这个方法使我们能够从“国家”列中移除脚注。
  2. 如何使用str.strip()方法来去除字符串首尾的空白字符,这是一个常见的文本处理步骤。“首部”指字符串开头,“尾部”指字符串结尾。.strip()会移除空格、制表符和换行符。

你在文本处理方面做得非常出色!我们已经在网页抓取的背景下,见识了许多处理Python文本数据的核心方法。

在下一个模块中,你将开始处理从网络上抓取的更复杂的数据。完成本课的练习评估和实践实验室后,希望你能继续加入我的学习。

014:网络请求基础 🕸️

在本节课中,我们将学习网络请求的基本概念。我们将了解客户端与服务器如何通过“请求-响应”周期进行通信,并探讨在网络爬虫过程中可能遇到的挑战。


概述

上一节我们介绍了如何从网页抓取数据并进行预处理。本节中,我们将退一步,审视这种涉及网络通信的编程新范式。网络通信类似于你的计算机(客户端)与提供网站内容的服务器之间的对话。理解这个过程对于成功进行网络数据抓取至关重要。

客户端与服务器的对话

网络通信可以看作客户端与服务器之间的对话。客户端向服务器发出请求,服务器则对该请求作出响应。这个完整的交互过程被称为“请求-响应”周期。

例如,当你使用 pd.read_html() 函数抓取网页时,你的计算机(客户端)会向你指定的URL发送一个请求。服务器则通过返回你请求的表格数据来响应。

这种网络通信不仅发生在Python代码中,也发生在你的浏览器里。当你访问Coursera网站时,你就是在向Coursera的服务器发送请求,而服务器则用其主页内容来响应。

通信协议:HTTP

网络通信依赖于协议,即确保客户端和服务器能够通信的标准化规则。对于网络爬虫而言,最重要的协议是HTTP(超文本传输协议)。

HTTP协议规定了客户端和服务器应如何格式化请求和响应。一个HTTP请求通常包含两个主要部分。

以下是HTTP请求的两个核心组成部分:

  1. 动词:定义你要执行的操作。对于网络爬虫,你通常使用 GET 来获取数据,但在某些情况下也可能使用 POSTPATCHDELETE 等其他动词。
  2. 路径:指明请求的目标地址。例如,你作为参数传递给 pd.read_html() 函数的URL,就充当了指向你所需数据的路径。

网络爬虫的挑战

在你之前的编程中,每个变量和函数都在你的控制之下。然而,网络爬虫涉及与互联网的交互,这个过程引入了新的挑战,因为爬虫的成功与否取决于你无法直接控制的外部因素。

与在Python中编码不同(你可以相当确定程序中的任何错误都可追溯到你自己的代码),网络通信会引入多种挑战。你可以把它想象成发短信向朋友借自行车。

以下是网络请求中可能遇到的几种情况,类似于向朋友借自行车:

  • 他们可能立刻热情地回复“可以”。
  • 他们可能过一段时间才回复你。
  • 他们可能根本不回复。
  • 他们可能回复说“不行,我的自行车正在修理”。

你在访问网站时会遇到同样类型的障碍。

服务器响应与状态码

网站可能响应缓慢。除了创建非常庞大的可视化图表,你之前的Python代码可能几乎都是瞬间运行的。但你肯定有过访问一个需要很长时间才能加载的网站的经历。网络爬虫也会经历同样的延迟。

更重要的是,发出请求并不保证一定能得到响应。事实上,网站通常会实施不同的措施来保护自己免受不必要的爬取。即使你得到了响应,也可能只是告诉你请求的页面不存在。

当服务器处理你的请求时,它会发回一个包含状态码的响应。这些代码帮助你理解你的请求是成功了,还是出了问题。

状态码是一个三位数,分为几类:

  • 代码 200 表示你的请求成功,你得到了所需的内容。
  • 4 开头的代码(如400或404)表示你这边出了问题,通常是你使用的路径服务器无法识别。
  • 5 开头的代码(如500或503)表示服务器端存在问题,问题不在你。例如,可能在你发送请求时,服务器正因维护而关闭。

总结

本节课中,我们一起学习了网络请求的基础知识。我们了解了客户端与服务器通过“请求-响应”周期进行通信,认识了HTTP协议及其请求结构(动词和路径),并探讨了网络爬虫中因依赖外部资源而带来的挑战,如响应延迟和服务器返回的各种状态码。虽然你通常不会手动编写请求-响应周期的每个细节,但理解其工作原理对于有效地进行网络数据抓取非常重要。

请跟随我到下一个视频,开始一项涉及更复杂网络爬虫的新任务。

015:使用requests库抓取网页 🌐

在本节课中,我们将学习如何使用Python的requests库从互联网上获取网页内容。这是进行网络数据抓取的第一步,为后续从复杂网页结构中提取所需信息打下基础。

概述

上一节我们介绍了从网页中预处理数据的基础。但现实中,所需的数据通常不会像之前看到的表格那样规整。因此,我们需要掌握从复杂格式中提取信息的创造性方法。

本节中,你将扮演一家专营天文设备(如望远镜)的零售商的数据分析师。公司希望根据天文事件来规划产品促销,以提升2030年的销售额。你的任务是识别即将到来的事件(例如流星雨),以便为每个事件设计主题优惠券。在搜索中,你发现了这个网站:一个包含2030年大量天文事件的日历。

像一月的流星雨这类事件非常适合营销推广。你的目标是从该网站提取日期、事件和描述信息,并将其导入Python笔记本进行处理。

获取网页内容

首先,导入pandas库。你可以使用练习项目来跟随演示。

import pandas as pd

保存目标网站的URL。

url = "https://www.seasky.org/astronomy/astronomy-calendar-2030.html"

你可能会首先尝试使用pd.read_html

tables = pd.read_html(url)

但你会遇到一个错误。请务必滚动到错误信息的底部查看最新问题,你会看到ValueError: No tables found。这是预料之中的,因为检查网站后你会发现页面上并没有表格。所以,pd.read_html在处理表格时很好用,但没有表格时就几乎无用。对于这个网站,你需要进行适当的网络抓取。

引入新的工具

为了抓取所需数据,你需要两个新模块:

  • requests:用于获取网页。
  • beautifulsoup4:用于解析网页(“解析”意味着查找并格式化正确的信息)。

之前,Pandas为你同时完成了这两个步骤(请求页面和解析)。现在,你需要自己分别完成这些步骤。

导入这些模块:

import requests
from bs4 import BeautifulSoup

现在,你可以使用requests模块向这个URL发送一个GET请求。GET请求意味着你要求返回一些信息。将响应保存在一个变量中,例如response。你可以为这个变量取任何你觉得有意义的名称。

response = requests.get(url)

运行单元格没有产生任何错误。但请记住,网络请求可能以各种方式失败。使用response.status_code直接检查状态码。注意,状态码是一个属性,而不是一个方法。

print(response.status_code)

结果是状态码200,这表示你成功收到了响应,没有任何错误。

现在,你可以打印response.content来查看网站发回了什么信息。

print(response.content)

输出内容非常多。这本质上与你回到原始页面点击“查看源代码”时看到的网站代码相同。

理解响应内容

在这段文本中,埋藏着你需要的所有数据。注意,这个response.content<!DOCTYPE html>这样的文本和尖括号开头。这个响应是一个HTML文档。换句话说,它是一个.html文件的内容。

一个小提示:如果你对笔记本中显示的大量文本感到困扰,可以随时向AI助手寻求缩短显示的建议,例如:“如何只显示这个字符串response.content的前1000个字符?”

本节回顾

总结一下,你已经了解了如何使用requests模块检索网页内容。

以下是关键步骤:

  1. 使用requests.get()函数向指定的网站URL发送GET请求。
  2. 将网站的URL作为字符串输入,函数会返回一个响应对象。
  3. 使用响应对象的.status_code属性来检查请求是否成功。
  4. 最后,使用.content属性获取网页的原始HTML,这本质上是完整的网站代码。

现在你已经看到了如何使用requests模块获取网站内容,接下来需要更好地理解这些内容是如何组织的。网络抓取通常涉及处理HTML文件。请跟随下一节视频学习它们的工作原理。

总结

本节课我们一起学习了网络数据抓取的第一步:使用requests库获取网页的原始HTML内容。我们了解了如何发送GET请求、检查响应状态码,并认识到原始HTML是后续信息提取的基础。在下一节中,我们将学习如何解析这些HTML内容以提取出我们真正需要的数据。

016:HTML基础 🏗️

在本节课中,我们将学习超文本标记语言(HTML)的基础知识。HTML是构成网页的骨架,理解其结构对于后续进行网页数据抓取至关重要。

概述

上一节我们介绍了如何使用 requests 模块获取网页,得到的是一个HTML文档。本节中,我们将深入探索HTML如何用于构建网页结构,以及这种结构如何影响网络爬虫的工作。

HTML元素与标签

几乎所有网页都使用超文本标记语言。你可以将HTML理解为通过标签增强的文本,这些标签提供了额外的信息。标签通常成对出现,包裹着某些内容。

例如,上一节视频中看到的C sky网站包含页面标题“Astronomy Calendar of Celestial Events for Calendar Year 2030”。这段文本被包裹在 <h1> 标签中。<h1> 表示一级标题,是最大的标题类型,因此浏览器知道将其格式化为大标题。注意,闭合标签包含一个斜杠 /。整个包含标签和内容的块被称为一个元素。所以这个标题就是一个 <h1> 元素。

常见的元素包括:

  • <h1><h6>: 用于标题。
  • <p>: 用于段落。
  • <a>: 代表“锚点”,用于链接到其他页面。

大多数元素像上面那样同时拥有开始和结束标签。HTML也包含一些自闭合标签,它们不需要单独的结束标签。最常见的自闭合标签是 <img>,用于图像。

HTML属性

HTML标签还可以拥有属性,这些属性提供了关于元素的额外信息,如大小、颜色、位置或内容来源。属性位于开始标签的尖括号内。

最常见的属性之一是 <a> 标签中的 hrefhref 属性代表“超文本引用”,它指定了点击该元素时,链接应跳转到的URL地址。

例如,C sky网站上的这个 <a> 标签包含内容“US Naval Observatory”,并链接到其网站 usno.navy.mil。如果没有 href 属性,这个 <a> 标签将无法链接到任何地方。

<a href="https://www.usno.navy.mil">US Naval Observatory</a>

让我们看看C sky网站上的这个图像标签。它包含四个属性:srcaltwidthheight

<img src="calendar-legend.jpeg" alt="Calendar Legend" width="200" height="100">
  • src 代表“源”,指定了要显示的图像的URL或文件路径。此图像显示来自C sky网站的 calendar-legend.jpeg
  • alt 代表“替代文本”,是图像加载失败时显示的文本。
  • widthheight 告诉浏览器显示图像的尺寸。

HTML类

HTML类是一种特殊类型的属性,允许你将元素分组,并对它们应用一致的样式或功能。类使用 class 属性在HTML标签内定义,通常分配给多个元素。

例如,注意C sky网站上的每个日期都以粗体和绿色格式化。网站没有为每个单独的元素设置颜色和样式,而是将这些元素分组到同一个类 date-text 中。

<span class="date-text">January 1</span>

使用类来分组元素可以使某些网页抓取任务更容易。在这种情况下,你可以使用一个类属性 date-text 来查找该网站中的每个日期(假设格式正确)。

HTML文档结构

虽然HTML标签描述了视觉元素的目的和外观,但整个HTML文档将各个视觉元素组织在一个层次结构中。许多元素可以包含其他元素。例如,一个段落元素 <p> 可能包含几个用于链接的锚元素 <a>

HTML的结构就像一棵树,元素被组织在其他元素内部。在树的根部,你会找到 <html> 标签,它包含特定网页的所有HTML内容。这种组织结构很重要,因为如果你在寻找一个特定的元素(比如包含天体事件的段落),你需要知道它在HTML树中的位置。

看看C sky网站的组织结构。它被组织成:

  • 顶部的页眉用于导航。
  • 左侧的边栏包含不同的天文页面。
  • 右侧是主要内容区域。

在这个主要内容区域内,有页面标题、指向不同年份的链接、总体描述,然后是不同类型事件的图例,最后才是各个事件本身。

容器元素

HTML包含几个容器元素,旨在通过分组其他元素来逻辑地组织内容。你会看到的最常见的容器是 <div>,是“division”(分区)的缩写。它是一个灵活的容器,充当存储多个元素的空盒子。一个 <div> 创建一个块,该块从新行开始,并占据页面上全部的可用宽度。

你还会看到:

  • <ul>:无序列表元素,本质上是项目符号列表。
  • <ol>:有序列表元素,即编号列表。
  • 这些列表都包含多个 <li>(列表项)元素。

你还会遇到 <span>,它通常用于组织小段文本。它的作用类似于 <div>,像一个存储多个元素的空盒子,但它不从新行开始,也不占据全部可用宽度。

HTML表格

在上一课中,你使用了 pd.read_html() 来从网站抓取表格。这个函数只适用于正确组织的 <table> 元素,这是一种容器。

以下是具有类似内容的HTML表格可能的样子:

<table>
  <tr>
    <td>Date</td>
    <td>Event</td>
  </tr>
  <tr>
    <td>Jan 1</td>
    <td>New Moon</td>
  </tr>
</table>

注意它相当复杂,行由 <tr> 指定,数据由 <td> 指定。pd.read_html() 在C sky网站上会报错,因为即使信息组织良好,但其中没有任何HTML表格元素。

总结

本节课中,我们一起学习了HTML如何构成网页的基础。你无需记忆本视频中看到的每个元素,随着你更多地使用它们,你会更好地理解它们。你随时可以与你的大语言模型交流,以帮助你回忆细节。

在下一节视频中,我们将学习如何使用 BeautifulSoup 来解析HTML,敬请关注。

017:HTML解析规划 📋

概述

在本节课中,我们将学习如何规划网页数据的解析过程。我们将分析一个天文事件网站的HTML结构,并制定一个清晰的步骤计划,以便后续使用代码提取所需信息。

上一节我们介绍了网站如何使用HTML构建结构。本节中我们来看看如何利用这些知识来规划数据解析任务。

分析HTML结构

你是一家天文设备零售商的数据分析师。公司希望根据天象事件来规划2030年的产品促销活动,以提升销量。为此,你的经理要求你识别即将发生的天象事件。

在开始解析网页之前,查看HTML结构有助于规划后续操作。右键点击页面并选择“检查”,然后导航到右侧栏的内容元素。

当你将鼠标悬停在每个元素上时,网页上对应的部分也会高亮显示。第三个div容器元素包含一个格式统一的事件无序列表。

每个事件都位于一个列表项标签内的段落标签中,该列表项包含多个元素。右键点击并选择“查看网页源代码”,然后导航到右侧栏内容元素。你也可以使用Ctrl+F搜索该元素在源代码中的位置。

首先,是一个带有date-text类的span标签,它包含事件日期。

其次,是另一个带有title-text类的span标签,包含事件名称。

最后是事件描述,它没有自己的容器标签。

规划解析逻辑

这个结构非常重要,因为它明确指出了每个信息片段的位置。由于每个事件都遵循相同的模式,你可以规划遍历右侧栏内容元素下的所有列表项标签。

从那里,你将通过定位它们各自的标签和类来提取日期、事件标题和描述。提前了解结构能使解析过程更高效,并确保在使用该结构解析数据时不会遗漏任何细节。

利用这个结构,让我们写出一些代码注释,来规划抓取这个网站时需要采取的行动。

以下是解析步骤的规划:

  1. 创建存储列表:首先创建一个列表来存储所有事件。初始时它是空的。
  2. 定位列表项:需要找到HTML中的所有列表项(li元素)。为了更精确,最好只查找带有任何类名的项,因为你看到这些列表项有诸如B9等类名。
  3. 遍历元素:创建一个循环来逐个检查这些项。
  4. 提取日期:识别带有date-text类的span标签,并将其保存到一个变量中。
  5. 提取标题:识别带有title-text类的span标签。
  6. 提取描述:识别段落(p)标签。
  7. 获取纯文本:最后,从每个元素中提取纯文本,丢弃HTML标签和属性。
  8. 组合并存储:创建一个包含该事件的日期、标题和描述的列表,并将其保存到你的事件总列表中。

这个过程初看起来可能很复杂,但随着你更多地使用HTML解析,你会越来越得心应手。

总结

在本节课中,我们一起学习了如何为复杂的数据预处理任务规划解析方法。我们分析了一个具体网页的HTML结构,识别了数据所在的关键标签和类,并据此制定了一个从查找元素到提取文本的完整步骤计划。

在开始编码前规划我们的方法对于复杂的预处理任务至关重要。请跟随我到下一个视频,在那里你将使用BeautifulSoup库来实现这个计划。

018:使用Beautiful Soup解析HTML 🕸️

在本节课中,我们将学习如何实现HTML解析计划,使用Beautiful Soup库从网页中提取结构化数据,并将其整理成可供分析的DataFrame。


上一节我们介绍了如何根据网站结构制定解析计划。本节中,我们来看看如何用代码实现这个计划。

首先,我们已使用requests模块获取了网站内容,并存储在变量response中。下一步是解析HTML。解析意味着找到并格式化所需的信息。

以下是实现步骤:

  1. 创建Beautiful Soup对象来解析HTML。
  2. 使用soup.find_all方法定位包含目标数据的HTML元素。
  3. 遍历这些元素,提取每个事件的具体信息(如日期、标题、描述)。
  4. 将提取的文本信息整理成列表,最终转换为Pandas DataFrame。

让我们开始编写代码。

首先,调用Beautiful Soup并传入response.content作为参数。

from bs4 import BeautifulSoup
soup = BeautifulSoup(response.content, 'html.parser')

将解析后的内容存储在变量(如soup)中。打印soup,你会看到以更有序方式呈现的HTML。

使用这个soup对象,我们来实施上一视频中的计划。以下是用于构建代码的注释框架:

# 创建一个空列表来存储所有事件
# 使用soup.find_all找到所有具有特定class的列表项(li)元素
# 遍历找到的每个事件元素
#   在每个事件元素中,找到日期、标题和描述对应的标签
#   提取这些标签内的文本
#   将文本组成一个列表,代表一行数据
#   将这行数据添加到总事件列表中
# 将总事件列表转换为DataFrame

首先,创建一个名为event_list的空列表来存储事件。

然后,使用Beautiful Soup查找所有class属性为truth的列表项(li)元素。代码是soup.find_all('li', class_='truth')。注意,class后面需要加下划线(class_),因为class是Python的保留字。

现在,遍历这些元素:

for event in soup.find_all('li', class_='truth'):

在循环内部,你现在处理的是每个独立的事件。如果运行这个循环并打印每个event,你会得到包含日期、标题和描述的每个事件块。

接下来,移除打印语句。在编写代码时,可以参考HTML的结构。

创建一个新变量date来保存日期。在这个列表项内,使用event.find('span', class_='date-text')来找到日期的span标签。

对于标题,使用event.find('span', class_='title-text')

最后,对于描述,没有比直接获取整个没有class的段落(p)标签更好的方法了,可以使用event.find('p')

在提取文本之前,让我们看看如果直接将这些标签添加到列表中会发生什么。

创建一个新列表,称为event_row,因为它对应DataFrame中的一行。创建一个包含datetitledescription的新列表来代表你的事件。最后,将这个列表追加到代表整个DataFrame的事件总列表中。

运行此代码并查看第一个事件。你会注意到有三个元素:日期文本、标题文本和描述。但是,你并不希望DataFrame中包含这些额外的HTML标签。

与其直接将标签添加到DataFrame,你可以在每个find调用后使用.text属性来只获取文本内容。

好的,现在运行那个循环。再看看第一个元素。太棒了,你现在只有页面每个组成部分的内部文本。

作为最后一步,你可以使用pd.DataFrame()将这些列表转换为DataFrame,并将结果保存到变量df中。

import pandas as pd
df = pd.DataFrame(event_list, columns=['Date', 'Title', 'Description'])

这样就得到了一个格式良好的DataFrame。当然,还有很多预处理工作要做,但你已经抓取并解析了所需的核心信息。

最后一点说明:你在这个视频中看到了很多Beautiful Soup代码。你不需要记住所有这些步骤。实际上,大部分工作是在上一个视频中规划方法时完成的。

不相信吗?我来展示一下。打开你的LLM聊天机器人,使用以下提示词:

“根据以下注释为soup变量编写代码。目标是从这些抓取的数据中创建一个DataFrame。”

然后粘贴进你的代码注释。复制生成的代码并尝试一下。你可以移除那些import语句,因为已经导入了这些模块。

看,这很有效。重点是,你不需要记住所有命令,因为LLM可以帮助你编写具体代码,但你应该理解如何规划方法以及如何解释这段代码。

让我们快速回顾一下:

  1. 首先使用soup.find_all获取你需要的HTML元素列表。第一个参数是你要查找的HTML标签(本例中是‘li’),然后使用class_命名参数设置为‘truth’,以仅查找具有该class属性的元素。
  2. 使用循环逐个处理每个事件。
  3. 在该循环内,使用event.find方法定位具有指定class的特定元素的第一个出现。.find的参数与.find_all相同。例如,你用它来查找第一个具有特定class(如date-text)的span标签。
  4. 然后使用.text属性提取你找到的标签的内部文本。
  5. 最后,你将每个事件组装成一个列表,将该项追加到事件列表中,并从这些列表构建DataFrame。
  6. 你还看到了如何使用LLM根据注释生成代码。

规划你的抓取方法至关重要。一旦计划就绪,你可以借助LLM来配对代码,以获得你想要的结果。这非常棒。


在本视频中,你已经看到,解析HTML就是将用于人类浏览的内容重塑成可供分析的东西。

在下一个视频中,请跟随我,你将运用文本处理技能来清理刚刚创建的这个DataFrame。

019:数据框设置与预处理 🛠️

在本节课中,我们将学习如何对从网页抓取到的原始数据进行整理和预处理,使其成为整洁、可用于分析的数据框。我们将涵盖重命名列、清理文本数据、分割字符串以及转换日期格式等关键步骤。


在上一节中,我们使用 Beautiful Soup 解析了一个网页。虽然成功将数据提取到了数据框中,但结果仍然比较杂乱,不符合我们的分析需求。本节中,我们来看看如何对这类抓取数据进行关键的预处理。

简单回顾一下背景:你是一家天文设备零售商的数据分析师。公司希望根据天文事件来安排产品促销以提升销量。为了规划2030年的活动,你的经理要求你识别即将到来的天文事件。你的最终目标是创建一个事件日历,以便营销团队能据此安排促销和社交媒体发帖时间。

在之前的笔记本中,我们使用 requests 模块获取了“In-The-Sky”网站的数据,然后使用 Beautiful Soup 将原始 HTML 转换成了一个名为 df 的数据框。这个数据框有三列(日期、标题、描述),每一行代表一个事件。

首先,我们需要给这些列赋予有意义的名称,而不是数字索引。

以下是设置列名的步骤:

  1. df.columns 属性设置为一个列表:['date', 'title', 'description']
  2. 使用 df.head() 查看结果。

代码示例:

df.columns = ['date', 'title', 'description']
df.head()

现在列名已经设置好了。接下来,我们注意到标题列中包含了一个不必要的句点。我们可以使用 .str.replace() 方法来移除它。

以下是清理标题的步骤:

  1. df['title'] 列使用 .str.replace('.', '') 来移除所有句点。
  2. 再次查看 df.head() 以确认更改。

代码示例:

df['title'] = df['title'].str.replace('.', '')
df.head()

很好。现在请注意,有些日期包含了日期范围(例如 “January 3, ...”)。我们可以通过分割字符串来只保留起始日期,以便将来将此列视为日期类型。

以下是处理日期范围的步骤:

  1. df['date'] 列使用 .str.split(','),这将根据逗号进行分割并移除逗号。
  2. 使用 .str[0] 来选取结果列表中的第一个元素(即起始日期)。

代码示例:

df['date'] = df['date'].str.split(',').str[0]

现在查看描述列。以第一个值为例,我们实际需要的描述内容是在第一个句点之后的部分。但由于描述由多个句子组成,存在多个句点。我们可以使用 .str.split() 并指定分割次数来实现。

以下是提取有效描述的步骤:

  1. df['description'] 列使用 .str.split('.', n=1)。参数 n=1 表示只分割一次,即在第一个句点处将字符串分成两部分。
  2. 使用 .str[1] 来选取第二部分(索引为1),并将其保存回描述列。

代码示例:

df['description'] = df['description'].str.split('.', n=1).str[1]
df.head()

运行代码后,日期和标题等无关信息已从描述中移除。最后一步,当我们最初抓取数据时,Python 将日期视为纯文本(可以通过 df.dtypes 查看)。这种格式使得基于日期的分析或排序变得困难。

这个步骤可能有点复杂,因此我们使用一个名为 convert_date_column 的辅助函数。从 helper_functions 中导入它,然后按如下方式使用:

代码示例:

from helper_functions import convert_date_column
df = convert_date_column(df, 'date', 2030)

如果你好奇,可以随时查看 helper_functions.py 文件本身,或者让大语言模型(LLM)带你逐步理解代码。现在查看 df.head(),可以看到日期已经转换成了日期时间格式。

既然本分析的目标是找出如何最好地营销天文设备,那么了解天文事件最常发生在何时会很有帮助。这些信息可以帮助你有效地安排营销活动,使其与天文活动的高峰期保持一致。

首先,你需要分析每个月列出的事件数量。为此,你可以使用 .dt.month 从日期列中提取月份数字,然后使用 .value_counts() 方法来计算每个月关联的事件数量。为了确保月份顺序正确,还可以加上 .sort_index()

代码示例:

monthly_counts = df['date'].dt.month.value_counts().sort_index()
monthly_counts

计算结果显示,大多数事件发生在十二月和十一月。这将是推广天文设备的理想时间。为了可视化这些结果,你还可以添加 .plot(kind='bar')

代码示例:

monthly_counts.plot(kind='bar')


总结

本节课中,我们一起学习了在数据框中组织抓取数据的几种新技术:

  1. 首先,通过设置 df.columns 为一个字符串列表来为数据框的列命名。
  2. 然后,使用 .str.replace.str.split 来清理数据框中的值。
  3. 我们了解了 split 方法中的命名参数 n,它用于指定希望进行的分割次数。
  4. 最后,我们使用辅助函数将日期从字符串转换为日期时间格式,从而能够将这些事件视为时间序列进行处理。

干得漂亮。正如你所见,Beautiful Soup 是一个强大的工具,能将杂乱的网络数据转换为可供分析的整洁格式。接下来,你将学习如何搜索灵活的文本模式以精确提取所需内容。希望你能继续学习。

020:正则表达式 🧩

在本节课中,我们将学习如何使用正则表达式进行灵活的文本模式匹配。正则表达式是一种强大的工具,特别适用于从非结构化文本(如网页抓取的数据)中提取结构化信息。

概述

从网络抓取的许多数据以纯文本格式呈现。在之前的视频中,我们创建了一个包含各种天文事件的日期、名称和描述的数据框。然而,为了营销活动,我们需要搜索这个日历来定位关键事件。例如,我们可能希望搜索每个事件的描述,以提取事件发生的时间并添加为新列。由于没有特定的单一时间可寻,我们需要一种模式匹配的方法。

正则表达式简介

正则表达式(简称 regex)是定义搜索模式的字符序列。它们对于搜索结构化文本格式(如日期、电话号码)特别有用。

正则表达式模式由两个主要组件构成:要匹配的文本模式该模式出现的频率

文本模式匹配

文本模式可以是特定字符、字符集合或字符类。

  • 特定字符:例如 12happy
  • 字符类
    • [0-9] 匹配任意单个数字。
    • [A-Z] 匹配任意大写字母。
    • [a-z] 匹配任意小写字母。
    • [A-Za-z0-9] 匹配任意字母或数字。
  • 特殊序列
    • \d 匹配任意数字(等价于 [0-9])。
    • \s 匹配任意空白字符(如空格、制表符)。

例如,要匹配任意数字后跟任意字母(如 9a7Q),可以使用模式:\d[A-Za-z]

频率指定

频率指定模式应重复的次数。

  • 花括号 {n}:精确匹配 n 次。
    • 例如,\d{2}:\d{2} 匹配任意两个数字,后跟一个冒号,再跟任意两个数字(如 12:30)。这正是 HH:MM 时间格式的模式。
  • 加号 +:匹配 1 次或多次
    • 例如,\d+ 匹配一个或多个数字序列。
  • 星号 *:匹配 0 次或多次
    • 例如,\d{3}\s*\d{4} 可以匹配一个电话号码:三个数字,后跟零个或多个空白字符,再跟四个数字。

在 Pandas 中应用正则表达式

我们可以将正则表达式与之前学过的 Pandas 字符串操作结合使用,例如 .str.contains().str.replace()

另一个强大的方法是 .str.extract(),它会返回匹配到的文本,而不仅仅是像 .contains() 那样告知模式是否存在。

示例:提取电话号码

假设我们有一个数据框,包含销售团队关于客户订购望远镜的消息。我们希望从该列中提取电话号码,格式为:三位数字、一个短横线、三位数字、一个短横线、四位数字(例如 123-456-7890)。

在 Python 中,匹配模式通常写作原始字符串(使用 r 前缀)。原始字符串让程序按字面意义处理反斜杠 \,而不是将其视为换行符 \n 等转义序列的一部分。

对应的匹配模式是:r'\d{3}-\d{3}-\d{4}'

使用 .str.extract() 方法:

df['message'].str.extract(r'(\d{3}-\d{3}-\d{4})')

此代码将返回该列中每个单元格找到的第一个匹配项(即使存在多个匹配,也仅返回第一个)。该方法主要参数就是匹配模式。

总结

本节课我们一起学习了正则表达式的基础知识。我们了解到,正则表达式允许我们进行非常灵活的模式匹配,这对于从文本中提取特定格式的信息至关重要。虽然手动编写复杂的匹配模式可能有些繁琐,但掌握其基本原理有助于我们进行问题排查。在接下来的视频中,我们将探索如何利用大语言模型(LLM)来辅助编写正则表达式模式。

021:使用LLM编写正则表达式 📝

在本节课中,我们将学习如何利用大型语言模型(LLM)辅助编写正则表达式,以从文本数据中提取特定信息。我们将基于一个天文事件数据集,演示如何提取事件描述中的时间信息。


概述

上一节我们介绍了正则表达式的基础操作及其在灵活匹配文本中的应用。本节中,我们来看看如何与LLM协作,共同编写匹配模式,以解决实际的数据提取问题。

我们当前处理的数据集包含2030年的天文事件信息,列包括事件的完整日期、事件名称(例如“满月”)以及事件的详细文本描述。假设我们希望专注于这些事件发生的具体日期和时间,以优化社交媒体帖文的发布时间安排。为此,我们可以编写一个匹配模式来从事件描述中提取时间信息。

从数据集中提取时间信息

以下是我们在Python笔记本中暂停时的工作进度。我们使用Beautiful Soup抓取了数据,创建了规整的列,并将所有数据保存在变量Df中。其前几行数据如下所示。

检查索引0处的事件描述,它不包含时间信息。但索引1处的描述则包含时间信息。





请求LLM生成匹配模式

现在,我们请求LLM编写一个合适的匹配模式。关键在于指令要非常明确。

我们向LLM提供以下指令:

编写一个正则表达式模式,用于匹配 hour hour minute minutehour minute minute 格式的时间,允许其周围存在任意数量的空白字符或其他字符。然后编写代码,使其在给定字符串中仅匹配该模式的第一个实例。以向量化方式将此匹配模式应用于pandas数据框的 Df['description'] 列,并将结果保存到名为 Df['time'] 的新列中。请确保此正则表达式格式正确,可与 .str.extract 方法配合使用。

请注意,我们为LLM提供了解决问题所需的全部信息,因此可以忽略顶部不必要的示例数据框。

LLM生成的代码将模式保存为原始字符串变量,然后提取第一个匹配的时间,并将其存入新列。实际上,我们也不需要打印整个数据框。

让我们尝试运行这两行代码。

检查提取结果

现在,让我们查看 Df.head() 的输出。运行此代码时期望的结果是什么?

索引0的行不应有时间,而索引1的行应包含时间 2:50。太棒了,这个正则表达式生效了。手动编写这样的模式会非常麻烦。

以下是使用LLM生成模式并进行提取的核心代码示例:

# LLM生成的正则表达式模式,用于提取时间
pattern = r'\b(\d{1,2}:\d{2})\b'
# 应用提取模式
Df['time'] = Df['description'].str.extract(pattern)

重要提示

请务必始终将LLM的输出与你的期望进行核对。如果第一次尝试未能得到正确的模式,不要害怕进行迭代调整。

总结

本节课中,我们一起学习了如何利用LLM辅助编写正则表达式,从而从文本中提取可操作的数据。在最终的视频中,我们将通过探讨网络抓取的伦理问题来结束本系列课程。

022:网络爬虫伦理 ⚖️

在本节课中,我们将要学习网络爬虫所涉及的伦理与法律考量。你将了解到使用他人网站数据时需要注意的关键事项,包括版权、服务器负载以及如何通过 robots.txt 文件了解网站的爬取规则。


概述

在本模块中,你一直在使用网络爬虫来获取他人网站上创建的数据。但这个过程伴随着伦理和法律方面的考量,这些是你进行数据分析工作时必须牢记在心的。

法律与版权考量

首先,网络爬虫可能会遇到版权或访问权限问题。数据在线并不代表你可以随意将其用于你的项目。一些网站限制其内容的再利用,特别是用于商业目的。

这里需要区分根据当地法律什么是合法的,以及什么构成了违约或违反了网站的服务条款。例如,爬取竞争对手电子商务网站的每一个页面以获取其产品价格,可能会导致你的爬虫被屏蔽。即使最终没有面临直接的法律诉讼,你也应该始终检查网站的服务条款许可规则是否允许你打算进行的操作。

请注意:我不是律师,这也不是法律建议。

服务器负载考量

你还应考虑你的请求对网站造成的负担。每次你爬取网站,他们的服务器都必须处理你的请求,即使是为了拒绝。这种处理会消耗他们的时间和资源。

如果你发送请求的速度过快,可能会压垮服务器,导致响应时间变慢,甚至对你和其他用户造成服务中断。因此,只爬取页面一次,然后在自己的计算机或服务器上进行所有处理,而不是为了获取特定数据而发出许多小请求,这是一种良好的做法。

例如,你可以将请求代码与处理代码分开,这样如果你需要修改,就不必重新获取数据。你可能已经注意到,在之前的演示中,你只对整个页面设置了一次请求,然后在 Python 笔记本中完成了预处理步骤。

限制请求频率

过多的请求可能会触发验证码或导致封禁。如果你计划爬取许多页面,你应该限制请求频率。你可以在 Python 中使用 time 模块来实现这一点,它允许你在请求之间暂停。

例如,在这段代码中,time.sleep(1) 将在每个请求之间暂停代码一秒钟。

import time
# 假设在一个循环中发送请求
for url in list_of_urls:
    # 发送请求的代码...
    time.sleep(1)  # 暂停1秒

理解 robots.txt 文件

网站了解网络爬虫的存在,并经常使用 robots.txt 文件来设定你可以收集什么以及不应触碰什么的界限。要访问此文件,只需在网站的根域名后添加 /robots.txt。根域名是网站地址的主要部分,例如 google.comdeeplearning.aiwikipedia.org

以下是查看 robots.txt 文件的方法:

  • deeplearning.ai/robots.txt
  • wikipedia.org/robots.txt

例如,deeplearning.airobots.txt 文件很短,因为它允许你自由爬取所有页面的信息。然而,看看维基百科的 robots.txt 文件,许多 robots.txt 文件看起来又长又复杂。

使用LLM解读规则

为了快速了解允许和禁止的内容,你可以使用大型语言模型。让我们使用 Claude 3.7。请注意,你可能可以使用更高级的模型。

你可以编写一个提示词,要求它根据这个 robots.txt 文件简要解释允许哪些类型的网络爬虫,然后粘贴维基百科的文件内容。

提示词示例

请根据以下维基百科的 robots.txt 文件内容,简要总结允许和禁止哪些网络爬虫行为。

(然后粘贴文件内容)

LLM 会总结出你可以做和不可以做的事情。它会告诉你,你可以对文章页面进行常规爬取,也可以访问一些不同的 API。但它也会告诉你存在一些限制:许多仅限管理员访问的页面、讨论页面和特殊页面是被禁止的。你无法访问已删除的讨论、版权侵权报告或用户相关页面。

最后,快速或激进的爬取是被禁止的,并明确警告访问可能会被阻止。这就是你需要使用 time.sleep() 函数的原因,以避免被屏蔽。


总结

本节课中我们一起学习了网络爬虫的伦理与法律边界。我们探讨了尊重版权和服务条款的重要性,了解了如何通过控制请求频率来减轻服务器负担,并学会了如何查找和解读网站的 robots.txt 文件来合规地进行数据采集。记住,负责任的数据获取是数据分析师的基本素养。

本模块的学习到此结束。你现在已掌握完成关于分析科技行业工作的评分作业和实验所需的所有知识。完成作业和实验后,请跟随我进入下一个关于使用 API 收集和预处理数据的模块。我们那里见。😊

023:模块2 简介 🚀

在本节课中,我们将要学习吴恩达课程《数据输入/输出与预处理(Python 和 SQL)》的模块2。这个模块的核心是掌握如何从网络获取结构化数据,并进行预处理,为后续分析做好准备。我们将重点学习应用程序编程接口(API)的使用以及数值数据的清洗技术。

概述

模块2的主题是“API与数值数据清洗”。本模块旨在使你掌握访问和预处理网络结构化数据所需的技能,以便进行分析。你将学习如何从应用程序编程接口(API)检索数据,并准备数值数据用于深入分析。

课程内容详解

上一节我们概述了本模块的目标,本节中我们来看看具体的学习路径。整个模块包含三个核心课程。

以下是三个课程的主要内容:

  1. 第一课:探索API
    你将了解API的工作原理及其众多自定义选项,包括参数和分页。你将学习如何将特殊JSON格式的数据转换为用于分析的数据框。

  2. 第二课:API身份验证
    你将通过使用API密钥访问数据来提升API技能。你将学习行业标准的安全技术,以保护你的密钥(类似于密码)的安全。

  3. 第三课:数值数据预处理
    你将处理关键的预处理步骤,如标准化分箱归一化异常值处理。这些技术对于处理数值数据至关重要,能确保你的数据无论原始格式如何,都为分析做好了准备。你还将探索如何从四个维度评估数据质量。

模块目标总结

本节课中我们一起学习了模块2的框架。到本模块结束时,你将掌握从API检索、清洗和准备数据的基础技能,这是任何数据分析师的关键能力。

让我们开始学习吧。

024:API 介绍 🚀

在本节课中,我们将要学习什么是应用程序编程接口(API),以及它如何作为一种比网络爬虫更高效、更可靠的方式,从互联网上获取结构化的数据。我们将以美国食品药品监督管理局(FDA)的食品召回数据API为例,了解其基本用法和数据格式。


在之前的模块中,你看到了网络爬虫如何成为从网站收集数据的一种有用方法。

实际上,你还有另一个非常有用的选项可以从互联网上收集结构化数据。网络爬虫涉及从为人类读者而非为你的Python代码格式化的网页中提取数据。正如你在上一个模块中看到的,从网络爬虫收集的数据可能难以处理,主要是因为网站并非为提供结构化数据而构建。因此,你经常需要从格式复杂、包含大量额外文本和代码的HTML中提取所需内容。你还看到网站的布局不一致,同一网站的不同页面可能遵循略有不同的结构。

这种不一致性使得编写一个适用于所有情况的爬虫代码变得困难。此外,你还会遇到服务条款禁止爬取的网站,这使你的数据获取工作复杂化。然而,有方法可以从互联网上获取格式良好、用于分析的结构化数据。为此,你可以使用应用程序编程接口,简称API。

一个API允许你直接从网站服务器请求结构化的数据。你通过编写代码来访问API,因此得名“编程接口”,即代码访问某种东西的方式。API是专门为提供数据而构建的,因此当它们可用时,与网络爬虫相比,它们是访问信息更可靠、更高效的方式。你可能只有在所需数据无法通过API获得时,才会使用网络爬虫。

API也使用你在上一个模块中学到的相同网络范式:你的计算机作为客户端,向服务器发送请求,服务器处理请求并发回响应。在本模块中,你将通过一个API数据的案例研究进行实践,该案例涉及为消费者权益保护组织完成一项分析任务。你将跟踪和分析食品召回事件,以评估对公众的潜在风险。了解食品从市场下架的频率和原因,有助于为政策建议提供信息。

在本课中,你将使用美国食品药品监督管理局(FDA)提供的食品执法API。使用此API,你可以获取关于召回食品产品的结构化数据。然后,你将预处理这些数据,并准备一份总结产品召回模式的报告。


在编写代码使用API之前,让我们先查看其文档。

FDA维护着这个网站,列出了其所有API并解释了它们的工作原理。如果你正在寻找关于食品召回执法的数据,可以从左侧列表中选择“食品API端点”。然后选择“召回执法报告”,再选择“概述”。它会给你一些关于此数据的信息:它包含提交给FDA的召回事件信息,涵盖从2004年至今的数据。这是此API相比平面文件的一个主要优势:数据是最新的。当你看到这些时,你将拥有比这里显示的更最新的信息。

现在,看看如何使用这个API。

向下滚动到“进行简单的API调用”部分。你可以从网络浏览器进行API调用,并且它给出了一个示例。让我们暂时忽略搜索和限制参数,只看用蓝色标记的基础端点。

打开一个新标签页,这就是数据。复制基础端点并将URL粘贴到搜索栏中。

这只是一个结果。如果你查看花括号、方括号和缩进,这个结果不是HTML。它是一种称为JSON的格式,你将在下一个视频中探索它。

现在,你可以浏览一下这个结果。顶部有一些元信息,解释了上次更新时间、结果数量以及总共有多少可用结果。然后你在下方得到那个显示的结果,它是一个结果列表。这是一起来自美国佛罗里达州的已终止召回。召回来自Pharmach LOC公司,涉及某种类型的膳食补充剂。

因此,在编写代码使用新API之前,探索文档并了解你将处理的数据结构是极好的做法,就像你在进行网络爬取之前所做的那样。在本视频中,你在浏览器中检查了API的一个结果。API结果通常以JSON格式返回,这是一种存储结构化数据的常见方式。请继续观看下一个视频以了解其工作原理。


本节课中我们一起学习了API的基本概念及其相对于网络爬虫的优势。我们通过FDA食品召回API的实例,查看了如何通过浏览器直接访问API端点并初步了解其返回的JSON数据结构。在开始编码前查阅和理解API文档是至关重要的准备工作。

025:JSON格式入门 🗂️

在本节课中,我们将学习JSON格式的基础知识,包括其结构、与Python字典的关系,以及如何从嵌套的JSON数据中提取所需信息。


概述

上一节我们介绍了API,并学习了如何在浏览器中使用API获取结构化数据。本节中,我们将深入了解API返回结果通常采用的格式——JSON。我们将学习JSON的结构,它如何与Python字典对应,以及如何通过链式索引访问嵌套数据。


什么是JSON?

JSON,全称JavaScript Object Notation(JavaScript对象表示法),是一种常用于应用程序之间传输数据的格式。它以结构化的方式组织数据。

这种格式起源于网络世界,其中JavaScript是一种常见的编程语言,因此JSON模仿了JavaScript的书写方式。

JSON是一个由键值对组成的集合。每个键用于访问一个值。

回想一下Python中的列表,它是一种有序集合,使用索引(如0, 1, 2...)来访问值。而JSON使用来访问值,键必须是带引号的字符串。

如果你还记得上一门课程中简要接触过的Python数据结构——字典,你可能会注意到JSON的结构与字典非常相似。一旦将JSON加载到Python中,它就会被表示为一个字典。

公式/代码表示:
一个简单的JSON对象看起来像这样:

{
  "key1": "value1",
  "key2": 123,
  "key3": true
}

JSON的结构特点

以下是JSON结构的几个核心特点:

  • 键值对:JSON数据由成对的键和值组成。例如,在 "name": "Andromeda Galaxy" 中,"name" 是键,"Andromeda Galaxy" 是值。
  • 花括号:整个JSON对象被一对花括号 {} 包围。
  • 嵌套结构:与HTML元素可以包含其他元素类似,JSON的值也可以是嵌套的。这意味着数据可以分层组织,一些值可能是简单的数字或字符串,而另一些值可能是列表或其他JSON对象,从而形成信息的多层结构。

例如,考虑以下关于一个遥远星系的数据:

{
  "name": "Andromeda Galaxy",
  "distance_ly": 2537000,
  "visible_from_earth": true,
  "neighboring_galaxies": ["Triangulum Galaxy", "Milky Way"]
}

这个JSON对象包含4个键值对。其中,"neighboring_galaxies" 的值是一个嵌套的列表。

处理JSON时,通常需要先浏览整个结果,以了解有哪些可用数据以及需要访问哪些部分。与表格中所有数据都整齐排列在行和列中不同,JSON结构可能更复杂,需要你导航不同的层级才能获取所需数据。


JSON与Python字典

如前所述,在Python中,JSON被表示为字典,两者看起来极其相似。字典也用花括号书写,同样表示键值对。

不过,两者存在一些细微差别:

  • 在JSON中,空值用 null 表示;在Python中,用 None 表示。
  • 在JSON中,布尔值 truefalse 是小写;在Python中,它们是首字母大写的 TrueFalse

你不需要记忆这些差异。重要的是记住:JSON是使用API时会遇到的一种数据结构格式,它在Python中与字典的表示方式非常相似。pandas 等库会自动为你完成这种转换。


访问嵌套的JSON数据

现在,让我们通过代码示例看看如何实际操作JSON(字典)数据。

假设我们有一个Python字典,它代表了从FDA执法API获取的响应数据。

首先,我们可以查看这个字典的键,以了解有哪些可用信息。

代码示例:

# 假设 `data` 是包含API响应的字典
print(data.keys())  # 输出:dict_keys(['meta', 'results'])

注意,keys() 是一个方法(因为它有括号),而不是属性。这里我们得到了两个键:'meta''results'。这是嵌套的第一层。

接下来,查看 'results' 部分。我们可以通过 data['results'] 来访问这个键的值。这可能返回一个包含召回报告信息的列表。

这个字典(API响应)的巧妙之处在于,你可以深入到更深的嵌套层级。例如,data['results'] 可能返回一个列表(可以通过方括号 [] 识别),因为一次可以返回多个结果。

如果你想获取列表中的第一个结果,可以使用 data['results'][0]。现在,你深入了一层,得到了一个字典。所以,现在的结构是:字典内嵌套了一个列表,列表内又嵌套了一个字典

假设我们想从这个字典中获取产品数量 product_quantity

代码示例:

# 链式索引访问深层数据
product_qty = data['results'][0]['product_quantity']
print(product_qty)  # 输出可能是:1990 bottles

你通过 data['results'][0]['product_quantity'] 解包了所有这些层级,最终获取到了需要的数据。


总结

本节课中,我们一起学习了如何操作JSON数据。

  • JSON定义了一种用于组织数据的、嵌套的键值对结构。
  • 我们了解到,JSON在Python中被格式化为字典,字典由花括号定义,同样也是嵌套的键值对结构。
  • 我们看到了如何使用链式索引来解包字典的各个层级,以获取所需数据。例如,通过先访问 results 列表,再获取该列表的第一项,最后从代表该召回事件的字典中访问 product_quantity,从而获取了第一个召回结果的产品数量。

现在你已经更深入地理解了JSON的结构,准备好从数据中提取所需的关键信息了。希望你能在下一节课中继续与我一同学习。

026:使用Python发起API请求与处理响应 🚀

在本节课中,我们将学习如何使用Python代码从API获取数据。你将看到,这与网络爬虫有许多相似之处。我们将使用requests库向FDA食品执法API发送请求,并处理返回的JSON格式响应数据。

准备工作

在开始编写代码之前,我们需要导入必要的Python模块。

以下是所需的导入语句:

import requests
import pandas as pd

提示:你可以使用练习实验室项目来跟随演示进行操作。

发起GET请求

上一节我们介绍了API的基本概念,本节中我们来看看如何用代码发起请求。首先,你需要定义要从中获取数据的API端点URL。

url = "https://api.fda.gov/food/enforcement.json"

这个URL是FDA执法API的“基础端点”,你将在后续视频中了解更多关于端点的知识。

接下来,我们向这个URL发送一个GET请求。GET请求就像是向服务器询问:“你好,我可以获取这些数据吗?”

response = requests.get(url)

与爬取网页数据时一样,在进一步处理之前,你应该先检查状态码,以确认请求是否成功。

print(response.status_code)

如果状态码是200,则表示成功。这意味着请求已通过,并且你很可能已经获得了所请求的数据。

提取与处理响应数据

成功收到响应后,我们需要从中提取出有用的数据。响应对象内部存储了许多信息,状态码就是其中之一。

以下是提取JSON数据的新代码行:

data = response.json()

使用response.json()方法可以从响应中获取JSON格式的数据。

现在,让我们查看一下获取到的数据。瞧,这里就有来自佛罗里达州的召回信息。😊

为了验证数据的有效性,我们可以进行一些快速检查。

以下是几个有用的数据检查步骤:

  1. 检查数据类型:确认data是一个Python字典。这符合我们之前所学:JSON数据在Python中通常以字典形式存储。
  2. 检查字典长度:使用len(data)查看返回的键的数量。你也可以使用.keys()方法来访问这些键。
  3. 检查结果列表:访问data[‘results’](你在上一个视频中见过这个键),并检查其列表长度。这能告诉你收到了多少个具体的结果项。

例如:

print(type(data))  # 应输出 <class 'dict'>
print(len(data))   # 检查键的数量
print(len(data[‘results’]))  # 检查结果列表中的项目数

在这个例子中,结果列表的长度为1,表示只有一个召回结果。在收到API响应后,进行此类检查是确认数据正确性的良好实践。

课程总结

本节课中,我们一起学习了如何从API实时获取数据。

你做得很好!你使用Python的requests库发送了GET请求,并通过检查状态码确保请求成功,这与网页爬虫的步骤类似。随后,你使用.json()方法将响应中的JSON数据提取为Python字典,以便进行后续操作。

到目前为止,你在API使用方面表现优异。请跟随我进入下一个视频,学习如何定制你的API请求。

027:API查询参数详解 🔍

在本节课中,我们将学习如何通过添加查询参数来精确控制从API获取的数据。你将了解查询参数的作用、常见类型以及如何在代码中优雅地使用它们。


概述

上一节我们介绍了如何使用代码从API获取响应。本节中,我们来看看在向API请求数据时,还有哪些选项可以优化我们的请求。

当你发送一个请求时,它会指向一个特定的URL,这个URL被称为API端点。例如,在上一个视频中,你向FDA食品执法API的基础端点发送了请求。可以将端点视为你请求的目的地,它是数据所在的位置。你可以使用的端点由提供API的公司决定。

但你并不总是希望获得该端点的默认响应。例如,你的一次API调用只收到了一个召回事件。如果你希望通过一次请求就收到多个事件,该怎么办?你可以通过添加额外的细节(称为查询参数)来优化你的请求。

“查询”是“请求”的同义词,两者都表示请求信息。就像端点一样,可用的参数取决于你使用的API。了解可用选项的最佳途径是查阅API文档。


查询参数基础

以下是查询参数的基本概念和用法。

参数结构与语法

查询参数附加在基础端点URL之后。一个问号 ? 标志着查询参数的开始。参数使用 key=value 的格式,类似于函数中的命名参数。& 符号允许你在单个请求中添加多个参数。

例如,考虑以下基础端点:

https://api.fda.gov/food/enforcement.json

要添加一个查询参数来搜索全国分布的召回事件,URL会变成:

https://api.fda.gov/food/enforcement.json?search=distribution_pattern:nationwide

你可以使用 & 添加另一个参数,例如 limit,来限制返回结果的数量:

https://api.fda.gov/food/enforcement.json?search=distribution_pattern:nationwide&limit=5

在这个例子中,你请求的是5个结果,而不是默认的1个。

查找可用参数

要了解FDA食品执法API的可用参数,你可以访问其官方文档页面。通常,文档会有一个“可搜索字段”页面,列出所有可能的过滤选项,例如按城市、召回原因和状态进行过滤。


在代码中使用查询参数

让我们回到代码中,看看如何实际操作。

方法一:手动拼接URL

假设你之前已经向基础端点发送了请求,并将响应保存在变量 data 中。如果只查询基础端点而不带任何参数,你只会得到一个结果。

如果你想获取五个结果,可以在基础端点后添加 ?limit=5

import requests

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-dtio-preprroc/img/c9fd1a7a6a31675e64119486d2638619_7.png)

url = “https://api.fda.gov/food/enforcement.json?limit=5”
response = requests.get(url)
data = response.json()

检查 data[‘results’],你会得到一个包含五个项目的更长列表。

如果你想只关注夏威夷的召回事件,可以按照FDA网站所示的格式添加 search 参数:

url = “https://api.fda.gov/food/enforcement.json?search=state:HI&limit=5”

方法二:使用 params 参数(推荐)

当API有很多参数时,不断将它们拼接到URL中会显得很混乱。requests 模块提供了一种更清晰的方法:你可以将查询参数作为一个字典,通过 params 参数传递给 requests.get() 函数。

例如,如果你想将响应数量限制为5,可以创建一个字典:

parameters = {‘limit’: 5}

然后,在调用 requests.get() 时,添加一个新的命名参数 params=parameters

url = “https://api.fda.gov/food/enforcement.json”
parameters = {‘limit’: 5}
response = requests.get(url, params=parameters)
data = response.json()

请注意,将变量命名为与函数中的命名参数相同是完全可行的。运行这段代码,你将得到与之前相同的响应,包含5个事件而不是1个。

使用 params 字典的优势在于,它使你的代码更整洁、更易于管理,并且 requests 库会自动处理参数的编码和格式化,减少了API请求中出错的可能性。


常见查询参数

许多API共享一些常见的查询参数,以下是最有用的两个:

  • limit:控制你获得的结果数量。
  • skip:允许你在返回结果之前偏移或忽略一定数量的记录,常用于分页。

总结

本节课中,我们一起学习了API查询参数。我们了解到,通过使用查询参数,你可以精确地获取所需的数据,不多不少。我们从查询参数的基础语法讲起,学习了如何手动将它们添加到URL中,并重点介绍了更优雅、更不易出错的方法——使用 requests.get()params 参数来传递参数字典。

在下一个视频中,你将使用参数来扩展你的API请求,并以一种能使数据洞察更清晰的方式格式化数据。

028:从JSON到数据框 📊➡️📈

在本节课中,我们将学习如何将从API获取的JSON数据转换为Pandas数据框,并进行初步的数据验证与预处理。这是数据分析流程中从数据获取到实际分析的关键一步。


在上一节视频中,我们学习了如何为API请求添加参数。一旦我们请求到正确的数据,就需要将其转换为数据框,以便开始预处理和分析。

回顾一下,我们正在为一个消费者权益组织执行一项分析任务。我们使用FDA食品执法API来追踪和分析食品召回事件,以评估其对公众的潜在风险。到目前为止,我们只从FDA API请求了最多5条结果。如果我们想分析更广泛的产品召回趋势,这个数量远远不够。

让我们更进一步,请求1000条结果,这是该特定API单次请求所能获取的最大数量。API通常会设置请求限制以保护服务器资源。




请注意,这次请求完成所需的时间稍长一些。

现在,Pandas提供了一个方法,可以将这个字典(JSON数据)转换为数据框。

再次查看返回数据的键(keys),你认为应该将哪个键对应的值转换为数据框?

答案就是 results 键对应的列表。

你只需要使用 pd.DataFrame(data[‘results’]) 即可完成转换。

将结果保存到一个变量中,我们称之为 df

在进行任何预处理步骤之前,应该先进行一些数据验证。

这个数据框应该有多少行?

1000行。你可以用 len(df) 来检查,结果完全正确。

那么列呢?列应该是每个执法事件的所有特征,例如状态(status)、城市(city)、召回编号(recall_number)等等。是的,列表 data[‘results’] 中每个字典的键(keys)就成为了数据框的列。

查看 df.dtypes,你会发现所有列的数据类型都是 object

正如之前所学,Python默认将所有内容视为对象(基本上是文本)。然而,有些列应该是数值型或日期型。

例如,recall_initiation_datecenter_classification_datetermination_datereport_date 应该被视为日期。

这些日期的格式看起来是一致的,因此你可以尝试使用 pd.to_datetime() 将它们转换为日期时间类型。

现在,当你查看 df.head() 时,可以看到它们已格式化为包含年、月、日的日期。

你还看到了哪些数据预处理的机会?

如果你查看 classification 列的唯一值,会发现只有三个值:Class 1Class 2Class 3

我们可以用数字替换这些值,以便进行数值化处理。但这实际上取决于你的分析方向,在某些情况下,保留原样也可以。

以下是替换步骤:
首先,尝试将 Class 1 替换为字符串 ‘1’,对 Class 2Class 3 也进行同样的操作。

现在检查唯一值以进行验证。等等,这看起来不对。你能看出发生了什么问题吗?

看起来所有包含 ‘Class 1’ 的实例都被替换了,甚至当它是字符串 ‘Class 2’‘Class 3’ 的一部分时也被替换了。

让我们向大语言模型(LLM)咨询如何解决这个问题:“我的数据框中有一个列,只有 Class 1Class 2Class 3 这三个值。我尝试使用下面的代码进行替换,但得到了下面的错误结果。” 附上你的代码和奇怪的结果。


大语言模型建议使用正则表达式,这是一个选择。但你也可以问:“有没有更简单的方法?” 它可能会建议使用 map 方法,你当然可以尝试。或者,更简单的思路是:先替换 Class 3,然后替换 Class 2。不过,我们按照模型的建议试试看。

现在,由于你已经修改了数据框,你需要从原始的JSON数据重建它,否则你的处理将基于这些错误的值开始。

尝试运行大语言模型提供的代码,成功了!现在值是 213

最后,使用 .astype(‘int64’) 保存这个结果。

太好了,这大大简化了这个数据框。


总结 📝

本节课中我们一起学习了:

  1. 如何使用 pd.DataFrame() 从字典列表中创建数据框。
  2. 生成的数据框长度与列表相同,列名是列表中每个字典的键。
  3. 进行了初步的数据验证(检查行数、列类型)。
  4. 将日期字符串列转换为日期时间类型。
  5. 处理了分类数据的数值化替换,并注意了替换顺序可能带来的问题。

干得漂亮!你刚刚基于字典创建了一个数据框。

在下一个视频中,你将使用一种称为“分页”(Pagination)的技术来获取25,000条结果,而不仅仅是1000条。我们下节课见。

029:分页处理 📄

在本节课中,我们将学习如何使用分页技术从API获取大量数据。当单次API调用无法返回所有所需数据时,分页允许我们分批请求数据,并将结果合并成一个完整的数据集。

上一节我们介绍了如何通过单次API调用获取和处理数据。然而,许多API对单次请求返回的数据量有限制。本节中我们来看看如何突破这个限制,获取全部数据。

什么是分页?

分页是一种分批获取数据集的技术。这个词来源于“页面”。想象一下在谷歌上搜索:第一页显示25个结果,点击“下一页”会再获取25个结果。对API的请求也可以实现类似的技术,以获取超过单次调用上限的结果数。

如果你想从API获取大量结果,可以使用分页技术发起多次请求,并将每次的结果逐步构建成一个数据框。

以下是其工作原理:

如果存在25000条记录,你可以每次请求1000条。该技术使用 skiplimit 查询参数。

  • limit 参数始终设为 1000,因为你希望每次获取1000条记录。
  • skip 参数会变化。它告诉服务器在本次请求中要跳过开头的多少条结果。
    • 第一次请求,skip 设为 0,获取前1000条结果。
    • 第二次请求,skip 设为 1000,跳过已获取的前1000条,获取接下来的1000条记录。
    • 你将不断将 skip 参数增加 1000,直到获取所有记录。

实现分页数据收集

首先,创建一个空列表。你将把每批1000条结果转换成的数据框追加到这个列表中,最后再合并所有结果。

现在开始编写循环,向API请求数据。

你的计划是编写代码来检索25000条记录。由于每次只能获取1000条,你需要发起25次API调用。真正新增的代码只有几行。

以下是实现步骤:

  1. 启动循环for i in range(25):。这意味着循环将运行25次,每次 i 的值增加1。
  2. 定义参数:使用字典来保持代码整洁。你需要两个参数:limitskip
    • limit 始终为 1000
    • skip 等于 i * 1000
      • 开始时 i0skip0
      • 然后 i1skip1000,接着是 2000,依此类推。
  3. 请求数据:像往常一样请求数据并提取JSON。
  4. 创建并存储数据框:从结果创建一个pandas数据框,然后将这个数据框追加到你的数据框列表中。稍后再将它们全部合并。
import pandas as pd
import requests

base_url = "你的API基础端点URL"
dfs = [] # 空列表,用于存储每个批次的数据框

for i in range(25):
    params = {
        'limit': 1000,
        'skip': i * 1000
    }
    response = requests.get(base_url, params=params)
    data = response.json()
    df_batch = pd.DataFrame(data['results']) # 假设数据在‘results’键下
    dfs.append(df_batch)

合并数据框

在循环结束后,你需要将这些数据框合并成一个大的数据框。

在循环外,使用 pd.concat() 函数。第一个参数是 dfs,即你想要合并的数据框列表。你还需要参数 ignore_index=True。否则,你将得到25组索引从0到999的行。

final_df = pd.concat(dfs, ignore_index=True)

运行这段代码可能需要几秒钟时间,这是网络请求的正常耗时。

你期望得到多少行?应该是25,000行。列应该和之前视频中的一致,即召回事件的每个特征。

现在,你可以像之前一样,对所有数据执行相同的预处理步骤,包括类型转换,我们将在下一个视频中继续讲解。

总结

本节课中我们一起学习了如何使用分页从API获取成千上万条结果。

  • 你编写了一个循环,使用固定的 limit 和递增的 skip 参数来分批获取记录。
  • 从API获取的每批数据都被转换为一个pandas数据框。
  • 然后你使用 pd.concat 将单个数据框合并成一个。
    • concat 函数本质上是将数据框按行堆叠起来,并自动按列名匹配列。因此,即使列顺序不一致,最终的数据框也会保持规整。
    • 设置 ignore_index=True 会为合并后的数据框中的行索引重新编号,从0开始。

干得漂亮!你已经将大量的API数据整合到一个数据框中,以便进行分析。在下一个视频中,你将学习更高级的技术来预处理和分析这个合并后的数据。希望你能继续学习。

030:组合数据分析 📊

在本节课中,我们将学习如何对从API批量获取并组合好的数据进行预处理和分析。我们将处理一个包含FDA食品召回记录的数据集,识别并修复数据问题,计算召回事件的持续时间,并通过可视化分析其趋势。


概述

上一节我们介绍了如何使用分页技术从API批量请求数据。现在,我们已经将所有数据合并到一个单一的DataFrame中,是时候对其进行预处理和分析了。

我们正在为一个消费者权益组织进行一项分析任务,使用FDA食品执法API来追踪和分析食品召回事件,以评估潜在的公共风险。目前,我们有一个包含25000条记录的DataFrame。

数据预处理

首先,我们需要执行与之前视频中相同的预处理步骤。

以下是第一步,将classification列转换为数值类型。

# 将classification列转换为数值类型
df['classification'] = pd.to_numeric(df['classification'])

所有步骤合并执行后,数据看起来不错。接下来,将日期列转换为datetime类型。

# 将日期列转换为datetime类型
date_columns = ['recall_initiation_date', 'termination_date']
for col in date_columns:
    df[col] = pd.to_datetime(df[col])




处理数据异常

转换过程中出现了问题。让我们滚动到错误底部查看详情:out of bound timestamp 0, 2, 1, 2, 1207

看起来有人可能错误地将召回日期输入为212年。这很可能是一个异常值。通过检查最早的日期可以确认这一点。

# 查看最早的召回日期
df['recall_initiation_date'].sort_values()

结果显示,除了这个212年的异常值,其他日期最早可追溯到2008年。

我们需要修复这个时间戳。以下是修复代码,将DataFrame中位置20513行的recall_initiation_date列值设置为212-12-07

# 修复异常时间戳
df.at[20513, 'recall_initiation_date'] = '212-12-07'

现在再次尝试运行日期时间转换。

# 再次尝试转换日期列
for col in date_columns:
    df[col] = pd.to_datetime(df[col])

运行成功,没有错误。检查df.dtypes以确认所有日期列都已正确转换为datetime类型。

计算召回持续时间

现在,我们可以开始进行分析,以实现分析随时间变化的召回率这一最终目标。我们可以研究召回事件平均解决速度,以及这个速度是变快了还是变慢了。

首先,创建一个新列,计算termination_date减去recall_initiation_date,得到从事件开始到解决的持续时间。

# 计算召回持续时间
df['duration'] = df['termination_date'] - df['recall_initiation_date']


请注意,检查df.info()会发现只有大约23700个非空持续时间值。这是因为查看status的唯一值时,发现有些案例仍在进行中,因此没有终止日期。

处理这些缺失值有几种方法,其中一种选择是创建一个新的DataFrame duration_df,并删除这些行。

# 删除duration列为空的行
duration_df = df.dropna(subset=['duration'])

记住,subset参数必须指定为列表。现在检查duration_df.info(),可以看到有23755行。

数据分析

现在,我们可以按recall_initiation_date升序对数据框进行排序,这样数据就从2008年开始,一直到2024年。

# 按召回开始日期排序
duration_df = duration_df.sort_values('recall_initiation_date')

检查前几行,发现有些案例仅在一周内就关闭了。





平均持续时间略低于一年。有一个案例在近10年后才关闭。但75%的召回在500天内得到解决。

数据可视化

现在,我们可以为报告做一些绘图。导入matplotlib

以下是绘制请求持续时间的代码。不必过于担心代码细节,可以专注于洞察。

import matplotlib.pyplot as plt

# 绘制持续时间直方图
plt.hist(duration_df['duration'].dt.days, bins=50)
plt.xlabel('Duration (days)')
plt.ylabel('Number of Recalls')
plt.title('Distribution of Recall Durations')
plt.show()

数据强烈右偏。大多数案例在500天内解决,但仍有一些事件超过500天仍未解决。

为了展示召回持续时间如何随时间变化,可以创建一个折线图,绘制每个季度开始的召回平均持续时间。这需要对数据进行分组。

# 按季度分组并计算平均持续时间
duration_df['quarter'] = duration_df['recall_initiation_date'].dt.to_period('Q')
avg_duration_by_quarter = duration_df.groupby('quarter')['duration'].mean()

# 绘制折线图
plt.plot(avg_duration_by_quarter.index.astype(str), avg_duration_by_quarter.dt.days)
plt.xlabel('Quarter')
plt.ylabel('Average Duration (days)')
plt.title('Average Recall Duration Over Time')
plt.xticks(rotation=45)
plt.show()



如图所示,从2008年到2024年,召回持续时间显著减少。2024年最近的下降可能部分是由于删除了未结案例的行,但总体趋势非常清晰。这是一个很好的洞察,可以与消费者分享,帮助他们理解FDA的辛勤工作。

总结

本节课中,我们一起学习了如何从API请求结构化数据、将JSON转换为DataFrame、处理数据异常、计算关键指标(如召回持续时间)以及通过可视化分析趋势。网络爬虫和API请求对于任何数据分析师来说都是极其宝贵的技能。

接下来,请完成练习作业。完成后,请跟随我进入下一课:API密钥。

031:API密钥 🔑

在本节课中,我们将要学习API密钥的概念、作用以及为何大多数API都需要它。我们将探讨认证的重要性,并了解API密钥如何作为访问受保护数据的“数字钥匙”。


在上一节课中,我们学习了如何使用API从网络收集结构化数据。

然而,你会发现现实世界中的许多API都需要一个称为“密钥”的特殊字符串才能访问。

作为一名数据分析师,你必须应对这一安全层。之前你使用的API不需要认证。认证是验证谁在向API发出请求的过程。

每次登录音乐或电影流媒体账户时,你都在执行认证。平台需要知道是谁在登录,以便向你展示个性化推荐,并确保你对自己的账户保持控制权。

无需认证即可访问API,意味着你可以在不提供任何身份信息的情况下发送请求,并且仍然能收到响应。

不需要认证的API相对罕见。FDA发布食品执法API并使其公开可访问,这很好,但大多数API都需要认证。

一些API不使用认证有几个关键原因。

首先,有些API不需要跟踪单个用户,因为它们提供的数据不需要大量资源来服务。

例如,一群爱好者可能维护一个小型API,用于访问他们最喜欢的电子游戏的数据。他们每月可能只收到几百个请求,维护成本很低。

其次,许多政府机构提供公共API,让公众能够访问重要数据,例如公共卫生记录。这些API旨在最大限度地提高可访问性,因此使用限制会与最终目标背道而驰。

然而,大多数API确实需要认证。在访问数据之前,你需要提供身份证明。

API要求认证有几个原因。

首先,运行API需要计算能力,因此需要花钱。你请求的数据越多,使用的计算能力就越多。公司通常会跟踪API使用情况,以便根据你的使用情况向你收费。

其次,许多API提供对私有数据的访问。例如,Google提供了Google Drive API,你可以通过它访问自己的文件。认证确保每个用户只能访问自己的信息。

第三,认证可以防止恶意请求。没有认证,用户可能会淹没一个API。

过多的请求,恶意行为者可以更容易地被识别和阻止。

认证API请求最常见的方法之一是使用API密钥。API密钥是API提供商在你注册时分配给你的唯一标识符。这个密钥充当数字签名,证明你的请求来自授权来源。

API密钥通常看起来像这样:一长串字母和数字,有时包含特殊字符。不同的API对密钥的格式有不同的要求。

当你向需要认证的API发送请求时,你需要将你的API密钥作为请求的一部分包含进去。API服务器收到你的请求,并根据其授权的密钥列表进行检查。如果你的密钥有效并且你拥有必要的权限,服务器将发回包含你所请求数据的响应。

然而,如果你的API密钥缺失、不正确或已超过其使用限制,服务器将拒绝你的请求,而不是返回数据。响应可能包含类似“无效API密钥”的错误消息,具体取决于具体问题。

现在你已经了解了API密钥的目的以及它们如何工作,接下来请观看下一个视频,学习如何编写代码来使用API密钥。


本节课中,我们一起学习了API密钥的核心概念。我们了解到,API密钥是访问大多数受保护API的必备“通行证”,它用于身份验证、控制访问权限、管理资源使用和防止滥用。记住,保护好自己的API密钥就像保管好家门钥匙一样重要。

032:API密钥使用 🔑

在本节课中,我们将学习如何使用API密钥进行身份验证,以访问需要认证的API数据。我们将通过一个食品安全检查报告的API实例,演示如何获取、加载并使用API密钥来成功获取数据。


概述

在之前的课程中,我们了解到许多API需要一个密钥来进行身份验证。没有这个密钥,你将无法访问感兴趣的数据。在接下来的课程中,我们将继续使用公开数据,以提高透明度和食品安全性。我们启动了一个新项目,旨在开发一个报告系统,为消费者提供美国各地食品制造工厂安全性的最新信息。我们的计划是分析政府检查报告中的公开数据,以支持这个报告系统。我们已经找到并注册了一个食品安全检查报告API,该API提供食品加工设施的最新检查报告数据。该提供商要求通过API密钥进行身份验证才能访问。你将在接下来的阅读材料中学习如何获取API密钥。我们设置了一个新的笔记本,准备开始使用食品安全API。请记住,你可以使用实践实验室项目来跟随演示。

导入请求模块

首先,我们需要导入requests模块,这是Python中用于发送HTTP请求的常用库。

import requests

设置API基础端点

以下是我们将要使用的基础端点。请注意,此API中的数据是为了在结构上模拟另一个公共检查报告API而生成的。这个API只有一个密钥,我们稍后会看到。我们将使用由deeplearning.ai提供的这个API,以避免注册密钥的麻烦。

base_endpoint = "https://api.deeplearning.ai/food-safety/inspections"

尝试无密钥请求

使用GET请求来检索检查数据。首先尝试在没有密钥的情况下发送请求。注意,params字典是空的。

response = requests.get(base_endpoint, params={})
print(response.json())

你没有得到预期的数据,而是收到一个错误,提示“未经授权:无效或缺少API密钥”。这个错误意味着你需要验证你的访问权限。

加载并使用API密钥

你需要用以下四行代码来完成身份验证。这段代码的细节我们将在下一个视频中详细探讨。现在,请相信这段代码会加载你的API密钥并将其保存到变量api_key中。这一步遵循行业标准的安全实践。

import os
from dotenv import load_dotenv

load_dotenv()
api_key = os.getenv("API_KEY")

如果你查看api_key,它是一个由字母和数字组成的长字符串。请注意,你绝不应该与任何人分享你的API密钥。这里仅用于演示目的,以便你能看到它的样子。

一旦你有了API密钥,你可以通过将其作为参数添加到请求中来使用它。API密钥参数的名称取决于具体的API,你应该查阅其文档。对于这个API,参数名是api_key

params = {"api_key": api_key}
response = requests.get(base_endpoint, params=params)
data = response.json()

当你发送这个请求时,服务器将检查你是否有权限访问数据。如果一切正常,服务器将返回你请求的数据。

解析返回的数据

从数据中可以看到,这个API包含美国不同设施的检查列表。在底部,有一些元数据显示,默认限制是每次10份报告,没有偏移量。offset参数的功能与skip相同,看起来总共有15000份检查报告可用。

检查这些数据的键,这次你有三个选项:datametadatasuccessdata的长度是10,这与限制相符。这个API用于包含结果的键有不同的名称,在这个例子中是data,而不是results

以下是每个检查包含的信息示例:

  • facility_idfacility_name: 唯一标识每个设施。
  • critical_violations: 关键违规数量。
  • noncritical_violations: 非关键违规数量。
  • days_since_last_inspection: 自上次检查以来的天数。
  • inspection_date: 检查日期。
  • 以及其他与每个设施特征相关的功能。

总结

本节课中,我们一起学习了如何使用API密钥进行身份验证。我们看到,要使用API密钥,通常可以将其作为参数添加到params字典中,并随请求一起发送。你需要查阅API文档,以确保你使用了正确的基础端点和参数名称。你已经成功发出了一个经过身份验证的请求并收到了返回的数据。接下来,我们将更详细地分解加载API密钥的代码,以便你能确切了解它是如何工作的。请跟随我进入下一个视频。

033:环境变量 🔑

在本节课中,我们将学习如何安全地管理API密钥。我们将探讨为什么不能将密钥直接硬编码在代码中,并介绍使用环境变量和.env文件来保护敏感信息的行业标准做法。


你已经成功发送了经过身份验证的请求并收到了数据。

但看起来你像是凭空加载了你的API密钥。

让我们退一步,分解一下你所看到的代码是如何安全加载API密钥的。

第一个问题:为什么要费心隐藏你的API密钥?

简短的回答是:你的API密钥就像密码。

你不希望直接将这个密码存储在代码中,因为分享代码是很常见的行为。这就像把你的电子邮件密码存储在一个与所有同事共享的谷歌文档里。

在编程中,通常有多种方法来完成同一项任务,你经常会遇到在简单性和安全性之间的权衡。虽然API密钥在技术上是一个字符串,但你几乎永远不会看到有经验的程序员以这种方式存储他们的API密钥。

像密码一样,API密钥代表你提供对服务的访问权限。如果其他人拿到了你的API密钥,他们可以以你的名义发送请求,在你的账户上产生巨额费用,或者访问可能包含敏感信息的API使用历史记录。

本质上,如果有人拥有你的API密钥,他们就可以登录你的账户。如果你不小心将密钥上传到公共地方,它可能会被窃取。

当你在一家公司担任数据分析师时,你经常需要分享你的代码,但你不想分享你的API密钥。你现在学习最佳实践,是为了避免未来的安全问题。


你可以使用环境变量。环境变量是存储在你的Python文件外部的值,你的代码可以在需要时访问它们。

与你目前一直在使用的变量不同(那些变量只存在于你正在运行的程序内部),环境变量可以被你计算机上运行的多个程序或笔记本访问。

在本课程和上一门课程中,你听到过“Jupyter notebook环境”这个术语。这里的“环境”指的是你计算机中代码运行的工作空间。环境变量是该工作空间的一部分,它们在更高的层级上被管理,独立于任何单个文件。

使用环境变量的主要原因是安全性。它们可以防止像API密钥这样的敏感信息被意外分享。

因此,与其将这些值直接输入到你的代码中,不如将它们存储在代码外部,并在需要时加载。

.env文件是你存储实际秘密API密钥的地方。它是一个纯文本文件,看起来像一堆字符串变量,每个变量独占一行,就像这样:

FOOD_SAFETY_API_KEY=your_actual_key_here

你甚至可以添加多个密钥。只需确保这个文件永远不会被分享或上传到公共网络。

现在,如果你的电脑里只有一个.env文件躺着,它对你没什么用。你需要让你的Python文件能够访问那些信息。

让我们看看你之前使用的代码,并逐行分解它。

import os
from dotenv import load_dotenv

load_dotenv()
API_KEY = os.getenv(‘FOOD_SAFETY_API_KEY’)

以下是每行代码的作用:

  • os模块允许Python与操作系统交互,包括读取环境变量。你有时会看到os被用于在文件夹间导航或与文件交互。
  • dotenv模块帮助从.env文件加载环境变量,这个文件是你之前创建并添加了API密钥的。
  • load_dotenv()函数读取.env文件,并将其内容作为环境变量提供。
  • API_KEY = os.getenv(‘FOOD_SAFETY_API_KEY’)从环境中检索FOOD_SAFETY_API_KEY的值,并将其存储在API_KEY变量中。

关于API密钥的出色工作!你已经学会了一项行业标准的安全措施,作为数据分析师,你会经常用到它。

在下一课中,你将着手清理你检索到的数值数据。我们下节课见。


本节课总结

在本节课中,我们一起学习了:

  1. API密钥的重要性:它类似于密码,需要被保护,以防止未经授权的使用和潜在的安全风险。
  2. 环境变量的概念:它们是存储在程序外部的键值对,可以被多个程序访问,用于将配置信息与代码分离。
  3. .env文件的作用:一个本地存储敏感信息(如API密钥)的纯文本文件,必须避免将其提交到版本控制系统或公开分享。
  4. 安全加载API密钥的代码实践:通过结合使用ospython-dotenv库,从.env文件安全地将密钥加载到Python环境中。

通过采用这种方法,你可以在分享代码的同时,确保你的凭证安全无虞。

034:数据缩放 📏

在本节课中,我们将学习数据缩放的概念及其在数据分析中的应用。我们将通过一个食品安全报告的案例,了解如何通过缩放技术,使不同规模设施的数据能够进行公平比较。


上一节课我们介绍了如何使用环境变量安全地加载API密钥以获取数据。现在数据已经就绪,是时候为分析做准备了。

简单回顾一下,你在这个项目中的角色是帮助提高透明度和食品安全。你的目标是开发一个报告系统,为消费者提供关于食品制造商的最新信息。看看你从所选API获取的数据。

乍一看,你可能会想直接公布违规次数,以帮助消费者比较不同设施。违规次数是指一个设施在检查期间存在的安全问题数量。然而,设施规模差异很大。大型设施自然有更多可能出现违规的空间。因此,简单的计数并不能反映全貌。

例如,如果要比较一个在1000平方英尺内有一次违规的设施,与一个在30000平方英尺内有两次违规的设施,你认为哪个情况更糟?

为了公平地比较它们,你需要缩放你的数据。

缩放是一种将数值调整到一致度量标准的技术,以便在不同背景下进行公平比较。在本例中,根据设施规模计算违规率,可以为你提供更准确的风险度量。

让我们看看如何在Python中缩放数据。

在Notebook的这个阶段,你已经使用API密钥从API请求了数据。你收到的响应是JSON格式,数据包含每个设施的信息,包括其规模、产量以及其他对分析有价值的信息。

首先,你需要修改这个请求以获取所有检查报告。你可以添加一个新的查询参数 limit 并将其设置为1000。1000是该API任何单次请求允许的最大结果数。你可以先预处理这些行来验证你的方法,然后稍后通过分页进行扩展。执行GET请求。

为了将这些数据转换为数据框,使用 pd.DataFrame() 并传入设施列表,即 data[‘data’]

以下是具体步骤:

  1. 首先导入pandas。
  2. 然后创建你的数据框。
import pandas as pd
df = pd.DataFrame(data['data'])

查看 df.info()。你有15个特征和1000行数据。没有缺失值,并且许多特征目前存储为整数,这很好。

你也可以尝试 df.describe() 来更好地了解那些数值特征。概括来说,关键违规的平均值为1.4次,自上次检查以来的平均天数为182天,设施规模的平均值为161,000平方英尺。

假设你想计算每10万平方英尺的违规次数,以创建一个缩放度量。数据集包含关键和非关键违规的详细信息。为了更全面地了解每个设施的问题,你可以将关键和非关键违规次数相加,得到总违规次数。

将此列添加到你的数据框中,它会出现在末尾。

df['total_violations'] = df['critical_violations'] + df['noncritical_violations']

因此,如果你查看第一个设施,它在182,000平方英尺内有2次违规,而第三个设施在116,000平方英尺内有4次违规。

为了缩放这些数据,将总违规次数除以该值,得到每平方英尺的违规次数。将其保存为数据框中的一个新列。

df['violations_per_sqft'] = df['total_violations'] / df['facility_size']

如果你再次查看 df.head(),会发现缩放后的违规值非常小。这是因为大多数设施规模相当大,而违规次数很少。为了使这个度量更容易解释,你可以乘以100,000,将此特征转换为每10万平方英尺的违规次数。

df['violations_per_100k_sqft'] = df['violations_per_sqft'] * 100000

然后你可以描述此列以查看摘要统计信息。例如,平均值约为每10万平方英尺3.2次违规。

现在,如果你再次查看前几行,第一个设施的缩放违规计数为1.09,而设施3为3.43。这是一个更公平的比较,也是对消费者来说更好的报告指标。

因此,第一个设施的缩放违规计数远低于平均值,而第三个设施则更接近平均值。

最后,为了提高可读性,你可以使用 round 方法四舍五入到2位小数。

df['violations_per_100k_sqft'] = df['violations_per_100k_sqft'].round(2)

唯一需要的参数是你想要四舍五入到的小数位数。查看结果以确认四舍五入是否正确。

现在,你可以查看设施规模与缩放违规次数的散点图,以真正识别异常值。

注意,较大的设施通常每10万平方英尺的违规次数较少,而较小的设施仅因几次违规就会受到很大影响。


在本视频中,你计算了每10万平方英尺的违规次数,以便在不同设施之间进行比较。你通过将违规次数除以平方英尺,然后乘以100,000来缩放违规计数,从而得到每10万平方英尺的违规次数。

你还看到了如何对Series使用 round 方法,其中小数位数是唯一的参数。

缩放有助于将原始数字转化为有意义的比较。

接下来,你将探索一种将数值数据分组到有意义类别中的技术。希望你能继续学习。


总结

本节课我们一起学习了数据缩放。我们了解到,直接比较不同规模设施的原始违规次数是不公平的。通过将违规次数除以设施面积,并转换为每单位面积(如每10万平方英尺)的违规率,我们得到了一个更公平、更有意义的比较指标。这个过程就是数据缩放,它是数据预处理中使不同量级数据具有可比性的关键步骤。我们还实践了使用Pandas进行列计算、数据转换和四舍五入操作。

035:分箱处理 📊

在本节课中,我们将要学习一种名为“分箱”的数据预处理技术。分箱可以将连续的数值数据分组到不同的类别或“箱子”中,从而简化数据分析,并使其更易于解释。

上一节我们介绍了数据缩放,它通过将数值调整到共同尺度来确保公平比较。本节中我们来看看另一种提升数据可解释性的方法:将数值数据分组到有意义的类别中。

什么是分箱?

分箱是指将数值数据归类到不同的组或“箱子”中。这个过程有助于通过减少数据噪声来简化数据分析。

分箱主要有两种通用方法:

  1. 使用等频分箱。
  2. 使用自定义分箱。

等频分箱

等频分箱,也称为分位数分箱,是指将数据划分到包含相等数量观测值的箱子中。这种方法能确保各个类别间的样本量均匀,是一种快速简便的数值数据分组方法。

以下是使用Pandas库进行等频分箱的示例代码:

import pandas as pd

# 假设 df 是包含‘production_volume’列的DataFrame
# 使用 qcut 函数进行三等频分箱,并指定标签
df['volume_category'] = pd.qcut(df['production_volume'], q=3, labels=['低', '中', '高'])

# 查看各箱子的计数
print(df['volume_category'].value_counts())

自定义分箱

另一种选择是创建自定义分箱。例如,行业标准可能定义了特定的分界点,或者你可能希望使用百分位数来定义箱子。当数据分布不均匀(特别是高度偏斜)或某些范围更具实际意义时,自定义分箱尤其有用。例如,你可能希望对数据的特定部分(如排名前5%的客户)进行更精细的划分。

以下是如何使用自定义边界进行分箱的示例:

# 定义自定义分箱的边界
# 例如,将分数分为‘有风险’、‘平均’、‘优秀’三类
boundaries = [60, 75, 90, 100]  # 需要 n+1 个边界来定义 n 个箱子
labels = ['有风险', '平均', '优秀']

# 使用 cut 函数进行自定义分箱
df['score_category'] = pd.cut(df['previous_score'], bins=boundaries, labels=labels)

# 查看各箱子的计数和比例
print(df['score_category'].value_counts())
print(df['score_category'].value_counts(normalize=True))  # 显示比例

如何选择分箱方法?

  • qcut 函数:自动确定分箱切点,使得每个箱子包含大致相等的观测值数量。你需要指定要分箱的数据列、箱子的数量(q)以及可选的箱子标签(labels)。
  • cut 函数:创建自定义分箱。这种方法要求你通过向 bins 参数提供一个列表来明确指定箱子的边界。

本节课中我们一起学习了两种将数值数据分组的方法:等频分箱和自定义分箱,并了解了它们各自的适用场景。你已经看到,分箱如何将复杂的数值转化为直观的类别,从而提升数据的可读性和分析效率。

在下一个视频中,你将学习如何使用归一化来进一步提升数据的可解释性。

036:数据归一化 📊

在本节课中,我们将学习如何通过数据归一化,将不同尺度的变量转换到统一范围,从而创建更具解释性的复合评分。归一化是数据预处理的关键步骤,能确保在构建综合指标时,各个变量具有可比性。

概述

上一节我们介绍了数值数据分类的两种分箱方法。本节中,我们将探讨如何通过归一化处理,使数据对利益相关者更具意义。

假设我们需要比较食品制造工厂的健康与安全绩效。影响安全的因素很多,例如设施规模、违规次数、员工培训时长等。我们可以从多个变量中创建一个复合评分,将所有安全方面综合为一个数字。这个评分对消费者来说将更易于理解。

在创建评分之前,需要先应用归一化。归一化是转换数据的过程,目的是让不同变量按一致的比例缩放。

什么是归一化?

归一化通过调整数值使其适应一个共同的范围(通常是0到1之间)来工作。其方法是减去最小值并除以范围(最大值减最小值)。这种方法称为最小-最大归一化,当然也存在其他选项。

Z-score标准化是另一种常见的归一化方法,我们之前已经见过。

归一化确保没有任何单一变量仅因其数值尺度较大而主导结果。例如,如果你想将员工数量和占地面积纳入评分,一个特征的数值通常在几百,而另一个通常在几十万。在这种情况下,占地面积会对评分产生不成比例的影响。

归一化允许你在相同的尺度上比较不同的变量,从而更容易评估相对价值。

构建安全复合评分

我们的目标是创建一个结合了多个检查因素的复合评分。为此,我们可以访问食品安全检查API。该API包含一个总分,但我们不知道它是如何构建的。为了提高透明度,我们将创建自己的评分。

我们将构建一个安全复合评分,它是一个介于0到100之间的单一数字,数值越高表示越好。该评分将基于两个特定输入:

  1. 自上次检查以来的天数:检查越近越好。
  2. 每10万平方英尺的违规次数:违规越少越好。

你需要将每一项归一化,使其处于0到1的尺度上。最后,你需要为每个组成部分分配权重,即它应占评分的多少比例。

  • “自上次检查以来的天数”将贡献 30%
  • “每10万平方英尺的违规次数”将贡献 70%

在当前的代码笔记本中,我们已经建立了一个包含每个设施信息的数据框,其中包括创建了按比例缩放的违规列(即每10万平方英尺的违规次数)。

实施归一化与计算评分

以下是实施步骤:

首先,你需要对两个输入进行最小-最大归一化,使它们处于0到1的共同尺度上。让我们导入一个辅助函数来完成这个计算。

这个 normalize 函数接收数据的一列,并返回该列的归一化结果。它计算 (每个值 - 最小值) / 范围,其中范围是最大值减最小值。这种转换确保该序列中的最小值变为0,最大值变为1,其他所有值介于两者之间。

查看“自上次检查以来的天数”归一化后的输出。它是一个序列,长度与原始列相同,但尺度在0到1之间。例如,设施999的天数几乎是最高的。

在将此保存到新列之前,你需要用1减去它。这是因为在复合评分中,对于“自上次检查以来的天数”这个指标,数值越高实际上越差。距离上次检查的时间越长,对评分的负面影响应该越大。这样处理后,拥有最近检查记录的设施将获得更高的分数。将结果保存到新列 days_normalized

现在对违规次数进行同样的操作。这个指标也需要反转,因为违规越少越好。比较归一化后的违规次数与按比例缩放的违规次数的直方图,两者的分布完全相同,只是方向翻转了。

计算加权复合评分

两个因素都归一化到0到1的共同尺度后,你可以通过计算加权平均值来计算复合安全评分。

  • “自上次检查以来的天数”贡献 30%
  • “每10万平方英尺的违规次数”贡献 70%

最后,将结果乘以100,使你的评分范围在0到100之间,并进行四舍五入,以便获得更易于解释的整数而不是浮点数。将结果保存为数据框的新列。

结果示例

让我们查看一些例子。

  • 对于“Tropical Harst Oasis Operations”设施,距离上次检查已有267天,每10万平方英尺有1.09次违规。他们的归一化天数得分对他们帮助不大,只有0.26(因为267天内可能发生很多变化),但他们在归一化违规次数上得分较高(违规次数相对较少)。他们的复合评分约为73,所以还不错,但不算优秀。
  • 另一方面,“Har Harst Pantry House”的得分为87,高出15分。他们在“自上次检查以来的天数”上得分很好,因为距离上次检查只有大约一个月。

总结

本节课中,我们一起学习了数据归一化的核心概念与应用。我们使用辅助函数进行最小-最大归一化,将不同因素转换到0到1的共同尺度;根据各因素在食品安全中的重要性为其分配权重;并最终创建了一个可用于比较不同设施的单一复合评分。

你刚刚创建了一个评分系统,让消费者能够快速评估不同设施的食品安全风险。在数值数据中,一个常见的情况是存在异常值,即不寻常的数值。请跟随我进入下一个视频,学习如何识别它们。

037:异常值识别 🎯

在本节课中,我们将学习什么是数据中的异常值,以及如何识别它们。理解异常值对于确保数据分析的准确性和可靠性至关重要。

什么是异常值?

异常值是数据集中的不寻常数值。它们可能难以定义和处理。

顾名思义,异常值是指那些远离数据主体范围的数值。在数据分析的应用统计学中,异常值通常表现为箱线图“须”之外的独立数据点。理解异常值具有挑战性,有时它们代表真实的变异性,有时则是错误或异常。在决定如何处理异常值之前,区分这两种情况可能很困难,因此,弄清楚它们存在的原因非常重要。

你应该对每个特征的可能数值范围有一个预期。例如,在之前的一个视频中,你遇到了使用pandas将一列数据转换为日期时间格式的问题。检查有问题的数值后,你发现该日期代表212年,而所有其他数据都在2008年至2025年之间。这显然是一个错误,而非真实的变异性。

如何识别异常值?

你有几种识别异常值的方法。以下是两种常用方法:

  • 四分位距法:此方法用于识别箱线图中的异常值。它将异常值定义为低于第一四分位数 - 1.5倍IQR 或高于第三四分位数 + 1.5倍IQR 的数值。公式表示为:
    Outlier < Q1 - 1.5 * IQROutlier > Q3 + 1.5 * IQR
  • Z分数法:这种方法对正态分布的数据效果很好。在此方法中,你通过减去均值并除以标准差来计算每个数据点的Z分数。通常,Z分数大于3或小于-3的数据点被视为异常值。

你对此有直觉吗?回想一下“3西格玛法则”,在正态分布中,99.7%的数据点预期落在均值的三倍标准差范围内。因此,这里的异常值被定义为最极端的0.3%的数值。与中位数类似,IQR法对偏斜数据更具鲁棒性。当你怀疑数据可能遵循正态分布时,Z分数法可能更合适。

总结

本节课我们一起学习了异常值的概念及其重要性。我们介绍了两种主要的识别方法:基于数据分布的四分位距法和适用于正态分布的Z分数法。理解这些方法是正确处理数据异常的关键第一步。

现在你已经熟悉了识别异常值的步骤,请跟随我进入下一个视频,学习如何处理这些异常值。

038:异常值处理 🎯

在本节课中,我们将学习如何识别和处理数据集中的异常值。异常值是指那些与数据集中其他观测值显著不同的数据点,它们可能由测量误差、数据录入错误或真实的极端情况引起。正确处理异常值对于确保数据分析的准确性和可靠性至关重要。

概述

识别出异常值后,工作并未结束。一旦确定了数据中的哪些值是异常值,就需要选择合适的方法来处理它们。处理异常值有三种主要选项,这些选项与处理缺失值的方法有相似之处。

处理异常值的三种方法

以下是处理异常值的三种主要策略:

  1. 删除异常值:如果异常值明显是错误,删除它们是合理的做法。但需要注意,不应在没有正当理由的情况下随意删除异常值。
  2. 保留并单独分析:有时异常值本身可能包含有价值的信息,但若与其他数据点一起分析会扭曲整体结果。此时,可以将异常值保留下来,但进行单独分析。
  3. 转换异常值:可以使用预定义的百分位数替换极端值,或使用对数变换等方法。这些处理过程相对高级,超出了本课程的范围。你可以利用大型语言模型进一步探索这些概念。

实战演练:分析员工培训时长

假设你被要求查看不同工厂提供的培训时长数据。你的同事担心数据中存在异常值,并建议删除其中大部分。你的目标是计算每位员工的平均培训时长。

让我们通过代码来具体分析。在当前的笔记本中,我们已经建立了一个包含各工厂信息的数据框,其中包括创建了标准化违规数列和综合评分列。

在进行任何操作之前,让我们先标准化“月度培训时长”这一列。因为各工厂的员工数量不同,我们需要创建一个新列,用月度培训总时长除以员工数量。

# 创建人均月度培训时长列
df[‘training_hours_per_employee’] = df[‘training_hours_monthly’] / df[‘employee_count’]

前三个工厂的人均培训时长约为2.6小时,接着是约3小时,然后是约3.3小时,这些数据看起来相当一致。

下一步是查看该特征的直方图,以判断数据是否服从正态分布。这将帮助你决定应该使用四分位距法还是Z分数法来识别异常值。

这个直方图看起来有些奇怪。让我们查看describe函数的输出,以了解更多关于数据分布的信息。

# 查看数据描述性统计
df[‘training_hours_per_employee’].describe()

观察这些统计数据时,最引人注目的是最大值68。这很可能是一个异常值,这也是为什么直方图中只有一个高大的条形。除此之外,可以看到均值和中位数相对接近,因此数据分布看起来仍然可能是对称的。所以,使用Z分数法来识别异常值是合理的。

使用Z分数识别异常值

创建一个新列用于存储Z分数。Z分数的计算公式是:每个值减去均值,然后除以标准差。

首先,让我们使用箱线图可视化人均培训时长的分布。

# 绘制箱线图
import seaborn as sns
sns.boxplot(x=df[‘training_hours_per_employee’])

从可视化结果中,可以看到有三个数据点明显超出了其余数据的范围。这些是明确的候选过滤点。每月人均40小时或更多的培训时长看起来非常极端,因此这些很可能是错误数据。

接下来的步骤是过滤掉这些极端值。根据你的直觉,可以得出结论:在给定的月份中,每位员工接受超过20小时的培训是不现实的。请记住,你可能希望将来将分析扩展到完整的数据集,因此需要给自己留出一些空间,以排除可能观察到的其他异常值。

分析过滤后的数据

现在你已经移除了那些极端的异常值,绘制剩余数据的分布直方图可能会很有用。

# 绘制过滤后的直方图
filtered_df = df[df[‘training_hours_per_employee’] <= 20]
sns.histplot(x=filtered_df[‘training_hours_per_employee’])

根据这个可视化结果,看起来这里有两组数据:一个较大的数据簇集中在每月人均约3小时培训时长附近,还有一个较小的数据簇,其人均月度培训时长稍高一些。

让我们使用Z分数法来探索是否能系统地将这两个簇分开。

计算Z分数并分割数据

要计算Z分数,你需要从每个培训时长值中减去均值,然后除以标准差。从直方图来看,低端似乎没有异常值,因此你可以通过只过滤出Z分数大于3的数据点来分割数据。

让我们在数据中创建两个部分:第一部分包含Z分数小于或等于3的数据点;异常值部分则包含Z分数大于3的数据点。

# 计算Z分数
mean_val = filtered_df[‘training_hours_per_employee’].mean()
std_val = filtered_df[‘training_hours_per_employee’].std()
filtered_df[‘z_score’] = (filtered_df[‘training_hours_per_employee’] - mean_val) / std_val

# 分割数据
non_outliers = filtered_df[filtered_df[‘z_score’] <= 3]
outliers = filtered_df[filtered_df[‘z_score’] > 3]

比较两个数据簇

现在让我们分析这两个数据段,以理解这两个簇之间的差异。我们来看看这两个部分(异常值和非异常值)的产量数据。

# 比较平均产量
mean_prod_non_outliers = non_outliers[‘production_volume’].mean()
mean_prod_outliers = outliers[‘production_volume’].mean()
print(f“非异常值工厂平均产量: {mean_prod_non_outliers:.0f} 单位”)
print(f“异常值工厂平均产量: {mean_prod_outliers:.0f} 单位”)

看起来,非异常值工厂的平均产量略低于11000单位,而异常值群体的平均产量约为20000单位。这表明,人均月度培训时长较高的工厂,其平均产量也显著更高。

这个结果可能会促使你进一步深入研究这些生产工厂之间的差异。一个可能的结论是,投资于更高的员工月度培训可能会带来更高的平均产量。但另一个可能的结论是,导致高产量的可能不是培训时长本身,而是这些工厂包含了需要更多培训的专业员工。

你可以通过推断统计进一步深化这个分析,但至此,你已经学会了如何处理异常值:既包括删除错误的异常值,也包括分离出合理的异常值进行分析。

总结

本节课中,我们一起学习了异常值处理的完整流程。我们首先了解了处理异常值的三种策略:删除、保留并单独分析、转换。接着,我们通过一个分析员工培训时长的实战案例,演示了如何利用Z分数法识别异常值,并通过可视化(箱线图、直方图)和数据分割来深入理解数据。最后,我们比较了不同数据簇的特征,并探讨了数据背后可能的故事。你已经掌握了处理新数据中异常值的关键技能,为进行高质量的数据分析打下了坚实基础。

039:数据质量评估 📊

在本节课中,我们将学习数据质量的核心概念及其四个关键维度。理解数据质量是数据分析的基础,低质量的数据可能导致错误结论或增加分析难度。

概述

作为数据分析师,理解数据质量至关重要。低质量数据可能带来后续问题,甚至导致错误结论。数据质量参差不齐,在职业生涯中,你将同时处理高质量和低质量的数据。有时,低质量数据集是唯一可用的数据,但你仍可能从中提取有价值的见解。

数据质量可以分解为四个关键维度。

数据质量的四个维度

以下是评估数据质量的四个核心方面:

  1. 完整性:所有必需的值是否都存在?
  2. 准确性:数据是否正确反映了现实世界?
  3. 一致性:相同的数据在所有记录中是否以相同的方式呈现?
  4. 及时性:数据是否是最新的?

如果你的数据完整、准确、一致且及时,那么你可以认为它是高质量的数据。

数据质量问题的影响

每个维度都会在你的分析中引发不同的问题。

例如,不完整的数据可能使分析无法进行,而不准确的数据虽然允许你进行分析,但无法得出可靠的结论。不一致的数据需要花费大量时间进行预处理,而不及时的数据则可能使你的结论缺乏普遍性。

请注意,这些维度与你的业务问题相关。例如,什么算作“及时”,可能取决于你试图回答的具体问题。

案例分析:“Ask a Manager”薪资数据集

在本课程中,你已经多次遇到“Ask a Manager”薪资数据集。它之所以引人注目,正是因为其混乱的特性。让我们从“估算不同行业真实平均薪资”的角度,来审视这个数据集的数据质量维度。

以下是2019年的调查回复。

首先从及时性维度来看,如果你试图找出当前各行业的真实平均薪资,那么这份数据缺失了疫情时期和几年的通胀影响。从及时性角度看,这可能不是最有用的数据。

完整性来看,大多数行似乎都已填写,但有必要确定有多少缺失的行业和薪资数据,因为你的分析需要这些信息。

至于准确性,这些数据都是自我报告的。你无法验证这些人是否说了实话。

一致性的角度看,这份数据的质量确实很差。正如你所见,例如在第245行,有人在金融科技行业工作,职位是企业社会责任经理。

他们的薪资看起来是170万美元,但他们将货币单位填在了“其他货币”类别中。你需要单独分析这些条目,而识别它们可能具有挑战性,因为这是一个纯文本字段。

你之前也见过这些薪资不一致的情况。这个人写了“90K”而不是“90000”。这个人写了“42000-ish”,而这个人则包含了他们的健康保险津贴。因此,你需要进行大量的文本处理,可能需要使用一些正则表达式来尝试移除所有文本和符号,并处理缩写形式。

这个数据集有趣之处在于,这项调查仍然存在,并且调查的所有者实际上已经更新了他们的问题,解决了许多你刚刚发现的问题。

数据质量的改进:2024年数据

以下是2024年的数据。每一行仍然是一个调查回复,但请注意问题的变化。

例如,现在他们区分了“雇主所在行业”和“你的工作职能领域”。例如,这个人的职能领域是教育,但他们在医疗保健行业工作。这可能是一个大学的学生健康服务主任。

他们还区分了总收入与额外薪酬,并分别列出了国家、州和地区。

因此,一致性得到了极大改善。关于完整性,看起来大多数字段都是必填的。薪资字段似乎都是数字。所以这是质量高得多的数据,不仅更新,而且更完整、更一致。

然而,这份数据在准确性维度上仍然存在问题。你仍然不知道这些人是否真的报告了他们的真实薪资。这可能是一份有偏见的调查,只在某些社区或高收入人群中流传。

开始分析前的自问

这些都是你在开始处理数据之前应该检查的数据质量维度。问自己一些尖锐的问题:

  • 这份数据真的比没有数据好吗?
  • 这份数据真的能帮助回答我试图解决的业务问题,还是会制造更多困惑?

总结

我们已经了解了为什么数据质量检查是数据分析中最重要的方面之一。在开始预处理之前,你应该始终进行质量检查。

本节课到此结束,你也即将完成本模块的学习。完成练习实验和练习作业后,你将完成本课程的评分项目。在评分实验中,你将使用人口普查数据为非营利组织识别弱势群体。

完成后,我们将在下一个模块再见,该模块将全部关于数据库。

040:模块3 简介 🗄️

在本模块中,我们将学习在数据库中处理数据的基础知识。数据库在商业中无处不在,它们能帮助高效地管理大量数据。通过本模块的学习,你将掌握从数据库中检索、限制和排序数据的方法,并理解其工作原理与优势。


第一课:数据存储系统概览

上一节我们介绍了本模块的整体目标,本节中我们来看看数据存储的各种形式。数据可以存储在多种系统中,选择合适的数据存储系统会直接影响分析师的工作效率。

以下是常见的数据存储选项:

  • 文本文件:如 .txt 文件。
  • 平面文件:如 .csv 文件。
  • 结构化格式:如 JSON 格式。
  • 数据库:用于管理大规模数据集。

此外,你将了解到数据库是由一种称为关系型数据库管理系统的软件进行管理的。


第二课:数据库中的数据组织

了解了数据存储在哪里之后,本节我们将探讨如何有效地在数据库中组织数据。关键在于对现实世界中的实体及其关系进行建模。

你将学习数据库如何表示这些实体和关系,这是构建高效、可查询数据库的基础。


第三课:SQL 入门与实践

在掌握了数据组织方式后,本节我们将开始学习 SQL。SQL 是一种专门为访问和操作数据库数据而设计的编程语言,是当今分析师最重要的技能之一。

通过动手实践,你将学习提取、组织和操作数据的技巧。这些技术将为你在本课程最后一个模块中进行更高级的预处理和分析打下坚实的基础。

以下是本课将涉及的核心操作:

  • 提取数据:使用 SELECT 语句。
  • 组织数据:使用 ORDER BY 进行排序。
  • 限制数据:使用 LIMIT 子句。

总结

本节课中,我们一起学习了数据库模块的核心内容。我们从数据存储系统开始,探讨了如何有效地在数据库中组织数据,并最终入门了用于数据操作的 SQL 语言。掌握这些知识将使你能够自信地从数据库中检索、限制和排序数据。

让我们在下一个视频中,从数据存储系统开始正式学习。

041:40_数据存储系统 📂

概述

在本节课中,我们将要学习数据存储系统。数据可以以多种方式存储,具体取决于其结构、复杂性以及业务需求。我们已经了解了多种数据输入方法,包括平面文件、网络爬取和API。现在,我们将深入探讨数据存储的细节,了解不同的存储方式及其对数据分析的影响。


数据存储的重要性

数据存储指的是为了后续访问和数据分析而保留数字信息。在你之前学习的数据分析生命周期中,第一步是定义问题,第二步是收集和预处理数据,第三步是分析数据并识别洞察,第四步是分享结果,第五步是评估结果。

在开始分析数据或分享结果之前,你的数据需要在第二步中被存储起来。数据存储的位置和方式直接影响你从中获取洞察的难易程度。

事实上,数据存储是一个相当复杂的问题。正如你在课程早期所学,有一个专门的职位——数据工程师——负责收集和存储数据。


数据存储方式的演进

根据复杂度的递增顺序,数据可以存储在文本文件、平面文件(如CSV或Excel)、结构化文件格式(如JSON)或关系型数据库中。

  • 文本文件:通过允许你稍后检索信息来实现数据存储的基本功能,但它不提供太多结构。
  • CSV文件:对于以行和列的形式结构化数据非常有用。
  • JSON文件:正如你在上一个模块中所见,它比平面文件更进一步,因为它可以分层组织数据。
  • 关系型数据库:这是最终的数据存储机制。它将数据存储在多个表中,每个表都有行和列,并且可以表示这些表之间的关系。

在接下来的视频中,你将更深入地了解数据库。


存储需求随业务增长而变化

数据存储需求通常会随着公司的发展而演变。

思考一下你如何管理自己的购物清单。如果只有几件物品,你可能会在手机的备忘录应用中草草记下。但假设你开始经营自己的杂货配送业务。如果你需要为多人管理购物清单,你就需要开始以结构化的方式组织它们,以防止遗漏物品,并能够分析这些清单。

你可以使用像Google Sheets这样的电子表格程序,它可以让你高效地跟踪许多客户的数据,并与你的团队共享访问权限。

但是,假设你现在每周要为成千上万的客户管理杂货配送,这些客户遍布多个地区,有不同的定价层级和季节性折扣。起初,导出一个CSV文件然后加载到Python笔记本中进行分析可能可行,但随着数据增长,这会变得越来越慢。此外,它不会动态更新。为了保持分析的时效性,你必须不断下载数据。如果多个团队成员在处理同一个文件,很容易创建冲突的版本,并意外覆盖他人的工作。

适用于少数客户的解决方案将不再适用。此时,你的公司可能会开始使用数据库。


分析师在数据存储中的角色

作为分析师,你的工作通常不是决定最合适的存储系统或自己构建它。相反,你的角色是理解每个系统提供的权衡,以便你能更好地为选择系统的决策者和构建它的数据工程师提供建议。

你应该将自己视为数据生态系统中的积极参与者,而不是被动的消费者。事实上,你可以帮助塑造公司存储信息的方式,并确保在你需要时,所需的数据已准备就绪且可用。

请记住,所有这些数据存储步骤都发生在你将数据加载到Python笔记本进行分析之前。你已经了解了如何从CSV文件、API或通过网络爬取导入数据。你可以使用已经学到的技术,对来自数据库的数据执行相同类型的预处理和分析,尽管处理不同来源的数据可能会面临不同的挑战。


总结

本节课中,我们一起学习了数据存储系统的基础知识。我们了解到,数据存储是数据分析生命周期中至关重要的一步,存储方式的选择直接影响后续分析的效率和效果。我们探讨了从简单的文本文件到复杂的关系型数据库等多种存储方式,并理解了存储需求如何随着业务规模的增长而演变。最后,我们明确了数据分析师在数据存储生态系统中的角色——不是被动地接受现有系统,而是主动理解不同存储方案的优劣,为团队决策提供支持,并确保所需数据的可用性。你已经具备了处理这些数据所需的分析技能。

你看到数据库比文本文件、平面文件甚至JSON数据更强大、更灵活,但它们到底是什么,又是如何工作的呢?请跟随我进入下一个视频了解更多。😊

042:数据库概念 📚

在本节课中,我们将要学习数据库的核心概念。我们将了解数据库是什么、它们如何工作,以及它们相比其他数据存储方式(如电子表格)的优势。


什么是数据库?💾

数据库在数据分析中无处不在。让我们来看看它们是什么以及它们如何工作。

数据库的核心是一个用于存储数据的结构化系统,它被设计为能够高效地处理海量数据。你可以将数据库想象成一个本质上由一系列有组织的文件组成的集合,并附有一个专门的软件。该软件处理数据如何从文件中添加、删除或更新,并管理所有文件之间的关系。

数据库通常托管在服务器或云端,以便许多人可以从自己的计算机访问它们。但就像任何其他文件集合一样,只要你有足够的空间,你也可以在自己的计算机上存储数据库。


数据库的优势 🚀

上一节我们介绍了数据库的基本定义,本节中我们来看看数据库相比电子表格和CSV文件有哪些关键优势。数据库专为处理大量数据而设计,因此具有几个关键优势。

以下是数据库的主要优势:

  1. 高效性:数据库在存储和检索大量信息方面极其高效。这里的“高效”意味着快速。它们使用优化技术,允许你快速检索数据。
  2. 并发性:数据库被设计为可以处理多个并发用户。数据库的软件部分明确处理潜在的冲突,并动态管理谁可以与存储的信息进行交互。
  3. 数据完整性:数据库通常强制执行约束。约束是关于数据应如何存储的规则。例如,一个约束可能强制要求某个值必须是数字,或者一行的所有单元格都必须填充值,而不是缺失。这些约束维护了数据的完整性。
  4. 关系建模:数据库可以建立关系模型。它们可以连接不同表中的相关信息,而这在电子表格中很难做到。你在之前的课程中简要接触过这个想法,当时你看到了如何使用电子表格函数 XLOOKUP 从另一个相关的表中抓取数据。


如何与数据库交互?🔍

了解了数据库的优势后,我们来看看如何与数据库进行交互。

你将通过一个称为“查询”的过程与数据库交互。你会向数据库发送一个查询,然后它会返回数据给你。这类似于你在网络抓取或使用API时已经习惯的请求-响应周期。

一个数据库查询可以返回满足特定条件的行,可以返回汇总统计信息,甚至可以返回连接在一起的多个表的行。查询实际上可以变得相当复杂,允许你精确地请求所需的数据。你将在本模块的第三课中编写你的第一个查询。

你可以通过公司提供的网站查询数据库,也可以使用像Python笔记本中的代码来查询。在本课程中,你将看到这两种方法。


数据库的类型:关系型 vs. 非关系型 🗂️

上一节我们介绍了如何查询数据库,本节中我们来看看数据库的两种主要类型。

数据库可以大致分为两种主要类型:关系型和非关系型。

  • 关系型数据库将数据组织成定义良好的表格,类似于具有行和列的电子表格,行和列分别代表记录和字段。这里的“记录”是观测值的另一个术语,“字段”是特征的另一个术语。每个表代表一个“实体”,这是对你所观测对象(如客户、订单、产品、鬣蜥)的一个花哨术语。每一行代表一个实体(例如,一只鬣蜥)。例如,一个企业有下订单的客户。客户和订单是两个不同的实体。关于每个实体的数据被分开收集和存储,因为客户详细信息(例如,姓名、联系方式等)与订单详细信息(例如,产品名称、订单日期)不同。但数据库可以连接这两个概念,以便你可以分析这些实体如何交互(例如,跟踪哪些客户下了哪些订单)。关系型数据库是最常见的数据库类型。
  • 非关系型数据库通常用于存储结构和内容多变的数据,因为它们不使用具有固定列的表格。虽然它们牺牲了关系型数据库的一些严格一致性,但它们通常为具有灵活结构的高容量数据提供更好的性能。

总结 📝

本节课中我们一起学习了数据库的基础知识。我们了解到数据库是用于高效存储和管理大量数据的结构化系统。我们探讨了数据库相比电子表格的四大优势:高效性、并发性、数据完整性和关系建模能力。我们还学习了通过“查询”与数据库交互,并区分了关系型和非关系型数据库的主要特点。在接下来的视频中,我们将深入了解数据库背后的软件——数据库管理系统。

043:数据库管理系统 🗄️

在本节课中,我们将学习数据库管理系统(DBMS)的核心概念,了解它是什么、有哪些常见的类型,以及它在数据管理中的关键职责。

概述

上一节我们介绍了数据库是文件和用于管理这些文件的软件的集合。本节中,我们来看看这个软件组件——数据库管理系统。

什么是数据库管理系统?

数据库管理系统是管理数据库文件的软件组件。

一些常见的关系型数据库管理系统包括 MySQLSQLitePostgreSQL(有时缩写为 Postgres)、OraclePresto。您可能还会遇到非关系型数据库管理系统,如 MongoDBCassandra

一个有趣的例子是,Netflix 就使用了 Presto。本课程将使用 SQLite

有时很难理解这些应用程序之间的区别。可以做一个类比:想象一下 CSV 文件与 Numbers、Google Sheets 或 Excel 这类程序之间的关系。

CSV 文件就像数据库存储的文件集合。所有这些程序都可以加载 CSV 文件,尽管每个程序都有自己的功能和界面。

MySQL、SQLite 等数据库管理系统类似于 Numbers、Google Sheets 和 Excel。它们各自创建和管理数据库文件,您在不同的公司很可能会遇到不同的系统。

关系型数据库管理系统的职责

关系型数据库管理系统,也称为 RDBMS,是作为数据分析师或您使用的任何应用程序与数据库文件之间的接口。

它承担着多项职责。

以下是其主要职责列表:

  • 处理查询:当您需要从数据库获取数据时,您会编写一个查询并将其发送给 RDBMS。RDBMS 会解释该查询并返回结果。
  • 控制并发:它管理多个用户同时与数据库交互的情况,确保同时进行的更新不会相互冲突。
  • 负责安全:通常通过身份验证来控制谁可以访问哪些数据。
  • 维护备份与恢复机制:这有助于防止数据丢失。

您之前了解到,数据库的一个优势在于它们能强制执行关于所存储数据的约束或规则。RDBMS 就是实际执行这些规则的引擎,这有助于维护数据完整性。

您可以将数据完整性理解为类似于数据质量,其关键维度包括准确性、完整性和一致性。

数据完整性与约束

RDBMS 维护着防止无效数据被存入表的规则。

例如,它可能强制执行这样的约束:表中的每一行都必须有一个唯一的标识符,比如客户 ID。

约束也可以相当复杂。例如,您的数据库可能建模了客户和订单之间的关系,而 RDBMS 可能强制执行这样的约束:订单不能引用一个不存在的客户。

当有人试图将违反约束的数据放入数据库时,RDBMS 将拒绝该操作并提供错误消息。

与通常为可选功能的电子表格数据验证相比,这种强制执行要严格得多。

总结

本节课中我们一起学习了数据库管理系统的核心概念。您现在已经掌握了在数据库中存储数据背后的基本原理。完成练习作业后,请跟随我进入下一课,学习数据库的组织结构。

044:整洁数据原则 📊

在本节课中,我们将学习“整洁数据”这一核心概念。我们将了解整洁数据的定义、其三条基本原则,以及遵循这些原则如何使数据预处理和分析变得更加高效和可靠。


什么是整洁数据?

上一节我们介绍了数据处理中可能遇到的混乱情况。本节中,我们来看看如何定义和组织“整洁”的数据。

观察左侧关于教师和学生的数据,其中包含不同学生的成绩、出勤率、教师信息以及底部的汇总信息。想象一下,如果你尝试使用 pd.read_csv() 将这个数据读入Python进行分析,将会非常困难。

分析之所以困难,主要有两个关键原因:

  1. 并非每一行都对应一个单一的观察结果。例如,某一行同时包含了学生和教师的信息。
  2. 并非每一列都只包含一个特征。例如,C列不仅混合了数学和英语两种分数,还包含了文本说明和汇总数据。

这种将多个实体(如学生和教师)混合在同一个表格中,且行列结构不规范的数据,就是不整洁的数据


整洁数据的三原则

“整洁数据”虽然听起来像日常用语,但实际上是一个技术术语。它指的是符合以下三个条件的数据:

  1. 每个特征构成一列。每一列应只代表一个变量或属性。
  2. 每个观察构成一行。每一行应只代表一个独立的观察样本。
  3. 每种类型的实体拥有自己的表。例如,学生和教师的信息应分别存放在不同的数据表中。

这个概念由数据科学家Hadley Wickham于2014年正式提出。他在一篇著名的论文开篇写道:“人们常说,数据分析80%的时间都花在清理和准备数据上。”他的目标正是通过这三条规则,来定义如何维护易于分析的数据结构。

以下是遵循整洁原则重新组织后的教师和学生数据:

  • 学生表:每个实体(学生)独占一表。每一行是一个观察(一名学生),每一列是一个特征(ID、姓名、数学成绩、英语成绩、出勤率)。
  • 教师表:同样,每一行是一名教师,每一列是一个特征(姓名、所授科目)。

将数据以此种结构存储后,使用 pd.read_csv() 导入并进行分析就变得非常简单。你几乎不需要进行任何预处理,因为你可以按照熟悉的方式操作行和列,而无需处理之前所见的那种杂乱无章的结构。


整洁数据的好处

了解了整洁数据的定义后,我们来看看遵循这些原则能带来哪些具体好处。

整洁数据为预处理和分析带来诸多优势:

  • 标准化数据清洗:你可以开发出可重复使用的工具来解决类似问题。如果没有整洁的数据,每次拿到新数据集时,你可能都需要调整清洗工具。
  • 最小化错误:通过降低复杂性,整洁数据使得汇总、可视化和建模都可以采用标准方法进行。这让你能更专注于业务问题,而非繁琐的数据管理问题。
  • 提升效率:整洁的数据结构与Pandas中常用的向量化操作能完美配合,从而提高计算效率。

尽管你可以使用Pandas和Python来整理不整洁的数据,但更好的做法是在工作流程的更早阶段(例如,在数据存入数据库时)就以整洁的方式存储数据。


总结与延伸

本节课中,我们一起学习了“整洁数据”的核心概念。我们明确了整洁数据的三个基本原则:每个特征一列、每个观察一行、每种实体一表。遵循这些原则能显著提升数据处理的标准化程度、减少错误并提高效率。

如果你对这个概念感兴趣,强烈推荐阅读Hadley Wickham在2014年发表的论文。这是一个非常精妙的概念,很难相信如此简单的理念直到近年才被正式定义。在实际工作中,应尽可能遵循整洁数据的原则。

接下来,我们将在下一个视频中看看实体和属性如何在整洁的数据库中进行表示。

045:实体与属性 🧱

在本节课中,我们将学习关系型数据库中“实体”与“属性”的核心概念。这是理解如何构建整洁、高效数据库的基础。

上一节我们介绍了整洁数据原则如何简化数据分析。本节中,我们将进一步学习这些原则如何应用于关系型数据库的实体和属性。数据的结构方式,包括其分类和关联方式,对其分析价值有重大影响。

实体:数据库中的独立事物

实体代表现实世界中一个独立的事物。这些事物可以是有形的,如人、产品或鬣蜥;也可以是无形的,如事件或过程。在关系型数据库中,实体通常以表的形式表示。

实体有两个关键约束:

以下是关于实体的两个关键约束:

  1. 每个实体必须可被唯一标识。
    例如,假设你有一个客户表,其中有多个同名客户(如“John Smith”)。如果没有唯一的标识符(如客户ID),就无法区分不同的“John Smith”,也无法追踪他们各自的交易。

  2. 每个表必须只包含一个实体。
    例如,你的客户表应该只包含客户信息。如果一个表同时包含了客户详情(如姓名、邮箱)和订单详情(如产品名称、购买数量),你就创建了一个混合实体,这会使分析变得更加复杂。相反,客户数据应存储在一个表中,订单数据存储在另一个表中。当需要组合客户和订单数据时(例如,分析每位客户购买了哪些不同种类的商品),你可以使用一种特殊的查询来临时连接这些表。但为了保持整洁性,客户和订单数据应分开存储。

主键:实体的唯一标识符

数据库表中的唯一标识符称为主键。例如,客户表中的 customer_id。虽然许多客户可以有相同的姓名或订单数量,但任何两个客户都不能拥有相同的ID。主键允许你区分两个信息完全相同的客户。

主键具有以下特性:

  • 非空性:这意味着每一行都必须有一个主键。你的关系型数据库管理系统会强制执行此约束,并拒绝任何试图添加主键为空的行的操作。
  • 稳定性:这意味着其值理想情况下应永不改变。例如,customer_id 很可能永远不会被更新,除非客户删除账户并重新创建。

属性:实体的特征

实体拥有属性,即该实体的特征。你可以将属性视为数据中的特征。例如,在客户表中,属性可能包括姓名、邮箱和电话号码。就像在 Pandas 的 DataFrame 中一样,数据库的每一列都代表一个属性。

属性最好有明确定义的数据类型。在 Python 中处理数据时,你应该知道每列包含什么类型的数据,并且该列的所有数据都应是同一类型。数据库的数据类型选项与 DataFrame 非常相似,包括:

  • 文本:用于姓名、地址或描述。
  • 整数:用于离散数值,如年龄或数量。
  • 浮点数:用于连续数值,如价格。
  • 布尔值:用于真/假标志,如客户是否活跃。
  • 日期和时间:用于捕获时间数据,如订单日期。

数据库中的主键通常是整数。你应该仔细考虑存储特定属性的最佳类型。例如,存储电话号码时,你可能会认为应该将其存储为数字。然而,实际上通常将电话号码存储为文本。电话号码通常包含特殊字符(如国家代码前的加号“+”),并且如果电话号码以零开头,当该值被存储为整数时,这个零会消失。

总结与展望

本节课中,我们一起学习了关系型数据库的核心构件:实体属性。我们了解到实体是独立的、可唯一标识的事物,以表的形式存在;而属性是实体的特征,以列的形式存在,并具有特定的数据类型。主键作为实体的唯一标识符,确保了数据的准确性和可追溯性。

一旦确定了数据库中应包含的实体和属性,下一步就是通过建立关系将它们连接起来。请跟随我进入下一个视频,了解如何实现这一点。

046:关系型数据 📊

在本节课中,我们将学习关系型数据库的核心概念——如何通过建立表与表之间的关系来有效组织数据。我们将重点理解外键的作用,以及如何用它来建模“一对多”、“多对多”和“一对一”这三种基本关系。


数据库的核心优势:建模关系

数据库的一个关键优势在于能够为不同实体之间的关系建立模型。

上一节我们介绍了数据库和表的基本概念,本节中我们来看看如何在数据库中表示实体间的联系。

在商业环境中,数据通常涉及许多相互关联的实体。例如,你可能需要追踪客户、订单和产品的信息。这些实体并非孤立存在,而是紧密相连的。客户下达订单,而每个订单包含特定的产品。一个客户可以有多个订单,每个订单也可以包含多个产品。然而,正如你所见,这些不同的实体被存储在不同的表中。

那么,数据库如何有效地为这些关系建模呢?

关系背后的思想:使用外键

关系背后的核心思想是为每个实体提供一种引用另一个表的方式。

以下是一个例子。假设你有一个 customers(客户)表。每个客户作为一行,拥有诸如 customer_id(客户ID)、name(姓名)和 email(邮箱)等属性。customer_id 是主键。

你还有一个 orders(订单)表。每个订单作为一行,拥有诸如 order_id(订单ID)、order_date(订单日期)、products(产品)和 quantity(数量)等属性。

你如何将订单与下达该订单的客户关联起来呢?

你可以为 orders 表添加一个新属性:customer_id。现在,对于代表单个订单的每一行,你都有一个对 customers 表的引用,这使你能够在订单表中唯一标识下达订单的客户。

在订单表中,order_id 是主键,而 customer_id 被称为外键。之所以称为“外”键,是因为它来自另一个表,几乎就像是对另一个“领域”的引用。它的目的是将订单链接回客户表中对应的客户。

总结来说,数据库使用外键来定义实体之间的关系。外键是一个表中对另一个表主键的引用。这种设置允许不同类型的数据保持连接,同时遵循整洁数据的原则。

关系型结构的优势

这种结构有几个好处。例如,假设你尝试将所有数据存储在一个大表中。对于每个订单,你都必须重复客户的详细信息(如姓名和邮箱)以及产品的详细信息(如名称和价格)。这种重复不仅浪费存储空间,还会使更新操作容易出错。

例如,如果客户更新了他们的邮箱,你只需要在 customers 表中更改一次,更新后的信息就会立即在所有引用该客户的地方反映出来。

关系的类型

你可以使用外键建模多种关系,包括“一对多”、“多对多”和“一对一”。“一对多”和“多对多”关系非常常见,而“一对一”关系则较为少见。

以下是这三种关系的定义:

  • 一对多关系:当一个实体与另一个实体的多个实例相关联时发生。例如,一个客户有多个订单,但每个订单只属于一个客户。
  • 多对多关系:每个实体都可以与另一个实体的多个实例相关联。例如,一个学生可以选多门课,而一门课可以有多个学生。多对多关系的建模更为复杂。
  • 一对一关系:当一个实体的每个实例恰好与另一个实体的一个实例相关联时发生。它不如前两种类型常见。例如,一个公民有一本护照,而一本护照只属于一个公民。

如何在数据库中表示关系

这些关系在数据库中通过外键的不同排列方式来表示。

一对多关系的表示方法是在“多”的一方放置外键。

为了表示一个客户有多个订单,你可以在 orders 表中放置一个 customer_id 列作为外键。这个外键将每个订单链接到其客户,允许同一个 customer_idorders 表中出现多次。

为了建模多对多关系,你需要使用一个单独的表,其中包含两个实体的外键。

例如,如果你想建模学生和课程之间的多对多关系,你可以有 students 表和 classes 表,然后有另一个通常称为 student_classes 的表。该表的每一行都有一个 student_id 和一个 class_id。因此,每一行都代表一个学生和一门课程之间的关系(即该学生选了这门课,以及这门课里有这个学生)。

对于一对一关系,通常将外键放在更“可选”的表中。

例如,在公民和护照的例子中,你应该将 citizen_id 放在 passports 表中,因为每本护照都有一个公民,但并非每个公民都有护照。这种方法有助于避免外键出现空值。

参照完整性

你的关系型数据库管理系统通常会为外键强制执行参照完整性,这仅仅意味着外键不能引用不存在的行。


总结

本节课中,我们一起学习了关系型数据库中关系的核心概念。我们了解到,数据库通过外键来连接不同的表,从而高效地建模现实世界中的复杂联系。我们探讨了三种基本关系类型(一对多多对多一对一)及其在数据库中的具体表示方法,并理解了关系型结构在避免数据冗余和确保数据一致性方面的优势。

现在你已经熟悉了数据库中关系的表示方式,接下来就可以准备在数据库中构建多个相互关联的表了。请跟随进入下一个视频,学习有效的数据库设计。

047:数据模型与数据模式 📊

在本节课中,我们将学习数据库设计的两个核心概念:数据模型与数据模式。理解它们有助于我们组织数据,确保数据存储的一致性和高效性,并为后续的数据分析工作打下坚实基础。


概述

每个数据库都有其独特的结构,这个结构允许你访问关于不同实体及其关系的数据。数据模型和数据模式共同定义了这个结构。

上一节我们介绍了数据库的基本概念,本节中我们来看看如何具体规划和设计数据库的结构。


数据模型:数据库的蓝图 🏗️

数据模型就像是数据库的草图,它包含了所有实体和关系。它是一个用于组织和构建数据的框架,但不会具体规定这个结构在数据库中应如何实现。

数据模型的目标是设计一个既能支持高效数据存储,又能方便数据分析的结构。

作为一名数据分析师,你通常不会从零开始设计数据模型,但你应该熟悉它的开发过程,以便更好地与数据工程师沟通。

构建数据模型的步骤

以下是构建数据模型时需要考虑的关键步骤:

  1. 识别实体:首先,勾勒出所涉及的实体。例如,一个宠物用品连锁店可能包含顾客、订单、员工、门店、产品等实体。大型企业通常有数十甚至数百个实体。
  2. 定义关系:接下来,定义这些实体之间如何连接。例如:
    • 一个顾客可以有多个订单(一对多关系)。
    • 一个订单可以包含多个产品(一对多关系)。
    • 一个门店可以有多名员工(一对多关系)。
    • 一名员工也可能在多家门店工作(多对多关系)。

一旦确定了有助于存储业务关键数据的数据模型,你就可以进一步思考该模型的具体实现方式了。


数据模式:模型的具体实现 ⚙️

上一节我们介绍了数据模型作为蓝图的概念,本节中我们来看看它的具体实现——数据模式。

如果说数据模型提供了实体和关系的草图,那么数据模式就是该模型的具体实现。它包括:

  • 需要哪些表
  • 每个表有哪些属性及其对应的数据类型
  • 不同的表如何通过键(Keys)链接在一起

在实践中,数据模式强制执行规则以确保数据的一致性。例如,你可能要求每一条订单记录都必须引用一个有效的顾客。

从模型到模式的示例

假设你正在处理宠物用品连锁店的数据。你的数据模型已经指定应该有五个实体:顾客、订单、员工、门店和产品,并且存在以下关系:

  • 顾客与订单:一对多
  • 订单与产品:一对多
  • 门店与员工:多对多

那么,你的数据模式将定义每个实体及其属性。例如:

顾客表 可能包含以下属性:

  • customer_id (整数,主键 Primary Key)
  • name (字符串)
  • address (字符串)
  • age (整数)

订单表 可能包含以下属性:

  • order_id (整数,主键 Primary Key)
  • order_date (日期时间)
  • customer_id (整数,外键 Foreign Key,链接到顾客表)

注意:对于“多对多”关系(如门店与员工),你需要一个单独的表,其中包含链接另外两个表的外键。


数据库设计的关键考量 🤔

在设计数据库结构时,退一步思考非常重要。

  1. 明确存储目的:首先理解存储所有这些数据的目的是什么。思考你需要用这些数据回答什么问题。记住,你不仅对特定结果(如销售额)感兴趣,也对丰富你分析故事的背景信息感兴趣。
  2. 识别核心实体与关系:确定你要分析的主要对象以及它们之间的关系。例如,如果你的目标是分析销售业绩,你的数据模型还应包含顾客、订单和产品等实体。
  3. 规划数据模式:思考你需要哪些表,每个表应包含哪些属性,以及它们应如何通过外键连接。

像大语言模型这样的生成式AI工具,可以帮助你构思可能的数据结构,或澄清关于如何组织复杂数据的问题。例如,你可以询问关于如何为实体间复杂关系建模的建议。


总结

本节课中,我们一起学习了数据模型与数据模式。

  • 数据模型是高级别的蓝图,定义了数据库中的实体关系
  • 数据模式是数据模型的具体实现,定义了属性数据类型

数据模型和数据模式有助于确保你的数据在投入数据库实现之前就是整洁有序的。在你熟悉数据库时,也可以参考数据模式。在下一个视频中,你将学习数据库中会遇到的不同类型的表。

048:47_表类型 🗂️

在本节课中,我们将学习数据库中三种主要的表类型:维度表、关系表和事实表。理解这些表类型对于有效地组织和分析数据至关重要。

正如你在多对多关系中已经看到的,并非所有表都服务于相同的目的。本节视频将介绍这三种表的核心概念和用途。

维度表 📊

上一节我们介绍了表的不同用途,本节中我们首先来看看维度表。维度表存储关于实体的描述性信息。“维度表”是一个专业术语,指代你在本课程中一直在处理的那种数据表,例如客户表或钻石表。它们为数据库中的实体建模。

它之所以被称为维度表,是因为它为单一实体类型增添了上下文,就像维度为物理对象增添了深度一样。可以将其视为提供了构成分析框架的“谁、什么、哪里、何时”等信息。

以下是维度表的一些例子:

  • 对于宠物用品连锁店,一个维度表是产品表,它包含诸如 product_idnamecategoryprice 等属性。
  • 这些字段描述了产品,但不存储交易数据,例如产品何时被购买或每个产品包含在多少订单中。

这个维度表将帮助你回答诸如“我们销售的产品名称和价格是什么?”这样的问题。

关系表 🔗

接下来,我们看看关系表。关系表用于将实体连接在一起。这些表总是包含链接到其他表主键的外键。

外键创建了实体之间的关系,使得跨表连接和分析数据成为可能。没有外键,实体之间的联系将不复存在。

以下是关系表的一个例子:

  • 在宠物用品店,你可能有一个名为 stores_employees 的表。它通过存储 store_id(链接到商店表的外键)和 employee_id(链接到员工表的外键)来将员工与商店关联起来。

这个关系表允许你回答“哪些员工在每家商店工作?”这样的问题。

事实表 📈

最后,我们介绍事实表。事实表捕获事件或交易,例如销售或订单。

事实表不像客户那样为单个实体建模,而是为涉及多个实体的事件(如一次销售)建模。它们通常用于跟踪关键指标,如销售额或体育比赛中的得分。

事实表包含连接到维度表的外键。维度表和事实表之间的关键区别在于它们存储的内容。

以下是事实表的一个例子:

  • 维度表描述实体的细节,如客户姓名、产品类别或商店位置。
  • 事实表用于记录交易。在宠物用品店,订单表就是一个事实表。它包含诸如 order_idorder_datecustomer_idtotal_amount 等数据。每一行代表一笔独立的交易。

事实表回答诸如“我们从特定订单中产生了多少收入?”或“某个产品售出了多少单位?”等问题。

表类型的协同工作 🤝

当这些表类型在数据库模式中组合使用时,它们允许你以一种支持复杂分析的方式为数据建模。

例如,如果你想分析宠物用品店的总销售额,你会使用订单事实表来收集交易数据,并使用客户和产品维度表来添加关于谁购买了商品以及售出了什么的描述性细节。

维度表、关系表和事实表帮助你组织数据以回答正确的问题。说到回答问题,是时候通过实践作业来测试你到目前为止所学到的知识了。完成后,请跟随我进入下一课,学习如何使用 SQL 查询数据库。


本节课总结:在本节课中,我们一起学习了数据库中的三种核心表类型。维度表(如 产品表)存储描述性信息,关系表(如 商店员工表)通过外键连接实体,而事实表(如 订单表)则记录交易事件。理解它们的区别和联系是构建有效数据模型和进行分析的基础。

049:SQL入门 🚪

在本节课中,我们将要学习结构化查询语言(SQL)的基础知识。SQL是用于与关系型数据库交互的核心编程语言,对于数据分析师而言是一项至关重要的技能。我们将了解SQL是什么、它能做什么,以及为何它在数据处理中如此高效。


什么是SQL?🤔

上一节我们介绍了数据库是存储数据的可靠方式。本节中,我们来看看如何从数据库中检索特定信息进行分析。

你已了解到数据库支持查询操作。作为关系型数据库的数据分析师,查询将是你与数据库的主要交互方式,而你将通常使用结构化查询语言(SQL)来编写这些查询。

SQL是一种专门设计用于与关系型数据库中存储的数据进行交互的编程语言。你可能会听到人们使用“SQL”或“Sequel”两种发音,两者都很常见。

这种专门化的设计使其在与结构化数据交互时非常高效。虽然像Python这样的通用编程语言可以执行广泛的任务(如统计建模或数据可视化),但SQL仅用于与数据库交互。

以下是SQL可以执行的核心操作:

  • 检索记录
  • 更新记录
  • 创建记录
  • 删除记录

不过,你无法用它来运行线性回归模型。


理解查询 🔍

正如之前所学,检索记录的命令称为查询。查询本质上是向数据库提问,例如“上个月的总销售额是多少?”,然后得到答案。

查询是使用SQL工作的核心部分,它允许你提取分析所需的精确信息。

除了查询(不改变数据库内任何数据)之外,SQL还包括用于创建、修改或删除数据的命令。然而,作为数据分析师,你的主要重点将是通过查询来检索数据。更新、创建或删除记录以改变数据库的操作,通常由数据库管理员或数据工程师处理,尤其是在大型组织中。


你的第一个SQL查询示例 📝

以下是一个SQL查询的示例。花点时间看看它,可以暂停视频,猜猜它的作用。

SELECT order_id, total_amount
FROM orders
ORDER BY total_amount DESC
LIMIT 10;

这个查询从orders表中选择了order_idtotal_amount两列数据,并按照total_amount降序排列结果行。这样你就能在顶部看到最大的总金额。此查询将结果限制为仅10条。

通常,你可以像读句子一样阅读SQL查询:

orders表中选择order_idtotal_amount,并按total_amount降序排列,将结果限制为10条。

在下一个视频中,你将更深入地学习如何编写SQL查询。


为何SQL对数据分析师很重要?💡

SQL是与关系型数据库交互的标准语言,这使其成为数据分析师一项非常抢手的技能。

关系型数据库因其高效管理结构化数据的能力而获得广泛采用。SQL查询能高效处理海量数据集,并且返回结果的速度比pandas中的许多操作快得多。这种速度优势在数据规模很大时尤其明显。事实上,即使处理数百万条记录,SQL查询也能在几秒钟内返回结果。

也就是说,根据你试图执行的操作,pandas或SQL都可能是合适的选择。像过滤或计数这样的简单操作,通常用SQL更高效;而更复杂或自定义的计算,则更适合在你的Python笔记本中进行。

作为数据分析师,SQL很可能成为你日常工作的核心部分,因为它是查询关系型数据库中结构化数据的主要工具。你会在求职面试中遇到SQL问题,也会发现它在许多行业中都非常有价值。这就是本课程教授SQL的原因,以确保你能为在实际场景中有效使用它做好充分准备。

你将使用SQLite,这是一个轻量级的、基于文件的数据库,非常适合小型项目。

通过掌握SQL,你将为处理更大的数据集和更复杂的工具做好准备。


总结 📚

本节课中我们一起学习了:

  1. SQL的定义:一种专门用于与关系型数据库交互的编程语言。
  2. 查询的核心作用:作为向数据库提问并获取答案的主要方式。
  3. SQL的基本能力:检索、更新、创建和删除数据记录。
  4. SQL查询示例:初步了解了SELECTFROMORDER BYLIMIT等基本子句的结构。
  5. SQL的重要性:因其在处理大规模结构化数据时的高效性和行业普遍性,成为数据分析师的关键技能。

在下一个视频中,我们将一起看看SQL在实际操作中是什么样子。

050:SQL语句结构详解 📝

在本节课中,我们将深入学习SQL查询语句的基本结构。我们将拆解一个SQL查询的各个组成部分,了解其语法规则和书写惯例,为后续编写自己的查询打下坚实基础。


SQL查询的基本结构

上一节我们介绍了SQL的用途,本节中我们来仔细剖析一个SQL查询的构成。

SQL通过数据库能理解的命令帮助你从数据库中提取数据。所有查询中最基础的命令是 SELECT,它允许你从数据库中检索特定的列。

以下是一个示例:

SELECT invoice_number FROM invoices;

你编写的所有查询都将包含 SELECTFROM。你通过它们指定你想要什么数据以及从哪个表中获取。

SELECTFROM 被称为关键字。每个关键字开启一个子句,即整个命令的一部分。

子句通常包含标识符。标识符是子句中除关键字外的另一部分,它们与你正在处理的数据相关,包含诸如列名或表名等信息。

所有这些子句共同构成了完整的SQL命令。最后,SQL查询以分号结束。


SQL代码的书写惯例

在展示的SQL代码中,还有两个可选的惯例或习惯。

以下是关于关键字大小写的惯例:

  • SQL关键字通常全部大写。这有助于将它们与命令的其他部分区分开来。但这只是一种风格选择,SQL关键字本身不区分大小写。
  • 一些SQL界面会应用特殊格式(如加粗或颜色)使关键字更突出。你在使用Python代码的Jupyter笔记本中已经见过类似的例子。

以下是关于代码排版的惯例:

  • 子句通常在新的一行开始。这个惯例有助于提高可读性,尤其是当你的查询变得更长、更复杂时。
  • 然而,SQL对空白字符不敏感。因此,你的换行、空格和制表符不会影响查询。将所有查询写在一行仍然有效,但会难以阅读。

你可以通过添加更多子句,或为每个子句添加更多细节来扩展你的SQL查询。你将在下一个视频中看到更多示例。


关于大小写敏感性和命名的注意事项

不同的数据库管理系统在编写SQL代码时有略微不同的惯例。

以下是关于分号使用的说明:

  • 一些RDBMS(如MySQL)要求你以分号结束每个SQL查询。
  • 其他系统(如本课程将使用的SQLite)则更灵活,允许你在单个查询中省略分号。
  • 尽管如此,使用分号结束查询是一个应该保持的好习惯。它能确保你的代码在不同的数据库系统中都能工作,并避免在多个查询一起执行时出错。

你刚才了解到,SQL对于像SELECTFROM这样的关键字是不区分大小写的。它们可以全部大写、全部小写或混合大小写,尽管全部大写的格式通常更具可读性。

然而,列名和表名是否区分大小写则取决于具体的RDBMS。

以下是关于标识符大小写的说明:

  • SQLite默认不区分大小写。因此,如果你要选择invoice_number,你可以像这样全部大写,或像这样首字母大写,或全部小写,仍然会得到相同的响应,无论列名在数据库中是如何存储的。
  • 然而,其他RDBMS(如Postgres)对于大小写敏感性有更复杂的规则。你需要确保遵循你正在使用的特定RDBMS的惯例。

在SQLite中,如果你处理的列名包含空格(例如employee base name),你应该用双引号将其括起来。其他RDBMS可能有不同的规则。

最好确保你的数据库具有一致的大小写规范,并尽可能使用下划线而不是空格。然而,当你处理较旧的系统时,有时会遇到格式不理想的列名。


在SQL中编写注释

就像在Python中一样,你也可以在SQL中编写注释。

单行注释用两个破折号编写:

-- 这是一个单行注释
SELECT column FROM table;

破折号告诉计算机忽略该行上的任何其他文本并跳到下一行。

在你的SQL代码中编写注释有助于你或他人理解查询在做什么。当你的查询很复杂时,注释尤为重要。


总结

本节课中我们一起学习了SQL查询语句的核心结构。我们明确了SELECTFROM关键字的基础作用,了解了子句和标识符的概念。我们还探讨了SQL代码的书写惯例,包括关键字大写、子句换行以及使用分号的重要性。最后,我们讨论了不同数据库系统在大小写敏感性和命名规则上的差异,并学会了如何使用注释来提高代码的可读性。

现在,是时候动手实践SQL了。请跟随我到下一个视频,开始编写你自己的查询。

051:SQL 选择查询 🎯

在本节课中,我们将学习如何使用 SQL 的 SELECT 语句从数据库表中检索数据。我们将通过一个音乐流媒体服务的案例,探索其数据库中的专辑和曲目信息,并了解编写查询语句的基本规则和最佳实践。

数据库结构与数据概览

上一节我们介绍了 SQL 查询的整体结构,本节中我们来看看如何动手设计和编写 SQL 查询。

在本课程中,你将扮演一家音乐流媒体服务公司的数据分析师角色。你的公司最近收购了一家旧的数字音乐商店,该商店过去在线销售单曲。现在的目标是将这家新公司庞大的音乐目录整合到一个订阅服务中。你的任务是对收购公司的 SQLite 数据库进行探索性数据分析,以更好地了解其目录范围。

在开始编写查询之前,你应该先查看数据库模式,以了解有哪些数据可用。否则,你将不知道不同表和属性有哪些标识符可用。

以下是你要使用的数据库的实体关系图。从左上角开始,有一个包含艺术家 ID 和艺术家姓名的表。该表与专辑表存在关系,因为一位艺术家拥有多张专辑,所以外键位于这个“多”方关系的表中。因此,每张专辑都有一个 ID、一个标题和链接到艺术家表的艺术家 ID。

还有许多其他表,例如曲目表,它包含更多字段,包括 album_id(对应曲目所属的专辑)、media_type_id(另一个外键)、genre_id(另一个外键)、composermilliseconds 等。

你可以使用实体关系图来了解有哪些属性可用,以及表之间是如何链接的。

执行基础查询

既然你了解了数据库的布局,现在可以查看实际数据了。本课程的前几个 SQL 查询将在这个网站界面中执行,其格式与许多允许你直接查询数据库的分析应用程序非常相似。

由于你的目标是更好地了解音乐目录,第一步可能是选择一些专辑标题。

以下是编写基础查询的步骤:

  • 选择特定列:使用 SELECT 语句指定要检索的列,用 FROM 指定表名,并用 LIMIT 限制返回的行数。
    SELECT title FROM albums LIMIT 10;
    
    这条语句请求专辑表中的前 10 个标题。

  • 选择多列:通过用逗号分隔每个列名,可以同时获取多个列。
    SELECT name, milliseconds, unitprice FROM tracks LIMIT 10;
    
    现在你获得了每条曲目的多个列。注意,unitprice 的写法是单词间无空格但每个单词首字母大写。

  • 注意语法细节:SQLite 对关键字不区分大小写,但对标识符(如表名、列名)中的额外空格敏感。例如,unit price(带空格)会导致查询无法正确执行,而 UNITPRICE(全大写)则可以正常工作。

我鼓励你在接下来的练习中尝试不同的空格、大小写、逗号和分号用法,以更好地理解什么有效、什么无效。

使用 SELECT * 进行探索

如果你手头没有实体关系图,或者只是想获取表中的所有信息,可以使用 SELECT * 这个快捷方式。

以下是使用 SELECT * 的示例:

  • 检索所有列SELECT *(或 SELECT asterisk)会选择表中的所有列。

    SELECT * FROM tracks LIMIT 10;
    

    你会看到与之前相同的曲目,但信息更完整。

  • 探索外键关联SELECT * 对于探索性数据分析很有用。例如,在曲目表中看到 media_type_id 为 2,你可以查询 media_types 表来了解其含义。

    SELECT * FROM media_types;
    

    结果显示它是受保护的 AAC 音频文件。

虽然 SELECT * 很方便,但应注意,对于非常大的表,它可能会变得缓慢或计算成本高昂。通常最好只选择分析所需的列。

课程总结与注意事项

本节课中我们一起学习了 SQL 的基础查询操作。

快速回顾一下,你看到可以使用 SELECT column_names FROM table_name 从特定表中检索特定列,多个列用逗号分隔。在 SQLite 中,这些标识符不区分大小写,但对额外的空格敏感。你还使用了 LIMIT 来控制查询返回的结果数量,这是在将查询应用于全部数据范围之前进行测试的好方法。最后,你看到了 SELECT * 操作符的实际应用,它选择表中的所有列。

需要记住的是,就像在 Python 中一样,仅仅因为你的代码运行了,并不意味着它做了正确的事情。事实上,虽然错误会阻止你的代码运行,但逻辑错误可能会悄无声息地给出错误的答案。你应该提前对查询结果的样子有一个预期,例如,应该返回多少行、多少列以及会看到什么数据类型。在接下来的视频中,你将看到一些调试查询的策略。

这些 SQL 查询工作做得很好。使用快速数据库工作非常令人满意,在接下来的视频以及下一个模块中,你将获得越来越多的工具。接下来,你将看到如何组织 SQL 查询的结果。

052:SQL 结果排序 📊

在本节课中,我们将学习如何使用 SQL 的 ORDER BY 子句对查询结果进行排序。排序是组织和理解数据的关键步骤,能帮助我们快速识别数据中的模式,例如找出最大或最小的值。


概述

SQL 提供了强大的功能来组织查询结果中的信息。本节视频将重点介绍如何对查询结果进行排序。我们将通过一个音乐流媒体服务的案例来实践,该服务最近收购了一个竞争对手,需要分析其音乐目录,以了解不同曲目的长度和存储空间需求,从而为存储音乐分配适当的数据库资源。


基础排序:使用 ORDER BY

首先,我们来看一个基础查询,它从 tracks 表中选择了名称、毫秒数和字节数。

SELECT name, milliseconds, bytes FROM tracks;

这个查询会返回 tracks 表中的所有行和三列数据。bytes 列表示文件在计算机上占用的存储空间大小。你会发现这些曲目的大小差异很大。

为了根据文件大小对这些曲目进行排序,我们可以使用 ORDER BY 关键字。

SELECT name, milliseconds, bytes FROM tracks ORDER BY bytes;

默认情况下,SQL 会按升序排序。对于数字,这意味着从小到大;对于日期,从早到晚;对于文本,则按字母顺序。因此,执行上述查询后,结果集中最先显示的是数据集中最小的文件。


降序排序:使用 DESC 关键字

如果你希望最大的曲目显示在结果顶部,可以使用 DESC(降序)关键字。只需将其添加到 ORDER BY 子句的末尾即可。

SELECT name, milliseconds, bytes FROM tracks ORDER BY bytes DESC;

现在,结果将按字节数从大到小排列,最大的文件会出现在最前面。


按多列排序

有时,你可能希望按多个条件组织数据。例如,你可能想先按流派对曲目进行分组,然后在每个流派内按文件大小降序排列。

这可以通过在 ORDER BY 子句中指定多个列来实现,列名之间用逗号分隔。

SELECT name, genre_id, bytes FROM tracks ORDER BY genre_id ASC, bytes DESC;

这个查询会首先按 genre_id 升序排列,然后在每个流派内按 bytes 降序排列。排序的顺序很重要:首先列出的列拥有最高的排序优先级。

需要注意的是,用于排序的列不一定需要出现在 SELECT 子句中。你可以隐藏它,这在你希望优化查询速度或简化输出时很有用。


使用 AS 关键字重命名列

当表中原有的列名较长或不易理解时,可以使用 AS 关键字为列创建临时别名。这不会改变底层数据库的结构,只是在你查询时提供一个更友好的名称。

例如,为了让输出对业务方更清晰,可以这样写:

SELECT
    name AS track_title,
    milliseconds AS length_in_ms,
    bytes AS size_in_bytes
FROM tracks
ORDER BY genre_id, bytes DESC;

这样,输出的列标题将显示为 track_titlelength_in_mssize_in_bytes,更易于理解。

以下是关于排序和列别名的一些关键点总结:

  • ORDER BY 子句用于对结果排序,应放在 FROM 子句之后。
  • 默认排序是升序(ASC)。
  • DESC 关键字用于指定降序排序。
  • 多列排序时,列名用逗号分隔,排序优先级从左到右。
  • AS 关键字用于为列创建临时别名,提高结果的可读性。

总结

本节课我们一起学习了 SQL 中结果排序的核心技巧。我们掌握了如何使用 ORDER BY 进行单列和多列排序,了解了升序与降序的区别,并学会了使用 AS 关键字来重命名输出列,使结果更清晰。这些技能是进行有效数据探索和分析的基础。

你已经学会了编写几种强大的 SQL 查询。在接下来的视频中,我们将探索如何使用大语言模型(LLMs)来处理数据库。

053:在数据库应用中利用大语言模型 🧠💾

在本节课中,我们将学习如何利用大语言模型来辅助数据库工作。自从大语言模型出现以来,与数据库的协作变得比以往任何时候都更加高效。你将看到几种使用大语言模型处理 SQL 数据库的不同技术。

我们将继续使用音乐数据库,并通过探索性数据分析来更好地理解其目录结构。

概述:使用推理模型

本次演示使用了 OpenAI 提供的 ChatGPT-o1 推理模型。该模型在回应前会花更多时间进行逐步思考。它目前对免费和付费账户均开放。请注意,你还有其他推理模型选项,例如 Claude 3.5 Sonnet 的扩展思考模式,并且你可能拥有更新的可用模型。


理解数据库模式

假设你刚刚加入这个项目,并拿到了这个数据库模式图。理解数据库模式是一项涉及大量视觉推理的复杂任务。

因此,你可以尝试使用像 ChatGPT-o1 这样的推理模型。这类模型在回应前会花更多时间,逐步思考你的请求。

你可以复制这张实体关系图的截图,并将其粘贴到 ChatGPT-o1 中,然后询问相关信息。

例如,你可以提问:“为这个数据库模式创建一个探索性数据分析的提纲。”



模型会基于你的上下文(例如:“我的公司刚刚收购了拥有这些数据的公司。我们正计划将其整合到我们自己的数据库中。我的目标是更好地理解与音乐相关的表,以及将这些数据与我们自己的音乐目录一起存储可能需要什么。”)来生成建议。

以下是模型建议的主要步骤:

  1. 高层评估与模式熟悉:首先,对整体模式和实体关系进行高层评估和熟悉。
  2. 数据字典总结:总结描述列定义以及所有主键和外键约束的数据字典。
  3. 数据质量与剖析:讨论数据质量和剖析,理解关于数据的基本描述性统计以及某些特征的分布情况。
  4. 集成考量:阐述关于模式对齐、数据映射与转换以及迁移需求的思考方法。该建议还提到处理历史或事务数据,建议考虑是否要保留部分数据,以及如何将部分数据合并到现有数据中。







识别过时信息

上一节我们介绍了如何利用大语言模型理解数据库结构,本节中我们来看看如何让它帮助识别潜在的无用信息。

作为快速跟进,你可以要求模型识别对于你的分析可能过时或无关的属性和表,并确保指出任何遗留技术或过时信息。

例如,你可能会注意到一个可能过时的列是“员工传真号码”,这可能是一个不再相关的遗留列。

因为你使用了推理模型,你可能会注意到模型处理这个请求花了更多时间,因此它使用了更多的计算能力来给出答案。在此期间,模型正在逐步思考你的请求,几乎就像在自言自语地解决问题。


这个查询对模型来说思考起来更复杂一些,它花了 14 秒来完成推理过程。

模型可能会指出,无关的表可能包括 employeescustomersinvoices,以及潜在的 playlists(尽管可以说播放列表可能有用)。如果你的公司维护某种播放列表架构,你或许可以将这些旧的播放列表与你自己的整合。



你可能还会注意到,在遗留技术指标下,它确实指出传真号码是遗留字段的一个典型例子。因此,这可能是你希望从数据中删除内容的一个好例子。


生成结构化数据格式

除了识别问题,大语言模型还能协助进行数据文档化。作为文档化步骤,你可以要求该模型获取这张图片,并创建一个结构化的数据格式来存储图片内容。

例如,你可以提出请求:“创建一个表示此数据库模式内容的 JSON 文件。”

以前,这对于大型多模态模型来说是一项非常具有挑战性的任务。虽然这仍然是一项非常具有挑战性的任务,并且你需要验证其输出,但与以前的模型相比,像 ChatGPT-o1 这样的推理模型往往更擅长此类任务。

这项任务看起来很困难可能违反直觉,因为它只是读取文本和解释箭头。然而,你可以将这些人工智能系统视为具有“模糊视觉”。它们能很好地看到图像的大致轮廓,但可能在细节上遇到困难。

以下是模式的一种可能的 JSON 表示:

{
  "database_schema": {
    "tables": [
      {
        "table_name": "Artist",
        "primary_key": "ArtistId",
        "columns": [
          {"column_name": "ArtistId", "data_type": "INTEGER"},
          {"column_name": "Name", "data_type": "TEXT"}
        ]
      }
      // ... 其他表
    ]
  }
}

它实际上非常复杂地解释了这些信息。在验证了这些信息的准确性之后,你可以将此 JSON 文件分享给你的数据团队,以帮助他们将这些数据迁移到你公司的数据库中。


总结

本节课中,我们一起学习了如何利用大语言模型辅助数据库工作。主要内容包括:

  • 使用推理模型(如 ChatGPT-o1)来理解和分析数据库模式图。
  • 让模型为探索性数据分析生成提纲,涵盖高层评估、数据字典、质量剖析和集成考量。
  • 识别数据库中可能过时或无关的属性和表(例如遗留的传真号码字段)。
  • 将数据库模式图转换为结构化的 JSON 格式,以辅助数据迁移和团队协作。

大语言模型可以帮助你在开始编写 SQL 查询之前更好地理解数据库的结构。

接下来,你将通过学习如何在 Python 中编写 SQL 查询来结束这个模块。我们将在本模块的最后一个视频中再见。

054:在 Python 中使用 SQL 🐍➡️🗄️

在本节课中,我们将学习如何在 Python 环境中直接执行 SQL 查询。你将了解如何连接数据库、编写查询语句,并将查询结果转换为 Pandas DataFrame 以便进行后续的数据分析和预处理。


概述

到目前为止,我们主要在网页界面中编写 SQL,这是一种与数据库交互的常见方式。然而,为了进行更强大的分析,我们需要能够在 Python 笔记本中直接执行 SQL 查询。本节将介绍实现这一目标的具体步骤。

导入必要的库

开始之前,首先需要导入必要的 Python 库。我们将使用 pandas 进行数据处理,并使用 sqlite3 模块来连接和操作 SQLite 数据库。

以下是导入代码:

import pandas as pd
import sqlite3

建立数据库连接

第一步是与数据库建立连接。这就像给数据库“打电话”,接通线路后才能向它提问。

我们使用 sqlite3.connect() 函数来建立连接,并传入数据库文件的路径。SQLite 是一种基于文件的数据库。

连接数据库的代码如下:

connection = sqlite3.connect(‘music.db’)

这里的 music.db 就是我们的数据库文件,它包含了专辑、曲目等相关信息。

编写并执行 SQL 查询

在 Python 中,SQL 查询语句以字符串形式存储。查询的编写方式与普通 SQL 完全一致。

一个简单的查询示例如下:

query = “SELECT * FROM artists;”

要执行这个查询并向数据库提问,我们需要使用 pandas 的 pd.read_sql_query() 函数。该函数接受两个参数:查询语句字符串和数据库连接对象。

执行查询的代码如下:

df = pd.read_sql_query(query, connection)

这个函数会返回一个 Pandas DataFrame,其中每一行代表一位艺术家,列对应数据表中的属性(如 artist_idname)。

使用多行字符串编写复杂查询

将复杂的 SQL 查询写在一行中很不方便。遵循最佳实践,我们应该将每个子句写在单独的行上。

在 Python 中,可以使用三引号来创建多行字符串,以便编写格式清晰的查询。

以下是使用多行字符串的示例:

query = “””
SELECT *
FROM artists;
“””

然后,同样使用 pd.read_sql_query() 来执行这个格式良好的查询。

在 Python 中分析查询结果

将数据获取到 DataFrame 后,就可以利用 Python 强大的数据分析工具了。

例如,你可以:

  • 使用 df.describe() 获取数据概览。
  • 使用 df.info() 查看数据类型并检查缺失值。
  • 使用 df[‘bytes’].hist() 绘制直方图来探索数据分布。

这些工具能帮助你深入理解从数据库中检索到的数据。

关闭数据库连接

完成所有数据库操作后,良好的习惯是关闭连接。保持连接开放会持续占用计算机资源。

关闭连接的代码如下:

connection.close()

请注意,关闭连接后,除非重新建立连接,否则无法再执行新的查询。

总结

本节课我们一起学习了在 Python 中使用 SQL 的核心流程:

  1. 导入库:导入 pandassqlite3
  2. 建立连接:使用 sqlite3.connect(‘数据库文件.db’) 连接数据库。
  3. 编写查询:将 SQL 语句写成字符串,复杂查询可使用三引号的多行字符串。
  4. 执行查询:使用 pd.read_sql_query(查询语句, 连接对象) 获取 DataFrame 格式的结果。
  5. 分析数据:利用 Pandas 的各种方法对查询结果进行探索和分析。
  6. 关闭连接:使用 connection.close() 释放资源。

后续步骤

现在,你已经掌握了在 Python 笔记本中连接和使用数据库的技能,为进行高效的数据预处理与分析打下了基础。接下来,你将在练习和作业中巩固这些 SQL 查询技巧。在课程的最后一个模块,我们将学习更高级的、在工作中常用的 SQL 查询技术。

055:SQL数据预处理与验证模块简介 🧹

在本课程中,我们将学习如何使用SQL进行数据预处理与验证。SQL是处理关系型数据库的强大工具,掌握其核心技巧能帮助我们高效地清洗和验证数据,为后续分析打下坚实基础。

模块概述 📋

欢迎来到模块四:使用SQL进行数据预处理与验证。

在本模块中,你将深化对关键SQL技术的理解,以有效地清洗和验证数据。

课程内容详解 📚

第一课:数据筛选与条件语句

上一节我们介绍了本模块的整体目标,本节中我们来看看数据筛选与条件语句。

你将探索SQL中的筛选和条件语句。筛选功能允许你仅选择感兴趣的数据子集,这通过使用WHERE关键字实现。

以下是本课将涉及的核心操作:

  • 你将使用诸如INBETWEEN等条件,以及基于字符串的条件来筛选数据。
  • 你将学习如何通过筛选来创建新的数据特征。

第二课:数据验证

掌握了数据筛选后,接下来我们将聚焦于数据验证。

数据验证包括识别和解决数据缺失或异常问题。

以下是本课将学习的关键方法:

  • 你将结合使用分组与聚合函数(如COUNTSUMMINMAXAVERAGE)来验证数据是否符合预期。
  • 你将学习如何在数据分组后进行筛选。

第三课:表连接

在学习了数据验证之后,最后我们将探索不同类型的表连接,这是处理关系型数据库的基石。

你将使用LEFT JOININNER JOINOUTER JOIN等操作,将来自多个表的数据组合起来。

总结 🎯

在本课程中,我们一起学习了SQL数据预处理与验证模块的核心内容。

到本模块结束时,你将能熟练地使用SQL对数据进行筛选、分组、聚合和连接。这些技能对于应对现实世界中的数据挑战是不可或缺的。

让我们在下一个视频中开始学习。

056:SQL 与 Python 对比 🆚

在本节课中,我们将探讨 SQL 与 Python 在数据处理任务中的适用场景与选择策略。你将了解这两种工具各自的优势,以及如何在实践中将它们结合使用。

在接下来的课程中,你将探索许多在 pandas 中也有对应操作的 SQL 技术。

换句话说,完成本模块后,你将掌握完成同一任务的两种方法。

这是一个由来已久的问题:何时应该使用 SQL,何时应该使用 Python?

SQL 的适用场景 🗃️

通常,在最高效的情况下你会希望使用 SQL,而在需要更定制化分析时则使用 Python。

在实践中,你经常需要处理包含数百万或数十亿行的大规模数据集。

将所有数据行通过网络拉到你的本地计算机上仅仅是为了进行过滤或聚合,这可能会浪费大量时间。

使用 SQL 仅选择分析所需的行通常更为高效。具体来说,分组、通过连接合并表以及排序等操作,通常由关系数据库管理系统进行高度优化,这使得它们非常快速且内存高效。

SQL 在协作环境中也可以成为一个更好的起点。当多个分析依赖于相同的逻辑时,你可以维护一套标准化的 SQL 查询,以保持解释的一致性。

SQL 查询还可以被调度,这意味着同一个查询可以定期运行。

以下是 SQL 调度查询的一个例子:

-- 获取过去七天所有运输订单的数据,用于生成周报
SELECT * FROM shipping_orders WHERE order_date >= DATE_SUB(CURDATE(), INTERVAL 7 DAY);

与 Python 笔记本相比,这种类型的调度查询可能更容易维护。

Python 的适用场景 🐍

当你需要进行更高级的分析,而对效率和可重复性的要求相对较低时,Python 就展现出其优势。

例如,如果你处于探索模式,可能会优先考虑快速可视化,并快速组合多个分析。在 SQL 中进行探索性数据分析可能会感觉更笨拙。

Python 对于高级和定制化分析也是必需的。SQL 查询无法构建箱线图或训练线性回归模型。

此外,不要忘记,SQL 仅在你的数据存储在数据库中时才相关。如果你处理的是平面文件,或者正在使用来自 API 的数据,你将更多地依赖你的 Python 技能。

混合工作流 🤝

你经常会使用混合工作流。

例如,如果你正在为某个消息平台整理一份关于按年龄组划分的周活跃用户的报告,你可能会先在 SQL 中过滤数据,仅选择特定年龄组的行,然后在 Python 中使用线性回归对该行为进行建模并进行可视化。

最终,Python 和 SQL 是互补的,而非相互排斥的工具。

总结 📝

本节课中,我们一起学习了如何在相似的操作中选择使用 SQL 还是 Python。你已经了解了 SQL 在处理大规模数据、标准化查询和调度任务方面的效率优势,以及 Python 在高级分析、定制化处理和探索性工作方面的灵活性。关键在于根据具体任务的需求,灵活选择或将两者结合使用。

在下一课中,我们将开始学习数据预处理与 SQL。

057:SQL 数据过滤 🎯

在本节课中,我们将学习如何使用 SQL 进行数据预处理,特别是通过过滤操作,从数据库中提取干净且相关的数据,以便在 Python 笔记本中进行分析。SQL 的过滤功能与 Python 中的过滤逻辑非常相似。

概述

SQL 是数据预处理的强大工具,它允许你将仅经过清洗且相关的数据提取到 Python 笔记本中进行分析。你第一个要掌握的 SQL 预处理工具就是数据过滤。

SQL 过滤的工作原理与 Python 中的过滤非常相似。你将使用 WHERE 关键字来过滤数据集,后面跟上过滤条件。查询将只返回满足该条件的行。

项目背景与数据介绍

最近,你与一家转售乐高套装的企业签订了一个数据分析项目。乐高是一种可以拼接在一起的彩色塑料积木。乐高套装包含了搭建一个物体或场景(如巫师塔)所需的所有零件。

你的总体目标是识别这些乐高套装的趋势,例如主题和尺寸。你有一个 SQLite 数据库文件 lego_sets.db,其中存储了 1950 年至 2017 年间发布的所有乐高套装数据。你将主要使用 sets_with_themes 这个数据表。

你的第一个任务是识别多年来流行的乐高套装主题。你对最大的套装特别感兴趣,因为它们往往更昂贵。

在 Python 笔记本中操作

在本次演示以及本课程所有后续演示中,你将直接在 Python 笔记本中操作。请记住,你可以使用课程中提供的练习项目来跟随演示。

首先,导入必要的库并建立数据库连接。

import pandas as pd
import sqlite3

# 建立数据库连接
conn = sqlite3.connect('lego_sets.db')

请记住,此连接有效是因为 lego_sets.db 文件与此笔记本位于同一文件夹中。本课程中你无需进行文件管理,但应了解此约束。

所有查询都将以包含 SQL 代码的多行字符串形式出现,然后使用 pd.read_sql_query() 根据查询结果创建 DataFrame。

初步探索数据

为了熟悉数据集,我们先执行一个未过滤的查询来更好地了解数据。

query = """
SELECT *
FROM sets_with_themes
"""
df = pd.read_sql_query(query, conn)
df.info()

结果显示有超过 11,600 行和 7 列。请记住,列编号从 0 开始。你有 3 个分类列和 4 个数字列。

查看 theme 列的唯一值,你会发现有“城堡”、“建筑”、“星球大战”等多种不同类型的套装。

使用 df.describe() 来总结数值列:

  • 平均年份是 2002 年,年份范围从 1950 年到 2017 年,所以目录中包含一些古董套装。
  • 零件的平均数量是 162 个,最大的套装拥有近 6000 个零件。
  • 检查 num_parts 列的最小值,它是 -1。乐高套装似乎不可能有 -1 个零件,这值得进一步调查,我们将在后续视频中进行。

应用数据过滤

上一节我们介绍了数据集的基本情况,本节中我们来看看如何使用 WHERE 子句进行数据过滤。

1. 基于数值范围的过滤

由于你想探索最大的套装,可以从过滤零件数超过 1000 的套装开始。

以下是过滤零件数大于 1000 的套装的查询:

SELECT *
FROM sets_with_themes
WHERE num_parts > 1000

此查询将只返回 num_parts 大于 1000 的行。

query_large = """
SELECT *
FROM sets_with_themes
WHERE num_parts > 1000
"""
df_large = pd.read_sql_query(query_large, conn)
df_large.info()

df.info() 显示这个 DataFrame 现在只包含 278 个套装。使用 df.describe() 可以看到,这些套装的平均年份(2009年)比整体平均(2002年)要新。但请注意,你需要进行统计检验才能确定这种观察到的差异是否显著。

2. 基于特定值的过滤

接下来,我们学习如何使用 IN 关键字过滤多个特定值。

假设你被要求分析 1999 年和 2001 年的套装数据,你的查询将如下所示:

SELECT *
FROM sets_with_themes
WHERE year IN (1999, 2001)

IN 是一个新的关键字,其工作原理与 Python 中的 in 运算符完全相同。它检查指定列中的值是否属于列表中的某一项。你的值列表必须放在括号内,例如 (1999, 2001)

query_years = """
SELECT *
FROM sets_with_themes
WHERE year IN (1999, 2001)
"""
df_years = pd.read_sql_query(query_years, conn)
df_years.info()

查看 df.info(),有 639 个套装满足此条件。你可以从这里继续分析它们的特征。

总结

本节课中我们一起学习了 SQL 中两种不同的数据过滤方法。

两种过滤方法都使用带有不同条件的 WHERE 子句:

  1. 你可以使用布尔表达式(如 ><=)来过滤小于、大于或等于特定值的行。
  2. 你可以使用 IN 关键字来过滤多个特定的值。请注意,各个值必须放在括号内并用逗号分隔。

在下一个视频中,你将扩展过滤技能,学习编写具有多个条件的复杂查询。这些查询将使你能够回答更细致的业务问题。

058:复合条件过滤 🧩

在本节课中,我们将学习如何在 SQL 中使用 ANDOR 逻辑运算符构建复合条件,以回答更复杂的数据查询问题。

概述

为了回答更复杂的问题,你可以使用复合条件来过滤数据。让我们看看如何在 SQL 中使用它们。与 Python 类似,SQL 可以使用逻辑运算符基于复杂条件过滤行,包括 AND(返回所有条件都为真的行)和 OR(返回至少一个条件为真的行)。这些运算符允许你通过多个列来过滤行。

使用 AND 运算符

假设你正在处理乐高套装数据库,你需要根据主题和年份两个条件来过滤套装,具体关注水生主题。

首先,你需要查询选择 1995 年和 1996 年、主题为 “Aquanauts” 的所有套装。“Aquanauts” 是一个关于海底矿工的主题套装,并且只在这两年生产。

以下是查询语句:

SELECT * FROM sets_with_themes
WHERE year IN (1995, 1996) AND theme_name = ‘Aquanauts‘;

第一个条件检查年份是否为 1995 或 1996。第二个条件检查主题是否为 “Aquanauts”。只有同时满足这两个条件的行才会被返回。

查询结果只返回了满足这两个条件的七个套装。请注意,此查询中 “Aquanauts” 需要加引号。如果省略引号,会出现错误。

使用 OR 运算符

由于你知道这些套装只在 1995 年和 1996 年发布,你可以省略查询的第一部分,转而过滤属于 “Aquanauts” 或 “Aquasharks” 主题的套装。

以下是查询语句:

SELECT * FROM sets_with_themes
WHERE theme_name = ‘Aquanauts‘ OR theme_name = ‘Aquasharks‘
ORDER BY theme_name;

请注意,你必须为每个条件包含列名,以区分这两组主题。现在你得到了七个 Aquanauts 套装和六个 Aquasharks 套装。

组合 AND 与 OR

你还可以组合 ANDOR 来创建更复杂的过滤器。例如,假设你只想关注来自 Aquanauts 和 Aquasharks 的、零件数超过 100 的大型套装。

你可能会尝试编写以下查询:

SELECT * FROM sets_with_themes
WHERE theme_name = ‘Aquanauts‘ OR theme_name = ‘Aquasharks‘ AND num_parts > 100
ORDER BY theme_name;

检查结果,你会发现这并不正确。数据框中包含了零件数少于 100 的套装。SQL 似乎只对 Aquasharks 检查了零件数量条件,而没有检查 Aquanauts。

这是一个逻辑错误而非代码错误的典型例子。你的代码运行了并返回了一个数据框,但它没有包含正确的信息。这提醒我们要始终检查查询的输出,确保它符合预期。

要修复此问题,你需要使用括号来确保 OR 应用于主题名称。修正后的查询如下:

SELECT * FROM sets_with_themes
WHERE (theme_name = ‘Aquanauts‘ OR theme_name = ‘Aquasharks‘) AND num_parts > 100
ORDER BY theme_name;

这个查询是一个很好的例子,说明了括号在分组条件中的重要性。

总结

本节课中,我们一起学习了如何编写带有复合条件 ANDOR 的查询。我们还了解到 SQL 会先计算 AND 再计算 OR,这是因为 AND 创建了更严格、更具体的条件,而 OR 则允许更大的灵活性。

为了避免查询中出现静默的逻辑错误,你需要使用括号来对条件进行分组。例如,你看到了如何在检查乐高套装零件数量之前,先对主题名称的检查进行分组。

复合条件为你检索正确数据提供了更大的灵活性。在下一个视频中,你将通过编写查询来匹配模式,从而将过滤应用于文本数据。

059:基于字符串的条件过滤 🧩

在本节课中,我们将学习如何在 SQL 中使用 LIKE 关键字进行灵活的文本模式匹配,以实现基于字符串的条件过滤。

概述

SQL 中的 LIKE 关键字允许我们使用模式来匹配文本数据。它类似于正则表达式,但功能相对有限。通过结合通配符,我们可以灵活地搜索符合特定模式的字符串。

LIKE 关键字与通配符

LIKE 运算符为使用模式过滤文本数据提供了一种灵活的方法。它可以与字符串和引号一起使用来精确匹配值。然而,LIKE 更强大的功能在于使用两种称为通配符的特殊字符。

以下是两个核心通配符及其含义:

  • 百分号 %:匹配任意一个或多个字符的序列。
  • 下划线 _:精确匹配一个字符。

你可以将 LIKE 看作一个简化版的正则表达式。它提供了很大的灵活性,但无法像正则表达式那样定义同样精细的模式。

实践示例:乐高数据集

以乐高套装数据集为例。你可能对查找名称中包含“Castle”的所有套装感兴趣。但你知道,“Castle”这个词可能出现在套装名称的不同位置。

让我们看看如何在代码中实现这一点。快速提醒一下,你已在本笔记本顶部导入了必要的模块,并使用 lego_sets.db 打开了数据库连接。

精确匹配

你可以从以下查询开始:

SELECT * FROM sets_with_themes WHERE name LIKE 'Castle';

此查询返回所有恰好命名为“Castle”的套装。结果显示有两套。看起来乐高经典城堡在1981年重新发布时,零件数减少了两个。

使用通配符进行模式匹配

然而,你可能对查找“Castle”这个词开头的所有套装感兴趣,而不仅仅是恰好命名为“Castle”的套装。

要查找这些套装,可以使用以下查询:

SELECT * FROM sets_with_themes WHERE name LIKE 'Castle%';

百分号 % 匹配任何字符序列。因此,只要套装名称以“Castle”开头,此查询就会包含该套装。

你可以看到结果多了很多,包括“Castle Minifig”和“Castle Dragons Accessory Set”等。你可以使用 df.name.unique() 来检查所有被选中的不同套装名称。看起来数据框中存在一些重新发布的记录,因为这里有20个套装名称,而不是27个。

复合条件匹配

你也可以将 LIKE 用于复合条件。例如,如果你想将搜索范围扩大到包括以“Castle”或“Knight”开头的套装,可以使用以下查询:

SELECT * FROM sets_with_themes WHERE name LIKE 'Castle%' OR name LIKE 'Knight%';

在 SQLite 中,匹配字符串(如‘Castle’)的大小写不需要与你搜索的文本完全一致。因此,全部大写或全部小写都会得到相同的结果。然而,其他关系型数据库管理系统(RDBMS)的行为可能不同。所以你需要了解你公司使用的系统的规则。只需记住在匹配字符串周围加上引号。

性能注意事项与最佳实践

最后需要注意,通配符(如百分号 %)在处理大型数据集时可能会降低查询速度。特别是,在模式开头使用前导通配符(例如 %Castle)会导致效率低下。

如果可能,应避免使用前导通配符。如果你需要频繁搜索相同的模式,可以考虑通过创建一个带有标签或类别的新列来预处理你的数据集,然后直接在该列上进行过滤。

例如,如果你经常搜索包含“Minifig”(乐高人仔)的套装,你可以在数据集中添加一个包含该信息的新列。

总结

本节课中,我们一起学习了 SQL 中基于模式的文本过滤。你了解到 LIKE 关键字允许你匹配字符串中的模式。我们使用 WHERE name LIKE 'Castle%' 这样的子句来搜索所有名称以“Castle”开头的套装。基于模式的 SQL 过滤是处理文本数据的一个多功能工具。

在下一个视频中,你将学习如何使用 SQL 条件语句为数据创建类别。希望你能继续学习。

060:SQL CASE 条件语句 📊

在本节课中,我们将要学习 SQL 中的 CASE 条件语句。这是一种强大的工具,允许你根据数据中的条件动态创建新的分类或属性,类似于 Python 中的 if-elif-else 逻辑。


概述

SQL 条件语句让你能够高效地在数据中创建分类。当你需要动态创建新属性时,它们尤其有用。SQL 条件语句的功能与你在先前课程中接触过的 Python ifelif 语句类似。条件语句让你可以直接在查询中添加决策逻辑,评估条件并根据这些条件返回特定的结果。

例如,假设你想根据零件数量将乐高套装分类为小、中、大,以便更好地组织它们。你可以使用 SQL 查询来实现。


数据准备与初步观察

首先,我们执行一个简短的查询,从 sets_with_themes 表中选择名称和零件数量。

SELECT name, num_parts FROM sets_with_themes;

使用 df.describe() 查看数据摘要,可以发现零件数量的范围很广,从之前识别出的异常值 -1 到接近 6000,但平均值大约在 162 左右。快速查看直方图会发现,其分布是相当偏斜的。


使用 CASE 语句创建分类

为了对这些套装进行分类,我们可以在 SQL 查询中添加一个新列。是的,SQL 可以为你创建新列。

如果你想定义小、中、大套装,可以使用分位数边界。例如,小套装可能是底部 25% 的套装,中套装介于第 25 和第 75 百分位数之间,大套装则是高于第 75 百分位数的任何套装。

要创建这个查询,你需要使用 CASE 语句。从你的原始查询开始,SELECTFROM 语句保持不变。现在,由于你要创建一个新列,需要添加一个逗号,然后添加 CASE。这标志着一个新条件块的开始。

本质上,你将编写一些不同的 CASE 或条件来告诉 SQL 在新列中放入哪个值。

以下是构建 CASE 语句的步骤:

  1. 使用 WHEN 定义条件:指定一个要评估的条件。
  2. 使用 THEN 指定结果:当条件为真时,在新列中放入指定的值。
  3. 使用 ELSE 处理其他情况:为不满足任何前述条件的行提供一个默认值。
  4. 使用 END 结束语句:用 END 关键字关闭 CASE 语句。
  5. 使用 AS 命名新列:使用 AS 关键字给这个新列起一个名字。

让我们来看一个具体的例子。假设我们根据零件数量分类,边界是 10(第25百分位数)和 172(第75百分位数)。

SELECT
    name,
    num_parts,
    CASE
        WHEN num_parts < 10 THEN 'small'
        WHEN num_parts BETWEEN 10 AND 172 THEN 'medium'
        ELSE 'large'
    END AS set_size
FROM sets_with_themes;

在这个查询中:

  • WHEN num_parts < 10 THEN 'small':如果零件数小于 10,则在新列 set_size 中填入 ‘small’。
  • WHEN num_parts BETWEEN 10 AND 172 THEN 'medium':如果零件数在 10 到 172 之间(包含),则填入 ‘medium’。
  • ELSE 'large':对于所有其他情况(即零件数大于 172),则填入 ‘large’。
  • END AS set_size:结束 CASE 语句,并将生成的新列命名为 set_size

检查 df.head() 的结果,你现在有了一个名为 set_size 的新列,它似乎根据大小正确地分类了前五个套装。


应用与优势

现在,你可以继续做一些分析,例如分别计算每个类别的平均零件数量,这可以作为细分分析的开端。

条件语句非常适合高效地细分你的数据,或者减少你在分析或可视化中需要处理的不同值的数量。SQL 条件语句是在数据中创建类别的强大工具,它们还能让你有效地处理空值。


总结

本节课中,我们一起学习了如何使用 CASE 语句编写 SQL 条件语句。

  • CASE 语句会根据你编写的条件创建一个带有新值的新列。
  • 你使用 WHENTHEN 来编写条件:当某个条件为真时,就将指定的值添加到列中。
  • 最后,使用 END 结束你的 CASE 语句,并使用 AS 为列命名。

掌握 CASE 语句能极大地增强你直接在数据库层面处理和转换数据的能力。在下一个视频中,我们将看到它如何处理空值。

061:SQL空值处理指南 🧩

在本节课中,我们将学习如何在SQL中正确处理数据中的空值(Null)。空值是数据集中常见的缺失值,掌握其处理方法对于数据清洗和分析至关重要。

概述

在数据分析过程中,我们经常会遇到数据缺失的情况,这些缺失值在SQL中被称为空值(Null)。本节将介绍两种处理空值的主要方法:过滤空值行和使用特定值填充空值。

空值的基本概念

在SQL中,空值使用关键字NULL表示。它代表某个字段没有值。这与Python中处理缺失值的方式类似。

查看空值数据

首先,让我们查看包含空值的具体数据行,以便更好地理解数据结构。

以下SQL查询用于选择parent_theme_id为空值的所有行:

SELECT * FROM sets_with_themes WHERE parent_theme_id IS NULL;

运行此查询后,我们发现这些行的parent_theme_id列均为NULL。经分析,这些主题本身就是父主题,因此没有上级父主题。

方法一:过滤空值行

处理空值的一种直接方法是排除包含空值的行。

我们可以使用IS NOT NULL条件来筛选出parent_theme_id不为空的行:

SELECT * FROM sets_with_themes WHERE parent_theme_id IS NOT NULL;

这种方法将数据集从约11,600行减少到约8,000行。虽然有效,但可能会丢失大量有效数据,因此并非总是最佳选择。

方法二:使用COALESCE函数填充空值

另一种更优的方法是填充空值,而不是直接删除行。我们可以使用SQL的COALESCE函数。

COALESCE函数接受一系列参数,并返回第一个非空值。其基本语法如下:

COALESCE(value1, value2, ..., valueN)

在本例中,如果parent_theme_id为空,我们希望用theme_id的值来填充它。

以下是实现此功能的查询:

SELECT
    *,
    COALESCE(parent_theme_id, theme_id) AS updated_parent_theme_id
FROM sets_with_themes;

执行此查询后,原本parent_theme_idNULL的行,其updated_parent_theme_id列现在已被theme_id的值填充。这样,我们既保留了所有数据行,又解决了空值问题,便于后续对父主题进行分析。

总结

本节课我们一起学习了SQL中处理空值的两种核心方法:

  1. 过滤:使用IS NULLIS NOT NULL条件筛选数据。
  2. 填充:使用COALESCE函数,用指定的非空值替换空值。

现在,你已经掌握了有效过滤查询和处理缺失值所需的全部工具。接下来,请完成本课的练习作业和实践实验室。完成后,请继续学习下一课,我们将探索用于数据验证的更高级SQL查询。

062:数据验证 🛡️

在本节课中,我们将学习数据验证的重要性及其在数据分析流程中的关键作用。数据验证是确保你所处理的数据符合预期、准确且完整的过程,它能帮助你在开始深入分析或建模之前,及时发现并纠正数据中的问题。

概述

在开始数据预处理和分析之前,你需要确认正在处理的数据是否符合你的预期。有时,你的数据可能并非如你所想,这会导致分析中的错误或失误。

为什么需要数据验证?

上一节我们介绍了数据验证的目的,本节中我们来看看数据不符合预期可能带来的具体问题。

例如,假设你运行了一个查询来获取一月份的销售数据。你期望看到数百条交易记录,但结果却只得到一个包含寥寥几个值的数据框。如果你在没有验证数据的情况下继续操作,可能会浪费大量时间在不完整的数据上创建报告或进行计算。

以下是进行数据验证时常见的一些问题:

  • 意外的数据格式:例如,日期可能以文本形式存储,而不是正确的日期类型,这使得无法按时间顺序进行排序或筛选。
  • 重复或缺失的条目:一个客户ID可能出现两次,这会虚增客户总数;或者完全缺失,导致分析不完整。
  • 意外的值:像销售额这样的值可能不符合你对高低的预期。例如,你可能会质疑负的销售额是代表亏损,还是一个需要纠正的异常值。

作为分析师,你有责任在每个步骤验证数据,以确保其符合你的预期和分析目标。

如何进行数据验证?

你有一些工具可以做到这一点,特别是思考预期通过子集进行验证

考虑一个包含用于预测患者是否患有糖尿病的诊断测量值的数据集。该数据专门从21岁或以上的皮马印第安女性中收集。在对该数据集运行任何查询之前,你应该思考你期望得到什么样的结果。

例如:

  • 你应该知道outcome特征只能有两个值:1代表糖尿病,0代表无糖尿病。如果此列有任何其他数字,则表明数据有问题。
  • 同样,由于研究仅针对21岁或以上的女性进行,你知道任何低于21的值都应该被调查,以了解它们是错误,还是你对数据集的初步理解不正确。

这对数据集中的其他值也是如此。即使你不必成为医学专家,了解葡萄糖、血压、胰岛素和身体质量指数等测量指标的正常值范围也很有价值。这样,如果你看到任何人类不可能拥有的值,你会立即得到一个警告信号,表明数据有问题。

使用子集进行初步验证

当你处理一个拥有数百万行的大规模数据集时,立即加载和分析整个数据集通常是不必要甚至不切实际的。通过首先在数据集的一个小样本上测试查询,你可以验证数据的结构,及早识别缺失值或不一致之处,并在运行完整分析之前测试你的逻辑以确保其有效。

以下是具体步骤:

  1. 运行一个简单的查询:例如,如果你想探索血糖低于100的老年患者,可以从一个无过滤的查询开始:

    SELECT * FROM diabetes LIMIT 10;
    

    输出将仅限于前10条记录。以这样的查询开始也允许你验证数据:值是否如预期那样是数字?

  2. 逐步添加条件:一旦确认数据看起来正确,你可以添加更多条件。例如,你现在可以添加一个WHERE子句来关注年龄超过60岁且血糖低于100的患者:

    SELECT * FROM diabetes WHERE age > 60 AND glucose < 100 LIMIT 10;
    

    这一步缩小了数据范围,并确保筛选按预期工作。

  3. 检查结果:你应该再次检查结果,看看WHERE子句是否正确过滤了行。年龄和血糖值是否在合理范围内?血糖是否与其他健康指标(如outcome特征)有合理的相关性?

手动审查你的数据可以让你更熟悉它。你并非在浪费时间,而是在培养良好的数据习惯。

当结果不符合预期时

如果你的结果不符合预期,请暂停并开始调查。

  • 首先,检查查询逻辑:例如,如果你正在筛选年龄大于60岁的患者,请仔细检查条件是否正确应用。
  • 其次,利用数据元数据:审查你应该期望哪些值。数据工程师或数据团队中更了解数据收集方式的其他成员,可能能够帮助你更好地理解它。
  • 另一种选择是测试替代查询,甚至使用可视化来更好地理解潜在的差异。

请记住,意外的值并不总是错误的,它们可能揭示了关于你数据的一些重要信息。保持好奇心。

总结

在本节课中,我们一起学习了数据验证的核心概念。我们了解到,在分析前验证数据至关重要,可以避免基于错误或不完整数据得出错误结论。我们探讨了通过思考预期值和使用子集进行初步查询来验证数据的方法。最后,我们讨论了当发现数据异常时应采取的调查步骤。

你有多种选择来检查你的数据是否符合预期。在接下来的视频中,我们将开始学习在SQL中进行数据验证,从关键字COUNTDISTINCT开始。

063:计数与去重 📊

在本节课中,我们将学习两种基础但至关重要的数据验证技术:计数去重。我们将通过SQL查询来检查数据的总行数、非空值数量以及列中的唯一值,从而确保数据的完整性和准确性。


概述

数据验证是数据处理流程中的关键步骤。通过验证,我们可以确认数据是否符合预期,并发现潜在问题,如数据缺失或重复记录。本节课将重点介绍如何使用SQL的COUNTDISTINCT关键字来执行这些验证。


计数(COUNT)验证

上一节我们介绍了数据验证的重要性,本节中我们来看看如何使用COUNT进行验证。COUNT关键字用于回答一个基础但重要的问题:数据集中有多少条记录? 将这个数字与你的预期进行比较,可以揭示数据是否缺失或是否存在重复行。

COUNT只计算非空行。而COUNT(*)用于计算每一行,即使某些列包含空值。

以下是使用COUNT进行验证的步骤:

  1. 计算总行数:使用SELECT COUNT(*) FROM table_name;。这能告诉你数据集的总记录数。
  2. 计算特定列的非空值数量:使用SELECT COUNT(column_name) FROM table_name;。这能告诉你该列中有多少有效数据。

去重(DISTINCT)验证

了解了如何计数后,我们来看看如何检查数据的唯一性。DISTINCT关键字用于识别列中的唯一值。这有助于你理解数据的多样性,并且对于检测重复条目特别有用。

以下是使用DISTINCT进行验证的步骤:

  1. 获取唯一值列表:使用SELECT DISTINCT column_name FROM table_name ORDER BY column_name;。这会返回一个按字母顺序排序的唯一值列表。
  2. 计算唯一值的数量:将COUNTDISTINCT结合使用,可以计算列中不同值的总数。

实践演练:使用乐高数据集

为了看COUNTDISTINCT如何实际操作,让我们使用上一课中使用的乐高数据集运行一些查询。你可以使用提供的练习文件跟着操作。

首先,导入库并打开数据库连接。快速回顾一下数据表的结构:

SELECT * FROM sets_with_themes;

现在,我们可以生成一些SQL查询来验证数据。

首先,计算总行数:

SELECT COUNT(*) FROM sets_with_themes;

结果是11,673,这是正确的。如果数字更高,可能表明存在重复记录或其他问题。

接着,计算特定列的非空值数量:
在上一课中,我们注意到有几千个parent_theme_id值缺失。为了计算该列中有多少非空条目,可以将COUNT(*)替换为COUNT(parent_theme_id)

SELECT COUNT(parent_theme_id) FROM sets_with_themes;

该列包含8046个非空值。这个值与COUNT(*)返回的值之间的差异表明该列中存在一些空值。

然后,检查唯一值:
你可以使用DISTINCT关键字获取所有唯一主题名称的列表。

SELECT DISTINCT theme_name FROM sets_with_themes ORDER BY theme_name;

现在你看到了按字母顺序排序的各种主题。

最后,结合使用COUNT和DISTINCT:
结合COUNTDISTINCT关键字是一种强大的数据验证技术。例如,假设你想检查每个主题是否具有唯一的ID。

你可以检查theme_nametheme_id两列中不同值的数量。

SELECT COUNT(DISTINCT theme_name) FROM sets_with_themes;

输出显示有386个独特的主题。

SELECT COUNT(DISTINCT theme_id) FROM sets_with_themes;

有趣的是,这里有575个不同的结果。所以看起来有些主题可能名称相同,但ID不同。值得进一步调查这种差异。


总结

本节课中我们一起学习了数据验证的核心技术。

当验证你的数据时,总是从对数据应该是什么样子的直觉开始,然后使用这些方法来确认和完善你的理解。

  • 使用 COUNT 来确认总行数或列中非空值的数量。
  • 使用 DISTINCT 来探索数据的唯一性和多样性。
  • COUNTDISTINCT 结合使用,以验证关键特征,如完整性。

这些技术是你防御数据错误的第一道防线。


COUNTDISTINCT允许你验证数据的基本特征,但如果你想验证数据的特定子集,你将需要用于分组数据的工具。请继续观看下一个视频来了解如何操作。

064:分组聚合 🧱

概述

在本节课中,我们将要学习 SQL 中一个非常强大的功能:分组聚合。我们将了解如何使用 GROUP BY 语句将数据按照一个或多个属性进行分组,并在此基础上应用聚合函数(如 COUNTSUM)来分析每个组的特征。


分组操作简介

SQL 提供了工具,让你能够基于一个或多个属性对数据进行分组,从而创建数据的子集。

假设你的任务是分析乐高套装在不同主题系列中的分布情况。你可以先按主题对数据进行分组,然后分析这些组的特征。这个任务可以通过 GROUP BY 语句来完成。

使用 GROUP BY 进行分组

首先,请确保你已在笔记本顶部导入了必要的模块,并使用 lego.db 打开了数据库连接。

你可以使用 GROUP BY 语句按主题对套装进行分组。当 GROUP BY 子句与 COUNTSUM 等聚合函数结合使用时,其功能会变得非常强大。

例如,如果你的目标是查看每个主题关联了多少个套装,你可以将 GROUP BY 语句与 COUNT 函数结合使用。

以下是具体的查询示例:

SELECT theme_name, COUNT(*) AS set_count
FROM sets_with_themes
GROUP BY theme_name
ORDER BY set_count DESC;

在这个查询中,GROUP BY theme_name 根据唯一的主题名称将数据组织成组,而 COUNT(*) 则统计每个组中的套装数量。尽管 COUNT(*) 在查询中出现在分组之前,但它实际上是在分组之后执行的。通过按降序排列结果,你可以快速找出最受欢迎的主题。

执行这个查询可以清晰地展示套装在不同主题间的分布情况。像“补充包”、“科技系列”、“城市系列”和“好朋友系列”这样的基础主题似乎拥有最多的套装数量。

如果你对最不受欢迎的主题更感兴趣,可以按套装数量升序排序。

SELECT theme_name, COUNT(*) AS set_count
FROM sets_with_themes
GROUP BY theme_name
ORDER BY set_count;

检查结果时,你可能会发现一些鲜为人知的主题,由于其稀有性,可能更具价值。

使用 COUNT DISTINCT

你还可以使用 COUNT DISTINCT 模式来检查每个组内唯一值的数量。

例如,你之前可能注意到有些主题名称对应多个唯一的 ID。你可以使用以下查询来检查每个主题名称关联了多少个 ID:

SELECT theme_name, COUNT(DISTINCT theme_id) AS theme_count
FROM sets_with_themes
GROUP BY theme_name
ORDER BY theme_count DESC
LIMIT 10;

然后查看前 10 个值,看看哪些主题名称拥有最多的唯一 ID。有趣的是,一些名称更通用的主题,如“补充包”、“消防与航空”,每个名称关联了 11 个或更多的唯一主题 ID。

SQL 的执行顺序

现在我们来回顾一下。你可以使用 GROUP BY 语句并提供要分组的列名来对行进行分组,这类似于在 Pandas 中对行进行分组。如果你的查询包含分组,那么像 COUNT 这样的聚合函数将分别应用于每个组。

有趣的是,你并不一定要选择你用来分组的列,但选择它有助于你更好地理解结果。

你刚才了解到,像 COUNT 这样的聚合操作发生在分组之后。SQL 有一套固定的操作顺序:

  1. FROM:确定数据来源的表。
  2. WHERE:进行过滤。
  3. GROUP BY:进行分组。
  4. 聚合函数:如 COUNT
  5. SELECT:选择你请求的列或聚合结果。
  6. ORDER BY:最后进行排序。

你不需要死记硬背这个顺序,但你应该知道 SQL 有固定的操作顺序,并且聚合操作发生在分组之后。

总结

本节课中我们一起学习了 SQL 中的分组聚合操作。

一旦你对数据进行了分组,就可以对创建的子集应用许多不同的聚合函数。在本视频中,你应用了 COUNTCOUNT DISTINCTGROUP BY 结合使用。

在下一个视频中,你将探索几个新的聚合函数。我们下节课见。

065:SQL 聚合函数(最小值、最大值、求和)📊

在本节课中,我们将学习如何使用 SQL 中的聚合函数,特别是 MINMAXSUM,来对数据进行汇总分析。我们将了解如何结合 GROUP BY 子句,高效地计算分组数据的统计信息。

除了计数,SQL 还提供了多种聚合数据的方式。对于数值型数据,常见的选项包括最小值、最大值和求和,用于分析分组数据。

SQL 提供了强大的聚合函数。这些函数与 GROUP BY 子句协同工作,先将数据集组织成有意义的组,然后再进行计算。

可以这样理解:Python 中单独的 groupby 函数本身不会显示太多信息,你需要配合 summean 这样的汇总函数来提取洞察。

SQL 遵循相同的原则,它将 GROUP BY 与聚合函数结合,以高效地生成汇总级别的计算。

关键的聚合函数包括:

  • COUNT:你在之前的视频中已经使用过。
  • AVERAGE:计算每个组中某列的平均值。
  • SUM:对每个组中某列的值进行求和。
  • MINMAX:找出每个组中的最小值和最大值。

作为快速回顾,你已在本笔记本顶部导入了必要的模块,并使用 Legoets.bbb 打开了数据库连接。

假设你想探索每个主题系列中最大的乐高套装。你可以使用以下查询:

SELECT theme_name, MAX(num_parts)
FROM sets_with_themes
GROUP BY theme_name;

结果显示,“12 volt”主题系列有一个包含超过 700 个零件的套装,而“agogori”系列的套装似乎非常小。

这个查询告诉了你最大套装的尺寸,但没有告诉你具体是哪个套装。为了包含每个主题系列中最大套装的所有详细信息,你可以在 SELECT 语句中添加 *,这将显示该最大套装所在行的所有列。

请注意,这是 SQLite 的一个特殊功能,并不适用于所有数据库系统。添加 * 并非冗余,因为当你使用 MAX(num_parts) 时,你选择的是该列具有最大值的行。然而,要检索该行的所有详细信息,你需要查看所有列。因此,选择像 MAX 这样的聚合函数与选择相应的列值是分开进行的。

看起来最大的“12 volt”套装是“Intercity passenger train”,这很合理,它似乎会有很多零件,并且发布于 1980 年。

现在,让我们对零件数最少的套装进行同样的操作。你只需将查询中的 MAX 改为 MIN,并添加排序以查看拥有最小套装的系列:

SELECT theme_name, MIN(num_parts)
FROM sets_with_themes
GROUP BY theme_name
ORDER BY num_parts;

有趣的是,有三个套装的零件数是 -1。这代表什么意思?如果不进一步阅读数据集说明,并不完全清楚。其中一些零件数为 0 和 -1 的主题甚至根本不是乐高套装,它们是木制收纳盒或创意书籍。目前尚不清楚 -1 和 0 是否有不同的定义,或者这些负值是否代表某种无效值。有时,当你想保持列为数值型时,负值会被用作特定原因的标记。

SQL 还允许你在一个查询中包含多个聚合函数。例如,与其分别查看每个主题系列的零件数计数、最大值和最小值,不如将它们合并到一个查询中:

SELECT theme_name,
       COUNT(*) AS set_count,
       MIN(num_parts) AS min_parts,
       MAX(num_parts) AS max_parts,
       AVG(num_parts) AS average_parts
FROM sets_with_themes
GROUP BY theme_name;

这非常有用。现在你得到了一个类似于 df.describe() 的表格,并且可以在大型数据集上高效运行,而无需将所有数据加载到本地计算机上。

总结

本节课中我们一起学习了:

  • SQL 提供了多种不同的聚合函数,包括 MINMAXAVERAGE
  • 通过选择所有列(*),你可以了解更多关于包含某列最小值或最大值的行的信息。请记住,这通常只适用于 SQLite,你需要探索其他数据库系统的功能以确保兼容。
  • 你学会了可以在同一个查询中同时使用多个聚合函数。

就像 pandas 一样,SQL 也提供了许多不同的聚合函数。我鼓励你在本课的最后一个视频中探索所有可用的选项。在下一个视频中,你将学习如何使用聚合结果来过滤分组数据。我们下个视频见。

066:数据验证 - HAVING子句 📊

在本节课中,我们将学习如何使用SQL的HAVING子句对分组后的数据进行筛选。这是一种识别数据中异常值和重复项的有效技术。

概述

分组数据后,可以基于聚合函数的结果对分组进行筛选。HAVING子句在此过程中扮演关键角色。

在SQL中,筛选可以在分组之前或之后进行。

分组前筛选:WHERE子句

分组前,可以使用WHERE语句基于单行数据进行筛选。

例如,可以检查每个独立的数据集是否包含超过10个部分。然而,有时我们更关心分组后的筛选。

分组后筛选:HAVING子句

上一节我们介绍了分组前筛选,本节中我们来看看如何在分组后进行筛选。

例如,可以筛选出总数据集数量少于50的主题。这个操作必须在分组之后进行,因为只有在分组完成后,我们才能知道每个组中的数据量。

以下是具体操作步骤。

首先,需要在本笔记本顶部导入必要的模块,并与Legos数据库建立连接。

在之前的视频中,我们使用了以下查询来统计每个主题的数据集数量:

SELECT theme_name, COUNT(*) AS set_count FROM sets_with_themes GROUP BY theme_name

可以扩展此查询,通过添加HAVING COUNT(*) > 50语句,仅包含至少拥有50个数据集的主题。

SELECT theme_name, COUNT(*) AS set_count FROM sets_with_themes GROUP BY theme_name HAVING COUNT(*) > 50

这里,HAVING COUNT(*) > 50充当过滤器,只保留数据集数量超过50的主题。此语句有助于将分析重点集中在最受欢迎的主题上。

WHERE与HAVING的区别

需要记住,WHERE处理行级筛选,而HAVING在聚合后生效。当处理大型数据集并需要对结果进行精确控制时,这一区别至关重要。

分组后筛选不一定需要使用与SELECT语句中相同的聚合函数。

例如,可以先筛选出至少拥有10个数据集的主题,然后显示每个主题的平均零件数。

该查询如下所示:

SELECT theme_name, COUNT(theme_id) AS set_count, AVG(num_parts) AS average_parts FROM sets_with_themes GROUP BY theme_name HAVING COUNT(theme_id) > 10 ORDER BY average_parts DESC

这个查询允许我们仅对拥有最小样本量(10个)的主题计算平均值。应该包含数据集数量以验证HAVING语句是否正确执行。按平均零件数降序排列,可以查看平均零件数最多的主题。

因此,模块化建筑(非常大且复杂的套装)平均拥有许多零件,而钥匙链则很少。事实上,似乎有很多钥匙链的平均零件数低于1,因为平均值小于1。

总结

本节课中我们一起学习了HAVING语句,它允许基于聚合函数的值对分组后的行进行筛选。

通常基于COUNTAVG等聚合函数进行筛选。

课程结尾

学得很棒!您已经掌握了使用SQL进行数据验证的许多强大工具,从聚合到分组。

接下来,您将完成本课的练习作业和实践实验室。完成后,请跟随我进入本课程的下一节,也是最后一节课:SQL连接。

067:表连接介绍 🧩

在本节课中,我们将要学习 SQL 中一个极其强大的功能——表连接(Joins)。到目前为止,我们编写的查询都只能从单个表中检索数据。然而,当数据分散在多个表中时,通过连接将表关联起来,SQL 的能力将得到质的飞跃。

为什么需要连接表? 🔗

在关系型数据库中,为了保持数据的组织性和高效性,信息通常被拆分存储在不同的表中。每个表通常专注于一个单一的实体。例如,你可能有一个存储客户信息的表,一个存储交易记录的表,以及一个存储产品信息的表。

上一节我们介绍了多表存储的优势,本节中我们来看看它的局限性。这种设计虽然整洁,但无法直接回答那些需要组合多个表信息的问题。例如,假设你是一名数据分析师,正在分析一个P2P借贷平台的数据集。你想知道某位特定客户去年申请了哪些贷款。客户详情可能存储在一个表中,交易记录在另一个表中,而贷款信息又在第三个表中。

如果没有表连接功能,你将不得不通过耗时且容易出错的手动流程来匹配和合并这些数据。SQL 连接正是为了解决这个问题而生的。

表连接的核心原理 ⚙️

SQL 连接允许你根据表之间列的关联关系,显式地将多个表链接起来,从而将所有相关数据整合在一起。连接操作通常依赖于一个唯一的标识符,最常见的是像 customer_id 这样的 ID 列。

核心概念公式:

结果集 = 表A JOIN 表B ON 表A.关联键 = 表B.关联键

如果客户表和贷款表都有一个 customer_id 列,你就可以通过这个列将客户信息“附加”到贷款记录上,为贷款数据提供更丰富的上下文。这种连接表的能力是关系型数据库最强大的特性之一。

一个简单的类比是 Excel 或 Google Sheets 中的 XLOOKUP 函数(如果你不熟悉也没关系,这只是一个给了解它的人做的快速类比)。XLOOKUP 允许你在一个工作表中搜索特定值,并从另一个工作表中返回对应的详细信息。SQL 连接遵循相同的原理,但规模更大、灵活性更高。它允许你搜索一个表中的匹配项,并将这些匹配的记录带入你的原始表中。

SQL 连接的主要类型 📊

SQL 中有几种不同类型的连接,每种连接根据你希望如何在表之间匹配行而服务于不同的目的。让我们继续以贷款数据为例,假设有两个表:一个包含客户及其人口统计详情(customers 表),另一个包含记录每笔贷款交易的详情(loans 表)。

以下是不同连接类型的工作原理:

在深入了解每种类型之前,以下是主要的连接类型及其简要说明:

  • 左连接 (LEFT JOIN):返回左表(此处是 customers 表)的所有行,以及右表(loans 表)中的匹配行。如果某个客户没有申请过贷款,其详细信息仍会出现在结果中,而贷款信息将显示为 NULL
  • 内连接 (INNER JOIN):仅返回两个表中都有匹配的行。例如,如果你只想查看那些申请过贷款的客户,内连接将只给出这部分数据,排除没有贷款的客户,以及那些在客户表中找不到匹配项的贷款记录。
  • 外连接 (OUTER JOIN):返回两个表中的所有行,即使没有匹配项。它通常用于表示同一实体的两个表。例如,如果你有两个来自不同公司的客户表,你可以使用外连接将它们合并成一个包含所有客户的巨型表。

总结与展望 🚀

本节课中我们一起学习了 SQL 表连接的重要性、核心原理以及主要类型。你了解了为什么在多表数据库中连接是必不可少的,并且能够区分左连接、内连接和外连接的不同用途。

现在你已经明白了连接为何如此重要,并能区分不同的连接类型,接下来就准备好观看它们在实战中如何应用吧。请跟随我进入下一个视频。

068:左连接 🧩

在本节课中,我们将要学习 SQL 中的 左连接 操作。左连接是一种强大的工具,它允许我们将一个表中的所有记录与另一个表中的匹配记录组合起来,从而为现有数据添加更多上下文信息。


回顾与引入

上一节我们介绍了 SQL 连接的基本概念,它用于合并来自不同表的数据。

本节中我们来看看 左连接 的具体应用。左连接有助于为你当前的表添加更多背景信息。在本模块之前的课程中,我们一直使用乐高套装数据库中的一个单一表(sets_with_theme)。实际上,这个表是由多个表组合而成的。在实践中,数据库的不同部分通常分开存储。以乐高数据库为例,套装和主题信息就分别存储在不同的表中。

查看实际的乐高数据模式,你会注意到套装和主题存储在独立的表中。set_numsets 表的主键。那么 sets 表中的 theme_id 是什么呢?


theme_id 是一个外键,它允许你访问 themes 表中的主题信息。因此,左连接将允许你把主题名称和父级ID等信息添加到套装表中。


在 Python 中实践左连接

让我们看看如何在你的 Python 笔记本中执行类似的连接操作。假设你对查看每个套装包含的零件感兴趣。

你可以将 parts 表左连接到 sets 表,以添加关于每个零件所属套装的信息,例如套装名称和发布年份。

以下是具体步骤,你可以使用本课提供的练习项目跟随操作。

首先,导入必要的库并创建数据库连接。

# 示例:导入库和建立连接(具体代码取决于你使用的库,如 sqlite3, pandas, sqlalchemy 等)
import pandas as pd
import sqlite3

conn = sqlite3.connect(‘your_database.db’)

左连接会返回左表的所有条目,以及右表的匹配条目。如果没有匹配项,结果中右表的列将显示为 NULL 值。

我们将逐步构建查询。首先,执行 SELECT * FROM sets_with_themes,这会给出每个套装及其主题的信息。数据框底部显示总共有 11673 行。

同时,查询 SELECT * FROM sets_parts 会给出 set_num, part_num, color_id, quantityis_spare。这个表代表每个零件,因此只包含零件的独立信息。数据框底部显示有近 600,000 行。

为了连接它们,我们这样写查询:

SELECT *
FROM sets_with_themes
LEFT JOIN sets_parts
ON sets_with_themes.set_num = sets_parts.set_num;

你已经知道查询的前两个语句。LEFT JOIN 行指明了你要执行的连接类型。然后你指定想要从哪个表添加信息。最后一行指明应该匹配两个表中的哪些列。

两个表都有一个 set_num 列,它是每个套装的唯一标识符,因此你可以使用这个数字来匹配行。此查询将匹配 sets_with_themes 表中与 parts 表中 set_num 相同的行。

ON 子句中的点符号(table.column)有助于明确指定某个列来自哪个表。如果没有点符号,你会写成 set_num = set_num,SQL 将无法判断你指的是哪个表,这会导致“列名不明确”的错误。为了可读性,通常将左表放在等号左边,右表放在等号右边。

现在,这个查询会返回一个巨大的表,因为你连接了所有套装和所有零件,这意味着现在每个套装中的每个零件都对应一行。这是一个可以进一步探索的有趣表格。

例如,你可以在查询末尾添加 WHERE sets_with_themes.set_num = ‘8480-1’ 来筛选出 1996 年发布的复古太空梭套装(这是一个热门套装)。现在,你可以逐行查看这个套装中的各个零件。


左连接的高级应用

这种连接还允许你做一些很酷的分析,比如检查每年发布的套装中包含的独特零件数量。

以前,你无法完成这个计算,因为发布年份和零件信息是分开存储的。现在可以尝试以下查询:

SELECT year, COUNT(DISTINCT part_num) AS parts_per_year
FROM sets_with_themes
LEFT JOIN sets_parts
ON sets_with_themes.set_num = sets_parts.set_num
GROUP BY year;

确保选择 year 列,这样你才能看到每年对应的零件数量。结果显示,1950年只有6个独特零件,1953年也是如此,然后这个数字似乎逐渐增加,直到2010年代达到300多个。

你还可以使用折线图将这些数据可视化。

增长趋势非常明显。


本节总结

本节课中我们一起学习了如何使用 左连接 为套装表添加每个零件的详细信息。

  • LEFT JOIN 语句指明了要向 FROM 语句中的表添加哪个表的信息。
  • ON 语句则告诉 SQL 可以使用哪对键来匹配行。ON sets_with_themes.set_num = sets_parts.set_num 指定了每个表中要匹配的列。

我们看到,这种连接可以创建一个更丰富的数据集,并实现以前不可能完成的分析。


你已经学会了如何使用左连接为当前表添加更多上下文。在下一个视频中,你将练习使用内连接来合并多个表中的共同观测数据。

069:68_内连接 🧩

在本节课中,我们将学习 SQL 中的 内连接(INNER JOIN) 操作。内连接用于合并两个表中具有匹配值的行,从而获取更丰富的数据上下文。我们将通过具体示例来理解其工作原理和应用场景。

概述

在数据分析中,我们经常需要处理多个包含相同实体信息的表格。通过连接这些表格,我们可以找到共同的行,从而获得关于该实体的更丰富上下文。内连接是其中一种核心的连接方式。

内连接的基本概念

内连接将两个表中在指定列(ON 子句中定义)具有匹配值的行组合起来。这种连接只包含同时存在于两个表中的行。这些表格可以代表同一个实体,也可以代表两个具有有意义关系的不同实体。

其基本语法结构可以用以下伪代码表示:

SELECT 列名
FROM 表A
INNER JOIN 表B
ON 表A.共同列 = 表B.共同列;

内连接的应用示例

假设您有两家医院的病人记录,并且只想分析同时入住过这两家医院的病人。以下查询可以实现这个目标:

SELECT *
FROM hospital_A
INNER JOIN hospital_B
ON hospital_A.patient_id = hospital_B.patient_id;

此查询的输出结果将只显示同时去过 A 医院和 B 医院的病人。在这个案例中,需要假设两家医院使用相同的病人 ID 系统,因此同一病人在每家医院都有相同的 ID。

内连接的可视化理解

我们可以用韦恩图来可视化内连接。左边的圆圈代表第一个表(例如 A 医院的数据),右边的圆圈代表第二个表(例如 B 医院的数据)。

内连接代表两个圆圈重叠的交叉区域。只属于 A 医院或只属于 B 医院的病人不会被包含在内。因此,内连接最适合用于聚焦两个表之间的共享数据。

通过中间表进行连接

有时,两个表的数据无法直接连接,因为它们没有共同的字段。在这种情况下,可以使用一个中间表作为桥梁。

考虑一个涉及学生、课程和中间选课表的场景:

  • students 表包含学生信息,如 student_idnamedate_of_birthemail
  • courses 表包含课程详情,如 course_idcourse_namedepartmentinstructor
  • enrollments 选课表通过 student_idcourse_id 列将学生与他们所选的课程关联起来。

要查找所有学生及其注册的课程,可以编写如下查询:

SELECT students.name, courses.course_name
FROM students
INNER JOIN enrollments ON students.student_id = enrollments.student_id
INNER JOIN courses ON enrollments.course_id = courses.course_id;

这个查询首先通过 student_id 这个共同字段将 students 表与 enrollments 表连接起来。然后,这个新形成的表再通过 course_id 这个共同字段与 courses 表连接。换句话说,查询利用 enrollments 表将学生和他们的课程联系起来。

以这种方式使用中间表对于处理“多对多”关系至关重要,例如学生和课程的关系(一个学生可以选多门课,一门课可以有多个学生)。

总结

本节课我们一起学习了 SQL 中的内连接操作。我们了解到,内连接是 SQL 中最复杂、最强大的操作之一,它通过匹配两个表中的共同值,来合并行并聚焦于共享数据。我们还学习了当表之间没有直接共同字段时,如何利用中间表作为桥梁进行连接,这对于处理现实世界中的多对多关系非常有用。

内连接是连接操作的基础。在下一节视频中,我们将学习另一种重要的连接类型:外连接(OUTER JOIN)

070:外连接 🧩

在本节课中,我们将学习如何使用外连接来组合多个数据表。外连接是一种强大的工具,它允许我们合并代表同一实体的表格,即使某些记录在另一个表中没有匹配项,也能将其包含在结果集中。


当处理代表同一实体的多个表格时,您可能希望组合成一个完整的数据集。即使某些记录不匹配,您仍然可以将这些表连接起来。组合代表同一实体的多个表通常使用外连接来完成。

请注意,SQLite 不支持外连接,但许多其他关系型数据库管理系统支持。您应该熟悉外连接的工作原理。

外连接包含来自一个或两个表的行,即使这些行在另一个表中没有匹配项。

例如,您可以编写如下查询:

SELECT * FROM hospital_A FULL OUTER JOIN hospital_B ON hospital_A.patient_id = hospital_B.patient_id;

查询输出将显示来自两家医院的所有患者的完整列表,包括重叠的部分。换句话说,结果会包含同时去过两家医院的患者,以及只去过其中一家医院的患者数据缺失的情况。

再次强调,您需要假设患者ID系统在医院之间是共享的。

您可以使用类似的维恩图来可视化外连接。对于内连接,左边是医院A的第一个表,右边是医院B的数据。

外连接代表整个区域,包括重叠和非重叠的部分。所有患者都被包含在内,无论他们去过医院A、医院B,还是两家都去过。

因此,当您需要所有记录而无论它们是否重叠时,外连接最适合用于创建全面的数据集。

虽然外连接有一些限制需要注意,但它们在汇集代表特定实体的大型组合数据集方面极其有用。

每种连接都有其适用场景,具体取决于您的数据和业务问题。作为数据分析师,您的职责是将业务目标与最合适的连接类型相匹配。

这带您来到了本课的结尾,也几乎来到了本课程的终点。

接下来,您将完成本课的练习作业和实践实验,然后处理评分项目,包括本课程的评分作业、实验和顶点项目。

在顶点项目中,您将探索餐厅特征,以更好地理解如何预测餐厅的检查表现。

完成顶点项目后,我将在最后一个视频中与您见面,讨论您作为数据分析师的下一步计划。


总结

本节课中,我们一起学习了外连接的概念和应用。我们了解到,外连接能够合并多个表格的所有记录,无论它们是否匹配,这对于创建全面的数据集至关重要。通过具体的SQL代码示例和维恩图解释,我们掌握了外连接的执行方式和适用场景。最后,我们明确了根据具体业务需求选择合适连接类型是数据分析师的关键技能。

071:数据输入/输出与预处理(Python 和 SQL)🎯

在本节课中,我们将对已完成的课程内容进行总结,并展望后续的学习路径。


恭喜你完成了顶点项目以及本门课程。你在数据输入/输出与预处理方面取得了令人瞩目的进步。

上一节我们回顾了核心技能,本节中我们来看看你具体掌握了哪些知识。

以下是你在本课程中学到的关键技能:

  • 数据获取:你学习了如何使用 pandasBeautifulSoup 从网页抓取数据,以及如何使用 API 密钥通过 API 收集数据。
  • 数据预处理:你掌握了核心的数据预处理技术,包括文本处理、正则表达式、标准化、归一化等。
  • 数据库与 SQL:你在最后两个模块中探索了数据库结构,并学习了使用 SQL 进行查询。
  • 实战分析:你分析了各种现实世界的数据集,涵盖音乐、乐高、食品安全检查、科技工作等多个领域。

你现在已经准备好处理任何数据库了。当然,学无止境。

因此,我希望你能加入本系列的下一个课程——《数据叙事》。

在专业证书项目的最后一门课程中,你将探索高级的数据叙事技巧,以有效传达你的见解。

以下是《数据叙事》课程的核心内容:

  • 沟通技巧:你将学习如何构建有说服力的备忘录、笔记、演示文稿和报告。
  • 可视化工具:你将学习 Tableau 这一行业标准数据可视化软件的基础知识。
  • 实战应用:你将使用 Tableau 创建具有独特交互功能(如工具提示)的图表和仪表板,甚至尝试地图等新的可视化类型。
  • 顶点项目:最后,你将完成项目的顶点项目,在一个端到端的分析项目中综合运用所有技能。

本节课中我们一起回顾了在数据 I/O 与预处理课程中的学习成果,并了解了后续《数据叙事》课程的核心内容。再次祝贺你完成本课程。我们下一门课《数据叙事》再见。😊

posted @ 2026-03-26 08:17  布客飞龙II  阅读(0)  评论(0)    收藏  举报