Streamlit-数据科学入门指南-全-
Streamlit 数据科学入门指南(全)
原文:
annas-archive.org/md5/df0f5acbc62fbd6b954754920c807fa5译者:飞龙
前言
在 2010 年代,数据科学家和机器学习工程师主要做的是静态分析。我们创建文档来为决策提供依据,文档中充满了关于我们发现的图表和度量数据,或者是我们创建的模型。创建完整的 Web 应用程序,让用户与分析结果互动,至少可以说是繁琐的!Streamlit 出现了,它是一个为数据工作者在每个步骤中着想而设计的 Python 库,用于创建 Web 应用程序。
Streamlit 缩短了数据驱动型 Web 应用程序的开发时间,使数据科学家能够在几小时内,而非几天,使用 Python 创建 Web 应用原型。
本书采用实践操作的方法,帮助你学习一些技巧和窍门,让你迅速掌握 Streamlit。你将从创建一个基本应用开始学习 Streamlit 的基础知识,并逐步在此基础上,制作高质量的图形数据可视化,并测试机器学习模型。随着章节的深入,你将通过个人和工作相关的数据驱动型 Web 应用的实际示例,逐步学习使用 Streamlit 组件、美化应用程序以及快速部署新应用程序等更复杂的主题。
本书的读者对象
本书面向数据科学家和机器学习工程师或爱好者,他们希望使用 Streamlit 创建 Web 应用。无论你是一个初级数据科学家,想通过部署第一个机器学习项目来提升简历,还是一个高级数据科学家,致力于通过动态分析说服同事,本书都适合你!
本书内容概述
第一章,Streamlit 入门,通过创建你的第一个应用程序来教授 Streamlit 的基本知识。
第二章,上传、下载和处理数据,介绍了数据;数据应用需要数据!我们将在这里学习如何在生产应用中高效、有效地使用数据。
第三章,数据可视化,教授如何在 Streamlit 应用中使用你最喜欢的 Python 可视化库。无需学习新的可视化框架!
第四章,使用 Streamlit 与机器学习,涵盖了机器学习。曾经想在几个小时内将你新建的炫酷机器学习模型部署到面向用户的应用中吗?从这里开始,获取深入的示例和技巧。
第五章,使用 Streamlit Sharing 部署 Streamlit,介绍了 Streamlit 的一键部署功能。我们将在这里学习如何简化部署过程!
第六章,美化 Streamlit 应用,介绍了 Streamlit 提供的丰富功能,帮助你制作精美的网页应用。在这一章中,我们将学习所有的技巧和窍门。
第七章,探索 Streamlit 组件,教授如何通过开源集成(称为 Streamlit 组件)利用 Streamlit 周围蓬勃发展的开发者生态系统。就像乐高玩具,只不过更好。
第八章,使用 Heroku 和 AWS 部署 Streamlit 应用,教授如何使用 AWS 和 Heroku 来部署 Streamlit 应用,作为 Streamlit Sharing 的替代方案。
第九章,通过 Streamlit 提升求职申请,将帮助你使用 Streamlit 应用向雇主展示你的数据科学能力,包括从简历生成应用到面试的带回家任务应用。
第十章,数据项目 – 在 Streamlit 中进行原型设计项目,讲述了如何为 Streamlit 社区和其他用户制作应用程序,既有趣又富有教育意义。我们将通过一些项目示例,帮助你学习如何开始自己的项目。
第十一章,为团队使用 Streamlit,讲解如何使用 Streamlit 产品 Streamlit for Teams 部署私有的 Streamlit 仓库并强制执行用户身份验证。
第十二章,Streamlit 高级用户,提供了更多关于 Streamlit 的信息,尽管这是一个相对年轻的库,但它已经得到了广泛应用。从 Streamlit 创始人、数据科学家、分析师和工程师的深入访谈中学习最前沿的知识。
为了充分利用本书
本书假设你至少是一个 Python 初学者,这意味着你已经熟悉基本的 Python 语法,并且之前有学习过 Python 的教程或课程。本书也面向对数据科学感兴趣的用户,涉及统计学和机器学习等主题,但不要求具备数据科学背景。如果你知道如何创建列表、定义变量,并且之前写过for循环,那么你已经具备足够的 Python 知识,可以开始本书的学习了!

如果你正在使用本书的数字版,我们建议你自己输入代码或从本书的 GitHub 仓库访问代码(下一个章节中会提供链接)。这样做可以帮助你避免因复制粘贴代码而可能出现的错误。
下载示例代码文件
你可以从 GitHub 下载本书的示例代码文件,访问 github.com/tylerjrichards/Getting-Started-with-Streamlit-for-Data-Science 或通过 Packt 的 GitHub 访问 github.com/PacktPublishing/Getting-Started-with-Streamlit-for-Data-Science。如果代码有更新,它将会在这些 GitHub 仓库中更新。
我们还有其他代码包,来自我们丰富的书籍和视频目录,访问 github.com/PacktPublishing/。欢迎查看!
下载彩色图片
我们还提供了一份 PDF 文件,里面包含了本书中使用的截图和图表的彩色图片。你可以在此下载:static.packt-cdn.com/downloads/9781800565500_ColorImages.pdf。
使用的约定
本书中使用了许多文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号。例如:格式应为 ec2-10-857-84-485.compute-1.amazonaws.com。这些数字是我编造的,你的应该与此类似。
代码块按如下方式设置:
import pandas as pd
penguin_df = pd.read_csv('penguins.csv')
print(penguin_df.head())
所有命令行输入或输出都按以下方式书写:
git add .
git commit -m 'added heroku files'
git push
粗体:表示新术语、重要单词或屏幕上显示的词语。例如,菜单或对话框中的词语通常会以粗体显示。以下是一个例子:我们将使用Amazon Elastic Compute Cloud,简称Amazon EC2。
提示或重要说明
如下所示。
联系我们
我们欢迎读者的反馈。
一般反馈:如果你对本书的任何方面有疑问,请通过 customercare@packtpub.com 给我们发送电子邮件,并在邮件主题中注明书名。
勘误:尽管我们已尽最大努力确保内容的准确性,但错误仍然可能发生。如果你在本书中发现了错误,我们将非常感激你能报告给我们。请访问 www.packtpub.com/support/errata 并填写表单。
盗版:如果你在互联网上发现任何我们作品的非法复制形式,请提供该位置地址或网站名称。请通过 copyright@packt.com 联系我们,并附上相关材料的链接。
如果你有兴趣成为作者:如果你在某个领域有专长,并且有兴趣撰写或参与书籍创作,请访问 authors.packtpub.com。
分享你的想法
阅读完《Streamlit 数据科学入门》后,我们非常希望听到你的想法!请点击这里直接进入亚马逊书评页面并分享你的反馈。
你的评价对我们和技术社区非常重要,将帮助我们确保提供优质的内容。
第一部分:创建基础的 Streamlit 应用程序
本节将介绍 Streamlit 应用程序的基础知识、Streamlit 中的数据可视化、如何部署应用程序,以及如何在 Streamlit 应用程序中实现模型。
本节涵盖以下章节:
-
第一章,Streamlit 入门
-
第二章,上传、下载与数据操作
-
第三章**,数据可视化
-
第四章**,在 Streamlit 中使用机器学习
-
第五章**,使用 Streamlit Sharing 部署 Streamlit
第一章:第一章:Streamlit 入门
Streamlit 是一个 Web 应用框架,帮助你构建和开发基于 Python 的 Web 应用,这些应用可以用来共享分析结果、构建复杂的交互体验并展示新的机器学习模型。更重要的是,开发和部署 Streamlit 应用非常快速且灵活,通常将应用开发的时间从几天缩短为几个小时。
在本章中,我们将从 Streamlit 的基础知识开始。我们将学习如何下载和运行示例 Streamlit 应用,如何使用自己的文本编辑器编辑示例应用,如何组织我们的 Streamlit 应用,最后,如何制作我们自己的应用。然后,我们将探索 Streamlit 中的数据可视化基础。我们将学习如何接受一些初始用户输入,并通过文本为我们的应用添加一些修饰。在本章结束时,你应该能够轻松地开始制作自己的 Streamlit 应用!
具体来说,我们将涵盖以下主题:
-
为什么选择 Streamlit?
-
安装 Streamlit
-
组织 Streamlit 应用
-
Streamlit 绘图示例
-
从零开始制作应用程序
在我们开始之前,我们将先了解技术要求,确保我们拥有开始所需的一切。
技术要求
以下是本章所需的安装和设置:
-
本书的要求是下载 Python 3.7(或更高版本)(
www.python.org/downloads/),并且需要一个文本编辑器来编辑 Python 文件。任何文本编辑器都可以。我使用 Sublime(www.sublimetext.com/3)。 -
本书的某些部分使用了 GitHub,建议拥有一个 GitHub 账号(
github.com/join)。了解如何使用 Git 对于本书不是必须的,但始终有帮助。如果你想入门,这个链接有一个有用的教程:guides.github.com/activities/hello-world/。 -
对 Python 的基本理解对本书也非常有用。如果你还没有达到这个水平,欢迎通过这个教程(
docs.python.org/3/tutorial/)或其他任何免费的教程来更好地了解 Python,准备好后再回来这里。我们还需要安装 Streamlit 库,我们将在后面的安装 Streamlit部分进行安装和测试。
为什么选择 Streamlit?
在过去十年里,数据科学家已成为公司和非营利组织日益重要的资源。他们帮助做出数据驱动的决策,使流程更加高效,并通过实施机器学习模型来在可重复的规模上改进这些决策。数据科学家面临的一个痛点是在发现新见解或创建新模型后,如何向同事展示动态结果、新模型或复杂的分析内容?他们可以发送静态的可视化图表,这在某些情况下有效,但对于需要构建多个步骤的复杂分析,或任何需要用户输入的情况就不适用了。另一种方法是创建一个结合文本和可视化的 Word 文档(或将 Jupyter notebook 导出为文档),但这同样无法处理用户输入,而且也更难以重复。还有一种选择是使用 Flask 或 Django 等框架从头开始构建一个完整的 Web 应用程序,然后再弄清楚如何将整个应用部署到 AWS 或其他云服务提供商上。但这些方法都不算理想。许多方法都比较慢,不能接收用户输入,或者对数据科学中至关重要的决策过程的支持不足。
介绍 Streamlit。Streamlit 旨在提供速度和互动性。它是一个帮助你构建和开发 Python Web 应用程序的框架。它内置了便捷的用户输入方法、使用最流行和强大的 Python 图形库进行绘图的方法,并能迅速将图表部署到 Web 应用程序中。
我在过去的一年里构建了各种不同类型的 Streamlit 应用,从个人作品集的数据项目,到为数据科学带回家的问题构建快速应用,甚至还为工作中的可重复分析构建了迷你应用。我真心相信,Streamlit 对你和你的工作将和对我一样有价值,所以我写了这篇文章,帮助你快速上手,以便你能够加速学习曲线,在几分钟或几个小时内构建 Web 应用,而不是花费几天的时间。如果这对你有帮助,接着往下看!我们将分为三个部分进行学习,从介绍 Streamlit 开始,逐步让你掌握如何构建自己的基础 Streamlit 应用。在第二部分,我们将扩展这一知识,探讨更高级的话题,例如生产环境部署方法,以及如何使用 Streamlit 社区创建的组件,打造更美观且实用的 Streamlit 应用。最后,在第三部分,我们将重点采访那些在工作、学术界以及学习数据科学技术中使用 Streamlit 的高级用户。在我们开始之前,我们需要先设置 Streamlit,并讨论本书其余部分示例的结构。
安装 Streamlit
为了运行任何 Streamlit 应用,你必须先安装 Streamlit。我使用了一种名为 pip 的包管理器来安装它,但你也可以使用任何你选择的包管理器(例如,brew)。本书使用的 Streamlit 版本是 0.81,Python 版本是 3.7,但它也应该适用于更新的版本。
在本书中,我们将混合使用终端命令和 Python 脚本中的代码。我们会在每个代码位置标明运行的环境,以便尽可能清晰。要安装 Streamlit,请在终端中运行以下代码:
pip install streamlit
现在我们已经下载了 Streamlit,我们可以直接在命令行中使用上述代码来启动 Streamlit 的demo.streamlit hello。
花些时间浏览 Streamlit 的演示,看看你觉得有趣的任何代码!我们将借用并编辑绘图演示背后的代码,它展示了 Streamlit 结合绘图和动画的应用。在我们深入之前,让我们先讨论一下如何组织 Streamlit 应用。
组织 Streamlit 应用
本书中我们创建的每个 Streamlit 应用都应该包含在各自的文件夹中。虽然很容易为每个 Streamlit 应用创建新的文件,但这会养成一个坏习惯,等我们讨论如何部署 Streamlit 应用时,会遇到权限和数据方面的问题。
对于本书,我建议你创建一个专用的文件夹来存放你在本书中创建的所有应用。我把它命名为streamlit_apps。以下命令会创建一个名为streamlit_apps的新文件夹,并将其设置为当前工作目录:
mkdir streamlit_apps
cd streamlit_apps
本书的所有代码都可以在github.com/tylerjrichards/Getting-Started-with-Streamlit-for-Data-Science找到,但我强烈推荐你亲自动手编写代码来练习。
Streamlit 绘图演示
首先,我们将开始学习如何制作 Streamlit 应用,通过重新制作我们之前在 Streamlit 演示中看到的绘图示例,并使用我们自己编写的 Python 文件。为此,我们将执行以下操作:
-
创建一个 Python 文件来存放我们所有的 Streamlit 代码。
-
使用演示中提供的绘图代码。
-
进行小幅修改以进行练习。
-
在本地运行我们的文件。
我们的第一步是创建一个名为plotting_app的文件夹,用来存放我们的第一个示例。以下代码将在终端运行时创建这个文件夹,将我们的工作目录切换到plotting_app,并创建一个空的 Python 文件,我们将其命名为plot_demo.py:
mkdir plotting_app
cd plotting_app
touch plot_demo.py
现在我们已经创建了一个名为plot_demo.py的文件,使用任何文本编辑器打开它(如果你还没有选择编辑器,我个人推荐 Sublime (www.sublimetext.com/))。打开后,将以下代码复制粘贴到plot_demo.py文件中:
import streamlit as st
import time
import numpy as np
progress_bar = st.sidebar.progress(0)
status_text = st.sidebar.empty()
last_rows = np.random.randn(1, 1)
chart = st.line_chart(last_rows)
for i in range(1, 101):
new_rows = last_rows[-1, :] + np.random.randn(5, 1).cumsum(axis=0)
status_text.text("%i%% Complete" % i)
chart.add_rows(new_rows)
progress_bar.progress(i)
last_rows = new_rows
time.sleep(0.05)
progress_bar.empty()
# Streamlit widgets automatically run the script from top to bottom. Since
# this button is not connected to any other logic, it just causes a plain
# rerun.
st.button("Re-run")
这段代码做了几件事。首先,它导入了所有需要的库,并在 Streamlit 的原生图形框架中创建了一个折线图,起始点是从正态分布中随机抽取的一个数字,均值为 0,方差为 1。接着,它运行一个for循环,持续以每次 5 个数字的批次抽取新随机数,并将其加到之前的总和中,同时等待二十分之一秒,以便让我们看到图表的变化,模拟动画效果。
到本书结束时,你将能够非常快速地制作像这样的应用。但现在,我们可以在本地运行它,在终端输入以下代码:
streamlit run plot_demo.py
这应该会在默认的网页浏览器中打开一个新标签页,显示你的应用。我们应该能看到我们的应用运行,效果如下图所示:

图 1.1 – 绘图演示输出
我们将通过先调用streamlit run并指向包含我们应用代码的 Python 脚本来运行每个 Streamlit 应用。现在让我们在应用中做些小改动,更好地理解 Streamlit 的工作原理。以下代码更改了我们在图表上绘制随机数的数量,但你也可以自由做任何你想要的修改。使用以下代码进行更改,在你选择的文本编辑器中保存这些更改,并再次运行文件:
import streamlit as st
import time
import numpy as np
progress_bar = st.sidebar.progress(0)
status_text = st.sidebar.empty()
last_rows = np.random.randn(1, 1)
chart = st.line_chart(last_rows)
for i in range(1, 101):
new_rows = last_rows[-1, :] + np.random.randn(50, 1).cumsum(axis=0)
status_text.text("%i%% Complete" % i)
chart.add_rows(new_rows)
progress_bar.progress(i)
last_rows = new_rows
time.sleep(0.05)
progress_bar.empty()
# Streamlit widgets automatically run the script from top to bottom. Since
# this button is not connected to any other logic, it just causes a plain
# rerun.
st.button("Re-run")
你应该会注意到 Streamlit 检测到源文件发生了更改,并会提示你是否希望重新运行该文件。如果你愿意,点击重新运行(或者点击总是重新运行,如果你想让这种行为成为默认设置,我几乎总是这么做),然后观察你的应用发生变化。
随意尝试对绘图应用进行其他更改,熟悉它!当你准备好后,我们可以继续制作自己的应用。
从零开始制作应用
现在我们已经试过了别人做的应用,接下来让我们自己动手做一个!这个应用将重点使用中心极限定理,这是一个统计学的基本定理,指的是如果我们从任何分布中进行带放回的随机抽样足够多次,那么我们的样本均值的分布将近似正态分布。
我们不会通过应用来证明这一点,而是尝试生成几个图表,帮助解释中心极限定理的强大之处。首先,确保我们在正确的目录下(我们之前叫它streamlit_apps),然后创建一个名为clt_app的新文件夹,并放入一个新文件。
以下代码会创建一个名为clt_app的新文件夹,并再次创建一个空的 Python 文件,这次叫做clt_demo.py:
mkdir clt_app
cd clt_app
touch clt_demo.py
每当我们启动一个新的 Streamlit 应用时,我们需要确保导入 Streamlit(在本书和其他地方通常使用别名st)。Streamlit 为每种内容类型(文本、图表、图片和其他媒体)提供了独特的函数,我们可以将它们作为构建所有应用的基础模块。我们将使用的第一个函数是st.write(),它是一个接受字符串(如后面所示,几乎可以接受任何 Python 对象,例如字典)的函数,并将其按调用顺序直接写入我们的 Web 应用。由于我们正在调用一个 Python 脚本,Streamlit 会顺序查看文件,每次看到一个函数时,就为该内容分配一个顺序位置。这使得使用起来非常简单,因为你可以写任何 Python 代码,当你希望某些内容出现在你创建的应用中时,只需使用st.write(),一切都准备好了。
在我们的clt_demo.py文件中,我们可以通过使用st.write()并编写以下代码,来实现基本的'Hello World'输出:
import streamlit as st
st.write('Hello World')
现在我们可以通过在终端中运行以下代码来进行测试:
streamlit run clt_demo.py
我们应该能看到字符串'Hello World'出现在我们的应用中,到目前为止一切正常。下图是我们在 Safari 中看到的应用截图:

图 1.2 – Hello World 应用
在这张截图中有三点需要注意。首先,我们看到了我们所写的字符串,这很好。接下来,我们看到 URL 指向8501。我们不需要了解计算机上的端口系统或传输控制协议(TCP)的任何内容。这里需要注意的关键是这个应用是本地的,运行在你的计算机上。第三点需要注意的是右上角的汉堡图标。下图展示了我们点击该图标后发生的事情:

图 1.3 – 图标选项
这是 Streamlit 应用的默认选项面板。在本书中,我们将深入讨论每个选项,特别是那些不那么显而易见的选项,例如清除缓存。目前我们只需知道的是,如果我们希望重新运行应用或查找设置或文档,我们可以使用这个图标来找到几乎所有需要的内容。
当我们托管应用程序供他人使用时,他们会看到相同的图标,但有一些不同的选项(例如,他们将无法清除缓存)。我们稍后将更详细地讨论这一点。现在回到我们的中心极限定理应用!
下一步是生成一个我们希望从中进行有放回抽样的分布。我这里选择了二项分布。我们可以把以下代码理解为使用 Python 包numpy模拟 1,000 次掷硬币,并打印出这 1,000 次掷硬币的平均正面朝上的次数:
import streamlit as st
import numpy as np
binom_dist = np.random.binomial(1, .5, 100)
st.write(np.mean(binom_dist))
现在,基于我们对中心极限定理的理解,我们可以预期,如果我们从binom_dist中进行足够多次抽样,那么这些样本的均值将近似于正态分布。
我们已经讨论过st.write()函数。我们下一步要探索的是将内容写入 Streamlit 应用程序,这次是通过图形。st.pyplot()是一个让我们可以使用流行的matplotlib库的所有功能,并将我们的matplotlib图形推送到 Streamlit 的函数。一旦我们在matplotlib中创建了一个图形,就可以明确告诉 Streamlit 通过st.pyplot()函数将其写入我们的应用程序。
所以,现在全部放在一起!这个应用模拟了 1000 次掷硬币,并将这些值存储在一个我们称之为binom_dist的列表中。然后我们从这个列表中随机抽取(带替换)100 个,计算均值,并将该均值存储在巧妙命名的变量list_of_means中。我们重复这一过程 1000 次(虽然有些过头了,我们也可以只用几十次抽样),然后绘制直方图。完成后,以下代码的结果应该显示一个钟形分布:
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
binom_dist = np.random.binomial(1, .5, 1000)
list_of_means = []
for i in range(0, 1000):
list_of_means.append(np.random.choice(binom_dist, 100, replace=True).mean())
fig, ax = plt.subplots()
ax = plt.hist(list_of_means)
st.pyplot(fig)
每次运行这个应用都会生成一个新的正态分布曲线。当我运行它时,我的正态分布曲线看起来像下面的图形。如果你的图形和接下来看到的图形不完全相同,没关系,因为我们的代码中使用了随机抽样:

图 1.4 – 正态分布曲线
正如你可能注意到的,我们首先通过调用plt.subplots()创建了一个空图形和空的坐标轴,然后将我们创建的直方图分配给ax变量。正因如此,我们能够明确告诉 Streamlit 在我们的 Streamlit 应用中展示这个图形。
这是一个重要的步骤,因为在 Streamlit 的某些版本中,我们也可以跳过这一步,不将我们的直方图分配给任何变量,而是在之后直接调用st.pyplot()。以下代码采用了这种方法:
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
binom_dist = np.random.binomial(1, .5, 1000)
list_of_means = []
for i in range(0, 1000):
list_of_means.append(np.random.choice(binom_dist, 100, replace=True).mean())
plt.hist(list_of_means)
st.pyplot()
我不推荐这种方法,因为它可能会给你一些意想不到的结果。举个例子,我们想先制作一个均值的直方图,然后制作一个仅包含数字 1 的新列表的直方图。
请稍等片刻,猜猜以下代码会做什么。我们会得到多少个图形?输出会是什么?
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
binom_dist = np.random.binomial(1, .5, 1000)
list_of_means = []
for i in range(0, 1000):
list_of_means.append(np.random.choice(binom_dist, 100, replace=True).mean())
plt.hist(list_of_means)
st.pyplot()
plt.hist([1,1,1,1])
st.pyplot()
我预计这将显示两个直方图,第一个是list_of_means的直方图,第二个是包含1的列表的直方图:

图 1.5 – 两个直方图的故事
实际上我们得到的结果是不同的!第二个直方图包含了来自第一个和第二个列表的数据!当我们调用plt.hist()而没有将输出分配给任何变量时,matplotlib会将新的直方图附加到全局存储的旧图形上,Streamlit 会将新的图形推送到我们的应用中。
这是解决这个问题的一种方法。如果我们显式创建了两个图形,我们可以在图形生成后,随时调用st.pyplot()函数,并更好地控制图形的位置。以下代码显式分离了这两个图形:
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
binom_dist = np.random.binomial(1, .5, 1000)
list_of_means = []
for i in range(0, 1000):
list_of_means.append(np.random.choice(binom_dist, 100, replace=True).mean())
fig1, ax1 = plt.subplots()
ax1 = plt.hist(list_of_means)
st.pyplot(fig1)
fig2, ax2 = plt.subplots()
ax2 = plt.hist([1,1,1,1])
st.pyplot(fig2)
上述代码通过先为每个图形和轴定义单独的变量(使用 plt.subplots()),然后将直方图赋值给相应的轴,来分别绘制两个直方图。之后,我们可以使用创建的图形调用 st.pyplot(),生成如下应用:

图 1.6 – 固定的直方图
我们可以清晰地看到,前面的图形中两个直方图现在已经分开,这是我们希望的行为。在 Streamlit 中,我们通常会绘制多个可视化,并将在本书的其余部分使用这种方法。接下来,开始接受用户输入!
在 Streamlit 应用中使用用户输入
到目前为止,我们的应用程序只是以一种花哨的方式展示我们的可视化内容。但大多数 web 应用都会接受某些用户输入,或者是动态的,而非静态的可视化。幸运的是,Streamlit 提供了许多函数来接受用户输入,这些函数根据我们想要输入的对象有所不同。比如有自由格式的文本输入 st.text_input();单选按钮 st.radio();数字输入 st.number_input();还有许多其他非常有用的函数可以帮助我们制作 Streamlit 应用。我们将在本书中详细探讨它们的大多数内容,但我们将从数字输入开始。
在之前的示例中,我们假设我们掷的是公平的硬币,硬币正反面出现的概率各是 50/50。现在让用户决定硬币正面朝上的百分比,将其赋值给一个变量,并将该变量作为输入用于二项分布。数字输入函数需要一个标签、最小值和最大值,以及默认值,我在以下代码中填写了这些内容:
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
perc_heads = st.number_input(label = 'Chance of Coins Landing on Heads', min_value = 0.0, max_value = 1.0, value = .5)
binom_dist = np.random.binomial(1, perc_heads, 1000)
list_of_means = []
for i in range(0, 1000):
list_of_means.append(np.random.choice(binom_dist, 100, replace=True).mean())
fig, ax = plt.subplots()
ax = plt.hist(list_of_means, range=[0,1])
st.pyplot(fig)
上述代码使用 st.number_input() 函数收集我们的百分比,并将用户输入赋值给变量(perc_heads),然后利用该变量来更改之前使用的二项分布函数的输入。它还将我们的直方图的 x 轴始终设置为在 0 和 1 之间,这样我们可以更清楚地看到输入变化时的差异。试着玩一玩这个应用;更改数字输入,注意当用户输入更改时应用如何响应。例如,下面是当我们将数字输入设置为 .25 时的结果:

图 1.7 - 当我们将数字输入设置为 0.25 时的结果示例
正如你可能已经注意到的,每次我们更改脚本的输入时,Streamlit 都会重新运行整个应用程序。这是默认行为,对于理解 Streamlit 性能非常重要;在本书后面,我们将探索一些可以更改此默认行为的方法,比如添加缓存或表单!我们还可以通过 st.text_input() 函数在 Streamlit 中接受文本输入,就像我们之前处理数字输入一样。以下代码将文本输入赋值给图表的标题:
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
perc_heads = st.number_input(label='Chance of Coins Landing on Heads', min_value=0.0, max_value=1.0, value=.5)
graph_title = st.text_input(label='Graph Title')
binom_dist = np.random.binomial(1, perc_heads, 1000)
list_of_means = []
for i in range(0, 1000):
list_of_means.append(np.random.choice(binom_dist, 100, replace=True).mean())
fig, ax = plt.subplots()
plt.hist(list_of_means, range=[0,1])
plt.title(graph_title)
st.pyplot(fig)
这段代码创建了一个 Streamlit 应用,包含两个输入框,一个是数字输入,另一个是文本输入,并利用这两个输入改变我们的 Streamlit 应用。最后,结果是一个看起来像下一张图的 Streamlit 应用,拥有动态标题和概率:

图 1.8 – 带有动态标题和概率的 Streamlit 应用
现在我们已经处理了一些用户输入,让我们深入探讨 Streamlit 应用中的文本部分。
完成的细节 – 向 Streamlit 添加文本
我们的应用已经能正常运行,但还缺少一些精美的细节。我们之前提到过st.write()函数,Streamlit 文档称它为 Streamlit 命令的瑞士军刀。几乎任何我们用st.write()包裹的内容都可以默认工作,如果不确定最佳方案,它应该是我们的首选函数。
除了st.write(),我们还可以使用其他内置函数来格式化文本,如st.title()、st.header()、st.markdown()和st.subheader()。使用这五个函数可以轻松地格式化我们的 Streamlit 应用中的文本,并确保在更大的应用中保持一致的字号。
更具体地说,st.title()会在应用中显示一个大块文本,st.header()使用的字体稍小于st.title(),而st.subheader()则使用更小的字体。除此之外,st.markdown()允许任何熟悉 Markdown 的人在 Streamlit 应用中使用这个流行的标记语言。接下来我们可以在以下代码中尝试使用几个函数:
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
st.title('Illustrating the Central Limit Theorem with Streamlit')
st.subheader('An App by Tyler Richards')
st.write(('This app simulates a thousand coin flips using the chance of heads input below,'
'and then samples with replacement from that population and plots the histogram of the'
' means of the samples, in order to illustrate the Central Limit Theorem!'))
perc_heads = st.number_input(
label='Chance of Coins Landing on Heads', min_value=0.0, max_value=1.0, value=.5)
binom_dist = np.random.binomial(1, perc_heads, 1000)
list_of_means = []
for i in range(0, 1000):
list_of_means.append(np.random.choice(
binom_dist, 100, replace=True).mean())
fig, ax = plt.subplots()
ax = plt.hist(list_of_means)
st.pyplot(fig)
以上代码首先添加了一个大标题(st.title()),接着在其下方添加了一个较小的子标题(st.subheader()),然后在子标题下方添加了更小的文本(st.write())。我们还将前面代码块中的长文本字符串分割成了三个更小的字符串,以提高可读性并使其在文本编辑器中更易编辑。最终效果应该像以下截图。请注意,由于我们使用的是随机生成的数据来绘制这个直方图,如果你的直方图与示例有所不同,这是可以接受的(也是预期的!):

图 1.9 – 中央极限定理应用
Streamlit 还提供了一个写文本的选项——st.markdown(),它可以将 Markdown 风格的文本解析并显示在你的 Streamlit 应用中。如果你已经熟悉 Markdown,那么这个选项比st.write()更适合测试。
总结
在本章中,我们首先学习了如何组织文件和文件夹,以便在本书的后续部分中使用,并迅速进入了下载 Streamlit 的指令。接着我们构建了第一个 Streamlit 应用程序——Hello World,并学习了如何在本地运行 Streamlit 应用程序。然后,我们开始构建一个更复杂的应用程序,从零开始展示中心极限定理的影响,从一个简单的直方图开始,到接受用户输入,并格式化不同类型的文本以提升应用程序的清晰度和美观度。
到现在为止,你应该已经熟悉了基本的数据可视化、在文本编辑器中编辑 Streamlit 应用程序以及在本地运行 Streamlit 应用程序等内容。在下一章,我们将更深入地探讨数据处理。
第二章:第二章:上传、下载和操作数据
到目前为止,本书中的 Streamlit 应用程序一直使用的是模拟数据。这对于掌握一些 Streamlit 基础知识非常有用,但大多数数据科学工作并非在模拟数据上进行,而是基于数据科学家已经拥有的实际数据集,或者用户提供的数据集。
本章将重点讲解 Streamlit 应用中的数据世界,涵盖使用 Streamlit 将数据集变为活跃的所有必要知识。我们将讨论数据操作、使用用户导入的数据、流程控制、调试 Streamlit 应用以及通过缓存加速数据应用等内容,所有这些内容都将通过一个名为 Palmer's Penguins 的数据集进行演示。
具体来说,我们将涵盖以下主题:
-
设置——Palmer's Penguins
-
调试 Streamlit 应用程序
-
在 Streamlit 中进行数据操作
技术要求
本章中,我们需要下载 Palmer's Penguins 数据集,可以通过以下链接找到:github.com/tylerjrichards/streamlit_apps/blob/main/penguin_app/penguins.csv。本章的设置和数据集的解释将在以下部分详细说明。
设置——Palmer's Penguins
本章中,我们将使用一个关于北极企鹅的数据集,该数据集来自 Kristen Gorman 博士的研究工作(www.uaf.edu/cfos/people/faculty/detail/kristen-gorman.php)以及南极洲 Palmer 站 LTER 项目(pal.lternet.edu/)。
数据集致谢
来自 Palmer LTER 数据仓库的数据由极地项目办公室(NSF Grants OPP-9011927, OPP-9632763, OPP-0217282)支持。
该数据集是著名的鸢尾花数据集的常见替代品,包含 344 只个体企鹅的数据,涉及 3 个物种。数据可以在本书的 GitHub 仓库中找到(github.com/tylerjrichards/streamlit_apps),位于penguin_app文件夹下,文件名为penguins.csv。
如我们之前所讨论的,Streamlit 应用程序是从我们的 Python 脚本中运行的。这将基准目录设置为 Python 文件所在的位置,也就是我们的 Streamlit 应用程序所在的位置,这意味着我们可以访问放在应用程序目录中的任何其他文件。
首先,让我们在现有的streamlit_apps文件夹中创建一个新的文件夹,用于存放我们的应用程序,代码块如下:
mkdir penguin_app
cd penguin_app
touch penguins.py
下载数据后,将生成的 CSV 文件(示例中的文件名为penguins.csv)放入penguin_app文件夹中。现在,我们的文件夹中应该有penguins.py文件和penguins.csv文件。在第一次操作时,我们只需要使用之前学过的st.write()函数打印出数据框的前五行,代码如下:
import streamlit as st
import pandas as pd
st.title("Palmer's Penguins")
#import our data
penguins_df = pd.read_csv('penguins.csv')
st.write(penguins_df.head())
上面的代码在我们终端运行 streamlit run penguins.py 时,会生成以下 Streamlit 应用:

图 2.1 – 前五只企鹅
现在我们对数据有了初步的了解,我们将进一步探索数据集,然后开始添加内容到我们的应用程序中。
探索 Palmer's Penguins
在开始处理这个数据集之前,我们应该先做一些可视化,以便更好地理解数据。正如我们之前看到的,数据中有许多列,从喙长到鳍长,再到企鹅所在的岛屿,甚至是企鹅的物种。对于第一个可视化,我们可以看到三种物种的鳍长与体重之间的关系:

图 2.2 – 鳍和体重
如我们所见,Gentoo 物种的鳍长和体重都较高,并且似乎所有物种的鳍长都与体重相关。接下来,让我们看看喙长与鳍长之间的关系:

图 2.3 – 鳍和喙
从这张图表中,我们可以看到 Chinstrap 物种似乎有比 Adelie 物种更长的喙。我们还可以绘制更多变量组合的散点图,但我们能否创建一个数据探索 Streamlit 应用来为我们完成这项工作?
这个小应用的最终目标是让用户指定一种企鹅物种,并选择两个其他变量来用于绘制散点图。我们将从学习如何获取这些输入开始,然后创建动态可视化。
我们之前学到的最后一个用户输入是数字输入函数,但在这里不会派上用场。Streamlit 有一个选择框函数(st.selectbox()),可以让我们让用户从多个选项中选择一个(在我们的例子中是三个选项),该函数返回用户选择的内容。我们将使用这个函数获取散点图所需的三个输入:
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
selected_species = st.selectbox('What species would you like to visualize?',
['Adelie', 'Gentoo', 'Chinstrap'])
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
这段代码从三个新的选择框中创建了三个新变量,用户可以在我们的 Streamlit 应用中输入这些变量。以下截图显示了上面代码所生成的 Streamlit 应用:

图 2.4 – 企鹅的用户输入
现在我们已经有了 selected_species 变量,我们可以筛选 DataFrame,并使用选定的 x 和 y 变量快速绘制散点图,如下一个代码块所示:
Import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
selected_species = st.selectbox('What species would you like to visualize?',
['Adelie', 'Gentoo', 'Chinstrap'])
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
penguins_df = pd.read_csv('penguins.csv')
penguins_df = penguins_df[penguins_df['species'] == selected_species]
fig, ax = plt.subplots()
ax = sns.scatterplot(x = penguins_df[selected_x_var],
y = penguins_df[selected_y_var])
plt.xlabel(selected_x_var)
plt.ylabel(selected_y_var)
st.pyplot(fig)
上述代码在前一个例子的基础上进行扩展,通过加载我们的 DataFrame、按物种筛选数据,然后使用上一章相同的方法绘制图表,最终将生成一个与之前相同的应用,但附加了散点图,如下图所示:

图 2.5 – 第一张企鹅散点图
尝试操作这个应用程序,确保所有的输入和输出都正常工作。还要注意,我们使用了输入变量来设置 x 轴和 y 轴的标签,这意味着当我们做出任何新选择时,它们会自动更新。我们的图表没有明确显示正在绘制的物种,因此让我们练习制作动态文本。以下代码使用 Python 本地的 format() 函数,为我们 Streamlit 应用的图表标题添加动态文本:
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
selected_species = st.selectbox('What species would you like to visualize?',
['Adelie', 'Gentoo', 'Chinstrap'])
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
penguins_df = pd.read_csv('penguins.csv')
penguins_df = penguins_df[penguins_df['species'] == selected_species]
fig, ax = plt.subplots()
ax = sns.scatterplot(x = penguins_df[selected_x_var],
y = penguins_df[selected_y_var])
plt.xlabel(selected_x_var)
plt.ylabel(selected_y_var)
plt.title('Scatterplot of {} Penguins'.format(selected_species))
st.pyplot(fig)
上面的代码将物种添加到了我们的散点图中,产生了如下的 Streamlit 应用:

图 2.6 – 动态图表标题
这看起来很棒!我们还可以像本书前面的介绍图表一样,按物种的颜色和形状绘制图表。以下代码实现了这一点,并使用 Seaborn 的深色网格主题,以便更好地突出显示在 Streamlit 的白色背景上:
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
penguins_df = pd.read_csv('penguins.csv')
sns.set_style('darkgrid')
markers = {"Adelie": "X", "Gentoo": "s", "Chinstrap":'o'}
fig, ax = plt.subplots()
ax = sns.scatterplot(data = penguins_df, x = selected_x_var,
y = selected_y_var, hue = 'species', markers = markers,
style = 'species')
plt.xlabel(selected_x_var)
plt.ylabel(selected_y_var)
plt.title("Scatterplot of Palmer's Penguins")
st.pyplot(fig)
*** Note: The code above is not in the correct format, please fix **
以下截图展示了我们新改进的 Palmer's Penguins 应用,它允许我们选择 x 和 y 轴,并为我们绘制一个散点图,不同的物种显示为不同的颜色和形状:

图 2.7 – 带形状的截图
注意
你可能正在通过一张黑白截图查看这个应用程序,只有形状的差异会被显示。
这个应用的最后一步是允许用户上传他们自己的数据。如果我们希望研究团队在任何时候都能上传数据到这个应用,并查看图形化的结果呢?或者如果有三个研究小组,每个小组都有自己独特的数据和不同的列名,而他们想使用我们创建的方法呢?我们将一步一步地解决这个问题。首先,我们如何接受用户的数据?
Streamlit 提供了一个叫做 file_uploader() 的函数,允许应用的用户上传最大 200 MB 的数据(默认值)。它的工作方式和我们之前使用过的其他交互式组件一样,有一个例外。我们交互式组件中的默认值通常是列表中的第一个值,但在用户没有与应用互动之前,默认上传的文件值是 None,这显然是没有意义的!
这开始涵盖 Streamlit 开发中的一个非常重要的概念——流程控制。流程控制可以理解为仔细思考应用程序的所有步骤,因为如果我们不明确指定某些事项,Streamlit 会尝试一次性运行整个应用程序。例如,想要等待用户上传文件后再尝试创建图表或操作 DataFrame 时,就需要特别注意。
Streamlit 中的流程控制
正如我们之前所讨论的,对于数据上传的默认问题,有两种解决方案。我们可以提供一个默认文件,直到用户与应用程序进行交互,或者我们可以暂停应用程序,直到上传文件。我们从第一个选项开始。以下代码在if语句中使用了st.file_uploader()函数。如果用户上传文件,应用程序使用该文件;如果没有上传文件,则使用我们之前使用的默认文件:
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
penguin_file = st.file_uploader('Select Your Local Penguins CSV (default provided)')
if penguin_file is not None:
penguins_df = pd.read_csv(penguin_file)
else:
penguins_df= pd.read_csv('penguins.csv')
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
fig, ax = plt.subplots()
ax = sns.scatterplot(x = penguins_df[selected_x_var],
y = penguins_df[selected_y_var], hue = penguins_df['species'])
plt.xlabel(selected_x_var) plt.ylabel(selected_y_var)
plt.title("Scatterplot of Palmer's Penguins")
st.pyplot(fig)
当我们在终端中运行前面的代码时,我们可以看到我们的三个用户输入(x轴,y轴和数据集),即使我们尚未上传文件,也会显示图形。以下截图展示了这个应用程序:

图 2.8 – 文件输入
这种方法的明显优势在于该应用程序始终显示结果,但这些结果可能对用户没有帮助!对于更大的应用程序来说,这也是一个次优解,因为无论是否使用,应用程序内存储的任何数据都会拖慢应用程序的速度。稍后,在第七章,“探索 Streamlit 组件”中,我们将讨论所有的部署选项,包括一个名为 Streamlit Sharing 的内置部署选项。
我们的第二个选项是完全停止应用程序,除非用户上传了文件。对于这个选项,我们将使用一个新的 Streamlit 函数,名为stop(),该函数(如预期的那样)在被调用时停止应用程序的流程。使用这个函数是调试应用程序的最佳实践,它可以鼓励用户做出一些改变或描述发生的错误。虽然对于我们来说并非必须,但这对于未来的应用程序来说是一个很有用的知识。以下代码使用if-else语句,并在else语句中使用st.stop()来防止在st.file_uploader()未使用时,整个应用程序运行:
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
penguin_file = st.file_uploader('Select Your Local Penguins CSV')
if penguin_file is not None:
penguins_df = pd.read_csv(penguin_file)
else:
st.stop()
sns.set_style('darkgrid')
markers = {"Adelie": "X", "Gentoo": "s", "Chinstrap":'o'}
fig, ax = plt.subplots()
ax = sns.scatterplot(data = penguins_df, x = selected_x_var,
y = selected_y_var, hue = 'species', markers = markers,
style = 'species')
plt.xlabel(selected_x_var)
plt.ylabel(selected_y_var)
plt.title("Scatterplot of Palmer's Penguins")
st.pyplot(fig)
如以下截图所示,直到我们上传自己的数据,我们将无法看到散点图,并且应用程序会停止。Streamlit 应用程序会等待,直到用户上传文件才会完全运行,而不是抛出错误:

图 2.9 – Streamlit 停止
在我们继续进行数据处理并创建更复杂的 Streamlit 应用程序之前,我们应该讨论一些调试 Streamlit 应用程序的最佳实践。
调试 Streamlit 应用程序
我们大致有两种 Streamlit 开发的选择。
-
在 Streamlit 中开发并使用
st.write()作为调试工具。 -
在 Jupyter 中进行探索,然后复制到 Streamlit。
在 Streamlit 中开发
在第一个选项中,我们直接在 Streamlit 中编写代码,进行实验并准确探索我们的应用程序将如何运行。如果我们有较少的探索工作和更多的实现工作,基本上我们已经采用了这个选项,这样非常有效。
优点:
-
所见即所得
-
无需维护 IPython 和 Python 版本的相同应用程序
-
更适合学习如何编写生产级代码
缺点:
-
较慢的反馈循环(必须运行整个应用才能得到反馈)
-
可能是一个不太熟悉的开发环境
在 Jupyter 中进行探索然后复制到 Streamlit
另一种选择是利用极受欢迎的 Jupyter 数据科学产品,先编写并测试 Streamlit 应用的代码,然后再将其放入必要的脚本中并正确格式化。这对于探索将会在 Streamlit 应用中使用的新方法非常有用,但也存在严重的缺点。
优点:
-
闪电般的快速反馈循环使得实验变得更加容易。
-
用户可能更熟悉 Jupyter。
-
不需要运行完整应用就能得到结果。
缺点:
-
如果顺序不对,Jupyter 可能会提供误导性的结果。
-
从 Jupyter 中‘复制’代码是非常耗时的。
-
Jupyter 和 Streamlit 之间可能存在不同的 Python 版本。
我的建议是,在应用将要运行的环境中开发 Streamlit 应用(即 Python 文件)。对于调试,充分利用 st.write() 函数,它可以打印出几乎任何你可能需要的 Python 对象(字典、DataFrame、列表、字符串、数字、图形等等)。尽量只在最后的情况下使用其他开发环境,如 Jupyter!
在 Streamlit 中进行数据处理
Streamlit 以脚本的方式从头到尾运行我们的 Python 文件,因此我们可以像在 Jupyter notebook 或常规 Python 脚本中那样,使用强大的库如 pandas 来执行数据处理。正如我们之前所讨论的,我们可以像往常一样进行所有常规的数据处理。以我们 Palmer's Penguins 应用为例,如果我们希望用户能够根据性别筛选企鹅呢?以下代码通过 pandas 筛选我们的 DataFrame:
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
penguin_file = st.file_uploader(
'Select Your Local Penguins CSV (default provided)')
if penguin_file is not None:
penguins_df = pd.read_csv(penguin_file)
else:
penguins_df = pd.read_csv('penguins.csv')
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
selected_gender = st.selectbox('What gender do you want to filter for?',
['all penguins', 'male penguins', 'female penguins'])
if selected_gender == 'male penguins':
penguins_df = penguins_df[penguins_df['sex'] == 'male']
elif selected_gender == 'female penguins':
penguins_df = penguins_df[penguins_df['sex'] == 'female']
else:
pass
fig, ax = plt.subplots()
ax = sns.scatterplot(x=penguins_df[selected_x_var],
y=penguins_df[selected_y_var], hue=penguins_df['species'])
plt.xlabel(selected_x_var)
plt.ylabel(selected_y_var)
plt.title("Scatterplot of Palmer's Penguins: {}".format(selected_gender))
st.pyplot(fig)
这里有几点需要注意。首先,我们添加了另一个 selectbox 插件,提供了男性、女性和全部选项。我们本可以通过请求文本输入来实现这一点,但对于数据处理,我们希望尽量限制用户的操作。我们还确保动态更改了标题,这是为了清晰起见,建议这样做,因为我们希望通过图表直接向用户展示数据已经根据他们的输入进行了筛选。
缓存介绍
随着我们创建更多计算密集型的 Streamlit 应用并开始使用和上传更大的数据集,我们应开始关注这些应用的运行时,并尽可能提高效率。提高 Streamlit 应用效率最简单的方法是通过缓存,即将某些结果存储在内存中,以便在可能的情况下,应用不会重复执行相同的工作。
应用程序缓存的一个很好的类比是人类的短期记忆,我们会将一些可能有用的信息保存在身边。当某个信息在我们的短期记忆中时,我们不需要费力思考就能访问它。同样,当我们在 Streamlit 中缓存某个信息时,我们就像是在打赌,我们会频繁使用该信息。
Streamlit 缓存的具体工作原理是通过将函数的结果存储在应用程序中,如果该函数在应用程序中被另一个用户(或我们重新运行应用程序时)使用相同的参数调用,Streamlit 并不会重新执行该函数,而是从内存中加载该函数的结果。
让我们证明这一方法有效!首先,我们将为 Penguins 应用程序的数据上传部分创建一个函数,然后使用 time 库故意让这个函数的运行时间比平常长得多,看看是否能够通过使用 st.cache() 来加速应用程序。
如下代码所示,我们首先创建了一个新的函数 load_file(),该函数等待 3 秒钟,然后加载所需的文件。通常我们不会故意让应用程序变慢,但我们想知道缓存是否有效:
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import time
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
penguin_file = st.file_uploader(
'Select Your Local Penguins CSV (default provided)')
def load_file(penguin_file):
time.sleep(3)
if penguin_file is not None:
df = pd.read_csv(penguin_file)
else:
df = pd.read_csv('penguins.csv')
return(df)
penguins_df = load_file(penguin_file)
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
selected_gender = st.selectbox('What gender do you want to filter for?',
['all penguins', 'male penguins', 'female penguins'])
if selected_gender == 'male penguins':
penguins_df = penguins_df[penguins_df['sex'] == 'male']
elif selected_gender == 'female penguins':
penguins_df = penguins_df[penguins_df['sex'] == 'female']
else:
pass
fig, ax = plt.subplots()
ax = sns.scatterplot(x=penguins_df[selected_x_var],
y=penguins_df[selected_y_var], hue=penguins_df['species'])
plt.xlabel(selected_x_var)
plt.ylabel(selected_y_var)
plt.title("Scatterplot of Palmer's Penguins: {}".format(selected_gender))
st.pyplot(fig)
现在,让我们运行这个应用程序,然后选择右上角的汉堡菜单图标,按下重新运行按钮(我们也可以直接按 R 键来重新运行)。
我们注意到,每次重新运行应用程序时,至少需要 3 秒钟。现在,让我们在 load_file 函数上方添加缓存装饰器,然后再次运行我们的应用程序:
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
mport seaborn as sns
import time
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
penguin_file = st.file_uploader(
'Select Your Local Penguins CSV (default provided)')
@st.cache()
def load_file(penguin_file):
time.sleep(3)
if penguin_file is not None:
df = pd.read_csv(penguin_file)
else:
df = pd.read_csv('penguins.csv')
return(df)
penguins_df = load_file(penguin_file)
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
selected_gender = st.selectbox('What gender do you want to filter for?',
['all penguins', 'male penguins', 'female penguins'])
if selected_gender == 'male penguins':
penguins_df = penguins_df[penguins_df['sex'] == 'male']
elif selected_gender == 'female penguins':
penguins_df = penguins_df[penguins_df['sex'] == 'female']
else:
pass
fig, ax = plt.subplots()
ax = sns.scatterplot(x=penguins_df[selected_x_var],
y=penguins_df[selected_y_var], hue=penguins_df['species'])
plt.xlabel(selected_x_var)
plt.ylabel(selected_y_var)
plt.title("Scatterplot of Palmer's Penguins: {}".format(selected_gender))
st.pyplot(fig)
当我们多次运行应用程序时,会注意到它明显变快了!当我们重新运行应用程序时,会发生两件事。首先,Streamlit 会检查缓存,确认是否曾经使用相同的输入运行过该函数,并从内存中返回 Penguins 数据;其次,它不会再次运行 load_file 函数,这意味着我们从未运行过 time.sleep(3) 命令,也没有花时间将数据加载到 Streamlit 中。我们将更详细地探讨这个缓存功能,但这个方法将为我们带来大部分的效率提升。
总结
本章充满了基础构件,这些构件将在本书的剩余部分中广泛使用,你也将用它们来开发自己的 Streamlit 应用程序。
在数据部分,我们介绍了如何将自己的 DataFrame 导入 Streamlit,如何接受以数据文件形式的用户输入,这使我们不再只能模拟数据。在其他技能方面,我们学习了如何使用缓存让数据应用程序运行得更快,如何控制 Streamlit 应用程序的流程,以及如何使用 st.write() 调试 Streamlit 应用程序。本章到此为止。接下来,我们将进入数据可视化部分!
第三章:第三章:数据可视化
可视化是现代数据科学家的基本技能。它通常是用来理解统计模型(例如,通过 AUC 图表)、关键变量的分布(通过直方图)或重要商业指标的核心视角。
在过去的两章中,我们在示例中使用了最流行的 Python 图表库(Matplotlib和Seaborn)。本章将重点扩展这些能力,涵盖多种 Python 图表库,并加入一些 Streamlit 的原生图表功能。
到本章结束时,你应该能够熟练使用 Streamlit 的原生图表功能,同时也能使用 Streamlit 的可视化功能,将通过主要 Python 可视化库生成的图表嵌入到自己的 Streamlit 应用中。
本章将涵盖以下主题:
-
旧金山树木——一个新数据集
-
Streamlit 内建的图表功能
-
Streamlit 内建的可视化选项
-
将 Python 可视化库导入到 Streamlit 中。在本节中,我们将介绍以下库:
(a) Plotly(用于交互式可视化)
(b) Seaborn+Matplotlib(用于经典的统计可视化)
(c) Bokeh(用于网页浏览器中的交互式可视化)
(d) Altair(用于声明式交互式可视化)
(e) PyDeck(用于交互式基于地图的可视化)
技术要求
本章将使用一个新数据集,可以在github.com/tylerjrichards/streamlit_apps/blob/main/trees_app/trees.csv找到。关于该数据集的进一步说明请参见下节内容。
旧金山树木——一个新数据集
在本章中,我们将处理各种各样的图表,因此需要一个包含更多信息的新数据集,特别是日期和位置。于是,我们引入了SF Trees。旧金山公共事务局拥有一个数据集(由 R 社区的优秀人员清理,他们运营 Tidy Tuesday,这是一个每周发布有趣数据可视化的活动),该数据集包含了旧金山城市中所有种植和维护的树木。这个数据集被巧妙地命名为EveryTreeSF – 城市森林地图,并且每天都会更新。我已随机选择了 10,000 棵树,并提供了完整信息,将该数据存放在主 GitHub 仓库的trees文件夹中(我承认,我的聪明程度不及旧金山 DPW 的数据工程师)。GitHub 仓库可以在github.com/tylerjrichards/streamlit_apps找到。如果你想下载完整数据集,可以点击此链接:data.sfgov.org/City-Infrastructure/Street-Tree-List/tkzw-k3nq。
在我们整个书籍过程中使用的主要 streamlit_apps 文件夹中,首先创建一个新文件夹,创建一个新的 Python 文件,然后将我们的数据下载到该文件夹中,就像我们在 第二章《上传、下载与处理数据》中所做的一样,只不过这次添加了一些新数据!你可以在终端中运行以下代码来完成设置:
mkdir trees_app
cd trees_app
touch trees.py
curl https://raw.githubusercontent.com/tylerjrichards/streamlit_apps/main/trees_app/trees.csv > trees.csv
在这里我想提到,如果这不奏效,或者你使用的操作系统没有这些命令(例如 Windows),你总是可以直接访问 GitHub 仓库下载 CSV 文件,如前面提到的仓库链接:github.com/tylerjrichards/streamlit_apps。
现在我们已经完成了设置,接下来的步骤是打开我们最喜欢的编辑器中的 trees.py 文件,开始构建我们的 Streamlit 应用。
注释
我们将在后续章节的开头继续按照这些步骤进行,因此将来我们不会再次详细介绍这些内容。
我们先通过以下代码给我们的应用命名,并打印出一些示例行:
import streamlit as st
import pandas as pd
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
st.write(trees_df.head())
然后我们可以在终端中运行以下命令,并在浏览器中查看生成的 Streamlit 应用:
streamlit run trees.py
请注意,这不是查看数据集前几行的最简单或最好的方法,但我们之所以这样做,完全是因为我们已经知道我们将使用这些数据构建一个 Streamlit 应用。一般的工作流通常是在 Streamlit 之外进行数据探索(通过 Jupyter 笔记本、SQL 查询,或者作为数据科学家或分析师你更喜欢的工作流)。也就是说,让我们继续查看数据集,并查看在浏览器中新 Streamlit 应用中执行上述代码的输出:

图 3.1 – 树木的前几行
这个数据集包含了关于旧金山树木的大量信息,从它们的直径(dbh)到经纬度、物种、地址,甚至是它们的种植日期。在我们开始绘制图表之前,先来了解一下我们面前的可视化选项。
Streamlit 可视化用例
一些 Streamlit 用户是经验丰富的 Python 开发者,拥有在自己选择的可视化库中经过充分测试的工作流。对于这些用户,最佳的前进路径就是我们迄今为止采取的方式,即在我们选择的库(如 Seaborn、Matplotlib、Bokeh 等)中创建图表,然后使用合适的 Streamlit 函数将其写入应用中。
其他 Streamlit 用户可能在 Python 图形绘制方面经验较少,尤其是对于这些用户,Streamlit 提供了一些内置的功能。我们将从内置库开始,并逐步学习如何将最流行和最强大的库导入到我们的 Streamlit 应用中。
Streamlit 内置的绘图功能
有三个内置的绘图函数——st.line_chart()、st.bar_chart() 和 st.area_chart()。它们的工作方式类似,通过试图找出你已经准备绘制的变量,然后分别将它们放入折线图、条形图或面积图。在我们的数据集中,有一个名为dbh的变量,它表示树木在胸部高度的直径。首先,我们可以按dbh对 DataFrame 进行分组,然后将其直接推送到折线图、条形图和面积图中。以下代码应该根据宽度对数据集进行分组,统计每个宽度的独特树木数量,并绘制每个的折线图、条形图和面积图:
import streamlit as st
import pandas as pd
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
df_dbh_grouped = pd.DataFrame(trees_df.groupby(['dbh']).count()['tree_id'])
df_dbh_grouped.columns = ['tree_count']
st.line_chart(df_dbh_grouped)
st.bar_chart(df_dbh_grouped)
st.area_chart(df_dbh_grouped)
上述代码应当显示我们三个图表,依次排列,如以下截图所示:

图 3.2 – 线条、条形、面积和树木高度
我们只给了函数一个 DataFrame,它就能够正确猜测应该将哪些项目放在x轴和y轴上,并将这些数据绘制到我们的 Streamlit 图表中。每个图表默认都是交互式的!我们可以进行缩放,悬停鼠标在点/条/线上的位置查看每个数据点,甚至可以查看全屏。这些 Streamlit 函数实际上是调用了另一个流行的图形库Altair,我们将在本章后面更深入地学习如何使用它。
现在我们了解了内置函数的基础(很明显,“内置”的定义在这里比较宽松,因为 Streamlit 被设计为一个很棒且方便的 Web 应用库,而不是一个很棒的可视化库),接下来我们将通过这些函数看看它们如何处理更多的数据。首先,我们将在df_dbh_grouped DataFrame 中使用numpy库生成一个-500 到 500 之间的随机数新列,并使用之前的绘图代码。以下代码绘制了两张折线图,一张是在我们添加新列之前,另一张是在添加新列之后:
import streamlit as st
import pandas as pd
import numpy as np
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
df_dbh_grouped = pd.DataFrame(trees_df.groupby(['dbh']).count()['tree_id'])
df_dbh_grouped.columns = ['tree_count']
st.line_chart(df_dbh_grouped)
df_dbh_grouped['new_col'] = np.random.randn(len(df_dbh_grouped)) * 500
st.line_chart(df_dbh_grouped)
这段代码应该生成一个应用,效果如以下截图所示:

图 3.3 – 两个连续的折线图
同样,这些函数将索引中的内容作为x轴,并将所有可以作为变量的列用作y轴。对于我们面对的非常简单的绘图问题(如示例中所示),这些内置函数非常有用。然而,相比于专门用于可视化的库,这些可视化函数的灵活性较差,调试这些函数的行为也可能会有些困难。这里的建议是,如果你的数据很容易被整理成一种格式,其中 DataFrame 的索引位于x轴,其他列绘制在y轴上,这些函数会表现得很好。对于更复杂的任务,我们应该使用其他绘图库。
还有一个我们应该在这里讨论的内置 Streamlit 图形函数,st.map()。就像前面的函数一样,它也是对另一个 Python 图形库的封装,这次是 st.map(),使用以下代码:
import streamlit as st
import pandas as pd
import numpy as np
t.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
trees_df = trees_df.dropna(subset=['longitude', 'latitude'])
trees_df = trees_df.sample(n = 1000)
st.map(trees_df)
这完美地工作,开箱即用!我们得到了一张美丽的旧金山树木交互式地图,正如下面的截图所示:

图 3.4 – 默认的旧金山树木地图
和其他函数一样,除了可选的缩放参数外,我们在这里没有太多自定义选项,但这对于快速可视化来说效果非常好。
如我们所见,这些内置函数对于快速制作 Streamlit 应用非常有用,但我们也牺牲了自定义性以换取速度。实际上,我在制作 Streamlit 应用时很少使用这些函数,但在快速可视化 Streamlit 中已有的数据时,常常会使用它们。在生产环境中,更强大的库,如 Matplotlib、Seaborn 和 PyDeck,为我们提供了所需的灵活性和可定制性。本章接下来将介绍六种不同的流行 Python 可视化库。
Streamlit 内置的可视化选项
本章剩余部分,我们将依次介绍 Streamlit 的其他可视化选项,分别是 Plotly、Matplotlib、Seaborn、Bokeh、Altair 和 PyDeck。
Plotly
Plotly 是一个交互式可视化库,许多数据科学家用它来在 Jupyter 中、在本地浏览器中,甚至在 Plotly 团队创建的可视化和仪表盘平台 Dash 上托管这些图表,以供查看。这个库与 Streamlit 的意图非常相似,主要用于内部或外部的仪表盘(因此得名 Dash)。
Streamlit 允许我们通过 st.plotly_chart() 函数在 Streamlit 应用中调用 plotly 图表,这使得迁移任何 Plotly 或 Dash 仪表盘变得非常简单。我们将通过制作旧金山树木高度的直方图来进行测试,这基本上就是我们之前制作的相同图表。以下代码生成了我们的 Plotly 直方图:
import streamlit as st
import pandas as pd
import plotly.express as px
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
st.subheader('Plotly Chart')
trees_df = pd.read_csv('trees.csv')
fig = px.histogram(trees_df['dbh'])
st.plotly_chart(fig)
如我们所注意到的,Plotly 的所有原生交互功能在 Streamlit 中默认是可以使用的。用户可以滚动查看直方图条形图并获得每个条形的准确信息。Plotly 还有一些其他有用的内置功能会传递到 Streamlit,例如缩放、将图表下载为 png 文件,以及选择一组数据点/条形/线条。完整的功能可以在以下截图中查看:

图 3.5 – 第一个 Plotly 图表
现在我们已经对 Plotly 足够熟悉,可以继续学习其他流行的可视化库,Matplotlib 和 Seaborn。
Matplotlib 和 Seaborn
本书前面,我们学习了如何在 Streamlit 中使用 Matplotlib 和 Seaborn 可视化库,因此这里我们只会简要讲解它们。树木数据集中有一列名为date,对应着树木栽种的日期。我们可以使用 datetime 库来计算每棵树的年龄(以天为单位),然后分别使用 Seaborn 和 Matplotlib 绘制该年龄的直方图。以下代码创建了一个名为 age 的新列,表示树木栽种日期与今天之间的天数差,然后使用 Seaborn 和 Matplotlib 分别绘制该年龄的直方图:
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import datetime as dt
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
trees_df['age'] = (pd.to_datetime('today') -
pd.to_datetime(trees_df['date'])).dt.days
st.subheader('Seaborn Chart')
fig_sb, ax_sb = plt.subplots()
ax_sb = sns.histplot(trees_df['age'])
plt.xlabel('Age (Days)')
st.pyplot(fig_sb)
st.subheader('Matploblib Chart')
fig_mpl, ax_mpl = plt.subplots()
ax_mpl = plt.hist(trees_df['age'])
plt.xlabel('Age (Days)')
st.pyplot(fig_mpl)
在前面的代码中,我们为每个图形定义了独特的子图,为每个图形创建了 Seaborn 或 Matplotlib 图形,然后使用st.pyplot()函数将每个图形按顺序插入到我们的 Streamlit 应用中。前面的代码应该会展示一个类似于以下屏幕截图的应用(我说“类似”是因为,根据你运行代码的时间,树木的年龄会有所不同,因为pd.to_datetime('today')将返回当前日期):

图 3.6 – Seaborn 和 Matplotlib 直方图
无论你使用 Seaborn 还是 Matplotlib,你都会以相同的方式使用st.pyplot()函数。既然我们已经对这些库更加熟悉,现在我们应该了解另一个交互式可视化库——Bokeh。
Bokeh
Bokeh 是另一个基于 Web 的交互式可视化库,且其上有构建仪表盘产品。它是 Plotly 的直接竞争对手,但更加专注于 Python 生态系统,而 Plotly 则是建立在Plotly.js之上的。无论如何,Bokeh 是一个极受欢迎的 Python 可视化包,Python 用户可能会非常熟悉使用它。
我们可以使用与 Plotly 相同的格式调用 Bokeh 图形。首先,我们创建 Bokeh 图形,然后使用st.bokeh_chart()函数将应用写入 Streamlit。在 Bokeh 中,我们必须先实例化一个 Bokeh 图形对象,然后在绘制之前更改该图形的一些属性。这里的重要教训是,如果我们在调用st.bokeh_chart()函数后更改 Bokeh 图形对象的某个属性,我们将不会改变 Streamlit 应用中显示的图形。例如,当我们运行以下代码时,我们将看不到新的x轴标题:
import streamlit as st
import pandas as pd
from bokeh.plotting import figure
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
st.subheader('Bokeh Chart')
trees_df = pd.read_csv('trees.csv')
scatterplot = figure(title = 'Bokeh Scatterplot')
scatterplot.scatter(trees_df['dbh'], trees_df['site_order'])
st.bokeh_chart(scatterplot)
scatterplot.xaxis.axis_label = "dbh"
相反,我们需要交换最后两行代码的顺序,这样它们就会在应用中显示出来。我们还将添加一个y轴以确保图形的完整性。
import streamlit as st
import pandas as pd
from bokeh.plotting import figure
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
st.subheader('Bokeh Chart')
trees_df = pd.read_csv('trees.csv')
scatterplot = figure(title = 'Bokeh Scatterplot')
scatterplot.scatter(trees_df['dbh'], trees_df['site_order'])
scatterplot.yaxis.axis_label = "site_order"
scatterplot.xaxis.axis_label = "dbh"
st.bokeh_chart(scatterplot)
前面的代码将创建一个 Bokeh 图表,展示dbh与site_order的关系,如下所示的屏幕截图:

图 3.7 – Bokeh 散点图
接下来,我们将介绍下一个可视化库——Altair!
Altair
我们已经使用过 st.line_chart() 或 st.map(),但现在我们将探索如何直接使用 Altair。Altair 是一个声明式可视化库,简单来说,意味着我们不需要显式地编写图表中的每个特性(如命名 x 轴),我们只需传递列之间的关系,Altair 会自动处理剩下的部分。
我们已经用这个数据集制作了不少图表,但为什么不探索一下新的列——看护人列呢?这一数据定义了谁负责照看这些树(公共还是私有),如果是公共的,那么是哪个政府机构负责维护。真是激动人心!
以下代码将我们的 DataFrame 按照看护人进行分组,然后在 Altair 中使用这个分组后的 DataFrame:
import streamlit as st
import pandas as pd
import altair as alt
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
df_caretaker = trees_df.groupby(['caretaker']).count()['tree_id'].reset_index()
df_caretaker.columns = ['caretaker', 'tree_count']
fig = alt.Chart(df_caretaker).mark_bar().encode(x = 'caretaker', y = 'tree_count')
st.altair_chart(fig)
Altair 还允许我们直接在 mark_bar() 的 y 值中汇总数据,因此我们可以通过以下代码来简化操作:
import streamlit as st
import pandas as pd
import altair as alt
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
fig = alt.Chart(trees_df).mark_bar().encode(x = 'caretaker', y = 'count(*):Q')
st.altair_chart(fig)
上述代码将创建一个 Streamlit 应用,展示 SF 中按照看护人分类的树木数量,如下图所示:

图 3.8 – Altair 条形图
对于传统的可视化库,这应该就是全部了,但 Streamlit 还允许我们使用更复杂的可视化库,如 st.map() 函数,并将在下一部分中深入探讨这个功能。
PyDeck
PyDeck 是一个可视化库,可以将可视化作为图层绘制在 Mapbox(一家拥有真正出色免费套餐的地图公司)地图上。Streamlit 和 PyDeck 都提供了一个基础的功能集,在未注册 Mapbox 账户的情况下可以使用,但当我们获得 Mapbox 令牌后,这些功能将大大扩展,我们将在下一部分获取该令牌。
配置选项
为了设置你自己的 config.toml,我们可以在终端中的任何地方运行以下命令来查看当前设置:
streamlit config show
Streamlit 提供了四种更改默认配置设置的方法,我将向你展示我推荐的选项和另一个选项,这两个选项应该能满足大多数使用场景。如果你觉得这些选项不足,Streamlit 文档(docs.streamlit.io/)会详细介绍这四种方法。
第一个选项是通过直接编辑 config.toml 文件来设置全局配置选项。我们可以通过文本编辑器直接打开该文件进行编辑。以下命令将在 sublime 中打开该文件。对于其他文本编辑器(如 Vim 和 Atom),请将 'sublime' 替换为相应的命令,或直接从文本编辑器打开该文件:
sublime ~/.streamlit/config.toml
如果失败了,这很可能意味着我们还没有生成文件。我们可以复制并粘贴 ~/.streamlit/config.toml 的输出,或者可以在 Mac/Linux 上运行以下快捷方式:
streamlit config show > ~/.streamlit/config.toml
现在,我们已经在sublime中打开了文件,可以直接查看并编辑任何选项。这个选项非常适合配置选项,比如8501 serverPort。改变全局选项来进行项目特定的更改是没有意义的,这也引出了第二个选项。
第二个选项是创建并编辑一个项目特定的config.toml文件。我们之前的配置设置了默认的配置选项,而这个选项则是针对每个 Streamlit 应用程序的特定设置。此时,我们在streamlit_apps文件夹中的每个单独项目文件夹就派上用场了!
从大致来说,我们将执行以下操作:
-
检查我们的当前工作目录。
-
为我们的项目创建一个配置文件。
-
在 PyDeck 中使用配置文件。
我们的第一步是确保当前工作目录是trees_app文件夹,可以通过在终端中运行pwd命令来查看当前工作目录,结果应该以trees_app结尾(例如,我的路径是Users/tyler/Documents/streamlit_apps/trees_app)。
现在,我们需要为我们的项目制作一个配置文件。首先,我们将创建一个名为.streamlit的文件夹,然后我们将重复之前在 Mac/Linux 上使用的快捷方式:
mkdir .streamlit
streamlit config show > .streamlit/config.toml
然后,我们可以像之前一样编辑我们的配置选项,但这将仅适用于我们从该目录运行 Streamlit 时的 Streamlit 应用程序。
现在,最后,我们可以回到37.77, -122.4。我们可以使用以下代码来实现,代码首先定义初始状态(我们希望从哪里开始查看地图),然后使用该初始状态调用st.pydeck_chart():
import streamlit as st
import pandas as pd
import pydeck as pdk
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
sf_initial_view = pdk.ViewState(
latitude=37.77,
longitude=-122.4
)
st.pydeck_chart(pdk.Deck(
initial_view_state=sf_initial_view
))
这段代码将生成一张旧金山的地图,我们可以用它来叠加数据点。我们在这里注意到几件事。首先,黑色的默认地图可能不太容易看到,其次,我们需要花时间缩放到旧金山以获得所需的视图。我们可以通过使用 Streamlit 文档中建议的默认值来修复这两个问题(docs.streamlit.io/),如下代码所示:
import streamlit as st
import pandas as pd
import pydeck as pdk
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
sf_initial_view = pdk.ViewState(
latitude=37.77,
longitude=-122.4,
zoom=9
)
st.pydeck_chart(pdk.Deck(
map_style='mapbox://styles/mapbox/light-v9',
initial_view_state=sf_initial_view,
))
上面的代码应该会创建一张如下截图所示的地图:

图 3.9 – PyDeck 映射:旧金山基础地图
这正是我们需要的!我们可以看到整个11,所以可以更好地看到每个数据点:
import streamlit as st
import pandas as pd
import pydeck as pdk
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
trees_df.dropna(how='any', inplace=True)
sf_initial_view = pdk.ViewState(
latitude=37.77,
longitude=-122.4,
zoom=11
)
sp_layer = pdk.Layer(
'ScatterplotLayer',
data = trees_df,
get_position = ['longitude', 'latitude'],
get_radius=30)
st.pydeck_chart(pdk.Deck(
map_style='mapbox://styles/mapbox/light-v9',
initial_view_state=sf_initial_view,
layers = [sp_layer]
))
缩放和半径参数的最佳值取决于您的可视化偏好。尝试一些选项,看看哪种效果最好。上面的代码将生成以下地图:

图 3.10 – 映射旧金山的树木
与之前的地图一样,这个地图默认是互动的,因此我们可以缩放到旧金山的不同区域,查看树木密度最高的地方。对于地图的下一个变化,我们将添加另一个图层,这一次是六边形图层,将根据旧金山的树木密度进行着色。我们可以使用与上面相同的代码,但将散点图层更改为六边形图层。我们还将包括一个选项,可以让六边形在垂直方向上突出显示,虽然这不是必须的,但绝对是一种有趣的可视化风格。
我们最后的改动是更改我们查看地图时的视角或角度。默认的视角,如我们所见,几乎是直接向下查看城市,但如果我们要在地图上查看垂直的六边形,这个角度将无法使用。以下代码实现了这些变化:
import streamlit as st
import pandas as pd
import pydeck as pdk
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
trees_df.dropna(how='any', inplace=True)
sf_initial_view = pdk.ViewState(
latitude=37.77,
longitude=-122.4,
zoom=11,
pitch=30
)
hx_layer = pdk.Layer(
'HexagonLayer',
data = trees_df,
get_position = ['longitude', 'latitude'],
radius=100,
extruded=True)
st.pydeck_chart(pdk.Deck(
map_style='mapbox://styles/mapbox/light-v9',
initial_view_state=sf_initial_view,
layers = [hx_layer]
))
与之前的地图一样,最佳的半径和视角参数将根据你的可视化效果而有所不同。试着更改这些参数几次,看看是否能掌握其诀窍!上面的代码将生成以下应用程序:

图 3.11 – 最终的旧金山树木地图
从这张截图中,我们可以看到PyDeck在旧金山的树木密度较高的地方绘制了较暗的圆圈。从中我们可以观察到很多有趣的细节,比如数据集似乎缺少了位于城市西侧著名的金门公园中的树木,而且金门大桥周围的区域似乎也在数据集中几乎没有树木。
总结
在这一章之后,你应该能够牢固掌握如何利用令人惊叹的开源 Python 可视化库来在 Streamlit 中创建 Web 应用程序。
首先,我们学习了如何使用默认的可视化选项,如st.line_chart()和st.map(),然后我们深入了解了像 Plotly 这样的交互式库,像 PyDeck 这样的地图绘图库,以及介于两者之间的一切。
在下一章中,我们将讲解如何在 Streamlit 中使用机器学习。
第四章:第四章:在 Streamlit 中使用机器学习
数据科学家常常遇到的一个非常普遍的情况是在模型创建过程结束时,不知道如何说服非数据科学家相信他们的模型是有价值的。他们可能有模型的性能指标或一些静态的可视化效果,但没有简单的方法让其他人与他们的模型进行交互。
在使用 Streamlit 之前,还有其他一些选择,其中最流行的是在 Flask 或 Django 中创建一个完整的应用程序,或者将模型转换为应用程序编程接口(API),并引导开发人员使用它。这些选项很棒,但往往耗时且不适合像应用程序原型制作这样的宝贵使用案例。
团队中的激励有点不一致。数据科学家希望为团队创建最好的模型,但如果他们需要花一两天时间(或者,如果有经验的话,几个小时)将他们的模型转化为 Flask 或 Django 应用,那么直到他们认为建模过程接近完成时,才去构建这个应用就没有太大意义。Streamlit 的好处在于,它帮助我们将这个繁琐的过程转化为无摩擦的应用创建体验。在本章中,我们将介绍如何在 Streamlit 中创建机器学习(ML)原型,如何为你的机器学习应用添加用户交互,以及如何理解机器学习的结果。
本章将涉及以下主题:
-
标准的机器学习工作流
-
预测企鹅物种
-
在 Streamlit 中使用预训练的机器学习模型
-
在 Streamlit 应用中训练模型
-
理解机器学习的结果
标准的机器学习(ML)工作流
创建一个使用机器学习的应用程序的第一步是机器学习模型本身。创建你自己的机器学习模型有许多流行的工作流,你很可能已经有自己的方法了!这个过程有两个部分需要考虑:
-
机器学习模型的生成
-
在生产中使用机器学习模型
如果计划是训练一次模型然后将此模型用于我们的 Streamlit 应用,那么最好的方法是先在 Streamlit 外部创建该模型(例如,在 Jupyter notebook 中或在标准的 Python 文件中),然后在应用中使用该模型。
如果计划是使用用户输入来训练我们应用程序中的模型,那么我们就不能再在 Streamlit 外部创建模型,而是需要在 Streamlit 应用程序内部运行模型训练。
我们将首先在 Streamlit 外部构建机器学习模型,然后再转向在 Streamlit 应用中训练模型。
预测企鹅物种
本章中我们主要使用的数据集是与 第一章,《Streamlit 简介》中使用的相同的 Palmer's Penguins 数据集。像往常一样,我们将创建一个新的文件夹,用于存放我们的 Streamlit 应用和相关代码。以下代码将在streamlit_apps文件夹内创建该新文件夹,并将数据从我们的penguin_app文件夹复制过来。如果你还没有下载 Palmer's Penguins 数据,请按照第二章,《上传、下载和处理数据》中的说明操作:
mkdir penguin_ml
cp penguin_app/penguins.csv penguin_ml
cd penguin_ml
touch penguins_ml.py
touch penguins_streamlit.py
正如你在前面的代码中可能注意到的那样,这里有两个 Python 文件,一个用于创建机器学习模型(penguins_ml.py),另一个用于创建 Streamlit 应用(penguins_streamlit.py)。我们将从penguins_ml.py文件开始,一旦我们得到了满意的模型,就会转到penguins_streamlit.py文件。
注意
你也可以选择在 Jupyter notebook 中创建模型,虽然这种方式设计上较不易复现(因为单元格可以乱序运行),但仍然是非常受欢迎的做法。
让我们重新熟悉一下penguins.csv数据集。以下代码将读取数据集并打印出前五行:
import pandas as pd
penguin_df = pd.read_csv('penguins.csv')
print(penguin_df.head())
当我们在终端运行 Python 文件penguins_ml.py时,前面代码的输出将类似以下截图所示:

图 4.1 – 前五只企鹅
对于这个应用,我们将尝试创建一个帮助野外研究人员识别企鹅物种的应用。该应用将根据企鹅的喙、鳍和体重的测量值,以及关于企鹅性别和位置的信息来预测企鹅的物种。
接下来的这一部分并不是为了创建最好的机器学习模型,而只是为了快速为我们的 Streamlit 应用创建一个原型,方便我们在此基础上进行迭代。因此,从这个角度来看,我们将删除含有空值的几行,并且不使用year变量作为特征,因为它与我们的使用场景不匹配。我们需要定义特征和输出变量,对特征进行独热编码(或者像 pandas 所说的,为我们的文本列创建虚拟变量),并对输出变量进行因子化(将其从字符串转换为数字)。以下代码应该能将我们的数据集整理得更适合进行分类算法的运行:
import pandas as pd
penguin_df = pd.read_csv('penguins.csv')
penguin_df.dropna(inplace=True)
output = penguin_df['species']
features = penguin_df[['island', 'bill_length_mm', 'bill_depth_mm',
'flipper_length_mm', 'body_mass_g', 'sex']]
features = pd.get_dummies(features)
print('Here are our output variables')
print(output.head())
print('Here are our feature variables')
print(features.head())
现在当我们再次运行 Python 文件penguins_ml.py时,我们会看到输出变量和特征变量已经分开,如下图所示:

图 4.2 – 输出变量
现在,我们希望使用我们数据的一个子集(在此情况下为 80%)来创建一个分类模型,并获取该模型的准确性。以下代码通过随机森林模型执行这些步骤,但如果你愿意,也可以使用其他分类算法。再次强调,这里的目的是为了快速制作一个原型,展示给企鹅研究人员,获取反馈!
import pandas as pd
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
penguin_df = pd.read_csv('penguins.csv')
penguin_df.dropna(inplace=True)
output = penguin_df['species']
features = penguin_df[['island', 'bill_length_mm', 'bill_depth_mm',
'flipper_length_mm', 'body_mass_g', 'sex']]
features = pd.get_dummies(features)
output, uniques = pd.factorize(output)
x_train, x_test, y_train, y_test = train_test_split(
features, output, test_size=.8)
rfc = RandomForestClassifier(random_state=15)
rfc.fit(x_train, y_train)
y_pred = rfc.predict(x_test)
score = accuracy_score(y_pred, y_test)
print('Our accuracy score for this model is {}'.format(score))
现在我们已经有了一个相当不错的模型来预测企鹅的物种!我们在模型生成过程中的最后一步是保存我们最需要的这两部分模型——模型本身和uniques变量,它将因子化的输出变量映射到我们可以识别的物种名称。对于之前的代码,我们将添加几行代码,用来将这些对象保存为 pickle 文件(pickle 文件是将 Python 对象转化为可以直接保存并且从其他 Python 文件中轻松导入的格式,譬如我们的 Streamlit 应用程序)。更具体来说,open()函数创建了两个 pickle 文件,pickle.dump()函数将我们的 Python 文件写入这些文件,close()函数用于关闭文件。open()函数中的wb表示写入字节,这告诉 Python 我们要写入文件,而不是读取文件:
import pandas as pd
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
import pickle
penguin_df = pd.read_csv('penguins.csv')
penguin_df.dropna(inplace=True)
output = penguin_df['species']
features = penguin_df[['island', 'bill_length_mm', 'bill_depth_mm',
'flipper_length_mm', 'body_mass_g', 'sex']]
features = pd.get_dummies(features)
output, uniques = pd.factorize(output)
x_train, x_test, y_train, y_test = train_test_split(
features, output, test_size=.8)
rfc = RandomForestClassifier(random_state=15)
rfc.fit(x_train, y_train)
y_pred = rfc.predict(x_test)
score = accuracy_score(y_pred, y_test)
print('Our accuracy score for this model is {}'.format(score))
rf_pickle = open('random_forest_penguin.pickle', 'wb')
pickle.dump(rfc, rf_pickle)
rf_pickle.close()
output_pickle = open('output_penguin.pickle', 'wb')
pickle.dump(uniques, output_pickle)
output_pickle.close()
现在,我们的penguin_ml文件夹中有了两个新的文件,一个是名为random_forest_penguin.pickle的文件,里面包含我们的模型,另一个是output_penguin_.pickle文件,它包含了企鹅物种和模型输出之间的映射关系。这就是penguins_ml.py函数的全部内容!我们可以继续进行 Streamlit 应用程序的开发。
在 Streamlit 中使用预训练的 ML 模型
现在我们已经有了我们的模型,我们希望将它(以及我们的映射函数)加载到 Streamlit 中。在我们之前创建的文件penguins_streamlit.py中,我们将再次使用pickle库,通过以下代码加载我们的文件。我们使用与之前相同的函数,但这次不是使用wb,而是使用rb参数,表示读取字节。为了确保这些是我们之前使用的相同的 Python 对象,我们将使用我们非常熟悉的st.write()函数来进行检查:
import streamlit as st
import pickle
rf_pickle = open('random_forest_penguin.pickle', 'rb')
map_pickle = open('output_penguin.pickle', 'rb')
rfc = pickle.load(rf_pickle)
unique_penguin_mapping = pickle.load(map_pickle)
st.write(rfc)
st.write(unique_penguin_mapping)
和我们之前的 Streamlit 应用程序一样,我们在终端中运行以下代码来启动我们的应用程序:
streamlit run penguins_streamlit.py
现在我们已经有了随机森林分类器,并且有了企鹅映射!我们的下一步是添加 Streamlit 函数以获取用户输入。在我们的应用程序中,我们使用了岛屿、喙长、喙深、鳍肢长、体重和性别来预测企鹅的物种,因此我们需要从用户那里获取这些信息。对于岛屿和性别,我们已经知道数据集中有哪些选项,并且希望避免解析用户的文本,因此我们将使用selectbox。对于其他数据,我们只需要确保用户输入的是一个正数,因此我们将使用st.number_input()函数,并将最小值设置为0。以下代码将这些输入值获取并在 Streamlit 应用程序中显示出来:
import streamlit as st
import pickle
rf_pickle = open('random_forest_penguin.pickle', 'rb')
map_pickle = open('output_penguin.pickle', 'rb')
rfc = pickle.load(rf_pickle)
unique_penguin_mapping = pickle.load(map_pickle)
rf_pickle.close()
map_pickle.close()
island = st.selectbox('Penguin Island', options=[
'Biscoe', 'Dream', 'Torgerson'])
sex = st.selectbox('Sex', options=['Female', 'Male'])
bill_length = st.number_input('Bill Length (mm)', min_value=0)
bill_depth = st.number_input('Bill Depth (mm)', min_value=0)
flipper_length = st.number_input('Flipper Length (mm)', min_value=0)
body_mass = st.number_input('Body Mass (g)', min_value=0)
st.write('the user inputs are {}'.format(
[island, sex, bill_length,
bill_depth, flipper_length, body_mass]))
前面的代码应该生成如下应用。试试看,修改数值,看看输出是否会有所变化。Streamlit 的设计使得默认情况下,每次更改一个值,整个应用都会重新运行。下面的截图显示了正在运行的应用,展示了一些我更改过的数值。我们可以通过右侧的(+ 和 -)按钮来修改数值,或者直接手动输入数值:

图 4.3 – 模型输入
现在我们有了所有的输入,并且也有了我们的模型。下一步是将数据格式化成与我们预处理数据相同的格式,例如,我们的模型没有一个叫做 sex 的变量,而是有两个叫做 sex_female 和 sex_male 的变量。一旦我们的数据格式正确,我们就可以调用 predict 函数,并将预测结果映射到我们原始的物种列表上,看看模型是如何工作的。以下代码正是完成这个任务的,同时还为应用添加了一些基本的标题和说明,以便用户使用。这个应用比较长,所以我会将它分成多个部分来提高可读性,从为应用添加说明和标题开始:
import streamlit as st
import pickle
st.title('Penguin Classifier')
st.write("This app uses 6 inputs to predict the species of penguin using"
"a model built on the Palmer's Penguin's dataset. Use the form below"
" to get started!")
rf_pickle = open('random_forest_penguin.pickle', 'rb')
map_pickle = open('output_penguin.pickle', 'rb')
rfc = pickle.load(rf_pickle)
unique_penguin_mapping = pickle.load(map_pickle)
rf_pickle.close()
map_pickle.close()
现在我们的应用已经有了标题和用户操作说明。下一步是像之前一样获取用户输入。我们还需要将 sex 和 island 变量转化为正确的格式,如前所述:
island = st.selectbox('Penguin Island', options=[
'Biscoe', 'Dream', 'Torgerson'])
sex = st.selectbox('Sex', options=['Female', 'Male'])
bill_length = st.number_input('Bill Length (mm)', min_value=0)
bill_depth = st.number_input('Bill Depth (mm)', min_value=0)
flipper_length = st.number_input('Flipper Length (mm)', min_value=0)
body_mass = st.number_input('Body Mass (g)', min_value=0)
island_biscoe, island_dream, island_torgerson = 0, 0, 0
if island == 'Biscoe':
island_biscoe = 1
elif island == 'Dream':
island_dream = 1
elif island == 'Torgerson':
island_torgerson = 1
sex_female, sex_male = 0, 0
if sex == 'Female':
sex_female = 1
elif sex == 'Male':
sex_male = 1
我们的数据格式已经完全正确!最后一步是使用 predict() 函数在我们的模型上进行预测,并使用新数据,这一部分代码已经完成:
new_prediction = rfc.predict([[bill_length, bill_depth, flipper_length,
body_mass, island_biscoe, island_dream,
island_torgerson, sex_female, sex_male]])
prediction_species = unique_penguin_mapping[new_prediction][0]
st.write('We predict your penguin is of the {} species'.format(prediction_species))
现在我们的应用应该像下面的截图一样。我在输入框中添加了一些示例值,但你应该尝试更改数据,看看是否能够改变物种预测!

图 4.4 – 完整的 Streamlit 预测
我们现在有一个完整的 Streamlit 应用,它利用了我们预训练的机器学习模型,接收用户输入并输出预测结果。接下来,我们将讨论如何在 Streamlit 应用中直接训练模型!
在 Streamlit 应用中训练模型
我们常常希望让用户输入改变我们模型的训练方式。我们可能想接受用户的数据,或者询问用户他们希望使用哪些特征,甚至允许用户选择他们希望使用的机器学习算法类型。所有这些选项在 Streamlit 中都是可行的,在本节中,我们将介绍如何使用用户输入来影响训练过程的基础知识。正如我们在上节中所讨论的那样,如果一个模型只需要训练一次,那么最好是在 Streamlit 外部训练模型,并将模型导入 Streamlit。但如果在我们的例子中,企鹅研究人员的数据已经存储在本地,或者他们不知道如何重新训练模型,但数据已处于正确的格式呢?在这种情况下,我们可以添加st.file_uploader()选项,并为这些用户提供一种输入自己数据的方法,从而无需编写任何代码就能为他们部署定制的模型。以下代码将添加一个用户选项以接受数据,并使用我们在penguins_ml.py中原本的预处理/训练代码为该用户制作一个独特的模型。这里需要注意的是,只有当用户的数据格式和风格与我们使用的完全相同时,这种方法才有效,这种情况可能比较少见。另一个可能的附加功能是向用户展示数据需要符合什么格式,以便此应用程序能够正确训练模型!
import streamlit as st
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import pickle
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
st.title('Penguin Classifier')
st.write("This app uses 6 inputs to predict the species of penguin using "
"a model built on the Palmer's Penguin's dataset. Use the form below"
" to get started!")
penguin_file = st.file_uploader('Upload your own penguin data')
本节导入我们需要的库,添加标题——如我们之前所用,并添加file_uploader()函数。然而,当用户尚未上传文件时会发生什么呢?我们可以将默认值设置为加载我们的随机森林模型,如果没有企鹅文件,如下一节代码所示:
if penguin_file is None:
rf_pickle = open('random_forest_penguin.pickle', 'rb')
map_pickle = open('output_penguin.pickle', 'rb')
rfc = pickle.load(rf_pickle)
unique_penguin_mapping = pickle.load(map_pickle)
rf_pickle.close()
map_pickle.close()
接下来的问题是如何接收用户的数据,清洗它,并基于这些数据训练一个模型。幸运的是,我们可以重复使用已经创建的模型训练代码,并将其放入下一个代码块中的else语句中:
else:
penguin_df = pd.read_csv(penguin_file)
penguin_df = penguin_df.dropna()
output = penguin_df['species']
features = penguin_df[['island', 'bill_length_mm', 'bill_depth_mm',
'flipper_length_mm', 'body_mass_g', 'sex']]
features = pd.get_dummies(features)
output, unique_penguin_mapping = pd.factorize(output)
x_train, x_test, y_train, y_test = train_test_split(
features, output, test_size=.8)
rfc = RandomForestClassifier(random_state=15)
rfc.fit(x_train, y_train)
y_pred = rfc.predict(x_test)
score = round(accuracy_score(y_pred, y_test), 2)
st.write('We trained a Random Forest model on these data,'
' it has a score of {}! Use the '
'inputs below to try out the model.'.format(score))
我们现在已经在应用内创建了模型,需要从用户那里获取预测输入。不过,这一次,我们可以对之前的做法进行改进。到目前为止,每次用户更改应用中的输入时,整个 Streamlit 应用都会重新运行。我们可以使用st.form()和st.submit_form_button()函数将其余的用户输入包裹起来,让用户一次性提交整个表单,而不是每次都提交:
with st.form('user_inputs'):
island = st.selectbox('Penguin Island', options=[
'Biscoe', 'Dream', 'Torgerson'])
sex = st.selectbox('Sex', options=['Female', 'Male'])
bill_length = st.number_input('Bill Length (mm)', min_value=0)
bill_depth = st.number_input('Bill Depth (mm)', min_value=0)
flipper_length = st.number_input('Flipper Length (mm)', min_value=0)
body_mass = st.number_input('Body Mass (g)', min_value=0)
st.form_submit_button()
island_biscoe, island_dream, island_torgerson = 0, 0, 0
if island == 'Biscoe':
island_biscoe = 1
elif island == 'Dream':
island_dream = 1
elif island == 'Torgerson':
island_torgerson = 1
sex_female, sex_male = 0, 0
if sex == 'Female':
sex_female = 1
elif sex == 'Male':
sex_male = 1
现在我们已经有了新表单的输入,我们需要创建我们的预测并将预测结果写给用户,如下一个代码块所示:
new_prediction = rfc.predict([[bill_length, bill_depth, flipper_length,
body_mass, island_biscoe, island_dream,
island_torgerson, sex_female, sex_male]])
prediction_species = unique_penguin_mapping[new_prediction][0]
st.write('We predict your penguin is of the {} species'.format(prediction_species))
就这样!我们现在有了一个 Streamlit 应用,允许用户输入自己的数据,并根据他们的数据训练模型并输出结果,如下图所示:

图 4.5 – 企鹅分类器预测
这里有潜在的改进方法,例如使用缓存功能(可以参考 第二章,上传、下载和操作数据)。像这样允许用户上传数据的应用要构建起来更为困难,尤其是在没有统一数据格式的情况下。截至目前,看到展示出色的机器学习模型和使用案例的 Streamlit 应用比直接在应用内构建模型的应用要更为常见(尤其是对于计算开销较大的模型训练)。正如我们之前提到的,Streamlit 开发者通常会在要求用户输入数据集之前提供必要的数据格式参考。然而,允许用户带入自己数据的选项仍然是可用且实用的,尤其是可以快速迭代模型构建。
理解机器学习结果
到目前为止,我们的应用可能很有用,但仅仅展示结果对于一个数据应用来说并不够。我们还应该解释一下为什么会得到这样的结果!为了做到这一点,我们可以在应用输出中添加一个帮助用户更好理解模型的部分。
首先,随机森林模型已经具备了一个内建的特征重要性方法,该方法来源于组成随机森林的单个决策树集合。我们可以编辑我们的 penguins_ml.py 文件来绘制这个特征重要性图,然后在我们的 Streamlit 应用中调用该图像。我们也可以直接在 Streamlit 应用中绘制这个图形,但在 penguins_ml.py 中绘制一次会更高效,而不是每次我们的 Streamlit 应用重新加载时(即每次用户更改输入时)都绘制一次。以下代码编辑了我们的 penguins_ml.py 文件并添加了特征重要性图,将其保存到我们的文件夹中。我们还调用了 tight_layout() 功能,这有助于更好地格式化我们的图表,并确保避免任何标签被截断。由于这段代码较长,文件的前半部分保持不变,因此只省略了库导入和数据清洗部分:
x_train, x_test, y_train, y_test = train_test_split(
features, output, test_size=.8)
rfc = RandomForestClassifier(random_state=15)
rfc.fit(x_train, y_train)
y_pred = rfc.predict(x_test)
score = accuracy_score(y_pred, y_test)
print('Our accuracy score for this model is {}'.format(score))
rf_pickle = open('random_forest_penguin.pickle', 'wb')
pickle.dump(rfc, rf_pickle)
rf_pickle.close()
output_pickle = open('output_penguin.pickle', 'wb')
pickle.dump(uniques, output_pickle)
output_pickle.close()
fig, ax = plt.subplots()
ax = sns.barplot(rfc.feature_importances_, features.columns)
plt.title('Which features are the most important for species prediction?')
plt.xlabel('Importance')
plt.ylabel('Feature')
plt.tight_layout()
fig.savefig('feature_importance.png')
现在,当我们重新运行 pengiuns_ml.py 时,应该能看到一个名为 feature_importance.png 的文件,我们可以从我们的 Streamlit 应用中调用这个文件。我们现在来做这个!我们可以使用 st.image() 函数加载我们 png 文件中的图像,并将其显示到我们的企鹅应用中。以下代码将我们的图像添加到 Streamlit 应用中,并且改进了我们关于预测的解释。由于这个代码块的长度,我们只展示从开始使用用户数据进行预测的代码部分:
new_prediction = rfc.predict([[bill_length, bill_depth, flipper_length,
body_mass, island_biscoe, island_dream,
island_torgerson, sex_female, sex_male]])
prediction_species = unique_penguin_mapping[new_prediction][0]
st.subheader("Predicting Your Penguin's Species:")
st.write('We predict your penguin is of the {} species'
.format(prediction_species))
st.write('We used a machine learning (Random Forest) model to '
'predict the species, the features used in this prediction '
' are ranked by relative importance below.')
st.image('feature_importance.png')
现在,您的 Streamlit 应用的底部应该看起来像下图所示(注意:您的字符串可能会因输入的不同而略有不同)。

图 4.6 – 特征重要性截图
如我们所见,喙长、喙深和鳍肢长是根据我们的随机森林模型最重要的变量。解释我们模型工作原理的最终选项是按物种绘制这些变量的分布图,并绘制一些垂直线表示用户输入。理想情况下,用户可以开始全面理解基础数据,从而理解模型给出的预测结果。为此,我们需要将数据导入到 Streamlit 应用程序中,这是我们之前没有做过的。以下代码导入了我们用来构建模型的企鹅数据,并绘制了三张直方图(分别为喙长、喙深和鳍肢长),同时将用户输入作为垂直线显示,从模型解释部分开始:
st.subheader("Predicting Your Penguin's Species:")
st.write('We predict your penguin is of the {} species'
.format(prediction_species))
st.write('We used a machine learning (Random Forest) model to '
'predict the species, the features used in this prediction '
' are ranked by relative importance below.')
st.image('feature_importance.png')
st.write('Below are the histograms for each continuous variable '
'separated by penguin species. The vertical line '
'represents your the inputted value.')
现在我们已经为直方图设置好了应用程序,我们可以使用 Seaborn 可视化库中的displot()函数来为我们最重要的变量创建三个直方图:
fig, ax = plt.subplots()
ax = sns.displot(x=penguin_df['bill_length_mm'],
hue=penguin_df['species'])
plt.axvline(bill_length)
plt.title('Bill Length by Species')
st.pyplot(ax)
fig, ax = plt.subplots()
ax = sns.displot(x=penguin_df['bill_depth_mm'],
hue=penguin_df['species'])
plt.axvline(bill_depth)
plt.title('Bill Depth by Species')
st.pyplot(ax)
fig, ax = plt.subplots()
ax = sns.displot(x=penguin_df['flipper_length_mm'],
hue=penguin_df['species'])
plt.axvline(flipper_length)
plt.title('Flipper Length by Species')
st.pyplot(ax)
上面的代码应该会创建出下图所示的应用程序,这是我们最终形式的应用。为了方便查看,我们将仅展示第一个直方图:

图 4.6 – 按物种分类的喙长
和往常一样,完整的最终代码可以在github.com/tylerjrichards/Getting-Started-with-Streamlit-for-Data-Science找到。这部分内容到此结束。我们现在已经创建了一个完整的 Streamlit 应用程序,它能够接受预先构建的模型和用户输入,并输出预测结果和解释输出。
总结
在本章中,我们学习了一些机器学习基础:如何在 Streamlit 中使用预构建的机器学习模型,如何在 Streamlit 中创建我们自己的模型,以及如何使用用户输入来理解和迭代机器学习模型。希望在本章结束时,你能对这些内容感到熟悉。接下来,我们将深入了解如何使用 Streamlit Sharing 部署 Streamlit 应用程序!
第五章:第五章:使用 Streamlit Sharing 部署 Streamlit
迄今为止,本书主要关注 Streamlit 应用开发,从创建复杂的可视化到部署和构建 机器学习(ML)模型。本章将学习如何部署这些应用,以便它们可以与任何拥有互联网连接的人共享。这是 Streamlit 应用的重要部分,因为如果没有部署 Streamlit 应用的能力,用户或你的作品消费者仍然会面临一定的使用障碍。如果我们认为 Streamlit 消除了在创建数据科学分析/产品/模型与与他人共享它们之间的障碍,那么我们也必须相信,广泛共享应用的能力和开发的简便性同样重要。
部署 Streamlit 应用有三种主要方式:通过 Streamlit 创建的产品 Streamlit Sharing,通过云服务提供商如 Amazon Web Services 或 Heroku,或通过 Streamlit 创建的另一款产品 Streamlit for Teams。这三种方式都需要付费,但 Streamlit Sharing 提供了免费版,而 Amazon Web Services 经常为学生、首次使用者和初创公司提供免费额度,Heroku 也有免费版。对于大多数 Streamlit 用户来说,最简单和首选的方法是 Streamlit Sharing,因此我们将在此直接介绍该方法,并将在本书后续章节中,分别在第八章(使用 Heroku 和 AWS 部署 Streamlit 应用)和第十章(数据项目 - 在 Streamlit 中进行原型设计)中介绍 Amazon Web Services、Heroku 和 Streamlit for Teams。
本章将涵盖以下主题:
-
开始使用 Streamlit Sharing
-
GitHub 简介
-
使用 Streamlit Sharing 部署
-
调试 Streamlit Sharing
技术要求
本章需要访问 Streamlit Sharing,截至目前它仍处于测试阶段。你可以在streamlit.io/sharing-sign-up申请 Streamlit Sharing 访问权限。每周他们都会发放新的申请名额!如果你仍在等待 Streamlit Sharing 的访问权限,并且希望立即部署应用,可以继续阅读第八章,使用 Heroku 和 AWS 部署 Streamlit 应用,在该章中我们将介绍如何在 AWS 和 Heroku 上部署。
本章还需要一个免费的 GitHub 账户,可以通过www.github.com进行注册。在本章稍后的GitHub 简介部分,可以找到有关 GitHub 的完整介绍及详细设置说明。
本章的代码可以在以下 GitHub 仓库中找到:
github.com/PacktPublishing/Getting-started-with-Streamlit-for-Data-Science
)
开始使用 Streamlit Sharing
Streamlit Sharing 是 Streamlit 对快速部署过程的解决方案,肯定是我部署 Streamlit 应用程序的首选方法。我记得第一次在 Streamlit Sharing 上部署应用时,我觉得不可能这么简单。我们只需要将代码推送到 GitHub 仓库,指向该仓库,然后 Streamlit 会处理其余的工作。有时候我们需要关注“其余的部分”,比如当我们想要配置可用的存储空间或内存时,但通常情况下,允许 Streamlit Sharing 处理部署、资源分配和链接创建会让我们的开发变得更加轻松。
这里的目标是将我们已经创建的 Palmer's penguins 机器学习应用程序部署到 Streamlit Sharing 上。在开始之前,Streamlit Sharing 依赖于 GitHub。如果你已经熟悉 Git 和 GitHub,可以跳过此部分,直接创建一个包含 penguins_ml 文件夹的 GitHub 仓库。
GitHub 快速入门
GitHub 和 Git 语言是为软件工程师和数据科学家提供协作工具,它们为版本控制提供框架。我们并不需要了解它们的所有工作原理就能使用 Streamlit Sharing,但我们确实需要能够创建自己的仓库(类似共享文件夹)并在更新应用时进行更新。处理 Git 和 GitHub 有两种方式,通过命令行和通过 GitHub Desktop。到目前为止,在本书中,我们主要使用命令行,本教程也将继续沿用此方法。然而,如果你更喜欢使用 GitHub Desktop,可以访问desktop.github.com并按照那里的说明操作。
现在,使用以下步骤在命令行中开始使用 Git 和 GitHub:
-
首先,前往
www.github.com并在这里创建一个免费的账户。 -
然后,我们需要将 Git 语言安装到本地计算机,并通过 Git 连接到我们的 GitHub 账户。我们可以在 Mac 上使用终端中的
brew来完成这个操作:brew install git -
我们还需要在 Git 中设置一个全局用户名和电子邮件(如果我们还没有设置的话),GitHub 推荐这样做。以下代码将全局设置这些信息:
git config --global user.name "My Name" git config --global user.email myemail@email.com
现在我们已经有了 GitHub 账户,并且 Git 也已经本地安装好了,我们需要创建我们的第一个仓库!我们已经有了包含必要文件的文件夹,名为 penguin_ml,因此我们应该确保当前工作目录就是该文件夹(如果不确定,可以使用命令 pwd 来查看当前工作目录)。我们将使用 penguins_streamlit.py 应用的最终版本,并在以下代码中简要说明一些背景信息:
import streamlit as st
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import pickle
st.title('Penguin Classifier')
st.write("This app uses 6 inputs to predict the species of penguin using "
"a model built on the Palmer's Penguin's dataset. Use the form below"
" to get started!")
penguin_df = pd.read_csv('penguins.csv')
rf_pickle = open('random_forest_penguin.pickle', 'rb')
map_pickle = open('output_penguin.pickle', 'rb')
rfc = pickle.load(rf_pickle)
unique_penguin_mapping = pickle.load(map_pickle)
rf_pickle.close()
map_pickle.close()
第一部分导入我们的库,设置应用的标题,并加载我们使用 penguins_ml.py 文件创建的模型。如果我们没有 random_forest_penguin.pickle 和 output_penguin.pickle 文件,这部分会失败。你可以去第四章,使用 Streamlit 进行机器学习,来创建这些文件,或者直接访问github.com/tylerjrichards/Getting-Started-with-Streamlit-for-Data-Science/tree/main/penguin_ml找到它们:
with st.form('user_input'):
island = st.selectbox('Penguin Island', options=[
'Biscoe', 'Dream', 'Torgerson'])
sex = st.selectbox('Sex', options=['Female', 'Male'])
bill_length = st.number_input('Bill Length (mm)', min_value=0)
bill_depth = st.number_input('Bill Depth (mm)', min_value=0)
flipper_length = st.number_input('Flipper Length (mm)', min_value=0)
body_mass = st.number_input('Body Mass (g)', min_value=0)
st.form_submit_button()
island_biscoe, island_dream, island_torgerson = 0, 0, 0
if island == 'Biscoe':
island_biscoe = 1
elif island == 'Dream':
island_dream = 1
elif island == 'Torgerson':
island_torgerson = 1
sex_female, sex_male = 0, 0
if sex == 'Female':
sex_female = 1
elif sex == 'Male':
sex_male = 1
new_prediction = rfc.predict([[bill_length, bill_depth, flipper_length,
body_mass, island_biscoe, island_dream,
island_torgerson, sex_female, sex_male]])
prediction_species = unique_penguin_mapping[new_prediction][0]
接下来的部分会获取我们预测所需的所有用户输入,从研究员所在的岛屿到企鹅的性别,再到企鹅的喙和鳍的测量数据,这些都为我们在接下来的代码中预测企鹅的物种做准备:
st.subheader("Predicting Your Penguin's Species:")
st.write('We predict your penguin is of the {} species'
.format(prediction_species))
st.write('We used a machine learning (Random Forest) model to '
'predict the species, the features used in this prediction '
' are ranked by relative importance below.')
st.image('feature_importance.png')
最后一部分创建了多个直方图,用以解释模型的预测,显示了按物种区分的喙长/喙深/鳍长数据。我们使用这三个变量,因为我们的特征重要性图告诉我们,这些是第四章,使用 Streamlit 进行机器学习中预测物种的最佳变量:
st.write('Below are the histograms for each continuous variable '
'separated by penguin species. The vertical line '
'represents your the inputted value.')
fig, ax = plt.subplots()
ax = sns.displot(x=penguin_df['bill_length_mm'],
hue=penguin_df['species'])
plt.axvline(bill_length)
plt.title('Bill Length by Species')
st.pyplot(ax)
fig, ax = plt.subplots()
ax = sns.displot(x=penguin_df['bill_depth_mm'],
hue=penguin_df['species'])
plt.axvline(bill_depth)
plt.title('Bill Depth by Species')
st.pyplot(ax)
fig, ax = plt.subplots()
ax = sns.displot(x=penguin_df['flipper_length_mm'],
hue=penguin_df['species'])
plt.axvline(flipper_length)
plt.title('Flipper Length by Species')
st.pyplot(ax)
现在我们已经进入了正确的文件夹并拥有了正确的文件,我们将使用以下代码来初始化我们的第一个仓库,并将所有文件添加并提交到仓库:
git init
git add .
git commit -m 'our first repo commit'
下一步是将我们本地设备上的 Git 仓库连接到我们的 GitHub 账户:
-
首先,我们需要通过返回 GitHub 网站并点击新建仓库按钮来设置一个新仓库,如下图所示:
![图 5.1 – 设置新仓库]()
图 5.1 – 设置新仓库
-
然后我们可以填写我们的仓库名称(
penguin_ml),并点击创建仓库:![图 5.2 – 仓库创建]()
图 5.2 – 仓库创建
-
现在我们在 GitHub 上有了一个新仓库,并且本地也有一个仓库,我们需要将两者连接起来。以下代码将两个仓库连接并将我们的代码推送到 GitHub 仓库,GitHub 还会在你点击
penguin_ml文件时建议如何连接两个仓库!如果我们有新的代码需要推送到仓库,我们可以按照一般格式使用git add .来添加文件更改,git commit –m "提交信息",然后最后使用git push将更改推送到仓库。
接下来我们可以进入 Streamlit 端的部署过程。
使用 Streamlit Sharing 进行部署
现在,所有必要的文件都已上传到 GitHub 仓库,我们几乎拥有了部署应用所需的一切。你可以按照以下步骤列表来部署我们的应用:
-
当我们部署到 Streamlit Sharing 时,Streamlit 使用自己的服务器来托管应用。因此,我们需要明确告诉 Streamlit 我们的应用运行所需的 Python 库。以下代码安装了一个非常有用的库,名为
pipreqs,并创建了一个符合 Streamlit 要求格式的requirements.txt文件:pip install pipreqs pipreqs . -
当我们查看
requirements.txt文件时,我们可以看到pipreqs检查了我们所有的 Python 文件,查看了我们导入和使用的库,并创建了一个文件,Streamlit 可以使用它来安装我们库的相同版本,从而避免错误:![图 5.3 – Requirements.txt]()
图 5.3 – Requirements.txt
-
我们有了一个新文件,因此我们也需要将其添加到我们的 GitHub 仓库中。以下代码将
requirements.txt添加到我们的仓库中:git add requirements.txt git commit -m 'add requirements file' git push -
现在,我们的最后一步是注册 Streamlit Sharing(share.streamlit.io),并点击
penguins_streamlit.py。你还应该将用户名从我的个人 GitHub 用户名(tylerjrichards)改为你自己的:![图 5.4 – 添加 URL]()
import streamlit as st import seaborn as sns import matplotlib.pyplot as plt import pandas as pd import pickle st.title('Penguin Classifier: A Machine Learning App') st.write("This app uses 6 inputs to predict the species of penguin using " "a model built on the Palmer's Penguin's dataset. Use the form below" " to get started!") penguin_df = pd.read_csv('penguins.csv') rf_pickle = open('random_forest_penguin.pickle', 'rb') map_pickle = open('output_penguin.pickle', 'rb') rfc = pickle.load(rf_pickle) unique_penguin_mapping = pickle.load(map_pickle) rf_pickle.close() map_pickle.close() -
现在,为了推送更改,我们需要更新我们的 GitHub 仓库。我们将使用以下代码来完成此操作:
git add . git commit -m 'changed our title' git push
当我们返回到应用时,你的应用将拥有自己的唯一 URL。如果你无法找到你的 Streamlit 应用,可以随时在 share.streamlit.io 上找到它们。现在,我们应用的顶部应该像以下截图所示:

图 5.5 – 我们部署的 Streamlit 应用
注意
应用重新加载可能需要几分钟时间!
现在我们有了一个完全部署的 Streamlit 应用!我们可以将这个链接分享给朋友、同事,或者在社交媒体上发布,例如 Twitter(如果你通过这本书做了一个有趣的 Streamlit 应用,请在 Twitter 上@我 @tylerjrichards,我很想看看!)。现在,让我们学习如何调试我们的 Streamlit 应用。
调试 Streamlit Sharing
Streamlit Sharing 还让我们可以访问应用的日志,这些日志会显示在我们的终端上,如果我们是在本地部署应用的话。在右下角,每当我们查看自己的应用时,会有一个 管理应用 按钮,允许我们访问日志。在这个选项菜单中,我们可以重启、删除或下载应用的日志,还可以查看其他可用的应用,并从 Streamlit 注销。
Streamlit 密钥
在创建和部署 Streamlit 应用时,你可能希望使用一些用户无法查看的信息。在 Streamlit Sharing 中,默认情况下是公开 GitHub 仓库,所有的代码、数据和模型都是公开的。但如果你想使用私密的 API 密钥(比如,许多 API(例如 Twitter 的抓取 API 或 Google Maps API)都需要此类密钥),或者希望以编程方式访问存储在受密码保护的数据库中的数据,甚至如果你想对你的 Streamlit 应用进行密码保护,你就需要一种方法,向 Streamlit 暴露一些私密数据。Streamlit 的解决方案是 Streamlit Secrets,它允许我们在每个应用中设置隐藏的、私密的“秘密”。我们从给我们的 Streamlit 应用设置密码保护开始,特别是我们现有的企鹅应用。
首先,我们可以编辑应用的顶部,要求用户在加载应用的其余部分之前输入密码。我们可以使用 st.stop() 函数,在密码错误时停止应用运行,代码如下:
import streamlit as st
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import pickle
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
st.title('Penguin Classifier')
st.write("This app uses 6 inputs to predict the species of penguin using "
"a model built on the Palmer's Penguin's dataset. Use the form below"
" to get started!")
password_guess = st.text_input('What is the Password?')
if password_guess != 'streamlit_password':
st.stop()
penguin_file = st.file_uploader('Upload your own penguin data')
这段代码将生成下图,只有当用户在文本输入框中输入 streamlit_password 字符串时,应用的其余部分才会加载:

图 5.6 – 密码检查器
要创建 Streamlit Secret,我们只需要访问 Streamlit Sharing 的主页 share.streamlit.io/,然后点击 编辑秘密 选项,如下图所示:

图 5.7 – 秘密
一旦点击 编辑秘密 按钮,我们就可以向应用中添加新的 Streamlit Secrets:

图 5.8 – 我们的第一个 Streamlit Secret
我们的最后一步是从已部署的应用中读取 Streamlit Secrets,通过代码中调用 st.secrets 和我们在 Secrets 中创建的变量。以下代码用 Streamlit Secret 替换了我们硬编码的密码:
st.title('Penguin Classifier')
st.write("This app uses 6 inputs to predict the species of penguin using "
"a model built on the Palmer's Penguin's dataset. Use the form below"
" to get started!")
password_guess = st.text_input('What is the Password?')
if password_guess != st.secrets["password"]:
st.stop()
penguin_file = st.file_uploader('Upload your own penguin data')
这段代码将创建以下 Streamlit 应用,使用我们设置的 Streamlit Secret 进行密码保护:

图 5.9 – 部署的密码
当我们将这段代码推送到 GitHub 仓库并重新启动我们的 Streamlit 应用时,我们将在 Streamlit Sharing 上部署一个受密码保护的 Streamlit 应用!我们可以使用相同的方法来处理私密的 API 密钥,或者任何其他需要隐藏数据的应用场景。
总结
在本章中,我们学习了如何在命令行中使用 Git 和 GitHub,如何在 Streamlit Sharing 上调试应用程序,如何使用 Streamlit Secrets 在公共应用中使用私密数据,以及如何通过 Streamlit Sharing 快速部署我们的应用程序。这完成了本书的第一部分!恭喜你坚持到这一点。下一部分将以第一部分为基础,讲解更高级的主题,例如更复杂的格式化、Streamlit 应用的美化以及使用由开源社区构建的宝贵附加组件——Streamlit 组件。
在下一章中,我们将通过主题、列以及更多功能来讲解如何美化 Streamlit 应用。
第二部分:高级 Streamlit 应用
本节涵盖了 Streamlit 的基础知识,并将向您展示如何创建第一个应用。第二部分 通过复杂的应用和使用案例来探索 Streamlit,旨在让您成为 Streamlit 专家用户。
本节包含以下章节:
-
第六章**, 美化 Streamlit 应用
-
第七章**, Streamlit 组件
-
第八章**, 使用 Heroku 和 AWS 部署 Streamlit 应用
第六章:第六章:美化 Streamlit 应用程序
欢迎来到本书的第二部分!在第一部分,创建基本的 Streamlit 应用程序中,我们专注于基础知识——可视化、部署和数据处理,这些都是开始使用 Streamlit 时至关重要的主题。在本部分中,我们的目的是通过更复杂的应用程序和用例来探索 Streamlit,目的是将您培养成 Streamlit 的专家用户。
在本章中,我们将处理包括侧边栏、列、颜色和主题等元素,来扩展我们制作美观 Streamlit 应用程序的能力。到本章结束时,您应该能更自如地创建比普通最小可行产品(MVP)更好的应用程序。我们将从学习列的使用开始,然后继续介绍本章讨论的其他元素,并将每个元素融入到主 Streamlit 应用程序中。
在本章中,我们将涵盖以下主题:
-
设置 SF(旧金山)Trees 数据集
-
在 Streamlit 中使用列
-
探索页面配置
-
使用 Streamlit 侧边栏
-
使用颜色选择器选择颜色
-
使用 Streamlit 主题
技术要求
本章需要一个免费的 GitHub 帐户,您可以在www.github.com注册。有关 GitHub 的完整介绍以及详细的设置说明,可以在前一章的GitHub 快速入门部分找到,第五章,使用 Streamlit Sharing 部署 Streamlit。
设置 SF Trees 数据集
在本章中,我们将再次使用 SF Trees 数据集,这是我们在第三章中使用的数据集,数据可视化。正如我们在之前的章节中所做的那样,我们需要按照以下步骤进行设置:
-
为本章创建一个新文件夹。
-
将我们的数据添加到文件夹中。
-
为我们的应用程序创建一个 Python 文件。
让我们详细查看每个步骤。
在我们的主streamlit_apps文件夹中,在终端运行以下代码来创建一个名为pretty_trees的新文件夹。您也可以在终端外手动创建一个新文件夹:
mkdir pretty_trees
现在,我们需要将数据从第三章,数据可视化,移动到本章的文件夹中。以下代码将数据复制到正确的文件夹:
cp trees_app/trees.csv pretty_trees
如果您没有trees_app文件夹并且还没有完成第三章,数据可视化,您也可以从github.com/tylerjrichards/Getting-Started-with-Streamlit-for-Data-Science下载必要的数据,数据将位于名为trees_app的文件夹中。
现在我们已经准备好了数据,我们需要创建一个 Python 文件来托管我们的 Streamlit 应用程序代码;以下代码正是为此目的而写:
touch pretty_trees.py
pretty_trees 文件将包含我们的 Python 代码,因此请打开它并在你选择的文本编辑器中进行编辑,章节正式开始,我们将学习如何在 Streamlit 中使用列!
在 Streamlit 中使用列
在此之前的所有应用中,我们都将每个 Streamlit 任务视为自上而下的体验。我们将文本作为标题输出,收集用户输入,然后再展示我们的可视化内容。然而,Streamlit 允许我们通过 st.beta_columns() 特性将应用格式化为动态列。截至目前,列特性仍处于测试版(因此函数名中有 beta_),但预计在 2021 年某个时候该功能将不再是测试版,届时它将被命名为 st.columns()。
我们可以将 Streamlit 应用分为多个不同长度的列,然后将每列视为我们应用中的一个独特区域,用来展示文本、图形、图片或任何其他我们希望展示的内容。
在 Streamlit 中,列的语法使用 with 语法,你可能已经熟悉这种语法,用于资源管理、处理文件的打开和写入等 Python 用例。将 with 语法应用于 Streamlit 列的最简单理解方式是,它们是自包含的代码块,告诉 Streamlit 在我们的应用中准确地放置项目。让我们通过一个示例来看它是如何工作的。以下代码导入我们的 SF Trees 数据集,并在其中创建三个等长的列,每个列中写入一些文本:
import streamlit as st
import pandas as pd
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
col1, col2, col3 = st.beta_columns((1,1,1))
with col1:
st.write('First column')
with col2:
st.write('Second column')
with col3:
st.write('Third column')
上述代码将创建如下截图所示的应用:

图 6.1 – 前三列
如我们所见,st.beta_columns() 定义了三个等长的列,我们使用 with 语法在每个列中打印一些文本。我们也可以直接在预定义的列上调用 st.write() 函数(或任何其他将内容写入 Streamlit 应用的 Streamlit 函数),以获得相同的结果,如以下代码所示。以下代码将产生与前一个代码块完全相同的输出:
import streamlit as st
import pandas as pd
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
col1, col2, col3 = st.beta_columns((1,1,1))
col1.write('First column')
col2.write('Second column')
col3.write('Third column')
随着我们编写更多复杂的 Streamlit 应用,每个列中包含更多内容,with 语句往往使得应用更加简洁,更容易理解和调试。本书的大多数部分将尽可能使用 with 语句。
在 Streamlit 中,列的宽度是相对于其他已定义列的大小的。因此,如果我们将每列的宽度从 1 扩大到 10,我们的应用不会发生任何变化。此外,我们也可以传递一个数字给 st.beta_columns(),它将返回指定数量的等宽列。以下代码块展示了三种列宽度选项,它们都会产生相同的列宽:
#option 1
col1, col2, col3 = st.beta_columns((1,1,1))
#option 2
col1, col2, col3 = st.beta_columns((10,10,10))
#option 3
col1, col2, col3 = st.beta_columns(3)
最后一个例子,以下代码块允许用户输入来确定每一列的宽度。请随意尝试生成的应用程序,以更好地理解我们如何使用列来改变 Streamlit 应用程序中的布局格式:
import streamlit as st
import pandas as pd
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
first_width = st.number_input('First Width', min_value=1, value=1)
second_width = st.number_input('Second Width', min_value=1, value=1)
third_width = st.number_input('Third Width', min_value=1, value=1)
col1, col2, col3 = st.beta_columns(
(first_width,second_width,third_width))
with col1:
st.write('First column')
with col2:
st.write('Second column')
with col3:
st.write('Third column')
在第三章《数据可视化》中,我们使用以下代码来展示 Streamlit 内置函数 st.line_chart()、st.bar_chart() 和 st.area_chart() 之间的差异:
import streamlit as st
import pandas as pd
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
df_dbh_grouped = pd.DataFrame(trees_df.groupby(['dbh']).count()['tree_id'])
df_dbh_grouped.columns = ['tree_count']
st.line_chart(df_dbh_grouped)
st.bar_chart(df_dbh_grouped)
st.area_chart(df_dbh_grouped)
上述代码块创建了以下 Streamlit 应用程序,其中三个按宽度分组的旧金山树木图表依次排列(为了简洁起见,仅显示了两个图表):

图 6.2 – SF 线形图和条形图
本次练习的目的是更好地理解 Streamlit 的三个函数,但如果我们需要滚动才能看到所有内容,那我们该怎么做呢?让我们通过使用三列将三个图表并排放置来改进这一点。以下代码预定义了三个宽度相等的列,并将每个图表放入其中:
import streamlit as st
import pandas as pd
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
df_dbh_grouped = pd.DataFrame(trees_df.groupby(['dbh']).count()['tree_id'])
df_dbh_grouped.columns = ['tree_count']
col1, col2, col3 = st.beta_columns(3)
with col1:
st.line_chart(df_dbh_grouped)
with col2:
st.bar_chart(df_dbh_grouped)
with col3:
st.area_chart(df_dbh_grouped)
当我们运行前面的代码时,得到一个奇怪的结果,如下截图所示:

图 6.3 – 窄图表
这显然不是我们想要的结果!每个图表都太窄了。幸运的是,这引出了我们的下一个小主题——Streamlit 中的页面配置。
探索页面配置
Streamlit 允许我们在每个 Streamlit 应用程序的顶部配置一些基本的页面特定功能。到目前为止,我们一直在使用 Streamlit 默认设置,但在 Streamlit 应用程序的顶部,我们可以手动配置所有内容,从在 Web 浏览器中打开 Streamlit 应用时显示的页面标题,到页面布局,再到侧边栏的默认状态(我们将在使用 Streamlit 侧边栏章节中介绍!)。
Streamlit 应用程序的默认设置是页面布局居中,这也是为什么我们的应用程序在边缘有大量空白空间的原因。以下代码将我们的 Streamlit 应用程序设置为宽屏格式,而不是默认的居中格式:
import streamlit as st
import pandas as pd
st.set_page_config(layout='wide')
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW')
trees_df = pd.read_csv('trees.csv')
df_dbh_grouped = pd.DataFrame(trees_df.groupby(['dbh']).count()['tree_id'])
df_dbh_grouped.columns = ['tree_count']
col1, col2, col3 = st.beta_columns(3)
with col1:
st.line_chart(df_dbh_grouped)
with col2:
st.bar_chart(df_dbh_grouped)
with col3:
st.area_chart(df_dbh_grouped)
当我们运行前面的代码时,我们会看到三个图表之间的间距合适,我们可以轻松比较三个图表。以下截图展示了宽格式的 Streamlit 应用程序:

图 6.4 – 宽格式图表
这标志着我们对 Streamlit 列的探索结束,也结束了对页面配置默认设置的首次介绍。在本书的剩余部分,我们将越来越多地使用这两项技能。我们的下一个主题是介绍 Streamlit 侧边栏。
使用 Streamlit 侧边栏
正如我们已经在 Streamlit 中看到的,当我们开始接受大量用户输入并且开发较长的 Streamlit 应用时,我们经常失去让用户在同一屏幕上查看输入和输出的能力。在其他情况下,我们可能希望将所有用户输入放入一个独立的部分,以清晰地将输入和输出在我们的 Streamlit 应用中区分开来。对于这两种用例,我们可以使用 Streamlit 侧边栏,它允许我们在 Streamlit 应用的左侧放置一个可最小化的侧边栏,并在其中添加任何 Streamlit 组件。
首先,我们可以制作一个基础示例,使用我们前面的应用中的一个图形,并根据用户输入过滤背后的数据。在这种情况下,我们可以要求用户指定树木所有者的类型(例如私人所有者或公共工程部门),并使用st.multiselect()函数在这些条件下进行过滤,该函数允许用户从列表中选择多个选项:
import streamlit as st
import pandas as pd
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW. The '
'histogram below is filtered by tree owner.')
trees_df = pd.read_csv('trees.csv')
owners = st.sidebar.multiselect(
'Tree Owner Filter', trees_df['caretaker'].unique())
if owners:
trees_df = trees_df[trees_df['caretaker'].isin(owners)]
df_dbh_grouped = pd.DataFrame(trees_df.groupby(['dbh']).count()['tree_id'])
df_dbh_grouped.columns = ['tree_count']
st.line_chart(df_dbh_grouped)
前述代码将创建以下 Streamlit 应用。与之前一样,我们将owners变量隐藏在if语句中,因为我们希望应用在用户未从选项中选择之前,使用整个数据集。侧边栏使用户能够轻松看到他们选择的选项和我们应用的输出:

图 6.5 – 第一个侧边栏
我们为这个应用程序的下一步将是添加更多的可视化,首先从我们在第三章中创建的树木地图开始,然后将侧边栏与我们在本章中学到的列结合。
以下代码将整个旧金山地区的树木地图,按我们的多选框过滤,放在直方图下方:
import streamlit as st
import pandas as pd
trees_df = pd.read_csv('trees.csv')
owners = st.sidebar.multiselect(
'Tree Owner Filter', trees_df['caretaker'].unique())
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW. The '
'histogram below is filtered by tree owner.')
st.write('The current analysis is of trees owned by {}'.format(owners))
if owners:
trees_df = trees_df[trees_df['caretaker'].isin(owners)]
df_dbh_grouped = pd.DataFrame(trees_df.groupby(['dbh']).count()['tree_id'])
df_dbh_grouped.columns = ['tree_count']
st.line_chart(df_dbh_grouped)
trees_df = trees_df.dropna(subset=['longitude', 'latitude'])
trees_df = trees_df.sample(n = 1000, replace=True)
st.map(trees_df)
以下截图展示了前述代码生成的 Streamlit 应用,其中折线图位于新地图的上方,地图显示了按树木所有者过滤的旧金山树木:

图 6.6 – 带侧边栏的过滤地图
我们为这个应用程序的下一步将是将我们在列中学到的内容与侧边栏结合,通过在地理地图上方添加另一个图形来实现。在第三章中,数据可视化,我们创建了一个树龄的直方图。我们可以将其用作这个 Streamlit 应用中的第三个图形。以下代码块实现了这一点,并且将折线图转换为与我们树龄图形相同的库(seaborn):
import streamlit as st
import pandas as pd
import seaborn as sns
import datetime as dt
import matplotlib.pyplot as plt
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW. The '
'histogram below is filtered by tree owner.')
trees_df = pd.read_csv('trees.csv')
trees_df['age'] = (pd.to_datetime('today') -
pd.to_datetime(trees_df['date'])).dt.days
owners = st.sidebar.multiselect(
'Tree Owner Filter', trees_df['caretaker'].unique())
if owners:
trees_df = trees_df[trees_df['caretaker'].isin(owners)]
df_dbh_grouped = pd.DataFrame(trees_df.groupby(['dbh']).count()['tree_id'])
df_dbh_grouped.columns = ['tree_count']
第一部分做了以下事情:
-
它加载树木数据集。
-
它基于数据集中的日期列添加了一个年龄列。
-
它在侧边栏创建了一个多选组件。
-
它根据侧边栏进行过滤。
我们的下一步是创建三个图形:
#define multiple columns, add two graphs
col1, col2 = st.beta_columns(2)
with col1:
st.write('Trees by Width')
fig_1, ax_1 = plt.subplots()
ax_1 = sns.histplot(trees_df['dbh'])
plt.xlabel('Tree Width')
st.pyplot(fig_1)
with col2:
st.write('Trees by Age')
fig_2, ax_2 = plt.subplots()
ax_2 = sns.histplot(trees_df['age'])
plt.xlabel('Age (Days)')
st.pyplot(fig_2)
st.write('Trees by Location')
trees_df = trees_df.dropna(subset=['longitude', 'latitude'])
trees_df = trees_df.sample(n = 1000, replace=True)
st.map(trees_df)
正如我们在 第三章 数据可视化 中讨论的那样,Streamlit 的内置功能,如 st.map() 和 st.line_chart(),对于快速可视化非常有用,但缺少一些配置选项,如适当的标题或轴名称更改。以下截图展示了我们的 Streamlit 应用,其中预设了几个树主筛选器:

图 6.7 – 三个过滤后的图表
本章要讨论的下一个特性是如何获取和使用用户输入的颜色,这将在下一节中详细介绍。
使用颜色选择器选择颜色
在应用程序中,获取颜色作为用户输入是非常困难的。如果用户想要红色,他们是想要浅红色还是深红色?是栗色还是带点粉色的红色?Streamlit 解决这个问题的方法是 st.color_picker(),它允许用户选择颜色,并返回该颜色的十六进制字符串(十六进制字符串是定义特定颜色阴影的唯一字符串,通常作为大多数图形库的输入)。以下代码将颜色选择器添加到我们之前的应用程序中,并根据用户选择的颜色更改 Seaborn 图表的颜色:
import streamlit as st
import pandas as pd
import seaborn as sns
import datetime as dt
import matplotlib.pyplot as plt
st.title('SF Trees')
st.write('This app analyses trees in San Francisco using'
' a dataset kindly provided by SF DPW. The '
'histogram below is filtered by tree owner.')
#load trees dataset, add age column in days
trees_df = pd.read_csv('trees.csv')
trees_df['age'] = (pd.to_datetime('today') -
pd.to_datetime(trees_df['date'])).dt.days
#add tree owner filter to sidebar, then filter, get color
owners = st.sidebar.multiselect(
'Tree Owner Filter', trees_df['caretaker'].unique())
graph_color = st.sidebar.color_picker('Graph Colors')
if owners:
trees_df = trees_df[trees_df['caretaker'].isin(owners)]
#group by dbh for leftmost graph
df_dbh_grouped = pd.DataFrame(trees_df.groupby(['dbh']).count()['tree_id'])
df_dbh_grouped.columns = ['tree_count']
与我们之前的应用不同的是,这里添加了 graph_color 变量,它是 st.color_picker() 函数的结果。我们为这个颜色选择器添加了一个名称,并将其放置在侧边栏中,紧挨着所有者的多选组件。现在,我们已获取到用户的颜色输入,可以使用这个颜色来改变图表中的颜色,如下所示的代码所示:
#define multiple columns, add two graphs
col1, col2 = st.beta_columns(2)
with col1:
st.write('Trees by Width')
fig_1, ax_1 = plt.subplots()
ax_1 = sns.histplot(trees_df['dbh'],
color=graph_color)
plt.xlabel('Tree Width')
st.pyplot(fig_1)
with col2:
st.write('Trees by Age')
fig_2, ax_2 = plt.subplots()
ax_2 = sns.histplot(trees_df['age'],
color=graph_color)
plt.xlabel('Age (Days)')
st.pyplot(fig_2)
st.write('Trees by Location')
trees_df = trees_df.dropna(subset=['longitude', 'latitude'])
trees_df = trees_df.sample(n = 1000, replace=True)
st.map(trees_df)
当你运行这个 Streamlit 应用时,你可以准确看到颜色选择器是如何工作的(本书为黑白印刷版,因此在纸质版中不可见)。它有一个默认颜色(在我们的例子中是黑色),你可以通过选择组件然后点击你想要的颜色来进行更改。以下截图展示了点击组件后的状态和我们 SF Trees 应用中的结果:

图 6.8 – 颜色选择器
现在我们已经知道如何在 Streamlit 中更改可视化图表的颜色,如何更改整个 Streamlit 应用程序的格式和配色方案呢?下一章将探讨 Streamlit 主题,以及如何设置不同的主题来美化 Streamlit 应用。
使用 Streamlit 主题
到目前为止,我们的应用程序除了在颜色选择器部分外,所有的背景和配色方案都是完全相同的。Streamlit 允许我们更新应用程序,并将背景和各种组件的颜色作为自定义功能进行更改。通过这个功能,我们可以创建默认使用暗黑模式的 Streamlit 应用,或是具有完美配色的应用,甚至可以创建确保色盲人士也能看到我们所有可视化内容的主题。
编辑应用程序主题有两种方法——通过 UI 界面或通过我们在第三章中使用的相同config.toml设置,数据可视化。当我们运行 Streamlit 应用程序时,在右上角会有一个小的汉堡菜单图标。点击该图标后,再点击设置,我们将看到以下选项出现在屏幕中间:

图 6.9 – 主题编辑
使用前面的菜单,我们可以在浅色和深色主题之间切换,当我们点击编辑当前主题时,可以看到所有的主题编辑选项,如下图所示:

图 6.10 – 编辑当前主题
从前面的截图中,我们可以观察到以下内容可以切换:
-
主色:用于交互的颜色
-
背景色:应用程序的背景
-
次要背景色:组件内部的背景
-
文本颜色/字体:应用程序文本的颜色和字体
随意点击它们并更改颜色,观察这如何影响你的 Streamlit 应用程序。一旦你找到了几种喜欢的颜色用于我们的 SF Trees 应用程序,你可以将它们添加到.streamlit/config.toml文件中的主题部分,以下是config.toml文件中的说明以及我为我的主题选择的颜色:
[theme]
# Primary accent color for interactive elements.
primaryColor = "#de8ba1"
# Background color for the main content area.
backgroundColor = "#f4f1ea"
# Background color used for the sidebar and most interactive widgets.
secondaryBackgroundColor = "#9fe4cc"
# Color used for almost all text.
textColor = "#262730"
当我们保存这个文件并重新运行应用程序时,我们将在下图中看到我们的应用程序现在拥有了自定义主题,正如预期的那样:

图 6.11 – 自定义主题输出
我发现让 Streamlit 主题看起来很棒的最简单方法是使用浏览器内的编辑器进行实时编辑,然后将结果复制并粘贴到 Streamlit 配置文件中,正如我们在这个例子中所做的那样。不过,和所有事情一样,玩得开心!尝试新的组合,尽可能使你的 Streamlit 应用程序变得更美丽。
总结
这就是我们与 SF Trees 数据集的冒险,以及学习如何使我们的 Streamlit 应用程序更加美观的方式。我们涵盖了将应用程序分成列和页面配置的内容,以及如何在侧边栏收集用户输入,通过st.color_picker()功能获取用户输入的特定颜色,最后学习如何使用 Streamlit 主题。
在下一章中,我们将通过了解如何下载和使用用户构建的 Streamlit 组件,来学习关于 Streamlit 的开源社区。
第七章:第七章:探索 Streamlit 组件
Streamlit 拥有一支全职开发团队,致力于新功能的开发,同时也因为它对社区驱动的开发持开放态度而蓬勃发展。毫无疑问,社区成员会有一些需求,想要实现一个没有进入优先开发路线图的特性。Streamlit 组件允许他们灵活地自己动手实现这些功能,并立即将自己的创意应用到他们的 Streamlit 应用中。
本章的重点是学习如何找到并使用社区制作的 Streamlit 组件。为此,我们将通过三个优秀的 Streamlit 应用进行实践,一个用于学习如何将代码嵌入到 Streamlit 应用中,另一个用于为应用添加美丽的动画,第三个用于将简便的自动化 探索性数据分析(EDA)嵌入到 Streamlit 应用中。
本章将涵盖以下内容:
-
使用 Streamlit 组件:
streamlit-embedcode -
使用 Streamlit 组件:
streamlit-lottie -
使用 Streamlit 组件:
streamlit-pandas-profiling -
查找更多组件
我们将在下一节中了解技术要求。
技术要求
在我们开始使用新的 Streamlit 组件之前,需要先下载它们。我们可以像在 第一章《Streamlit 简介》中一样,使用 pip(或任何其他包管理器)下载它们。这些是需要下载的组件:
-
streamlit-embedcode:要下载此库,在终端中运行以下代码:pip install streamlit-embedcodestreamlit-embedcode使得从其他位置(比如 GitHub 的 gist)导入代码块并直接在你的应用中显示变得容易,它是由 Streamlit 员工 Randy Zwitch 创建的。 -
streamlit-lottie:要下载此库,在终端中运行以下代码:pip install streamlit-lottiestreamlit-lottie使用lottie开源库,允许我们将 Web 本地动画(如 图形交换格式(GIF)文件)添加到我们的 Streamlit 应用中。坦率地说,这是一个美化 Streamlit 应用的绝妙库,由 Streamlit 应用创作者 Andy Fanilo 创建。 -
streamlit-pandas-profiling:要下载此库,在终端中运行以下代码:pip install streamlit-pandas-profiling
流行的 pandas Python 库是标准的 Python 数据分析库,通常位居数据科学家最受欢迎和最有用的 Python 库榜单之首。pandas-profiling 在我们创建的任何 DataFrame 上生成自动化的 EDA,并展示所有内容,从描述性统计到重复行的数量。它是由一位 GitHub 用户 Okld 创建的,GitHub 地址为 github.com/okld。
现在我们已经安装了这三个库,我们可以继续学习第一个库:streamlit-embedcode。
使用 Streamlit 组件 – streamlit-embedcode
如果我们想在 Streamlit 中显示代码,我们可以很容易地将代码作为文本处理,并使用熟悉的 st.write(),它接受文本作为输入,或者使用 st.markdown(),它接受 Markdown 格式的输入。这对于小片段可能很好,但对于格式化较长的代码或普通用户来说,可能会显得比较困难且不美观。因此,创建了 streamlit-embedcode 来帮助解决这个问题。
向他人展示代码片段是一个常见的需求,现有的一些解决方案包括通过 GitHub 代码片段(它们类似于只包含一个文本文件的迷你 GitHub 仓库)或 GitLab 代码片段(与 GitHub 代码片段类似,只是为 GitLab 提供)共享代码片段,或者使用 Pastebin,这是一个 GitHub/GitLab 以外的共享文本/代码片段平台。现在,我们可以创建一个包含示例 Streamlit 代码的 Python 文件,将其放入 GitHub 代码片段,并从一个新的 Streamlit 应用中调用它。为此,我们将按以下步骤操作:
-
对于这些选项,我们将首先创建一个简单的 Streamlit 应用,该应用只展示
Palmer's PenguinsStreamlit 应用背后的代码。我们可以将此应用放入独立的component_example文件夹中,使用以下来自streamlit_apps文件夹的代码:mkdir component_example cd component_example touch gist_example.py -
接下来,我们需要访问
gist.github.com/来创建我们自己的代码片段。在我们登录 GitHub 后,需要为代码片段命名,然后将Palmer's Penguins的代码粘贴到以下代码块中(其中包含一个简短的解释):import streamlit as st import pandas as pd import matplotlib.pyplot as plt import seaborn as sns st.title("Palmer's Penguins") st.markdown('Use this Streamlit app to make your own scatterplot about penguins!') selected_x_var = st.selectbox('What do want the x variable to be?', ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g']) selected_y_var = st.selectbox('What about the y?', ['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g']) penguin_file = st.file_uploader('Select Your Local Penguins CSV') if penguin_file is not None: penguins_df = pd.read_csv(penguin_file) else: st.stop()第一部分导入我们的库,添加一些标题文本,从用户那里获取一些输入,并下载正确的数据集。接下来,我们只需要绘制一个散点图,然后这段代码就完成了进入我们代码片段的部分。再次说明,这里的具体代码并不重要,因为它并不会在代码片段中执行——它只是将代码以更优美的方式传递给他人。以下代码片段展示了这一过程:
sns.set_style('darkgrid') markers = {"Adelie": "X", "Gentoo": "s", "Chinstrap":'o'} fig, ax = plt.subplots() ax = sns.scatterplot(data = penguins_df, x = selected_x_var, y = selected_y_var, hue = 'species', markers = markers, style = 'species') plt.xlabel(selected_x_var) plt.ylabel(selected_y_var) plt.title("Scatterplot of Palmer's Penguins") st.pyplot(fig)现在,我们应该有一个像这样的 GitHub 代码片段:
![图 7.1 – GitHub 代码片段示例]()
图 7.1 – GitHub 代码片段示例
-
当我们保存 GitHub 代码片段时,我们可以直接在 Streamlit 应用中链接到它。在
gist_example.pyPython 文件中,我们需要从我们新创建的库中导入github_gist()函数,并将其应用于我们刚刚创建的 GitHub 代码片段。以下代码展示了如何对我的代码片段进行操作,但你应该用你自己的代码片段链接替换:import streamlit as st from streamlit_embedcode import github_gist st.title("Github Gist Example") st.write("Code from Palmer's Penguin Streamlit app.") github_gist('https://gist.github.com/tylerjrichards/9dcf6df0c17ccb7b91baafbe3cdf7654')现在,如果我们需要编辑代码片段中的内容,我们可以直接编辑底层的代码片段,Streamlit 应用会自动更新。当我们启动保存在
gist_example.py中的 Streamlit 应用时,我们将看到以下的 Streamlit 应用:

图 7.2 – 带 GitHub 代码片段的 Streamlit 应用
注意
对于任何公开的 Streamlit 应用程序,我们总是可以通过设置标签轻松访问该应用程序的源代码。因此,这种方法对于展示 Streamlit 应用程序背后的代码并不十分有用,因为这一点已经是内置的,但它对于展示常用的代码块非常有用,例如生成机器学习(ML)模型的通用代码或用户可能学习到的更通用的结构化查询语言(SQL)查询。
现在,让我们来学习如何使用streamlit-lottie为我们的应用程序添加美丽的动画吧!
使用 Streamlit 组件 – streamlit-lottie
正如我们在本章开始时提到的,lottie是一个由Airbnb创建的原生 Web 开源库,旨在让在网站上添加动画变得像添加静态图像一样简单。大型且盈利的科技公司常常通过发布开源软件来回馈开发者社区(或更可能是为了吸引认为其软件很酷的开发者),这一点也不例外。在这个案例中,streamlit-lottie是一个包装lottie文件的库,将它们直接嵌入到我们的 Streamlit 应用程序中。
为此,我们首先需要导入streamlit-lottie库,然后将st_lottie()函数指向我们的lottie文件。我们可以导入本地的lottie文件,或者更有可能的是,我们可以在免费的站点(lottiefiles.com/)上找到一个有用的动画文件,并将其从该站点加载到我们的应用程序中。
为了进行测试,我们可以在我们之前创建的《第四章》 使用 Streamlit 进行机器学习的企鹅应用程序顶部,添加一个可爱的企鹅动画(lottiefiles.com/39646-cute-penguin)。在我们新的components_example文件夹中,我们可以添加一个新的penguin_animated.py文件,代码如下:
touch penguin_animated.py
然后,在这个新文件中,我们可以创建这个新应用程序。以下代码块创建了一个函数,如streamlit-lottie库中的示例所示(github.com/andfanilo/streamlit-lottie),它允许我们从 URL 加载lottie文件,并将该动画加载到应用程序顶部:
import streamlit as st
from streamlit_lottie import st_lottie
import requests
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
def load_lottieurl(url: str):
r = requests.get(url)
if r.status_code != 200:
return None
return r.json()
lottie_penguin = load_lottieurl('https://assets9.lottiefiles.com/private_files/lf30_lntyk83o.json')
st_lottie(lottie_penguin, height=200)
上一段代码使用了requests库来定义一个我们可以用来从链接加载lottie文件的函数。在这个例子中,我已经预先填写了一个链接,指向一个可爱的企鹅动画。然后,我们可以使用我们的新函数加载文件,并通过我们从streamlit-lottie库中导入的st_lottie()函数调用该文件。接下来,我们可以用之前定义的用户输入和散点图完成应用程序。代码如下所示:
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
penguin_file = st.file_uploader('Select Your Local Penguins CSV')
if penguin_file is not None:
penguins_df = pd.read_csv(penguin_file)
else:
penguins_df = pd.read_csv('penguins.csv')
sns.set_style('darkgrid')
markers = {"Adelie": "X", "Gentoo": "s", "Chinstrap":'o'}
fig, ax = plt.subplots()
ax = sns.scatterplot(data = penguins_df, x = selected_x_var,
y = selected_y_var, hue = 'species', markers = markers,
style = 'species')
plt.xlabel(selected_x_var)
plt.ylabel(selected_y_var)
plt.title("Scatterplot of Palmer's Penguins")
st.pyplot(fig)
这段代码将创建如下应用程序,它只是我们带有可爱企鹅动画的Palmer's Penguins应用程序(应用程序已被裁剪以简洁显示):
](https://github.com/OpenDocCN/freelearn-ds-zh/raw/master/docs/gtst-stlt-ds/img/B16864_07_3.jpg)
图 7.3 – 可爱的企鹅动画
streamlit-lottie 还允许我们通过 speed、width 和 height 参数分别改变动画的速度、宽度和高度。如果你觉得动画播放得太慢,可以将速度增加到如 1.5 或 2,这样可以将速度提高 50% 或 100%。但是,height 和 width 参数是动画的像素高度/宽度,默认值是动画的原始尺寸(例如,企鹅动画的大小大约是 ~700 像素 x ~400 像素)。在下面的代码块中,我们将改变动画的速度、宽度和高度:
import streamlit as st
from streamlit_lottie import st_lottie
import requests
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
def load_lottieurl(url: str):
r = requests.get(url)
if r.status_code != 200:
return None
return r.json()
lottie_penguin = load_lottieurl('https://assets9.lottiefiles.com/private_files/lf30_lntyk83o.json')
st_lottie(lottie_penguin, speed=1.5, width=800, height=400)
以下代码块与其他应用相同,只不过我们已经将动画的速度、宽度和高度分别更改为 1.5、800 和 400。需要一些时间才能适应这些输入如何交互,因为动画可能有不同的大小和速度!你可以在这里看到应用的不同设置:
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
penguin_file = st.file_uploader('Select Your Local Penguins CSV')
if penguin_file is not None:
penguins_df = pd.read_csv(penguin_file)
else:
penguins_df = pd.read_csv('penguins.csv')
sns.set_style('darkgrid')
markers = {"Adelie": "X", "Gentoo": "s", "Chinstrap":'o'}
fig, ax = plt.subplots()
ax = sns.scatterplot(data = penguins_df, x = selected_x_var,
y = selected_y_var, hue = 'species', markers = markers,
style = 'species')
plt.xlabel(selected_x_var)
plt.ylabel(selected_y_var)
plt.title("Scatterplot of Palmer's Penguins")
st.pyplot(fig)
当我们通过增加宽度和高度使企鹅动画变得比之前的版本大得多时,可以看到动画大小的变化,正如下面的截图所示。当你自己运行时,你还会注意到动画速度也有所增加。我强烈建议你运行这个应用,因为企鹅动画真的非常可爱:

图 7.4 – 最终版企鹅动画应用
这就完成了我们对 streamlit-lottie 的介绍!我已经养成了在我创建的每个 Streamlit 应用的顶部放置一个漂亮的动画的习惯——这营造了一种设计感,让 Streamlit 应用显得更有目的性,并立即提醒用户,这不是一个静态文档,而是一个动态互动的应用程序。接下来,让我们进入 pandas-profiling!
使用 Streamlit 组件 – streamlit-pandas-profiling
pandas-profiling 是一个非常强大的 Python 库,它自动化了一些探索性数据分析(EDA)工作,这通常是任何数据分析、建模甚至数据工程任务的第一步。在数据科学家开始几乎所有的数据工作之前,他们希望先了解底层数据的分布、缺失行的数量、变量之间的相关性以及许多其他基本信息。正如我们之前提到的,这个库自动化了这一过程,并将这个交互式分析文档嵌入到 Streamlit 应用中供用户使用。
在名为 pandas-profiling 的 Streamlit 组件背后,有一个同名的完整 Python 库,该组件从中导入它的功能。这里的 Streamlit 组件实际上是以非常易于集成的方式呈现 pandas-profiling Python 库的输出。对于这一部分,我们将首先学习如何实现该库,然后探索生成的输出。
在我们的示例中,我们将继续使用上一节关于帕尔默企鹅的数据代码,并将自动生成的个人资料添加到应用程序的底部。该代码只有几行——我们需要为数据集生成报告,然后使用 Streamlit 组件将其添加到应用程序中。接下来的代码块导入了必要的库,然后基于我们定义的penguins_df变量创建并将个人资料添加到应用程序中:
import streamlit as st
from streamlit_lottie import st_lottie
import requests
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from pandas_profiling import ProfileReport
from streamlit_pandas_profiling import st_profile_report
def load_lottieurl(url: str):
r = requests.get(url)
if r.status_code != 200:
return None
return r.json()
lottie_penguin = load_lottieurl('https://assets9.lottiefiles.com/private_files/lf30_lntyk83o.json')
st_lottie(lottie_penguin, speed=1.5, width = 800, height = 400)
st.title("Palmer's Penguins")
st.markdown('Use this Streamlit app to make your own scatterplot about penguins!')
selected_x_var = st.selectbox('What do want the x variable to be?',
['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'])
selected_y_var = st.selectbox('What about the y?',
['bill_depth_mm', 'bill_length_mm', 'flipper_length_mm', 'body_mass_g'])
本节使用了相同的streamlit-lottie库,同时加载了pandas_profiling和streamlit-pandas-profiling库以供使用。这是一个很好的经验——我们可以将 Streamlit 组件视为独特的乐高积木,随意组合它们来创建新的有趣的 Streamlit 应用程序。接下来的部分读取我们的 DataFrame,并将 pandas 个人资料添加到数据集中!以下是代码:
penguin_file = st.file_uploader('Select Your Local Penguins CSV')
if penguin_file is not None:
penguins_df = pd.read_csv(penguin_file)
else:
penguins_df = pd.read_csv('penguins.csv')
sns.set_style('darkgrid')
markers = {"Adelie": "X", "Gentoo": "s", "Chinstrap":'o'}
fig, ax = plt.subplots()
ax = sns.scatterplot(data = penguins_df, x = selected_x_var,
y = selected_y_var, hue = 'species', markers = markers,
style = 'species')
plt.xlabel(selected_x_var)
plt.ylabel(selected_y_var)
plt.title("Scatterplot of Palmer's Penguins")
st.pyplot(fig)
st.title('Pandas Profiling of Penguin Dataset')
penguin_profile = ProfileReport(penguins_df, explorative=True)
st_profile_report(penguin_profile)
生成的应用程序包含这个个人资料,开始时是一个概述,包含有关变量数量、数据集的任何警告(例如,我们被警告说企鹅数据集的某些行缺失了性别信息)以及其他基本信息。以下截图显示了个人资料的顶部部分:

图 7.5 – pandas 个人资料
我强烈建议你亲自尝试这个组件,看看这几行代码所生成的大量信息。它包括每个变量的直方图和基本统计信息,数据集开始和结束的样本行,甚至是一个相关矩阵,解释了几种不同的相关变量。以下截图显示了我们企鹅数据集的相关性部分输出——我们可以立即看到,身体质量与企鹅的鳍足长度呈正相关:

图 7.6 – Pearson 相关系数
注意
自己尝试一下,看看完整的色谱效果。
希望你现在已经对如何使用这个组件添加探索性数据分析(EDA)有了清晰的理解,这对于邀请用户提供他们自己的数据集(例如在企鹅示例中)应该会有所帮助。
寻找更多组件
这三个组件仅仅是社区创建的所有组件的一个小部分,到你阅读这篇文章时,我相信这些组件的数量会大大增加。寻找新的有趣组件的最佳地方是 Streamlit 网站的streamlit.io/gallery?type=components&category=featured 或讨论论坛discuss.streamlit.io/tag/custom-components。当你找到一个你认为有趣的组件时,就像我们之前做的那样,使用pip下载它并阅读足够的文档来开始使用!
总结
到此为止,我希望你已经非常熟悉如何下载和使用 Streamlit 组件,并且能够轻松找到社区创建的新的 Streamlit 组件。你还应该了解如何将 GitHub gist 示例、Lottie 动画和自动化的pandas-profiling功能添加到你构建的应用中。
在下一章,我们将深入探讨如何通过云服务提供商,如亚马逊网络服务(AWS),部署您自己的 Streamlit 应用。
第八章:第八章:使用 Heroku 和 AWS 部署 Streamlit 应用
在 第五章中,使用 Streamlit Sharing 部署 Streamlit,我们学习了如何使用 Streamlit Sharing 部署我们的 Streamlit 应用。Streamlit Sharing 快速、简便,对于大多数应用都非常有效,但也有一些缺点,主要是我们只能同时托管三个免费的应用,并且计算能力也有限。以下摘自 Streamlit Sharing 页面:
应用程序最多可使用 1 个 CPU、800 MB 的 RAM 和 800 MB 的专用存储,且都在共享执行环境中运行。
如果你处于一个需要同时部署超过三个应用,或者需要更多计算能力的情况,例如运行更复杂的机器学习模型(需要 GPU 或更多 RAM),那么本章就是为你准备的!我们将介绍如何在 AWS 和 Heroku 上设置账户,并如何在那里完全部署你的 Streamlit 应用。
在本章中,我们将涵盖以下主题:
-
在 AWS、Streamlit Sharing 和 Heroku 之间选择
-
使用 Heroku 部署 Streamlit
-
使用 AWS 部署 Streamlit
技术要求
这是本章所需安装项的清单:
-
Heroku 账户:Heroku 是一个受欢迎的平台,数据科学家和软件工程师用它来托管他们的应用、模型和 API(应用程序接口),并且它是由 Salesforce 所拥有。要获取 Heroku 账户,请前往
signup.heroku.com创建一个免费账户。 -
Heroku 命令行界面 (CLI):要有效使用 Heroku,我们需要下载 Heroku CLI,这样我们才能运行 Heroku 命令。要下载它,请按照此处列出的说明操作:
devcenter.heroku.com/articles/heroku-cli。 -
亚马逊网络服务 (AWS) 账户:在使用 AWS 之前,我们首先需要注册一个自己的 Amazon 账户,你可以通过
aws.amazon.com/free完成注册。幸运的是,学生(持 .edu 账户)、创业公司创始人和非营利组织有一个慷慨的免费套餐可以使用。一旦注册成功,我强烈建议你在账户中设置账单提醒(详情见console.aws.amazon.com/billing/home?#preferences),确保你不会超出免费套餐,并且在你部署自己的应用时,确保不会花费超过预期。 -
PuTTy(仅限 Windows):如果你使用 Windows,你需要下载并安装 PuTTY 程序,它允许 Windows 操作系统使用一种叫做 安全外壳协议(SSH)的协议。要下载 PuTTY,请前往
www.putty.org/并按照安装说明进行操作。然后,在本章中每次使用 SSH 时,打开 PuTTY 并按常规步骤进行操作!
现在我们已经有了需求,让我们开始吧!
选择 AWS、Streamlit Sharing 和 Heroku 之间的权衡
从高层次来看,当我们试图部署 Streamlit 应用,使得互联网上的用户能够看到我们的应用时,实际上我们在做的是租用一台由其他人(如 Amazon)拥有的计算机,并向这台计算机提供一系列启动我们应用的指令。选择使用哪个平台,如果没有系统部署的背景或者没有先试过每个选项,是很难决定的,但有几个启发式方法可以帮助你。这个决策的两个最重要因素是系统的灵活性和启动所需的时间。请注意,这两个因素是相互权衡的。如果你使用 Streamlit Sharing,你不能说“我想在 macOS 上运行,并且我想给这个应用加两个 GPU”等等,但作为回报,你获得了一个极其简单的过程,你只需要将 Streamlit Sharing 指向你的 GitHub 仓库,它会处理所有其他需要做的小决策。
另一方面,AWS 和 Heroku 给你提供了更多的灵活性,但需要时间来设置(正如你将发现的那样!)。这两者之间最大的区别在于,Heroku 是一个 平台即服务产品,而 Amazon 是一个 基础设施即服务产品,这意味着从实际操作上来说,Heroku 相比 Streamlit Sharing 提供了更多的灵活性,允许你做一些事情,比如提供更多的计算资源,并且比 AWS 更快部署,正如你在下图中所看到的:

图 8.1 – Heroku 与 AWS 与 Sharing
然而,AWS 的优势在于其极高的灵活性。AWS 允许你在 Ubuntu、macOS、Windows 和 Red Hat Linux 之间进行选择,支持多种不同类型的数据库,并且似乎是无限可定制的。当你在构建 Streamlit 应用时,如果你想快速制作一个原型来测试某个想法,Streamlit Sharing 是非常适合你的。如果你需要一个功能完善的公共应用,并且需要更多计算资源,那么 Heroku 可能是最佳选择。如果你需要为复杂的机器学习应用提供终极灵活性,或者你的业务完全依赖于 Streamlit,那么 AWS 可能是最佳选择。在本章的其余部分,我们将深入探讨如何在 AWS 和 Heroku 上部署你自己的应用,因为我们已经在第五章中直接介绍了 Streamlit Sharing 的内容,使用 Streamlit Sharing 部署 Streamlit。让我们从 Heroku 开始吧!
使用 Heroku 部署 Streamlit
Heroku 比 AWS 稍微更快、更简洁,但比 Streamlit Sharing 更繁琐。不过,如果你的 Streamlit Sharing 仓库已经用完,或者你需要更多计算资源,但不想要 AWS 提供的无限配置选项,那么 Heroku 就是适合你的地方。另一个优点是,你可以为应用设置自定义 URL,而 Streamlit Sharing 目前不支持这一点(至少目前还不支持)。要在 Heroku 上部署我们的 Streamlit 应用,我们需要执行以下步骤:
-
设置并登录 Heroku。
-
克隆并配置我们的本地仓库。
-
部署到 Heroku。
让我们详细看看每个步骤!
设置和登录 Heroku
在本章的技术要求部分,我们介绍了如何下载 Heroku 并创建账户。现在,我们需要通过命令行登录 Heroku,运行以下命令,并在提示时进行登录:
heroku login
这将把我们带到 Heroku 页面,一旦我们登录,就可以开始了。这个命令将使你在机器上保持登录状态,直到密码更改或你故意退出 Heroku。
克隆并配置我们的本地仓库
接下来,我们需要切换到企鹅机器学习应用所在的目录。我的应用文件夹在Documents文件夹内,因此以下命令将我带到该文件夹,但你的文件夹可能不同:
cd ~/Documents/penguin_ml
如果你还没有将仓库下载到本地,并且在 GitHub 上没有相应的仓库,可以去看看第五章,使用 Streamlit Sharing 部署 Streamlit,了解如何开始使用 GitHub。或者,你也可以运行以下命令,从我个人的 GitHub 上下载仓库,就像我们在从 AWS 部署时所做的那样:
git clone https://github.com/tylerjrichards/penguin_ml.git
强烈建议你使用自己的 GitHub 仓库进行实践,因为这比从我这里克隆应用并将其部署到 Heroku 要好得多。
现在,我们需要使用下一个命令为我们的应用创建一个唯一名称的 Heroku 应用(该应用将作为此名称进行部署,后缀为 .heroku.com)。我的名称是 penguin-machine-learning,你可以自己选择一个!
heroku create penguin-machine-learning
一旦我们有了这些,我们需要显式地将我们的 Git 仓库与刚创建的 Heroku 应用连接,这可以通过以下命令来完成:
heroku git:remote -a penguin-machine-learning
最后,我们将添加两个文件到仓库中,它们是启动 Heroku 所必需的:Procfile 文件和 streamlit_setup.sh 文件。Heroku 使用名为 streamlit run 的命令来启动我们的应用。我们先通过以下命令创建 streamlit_setup.sh 文件:
touch streamlit_setup.sh
我们可以用文本编辑器打开这个文件,并将以下内容添加进去,这样就能在根目录下创建我们熟悉的 config.toml 文件:
mkdir -p ~/.streamlit
echo "[server]
headless = true
port = $PORT
enableCORS = false
" > ~/.streamlit/config.toml
保存此文件后,我们需要创建一个 Procfile,运行 streamlit_setup.sh 文件,并启动我们的 Streamlit 应用:
touch Procfile
在我们刚刚创建的 Procfile 文件中,我们接下来将添加以下内容:
web: sh streamlit_setup.sh && streamlit run penguins_streamlit.py
现在我们已经设置好 Streamlit 应用,最后一步就是将其部署到 Heroku!
部署到 Heroku
在部署之前,我们的应用中有几个新的文件,所以我们需要通过以下命令将它们添加到 Git 仓库中:
git add .
git commit -m 'added heroku files'
git push
现在,本章的最后一步是将应用推送到 Heroku,我们可以通过以下命令来完成:
git push heroku main
这将启动 Heroku 构建,不久我们就会看到我们的企鹅应用被部署到 Heroku,任何人都可以查看。我们一直在开发并刚刚部署的应用可以通过以下链接找到(附带截图!),penguin-machine-learning.herokuapp.com/,该应用的 GitHub 仓库可以在 github.com/tylerjrichards/penguin_ml 找到。它与我们在本章早些时候部署到 AWS 的应用相同,如下图所示:

图 8.2 – Heroku 应用部署
我们已经成功地将一个 Streamlit 应用部署到 Heroku 平台,但如果我们需要更多控制权,想要更灵活地管理应用背后的服务器,我们需要直接在 AWS 上构建,如下节所示!
使用 AWS 部署 Streamlit
相比于 Heroku,AWS 部署应用的过程要复杂得多,但它提供了几乎无限的选项。部署自己的应用到 AWS 需要几个步骤,包括以下内容:
-
选择并启动虚拟机
-
安装必要的软件
-
克隆并运行你的应用
-
长期的 AWS 部署
我们将按顺序逐步进行!
选择并启动虚拟机
AWS 几乎拥有成百上千种服务选项,涵盖从部署机器学习模型、计算资源到各种其他应用。在本书中,我们提到了以下截图中的服务,这些服务都被统一归类为 AWS,但更准确地说,我们将使用 Amazon Elastic Compute Cloud,简称 Amazon EC2。下面的截图展示了仅针对计算资源的服务范围,这不包括任何与机器学习、业务应用或存储相关的服务:

图 8.3 – AWS 计算
Amazon EC2 是一种动态的、按需付费的服务,会根据使用情况自动扩展。如果你的 Streamlit 应用有 10、100 或 10,000 个并发用户,EC2 会自动调整分配给应用的计算资源,以满足用户需求。你只需为实际使用的资源付费!
要开始使用,前往 console.aws.amazon.com/ec2/v2/home 并点击显示 Launch instance 的按钮,如下截图所示。你的默认区域可能与我的不同,完全没问题!AWS 区域允许你选择计算资源的物理位置,以便满足低延迟需求,或者满足由于法规要求而必须选择特定地区的数据托管(例如,通用数据保护条例(GDPR)要求在欧盟地区)。大多数情况下,AWS 为你选择的默认区域已经足够适用:

图 8.4 – EC2 启动
一旦你启动了实例,将会有七个标签:
-
选择 AMI(亚马逊机器镜像)或你的虚拟机使用的操作系统
-
选择实例类型(选择虚拟机的计算/内存/存储配置)
-
配置实例
-
添加存储
-
添加标签
-
配置安全组
-
复习
你可能开始理解我之前提到的“灵活性与速度”之间的权衡了!幸运的是,我们只需要从这些选项中选择其中几个,首先从选取我们的 AMI 开始。当我们点击 Launch instance 按钮时,我们将看到包括但不限于以下选项:
-
Amazon Linux 2 AMI
这是亚马逊自有的选项,适用于免费套餐,并且与 EC2 的兼容性很好。
-
Red Hat 企业版 Linux
这个选项是由红帽基金会创建的企业版 Linux,红帽基金会专注于开源企业解决方案 (
www.redhat.com/en)。根据版本和卷类型的不同,提供了多种选项。 -
Ubuntu Server
Ubuntu 是另一个基于 Linux 构建的开源操作系统,类似于 Red Hat。它们也有各种免费和付费选项,与 Red Hat 类似。
我建议选择你最熟悉的操作系统。如果你已经使用过 Ubuntu 服务器,可以尝试最新的 Ubuntu 选项,在这种情况下是 Ubuntu Server 20.04。最常用的 AMI 选项都基于 Linux,这是一个开源操作系统,有很多变种,包括 Red Hat、Debian 和 Ubuntu。
为了跟上本章内容,选择默认的 Amazon 选项Amazon Linux 2。选中此选项后,你会进入选择实例类型页面,选择任何符合免费套餐条件的类型,如下图所示。当然,如果你想为更多内存或虚拟 CPU 付费也是可以的,但此时并不必要:

图 8.5 – AWS AMI 选项
接下来,我们可以跳过接下来的几个选项,直到你看到第六个标签,名为配置安全组。在这里,我们需要做几个编辑:
-
TCP 规则
我们需要设置我们的
8501,即自定义的 Streamlit 端口。 -
访问源
我们还需要允许任何人访问我们的应用,因此我们也将源设置为任何地方,如下图所示:

图 8.6 – 安全设置
现在,我们准备启动了!前往第七个标签审查,如果一切看起来正常,就点击启动按钮。接下来会弹出一个创建公私钥的方式,一把由 AWS 持有,另一把由你持有,以便你能够通过命令行访问这个新的虚拟计算机,如下图所示:

图 8.7 – 键值对
把它想象成一个独特的密码,它以文件的形式下载。你可以把这个文件保存在最方便和最安全的地方,但一定要确保从不将这个文件上传到公共位置,比如 GitHub 仓库,否则其他人可能会来访问你的虚拟机!现在,我们已经启动了 EC2 实例,可以通过命令行访问它并下载我们的应用。
安装必要的软件
在这个例子中,我们将尝试部署我们在第四章《使用 Streamlit 进行机器学习》中创建的企鹅 ML 应用,并在第五章《通过 Streamlit Sharing 部署 Streamlit》中进行部署,部署到 Streamlit Sharing 上。现在我们已经有了虚拟机和目标,接下来需要通过命令行访问我们的虚拟机。首先,我们需要找出 AWS 实例的公共 DNS。使用这个链接定位你的 AWS 实例,console.aws.amazon.com/ec2/v2/home#Instances,并查找 ec2-10-857-84-485.compute-1.amazonaws.com。这些数字是我随便编的,但你的应该与此类似。
现在,我们可以通过 SSH 访问我们的虚拟机,SSH 是安全外壳协议,使用以下命令来结合密码和公有 DNS:
ssh -i "path_to_private_key.pem" ec2-user@<insert_Public_DNS>
经常,AWS 命令会让人感觉像是魔法咒语,尤其是当你刚开始时。经过一段时间的实践,你会变得更加熟练。此时,AWS 可能会在命令行中询问你一些问题,关于是否允许某些类型的访问,这取决于你的本地机器上安全设置的配置。在你确认希望连接后,如果你看到类似下图的内容,就知道你已经连接成功:

图 8.8 – AWS 登录
这是你自己的新虚拟计算机!这台计算机上没有任何程序、文件夹,几乎没有其他东西;它就是从亚马逊的盒子里出来的全新机器。我们通过ec2租用的每台计算机,起初几乎什么也没有,因此我们需要下载项目所需的一切。有很多方法可以做到这一点。我们可以执行以下操作:
-
手动安装所有内容。
-
安装一个预打包的安装程序,如 Anaconda 或 Miniconda。
-
使用 Docker 创建一组安装指令。
我建议在大多数使用案例中选择第二个选项,因为 Anaconda 或 Miniconda 设计用来处理安装 Python 时遇到的所有困难,包括路径设置问题,并且能够安装各种 Python 和 R 包。Anaconda 及其精简版(即更小的)Miniconda,以其在非自带环境下安装困难而著名。如果你需要在虚拟机或本地计算机上安装其他 Python 版本,我建议选择选项 1或选项 3。
对于在虚拟机上安装和设置 Miniconda,我们可以运行以下命令,使用wget下载 Miniconda 到文件位置~/miniconda.sh,然后使用bash运行安装文件,最后更改我们的路径,以便可以更轻松地使用conda下载包:
wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh
bash ~/miniconda.sh -b -p ~/miniconda
export PATH="$HOME/miniconda/bin":$PATH
太好了!现在我们有了最新版本的python、pip以及一大堆 Python 包。然而,Miniconda 本身并不包含 Streamlit,因此我们将使用下一个命令来下载、安装并通过启动 Streamlit 演示应用来测试 Streamlit 的安装:
pip install Streamlit
streamlit hello
当我们运行此命令时,应该会在终端中看到以下内容(尽管网络和外部网址可能不同):

图 8.9 – 第一个 Streamlit 命令
当你从任何浏览器访问外部网址时,你将看到 Streamlit 演示应用,如下图所示:

图 8.10 – Streamlit 演示
我们现在已经从 AWS 部署了第一个 Streamlit 应用。现在,要部署我们构建的 Streamlit 应用。
克隆并运行你的应用
我们现在有了一个可以运行 Streamlit 的虚拟机,接下来的步骤是将我们自己的应用下载到机器上。最直接的方法是使用 Git 并克隆存储你企鹅机器学习应用的仓库。如果你还没有在第五章《使用 Streamlit Sharing 部署 Streamlit》中完成这一步,可以随时使用我的 GitHub 仓库:github.com/tylerjrichards/penguin_ml.git。以下代码下载git并从 GitHub 下载我们的应用:
conda install git
git clone https://github.com/tylerjrichards/penguin_ml.git
这将会在我们当前的目录下创建一个名为penguin_ml的新文件夹,里面包含 Streamlit 应用的所有文件。这个应用比 Miniconda 自带的库需要更多的库,比如 Seaborn 和 scikit-learn,因此我们需要在运行应用之前下载这些库。我们已经将这些库的名称放入一个名为requirements.txt的文件中,因此我们需要使用下一组命令将pip指向这个文件:
cd penguin_ml
pip install -r penguin_ml/requirements.txt
现在,我们的最后一步是运行我们的 Streamlit 应用:
streamlit run penguins_streamlit.py
当我们在 AWS 终端访问外部 URL 时,我们会看到我们的 Streamlit 应用在那里完全正常运行,如下图所示:

图 8.11 – AWS 企鹅应用
就这样!我们的应用现在已经在 AWS 上运行,可以被全世界看到。从这个点开始,我们可以通过你可能已经拥有的个人网站链接到我们的应用,或者将其发送给那些可能对分类自己的一组企鹅感兴趣的人。
长期 AWS 部署
我们面临的最后一个问题是,连接我们本地计算机与 AWS 的 SSH 会话必须保持运行,Streamlit 应用才能持续运行。在大多数使用场景下,这种方式不可行,因为你通常希望用户能够在本地计算机与 AWS 断开连接时,仍能与 Streamlit 应用进行交互。于是,tmux(终端复用器)就派上了用场,它可以保持终端会话持续运行,而不受本地连接的影响。要下载tmux,我们可以在连接到 AWS 虚拟机时运行以下命令:
sudo yum install tmux
现在,我们可以开始一个新的tmux会话,并通过运行以下命令启动我们的 Streamlit 应用:
tmux
streamlit run penguins_streamlit.py
如果我们与 AWS 的连接断开,tmux将保持我们的应用运行。我们可以随时按Ctrl + D退出tmux会话,并通过运行tmux attach重新进入会话。
这样就完成了 Streamlit 与 AWS 的部署!正如你所见,Streamlit Sharing 可以开箱即用地解决大部分这些难题,因此我会尽量让 Streamlit Sharing 在可能的情况下工作。然而,这一会话应该让你更好地了解当我们使用 AWS 时所面临的广泛选项和配置控制,这在将来可能会派上用场。
总结
到目前为止,这一章是我们所有章节中最具技术性的,所以恭喜你坚持了下来!部署应用程序通常既困难又耗时,并且需要软件工程和 DevOps 方面的技能,还常常需要具备版本控制软件(如 Git)和 Unix 风格命令与系统的经验。这也是为什么 Streamlit Sharing 如此重要的创新之一,但在本章中,我们已经学习了如何通过租用自己的虚拟机,并在 AWS 和 Heroku 上进行部署,推动 Streamlit 部署的前沿。我们还学习了如何在开始之前确定正确的部署策略,这将节省数小时甚至数天的工作(没有什么比完成应用程序部署后发现需要使用另一个平台更糟糕的了!)。
接下来,我们将进入本书的第三部分,也是最后一部分,重点讨论 Streamlit 的各种应用,首先是如何通过 Streamlit 提升求职申请。下一章将重点讲解如何通过 Streamlit 应用打动招聘经理和招聘人员,如何在实际求职申请环节中使用 Streamlit 应用程序,比如许多面试中臭名昭著的带回家部分,以及如何通过数据项目证明技能,来提升数据科学简历。
第三部分:Streamlit 使用案例
现在我们了解了如何创建和部署 Streamlit 应用程序,本节将重点介绍 Streamlit 的各种使用案例。我们将通过复杂项目的实际示例,并采访一些高级用户,了解他们如何利用 Streamlit 的一切功能。
本节涵盖以下章节内容:
-
第九章**,使用 Streamlit 改善求职申请
-
第十章**,数据项目 – 在 Streamlit 中进行原型项目
-
第十一章**,团队中使用 Streamlit
-
第十二章**,与高级用户的访谈
第九章:第九章:使用 Streamlit 提升工作申请
在本书的这一部分,你应该已经是一个经验丰富的 Streamlit 用户。你对所有内容都有很好的掌握——从 Streamlit 的设计到部署,再到数据可视化,及其间的一切。本章旨在应用为主;它将展示一些 Streamlit 应用的优秀用例,帮助你获得灵感,创造属于你自己的应用!我们将从展示如何使用 Streamlit 进行技能证明数据项目开始。然后,我们将讨论如何在工作申请的Take Home部分使用 Streamlit。
本章将涵盖以下主题:
-
使用 Streamlit 进行技能证明数据项目
-
在 Streamlit 中提升工作申请
技术要求
以下是本章所需的软硬件安装列表:
-
streamlit-lottie:要下载这个库,请在终端运行以下代码:pip install streamlit-lottie有趣的是,
streamlit-lottie使用了lottie开源库,这使我们能够在 Streamlit 应用中添加网页原生动画(如 GIF)。坦率地说,这是一个很棒的库,可以用来美化 Streamlit 应用,由 Streamlit 应用的多产创作者 Andy Fanilo 创建。 -
工作申请示例文件夹:本书的中央仓库可以在
github.com/tylerjrichards/Getting-Started-with-Streamlit-for-Data-Science找到。在这个仓库中,job_application_example文件夹将包含你在本章第二部分(工作申请)中所需的一些文件。如果你还没有下载这个主仓库,请在终端使用以下代码进行克隆:git clone https://github.com/tylerjrichards/Getting-Started-with-Streamlit-for-Data-Science
现在我们已经设置好一切,开始吧!
使用 Streamlit 进行技能证明数据项目
向他人证明你是一个熟练的数据科学家是出了名的困难。任何人都可以在简历上写上 Python 或机器学习,甚至可以在做机器学习的大学研究小组工作。但往往,招聘人员、你想合作的教授以及数据科学经理们更多依赖于简历上的一些间接标志来评估能力,例如是否就读于“名校”,或已经拥有一份高级的数据科学实习或工作经历。
在 Streamlit 出现之前,这个问题几乎没有什么有效的解决方案。如果你将一个 Python 文件或 Jupyter Notebook 放到你的 GitHub 个人资料上,别人要花费的时间来了解这个作品是否令人印象深刻,风险太大。如果招聘人员必须点击你 GitHub 个人资料中的正确仓库,然后点击多个文件,直到找到那份没有注释、难以阅读的 Jupyter Notebook,你就已经失去了他们的关注。如果招聘人员在你的简历上看到“机器学习”,但需要点击五次才能看到你写的任何机器学习产品或代码,那么你就已经失去了他们的兴趣。大多数感兴趣的人在你的简历上花的时间非常少;根据我的个人网站(www.tylerjrichards.com)的数据,访客平均在该网站上停留约 2 分钟,之后就会离开。
解决这个问题的一种方法是尝试创建和分享专门展示你想要最广泛展示的技能的 Streamlit 应用程序。例如,如果你在基础统计学方面有丰富经验,你可以创建一个 Streamlit 应用程序,证明或说明一个基本的统计定理,如中心极限定理——就像我们在本书中早些时候做的那样。如果你有自然语言处理的经验,你可以创建一个展示你所创建的新的文本生成神经网络的应用程序。关键在于最小化别人需要点击的次数,直到他们看到你在某个领域的能力证明。
我们已经创建的许多 Streamlit 应用程序确实达到了这个目的。让我们来看几个例子。
机器学习 – Penguins 应用
在第四章,使用 Streamlit 进行机器学习中,我们创建了一个随机森林模型,使用我们的 Palmer's Penguin 数据集进行训练,根据特征如体重、栖息岛屿和喙长来预测企鹅的物种。然后,我们保存了该模型,以便在 Streamlit 应用程序中使用。
为了生成我们的 Streamlit 应用程序,我们需要(在第一次迭代中)运行以下代码。这将创建要部署的模型:
import pandas as pd
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
import pickle
penguin_df = pd.read_csv('penguins.csv')
penguin_df.dropna(inplace=True)
output = penguin_df['species']
features = penguin_df[['island', 'bill_length_mm', 'bill_depth_mm',
'flipper_length_mm', 'body_mass_g', 'sex']]
features = pd.get_dummies(features)
output, uniques = pd.factorize(output)
x_train, x_test, y_train, y_test = train_test_split(
features, output, test_size=.8)
rfc = RandomForestClassifier(random_state=15)
rfc.fit(x_train, y_train)
y_pred = rfc.predict(x_test)
score = accuracy_score(y_pred, y_test)
print('Our accuracy score for this model is {}'.format(score))
在这一部分中,我们导入库,加载数据,并训练/评估我们的模型,同时打印出评估结果。然后,我们使用以下代码将模型结果保存到pickle文件中:
rf_pickle = open('random_forest_penguin.pickle', 'wb')
pickle.dump(rfc, rf_pickle)
rf_pickle.close()
output_pickle = open('output_penguin.pickle', 'wb')
pickle.dump(uniques, output_pickle)
output_pickle.close()
回想一下,在本章的结尾,我们添加了一个新功能,使得如果用户上传自己的数据集,他们可以使用我们的模型训练脚本,完全基于他们的数据训练一个模型(前提是数据格式相同,且有一些前提条件)。
这个应用的最终版本显示了我们至少具备了数据清理的知识,如何对变量进行独热编码,如何评估模型在测试数据上的表现,最后,如何将预训练的模型部署到应用中。仅这一点就比在简历上单纯写“机器学习”要好得多,而且它展示了我们一些技能的实际证据。没有这种技能证明,招聘人员或招聘经理在查看我们的申请时,只能要么完全相信我们简历上的信息(但根据多年的简历筛选经验,这是一个很糟糕的假设),要么通过某些替代标准来评估我们的能力,比如大学学位(但这同样是一个评估能力的糟糕标准)。
除此之外,当我们在第五章《使用 Streamlit Sharing 部署 Streamlit》一节中将应用部署到 Streamlit Sharing 时,我们讨论了 Streamlit Sharing 中免费的一个自动化功能:查看应用源代码按钮。如以下截图所示,当我们部署应用时,Streamlit 会在用户的设置下拉菜单中添加一个按钮,允许他们查看应用背后的源代码:

图 9.1 – 查看应用源代码选项
通过这种方式,用户可以随时检查,确保恶意代码(例如,研究人员的企鹅数据是否被应用存储)不会被 Streamlit Sharing 部署。作为附加功能,用户还可以查看你编写的构建应用的代码,这增强了我们将 Streamlit 作为技能证明工具的能力。
可视化 – 漂亮的树木应用
在第六章《美化 Streamlit 应用》中,我们开发了一个 Streamlit 应用,它可以创建美观且动态的旧金山树木可视化,最终形成了以下这个应用:

图 9.2 – 映射一个 Web 应用
在这个应用中,我们需要创建多个不同的可视化(即两个直方图和一个地图),这些可视化会根据右侧用户输入动态更新。通过这样的应用,我们能够展示我们在数据处理方面的技能、对 pandas、Matplotlib 和 Seaborn 库的熟悉程度,甚至展示我们理解如何在 Python 中处理日期时间。我们来看看这个应用中专注于可视化的代码部分:
#define multiple columns, add two graphs
col1, col2 = st.beta_columns(2)
with col1:
st.write('Trees by Width')
fig_1, ax_1 = plt.subplots()
ax_1 = sns.histplot(trees_df['dbh'],
color=graph_color)
plt.xlabel('Tree Width')
st.pyplot(fig_1)
with col2:
st.write('Trees by Age')
fig_2, ax_2 = plt.subplots()
ax_2 = sns.histplot(trees_df['age'],
color=graph_color)
plt.xlabel('Age (Days)')
st.pyplot(fig_2)
st.write('Trees by Location')
trees_df = trees_df.dropna(subset=['longitude', 'latitude'])
trees_df = trees_df.sample(n = 1000, replace=True)
st.map(trees_df)
这段代码对于任何熟悉 Python 或其他脚本语言的人来说都相当容易理解,而且它比简历上仅仅写“数据可视化”或“pandas”要好得多。
到这个时候,我希望你已经信服了。Streamlit 应用程序是展示你的工作给招聘人员、潜在的招聘经理或任何需要你证明技能的人群的绝佳方式。在下一节中,我们将更加详细地讲解这个过程,并演示如何利用 Streamlit 来增强你向你可能想要应聘的公司提交的申请。
改善 Streamlit 中的求职申请
通常,数据科学和机器学习职位的求职申请依赖于带回家的数据科学挑战来评估候选人。坦率地说,这是一个残酷且令人烦恼的经历,因为申请人与雇主之间的动态关系。举例来说,候选人可能需要 5-10 小时才能完成一个数据科学挑战,但雇主可能只需要 10 分钟来评估它。此外,一个虚拟或电话面试对于雇主来说可能需要 30-45 分钟,加上额外的 15 分钟写反馈,相比之下,申请人也要花费同样的 30-45 分钟。因为雇主通过获得 5-10 小时的工作可以在每分钟员工时间上获得非常高的信号,所以雇主趋向于在招聘申请中包含这些挑战。
在这里你可以利用这个机会,通过创建一个完整运行的应用程序来从人群中脱颖而出,而不是仅仅发送 Jupyter Notebook、Word 文档或 PowerPoint 演示文稿给公司。
问题
让我们通过一个虚构的例子来讲解一下,一个求职者正在申请一家美国大型航空公司的职位。他们被给出两个主要的问题需要解决——其中一个包含了数据集:
-
问题 1:机场距离
第一个练习要求:“给定包含机场和地点(以经纬度表示)的数据集,编写一个函数,接受一个机场代码作为输入,并返回按距离从近到远排列的机场列表。”
-
问题 2:表示方法
第二个问题问到:“你如何将一系列搜索转化为一个表示旅行的数字向量?假设我们有成千上万的用户,并且希望以这种方式表示他们的所有旅行。理想情况下,我们希望这是一个通用的表示,可以在多个不同的建模项目中使用,但我们更关心的是找到相似的旅行。那么,具体来说,你如何比较两次旅行,以了解它们的相似程度?你认为前述数据中缺少了哪些信息,能够帮助改善你的表示方法?”
注意
不用担心在这一部分编写代码;你可以简单描述你将执行的任何数据转换。你的描述应该足够清晰,以便让阅读它的数据科学家知道如果有必要,如何实现你的解决方案。
现在我们已经有了所需的问题,我们可以开始新的 Streamlit 应用程序。为此,我采用了迄今为止在每一章中使用的相同过程。我们在我们的中央文件夹(streamlit_apps)中创建一个新的文件夹,命名为job_application_example。在这个文件夹内,我们可以通过终端使用以下命令创建一个 Python 文件,命名为 job_streamlit.py:
touch job_streamlit.py
回答问题 1
对于你来说,理解如何回答当前问题并不至关重要,但整体框架是非常重要的。我们创建的 Streamlit 应用程序应该像一个极具动态感的文档,根据 Streamlit 的能力回答问题,并以一种独特的方式呈现,这种方式是用 Word 文档无法轻易复制的。
首先,我们可以创建一个标题,介绍我们自己,并为整个应用程序的格式开个头。这里的一个改进是,使用我们在第七章《探索 Streamlit 组件》中学到的 streamlit-lottie 库,在应用程序顶部添加一个可选的动画,代码如下:
import streamlit as st
from streamlit_lottie import st_lottie
import pandas as pd
import requests
def load_lottieurl(url: str):
r = requests.get(url)
if r.status_code != 200:
return None
return r.json()
lottie_airplane = load_lottieurl('https://assets4.lottiefiles.com/packages/lf20_jhu1lqdz.json')
st_lottie(lottie_airplane, speed=1, height=200, key="initial")
st.title('Major US Airline Job Application')
st.write('by Tyler Richards')
st.subheader('Question 1: Airport Distance')
上述代码将创建一个带有美丽飞机动画的应用程序,如下图所示:

图 9.3 – 一只飞机的 GIF 动画
接下来,我们需要将问题复制并粘贴到子标题下。Streamlit 提供了多种在应用程序中插入文本的方式。我们尚未使用的一种方式是将文本放入三个撇号内,这会告诉 Streamlit 使用 Markdown 语言来显示该文本。这对于大块文本非常有用,例如下面这段文字,它开始回答第一个问题:
'''
The first exercise asks us 'Given the table of airports and
locations (in latitude and longitude) below,
write a function that takes an airport code as input and
returns the airports listed from nearest to furthest from
the input airport.' There are three steps here:
1\. Load Data
2\. Implement Distance Algorithm
3\. Apply distance formula across all airports other than the input
4\. Return sorted list of airports Distance
'''
如本章的技术要求部分所述,完成此应用程序需要两个文件。第一个是机场位置数据集(称为 airport_location.csv),第二个是显示哈弗辛距离的图片(即球面上两点之间的距离;文件名为 haversine.png)。请将这些文件复制到与 Streamlit 应用程序 Python 文件相同的文件夹中。
现在,我们需要完成第一步:加载数据。我们需要在 Streamlit 中完成这一步,并且还需要将代码展示给用户。这与其他 Streamlit 应用程序不同,其他应用程序中的代码通常隐藏在后台。然而,由于用户肯定希望看到我们的代码,因为他们会基于这些代码进行评估,我们需要同时做这两件事。我们可以使用之前用过的 st.echo() 函数,将代码块打印到应用程序中。我们可以使用以下代码来实现:
airport_distance_df = pd.read_csv('airport_location.csv')
with st.echo():
#load necessary data
airport_distance_df = pd.read_csv('airport_location.csv')
我想在这里指出,我们在此代码顶部添加了一条注释。这不是为了给您作为读者注释代码,而是为了应用程序的读者。偶尔在您编写的代码内部和前后的文本块中对编写代码的目的进行评论是个好习惯;这样读者就能理解您试图采取的方法。这在求职申请中尤为重要,但对于协作的 Streamlit 应用程序也是一个良好的实践。
我们的下一步是解释 Haversine 公式,并在我们的 Streamlit 应用程序中展示图像,我们已在以下代码块中完成。在您的文本块中采用叙述格式是完全可以的。只需想象您希望招聘经理阅读的内容,并尽可能复制:
'''
From some quick googling, I found that the haversine distance is
a good approximation for distance. At least good enough to get the
distance between airports! Haversine distances can be off by up to .5%,
because the earth is not actually a sphere. It looks like the latitudes
and longitudes are in degrees, so I'll make sure to have a way to account
for that as well. The haversine distance formula is labeled below,
followed by an implementation in python
'''
st.image('haversine.png')
现在,我们的应用程序应该看起来与以下屏幕截图类似:

图 9.4 – 加载问题 1 的数据
我们有要解决的问题清单、动画、Haversine 距离公式以及读取数据的基本代码。此时,我们需要在 Python 中实现 Haversine 距离公式并展示我们的实现:
with st.echo():
from math import radians, sin, cos, atan2, sqrt
def haversine_distance(long1, lat1, long2, lat2, degrees=False):
#degrees vs radians
if degrees == True:
long1 = radians(long1)
lat1 = radians(lat1)
long2 = radians(long2)
lat2 = radians(lat2)
#implementing haversine
a = sin((lat2-lat1) / 2)**2 + cos(lat1) * cos(lat2) * sin((long2-long1) / 2)**2
c = 2*atan2(sqrt(a), sqrt(1-a))
distance = 6371 * c #radius of earth in kilometers
return(distance)
我们代码的第一部分并不创建我们的函数,而是将我们将要在 Streamlit 应用程序中创建的函数打印出来。这样,应用程序的读者可以查看我们编写的两个重要代码片段并与代码本身进行交互。如果我们只是创建了一个函数来实现 Haversine 距离,那么我们应用程序的读者实际上不会真正了解我们是如何解决手头问题的!以下代码块创建了这个函数:
#execute haversine function definition
from math import radians, sin, cos, atan2, sqrt
def haversine_distance(long1, lat1, long2, lat2, degrees=False):
#degrees vs radians
if degrees == True:
long1 = radians(long1)
lat1 = radians(lat1)
long2 = radians(long2)
lat2 = radians(lat2)
#implementing haversine
a = sin((lat2-lat1) / 2)**2 + cos(lat1) * cos(lat2) * sin((long2-long1) / 2)**2
c = 2*atan2(sqrt(a), sqrt(1-a))
distance = 6371 * c #radius of earth in kilometers
return(distance)
我们完成了 Haversine 的实现!每当我们想要找到两个位置之间的距离时,我们可以调用我们的公式,输入经度和纬度,然后获得以公里为单位的距离。这个应用程序很有用;然而,目前它不比一个 Word 文档好多少。我们的下一步是允许用户输入他们自己的点来检查和查看 Haversine 距离是否有效。几乎没有人知道地球上两点之间相隔多少公里,因此我已包含了默认点并检查了它们之间的实际距离:
'''
Now, we need to test out our function! The
distance between the default points is
18,986 kilometers, but feel free to try out
your own points of interest.
'''
long1 = st.number_input('Longitude 1', value = 2.55)
long2 = st.number_input('Longitude 2', value = 172.00)
lat1 = st.number_input('Latitude 1', value = 49.01)
lat2 = st.number_input('Latitude 2', value = -43.48)
test_distance = haversine_distance(long1 = long1, long2 = long2,
lat1 = lat1, lat2 = lat2, degrees=True)
st.write('Your distance is: {} kilometers'.format(int(test_distance)))
当我们输入默认值时,应用程序返回的距离大约偏差了 2 公里,如下屏幕截图所示:

图 9.5 – 实现 Haversine 距离
此时,我们的下一步是通过在给定数据集上使用实现的 Haversine 距离计算器来组合所有部分。这在以下屏幕截图中简要显示:

图 9.6 – 已给出的机场距离
这个数据集包含机场代码及其对应的lat和long值。以下代码块展示了一种将两种距离结合起来的解决方案,并省略了完整的get_distance_list函数,因为它只是我们已经实现过两次的函数的复制:
'''
We have the Haversine distance implemented, and we also have
proven to ourselves that it works reasonably well.
Our next step is to implement this in a function!
'''
def get_distance_list(airport_dataframe, airport_code):
df = airport_dataframe.copy()
row = df[df.loc[:,'Airport Code'] == airport_code]
lat = row['Lat']
long = row['Long']
df = df[df['Airport Code'] != airport_code]
df['Distance'] = df.apply(lambda x: haversine_distance(lat1=lat, long1=long,
lat2 = x.Lat, long2 = x.Long, degrees=True), axis=1)
return(df.sort_values(by='Distance').reset_index()['Airport Code'])
with st.echo():
def get_distance_list(airport_dataframe, airport_code):
*copy of function above with comments*
最后,我们可以在给定的数据框上实现此距离公式。我们可以让用户从我们拥有数据的选项中输入自己的机场代码,并返回正确的值:
'''
To use this function, select an airport from the airports provided in the dataframe
and this application will find the distance between each one, and
return a list of the airports closest to furthest.
'''
selected_airport = st.selectbox('Airport Code', airport_distance_df['Airport Code'])
distance_airports = get_distance_list(
airport_dataframe=airport_distance_df, airport_code=selected_airport)
st.write('Your closest airports in order are {}'.format(list(distance_airports)))
这是我们第一个问题的结束部分。我们可以在最后添加一个可选部分,讲述如果我们有更多时间来处理这个问题,我们会如何修改我们的实现。如果你知道自己只想在整个应用程序上花费几个小时,但又希望展示你知道如何在有更多时间的情况下改进它,这总是一个好主意。以下代码块展示了一个例子,应直接放在前面的代码块后面:
'''
This all seems to work just fine! There are a few ways I would improve this if I was working on
this for a longer period of time.
1\. I would implement the [Vincenty Distance](https://en.wikipedia.org/wiki/Vincenty%27s_formulae)
instead of the Haversine distance, which is much more accurate but cumbersome to implement.
2\. I would vectorize this function and make it more efficient overall.
Because this dataset is only 7 rows long, it wasn't particularly important,
but if this was a crucial function that was run in production we would want to vectorize it for speed.
'''
另外,你也可以直接以一段关于前述代码的说明来结束,并继续处理第二个问题。此时,我们对问题 1的回答已完成,并应类似于以下截图:

图 9.7 – 获取用户输入
我们现在已经成功回答了问题 1!我们可以手动检查这些机场之间的距离以获得相同的结果。但让我们继续处理应用中的第二个问题。
回答问题 2
第二个问题更为直接,仅要求提供文本答案。在这里,技巧是尽量添加一些列表或 Python 对象,以打破大量的文本段落。首先,我们将解释我们回答问题的尝试,然后展示它在数据框中的展示效果:
'''
For this transformation, there are a few things
that I would start with. First, I would have to define
what a unique trip actually was. In order to do this, I would
group by the origin, the destination, and the departure date
(for the departure date, often customers will change around
this departure date, so we should group by the date plus or
minus at least 1 buffer day to capture all the correct dates).
Additionally, we can see that often users search from an entire city,
and then shrink that down into a specific airport. So we should also
consider a group of individual queries from cities and airpots in the
same city, as the same search, and do the same for destination.
From that point, we should add these important columns to each unique search.
'''
现在,我们可以思考一些在用户搜索美国主要航空公司航班时可能有用的列。我们可以将它们放入一个示例数据框,如下所示:
example_df = pd.DataFrame(columns=['userid', 'number_of_queries', 'round_trip', 'distance', 'number_unique_destinations',
'number_unique_origins', 'datetime_first_searched','average_length_of_stay',
'length_of_search'])
example_row = {'userid':98593, 'number_of_queries':5, 'round_trip':1,
'distance':893, 'number_unique_destinations':5,
'number_unique_origins':1, 'datetime_first_searched':'2015-01-09',
'average_length_of_stay':5, 'length_of_search':4}
st.write(example_df.append(example_row, ignore_index=True))
对于问题的其余部分,我们可以添加一些关于如何使用不同方法找到两个点之间距离的知识,然后就可以结束了:
'''
For answering the second part of the question, we should take the euclidian distance
on two normalized vectors. There are two solid options for comparing two
entirely numeric rows, the euclidian distance (which is just the straight line
difference between two values), and the manhattan distance (think of this as the
distance traveled if you had to use city blocks to travel diagonally across manhattan).
Because we have normalized data, and the data is not high dimensional or sparse, I
would recommend using the euclidian distance to start off. This distance would tell
us how similar two trips were.
'''
第二个问题的答案应类似于以下截图:

图 9.8 – 回答问题 2
如你所见,这个例子展示了如何利用 Streamlit 库帮助解决家庭作业中的数据任务,并制作更具吸引力的应用。此项工作的最后一步是部署这个 Streamlit 应用,并将链接分享给招聘人员。我强烈建议你将其部署到 Heroku 上,以确保其他人无法查看招聘公司提供的问题或数据。你也可以采取进一步的预防措施,例如在应用程序开头放置一个文本框,作为应用的“临时”密码保护,如下方代码块所示:
password_attempt = st.text_input('Please Enter The Password')
if password_attempt != 'example_password':
st.write('Incorrect Password!')
st.stop()
现在,除非用户在文本框中输入 example_password,否则整个应用程序将无法运行。这显然并不安全,但对于相对不重要的(至少在保密性方面)应用程序,如带回家的应用程序,它是有用的:

图 9.9 – 输入密码
如你所见,只有输入正确的密码,应用程序才能加载。否则,用户将看到一个空白页面。或者,你也可以在 Streamlit Sharing 中使用 Streamlit secrets 设置密码,这是目前 Streamlit for Teams 的一个功能,将在第十一章中讲解,使用 Streamlit for Teams。
总结
本章是我们迄今为止创建的最注重应用的章节。我们重点讨论了数据科学和机器学习面试的职位申请及申请流程。此外,我们还学习了如何为我们的应用程序设置密码保护,如何创建能够向招聘人员和数据科学招聘经理证明我们是熟练数据科学家的应用程序,以及如何通过创建 Streamlit 应用程序在远程数据科学面试中脱颖而出。下一章将重点介绍 Streamlit 作为一个玩具,你将学习如何为社区创建面向公众的 Streamlit 项目。
第十章:第十章:数据项目——在 Streamlit 中进行项目原型设计
在上一章中,我们讨论了如何创建针对职位申请的 Streamlit 应用。Streamlit 的另一个有趣应用是尝试新的、令人兴奋的数据科学创意,并为他人创建互动式应用。例如,将一个新的机器学习模型应用于现有数据集,进行用户上传的数据分析,或创建基于私人数据集的互动分析。做这个项目的理由有很多,比如个人教育或社区贡献。
在个人教育方面,通常,学习一个新主题的最佳方式是通过将其应用到你周围的世界或你熟悉的数据集中,观察它如何真正运作。例如,如果你想了解 主成分分析 是如何工作的,你可以在教科书中学习它,或看别人将它应用到数据集中。然而,我发现,当我自己亲自实践时,对一个主题的理解会飞跃性提高。Streamlit 正是为了这种情况而设计的。它让你可以在一个互动、轻松的环境中尝试新创意,而且这些创意可以轻松与他人共享。学习数据科学可以是一个合作的过程,这也是我选择在 Streamlit 中创建数据项目的另一个原因。
就社区贡献而言,Streamlit 的一个亮点——老实说,数据科学的亮点——就是围绕我们日常使用的工具和玩具所形成的日益壮大的社区。通过与他人一起学习,并在 Twitter 上分享 Streamlit 应用(twitter.com/tylerjrichards)、LinkedIn 和 Streamlit 论坛(discuss.streamlit.io/)上分享应用,我们可以摆脱大多数学校和大学中所教的零和博弈经验(即如果你的同学得了高分,通常会相对地影响你),转而朝向一个正和博弈的体验,在这种体验中,你能直接从他人学到的经验中受益。举个例子,如果你创建了一个帮助你理解主成分分析背后统计学的应用,分享给他人也许会让他们学到一些东西。
在本章中,我们将从头到尾完整地进行一个数据项目,从一个创意开始,到最终产品结束。具体来说,我们将涵盖以下主题:
-
数据科学创意
-
数据收集与清洗
-
制作 最小可行产品(MVP)
-
迭代改进
-
托管与推广
技术要求
在本节中,我们将使用网站Goodreads.com,这是一个由亚马逊拥有的流行网站,用于跟踪用户的阅读习惯,从开始和完成书籍的时间到他们想要阅读的下一本书。建议你首先访问www.goodreads.com/,注册一个账户,并稍微浏览一下(也许你还可以添加自己的书单!)。
数据科学创意
通常,构思一个新的数据科学项目是最具挑战性的部分。你可能会有许多疑虑。如果我开始一个没人喜欢的项目怎么办?如果我的数据实际效果不好怎么办?如果我想不出任何点子怎么办?好消息是,如果你创建的是你真正关心并且会使用的项目,那么最坏的情况就是你只有一个观众!如果你把项目发给我(tylerjrichards@gmail.com),我保证会读的。至少那样的话,你的观众就有两个了。
我自己创造或观察到的一些例子包括以下内容:
-
录制一个学期的乒乓球比赛以使用 Elo 模型确定最佳玩家(
www.tylerjrichards.com/Ping_pong.html) -
使用自然语言处理来判断旅馆中 Wi-Fi 的质量(
www.racketracer.com/2015/11/18/practical-natural-language-processing-for-determing-wifi-quality-in-hostels/) -
分析成千上万条披萨评论,找出你附近的最佳纽约披萨(
towardsdatascience.com/adventures-in-barstools-pizza-data-9b8ae6bb6cd1) -
使用 Goodreads 数据分析你的阅读习惯(
www.tylerjrichards.com/books_reco.html)
这些数据项目中只有一个使用了 Streamlit,其余的在库发布之前就已经完成。然而,如果将这些项目部署到 Streamlit 上,而不是仅仅上传到 Jupyter Notebook(项目 #1)或 Word 文档/HTML 文件(项目 #2 和 #3),它们都可以得到改进。
你可以使用多种不同的方法来构思自己的数据项目创意,但最常见的方法通常分为三类:
-
查找只有你能收集到的数据(例如,你朋友的乒乓球比赛)
-
查找你关心的数据(例如,Goodreads 的阅读数据)
-
想出一个你希望存在的分析/应用程序来解决你的问题并实施它(例如,旅馆 Wi-Fi 分析或找到你附近的最佳披萨店)。
你可以尝试其中之一,或者从你已有的其他想法开始。最好的方法是最适合你的方法!在本章中,我们将深入走一遍并重建 Goodreads 的 Streamlit 应用,作为数据项目的示例。你可以通过以下链接再次访问:www.tylerjrichards.com/books_reco.html。
这个应用旨在抓取用户的 Goodreads 历史,并生成一组图表,帮助用户了解自从使用 Goodreads 以来的阅读习惯。图表的样式应该类似于以下截图:

图 10.1 – Goodreads 图表示例
我通过对我的书籍历史进行个人分析得出了这个想法,然后我想,也许其他人也会对这个分析感兴趣!其实没有比这个更好的理由了,很多最有趣的项目往往都是这么开始的。首先,我们将着手收集和清洗 Goodreads 上现有的用户数据。
收集和清洗数据
有两种方式可以从 Goodreads 获取数据:通过应用程序编程接口(API),允许开发者以编程方式访问书籍数据;以及通过其手动导出功能。遗憾的是,Goodreads 将在不久的将来停用 API,并且从 2020 年 12 月开始不再向更多开发者提供访问权限。
原始的 Goodreads 应用使用 API,但我们的版本将依赖于 Goodreads 网站提供的手动导出功能。要获取你的数据,请访问www.goodreads.com/review/import并下载你的数据。如果你没有 Goodreads 账号,可以使用我的个人数据,数据可以在github.com/tylerjrichards/goodreads_book_demo找到。我已经将我的 Goodreads 数据保存在一个名为goodreads_history.csv的文件中,文件位于一个名为streamlit_goodreads_book的新文件夹里。要创建你自己的文件夹并设置适当的环境,请在终端中运行以下命令:
mkdir streamlit_goodreads_book
cd streamlit_goodreads_book
touch goodreads_app.py
现在我们准备开始了。我们其实并不知道这些数据是什么样的,也不知道数据集里有什么,所以我们的第一步是做以下几件事:
-
在应用顶部放置标题和说明。
-
允许用户上传自己的数据,如果没有数据,默认使用我们的数据。
-
将前几行数据写入应用中,以便我们查看。
以下代码块完成了所有这些操作。你可以随意更改文本,使你的应用显示你的名字,并添加链接到个人资料,供他人查看!目前,大约 10%的访问量来自我制作的 Streamlit 应用:
import streamlit as st
import pandas as pd
st.title('Analyzing Your Goodreads Reading Habits')
st.subheader('A Web App by [Tyler Richards](http://www.tylerjrichards.com)')
'''
Hey there! Welcome to Tyler's Goodreads Analysis App. This app analyzes (and never stores!)
the books you've read using the popular service Goodreads, including looking at the distribution
of the age and length of books you've read. Give it a go by uploading your data below!
'''
goodreads_file = st.file_uploader('Please Import Your Goodreads Data')
if goodreads_file is None:
books_df = pd.read_csv('goodreads_history.csv')
st.write("Analyzing Tyler's Goodreads history")
else:
books_df = pd.read_csv(goodreads_file)
st.write('Analyzing your Goodreads history')
st.write(books_df.head())
现在,当我们运行这个 Streamlit 应用时,应该能看到一个类似于以下截图的界面:

图 10.2 – 前五行
如你所见,我们得到一个数据集,其中每本书都是唯一的一行。此外,我们还获得了关于每本书的大量数据,包括书名和作者、书籍的平均评分、你对书籍的评分、书籍的页数,甚至是你是否已经读过、计划阅读或正在阅读该书。数据看起来大部分是干净的,但也有些奇怪的地方;例如,数据中既有出版年份又有原始出版年份,ISBN(国际标准书号)以 ="1400067820" 的格式出现,这非常奇怪。现在我们对手头的数据有了更多了解,可以切换到尝试为用户构建一些有趣的图表。
创建一个 MVP
通过查看我们的数据,我们可以从一个基本问题开始:我可以用这些数据回答哪些最有趣的问题?在查看数据并思考我希望从 Goodreads 阅读历史中获得什么信息后,我想到了以下几个问题:
-
我每年读多少本书?
-
我需要多久才能完成一本已开始的书?
-
我读过的书籍有多长?
-
我读过的书籍有多旧?
-
我如何将书籍的评分与其他 Goodreads 用户进行比较?
我们可以根据这些问题,弄清楚如何修改数据以便很好地可视化它们,然后通过打印出所有图表来完成我们的第一个版本。
我每年读多少本书?
对于第一个关于每年读书数量的问题,我们有 Date Read 列,数据呈现为 yyyy/mm/dd 格式。以下代码块将执行以下操作:
-
将我们的列转换为日期时间格式。
-
从
Date Read列中提取年份。 -
根据此列对书籍进行分组,并计算每年读书的数量。
-
使用 Plotly 绘制图表。
以下代码块执行此操作,从日期时间转换开始。这里需要特别注意的是,和所有事情一样,我第一次尝试时并没有做到完全正确。事实上,我花了一些时间才弄清楚如何管理和转换这些数据。当你自己创建项目时,不要觉得清理和转换数据花费了很长时间就不好!通常来说,这一步是最困难的:
goodreads_file = st.file_uploader('Please Import Your Goodreads Data')
if goodreads_file is None:
books_df = pd.read_csv('goodreads_history.csv')
st.write("Analyzing Tyler's Goodreads history")
else:
books_df = pd.read_csv(goodreads_file)
st.write('Analyzing your Goodreads history')
books_df['Year Finished'] = pd.to_datetime(books_df['Date Read']).dt.year
books_per_year = books_df.groupby('Year Finished')['Book Id'].count().reset_index()
books_per_year.columns = ['Year Finished', 'Count']
fig_year_finished = px.bar(books_per_year, x='Year Finished', y='Count', title='Books Finished per Year')
st.plotly_chart(fig_year_finished)
上述代码块将生成以下图表:

图 10.3 – 完成年份条形图
这里我们实际上做了一个假设,即我们假设 Date Read 列中的年份表示我们阅读该书的时间。但如果我们在 12 月中旬开始读一本书,并在 1 月 2 日完成呢?或者,如果我们在 2019 年开始一本书,但只读了几页,然后在 2021 年才继续读呢?我们知道这并不是每年读书数量的完美近似,但将其表达为每年完成的书籍数量会更好。
我开始读一本书后,需要多长时间才能读完?
我们下一个问题是关于完成一本书所需的时间,一旦我们开始阅读它。为了回答这个问题,我们需要计算两列之间的差异:Date Read列和Date Added列。同样,这将是一个近似值,因为我们没有用户开始阅读书籍的日期,只有他们将书籍添加到 Goodreads 的日期。基于此,接下来的步骤包括以下内容:
-
将这两列转换为 datetime 格式。
-
计算两列之间的天数差异。
-
将这个差异绘制为直方图。
以下代码块从转换开始,和之前一样,然后执行我们的任务列表:
books_df['days_to_finish'] = (pd.to_datetime(
books_df['Date Read']) - pd.to_datetime(books_df['Date Added'])).dt.days
fig_days_finished = px.histogram(books_df, x='days_to_finish')
st.plotly_chart(fig_days_finished)
之前的代码块可以添加到当前 Streamlit 应用的底部,运行时应该会显示一个新的图表:

图 10.4 – 完成所需天数图
这对于我的数据来说不是最有用的图表。看起来,在某个时刻,我将曾经读过的书籍添加到了 Goodreads,这些书籍出现在了这个图表中。我们还有一组未完成的书籍,或者是在待阅读书架上的书籍,它们在这个数据集中是空值。我们可以做一些事情,比如过滤数据集,只保留天数为正的书籍,并且只保留已完成的书籍,以下代码块实现了这一点:
books_df['days_to_finish'] = (pd.to_datetime(
books_df['Date Read']) - pd.to_datetime(books_df['Date Added'])).dt.days
books_finished_filtered = books_df[(books_df['Exclusive Shelf'] == 'read') & (books_df['days_to_finish'] >= 0)]
fig_days_finished = px.histogram(books_finished_filtered,
x='days_to_finish', title='Time Between Date Added And Date Finished',
labels={'days_to_finish':'days'})
st.plotly_chart(fig_days_finished)
我们代码中的这个改动使得图表有了显著改进。它做了一些假设,但也提供了更准确的分析。完成后的图表可以在以下截图中查看:

图 10.5 – 改进后的完成所需天数图
这看起来好多了!现在,让我们继续下一个问题。
我读过的书籍有多长?
这个问题的数据已经处于相当好的状态。我们有一列名为Number of Pages,顾名思义,它包含了每本书的页数。我们只需要将这一列传递给另一个直方图,之后我们就可以开始了:
fig_num_pages = px.histogram(books_df, x='Number of Pages', title='Book Length Histogram')
st.plotly_chart(fig_num_pages)
这段代码将生成类似于以下截图的内容,显示书籍页数的直方图:

图 10.6 – 页数直方图
这对我来说有意义;大多数书籍的页数在 300 到 400 页之间,也有一些超大书籍,页数超过 1000 页。接下来,让我们继续看这些书籍的年龄!
我读过的书籍有多旧?
我们下一个图表应该是直观的。我们如何找出我们读过的书籍的年龄?我们倾向于选择最新出版的书籍,还是更喜欢阅读经典作品?我们可以从两个列中获取这个信息:出版年份和原出版年份。这个数据集的文档非常少,但我认为我们可以安全地假设原出版年份是我们要找的内容,而出版年份则是出版社重新出版书籍时使用的年份。以下代码块通过打印出所有原出版年份晚于出版年份的书籍来验证这一假设:
st.write('Assumption check')
st.write(len(books_df[books_df['Original Publication Year'] > books_df['Year Published']]))
当我们运行这个时,应用应该返回没有原出版年份晚于出版年份的书籍。现在我们已经验证了这一假设,接下来我们可以做以下操作:
-
按原出版年份对书籍进行分组。
-
在条形图上绘制此图。
以下代码块分为两个步骤:
books_publication_year = books_df.groupby('Original Publication Year')['Book Id'].count().reset_index()
books_publication_year.columns = ['Year Published', 'Count']
fig_year_published = px.bar(books_publication_year, x='Year Published', y='Count', title='Book Age Plot')
st.plotly_chart(fig_year_published)
当我们运行这个应用时,应该会得到以下图表:

图 10.7 – 书籍年龄图
乍一看,这个图表似乎并不是特别有用,因为有很多书籍的写作年代非常久远(例如,柏拉图的著作约公元前 375 年),整个图表很难阅读。然而,Plotly 默认是交互式的,它允许我们缩放到我们更关心的历史时期。例如,以下截图展示了当我们将范围缩放到 1850 年至今时发生的情况,这段时间内大多数我读过的书籍恰好都在此范围内:

图 10.8 – 缩放出版年份
这是一个更好的图表!接下来有几种选择。我们可以从一个不太有用的图表开始,并告诉用户放大,或者我们可以筛选数据集只显示较新的书籍(这会违背图表的主要目的),或者我们可以为图表设置默认的缩放状态,并在底部提醒用户可以根据需要进行缩放。我认为第三种选择是最好的。以下代码实现了这个选项:
Books_publication_year = books_df.groupby('Original Publication Year')['Book Id'].count().reset_index()
books_publication_year.columns = ['Year Published', 'Count']
st.write(books_df.sort_values(by='Original Publication Year').head())
fig_year_published = px.bar(books_publication_year, x='Year Published', y='Count', title='Book Age Plot')
fig_year_published.update_xaxes(range=[1850, 2021])
st.plotly_chart(fig_year_published)
st.write('This chart is zoomed into the period of 1850-2021, but is interactive so try zooming in/out on interesting periods!')
当我们运行这段代码时,应该会得到最终的图表:

图 10.9 – 带有提示文本的默认缩放
再答四个问题,剩下一个!
我如何与其他 Goodreads 用户比较我对书籍的评分?
对于这个最后的问题,我们实际上需要两个独立的图表。首先,我们需要绘制我们如何对书籍进行评分。其次,我们需要绘制其他用户对我们也评分过的书籍的评分情况。这并不是一个完美的分析,因为 Goodreads 只显示书籍的平均评分——我们并没有查看评分分布。例如,如果我们读过《雪球》这本沃伦·巴菲特的传记,并且给了它 3 星评分,而 Goodreads 上一半的读者评分为 1 星,另一半评分为 5 星,我们的评分就会和平均评分完全一样,但和任何个别读者的评分都不相同!不过,我们只能尽力利用我们拥有的数据。因此,我们可以做以下事情:
-
根据我们已评分(即已阅读)的书籍来筛选图书。
-
为我们的第一个图表创建每本书的平均评分直方图。
-
创建一个关于你自己评分的直方图。
以下代码块正是实现了这一功能:
books_rated = books_df[books_df['My Rating'] != 0]
fig_my_rating = px.histogram(books_rated, x='My Rating', title='User Rating')
st.plotly_chart(fig_my_rating)
fig_avg_rating = px.histogram(books_rated, x='Average Rating', title='Average Goodreads Rating')
st.plotly_chart(fig_avg_rating)
如下图所示,第一个图表展示了用户评分分布,看起来很不错。图表显示我主要给书籍评分为 4 星或 5 星,这总体来说是相当宽松的评分:

图 10.10 – 用户评分分布
当我们查看第二个图表时,我们会看到一个相当干净的分布。然而,我们遇到了之前已经提到的问题——所有评分的平均值比用户评分更加紧密地集中在一起:

图 10.11 – Goodreads 平均评分
我们始终可以将两个图表的 x 轴范围设置为 1–5,但这并不能解决我们实际的问题。相反,我们可以保留两个图表,同时计算我们是否普遍给书籍打高于或低于 Goodreads 平均评分的分数。以下代码块将计算这一点,并将其添加到平均 Goodreads 评分图表下方:
Fig_avg_rating = px.histogram(books_rated, x='Average Rating', title='Average Goodreads Rating')
st.plotly_chart(fig_avg_rating)
import numpy as np
avg_difference = np.round(np.mean(books_rated['My Rating'] – books_rated['Average Rating']), 2)
if avg_difference >= 0:
sign = 'higher'
else:
sign = 'lower'
st.write(f"You rate books {sign} than the average Goodreads user by {abs(avg_difference)}!")
这个代码块计算我们的平均值,并生成一个动态字符串,显示 Goodreads 用户的评分是高于还是低于 Goodreads 平均用户评分。我数据的结果如下:

图 10.12 – 添加平均差异
这更好,并且完成了我们的最小可行产品(MVP)。我们的应用程序已经处于一个不错的状态,复杂的数据处理和可视化步骤几乎完成了。然而,我们的应用程序看起来并不好,只是一些排成一行的图表。这对于 MVP 来说可能不错,但我们需要添加一些样式,真正提升我们的状态。这引出了我们的下一部分内容:对这个想法进行迭代,使其变得更好。
迭代改进
到目前为止,我们的应用几乎处于生产模式。迭代改进就是对我们已完成的工作进行编辑,并以一种使应用更加易用、更加美观的方式进行组织。我们可以尝试做出以下一些改进:
-
通过动画进行美化
-
使用列和宽度进行组织
-
通过文本和附加统计信息构建叙述
让我们通过动画来让我们的应用更美观吧!
通过动画进行美化
在 第七章,探索 Streamlit 组件 中,我们探讨了如何使用各种 Streamlit 组件:其中之一是 streamlit-lottie 组件,它使我们能够为 Streamlit 应用添加动画。我们可以通过以下代码向当前 Streamlit 应用的顶部添加动画,从而改善我们的应用。如果你想了解更多关于 Streamlit 组件的内容,请返回 第七章**,探索 Streamlit 组件:
import streamlit as st
import pandas as pd
import plotly.express as px
import numpy as np
from streamlit_lottie import st_lottie
import requests
def load_lottieurl(url: str):
r = requests.get(url)
if r.status_code != 200:
return None
return r.json()
file_url = 'https://assets4.lottiefiles.com/temp/lf20_aKAfIn.json'
lottie_book = load_lottieurl(file_url)
st_lottie(lottie_book, speed=1, height=200, key="initial")
这个 Lottie 文件是一本书翻动页面的动画,正如以下截图所示。这些动画总是为较长的 Streamlit 应用增添了一些亮点:

图 12.13 – Goodreads 动画
现在我们已经添加了动画,接下来可以继续改进我们应用的组织方式。
使用列和宽度进行组织
正如我们之前讨论的那样,我们的应用看起来并不十分美观,因为每个图表都一个接一个地显示。我们可以做出的另一个改进是让我们的应用设置为宽格式,而不是窄格式,然后将应用中的图表并排放在每一列中。
首先,在我们的应用顶部,我们需要设置第一个 Streamlit 调用,将 Streamlit 应用的配置设置为宽格式,而不是窄格式,代码如下所示:
import requests
st.set_page_config(layout="wide")
def load_lottieurl(url: str):
r = requests.get(url)
if r.status_code != 200:
return None
return r.json()
这将把我们的 Streamlit 设置为宽格式。到目前为止,在我们的应用中,我们已经为每个图表指定了一个独特的名称(如fig_year_finished),以便简化下一步操作。我们现在可以删除所有的st.plotly_chart()调用,并创建一组两列三行的布局,在这里我们可以放置我们的六个图表。以下代码创建了每个图表。我们首先命名每个位置,然后将其中一个图表填充进去:
row1_col1, row1_col2 = st.beta_columns(2)
row2_col1, row2_col2 = st.beta_columns(2)
row3_col1, row3_col2 = st.beta_columns(2)
with row1_col1:
st.plotly_chart(fig_year_finished)
with row1_col2:
st.plotly_chart(fig_days_finished)
with row2_col1:
st.plotly_chart(fig_num_pages)
with row2_col2:
st.plotly_chart(fig_year_published)
st.write('This chart is zoomed into the period of 1850-2021, but is interactive so try zooming in/out on interesting periods!')
with row3_col1:
st.plotly_chart(fig_my_rating)
with row3_col2:
st.plotly_chart(fig_avg_rating)
st.write(f"You rate books {sign} than the average Goodreads user by {abs(avg_difference)}!")
这段代码将创建如下截图所示的应用,截图已被裁剪为仅显示前两个图表以便简洁:

图 12.14 – 宽格式示例
这样可以使我们的图表更易于阅读,并且能够轻松地进行比较。我们故意按照评分将两个图表配对,其他图表也看起来很适合并排显示。我们的最后一步是增加一些文字,使整个应用更加易读。
通过文本和附加统计信息构建叙述
这些图表对于理解用户如何阅读已经非常有帮助,但我们可以通过在每个图表下方和应用程序开头添加一些有用的统计信息和文本来增强应用的可读性。
在我们开始定义列的地方,我们可以添加一个初步部分,显示我们读过的独特书籍数量、独特作者数量以及我们最喜欢的作者,一目了然。我们可以利用这些基本统计数据来启动应用程序,并告诉用户每个图表也是互动的:
if goodreads_file is None:
st.subheader("Tyler's Analysis Results:")
else:
st.subheader('Your Analysis Results:')
books_finished = books_df[books_df['Exclusive Shelf'] == 'read']
u_books = len(books_finished['Book Id'].unique())
u_authors = len(books_finished['Author'].unique())
mode_author = books_finished['Author'].mode()[0]
st.write(f'It looks like you have finished {u_books} books with a total of {u_authors} unique authors. Your most read author is {mode_author}!')
st.write(f'Your app results can be found below, we have analyzed everything from your book length distribution to how you rate books. Take a look around, all the graphs are interactive!')
row1_col1, row1_col2 = st.beta_columns(2)
现在,我们需要在四个尚未添加任何注释文本的图表下方添加四个新的文本部分。对于前三个图表,以下代码会为每个图表添加一些统计数据和文本:
row1_col1, row1_col2 = st.beta_columns(2)
row2_col1, row2_col2 = st.beta_columns(2)
row3_col1, row3_col2 = st.beta_columns(2)
with row1_col1:
mode_year_finished = int(books_df['Year Finished'].mode()[0])
st.plotly_chart(fig_year_finished)
st.write(f'You finished the most books in {mode_year_finished}. Awesome job!')
with row1_col2:
st.plotly_chart(fig_days_finished)
mean_days_to_finish = int(books_finished_filtered['days_to_finish'].mean())
st.write(f'It took you an average of {mean_days_to_finish} days between when the book was added to Goodreads and when you finished the book. This is not a perfect metric, as you may have added this book to a to-read list!')
with row2_col1:
st.plotly_chart(fig_num_pages)
avg_pages = int(books_df['Number of Pages'].mean())
st.write(f'Your books are an average of {avg_pages} pages long, check out the distribution above!')
这里的一个示例图表是关于书籍长度的直方图。前面的代码添加了一个平均长度以及一些图表下方的文本,如下图所示:

图 10.15 – 平均页数文本
对于最后一组图表,我们可以为没有上下文的图表添加文本:
with row2_col2:
st.plotly_chart(fig_year_published)
st.write('This chart is zoomed into the period of 1850-2021, but is interactive so try zooming in/out on interesting periods!')
with row3_col1:
st.plotly_chart(fig_my_rating)
avg_my_rating = round(books_rated['My Rating'].mean(), 2)
st.write(f'You rate books an average of {avg_my_rating} stars on Goodreads.')
with row3_col2:
st.plotly_chart(fig_avg_rating)
st.write(f"You rate books {sign} than the average Goodreads user by {abs(avg_difference)}!")
这完成了我们关于添加文本和附加统计信息的部分!现在,我们的最后一步是将其部署到 Streamlit Sharing 上。
托管和推广
我们的最后一步是将这个应用程序托管到 Streamlit Sharing 上。为了做到这一点,我们需要执行以下步骤:
-
为此工作创建一个 GitHub 仓库。
-
添加一个
requirements.txt文件。 -
使用 Streamlit Sharing 上的一键部署进行部署。
我们在第五章中已经详细介绍了这一点,通过 Streamlit Sharing 部署 Streamlit,所以现在就按照没有说明的方式尝试一下吧。如果遇到问题,可以返回到第五章,通过 Streamlit Sharing 部署 Streamlit,查找具体的指导。
总结
多么有趣的一章!我们在这里学到了很多东西——从如何提出自己的数据科学项目到如何创建初始 MVP,再到如何迭代改进我们的应用程序。我们通过 Goodreads 数据集的视角完成了这一切,并将这个应用程序从一个想法发展到一个完全功能的应用程序,并托管在 Streamlit Sharing 上。我期待看到你们创造的各种 Streamlit 应用。请创造一些有趣的内容,并在 Twitter 上 @tylerjrichards 发给我。下一章,我们将专注于学习如何在工作中使用 Streamlit,使用 Streamlit 新产品 Streamlit for Teams。到时候见!
第十一章:第十一章:为团队使用 Streamlit
在过去的两章中,我们深入探讨了如何使用 Streamlit 来进行个人数据项目、简历构建项目,甚至是创建适用于家庭作业求职申请的应用。在本章中,我们将重点讨论如何在工作中使用 Streamlit,作为数据科学家、机器学习工程师或数据分析师。我们已经知道,Streamlit 可以作为一个有说服力的工具,通过深思熟虑的互动分析来影响周围的人,我们将把这一点应用到数据科学家实际进行的工作中。
Streamlit 既是一家公司,又是一个开源库,通过成为数据科学工具包中一个如此优秀的工具来赚取收入,从而使公司愿意为增加数据科学家生产力的特别功能和定制化付费。最棒的一点是,公司的收益直接与用户体验相关,Streamlit 被激励让使用工具的体验尽可能有用且有价值;如果你因为 Streamlit 而成为更好的数据科学家,那么你的公司就更有可能为访问这些功能支付更多费用。
此外,Streamlit 已经设计为协作型工具。如果其他开发人员能够访问正在用于 Streamlit 应用的 GitHub 仓库,那么他们所做的任何修改都会自动出现在已部署的应用中。因此,Streamlit 天生适合个人数据科学家和从事类似分析或应用的团队协作。
在本章中,我们将讨论以下内容:
-
使用 Streamlit for Teams 分析假设性的调查成本
-
从私有仓库创建和部署应用
-
使用 Streamlit 进行用户身份验证
本章的开始,我们将通过一个例子来说明在工作中何时使用 Streamlit for Teams,而不是静态分析。
使用 Streamlit for Teams 分析假设性的调查成本
假设你是大型互联网公司(BIC)的一名数据科学家。BIC 向用户销售预算软件,你负责调查应用的用户,看看软件在哪些方面可以改进。你与一个相当典型的团队合作,团队成员包括一位产品经理、两位软件工程师、三位项目经理、两位用户体验研究员,以及你自己,唯一的数据科学家。一天,你的产品经理在 Slack 上给你发消息,要求你找出年龄在 16 到 24 岁之间的用户群体,这是业务中的一个关键 segment,让他们参加一个包含 10 个问题的调查。在一次头脑风暴会议上,你的研究员发现给用户一个 10% 获得 $500 礼品卡的机会,比给用户 $50 更能提高调查的回复率,并希望你将此纳入分析中。
这里有很多因素需要考虑:团队愿意花费多少?我们应该选择多少样本?最难的部分是样本的成本与代表性之间的取舍。我们有几个选项。我们可以推荐一个样本大小,但没有真正告诉团队为什么推荐这个大小。我们也可以给团队提供几个成本选项,列出一些优缺点。比这更好的选择是创建一个简短的 Streamlit 应用程序,帮助我们作为团队做出决定,从而了解所有的取舍。
假设我们想要做后者!我们接下来的步骤是:
-
设置新的 Streamlit 应用程序文件夹
-
说明样本的代表性
-
计算样本的成本
-
使用交互来展示取舍
现在我们需要从第一个选项开始,设置我们的文件夹。
设置新的 Streamlit 应用程序文件夹
我们在本书中之前已经多次进行过这一步。请创建一个名为random_survey_app的文件夹,并在其中放入一个名为app.py的文件,我们将在这个文件中编写新的 Streamlit 应用程序。现在进入核心问题!
说明样本的代表性
如果我们是 BIC 的 数据科学家,我们已经了解了很多关于目标用户群的数据。我们希望确保我们抓取的样本能够代表整个用户群,尤其是与业务的一个或多个关键变量相关的部分。对于这个例子,我们可以假设业务最重要的指标是用户在我们应用程序上花费的时间。我们已经知道了用户时间消耗的分布,可以使用以下代码在应用程序中表示这一点:
import streamlit as st
import numpy as np
st.title('Deciding Survey Sample Size')
np.random.seed(1)
user_time_spent = np.random.normal(50.5, 10, 1000)
我们使用np.random.seed(1)这一行代码,是为了让你看到与本书中图示相同的样本,但在开发面向用户的应用程序时,我建议去掉这一行,否则聪明的用户可能会对你的随机选择方法产生怀疑!在numpy中设置种子能够确保随机数据选择的结果可重复。
现在我们已经知道了用户在应用程序上花费的时间的分布,我们可以通过并排展示用户时间分布的两个样本,向用户展示不同规模的子样本代表性如何。虽然这并不是判断代表性的最佳方法,但它足以向你的观众证明一个大致的观点。接下来的代码块从 1,000 个样本中随机抽取 100 个,并绘制每个样本的直方图:
import streamlit as st
import numpy as np
import plotly.express as px
st.title('Deciding Survey Sample Size')
np.random.seed(1)
user_time_spent = np.random.normal(50.5, 10, 1000)
my_sample = np.random.choice(user_time_spent, 100)
fig = px.histogram(user_time_spent, title='Total Time Spent')
fig.update_traces(xbins=dict(start=0,end=100, size=5))
st.plotly_chart(fig)
fig = px.histogram(my_sample, title='Sample Time Spent')
fig.update_traces(xbins=dict(start=0,end=100, size=5))
st.plotly_chart(fig)
这个代码块将生成如下应用程序,显示在 100 个用户的情况下,它看起来相当具有代表性,能够反映总的时间消耗:

图 11.1 – 调查样本大小
现在我们需要弄清楚权衡点——如何确定一组用户样本的成本。
计算样本的成本
如我们之前讨论的那样,我们可以通过查看调查响应者的数量乘以每个响应者的成本来计算样本的成本。每个调查响应者的成本是 10% 的概率获得 $500 礼品卡,因此我们应该将期望值显示为 10% 乘以 $500,平均为 $50。我们还应能够动态地回答实验的成本有多少百分比的时间低于某个金额?,以便向团队说明与随机性相关的成本风险。
我们可以使用以下代码计算并输出预期成本:
np.random.seed(1)
num_surveys = 100
user_time_spent = np.random.normal(50.5, 10, 1000)
my_sample = np.random.choice(user_time_spent, num_surveys)
expected_cost = 50 * num_surveys
st.write(f'The expected cost of this sample is {expected_cost}')
一旦我们有了这个,我们可以模拟这个调查运行 10,000 次,统计实验的成本超过某个值的次数,这个值在下一个代码块中称为 max_cost。我们再次使用 numpy 库从二项分布中进行采样,这是给定一组独立事件和相同概率时的成功次数(例如,如果你投掷 10 次硬币,结果正面朝上的次数):
np.random.seed(1)
num_surveys = 100
user_time_spent = np.random.normal(50.5, 10, 1000)
my_sample = np.random.choice(user_time_spent, num_surveys)
#costing section
expected_cost = 50 * num_surveys
max_amount = 5000
percent_change_over = 100 * sum(np.random.binomial(num_surveys, 0.1, 10000) > max_amount/500)/10000
st.write(f'The expected cost of this sample is {expected_cost}')
st.write(f'The percent chance the cost goes over {max_amount} is {percent_change_over}')
对于我们的调查样本量 100 和最大成本 $5,000,期望成本为 $5,000,并且成本大约 41% 的时间会超过我们的限制:

图 11.2 – 调查的预期成本
现在我们已经有了所有的基本组件,接下来需要通过 Streamlit 的功能让这个应用变得交互式。
使用交互显示权衡
为了让这个应用比静态分析更好,我们需要让用户与我们的应用互动。我们可以通过两种方式来实现:首先允许用户更改调查人数,其次是更改我们指定的最大成本变量。我们可以使用以下代码块来实现这两项功能:
st.title('Deciding Survey Sample Size')
np.random.seed(1)
num_surveys = 100
num_surveys = st.slider(label='Number of Surveys Sent',
min_value=5, max_value=150, value=50)
max_amount = st.number_input(label='What is the max you want to spend?',
value=num_surveys*50, step=500)
user_time_spent = np.random.normal(50.5, 10, 1000)
my_sample = np.random.choice(user_time_spent, num_surveys)
在这个代码块中,我们为 Streamlit 滑块设置了最小值和最大值,并且将最大值的默认值设置为期望值,这样用户在更改调查人数时会更容易。我们还应该在此上方添加一些文本,指导用户如何与我们的应用互动,具体如下所示:
st.title('Deciding Survey Sample Size')
'''
Please use the following app to see how
representative and expensive a set sample
is for our survey design.
'''
np.random.seed(1)
num_surveys = 100
这两个功能的添加产生了以下截图:

图 11.3 – 带成本计算的 Streamlit 滑块
到目前为止,这一切运行得非常顺利。然而,由于你在公司工作,你希望确保应用中的这些信息不会被公开或泄露给竞争对手。因此,公开部署这个应用不可行,我们需要找出如何私下部署这个应用。在第九章,使用 Streamlit 改进工作申请中,我们讨论了如何通过密码保护让应用程序保持私密,但 Streamlit for Teams 还允许我们从私有 GitHub 仓库进行部署,这也是我们下一节的内容。
从私有仓库创建和部署应用程序
Streamlit for Teams 产品的一个强大功能是可以通过私有 GitHub 仓库使用 Streamlit Sharing。它的使用方式与我们在第五章《使用 Streamlit Sharing 部署 Streamlit》学到的一样,只不过是从私有仓库而非公共仓库进行部署。要进行这个更改,您需要拥有 Streamlit Teams 的访问权限,或者从 Streamlit 团队获取权限(如果你礼貌地请求,他们或许会允许你试用!)。
要创建一个私有 GitHub 仓库,前往github.com/new,确保选择私有而不是公共选项,如下图所示:

图 11.4 – GitHub 上的私有仓库
在将当前代码添加到我们的 GitHub 仓库后,我们可以像往常一样通过访问share.streamlit.io并按照一键部署的指引进行部署。我已经使用自己的私有仓库部署了这个 Streamlit 应用,可以通过share.streamlit.io/tylerjrichards/random_survey_app/main/app.py找到这个 Streamlit 应用。接下来的问题是寻找替代我们已经探索过的密码方法的方案,尤其是与 Streamlit 的用户特定认证。
用户认证与 Streamlit
Streamlit for Teams 目前在测试阶段的一个功能,预计将于 2021 年末发布,是基于 Google 的单点登录(SSO)认证。这将允许我们将应用设置为完全私密,仅对我们在允许列表中的用户可见。我们需要做的第一步是将我们的 Google 账户关联起来,方法是前往share.streamlit.io并点击右上角的设置。到达页面后,我们将看到接下来截图所示的界面:

图 11.5 – 关联 Google 账户
现在我们可以点击蓝色的连接按钮,然后登录我们的 Google 账户。完成后,我们需要前往share.streamlit.io的主页,找出我们希望限制流量的应用:

图 11.6 – Streamlit 共享选项
在这种情况下,我想限制第二个应用的访问,因此我将点击页面最右侧的图标进入该应用的设置,并选择设置,正如我们之前所做的那样。
在我们关联 Google 账户之前,我们只能编辑应用的Secrets部分,但现在我们有了一个全新的部分,叫做观看者,如下图所示:

图 11.7 – 观看者身份验证
我们可以通过勾选复选框将我们的应用设置为私有,并通过电子邮件将人们添加到此列表中。如果他们不在列表上,尝试访问应用时将收到 404 错误!这种方法对少量用户非常有效,但如果你试图覆盖数百或数千个用户,尤其是那些没有 Google 相关电子邮件账户的用户,这个方法就不太适用了。
总结
在这一章中,我们探讨了一个成本分析的例子,它展示了 Streamlit 在特定工作场景中的应用。在这个例子中,我们讨论了如何使用交互式的 Streamlit 应用程序来帮助团队改进并制定基于数据的决策。之后,我们还学习了如何从私有 GitHub 仓库部署 Streamlit 应用程序,并了解了几种方法,使我们的 Streamlit 应用程序仅对具有密码保护和 Google 单点登录的私密观众可用。本章到此结束。
在下一章中,我们将重点采访一些 Streamlit 的高级用户和创作者,了解他们的使用技巧和经验,为什么他们如此广泛地使用 Streamlit,以及他们认为这个库的未来发展方向。我们在那里见!
第十二章:第十二章:Streamlit 强力用户
欢迎来到本书的最后一章!在这一章中,我们将向最佳学习者学习,包括 Streamlit 的创始人和有丰富应用及组件开发经验的 Streamlit 用户,他们中的一些人已经转变为 Streamlit 员工,甚至还有现在管理支持 Streamlit 库开发的初创公司的创始人。我们与四位不同的用户坐下来进行了访谈,了解了他们的背景、他们使用 Streamlit 的经验,以及他们为不同经验水平的用户提供的建议。通过这些访谈,我们将了解他们如何在日常工作中、教学中使用 Streamlit,以及 Streamlit 接下来的发展方向。
本章分为四个访谈:
-
Fanilo Andrianasolo,Streamlit 创始人和 Worldline 技术负责人
-
Johannes Rieke,Streamlit 创始人转工程师
-
Adrien Treuille,Streamlit 创始人兼 CEO
-
Charly Wargnier,Streamlit 创始人兼 SEO 顾问
首先,让我们从 Fanilo 开始!
访谈 #1 – Fanilo Andrianasolo
(Tyler)嘿,Fanilo!在我们开始之前,你想向读者自我介绍一下吗?你有什么背景?你平时做什么工作,在哪工作?
(Fanilo)大家好!我叫 Fanilo Andrianasolo,来自马达加斯加,我在 Worldline 工作,Worldline 是欧洲领先的数字支付和交易服务公司之一。我在那里担任数据科学和商业智能倡导者及技术负责人,帮助内部产品和开发团队原型化新的数据科学应用场景、设计这些场景的架构,并将它们投入生产。所以,我的大部分工作专注于将数据分析集成到 Worldline 中,这是一项庞大的任务,因为它涉及多个行业,例如金融(如欺诈检测)、零售和客户分析。同时,我还是数据科学的倡导者,因此我会为内部员工或潜在客户提供多个讲座,向他们展示:“这就是数据科学,别担心,它并没有你想象的那么难”或“这就是随机森林,别担心,它没有你想象的那么复杂。”此外,我还在里昂大学教授大数据和 NoSQL,每年讲授大约 50 小时。这是一个极好的机会,可以与学生交流,并看到他们成长为出色的软件工程师和数据科学家!
(Tyler)听起来很棒!我觉得你基本上有两个非常有趣的角色,你需要在工作和大学中向他人解释数据科学概念。你在这两个场景中都使用 Streamlit 吗?
(Fanilo)是的,我用过!当我第一次发现 Streamlit 时,是为了内部工作。起初,我在追踪不同机器的指标,并希望展示它们,而不想使用 Jupyter Notebook,因为我不想每次都点击运行每一个单元格。我尝试了 Streamlit,发现它非常快速且易于使用,立刻就被它吸引了。
在我做大学讲座时,主要是讲 PySpark、MongoDB 和 Elasticsearch,这些都是通过它们的 Python 插件进行演示的。我会展示一个 Streamlit 应用,代码在旁边,我会在学生面前修改代码,所有的 Python 变量会实时在应用上更新。当你写这段代码来构建某个 MapReduce 任务时,比如在 PySpark 中,展示代码实时效果很容易,甚至可以和学生一起做编程练习。为了展示代码如何工作,我会在代码里放一个函数,结果会直接显示在 Streamlit 应用的右侧。这种方式非常动态,也让我能轻松地展示和讲解代码。
(Tyler)你为这两个团队开发 Streamlit 应用时,有什么不同吗?我觉得它们有很多相似之处,但具体有哪些区别呢?
(Fanilo)对于学生来说,这将是一个从上到下运行的单一脚本,方便他们跟着走;这对他们来说就像是一个实时演示。而在公司工作时,我将 Streamlit 集成到更大的项目中,因此通常会有一个已经建立的项目结构,并有一个专门用于 UI 的文件夹。对于工作项目,Streamlit 应用将会使用来自多个库和模块的函数,所以会有所不同。我们在工作中分发和使用 Streamlit 应用的方式也稍有不同,因为在大学里,我可以随意在我的机器上做任何事情。而在工作中,我必须考虑更多问题,比如能否将某些内容放入缓存中?能否重写同事的函数,因为它不能放入 Streamlit 哈希?我在职业工作中的一些限制让写 Streamlit 应用变得更复杂。
(Tyler)我很好奇你在工作中是如何进行部署的。你是在 Heroku 服务器上使用 Streamlit for Teams 吗?还是全部在本地进行?
(Fanilo)我们公司内部有两台本地服务器,我将 Streamlit 应用部署在这些服务器上。公司内部使用 Streamlit 的人不多,所以我可以暂时把应用托管在服务器上,让市场部门可以试用。而那些不懂 Python 的技术人员,我就直接把脚本发给他们,并告诉他们如何使用 Anaconda 运行。
(Tyler)你提到过第一次使用 Streamlit 的经历。你是通过 Twitter 发现它的吗?还是在某个论坛上看到的?是什么让你开始使用它的?最初的使用场景是什么?
(Fanilo)我想我是在 Twitter 上听说的它。一开始我并没有在意,想,“哦,这只是另一个 Python 库,我把它放到待办事项清单里,哪天试试就好。”然后我在两个月后尝试了它,当时我正在为一个潜在客户做一个实时数据科学演示。他们想追踪来自不同网站的访问指标并进行可视化。事实上,我们的大部分客户只是要求我们做一些数据分析。他们还没有想到 KPIs、分析业务用例,或者通过数据回答的商业问题。这些总是很难开始,我通常会为他们构建一个小的互动演示,帮助他们理解数据科学的过程。正是在那个时候,我想,“哦,我应该试试 Streamlit。”
在这之前,我实际上用 Jupyter Notebook 为另一个客户做过演示,但客户并不感兴趣,因为他们觉得这太技术化了;他们不喜欢看到代码。所以,我想对于这个客户,我或许应该试试 Streamlit,展示一组图表,使用滑块来决定你想要可视化多少数据。对于这个问题,我们想对一组日期做窗口处理,以回答这些网站的平均访问量是多少,并且希望能够决定窗口的大小。我们可以将它放在 Streamlit 的侧边栏中,这是一个很好的机会,能够实时展示结果给客户看。所以我们在 Streamlit 应用中加入了这个滑块并交给了客户,你可以看到,当出现一个峰值时,可以进一步分析。是的,这就是我通过内部和外部演示数据科学的过程中,更多地了解 Streamlit 的方式。
(Tyler)这种情况经常发生,客户在提出请求时并不完全知道自己想要什么。真是一个很棒的使用案例!我想跟你聊一聊 Streamlit 社区,过去一年你已经成为其中一个相当强大的成员了。Streamlit 的哪些方面让你继续投资这个生态系统呢?
(Fanilo)对我来说,吸引我进入 Streamlit 的东西,实际上就是我在做前端工程时错过的反馈循环。我可以写一些代码,并且立刻在屏幕上看到结果。我可以直接在 CSS 中编辑颜色,并看到变化。而且在 Streamlit 中,这个反馈循环非常短,加上 API 的简洁性,使得构建一个基础应用变得非常容易,这才是让我真正被这个框架吸引的原因。我还和 Adrian(Streamlit 的创始人)有过一次对话,我告诉他,这个反馈循环让我想起了网页生态系统,对我来说,这就是 Streamlit 的“秘密酱料”。
另一件事是现在我们可以很容易地将 web 组件集成到 Streamlit 中,这使得我们能够为数据科学家构建更多样化的交互式 web 应用程序变得非常简单。我总是从与我的学生、其他同事或者在聚会上与人交流中得到这样的印象,他们总是在努力构建交互式应用程序,可以在其中选择或绘制某些内容,并将这些绘制内容作为他们的机器学习模型的输入。为了做到这一点,他们需要预先绘制,然后将图像加载到 Jupyter 笔记本中,这太费时了,但有 HTML 组件可以进行绘制。Streamlit 作为 Python 与 web 之间的桥梁通过组件将我们作为创建者拉入到 Streamlit 中。
(Tyler) 所以说到基于 web 的平台,你花了很多时间构建像 streamlit-lottie (github.com/andfanilo/streamlit-lottie),我们之前在书中提到的 streamlit-drawable-canvas (github.com/andfanilo/streamlit-drawable-canvas),甚至 streamlit-echarts (github.com/andfanilo/streamlit-echarts) 这样的组件。你能谈谈你是如何想出这些组件的想法的吗?
(Fanilo) 所以对于这三个组件,我有两个不同的故事。我要从 streamlit-lottie 开始;这个想法是在一个无聊的周六晚上,我在 Twitter 上滚动时突然冒出来的。我看到使用 Lottie 文件的漂亮动画,心想,“哦,很不错,我想在 Streamlit 中也能有这样酷炫的动画”,于是我就开始构建它。就是这样,我想把更多的 web 组件引入到 Streamlit 中。
对于 streamlit-echarts 和 streamlit-drawable-canvas,有一个更大的用例。几年前,我因为一个使用 TensorFlow.js 的演示项目紧急的截止日期而处于压力之下。我想要在绘图上进行训练和推断,花了 5 天时间在 JavaScript 生态系统中创建这个演示项目,混合使用 Fabric.js 在画布上绘图和 Echarts 显示结果。这非常困难,特别是因为这是我第一次真正涉足前端工程领域。后来 Streamlit 推出时,我想看看其他用户是否能在几小时内构建这个演示项目,如果 Fabric.js 和 Echarts 组件已经存在的话,所以我把这个演示项目的代码提取出来做成了外部库,就这样!
我的思考过程总是围绕着,我们可以为 Streamlit 带来什么样的新输入和输出?比如,我们能否把实时音乐输入带到 Streamlit?现在深度学习领域大家都在谈论图像和声音,所以这将是一个很棒的组件。而作为输出,现如今有很多流行的 JavaScript 库可以以互动的方式展示数据,这对分析非常有帮助。我的目标始终是,能为 Streamlit 添加什么样的输入和输出,以利用 web 的能力?希望 web 开发者会想要在 Streamlit 中构建更多的东西!
(Tyler) 你可能看过很多使用你组件的 Streamlit 应用。我想你一定会对一些巧妙使用 streamlit-lottie 或 streamlit-echarts 的例子感到非常兴奋。看到这种东西时你是不是会特别激动?有没有一些你特别喜欢的应用,它们是基于你制作的一些组件搭建的?
(Fanilo) 这些是我真正投入精力去构建和推广的开源项目。我喜欢这些工具的灵活性,它们可以用于许多我从未想过的使用场景,特别是对于 streamlit-drawable-canvas。我看到有一个用户在 Streamlit 中展示了一个足球场的侧视图,然后他在画布上绘制了场地的边界,接着进行了一些 3D 重映射,把它转化为顶视图,以便分析球员的位置。我当时想,哇,谁会这么做呢?
有些人正在将 Streamlit 用于医疗目的。例如,他们想在一堆图像的每一张切片上绘制一些分子的位置,从而绘制出这种分子的 3D 版本。我甚至看到过 Jina AI 的一个应用,他们构建了一个 Pokémon 识别器(github.com/jina-ai/integration-streamlit-pokemon)!所以你可以画一个 Pokémon,它会将你的绘图输入到神经网络中,识别你想画的 Pokémon。你知道吗,我从未预测到这种应用,真是太神奇了。
(Tyler) 听起来真棒。对于刚接触 Streamlit 的初学者,你有什么建议吗?你有没有什么是你当初刚开始时希望知道的?
(Fanilo)对我来说,就是不断地尝试新的库。如果有一个新的 Python 库,我常常想要了解它是如何工作的。如果我改变这个参数,会发生什么?每个参数是如何影响输出的?所以,我只需启动一个新的 Streamlit 脚本并导入这个库。然后,对于你所拥有的每个参数,你可以构建一个滑块、一个数字输入框、一个日期输入框,然后开始构建这个未知库的演示,而 Streamlit 只是帮助你尽可能深入地探索它。接着,你会开始思考如何更好地布局,如何为应用保留一些状态,而这正是你开始深入了解 Streamlit 的地方。我们曾讨论过 Streamlit 组件的 Web 生态系统。但我也认为 Streamlit 是展示任何 Python 库的最佳方式之一,涵盖了 Python 生态系统中的一切。
除此之外,我还会建议大家在线参与社区互动。Streamlit 社区实际上是我第一次与一个完全陌生的在线社区互动,我既不认识这些人,也没有见过他们。我不记得自己写的第一个帖子或话题是什么,但我记得我投入了大量的精力去写它。我知道在公开场合提问需要勇气,所以我会说:敢于在论坛上发一个新帖子。如果你在使用 Streamlit 或 Python 时遇到困难,尝试在社区论坛上发布一些问题是一次不错的体验(discuss.streamlit.io/),因为那里的每个人都非常友好,总是有人愿意帮忙。我鼓励大家多玩 Streamlit 并进行创新,然后到论坛上提问那些无法解决的问题。论坛里有很多非常隐藏的功能,只有在论坛里才能找到,所以我鼓励大家去论坛看看。
(Tyler)是的,绝对如此。这是一个非常有趣的例子,展示了互联网中积累的知识如何以小块的形式存在。提出问题是让人害怕的,因为你总会想,“如果他们觉得我真的很傻怎么办?”这是经常出现的首要担忧:给别人传递出你知道自己在做什么的信号。但 Streamlit 社区的人似乎都非常友好,说实话,这在互联网上是一个巨大的变化。
(Fanilo) 是的,也许我已经习惯了和学生互动,这帮助我理解了向“陌生人”提问的恐惧。发生在我学生身上的最糟糕的事情是,他们想要克隆一个 Git 仓库,日志显示该文件夹已经存在于他们的机器上,因此无法克隆。答案很简单,直接写在我们的眼前的日志上,至少我们容易理解,对吧?但是,当你第一次写代码时,你根本不知道这个日志意味着什么,或者你甚至应该检查日志。所以,我尽量像回答第一次写代码的学生那样回答问题,并以此作为我们社区的标准,让每个人都意识到我们曾经都是初学者,我们应该引导他们进入这个庞大的 Python/Streamlit 世界,而不是回答“去读手册”。
一年前,我完全不接触在线社区。我那时候也很害怕做这件事。现在,我是一个论坛版主和 Streamlit 的创作者。一年前,我根本没想过会做这件事。所以,大家尽管提问吧!
(Tyler) 非常感谢你,Fanilo!对于任何想了解更多的人,可以在 github.com/andfanilo 找到 Fanilo 的 GitHub,另外他的 Streamlit 组件教程也可以在 streamlit-components-tutorial.netlify.app/ 查看。
采访 #2 – Johannes Rieke
(Tyler) 嗨,Johannes!在我们开始之前,你能给我们简单介绍一下自己吗?你过去在哪里工作过,你做什么工作,有什么背景?
(Johannes) 你好!我来自德国,现在住在柏林。嗯,正如你所知道的,我现在在 Streamlit 工作,已经工作了两个月,但我的背景实际上是在物理学方面。所以我本科时学的是物理,后来不知怎么的我进入了神经科学领域。我上了几门课,做了一些项目,真的很喜欢,尤其是与计算机科学结合,做神经细胞、脑部模拟等等这一类的事情。我对此产生了浓厚的兴趣。于是我决定攻读计算神经科学的硕士学位,这个学科一方面涉及神经科学,另一方面也涉及机器学习。在那个项目中,我做了很多与机器学习相关的工作,涉及各个领域,比如医学影像、自然语言处理、图神经网络等各种东西。硕士毕业后,我参与了一些开源项目。其实,我本来打算去旅行一段时间,但因为 Covid 的原因,我比预期更早回到了德国。然后我开始参与开源项目,做了很多与 Streamlit 相关的工作,现在我就在 Streamlit 担任产品工程师。
(Tyler) 非常有趣!你是什么时候开始接触 Python 生态系统的?那是你学习物理的时候吗?
(Johannes)是的,那是很久以前的事了,基本上是在我本科开始的时候,或者说是我本科的第一年。我从高中就开始编程了。最开始是用 Java,然后在 2013 年,在我本科期间,我接触了 Python,并且真的爱上了它。因为对于我所做的工作,从计算和模拟,到后来机器学习,Python 简直太棒了。
(Tyler)你在 Java 中做过机器学习吗?我没见过很多机器学习工程师在 Java 中工作,或者需要写 Java 代码。
(Johannes)我绝对是用 Python 做的。在我攻读硕士学位之前,我实际上在一个研究实验室工作过,利用一个空档年,做了一些计算相关的工作,用的是 C++,但那真是太糟糕了。如果我当时知道 Python,我可能会全部用 Python 做,时间也能缩短十倍。
(Tyler)我知道很多人会主要使用 Python 来做大部分工作,如果他们有强烈的需求让某个算法变得更快,他们就会切换到低级语言,这种做法是最有效的。那时,你已经深度参与 Python 的工作,并且在 Python 中编程一段时间了,之后你开始使用 Streamlit 做这些机器学习项目,比如你的 Traingenerator 应用(traingenerator.jrieke.com/),这是一个多用途的应用,可以为你写机器学习代码。你创建这些应用的动机是什么?是为了回馈社区、展示你的作品、创建个人作品集,还是其他完全不同的原因?

图 12.1 – Traingenerator 应用
(Johannes)是的,基本上是这些点的组合。我是在去年春天毕业后开始使用 Streamlit 的。毕业后我去旅行,但由于疫情,我不得不回到德国,而且显然有了很多空闲时间。我一些朋友和以前的同事开始做一个开源项目,涉及 Covid 追踪领域,所以我加入了他们。作为这个项目的一部分,我们需要一个小型的仪表盘,一个朋友告诉我 Streamlit,我试了一下,觉得它非常酷。
但基本上这就是我开始用 Streamlit 玩乐的方式。我开发的 Traingenerator 应用其实更具商业或初创意图。我想,试试自己的项目。然后我意识到,我真的很喜欢用一行代码训练机器学习模型的想法,就像 FastAI 和 PyTorch Lightning 所做的那样。PyTorch Lightning 是一个同领域的库。我在机器学习领域工作了很久,通常我使用的是非常标准和基础的模型,并将它们应用到新的数据集或新领域。有一件事一直让我困扰,就是你采用了一个非常标准的方法,但你必须写大量代码来实现一个模型,或者即使你已经有了一个实现好的模型,你也必须测试并调整它;你需要转换数据以使其适应这个模型。你还得写代码来追踪你的指标和实验,所有这些内容。因此,我的想法是:能否建立一个简化这个过程的工具,也许还有可能成为一个可行的产品、可行的商业模式?
我在这方面工作了一段时间,然后当我和朋友谈论这个项目时,Traingenerator 的想法就出现了。我们讨论了一个简化这个过程的 Python 库,这个库非常好,但当你想构建更复杂的东西时,它就会达到极限;显然,这是因为你不能轻松地修改里面的内容。我们认为它对于原型非常有用,但一旦你想把模型投入生产,就必须重新开始。
然后我们有了这样一个想法:建立一个网络应用,你可以输入你想要训练的内容以及你想使用的框架,然后它会为你生成代码。你可以直接使用这些代码来训练模型,或者根据需要修改它,这就是 Traingenerator 的功能。
之后我做的其他项目,比如 GitHub 2020 应用(gh2020.jrieke.com/),还有最佳 Streamlit 列表,那些更像是为了好玩而做的个人项目,当然也是为我的作品集增加内容。
(Tyler)这一切都非常有趣。我用过 Traingenerator 好几次,非常喜欢。当时你开发它时,是在 Streamlit Sharing 发布之前,你使用 Heroku 来部署这个应用。我很好奇,如果现在再做,你还会选择同样的方式吗?你是怎么决定应用部署在哪里的?
(Johannes)当时的原因很简单,我想要一个简短的自定义网址;我不想要一个很长、难以理解的网址。但实际上,我现在已经把它迁移到了 Streamlit Sharing!相比 Heroku,这提供了更好的开发者体验,因为应用在推送到 GitHub 后,几乎在一秒钟内就会更新,而不需要重启或其他操作。Sharing 目前默认不支持自定义网址,但论坛上有一个方法,我现在正在使用它。我已经因这个功能请求把我们的产品经理烦得不轻,所以希望不会太久就能实现!
(Tyler)你做的另一个应用是一个动态生成并分享用户 GitHub 统计数据的应用,叫做 Year on GitHub(gh2020.jrieke.com/)。它变得相当受欢迎;我几乎在互联网上每个地方都看到了它。你能给我们讲讲这个故事吗?

图 12.2 – GitHub 统计应用
(Johannes)当然可以。所以背后的原因或者想法其实是一个非常随机的想法,我真不知道我是怎么想到的。背后没有什么大故事。我已经做了 Traingenerator,并且得到了极大的好评。我又做了一些 Streamlit 相关的工作,然后在圣诞节前几天,我突然想到要做一个应用,只需要输入你的 GitHub 用户名,它就会显示你在 2020 年在 GitHub 上的某些统计数据。然后你可以把它发到 Twitter 上。所以这跟 Spotify Wrapped 每年年底做的事情类似。
在我想到这个点子后几天,实际上有一个新的 Python 库发布了,专门用于 GitHub API,这个库非常酷。然后我想,显然,在三月或四月发布这样的东西是没有任何意义的,这意味着我需要在接下来的两周内完成它,并且尽快发布。所以我在整个圣诞假期期间都在忙这个项目,感觉超级酷。
我认为通过这个应用生成了大约四五百条推文。Julia 编程语言的创建者也使用了它。看到这些反应真的很棒,而且 Streamlit 的员工们也看到了所有这些推文,我想这大概给我带来了我工作的半数机会。
(Tyler)我有一个理论,如果你真的想在某个地方找到工作,你应该为他们做一个 Streamlit 应用,解决他们的某个问题或者让他们注意到你。如果你这样做,你就更有可能真的被他们聘用。我已经试过几次,通常最后都会收到工作机会。
(Johannes)实际上当时根本没有计划这样做。那时我甚至没有想过要申请 Streamlit,但它运作得非常好。回想起来,我觉得他们内部肯定都喜欢这个应用,因为它带动了 Streamlit 推特账号上的巨大流量,看到关于这个新应用的所有推文也非常酷。
(Tyler)这一切我都能理解!所以在 Streamlit 工作了 8 或 9 个月后,你决定这是你想工作的地方,申请了那里的工作,然后拿到了这份工作。首先,祝贺你。其次,是什么让你觉得这个库将会在未来成为数据科学和机器学习社区中的重要工具?我对 Streamlit 有足够的信心甚至想为它写一本书,所以我显然是同意你的,但你能谈谈你对这家公司在这个领域的信心吗?
(Johannes)所以在我看来,Streamlit 的优势在于开发者体验和它的简单性。在我当前作为产品工程师的工作中,我也会看看其他许多 Web 框架和解决方案,并且需要尝试它们。虽然确实有其他工具具有一些很酷的功能,可以做一些比 Streamlit 更复杂的事情,因为 Streamlit 还比较年轻,但就开发者体验而言,开始一个项目有多简单,制作应用有多有趣,这些工具都远不及 Streamlit。我认为有很多因素在其中起作用。
在简单性方面,你基本上只需 5 分钟,几行代码就能快速搭建一个应用。而且这只是你通常编写的 Python 脚本;你不需要学习其他类或前端开发的任何内容。对新手来说,它非常直观。
然后是 Streamlit 的实时编码模式,它允许你编写应用,Streamlit 会同时重新运行你的应用,并且非常智能地决定需要重新运行什么,尤其是当你使用缓存时。我认为这就是 Streamlit 为什么这么有趣和让人上瘾的原因,因为你可以通过即时看到结果来获得超快速的反馈。
另一个方面是社区;Streamlit 社区简直太棒了。我的意思是,现在有很多开发者真的很喜欢使用 Streamlit。论坛是一个非常酷的地方,可以交流想法、获取建议,而且有很多人非常投入,愿意在空闲时间每周花上几个小时回答问题。
事实上,我们在 Streamlit 内部有三到五个人全职负责论坛,和开发者互动,给那些构建 Streamlit 应用的人送纪念品,这对于一个只有大约 25 名员工的公司来说是非常独特的。
(Tyler)我经常想到这种动态。当我开始时,Streamlit 只感觉是一个非常好的工具。然后我开始更多地玩它。后来我意识到,它可能更像一个玩具,在其中我不断地进行迭代和反馈。每当你有一个既是工具又是玩具的东西,它最终都会非常成功。
(Johannes)是的,绝对有。我认为 Streamlit 很酷的一个重要方面,以及为什么我认为它将来会变得更酷,是它有很多不同的部分。它不仅仅是一个开源库,还是 Streamlit Sharing,如果你想部署你的模型,它非常好用且简便。此外,正如我所说,论坛也是其中一部分,如果你遇到问题,肯定能在那里得到帮助。我认为未来还会有更多的部分加入。
(Tyler)你大概已经有 9 个月的 Streamlit 开发经验了。你有什么针对初学者的建议吗?有没有一些你希望当时知道的事情?
(Johannes)我其实认为,对于初学者来说,没有太多可以给的建议,除了就是试试看。这就是 Streamlit 很酷的地方;如果你开始使用它,基本上 10 分钟后你就能了解它是怎么工作的。而且开始使用起来非常简单。我也有几个朋友,我推荐了 Streamlit 给他们,结果我并不需要做很多说服工作。我只是告诉他们这个工具,第二天他们就回来说已经知道怎么用了。唯一的建议就是注册论坛,如果有任何问题,直接问或者通过任何方式联系我们。论坛上有很多乐于助人的人。正如我所说,在 Streamlit 内部有很多人的工作就是和开发者互动。
更复杂的内容,比如缓存和状态,对于初学者来说并不容易理解,需要一些解释,我们现在也在这一方面做了很多工作。
(Tyler)感谢 Johannes 来和我们聊一聊,我相信大家从中受益匪浅。Johannes 的个人网站可以在 www.jrieke.com/ 找到。
采访 #3 – Adrien Treuille
(Tyler)嘿,Adrien!感谢你愿意接受采访。在我们真正开始之前,你愿意先简要介绍一下自己吗?我知道你曾是卡内基梅隆大学的教授,在那之前你在做蛋白质折叠的研究。你也曾参与过自动驾驶汽车的研究,现在是 Streamlit 的创始人。那么你怎么介绍自己呢?
(Adrien)首先,当我还是教授时,整个 Python 数据栈算是比较新的东西。NumPy 甚至在 1.0 之前就已经存在,而且突然间有一个了不起的库叫 NumPy,让 Python 一下子和 MATLAB 比肩,后来,Python 甚至比 MATLAB 更强大。这是 Python 开始成为数值计算主流语言的起点,最终也成为了机器学习的主流语言。Python 曾经是一个脚本语言,是一个系统管理员语言,或者说是一个计算机科学 101 级别的语言。突然间,它拥有了一个庞大的、新的、极其重要的工业应用。从最初一群极客和教授在研究中使用它开始,他们发现它真的非常有用,而且比用 C 写自己的机器学习代码或者数值计算代码要容易得多。
我做的另一件事是创造了可以让人们开发科学问题的计算机游戏,最终,数百万人玩过这些游戏。稍微快进一下,我去了 Google 工作。在技术和机器学习的交汇点上,我有了一个非常有趣、酷的职业生涯。
然后我开始了 Streamlit。我们正在构建的工具基于 15 年前我们使用的数值 Python 栈(那时还不流行)。Streamlit 的另一个部分是构建一个社区。我认为这一方面非常特别。我的研究中这两个主题已经完美结合:数值 Python 世界和在线社区的构建。
(Tyler)在 2019 年 10 月的原始文档和随后的 Medium 文章中,Streamlit 最初被推介为机器学习工程师的工具。现在,你可以看到各种各样的人都在使用它。我不是一个机器学习工程师,但我一直在使用它。Streamlit 的想法随着时间的推移是如何变化的?
(Adrien)从一个更宏观的角度来看,我很真诚地说,很酷的是你能够观察到这一点。对于我们 Streamlit 内部的人来说,我们在谈论目标用户群体时会有一些微妙的变化。但这并不一定意味着其他人注意到了这些细微的变化。看到别人能注意到这一点真的很酷!
有几种方式可以回答这个问题。Streamlit 对 Python 编程的意义,比对机器学习或数据科学的意义更为根本。它满足了 Python 编程中,其他工具无法提供的需求。你可以编写命令行脚本,也有 Python 的 GUI 框架,但这些框架的使用相当复杂,因为 GUI 应用开发与数据工作是完全不同的编程风格。你也可以进入 Jupyter notebook,但那同样是另一种完全不同的编程风格。
Streamlit 是图形用户界面编程和脚本编程的融合。对我来说,这是一个非常有用的功能。它实际上超越了机器学习。因此,Streamlit 的潜力之一,举个例子,就是它可以在 CS 101 课程中发挥作用。Streamlit 的一个酷点是,它不仅有技术本身的概念,还有如何将它应用于不同问题和领域的思考。我们如何优先考虑应用,以便既能造福社区,又能创造一个可持续的公司?
(Tyler)我想再谈谈你刚才提到的社区,因为你之前做过很多在线游戏,比如 FoldIt (fold.it/),它有很强的众包功能。在我看来,Streamlit 有两个方面。一方面非常注重社区,它是一个开源语言,任何人都可以在其基础上构建,而且可以很容易地制作并添加 Streamlit 组件。另一方面则是 Streamlit 公司,显然它的存在是为了最终实现盈利。你是如何管理这两方面的?
(Adrien)我不觉得我们必须选择。当两者的利益相一致时,大家都能受益。从商业模式的角度来看,我们正在努力将这款软件推向世界,它在数据科学中有很多应用场景。例如,我为我儿子的班级编写了一个计算机游戏,使用 Streamlit 帮助他们理解乘法。它也有很多工业应用。这两个群体是相互契合的:社区中的人越多,越多的人为 Streamlit 做出贡献,我们的可服务用户群体也就越大,尤其是对于那些工业应用。
这是一个漏斗。数据科学家流入 Streamlit 的开源社区,然后再流入我们的客户基础。我们努力在漏斗的每个阶段都实现增长,这对商业模式和社区都是有利的。
(Tyler)你从自己十年构建在线众包游戏的经历中学到了什么,并且带到了 Streamlit 的世界中?因为社区感觉像是一个元游戏,人们在尝试为他人创建酷炫的 Streamlit 应用。你是如何培育这种氛围的?这是有意为之,还是仅仅因为它是一个酷工具的结果?
(Adrien)FoldIt 和 Streamlit 之间有一个有趣的相似点,那就是它们都被设计成像玩具一样。所谓“玩具”——这是游戏设计中的一个技术术语——指的是没有规则的游戏,你只想和它一起玩。GI Joe 玩具就没有使用手册,它只是一个通过让它做事情来玩的东西。你也可以为它加上规则,但并不是必须的。
这里还有一个 Streamlit 游戏化的方面:人们构建 Streamlit 应用并将其上线,来传达思想、展示工作成果、获得赞赏。这有助于其他人了解 Streamlit,然后他们也会构建更多应用,进一步壮大社区。我们意识到,“哇,发布和赞赏的循环是我们一个了不起的增长动力。”我们确实做了一些努力来支持那些展示自己作品的人,但我认为我们还没有真正探索 Streamlit 这一方面的所有可能性。我们只是没有时间去执行,因为有太多重要的事情要做。
(Tyler)如果我们在谈论公司优先级问题,你如何看待当前公司的优先事项?你提到了漏斗,那么就有三个主要部分,数据科学家、使用 Streamlit 的数据科学家,以及在公司内部使用 Streamlit 的数据科学家。当前 Streamlit 的焦点在哪里?
(Adrien)实际上,还有一个阶段,那就是有多少人正在观看那些公司内部创建的应用,因为我们目前的定价计划是基于观众人数,而不是开发者的数量。我们按每个观众每月收费。
所以,我们的优先级聚焦在这个管道的另一个层面上。
我们把发布后的第一年称为“开发者之年”,那时我们的重点是发展开源社区。经历了指数级增长后,运营发生了不连续的变化。我们没有为此做好准备。我们把整个工程团队轮流安排去回答社区论坛中的问题。他们给出的答案非常专业,但整个工程团队在三个月内几乎停滞不前。然后我们意识到,糟糕,我们不能通过雇佣工程师来解决社区问题。
现在是第二年,我们所处的这一年是“客户之年”。这一年主要是围绕建立初步的盈利引擎,并创建一个自助机制,让用户可以轻松点击按钮,使用 Streamlit for Teams。我们还没有完全实现这一点,但各个环节正在逐步到位!
(Tyler) 很多读这篇文章的人可能刚刚开始接触 Python 开发,或者处于 Python 和数据科学的初学者/中级阶段。他们有很多选择可以考虑,从 Django 或 Flask,到无代码工具,再到其他仪表盘框架。他们甚至可以转向 R 的 Shiny 应用开发。你有什么简洁的推荐给新用户?为什么选择 Streamlit?
(Adrien) 我会说,你应该直接尝试一下。Twitter 上有很多关于 Streamlit 有多“酷”的讨论。我认为 Streamlit 既强大又酷。Streamlit 基于脚本编程的理念,这是一种非常自然且简洁的数据流思维方式。我觉得其他人并没有真正做到这一点。它应该让你觉得有点神奇;编程本身并不是魔法,但它确实非常有趣。很多受过高等教育、薪水极高的人做应用只是因为这很有趣,所以我们喜欢用这种视角来接触它。如果你能感觉到每写一个函数调用都能得到比花费更多的回报,那么经历这个过程会非常激动人心。
(Tyler) 对我来说,我一开始选择它是因为它简单,后来坚持使用是因为它是最好的,你懂吗?
(Adrien) 我认为有一个事实是(也许我错了),在这个可视化/仪表盘应用/开发领域,越是深入了解它们的工作原理,越是会感到失望。你看到的演示看起来非常可定制,似乎很棒,但当你真正体验构建或尝试复制演示时,实际上很难实现它们在网站上承诺的功能。
这对于 Streamlit 来说根本不是问题。当你查看画廊中的所有演示应用时(streamlit.io/gallery),它们其实非常易于使用。我们并不打算去竞争“打造一个庞大且完美的应用”这一领域。
(Tyler) 感谢你过来和我们交流!我们应该指向的一个主要资源显然是 Streamlit 的官方网站(streamlit.io/)。你可以在medium.com/@adrien.g.treuille找到 Adrien 的文章,或者在 Twitter 上通过twitter.com/myelbows联系他。
采访 #4 – Charly Wargnier
(Tyler) 嗨,Charly!非常感谢你过来跟我们分享。首先,你能向读者介绍一下自己吗?
(Charly) 嗨!我来自法国,已经在英国生活了大约 13 年。我主要从事数字营销、商业智能和SEO(搜索引擎优化)咨询工作。最近几年,我转向了数据工程和数据科学。此外,我曾在公司内部和代理机构工作过,也曾为零售时尚的大公司和许多小型企业工作过。但自 2014 年以来,我的工作主要集中在企业级客户,而 2008 到 2014 年间则主要是为小型企业服务。
(Tyler) 我们稍微回顾一下,做 SEO 是什么感觉?它实际是什么样的工作?
(Charly) 我的核心技能主要是技术 SEO。SEO 是一个广阔的领域,正如你可以想象的那样,而我的核心技能是与网站的技术相关的任何事情,比如网站的编码方式,或者它是如何被爬虫抓取和传递的。确保 Google 和 Bing 能准确地抓取这些大型网站,显然,这涉及很多方面,比如确保付费搜索与 SEO 协同工作。
这是一份非常多元化的工作,你不仅需要与 SEO 人员合作,还需要与开发网站的人员,甚至公关、内容和产品团队合作。从互动的角度来看,这可以说是最具多样性的工作之一。
(Tyler) 明白了,一切都说得通。还有一个我们还没提到的事情是 Streamlit。你很常用 Streamlit,而且是一个多产的创作者。Streamlit 对你来说有什么特别有价值的地方?
(Charly) 我过去经常通过 Google Colab 或 Jupyter Notebook 提供一些 Python 脚本给别人。而自从使用 Streamlit 之后,我不再需要发送脚本,我可以直接发送这些应用程序!我可以展示一些 SEO 应用或功能,用户如果不是开发人员,可能根本无法使用这些功能。它真的拓宽了应用范围和用户接受度。在过去几个月我与几家公司合作时,我能够让更多的人开始使用我设计的 SEO 应用程序。真是发生了很大的变化。
(Tyler) 真有趣!你在 Python 中的另一个选择是为 Django 应用程序做 Flask;你曾考虑过为 SEO 应用做类似的事情吗?
(Charly) 是的,我曾经开发过一些 Flask 应用,但那时候对我来说开发起来比较繁琐,因为它需要很多 HTML 或 JavaScript 的技能。但使用 Streamlit 后,它让我能够非常快速地创建一些东西并与他人分享。所以不再用 Flask,我也试过 Django,但它的学习曲线相当陡峭。通常设计一个 Django 应用会花我很长时间。所以,确实没有可比性;我暂时放下了 Flask 和 Django,现在真的把精力集中在 Streamlit 上。
(Tyler) 完全同意,在 Streamlit 出现之前,我做了很多不同的数据科学项目,我的选择总是把它放在 Jupyter notebook 或者博客文章中,或者用 Flask 做一个完整的应用。对我来说,问题在于,在 Streamlit 出现之前,做一个完整的应用所需的时间和精力差异太大了。那几乎会把项目的时间翻倍!而有了 Streamlit,制作应用只是比制作 Jupyter notebook 稍微困难一点,但输出效果好得多。
(Charly) 现在我在尝试学习 React,希望能做出一些更集成、更精致的东西,同时用 Streamlit 作为最小可行产品(MVP)。我也用过其他一些 Web 框架,但你说得对,就快速性而言,到目前为止没有什么能比得上 Streamlit。我认为最接近的是 Panel(panel.holoviz.org/),它既灵活又简单易用,但我觉得 Streamlit 和 Panel 之间的最大区别就是社区。Streamlit 社区非常热情,包括你在内的许多人。即使是在几年前,我几乎没有用 Python 编程,而 Streamlit 真的是我接纳的应用。我认为社区真的是关键。
(Tyler) 说到社区,你一直活跃于 SEO 和 Streamlit 社区。你创建了像 StreamEA 这样的应用(www.charlywargnier.com/post/streamea-entity-analyzer),它能够从网页中提取和分析实体;你是怎么想到做这样的应用的呢?是先解决自己的问题,然后将其转化为应用,还是把以前为旧客户写的 Python 脚本转化成应用?
(Charly) 所以这真的是不同因素的结合。你提到了一些原因。首先,我是在解决我自己遇到的问题,因为我做 SEO 已经多年了,经常会碰到 SEO 方面的问题,想到的解决方案就是通过创建应用,大家也能使用这个解决方法。我一直对 Web 应用很感兴趣;我记得早在 2013 年时,我就有一个小小的“极客梦想”——开发 Web 应用!另外,你说得对,我有很多年一直在用的 Jupyter notebook 和脚本,至于用户采用方面,我希望确保任何人都能尝试这些工具。
我通常也会在公司内部或者向我合作过的客户收集反馈,所以我获得了很多用户反馈。有时也会是偶然发现一些新库,像你一样,我很感兴趣去尝试。最后一个方面是收费问题;市场上有很多 SEO 工具,价格可能很贵。我想,如果可以的话,我的一个秘密愿望就是能让那些应用免费开放。我有种“罪恶的快乐”,就是将一些人们愿意每月花费几百或几千英镑的应用,通过 Python 重新开发成免费的。这是一种很棒的满足感!
(Tyler) 我很想再谈谈 StreamEA,你能谈谈在开发这个应用时遇到的困难吗?这个过程是什么样的?

图 12.3 – StreamEA
(Charly) 实体分析在 SEO 领域是一个热门话题。谷歌已经从纯粹的关键词转向了各种语义技术进步,比如实体。例如,谷歌对这些实体赋予了一定的排名价值或权重,因此你需要确保你的页面或网站准确地针对这些实体进行优化。
就挑战而言,代码量还是比较大的,但也不算太具有挑战性。谷歌自然语言 API 相对简单。所以这个应用的构建方式主要分为两个部分,一个是用 BeautifulSoup 的抓取部分,另一个是通过 API 进行实体分析的部分。这里的难点在于,默认情况下,谷歌的自然语言 API 会获取一些数据,你需要创建一些函数来实现字典和数据框之间的转换,这对我来说有点挑战,因为我在去年八月刚开始做这个项目时,Python 知识并不算扎实。我记得当时我为此挣扎了很久,想要让谷歌的 API 能与我的数据框兼容。现在我已经觉得这很简单了,但我记得那时是个瓶颈,也是一个学习的过程。
然后你有一个小预算估算工具,因为如果不加以控制,谷歌 NLP API 的费用可能会非常高!很多人都在抓取 Wikipedia,它的页面可能很长,所以我认为,提供这样一个小预算估算工具,来估算使用 StreamEA 的费用(StreamEA 要求你上传自己的谷歌 API 凭证),会很有用。
最近,我开源了另一个应用程序,它从 Wikipedia 的 URL 中提取实体关系。我还没有对它进行预算估算,因此人们对我有点不满,这也是我加上警告的原因。它不仅抓取你输入的 Wikipedia URL,还抓取任何子页面,如果没有限制,可能会变得疯狂。前几天,有些人通过脚本将应用程序推到最大,例如从主页面提取 100 个子页面,这个过程非常长。他们因此被 Google 收取了几百英镑的费用!
(Tyler) 这是我在一开始为人们设置 AWS 或 GCP 账户时推荐的唯一建议,你应该绝对设置一个你能承受的预算限制,这样它会在你花费接近那个金额之前自动关闭你的服务。
所以你差不多是同时开始使用 Python 和 Streamlit 的,对于刚入门 Streamlit 的初学者,你有什么建议吗?有没有什么是你当初开始时希望自己知道的事情?
(Charly) 我是在 2016 年或 2017 年开始接触 Python 的,那时我几乎是在 GitHub 上找到一些脚本,什么也不做。只是用 Python 而并不一定理解这些代码的含义。然后我开始在线学习 Jupyter Notebook,最终有机会开始构建一些 Web 应用。
但回到你的问题,我给新用户的建议是依靠社区。对于 Streamlit,我认为这就是我们的独特之处。我会说,不要太害羞!那里的人非常乐于助人,他们一定会尽力提供帮助。而且不要害怕分享任何东西;拥抱 Twitter,利用它与别人分享信息。不要害怕分享你的项目和进展,而不仅仅是最终产品;毕竟没有“最终产品”这一说。我真的曾经在真空中工作,为自己工作,如果你不分享你的工作,就无法从社区的反馈中获得这种良好的情感推动。
它永远不会完美,你也永远不会完全准备好。所以,尽早发布,频繁发布!如果应用程序有一些问题,别担心。在你的推文或帖子中提醒一下,大家会理解的。毕竟,你的工具是免费的,所以大家不能要求太多。
(Tyler) 当我刚开始在学校做数据科学工作时,我从不分享任何东西,因为我非常紧张,觉得自己的东西和外面那些专业人士比起来不够好。所以我做了很多东西,却从不展示给任何人看,因为我觉得它不够好。后来我意识到这样行不通,于是开始分享更多的项目,结果发现这样好多了,变得更加有趣。
(Charly) 你这些年来发布了很多项目,真的很棒,非常令人印象深刻。
(Tyler)谢谢!你现在有没有其他想要推荐的项目?
(Charly)哦,是的,正如你想象的那样,现在有很多事情在进行中。我大约两年前就开始了其中一些项目,不过当时只是脚本或者笔记本,现在我正在慢慢将它们转换为 Streamlit 应用。我希望能把 StreamEA 变成一款付费应用,让它对 SEO 更加有用。我还在筹备一些机器学习应用,希望能尽快发布。
(Tyler)非常感谢你来和我们交流!你可以在 Twitter 上找到 Charly:twitter.com/DataChaz。
总结
这就结束了第十二章,Streamlit 高级用户,也标志着这本书的结束!在这一章中,我们讨论了很多深刻的内容,从与 Fanilo 讨论社区开发的重要性,到与 Johannes 交流流行应用的实际示例,甚至与 Adrien 一起探讨了 Streamlit 的玩具式功能,以及 Streamlit 的未来发展方向。我们还简单回顾了 Streamlit 在过去几年(2019 和 2020)中的历史,了解了 Charly 关于 SEO 生态系统的看法,并在过程中学到了许多技巧和窍门。我的一些最喜欢的建议包括:加入并在论坛上发帖(Fanilo),发布你认为有趣的 Streamlit 应用(Johannes),以及从 Adrien 那里领悟到 Streamlit 玩具式特性的魅力。
我只想说感谢你阅读这本书;这对我来说是一个充满热爱的工作,我最希望的就是你能联系我,让我知道它如何影响了你的 Streamlit 开发者体验。你可以在 Twitter 上找到我:twitter.com/tylerjrichards,希望你在阅读这本书时,能够像我写这本书一样享受其中。谢谢你,去制作一些很棒的 Streamlit 应用吧!







浙公网安备 33010602011771号