Jupyter笔记本是数据科学界最具争议的工具之一。有一些直言不讳的批评者,也有热情的粉丝。尽管如此,许多数据科学家都同意,如果使用得当,它们可以非常有价值。这就是我们在本文中要重点关注的内容,本文是我关于数据科学和机器学习工程的软件模式系列文章的第二篇。我将向您展示在探索性数据分析中使用Jupyter笔记本的最佳实践。
但首先,我们需要了解为什么笔记本在科学界应运而生。在数据科学很性感之前,笔记本并不存在。在此之前,我们有IPython,它被集成到IDE(如Spyder)中,试图模仿RStudio或Matlab的工作方式。这些工具在研究人员中得到了广泛的应用。
2014年,IPython发展成为Jupyter项目。由于科研人员纷纷转向工业界工作,Jupyter的使用量迅速增加。然而,适用于科学项目的笔记本使用方法不一定适用于企业的业务和产品部门进行的分析。大学毕业后刚入职的数据科学家在结构和展示分析结果方面往往会遇到新的期望,并可能感到困难。
在本文中,我们将从业务和产品的角度谈论Jupyter笔记本。正如我之前提到的,Jupyter笔记本是一个具有争议性的话题,所以让我们直接进入我的观点。
Jupyter笔记本应该仅用于纯粹的探索性任务或特定分析。
笔记本只应该是一份报告。它所包含的代码并不重要。只有它生成的结果才重要。理想情况下,我们应该能够隐藏笔记本中的代码,因为它只是回答问题的手段。
例如:这些表的统计特征是什么?这个训练数据集的特性是什么?将该模型投入生产的影响是什么?我们如何确保该模型优于以前的模型?这个A/B测试的表现如何?
![如何使用探索性笔记本【最佳实践】 四海 第1张-四海吧 Jupyter笔记本在不同领域和不同目的下都很有用](https://ai.miximages.com/i0.wp.com/neptune.ai/wp-content/uploads/2023/09/Best-practices-for-exploratory-notebooks.png?resize=1920%2C1920&ssl=1)
Jupyter笔记本:有效叙事的准则
编写Jupyter笔记本基本上是一种叙述故事或回答您一直在调查的问题的方式。但这并不意味着您必须展示达到结论所做的明确工作。
笔记本必须精炼。
它们主要是为了让作者理解问题,也是为了让同行们获得知识而无需深入研究问题。
范围
笔记本以非线性和树状的方式探索数据集,通常包含与问题无关的不导致任何答案的探索步骤。这不是笔记本最终的样子。笔记本应包含能最好地回答手头问题的最少内容。您应该对每个假设和结论进行评论和理由。执行摘要总是可取的,因为它们非常适合对话题有模糊兴趣或时间有限的利益相关者。它们也是为同行评审准备全面笔记本深入的好方法。
受众
笔记本的受众通常具有相当高的技术或业务了解能力。因此,您应该使用高级术语。然而,执行摘要或结论应始终用简单的语言编写,并链接到进一步和更深入解释的章节。如果您发现自己在为非技术受众创建笔记本时感到困难,也许您可以考虑创建一份幻灯片。在那里,您可以使用信息图表、自定义可视化和更广泛的方式来解释您的想法。
![如何使用探索性笔记本【最佳实践】 四海 第2张-四海吧 数据科学家的各个利益相关者具有不同的需求](https://ai.miximages.com/i0.wp.com/neptune.ai/wp-content/uploads/2023/10/The-different-stakeholders-of-a-data-scientist-all-have-different-demands.png?resize=1800%2C1800&ssl=1)
背景
始终为手头的问题提供背景。单独的数据并不能构建一个连贯的故事。我们必须将整个分析框架置于我们所工作的领域中,以便观众在阅读时感到舒适。使用链接到公司现有的知识库来支持您的陈述,并在笔记本的专用部分中收集所有引用。
如何构建Jupyter笔记本的内容
在本节中,我将解释我通常使用的笔记本布局。它可能看起来很繁琐,但我建议创建一个带有以下部分的笔记本模板,留出特定任务的占位符。这样的定制模板将为您节省大量时间,并确保笔记本之间的一致性。
- 标题:理想情况下,与任务相关的JIRA任务的名称(或任何其他问题追踪软件)与任务相关联。这样可以使您和观众将答案(笔记本)与问题(JIRA任务)无歧义地联系起来。
- 描述:在这个任务中你想要达到什么目的?这应该非常简洁。
- 目录:条目应链接到笔记本的各个部分,使读者可以跳转到他们感兴趣的部分。(Jupyter会为每个标题创建HTML锚点,这些锚点通过headline.lower().replace(” “, “-“)从原始标题派生而来,所以您可以使用纯粹的Markdown链接,例如[section title](#section-title)进行链接。您也可以通过在markdown单元格中添加<a id=’your-anchor’></a>来放置自己的锚点。)
- 参考资料:链接到内部或外部文档,其中包含背景信息或分析笔记本中使用的特定信息。
- TL;DR或总结:简洁地解释整个探索的结果,并强调您得出的关键结论(或问题)。
- 介绍和背景:将任务放入上下文中,添加有关问题周围的关键业务先例的信息,并对任务进行更详细的解释。
- 导入:库的导入和设置。配置第三方库(如matplotlib或seaborn)的设置。添加诸如日期之类的环境变量以修复探索窗口。
- 需要探索的数据:概述您正在探索/分析的表格或数据集,并引用它们的来源或链接其数据目录条目。理想情况下,您应该披露每个数据集或表格是如何创建以及更新频率。您可以将此部分链接到任何其他文档。
- 分析单元格
- 结论:对在分析部分得到的关键结果进行详细解释,并链接到读者可以找到进一步解释的笔记本的特定部分。
记住始终使用Markdown格式对标题进行编排,并强调重要的陈述和引用。您可以在Markdown Cells — Jupyter Notebook 6.5.2 documentation中检查不同的Markdown语法选项。
![如何使用探索性笔记本【最佳实践】 四海 第3张-四海吧 Example template for an exploratory notebook](https://ai.miximages.com/i0.wp.com/neptune.ai/wp-content/uploads/2023/10/how-to-us-exploratory-notebooks-3.png?resize=1448%2C814&ssl=1)
如何在Jupyter笔记本中组织代码
对于探索性任务,生成SQL查询、pandas数据处理或创建图表的代码对于读者来说并不重要。
但对于审核者来说,这仍然很重要,所以我们应该保持高质量和可读性。
我在笔记本中使用代码的建议如下:
将辅助函数移至普通的Python模块
通常,导入在Python模块中定义的函数要比在笔记本中定义它们更好。首先,在.py文件中的Git差异要比笔记本中的差异更容易阅读。阅读笔记本的读者也不需要知道函数在内部是如何工作的。
例如,通常您会有用于读取数据、运行SQL查询以及预处理、转换或丰富数据集的函数。所有这些函数都应该移至.py文件中,然后在笔记本中导入,这样读者只能看到函数调用。如果审核者需要更多细节,他们始终可以直接查看Python模块。
我发现这对于绘制函数特别有用,例如。通常情况下,我可以在笔记本中多次重用相同的函数来制作条形图。我需要进行一些小的更改,例如使用不同的数据集或不同的标题,但是整体的绘图布局和样式将保持不变。我不再需要复制和粘贴相同的代码片段,只需创建一个 utils/plots.py 模块,并创建可以通过提供参数导入和适应的函数。
以下是一个非常简单的示例:
import matplotlib.pyplot as plt import numpy as np def create_barplot(data, x_labels, title='', xlabel='', ylabel='', bar_color='b', bar_width=0.8, style='seaborn', figsize=(8, 6)): """使用Matplotlib创建可自定义的条形图。 参数: - 数据:要绘制的数据的列表或数组。 - x_labels:x轴的标签列表。 - title:图的标题。 - xlabel:x轴的标签。 - ylabel:y轴的标签。 - bar_color:条形的颜色(默认为蓝色)。 - bar_width:条形的宽度(默认为0.8)。 - style:要应用的Matplotlib样式(例如,“seaborn”,“ggplot”,“default”)。 - figsize:指定图的尺寸(宽度,高度)的元组。 返回: - 无 """ # 设置Matplotlib样式 plt.style.use(style) # 创建图和轴 fig, ax = plt.subplots(figsize=figsize) # 生成条形的x位置 x = np.arange(len(data)) # 创建条形图 ax.bar(x, data, color=bar_color, width=bar_width) # 设置x轴标签 ax.set_xticks(x) ax.set_xticklabels(x_labels) # 设置标签和标题 ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) ax.set_title(title) # 显示图 plt.show() # 在笔记本单元格中使用示例: create_barplot( data, x_labels, title="可自定义的条形图", xlabel="类别", ylabel="值", bar_color="天蓝色", bar_width=0.6, style="seaborn", figsize=(10,6) )在创建这些Python模块时,请记住代码仍然属于探索性分析的一部分。因此,除非您在项目的其他部分中使用它,否则不需要完美。只需足够可读和理解,以便供审查人员使用。
将绘图函数、数据加载、数据准备以及评估指标的实现放在纯Python模块中,可以使Jupyter notebook专注于探索性分析 | 来源:作者 在Jupyter单元格中直接使用SQL
有些情况下,数据不在内存中(例如pandas DataFrame),而是在公司的数据仓库中(例如Redshift)。在这些情况下,大部分数据探索和数据整理都将通过SQL完成。
在Jupyter笔记本中使用SQL的方式有几种。 JupySQL 可以让您在笔记本单元格中直接编写SQL代码,并将查询结果显示为类似于pandas DataFrame的形式。您还可以将SQL脚本存储在附带文件中,或者存储在我们在前一节中讨论的辅助Python模块中。
使用其中一种方式还是另一种方式,主要取决于您的目标:
如果您正在从数据仓库中的多个表进行数据探索,并且想要向同事展示数据的质量和有效性,那么在笔记本中显示SQL查询通常是最佳选择。您的审稿人将欣赏到他们可以直接看到您如何查询这些表格,您必须进行哪些连接以获得某些视图,您需要应用哪些过滤器等。
然而,如果您只是生成数据集以验证机器学习模型,并且笔记本的主要重点是显示不同的指标和可解释性输出,那么我建议尽可能隐藏数据集提取,并将查询保留在单独的SQL脚本或Python模块中。
现在让我们看一个如何同时使用这两个选项的示例。
从.sql脚本中读取和执行
我们可以使用通过数据库连接器库在笔记本中打开和执行的.sql文件。
假设我们在一个名为select_purchases.sql的文件中有以下查询:
SELECT * FROM public.ecommerce_purchases WHERE product_id = 123然后,我们可以定义一个执行SQL脚本的函数:
import psycopg2 def execute_sql_script(filename, connection_params): """ 使用psycopg2从文件执行SQL脚本。 参数: - filename:要执行的SQL脚本文件的名称。 - connection_params:包含PostgreSQL连接参数的字典, 例如'host'、'port'、'database'、'user'和'password'。 返回: - 无 """ # 提取连接参数 host = connection_params.get('host', 'localhost') port = connection_params.get('port', '5432') database = connection_params.get('database', '') user = connection_params.get('user', '') password = connection_params.get('password', '') # 建立数据库连接 try: conn = psycopg2.connect( host=host, port=port, database=database, user=user, password=password ) cursor = conn.cursor() # 读取并执行SQL脚本 with open(filename, 'r') as sql_file: sql_script = sql_file.read() cursor.execute(sql_script) # 将结果转换为Pandas DataFrame result = cursor.fetchall() column_names = [desc[0] for desc in cursor.description] df = pd.DataFrame(result, columns=column_names) # 提交更改并关闭连接 conn.commit() conn.close() return df except Exception as e: print(f"错误:{e}") if 'conn' in locals(): conn.rollback() conn.close()请注意,我们为数据库连接参数提供了默认值,这样我们就不必每次都指定它们。但是,请记住不要在您的Python脚本中存储机密信息或其他敏感信息!(在本系列的后续部分,我们将讨论解决此问题的不同解决方案。)
现在,我们可以在笔记本中使用以下一行代码来执行脚本:
df = execute_sql_script('select_purchases.sql', connection_params)使用JupySQL
传统上,ipython-sql一直是从Jupyter笔记本查询SQL的首选工具。但它已经在2023年4月被其原始创建者停用,并推荐切换到JupySQL,这是一个积极维护的分支。未来,所有的改进和新功能只会添加到JupySQL中。
要安装与Redshift一起使用的库,我们需要执行以下操作:
pip install jupysql sqlalchemy-redshift redshift-connector 'sqlalchemy<2'(您也可以将其与其他数据库一起使用,如snowflake或duckdb,)
在Jupyter笔记本中,您现在可以使用%load_ext sql魔术命令启用SQL,并使用以下代码片段创建一个SQLAlchemy Redshift引擎:
from os import environ from sqlalchemy import create_engine from sqlalchemy.engine import URL user = environ["REDSHIFT_USERNAME"] password = environ["REDSHIFT_PASSWORD"] host = environ["REDSHIFT_HOST"] url = URL.create( drivername="redshift+redshift_connector", username=user, password=password, host=host, port=5439, database="dev", ) engine = create_engine(url)接下来,只需要将引擎传递给魔术指令:
%sql engine --alias redshift-sqlalchemy然后你就可以开始了!
现在只需要使用魔术指令,编写任何你想执行的查询语句,结果将会显示在单元格输出中:
%sql SELECT * FROM public.ecommerce_purchases WHERE product_id = 123确保按顺序执行单元格
我建议在将笔记本推送到存储库之前,始终运行所有代码单元格。Jupyter笔记本在执行时保存了每个单元格的输出状态。这意味着你所编写或编辑的代码可能与单元格显示的输出不对应。
从上到下顺序运行笔记本还是一个很好的测试方式,可以检查你的笔记本是否依赖于任何用户输入才能正确执行。理想情况下,一切都应该无需干预地顺利运行。如果不能,你的分析很可能无法被其他人(甚至是你未来的自己)重现。
检查笔记本是否按顺序运行的一种方法是使用nbcheckorder pre-commit钩子。它会检查单元格的输出编号是否连续。如果不连续,则表示笔记本单元格没有按顺序执行,并阻止Git提交。
示例.pre-commit-config.yaml:
- repo: local rev: v0.2.0 hooks: - id: nbcheckorder如果你还没有使用pre-commit,我强烈建议你采用这个小工具。我建议你通过Elliot Jordan的入门指南开始学习。然后,你可以阅读详尽的文档了解其所有功能。
清除单元格的输出
甚至比前面的提示更好的方法是,清除笔记本中所有单元格的输出。一个好处是你可以忽略单元格的状态和输出,但另一方面,它会强制审阅者在本地运行代码才能看到结果。有若干种自动执行此操作的方法。
你可以使用nbstripout结合pre-commit来清除单元格输出,如Florian Rathgeber在GitHub上所解释的那样:
- repo: local rev: 0.6.1 hooks: - id: nbstripout你也可以像Yury Zhauniarovich所解释的那样,在自定义pre-commit钩子中使用nbconvert –ClearOutputpPreprocessor:
- repo: local hooks: - id: jupyter-nb-clear-output name: jupyter-nb-clear-output files: \.ipynb$ stages: [ commit ] language: python entry: jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace additional_dependencies: [ 'nbconvert' ]使用Jupyter笔记本生成和共享报告
现在,这里有一个在业界并不特别解决的问题。什么是与团队和外部利益相关者共享笔记本的最佳方式?
在Jupyter笔记本的共享分析方面,领域划分为三种不同类型的团队,它们培养了不同的工作方式。
翻译团队
这些团队认为,商业或产品部门的人员不会喜欢阅读Jupyter笔记本。因此,他们会根据预期的受众对分析和报告进行调整。
翻译团队从笔记本中提取他们的发现,并将它们添加到公司的知识系统中(例如Confluence、Google Slides等)。作为负面影响,他们失去了一些笔记本的可追溯性,因为现在更难以审查报告的版本历史。但是他们会争辩说,他们能够更有效地将结果和分析传达给相关的利益相关者。
如果您想这样做,我建议在导出的文档和Jupyter笔记本之间保持链接,以便它们始终保持同步。在这种设置中,您可以保持笔记本的文本和结论较少,更加关注原始事实或数据证据。您将使用文档系统来扩展执行摘要和对每个发现的评论。通过这种方式,您可以解耦两个可交付成果-探索性代码和结果发现。
内部团队
这些团队使用本地Jupyter笔记本,并通过构建适合其公司的知识系统和基础架构的解决方案与其他业务部门共享。他们确实认为业务和产品利益相关者应该能够理解数据科学家的笔记本,并对需要从发现到原始数据进行完全可追溯的谱系的需求感到强烈。
然而,财务团队不太可能去GitHub或Bitbucket阅读您的笔记本。
我在这个领域看到了几种解决方案的实施。例如,您可以使用像nbconvert这样的工具,从Jupyter笔记本中生成PDF或导出为HTML页面,以便可以轻松与任何人共享,即使是技术团队之外的人。
您甚至可以将这些笔记本移到S3中,并允许它们作为呈现视图的静态网站进行托管。您可以使用CI/CD工作流,在代码合并到特定分支时创建并推送笔记本的HTML渲染。
第三方工具倡导者
这些团队使用的工具不仅可以开发笔记本,还可以与组织中的其他人共享。这通常涉及处理复杂性,例如确保对内部数据仓库、数据湖和数据库的安全和简单访问。
在此领域,一些广泛采用的工具包括Deepnote、Amazon SageMaker、Google Vertex AI和Azure Machine Learning。这些都是完整的笔记本运行平台,允许在远程机器上启动虚拟环境来执行您的代码。它们提供互动绘图、数据和实验探索,简化了整个数据科学生命周期。例如,Sagemaker允许您可视化使用Sagemaker Experiments跟踪的所有实验信息,而Deepnote还提供了使用其Chart Blocks进行点和点击可视化。
此外,Deepnote和SageMaker允许您与任何同事共享笔记本,查看或甚至使用相同的执行环境进行实时协作。
还有一些开源替代品,如JupyterHub,但其设置和维护工作量不值得。在本地搭建JupyterHub可能不是最佳解决方案,只有在极少数情况下才有意义(例如需要特定硬件的非常专业的工作负载)。通过使用云服务,您可以利用规模经济,这比其他经营在不同行业的公司能够提供的容错架构要好得多。您需要承担初始设置费用,并将其维护委托给平台运营团队,以便为数据科学家保持其运行并确保数据安全和隐私。因此,信任托管服务将避免对基础架构带来无休止的头痛。
我对探索这些产品的一般建议是:如果您的公司已经使用像AWS、Google Cloud Platform或Azure这样的云提供商,则采用它们的笔记本解决方案可能是一个不错的主意,因为访问公司的基础架构可能更容易,看起来也更少风险。
拥抱高效的Jupyter笔记本实践
在本文中,我们讨论了优化Jupyter笔记本效用的最佳实践和建议。
最重要的观点是:
始终根据预期的受众和最终目标来创建笔记本。这样,您就知道在笔记本的不同维度(代码、分析、执行摘要等)上应该放多少注意力。
总的来说,我鼓励数据科学家使用Jupyter笔记本,但仅限于回答探索性问题和报告目的。
产品工件,例如模型、数据集或超参数,不应追溯到笔记本。它们应该起源于可重现和可重新运行的生产系统。例如,SageMaker Pipelines或Airflow DAGs应该得到良好维护和彻底测试。
关于可追溯性、可重现性和血统的这些最后思考将成为我在数据科学和机器学习工程中的软件模式系列的下一篇文章的起点,下一篇文章将重点介绍如何提升您的ETL技能。虽然数据科学家经常忽视,但我相信掌握ETL是确保任何机器学习项目成功的核心和关键。