
Data Science For Beginners用 Python 与 Pandas 处理数据——Series、DataFrame 与时间序列实战指南【免费下载链接】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 第 7 课展开系统讲解如何用 Python 与 Pandas 处理表格数据tabular data、时间序列、文本与图像等不同形态的数据。你将掌握 Pandas 两大核心数据结构 Series 与 DataFrame 的常用操作索引对齐、过滤、分组、重采样、可视化并跟随两个完整的实战案例——用约翰斯·霍普金斯大学 COVID-19 数据集建模疫情传播含 R₀/R_t 再生数计算以及用 CORD-19 论文元数据做药物-诊断共现分析——理解从数据到洞察的完整处理链路。为什么用 Python 处理数据虽然数据库第 5 课的 SQL 关系型数据库、第 6 课的非关系型数据库提供了高效的存储与查询手段但当你需要的是对数据的理解或洞察——例如分布形态、变量间的相关性、原始数据的变换——数据库查询就不够灵活了。此时最直接的方式是自己写程序处理数据。数据处理可以用任何语言完成但数据科学领域最常用的语言集中在这三种Python通用编程语言因语法简单常被视为最适合入门的选择。除了科学计算Python 还常用于 Web 开发并且拥有大量开箱即用的第三方库如解压 ZIP、图片转灰度图等。R为统计处理量身打造的传统工具拥有庞大的 CRAN 包仓库是数据处理的好选择但它不是通用编程语言很少在数据科学领域之外使用。Julia同样为数据科学而生设计目标是在性能上优于 Python非常适合科学实验。本课聚焦 Python 做简单数据处理并假设读者已有 Python 基础。课程同样提供了 R 语言版本的 notebook 可供对照。数据科学家的建议是遇到不会做的数据操作先去网上搜索Stack Overflow 上几乎总能找到典型的 Python 代码示例。数据有多种形态本课主要覆盖三类表格数据tabular data、文本text与图像images。核心库Pandas 与 Numpy处理表格数据有两个最常用的 Python 库Pandas操作所谓DataFrame它与关系表类似可以有带名字的列并支持对行、列及整个 DataFrame 进行各类操作。Numpy面向**张量tensor即多维数组array**的库。数组内的值类型相同比 DataFrame 更简单但数学运算更丰富、开销更低。另外两个需要知道的库Matplotlib用于数据可视化与绘制图表的库。SciPy包含大量补充科学函数在第 4 课概率与统计中已经遇到过。一个典型的导入方式如下完整代码见 notebook.ipynbimport numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy import ... # 需要指定你要用的具体子包Pandas 建立在几个基础概念之上下面逐一展开。Series带索引的一维序列Series是一组值的序列类似列表或一维 numpy 数组。关键区别在于Series 还带有一个index索引当对 Series 做运算比如相加时索引是对齐的。索引可以简单到整行号从列表或数组创建时的默认索引也可以是复杂结构比如一段日期范围。注意与本节配套的 notebook.ipynb 中有 Pandas 的入门代码这里只展示部分例子建议完整运行一遍 notebook。下面用一个场景演示分析冰淇淋摊的销售数据生成一段时间内每天的销量序列start_date Jan 1, 2020 end_date Mar 31, 2020 idx pd.date_range(start_date, end_date) print(fLength of index is {len(idx)}) items_sold pd.Series(np.random.randint(25, 50, sizelen(idx)), indexidx) items_sold.plot()现在假设每周都要为朋友聚会额外采购 10 盒冰淇淋。可以创建另一条按周索引的 Series 来表示additional_items pd.Series(10, indexpd.date_range(start_date, end_date, freqW))把两条 Series 相加就得到总数total_items items_sold.add(additional_items, fill_value0) total_items.plot()重要细节这里不能直接写total_items additional_items。那样会在结果序列中产生大量NaNNot a Number——因为additional_items在非周日没有值而任何数与NaN相加仍为NaN。所以相加时必须指定fill_value参数。这一索引对齐 NaN 传播是 Pandas 时间序列运算最常见的坑值得牢记。对于时间序列还可以按不同时间间隔重采样resample。例如要计算月均销量monthly total_items.resample(1M).mean() ax monthly.plot(kindbar)在 notebook.ipynb 中resample(1M)配合plot(kindbar)再用ax.set_xticklabels([x.strftime(%b-%Y) for x in monthly.index], rotation45)把横轴格式化为如 Jan-2020 的可读标签。DataFrame同索引 Series 的集合DataFrame本质上是一组拥有相同索引的 Series 的集合。把多个 Series 组合起来即可创建 DataFramea pd.Series(range(1, 10)) b pd.Series([I, like, to, play, games, and, will, not, change], indexrange(0, 9)) df pd.DataFrame([a, b])这会生成一张水平排列的表格行是 Series列为默认序号01234567801234567891IliketousePythonandPandasverymuch也可以把 Series 作为列并用字典指定列名df pd.DataFrame({ A : a, B : b })得到AB01I12like23to34use45Python56and67Pandas78very89much也可以对前一张表做转置得到同样的布局df pd.DataFrame([a, b]).T.rename(columns{ 0 : A, 1 : B })。其中.T表示 DataFrame 转置行列互换rename用于把列名改成与前例一致。DataFrame 常用操作列选择df[A]返回一条 Seriesdf[[B,A]]返回只含这两列的新 DataFrame。按条件过滤行例如只保留A列大于 5 的行写df[df[A] 5]。过滤的原理是df[A] 5先生成一条布尔 Series对每个元素给出 True/False把布尔 Series 当作索引使用时就会返回 DataFrame 中对应的行子集。因此不能直接写 Python 任意布尔表达式比如df[df[A] 5 and df[A] 7]是错的必须用布尔 Series 专用的运算符并加括号df[(df[A] 5) (df[A] 7)]。创建可计算的列用直观的表达式即可df[DivA] df[A] - df[A].mean()这里计算的是A相对其均值的偏离量。底层机制是先算出一条 Series再赋给左侧从而新建一列。因此不能使用对 Series 不成立的操作例如下面两行都是错误示例# 错误代码 - df[ADescr] Low if df[A] 5 else Hi df[LenB] len(df[B]) # - 结果错误第二行语法上没错但结果不对——它把整个 SeriesB的长度赋给了每一行而不是逐个元素的长度。要处理这类逐元素计算的复杂表达式应使用applydf[LenB] df[B].apply(lambda x: len(x)) # 或者直接传函数对象 df[LenB] df[B].apply(len)经过上述操作后得到ABDivALenB01I-4.0112like-3.0423to-2.0234use-1.0345Python0.0656and1.0367Pandas2.0678very3.0489much4.04按行号选择用iloc构造例如取前 5 行df.iloc[:5]**分组groupby**常用来实现类似 Excel 透视表的效果。例如按LenB分组、对每组的A求均值df.groupby(byLenB)[[A,DivA]].mean()如果同一分组内既要算均值又要计数可以用更复杂的aggregatedf.groupby(byLenB) \ .aggregate({ DivA : len, A : lambda x: x.mean() }) \ .rename(columns{ DivA : Count, A : Mean })得到LenBCountMean111.000000213.000000325.000000436.333333626.000000读取数据与可视化从 Python 对象构造 Series/DataFrame 很容易但真实数据通常来自文本文件或 Excel 表格。Pandas 提供了从磁盘加载数据的简单方式读取 CSV 只需df pd.read_csv(file.csv)数据科学家经常需要探索数据因此可视化很重要。当 DataFrame 很大时先用df.head()查看前几行确认一切正常——在 Jupyter Notebook 中会以美观的表格形式呈现。plot函数非常实用通过kind参数支持多种图表类型需要更复杂的效果时可以直接用 matplotlib 底层 API。关于可视化课程后续有专门章节系统讲解。挑战 1分析 COVID-19 的传播第一个实战是建模 COVID-19 疫情的传播。数据来自约翰斯·霍普金斯大学 CSSE 目录下已有三份本地副本time_series_covid19_confirmed_global.csv确诊、time_series_covid19_deaths_global.csv死亡、time_series_covid19_recovered_global.csv康复另有国家/地区信息表 UID_ISO_FIPS_LookUp_Table.csv含各国人口数据。请打开 notebook-covidspread.ipynb 从头到尾阅读最好逐格运行并完成文末留给你的挑战。下面是该 notebook 的完整分析流水线。加载数据可直接从网络加载最新数据或改用仓库本地副本import numpy as np import pandas as pd import matplotlib.pyplot as plt plt.rcParams[figure.figsize] (10, 3) # 放大图表 base_url https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/ # 从网络加载 # base_url data/COVID/ # 从本地磁盘加载 infected_dataset_url base_url time_series_covid19_confirmed_global.csv recovered_dataset_url base_url time_series_covid19_recovered_global.csv deaths_dataset_url base_url time_series_covid19_deaths_global.csv infected pd.read_csv(infected_dataset_url) infected.head()可以看到表格中每行代表一个国家/地区或省份的感染人数列对应日期recovered、deaths结构相同。Province/State列中澳大利亚、中国等国有更细的省级拆分。预处理本分析不关心省级拆分因此用groupby(Country/Region).sum()把各省数据汇总到国家层面infected infected.groupby(Country/Region).sum() recovered recovered.groupby(Country/Region).sum() deaths deaths.groupby(Country/Region).sum()分组后 DataFrame 以Country/Region为索引可用.loc访问具体国家infected.loc[US][3:].plot()。其中[3:]用于去掉序列开头三个非日期元数据列Province/State、Lat、Long也可以直接用infected.drop(columns[Lat,Long,Province/State], inplaceTrue)永久删除。构造国家级分析框架def mkframe(country): df pd.DataFrame({ infected : infected.loc[country], recovered : recovered.loc[country], deaths : deaths.loc[country]}) df.index pd.to_datetime(df.index) return df df mkframe(US) df.plot()计算每日新增用diff得到每天新增感染人数观察疫情推进速度df[ninfected] df[infected].diff() df[ninfected].plot()数据波动很大例如 2020 年 7 月呈现出明显的周内周期性这与各州上报习惯有关因此用**滚动平均running average**平滑曲线以观察趋势df[ninfav] df[ninfected].rolling(window7).mean() df[ninfav].plot()按人口归一化为了可比把感染数折算成占人口的百分比。从国家数据表提取人口注意该表同时含国家与省份行需用Province_State为空筛选出国家级记录countries pd.read_csv(countries_dataset_url) pop countries[(countries[Country_Region]US) countries[Province_State].isna()][Population].iloc[0] df[pinfected] df[infected] * 100 / pop df[pinfected].plot()计算再生数 R_t疫情传染性用基本再生数 R₀刻画表示一个感染者平均再感染多少人R₀ 1 时疫情倾向于扩散。R₀ 是疾病本身的属性不包含防护措施的影响。在疫情进行中可用 8 天窗口对 R_t 做近似估计$$R_t\frac{I_{t-7}I_{t-6}I_{t-5}I_{t-4}}{I_{t-3}I_{t-2}I_{t-1}I_t}$$其中 $I_t$ 是第 t 天的新增感染人数。Pandas 实现如下df[Rt] df[ninfected].rolling(8).apply(lambda x: x[4:].sum()/x[:4].sum()) df[Rt].plot()图中会出现空隙可能由NaN数据缺失或窗口宽度不足或inf除零造成。可用replace与fillna填充ax df[df.index2020-05-01][Rt].replace(np.inf, np.nan).fillna(methodpad).plot() ax.set_ylim([0, 6]) ax.axhline(1, linestyle--, colorred) # 画出 R_t1 参考线 plt.show()**导数每日差分**也是观察疫情增减的有效指标df[ninfected].diff().plot() # 直接看每日变化 df[df.index2020-06-01][ninfected].diff().rolling(7).mean().plot() # 平滑后观察如果不知道如何在 GitHub 上运行 notebook可参考相关教程文章。挑战 2分析 COVID-19 论文非结构化文本处理第二个挑战延续疫情主题聚焦科学论文文本处理。使用的是 CORD-19 数据集包含超过 7000 篇成文时COVID 相关论文提供元数据与摘要约一半还有全文。注意仓库不包含该数据集副本。需要先从 Kaggle 下载 metadata.csv可能需要注册也可以免注册从数据集历史版本站点下载但那会额外包含全部全文。打开 notebook-papers.ipynb 通读并运行。核心思路是从非结构化文本中提取结构化数据再用之前学到的 Pandas 技术分析。完整流水线如下。获取数据df pd.read_csv(https://datascience4beginners.blob.core.windows.net/cord/metadata.csv.zip, compressionzip) # df pd.read_csv(metadata.csv) df.head() df[publish_time] pd.to_datetime(df[publish_time]) df[publish_time].hist() # 绘制发表时间直方图甚至能看到 1880 年的冠状病毒论文关键词计数提取手工整理一份可能的 COVID 治疗药物清单与诊断词清单然后在摘要中逐一搜索计数medications [ hydroxychloroquine, chloroquine, tocilizumab, remdesivir, azithromycin, lopinavir, ritonavir, dexamethasone, heparin, favipiravir, methylprednisolone] diagnosis [ covid, sars, pneumonia, infection, diabetes, coronavirus, death] for m in medications: df[m] df[abstract].apply(lambda x: str(x).lower().count( m)) for m in diagnosis: df[m] df[abstract].apply(lambda x: str(x).lower().count( m))技巧搜索子串时在单词前加一个空格。如果不加chloroquine会错误地匹配到hydroxychloroquine内部。另外对abstract强制str转换可以避免空值报错——可以试试去掉str会发生什么。统计最常用药物抽出仅含药物计数的子表并求和排序dfm df[medications].sum().reset_index().rename(columns{ index : Name, 0 : Count }) dfm.sort_values(Count, ascendingFalse) dfm.set_index(Name).plot(kindbar)治疗策略的时间趋势按月汇总各药物出现次数并可视化dfm df[[publish_time] medications].set_index(publish_time) dfm dfm[(dfm.index 2020-01-01) (dfm.index 2021-07-31)] dfmt dfm.groupby([dfm.index.year, dfm.index.month]).sum() dfmt.plot()图中 2020 年 1 月和 2021 年 1 月会出现巨大尖峰——因为部分论文没有明确发表日期而被归到当年 1 月。可以抹去这两个月并用前向填充补值让曲线更平滑dfmt.loc[(2020, 1)] np.nan dfmt.loc[(2021, 1)] np.nan dfmt.fillna(methodpad, inplaceTrue)还能用 Pandas 的**面积图stack/area plot**观察各药物的相对热度甚至按行归一化成百分比dfmt.plot.area() # 堆叠面积图 dfmtp dfmt.apply(lambda x: x / x.sum(), axis1) # 计算相对占比 dfmtp.plot.area() # 百分比面积图药物-诊断共现矩阵最有价值的分析之一是探究什么诊断用什么药。为此计算共现频率矩阵——统计两个词在同一篇论文摘要中同时出现的次数。该矩阵本质上是二维矩阵用 numpy 数组表示最合适m np.zeros((len(medications), len(diagnosis))) for a in df[abstract]: x str(a).lower() for i, d in enumerate(diagnosis): if d in x: for j, me in enumerate(medications): if me in x: m[j, i] 1用热力图可视化plt.imshow(m, interpolationnearest, cmaphot) ax plt.gca() ax.set_yticks(range(len(medications))) ax.set_yticklabels(medications) ax.set_xticks(range(len(diagnosis))) ax.set_xticklabels(diagnosis, rotation90) plt.show()更直观的展示是用Sankey 图matplotlib 内置不支持需用 Plotly构建药物→诊断的节点与链接链接强度即共现矩阵的值并用go.Sankey绘制import plotly.graph_objects as go def sankey(cat1, cat2, m, treshold0, h1[], h2[]): all_nodes cat1 cat2 source_indices list(range(len(cat1))) target_indices list(range(len(cat1), len(cat1) len(cat2))) s, t, v, c [], [], [], [] for i in range(len(cat1)): for j in range(len(cat2)): if m[i, j] treshold: s.append(i); t.append(len(cat1) j) v.append(m[i, j]) c.append(pink if i in h1 or j in h2 else lightgray) fig go.Figure(data[go.Sankey( nodedict(pad40, thickness40, linedict(colorblack, width1.0), labelall_nodes), linkdict(sources, targett, valuev, colorc))]) fig.show() sankey(medications, diagnosis, m, 500, h2[0])这个例子展示的还只是预定义关键词列表式的提取更强大的做法是用 NLP自然语言处理做实体抽取例如 Azure Text Analytics for Health 云服务或 Python 的 NLTK 库NLTK 抽取文本信息的方法见其官方书籍第 7 章。图像数据调用预训练模型与云服务近年来出现了非常强大的图像理解 AI 模型很多任务可以借助预训练神经网络或云服务完成例如图像分类把图像归入预定义类别。可用 Custom Vision 等服务轻松训练自己的分类器。目标检测检测图像中的不同物体。Computer Vision 服务可识别一批常见物体也可用 Custom Vision 训练特定目标检测模型。人脸检测包括年龄、性别、情绪识别可通过 Face API 完成。这些云服务都能通过官方 Python SDK 调用从而无缝集成进数据探索工作流。两个已公开的探索案例一个是不写代码学数据科学的博客——用 Computer Vision 提取 Instagram 照片特征、再用 Azure Machine Learning AutoML 构建可解释模型研究什么样的照片能获得更多赞另一个是 FaceStudies 工作坊——用 Face API 从活动照片中提取人物情绪试图理解什么让人开心。课后作业与自测围绕两个挑战assignment.md 提供了深化练习COVID-19 传播建模在一张图上或并排多图绘制 56 个国家的 R_t 曲线对比。分析死亡数、康复数与感染数的相关性。通过目视对照感染率与死亡率曲线寻找异常推断典型病程时长可能需要比较多个国家。计算病死率及其随时间的变化可先按病程天数对时间序列做平移再计算。COVID-19 论文分析构建药物之间的共现矩阵可参照药物-诊断共现矩阵的代码改写观察哪些药物常在同一篇摘要中出现。用热力图可视化该矩阵。进阶目标用 chord diagram弦图可借助chord库可视化药物共现。另一进阶目标用正则表达式从take 400mg of chloroquine daily这类句子中提取药物剂量如400mg构建药物-剂量DataFrame注意把数字视为与药名在文本上邻近的值。评分标准全部完成且配有图示与解释、并至少完成一个进阶目标为优秀完成 5 项以上但未尝试进阶目标或结果不清晰为合格完成 35 项且可视化对结论无帮助为需改进。结论与延伸学习无论数据是结构化还是非结构化Python 都能覆盖数据处理与理解的全部环节。这正是大多数数据科学家把 Python 作为主力的原因——深入学习 Python 对数据科学之路很有价值。进一步学习资源Wes McKinney《Python for Data Analysis: Data Wrangling with Pandas, NumPy, and IPython》Pandas 官方教程 10 minutes to Pandas 与 Pandas 可视化文档通过 Turtle Graphics 与分形趣味学习 Python 的入门课程或 Microsoft Learn 上的Python 第一步学习路径提示本文档由机器翻译自英文原版原版为权威来源个别措辞可能略有出入关键信息请以仓库内英文原文为准。【免费下载链接】Data-Science-For-Beginners10 Weeks, 20 Lessons, Data Science for All!项目地址: https://gitcode.com/GitHub_Trending/da/Data-Science-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考