Data-Science-For-Beginners 学习指南:Working with Data——从关系数据库到 Python 数据准备的完整实战
【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners
本指南围绕开源课程 Data-Science-For-Beginners 第二章「Working with Data」展开,该章位于仓库 2-Working-With-Data/,共包含四节循序渐进的课程:关系数据库、非关系数据库、Python 与 Pandas 数据处理、数据准备与清洗。读完本文,你将掌握表的建模与主外键概念、SQL 的 SELECT/WHERE/JOIN 查询、NoSQL 与文档数据库的实践方法、Pandas 中 Series/DataFrame 的核心操作,以及缺失值与重复数据的处理策略,并了解仓库内配套的示例数据(SQLite 机场库、Cosmos DB 样例 JSON、COVID 数据集、表单 CSV)如何落地这些技能。
章节总览:一条从存储到清洗的数据管线
「Working with Data」对应仓库目录 2-Working-With-Data/,其学习目标非常明确:掌握在应用程序中管理、操作和利用数据的各种方法。四个子主题构成一条完整的数据处理链路:
- 关系数据库(Relational Databases)——理解表、主键、外键与 JOIN,用 SQL 检索多表数据;
- 非关系数据库(Non-relational Databases)——认识电子表格与四类 NoSQL 存储,并通过 Cosmos DB Emulator 实操文档数据库;
- 使用 Python 处理数据(Working with Python)——掌握 Pandas 的 Series、DataFrame 核心操作,并用真实 COVID 数据完成挑战;
- 数据准备(Preparing Data)——用 Pandas 处理缺失值、重复数据等"脏数据"问题。
这种编排反映了一个核心理念:数据库擅长高效存储与查询,而数据科学工作中大量的探索性分析、变换与可视化,则需要借助 Python 这样更灵活的工具来完成。接下来我们逐一深入。
一、关系数据库:从一张表到多表建模
1.1 一切始于表
关系数据库的核心构件是表(table),其本质与电子表格一致:行(row)存放具体数据,列(column)描述数据的含义。以存储城市信息为例:
| City | Country |
|---|---|
| Tokyo | Japan |
| Atlanta | United States |
| Auckland | New Zealand |
列名City、Country描述所存数据的含义,每一行则是一个城市的完整信息。这个朴素模型是理解关系数据库的起点。
1.2 单表方案的局限
当我们想在表中加入更多维度时,单表方案会暴露出严重问题。以城市年度降雨量为例,如果按"每行一条降雨记录"的方式存储:
| City | Country | Year | Amount |
|---|---|---|---|
| Tokyo | Japan | 2020 | 1690 |
| Tokyo | Japan | 2019 | 1874 |
| Tokyo | Japan | 2018 | 1445 |
城市名和国家被反复复制,浪费存储且毫无必要——Tokyo 只有一个名字。若改用"每年一列"的方式:
| City | Country | 2018 | 2019 | 2020 |
|---|---|---|---|---|
| Tokyo | Japan | 1445 | 1874 | 1690 |
| Atlanta | United States | 1779 | 1111 | 1683 |
| Auckland | New Zealand | 1386 | 942 | 1176 |
虽然避免了行重复,但每新增一个年份就要修改表结构,而且把年份当作列会让检索和计算变得笨拙。这正是需要多张表与关系(relationship)的原因:通过拆分数据避免重复,并在操作数据时获得更大灵活性。
1.3 关系核心:主键与外键
拆分数据的第一步,是为每个实体确定唯一标识。**主键(Primary Key,常缩写为 PK)**是表中唯一标识某一行的值。虽然理论上可以用城市名这类自然值做主键,但实践中几乎总应使用数字等自动生成的标识符——主键不应随业务数据变化而变化,否则会破坏关系。大多数情况下主键是自动生成的数字。
拆分后的cities表:
| city_id | City | Country |
|---|---|---|
| 1 | Tokyo | Japan |
| 2 | Atlanta | United States |
| 3 | Auckland | New Zealand |
随后建立rainfall表,不再重复城市全部信息,而是引用cities表的 id:
| rainfall_id | city_id | Year | Amount |
|---|---|---|---|
| 1 | 1 | 2018 | 1445 |
| 2 | 1 | 2019 | 1874 |
| 3 | 1 | 2020 | 1690 |
| 4 | 2 | 2018 | 1779 |
| 5 | 2 | 2019 | 1111 |
| 6 | 2 | 2020 | 1683 |
| 7 | 3 | 2018 | 1386 |
| 8 | 3 | 2019 | 942 |
| 9 | 3 | 2020 | 1176 |
rainfall表中的city_id列指向cities表的主键,在关系数据库术语中称为外键(Foreign Key,常缩写为 FK)——它是来自另一张表的主键,本质上是一个引用或指针:city_id = 1即引用 Tokyo。
补充说明:本课中 "id" 与 "primary key" 互换使用。这些概念同样适用于后续课程将遇到的 PandasDataFrame——虽然 DataFrame 不使用 "primary key" 这一术语,但行为上非常相似。
1.4 SQL 检索:SELECT 与 WHERE
数据拆分到多张表后,需要用结构化查询语言(SQL)把它们取回来。SQL 是关系数据库(MySQL、SQL Server、Oracle 等)的标准检索与修改语言。
最基础的是SELECT ... FROM ...:SELECT列出想看的列,FROM列出来源表。显示全部城市名:
SELECT city FROM cities; -- Output: -- Tokyo -- Atlanta -- AucklandSQL 语法不区分大小写,
select与SELECT等价;但表名、列名是否区分大小写取决于具体数据库。最佳实践是把所有内容都当作区分大小写来对待,且约定俗成将 SQL 关键字全部大写。
加WHERE子句实现过滤:
SELECT city FROM cities WHERE country = 'New Zealand'; -- Output: -- Auckland1.5 多表 JOIN:把数据缝起来
要把cities与rainfall两张表的数据合并,需要执行JOIN:在两张表之间建立一条"接缝",将两表中对应列的值匹配起来。本示例以rainfall.city_id匹配cities.city_id,采用的连接类型是inner join——任何在另一张表中没有匹配行的记录都不会显示。由于每个城市都有降雨数据,因此全部行都会返回。
分两步检索 2019 年所有城市的降雨量。第一步先连接两张表:
SELECT cities.city, rainfall.amount FROM cities INNER JOIN rainfall ON cities.city_id = rainfall.city_id第二步加上WHERE过滤出 2019 年:
SELECT cities.city, rainfall.amount FROM cities INNER JOIN rainfall ON cities.city_id = rainfall.city_id WHERE rainfall.year = 2019 -- Output -- city | amount -- -------- | ------ -- Tokyo | 1874 -- Atlanta | 1111 -- Auckland | 942小结:关系数据库的核心思想是把信息分散到多张表中,需要展示或分析时再通过连接把它们合并回来,从而在计算与数据操作上获得高度灵活性。
1.6 实战作业:用 SQLite 查询机场数据
仓库在 2-Working-With-Data/05-relational-databases/airports.db 提供了一个基于 SQLite 的机场数据库文件,配套作业见 assignment.md。该库包含两张表:
| Cities |
|---|
| id (PK, integer) |
| city (text) |
| country (text) |
| Airports |
|---|
| id (PK, integer) |
| name (text) |
| code (text) |
| city_id (FK to id inCities) |
由于一些城市可能有多个机场,所以拆成两张表,练习的核心正是 JOIN。可在 Visual Studio Code 中安装 SQLite 扩展,通过SQLite: Open database打开airports.db,用SQLite: New query新建查询窗口,以Ctl-Shift-Q(Mac 为Cmd-Shift-Q)执行 SQL。需要完成的查询包括:查询Cities表全部城市名、查询爱尔兰全部城市、查询全部机场名称及其所在城市与国家、查询英国伦敦的全部机场。这套练习能把上述主外键与 JOIN 知识直接落地。
二、非关系数据库:电子表格与四类 NoSQL
数据并不局限于关系数据库。本节聚焦非关系数据,覆盖电子表格基础和 NoSQL 两大主题。
2.1 电子表格:工作簿、工作表与单元格
电子表格因上手门槛低而广受欢迎。以 Microsoft Excel 为例,一个工作簿(workbook)包含一个或多个工作表(worksheet),工作表由标签页标识;表内的矩形称为单元格(cell),即行与列的交叉点——列用字母标记、行用数字标记,部分表格会在前几行放表头来描述数据含义。
仓库中提供了两个真实范例文件:
- CocaColaCo.xlsx:用于"Soda Profits"作业,要求计算 FY'15~'18 的毛利润(Gross Profit = Net Operating revenues − Cost of goods sold),并用AVERAGE 函数计算全部毛利润的平均值;
- InventoryExample.xltm:一个含 "Inventory List"、"Inventory Pick List"、"Bin Lookup" 三个工作表的库存示例,用于讲解公式与函数——**公式(formula)**以等号开头对单元格数据执行运算(如用 QTY × COST 计算库存价值),**函数(function)**则是预定义公式(如
SUM),需要按特定顺序传入参数才能得到正确结果。
2.2 NoSQL 四大家族
NoSQL 是非关系数据存储方式的统称,可理解为 "non-SQL"、"non-relational" 或 "not only SQL"。常见分类有四类:
- 键值存储(Key-value):将唯一键与对应值配对,底层通常借助哈希表与合适的哈希函数实现;
- 图数据库(Graph):用节点(node)与边(edge)描述数据之间的关系——节点代表现实世界中的实体(如学生、银行账单),边代表实体间的关系,节点与边都可带属性;
- 列族存储(Columnar):与关系结构类似按行列组织,但每一列被划分为列族(column family),同一列族下的数据作为整体被检索和更新;
- 文档存储(Document):在键值存储概念上扩展,由一系列字段(field)与对象值(object value)组成,下一节将详细展开。
2.3 文档数据库实战:Cosmos DB Emulator
Cosmos DB 完全符合 "Not Only SQL" 的定义——它的文档数据库恰恰依赖 SQL 查询数据,因此上一节学到的 SQL 基础可以直接迁移使用。其Emulator允许在本地创建和探索文档数据库。
文档是字段与对象值的集合,字段描述对象值代表什么。一个典型文档如下(示例数据保存在 PersonsData.json,以及从 Twitter API 抓取后存入的推文数据 TwitterData.json):
{ "firstname": "Eva", "age": 44, "id": "8c74a315-aebf-4a16-bb38-2430a9896ce5", "_rid": "bHwDAPQz8s0BAAAAAAAAAA==", "_self": "dbs/bHwDAA==/colls/bHwDAPQz8s0=/docs/bHwDAPQz8s0BAAAAAAAAAA==/", "_etag": "\"00000000-0000-0000-9f95-010a691e01d7\"", "_attachments": "attachments/", "_ts": 1630544034 }我们关注的是firstname、id、age这些业务字段,其余带下划线的字段由 Cosmos DB 自动生成。
Emulator 操作要点:启动后点击 "Start with Sample" 生成名为 SampleDB 的示例库,其中含Persons容器(container,存放文档集合),可在 Items 下浏览四个文档;点击 SQL Query 按钮即可运行查询——SELECT * FROM c返回容器内全部文档,SELECT * FROM c where c.age < 40返回年龄小于 40 的文档。这与关系数据库的 SQL 体验几乎一致,只是作用对象从表变成了文档集合。
挑战任务:将 TwitterData.json 上传到一个新建的独立容器(新容器按钮 → 选择 SampleDB → 设置 partition key 为/id→ 用Upload Item上传),再尝试用LIKE关键字找出text字段包含 "Microsoft" 的文档。
大多数 API 返回的 JSON 数据可以直接存入文档数据库,这正是文档模型在现代数据工程中被广泛使用的原因。
三、使用 Python 与 Pandas 处理数据
数据库能高效存储和查询,但最灵活的数据处理方式是编写自己的程序。数据科学中最常用的语言是Python(通用、易上手、库生态庞大)、R(专为统计处理设计的工具箱)与Julia(面向高性能科学计算)。本节聚焦 Python。
3.1 核心库与导入方式
处理表格数据最常用的两个库:
- Pandas:操作DataFrame(类似关系表,具命名列,可对行、列与整体做丰富操作);
- Numpy:处理张量(多维数组),类型统一、数学操作更多、开销更小。
此外还有可视化库Matplotlib和科学计算库SciPy。标准导入写法:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy import ... # 按需指定所需子包3.2 Series:带索引的值序列
Series是一系列值,与列表或 numpy 数组类似,但带有索引(index)——运算时会考虑索引对齐。索引可以是默认的整数行号,也可以是日期区间等复杂结构。完整代码见配套笔记本 notebook.ipynb。
以冰淇淋店日销量分析为例,先生成时间序列:
start_date = "Jan 1, 2020" end_date = "Mar 31, 2020" idx = pd.date_range(start_date, end_date) print(f"Length of index is {len(idx)}") items_sold = pd.Series(np.random.randint(25, 50, size=len(idx)), index=idx) items_sold.plot()每周派对额外准备 10 盒冰淇淋,用周频索引构建另一个 Series:
additional_items = pd.Series(10, index=pd.date_range(start_date, end_date, freq="W"))两个 Series 相加时必须用add(..., fill_value=0)而不是+:
total_items = items_sold.add(additional_items, fill_value=0) total_items.plot()原因在于:additional_items在多数索引点上没有值(缺失值),直接用+会得到大量NaN(Not a Number)——任何数与NaN相加结果都是NaN。
时间序列还可以按不同时间间隔重采样(resample),例如按月计算平均销量:
monthly = total_items.resample("1M").mean() ax = monthly.plot(kind='bar')3.3 DataFrame:同索引 Series 的集合
DataFrame 本质上是共享同一索引的一组 Series。既可以把 Series 作为行合并(pd.DataFrame([a, b])),也可以作为列并用字典命名(pd.DataFrame({'A': a, 'B': b})),还可以转置得到相同布局:
df = pd.DataFrame([a, b]).T.rename(columns={0: 'A', 1: 'B'})其中.T表示转置(行列互换),rename用于重命名列。基于此 DataFrame,本课演示了 Pandas 最核心的几类操作:
- 列选择:
df['A']返回一个 Series;df[['B','A']]返回列子集的 DataFrame; - 行过滤:
df[df['A'] > 5]仅保留满足条件的行。其机制是df['A'] > 5返回布尔 Series,再用布尔 Series 作索引筛选。注意不能写df[df['A']>5 and df['A']<7],必须用&运算符并加括号:df[(df['A']>5) & (df['A']<7)]; - 新建计算列:
df['DivA'] = df['A'] - df['A'].mean()计算 A 相对均值的偏离。但直接df['LenB'] = len(df['B'])是错误写法——它把整个 Series 的长度赋给了所有行,正确做法是使用apply:df['LenB'] = df['B'].apply(lambda x: len(x)) # 或 df['LenB'] = df['B'].apply(len) - 按序号选行:
df.iloc[:5]取前 5 行; - 分组聚合:类似 Excel 透视表。按
LenB分组求均值:df.groupby(by='LenB')[['A', 'DivA']].mean()同时求组内个数与均值,可用
aggregate:df.groupby(by='LenB') \ .aggregate({'DivA': len, 'A': lambda x: x.mean()}) \ .rename(columns={'DivA': 'Count', 'A': 'Mean'})
3.4 读取数据、打印与绘图
Pandas 读取 CSV 只需一行:df = pd.read_csv('file.csv')。探索大 DataFrame 时常用df.head()查看前几行(在 Jupyter Notebook 中会以表格形式美观呈现);plot函数可通过kind=参数支持多种图形类型,更复杂的图形则可直接使用 Matplotlib。
3.5 实战挑战:分析 COVID 数据
挑战 1——COVID 传播建模:使用约翰斯·霍普金斯大学 CSSE 提供的各国感染人数数据,完整示例见 notebook-covidspread.ipynb,从顶部逐格阅读并执行。配套作业 assignment.md 要求进一步:绘制 5~6 个国家的有效传染数 Rₜ 对比图、分析死亡/治愈与感染数的相关性、通过视觉对比推断病程时长、计算病死率及其随时间的变化。
挑战 2——COVID 论文分析:CORD-19 数据集收录了数千篇 COVID 论文(含元数据、摘要,约半数有全文)。简化版分析示例见 notebook-papers.ipynb。作业要求构建药物共现矩阵并热力图可视化,进阶目标是用正则表达式抽取剂量(如 "take 400mg of chloroquine daily" 中的400mg)并构建药物-剂量 DataFrame。
非结构化数据:文本和图像这类非结构化数据,需要通过"提取"步骤转成结构化形式(如从文本提取关键词、用神经网络识别图像中的对象、从视频流获取人物情绪),再套用前述 Pandas 处理流程。
最有用的建议:当你不确定如何用 Python 完成某类数据处理时,先在互联网上搜索——Stack Overflow 上通常有大量针对常见任务的现成代码片段。
四、数据准备:让"脏数据"变得可分析
原始数据往往存在不一致,会干扰分析与建模,即所谓的"脏数据"。本节的完整演示见 notebook.ipynb。
4.1 为什么清洗如此重要
- 易用与可复用:数据组织规范、标准化后,更易检索、使用和分享;
- 一致性:数据科学常需合并多个来源的数据集,统一各数据集的标准化规范才能保证合并后仍然可用;
- 模型准确性:清洗后的数据能提升依赖它的模型的准确率。
常见的清洗目标包括:探索数据集(用查询、可视化、抽样发现需清理之处)、格式化(解决空白字符、日期、数据类型等不一致,不同国家对日期和数字的表示标准可能不同)、去重(重复会带来不准确结果,但合并数据集中的重复也可能携带额外信息需保留)、以及缺失数据处理。
4.2 快速摸清 DataFrame:info / head / tail
面对 6 万行 400 列的数据,先用 Pandas 内建工具快速掌握整体情况。本课用经典的鸢尾花(Iris)数据集演示:
import pandas as pd from sklearn.datasets import load_iris iris = load_iris() iris_df = pd.DataFrame(data=iris['data'], columns=iris['feature_names'])iris_df.info()打印内容摘要:150 个条目、4 列、无空值、全部为 float64 类型;iris_df.head()查看前 5 行;iris_df.tail()查看最后 5 行。
仅凭这些元信息就能立刻对数据集的规模、形状与内容形成直观印象。
4.3 处理缺失数据
Pandas 用两种值表示缺失:NaN(IEEE 浮点规范中的特殊值,仅表示缺失的浮点值)和 Python 的None(用于非浮点缺失值)。两者各有限制,但合起来覆盖了绝大多数场景。
检测缺失值:isnull()与notnull()返回布尔掩码:
import numpy as np example1 = pd.Series([0, np.nan, '', None]) example1.isnull() # 0 False # 1 True # 2 False # 3 True注意:0是合法整数不算缺失,''是字符串对象也不算缺失。布尔掩码可以直接作为 Series/DataFrame 的索引来定位缺失(或存在)的值。
删除缺失值:dropna()直接移除缺失值。对 Series:
example1 = example1.dropna()DataFrame 有两个维度,删除方式更丰富。默认dropna()删除包含任一空值的整行;dropna(axis='columns')删除含空值的整列;how参数控制删除条件(默认how='any',可设how='all'只删全空的行/列);thresh参数则精细控制——保留非空值数量达到阈值的行/列:
example2.dropna(axis='rows', thresh=3)填充缺失值:fillna()返回缺失值被替换后的副本。可以填统一值(如0)、前向填充method='ffill'(用上一个有效值填充)、后向填充method='bfill'(用下一个有效值回填),DataFrame 还可指定axis方向。注意前向填充时若前方无可用值,空值会保留:
example3.fillna(0) example3.fillna(method='ffill') example3.fillna(method='bfill') example2.fillna(method='ffill', axis=1)取用哪种策略(删除、替换、如何替换)应由数据的具体情况决定,没有放之四海皆准的方案。
4.4 去除重复数据
duplicated()返回布尔掩码,标记某行是否为前面行的重复;drop_duplicates()返回删除重复后的副本。两者默认考虑全部列,也可指定列子集:
example4 = pd.DataFrame({'letters': ['A','B'] * 2 + ['B'], 'numbers': [1, 2, 1, 3, 3]}) example4.duplicated() # 0 False # 1 False # 2 True # 3 False # 4 True example4.drop_duplicates() example4.drop_duplicates(['letters'])重复数据会改变分析结果并带来不准确结论,去除重复是几乎所有数据科学项目的必经步骤。
实战作业:仓库提供了一份表单收集的 CSV 记录 data/form.csv 及其可视化问题描述,作业见 assignment.md,可配合 assignment.ipynb 与表单页面 index.html 实践,运用本节技术诊断表单数据采集的准确性与一致性。
五、章节配套资源与学习路径建议
围绕第二章,仓库还提供了可直接复用的数据与延伸材料:
- 示例数据:除上述文件外,data/ 目录还汇集了 COVID 时间序列、鸟类观测、糖尿病、蜂蜜、蘑菇、出租车等 CSV/TSV 数据集,可配合各节练习使用;
- 手绘笔记(Sketchnotes):sketchnotes/05-RelationalData.png、sketchnotes/06-NoSQL.png、sketchnotes/07-WorkWithPython.png、sketchnotes/08-DataPreparation.png 为四节课提供了可快速回顾的可视化笔记;
- 进阶路径:第四章「4-Data-Science-Lifecycle/15-analyzing」的数据探索课程与本章的数据准备互为补充,建议按章节顺序学习,先建立数据存储与操作的基础,再进入分析与建模环节。
结语
从关系数据库的表建模、主外键与 JOIN,到 NoSQL 的电子表格与四类文档/键值/图/列族存储,再到 Pandas 的 Series/DataFrame 操作与数据清洗策略,「Working with Data」章节完整覆盖了数据科学工作流中"获取与准备"的关键环节。学完本章,你应该能够:用 SQL 在多表之间检索关联数据、在本地 Emulator 中探索文档数据库、用 Pandas 灵活完成表格数据的变换与可视化,并有策略地处理缺失值与重复数据——这些能力将直接支撑你在后续章节中开展探索性分析与建模。
【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考