目录
- [Streamlit 完整教程](#Streamlit 完整教程)
Streamlit 完整教程
什么是 Streamlit
Streamlit 是一个开源 Python 库,专门为机器学习和数据科学应用设计。它允许开发者使用纯 Python 代码创建美观的 Web 应用,无需了解 HTML、CSS 或 JavaScript。
核心特点
- 简单易用: 只需要几行代码就能创建应用
- 快速迭代: 代码更改后应用自动刷新
- 交互性强: 内置各种交互组件
- 数据可视化: 原生支持多种图表库
- 机器学习友好: 深度集成 ML 工作流
- 部署简单: 一键部署到云端
环境准备
bash
# 使用 pip 安装
pip install streamlit
# 升级到最新版本
pip install --upgrade streamlit
快速开始
示例
python
# app.py
import streamlit as st
# 设置页面标题
st.title("我的第一个 Streamlit 应用")
st.header("简单示例")
st.subheader("欢迎来到 Streamlit 世界!")
# 显示文本
st.write("这是一个简单的文本示例。")
st.write("你可以在这里显示任何内容。")
# 显示标题
st.title("标题示例")
st.header("二级标题")
st.subheader("三级标题")
# 显示 Markdown
st.markdown("""
### Markdown 支持的标题
- 列表项 1
- 列表项 2
- **粗体文本**
- *斜体文本*
""")
# 显示数据
import pandas as pd
df = pd.DataFrame({
'列1': [1, 2, 3, 4],
'列2': [10, 20, 30, 40]
})
st.dataframe(df)
# 显示图表
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 10, 100)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)
st.pyplot(fig)
代码说明:
- 第1行:导入 Streamlit 库
- 第3-5行:设置页面标题、副标题和三级标题
- 第7-8行:使用
st.write()显示普通文本 - 第11-15行:使用 Markdown 语法显示格式化文本
- 第17-21行:创建并显示 Pandas DataFrame
- 第23-28行:创建并显示 Matplotlib 图表
运行结果 :
运行 streamlit run app.py 后,浏览器会自动打开显示:
- 页面标题和各级标题
- 格式化的文本内容
- 一个 4x2 的表格
- 一个正弦波形图
代码解析
- st.title(): 显示页面主标题
- st.header(): 显示二级标题
- st.subheader(): 显示三级标题
- st.write(): 显示任意类型的内容
- st.markdown(): 支持 Markdown 格式的文本
- st.dataframe(): 显示表格数据
- st.pyplot(): 显示 Matplotlib 图表
核心概念
概念1:Rerun 机制
定义:Streamlit 的核心特性,当代码发生变化时,整个应用会自动重新运行。
为什么重要:这使得开发者可以立即看到代码更改的效果,无需手动刷新页面,极大提高了开发效率。
工作原理:
修改代码
保存文件
Streamlit 检测到变化
重新执行脚本
更新界面
浏览器刷新
示例:
python
# 每次修改代码后,应用会重新运行
import time
import streamlit as st
st.write("当前时间:", time.time())
st.button("点击我") # 点击这个按钮不会触发 rerun
概念2:会话状态 (Session State)
定义:在应用重新运行时保持数据的机制。
为什么重要:由于 Streamlit 的 rerun 机制,常规变量会在每次 rerun 时重置。Session State 允许我们保持用户输入、计算结果等数据。
工作原理 :
Session State 存储在字典中,键值对形式存储,在应用会话期间保持存在。
示例:
python
import streamlit as st
# 初始化会话状态
if 'counter' not in st.session_state:
st.session_state.counter = 0
# 显示计数器
st.write("计数器:", st.session_state.counter)
# 增加计数器
if st.button("增加"):
st.session_state.counter += 1
# 重置计数器
if st.button("重置"):
st.session_state.counter = 0
概念3:Widget 交互
定义:用户界面交互组件的总称。
为什么重要:Widget 让应用具有交互性,允许用户输入数据、选择选项、调整参数等。
工作原理 :
每个 Widget 都有唯一的键(key),其值会保存在 Session State 中。
示例:
python
import streamlit as st
# 文本输入
name = st.text_input("请输入您的名字", "默认值")
st.write("你好,", name)
# 选择框
option = st.selectbox("选择一个选项", ["选项1", "选项2", "选项3"])
st.write("你选择了:", option)
# 滑块
age = st.slider("选择年龄", 0, 100, 25)
st.write("你的年龄是:", age)
# 复选框
if st.checkbox("显示详细信息"):
st.write("这里是一些详细信息。")
概念之间的关系
Rerun 机制
会话状态
Widget 交互
应用状态管理
用户体验优化
这些概念协同工作:Rerun 机制触发代码重新执行,会话状态保持数据,Widget 提供交互界面,三者共同实现了一个完整的交互式 Web 应用。
基础功能详解
功能1:文本和显示组件
用途:在应用中显示各种类型的内容。
基本语法:
python
st.title("主标题")
st.header("二级标题")
st.subheader("三级标题")
st.text("普通文本")
st.markdown("Markdown文本")
st.latex("数学公式")
参数说明:
text: 要显示的文本内容unsafe_allow_html: 是否允许 HTML 标签(默认 False)
简单示例:
python
import streamlit as st
st.title("文本显示示例")
# 基本文本显示
st.text("这是一个普通的文本")
st.write("这是 write 方法") # write 更灵活,可以显示任何对象
# Markdown
st.markdown("""
## Markdown 示例
- 列表项 1
- 列表项 2
- **粗体**
- *斜体*
- [链接](https://streamlit.io)
""")
# LaTeX
st.latex(r"""
\int_{a}^{b} f(x) dx = F(b) - F(a)
""")
常见用法:
python
import pandas as pd
import numpy as np
# 显示数据
st.write("## 数据显示")
# DataFrame
data = pd.DataFrame({
'姓名': ['张三', '李四', '王五'],
'年龄': [25, 30, 35],
'城市': ['北京', '上海', '广州']
})
st.dataframe(data)
# 表格格式化
st.write("### 格式化表格")
st.table(data.style.background_gradient(cmap='YlOrRd'))
# 显示图像
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.bar(data['姓名'], data['年龄'])
st.pyplot(fig)
-
使用 Markdown 格式化文本,更易读
-
使用
st.write()显示复杂对象,它会自动选择最佳显示方式 -
对于大量数据,考虑使用
st.data_editor()提供更好的交互体验 -
不要过度使用 HTML,除非必要
-
LaTeX 公式需要使用原始字符串(加 r 前缀)
功能2:输入组件
用途:收集用户输入的数据。
基本语法:
python
# 文本输入
text = st.text_input("标签", 默认值, max_chars=100)
text_area = st.text_area("文本区域", 高度=200)
# 选择输入
select = st.selectbox("选择框", 选项列表)
multiselect = st.multiselect("多选框", 选项列表)
slider = st.slider("滑块", 最小值, 最大值, 默认值)
# 按钮和开关
button = st.button("按钮")
checkbox = st.checkbox("复选框")
switch = st.toggle("开关")
参数说明:
label: 组件标签value: 默认值key: 组件的唯一标识disabled: 是否禁用help: 帮助文本
简单示例:
python
import streamlit as st
st.title("输入组件示例")
# 文本输入
name = st.text_input("请输入姓名")
st.write("你的名字是:", name)
# 滑块
age = st.slider("年龄", 0, 100, 25)
st.write("你选择了年龄:", age)
# 选择框
city = st.selectbox("选择城市", ["北京", "上海", "广州", "深圳"])
st.write("你选择了:", city)
# 复选框
if st.checkbox("显示更多选项"):
hobby = st.text_input("输入爱好")
st.write("你的爱好是:", hobby)
常见用法:
python
import streamlit as st
import pandas as pd
st.title("表单示例")
with st.form("my_form"):
col1, col2 = st.columns(2)
with col1:
name = st.text_input("姓名")
email = st.text_input("邮箱")
with col2:
age = st.number_input("年龄", 0, 120)
gender = st.selectbox("性别", ["男", "女", "其他"])
submitted = st.form_submit_button("提交")
if submitted:
data = {
'姓名': name,
'邮箱': email,
'年龄': age,
'性别': gender
}
st.write("提交的数据:")
st.json(data)
- 使用
st.form()创建表单,避免频繁触发 rerun - 为每个组件设置
key参数,特别是在循环中创建组件时 - 使用
columns组织界面布局
常见陷阱
- 没有设置 key 的组件可能导致状态冲突
- 文本输入框不要忘记设置最大字符数
功能3:布局组件
用途:组织和美化应用界面。
基本语法:
python
# 列布局
col1, col2 = st.columns(2)
col1.write("左侧内容")
col2.write("右侧内容")
# 侧边栏
st.sidebar.write("侧边栏内容")
# 标签页
tab1, tab2, tab3 = st.tabs(["标签1", "标签2", "标签3"])
# 容器
with st.container():
st.write("容器内的内容")
参数说明:
columns: 列的数量和宽度比例gap: 列之间的间距label: 标签或标题
简单示例:
python
import streamlit as st
import numpy as np
import matplotlib.pyplot as plt
st.title("布局示例")
# 侧边栏
st.sidebar.header("控制面板")
data_points = st.sidebar.slider("数据点数", 10, 100, 50)
# 主内容区域
st.header("主内容")
# 列布局
col1, col2, col3 = st.columns([1, 2, 1])
with col1:
st.write("左侧面板")
st.metric("指标", 42, "+5%")
with col2:
# 主要图表
x = np.linspace(0, 10, data_points)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)
st.pyplot(fig)
with col3:
st.write("右侧面板")
st.selectbox("选择数据类型", ["正弦", "余弦", "正切"])
# 标签页
tab1, tab2 = st.tabs(["视图1", "视图2"])
with tab1:
st.write("第一个标签页")
st.dataframe(np.random.rand(5, 3))
with tab2:
st.write("第二个标签页")
st.line_chart(np.random.randn(20, 2))
常见用法:
python
import streamlit as st
st.title("高级布局")
# 使用 expanders 折叠内容
with st.expander("点击展开详细信息"):
st.write("这里是展开的内容...")
st.write("可以包含任何组件")
# 使用 divider 分隔内容
st.divider()
st.write("分隔符上方的部分")
# 使用空格布局
st.write("布局示例:")
st.write("-" * 50)
col1, col2 = st.columns([3, 1])
with col1:
st.write("主内容占据更多空间")
with col2:
st.write("侧边栏")
# 使用容器进行分组
with st.container():
st.header("容器1")
st.write("容器1的内容")
with st.container():
st.header("容器2")
st.write("容器2的内容")
- 使用侧边栏放置控制选项,保持主界面整洁
- 使用列布局创建多栏设计
- 使用标签页组织相关内容
常见陷阱
- 不要过度嵌套容器,影响性能
- 确保布局在不同屏幕尺寸下都能正常显示
功能4:数据可视化
用途:以图表形式展示数据。
基本语法:
python
# 内置图表
st.line_chart(data)
st.bar_chart(data)
st.area_chart(data)
st.scatter_chart(data)
# Map 地图
st.map(data)
# 使用 Plotly
import plotly.express as px
fig = px.line(data, x='x', y='y')
st.plotly_chart(fig)
参数说明:
data: 要显示的数据use_container_width: 是否使用容器宽度width: 图表宽度height: 图表高度
简单示例:
python
import streamlit as st
import pandas as pd
import numpy as np
st.title("基本图表示例")
# 生成数据
chart_data = pd.DataFrame(
np.random.randn(20, 3),
columns=['A', 'B', 'C']
)
# 线图
st.subheader("线图")
st.line_chart(chart_data)
# 柱状图
st.subheader("柱状图")
st.bar_chart(chart_data.iloc[:5]) # 只显示前5行
# 面积图
st.subheader("面积图")
st.area_chart(chart_data)
# 散点图
st.subheader("散点图")
st.scatter_chart(
chart_data,
x='A',
y='B',
color='C'
)
常见用法:
python
import streamlit as st
import pandas as pd
import plotly.express as px
st.title("高级图表示例")
# 模拟销售数据
dates = pd.date_range('2024-01-01', periods=12, freq='M')
sales_data = pd.DataFrame({
'日期': dates,
'产品A': np.random.randint(1000, 5000, 12),
'产品B': np.random.randint(800, 4000, 12),
'产品C': np.random.randint(500, 3000, 12)
})
# 使用 Plotly 创建交互式图表
st.subheader("销售趋势 - Plotly")
fig = px.line(
sales_data,
x='日期',
y=['产品A', '产品B', '产品C'],
title='月度销售趋势',
markers=True
)
fig.update_layout(
xaxis_title='月份',
yaxis_title='销售额',
hovermode='x unified'
)
st.plotly_chart(fig, use_container_width=True)
# 地图可视化
st.subheader("地理位置分布")
# 模拟城市数据
cities = pd.DataFrame({
'city': ['北京', '上海', '广州', '深圳'],
'lat': [39.9042, 31.2304, 23.1291, 22.5431],
'lon': [116.4074, 121.4737, 113.2644, 114.0579],
'value': [2154, 2424, 1404, 1344]
})
st.map(
cities,
latitude='lat',
longitude='lon',
size='value',
zoom=4
)
- 对于快速原型,使用内置的简单图表
- 对于需要交互性的图表,使用 Plotly
- 考虑数据量,大数据集使用聚合显示
功能5:媒体组件
用途:在应用中显示图片、音频、视频等媒体内容。
基本语法:
python
# 图片
st.image(image, caption="图片说明", width=200)
# 音频
st.audio(audio_file, format="audio/mp3")
# 视频
st.video(video_file, format="video/mp4", start_time=0)
# 播放器
st.audio(audio_url, format="audio/mpeg", start_time=0)
参数说明:
image: 图片路径或 URLcaption: 图片说明width: 图片宽度use_column_width: 是否使用列宽format: 媒体格式start_time: 开始时间(秒)
简单示例:
python
import streamlit as st
st.title("媒体组件示例")
# 显示图片
st.subheader("图片示例")
st.image(
"https://streamlit.io/images/brand/streamlit-logo-secondary-colormark-darktext.png",
caption="Streamlit Logo",
width=300
)
# 音频播放器
st.subheader("音频播放器")
st.audio(
"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
format="audio/mp3"
)
# 视频播放器
st.subheader("视频播放器")
st.video(
"https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4",
format="video/mp4"
)
常见用法:
python
import streamlit as st
import numpy as np
from PIL import Image
st.title("高级媒体功能")
# 生成并显示图表图片
fig, ax = plt.subplots()
ax.bar(['A', 'B', 'C'], [1, 2, 3])
ax.set_title("动态生成的图表")
# 将图表转换为图片
import io
from matplotlib.backends.backend_agg import FigureCanvasAgg
buf = io.BytesIO()
fig.savefig(buf, format='png')
img = Image.open(buf)
st.image(img, caption="动态生成的图表", use_column_width=True)
# 文件上传
st.subheader("文件上传")
uploaded_file = st.file_uploader(
"选择一个图片",
type=['png', 'jpg', 'jpeg']
)
if uploaded_file is not None:
image = Image.open(uploaded_file)
st.image(image, caption="上传的图片")
# 显示图片信息
st.write(f"图片尺寸: {image.size}")
st.write(f"图片模式: {image.mode}")
- 使用
st.file_uploader()让用户上传本地文件 - 为媒体组件添加说明文字,提高可用性
- 考虑文件大小,避免加载过大的媒体文件
功能6:缓存机制
用途:提高应用性能,避免重复计算。
基本语法:
python
# 简单缓存
@st.cache_data
def load_data():
return expensive_computation()
# 带参数的缓存
@st.cache_data(ttl=3600)
def process_data(params):
return compute(params)
# T 缓存对象
@st.cache_resource
def load_model():
return load_ml_model()
参数说明:
ttl: 缓存有效期(秒)suppress_st_warning: 是否隐藏 Streamlit 警告show_spinner: 是否显示加载动画max_entries: 最大缓存条目数hash_funcs: 自定义哈希函数
简单示例:
python
import streamlit as st
import time
import pandas as pd
import numpy as np
st.title("缓存示例")
# 模拟耗时的数据加载
@st.cache_data
def load_large_dataset():
st.write("正在加载数据集...")
time.sleep(3) # 模拟耗时操作
return pd.DataFrame(np.random.randn(1000, 10))
# 使用缓存
data = load_large_dataset()
st.write("数据加载完成!")
st.write(f"数据形状: {data.shape}")
# 清除缓存
if st.button("清除缓存"):
st.cache_data.clear()
st.rerun() # 重新运行以重新加载数据
常见用法:
python
import streamlit as st
import time
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
st.title("机器学习模型缓存")
# 缓存数据集
@st.cache_data
def get_dataset(n_samples=1000):
st.write("正在生成数据集...")
X, y = make_classification(
n_samples=n_samples,
n_features=20,
n_informative=10,
n_redundant=5,
random_state=42
)
return X, y
# 缓存模型
@st.cache_resource
def train_model(X, y, n_estimators=100):
st.write("正在训练模型...")
model = RandomForestClassifier(
n_estimators=n_estimators,
random_state=42
)
model.fit(X, y)
return model
# 生成数据
X, y = get_dataset()
# 训练模型
model = train_model(X, y)
# 显示模型性能
accuracy = model.score(X, y)
st.metric("模型准确率", f"{accuracy:.2%}")
- 对数据加载和计算密集的操作使用缓存
- 为缓存函数添加描述性注释
- 合理设置 TTL 值,平衡性能和数据新鲜度
功能7:错误处理和调试
用途:处理运行时错误,提高应用稳定性。
基本语法:
python
# 错误捕获
try:
result = risky_operation()
except Exception as e:
st.error(f"发生错误: {e}")
# 显示异常信息
st.exception(e)
# 使用 st.form 避免部分操作触发 rerun
with st.form("safe_operation"):
# 代码在这里执行
submitted = st.form_submit_button("提交")
if submitted:
# 执行可能出错的操作
pass
# 调试信息
st.write("调试信息:", debug_data)
参数说明:
exception: 要显示的异常对象element: 要显示错误的组件help: 帮助文本
简单示例:
python
import streamlit as st
import pandas as pd
st.title("错误处理示例")
# 输入文件名
filename = st.text_input("输入文件名 (例如: data.csv)")
# 加载文件
try:
if filename:
df = pd.read_csv(filename)
st.write("文件加载成功!")
st.write(f"数据形状: {df.shape}")
st.dataframe(df.head())
else:
st.write("请输入文件名")
except FileNotFoundError:
st.error("文件不存在,请检查文件名是否正确")
except pd.errors.EmptyDataError:
st.error("文件为空,请检查文件内容")
except Exception as e:
st.error(f"发生未知错误: {e}")
# 显示详细错误信息
with st.expander("查看错误详情"):
st.exception(e)
常见用法:
python
import streamlit as st
import numpy as np
from sklearn.preprocessing import StandardScaler
st.title("数据处理示例")
# 文件上传
uploaded_file = st.file_uploader("上传 CSV 文件", type=["csv"])
if uploaded_file is not None:
try:
# 读取文件
df = pd.read_csv(uploaded_file)
# 数据预览
st.subheader("数据预览")
st.dataframe(df.head())
# 数据质量检查
st.subheader("数据质量")
# 检查缺失值
missing_values = df.isnull().sum()
if missing_values.sum() > 0:
st.warning("数据包含缺失值")
st.write(missing_values[missing_values > 0])
else:
st.success("数据没有缺失值")
# 检查异常值
numerical_cols = df.select_dtypes(include=[np.number]).columns
if len(numerical_cols) > 0:
st.subheader("异常值检测")
for col in numerical_cols:
Q1 = df[col].quantile(0.25)
Q3 = df[col].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df[col] < lower_bound) | (df[col] > upper_bound)]
if len(outliers) > 0:
st.warning(f"{col} 列存在 {len(outliers)} 个异常值")
else:
st.success(f"{col} 列没有异常值")
except Exception as e:
st.error(f"处理文件时发生错误: {e}")
st.exception(e)
- 使用 try-except 块处理可能出错的操作
- 为用户提供清晰的错误信息
- 使用
st.form()确保操作的原子性
实践项目
项目目标
通过构建一个销售数据分析仪表板,综合运用前面学到的 Streamlit 知识,包括数据加载、可视化、交互控件和布局设计。
项目需求
- 从 CSV 文件加载销售数据
- 提供数据筛选功能(时间范围、产品类别)
- 展示关键业务指标(销售额、利润等)
- 创建交互式图表
- 响应式布局设计
- 数据导出功能
项目结构
sales-dashboard/
├── app.py # 主应用文件
├── data/ # 数据文件夹
│ └── sales_data.csv # 示例销售数据
├── requirements.txt # 依赖包
└── README.md # 项目说明
实现步骤
步骤1:设置项目环境
目标:创建项目结构和基本依赖
代码:
python
# requirements.txt
streamlit>=1.0.0
pandas>=1.0.0
numpy>=1.0.0
plotly>=5.0.0
matplotlib>=3.0.0
seaborn>=0.11.0
解释:
- 定义项目所需的 Python 包
- 使用版本号确保兼容性
- 包括数据分析和可视化库
步骤2:生成示例数据
目标:创建模拟的销售数据文件
代码:
python
# generate_data.py
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import random
# 生成日期范围
start_date = datetime(2024, 1, 1)
end_date = datetime(2024, 12, 31)
dates = pd.date_range(start=start_date, end=end_date, freq='D')
# 生成产品数据
products = [
'笔记本电脑', '智能手机', '平板电脑', '耳机',
'键盘', '鼠标', '显示器', '打印机'
]
categories = ['电子产品', '配件']
# 生成销售数据
data = []
for i in range(10000):
date = random.choice(dates)
product = random.choice(products)
category = '电子产品' if product in ['笔记本电脑', '智能手机', '平板电脑'] else '配件'
quantity = random.randint(1, 10)
unit_price = random.randint(100, 5000)
cost_price = unit_price * random.uniform(0.6, 0.9)
# 添加一些季节性因素
if date.month in [11, 12]: # 黑色星期五和圣诞节
unit_price *= random.uniform(0.8, 1.2)
data.append({
'日期': date,
'产品': product,
'类别': category,
'数量': quantity,
'单价': unit_price,
'成本价': cost_price,
'销售额': quantity * unit_price,
'成本': quantity * cost_price,
'利润': quantity * (unit_price - cost_price)
})
# 创建 DataFrame
df = pd.DataFrame(data)
# 保存到 CSV
df.to_csv('data/sales_data.csv', index=False, encoding='utf-8-sig')
print(f"生成了 {len(df)} 条销售记录")
解释:
- 生成一年的日期数据
- 创建多样化的产品和类别
- 添加价格和数量的随机变化
- 包含季节性销售模式
- 计算销售额、成本和利润
步骤3:创建主应用文件
目标:构建完整的销售数据分析仪表板
代码:
python
# app.py
import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
from datetime import datetime
import matplotlib.pyplot as plt
import seaborn as sns
# 页面配置
st.set_page_config(
page_title="销售数据分析仪表板",
page_icon="📊",
layout="wide",
initial_sidebar_state="expanded"
)
# 缓存数据加载
@st.cache_data
def load_data():
df = pd.read_csv('data/sales_data.csv')
# 转换日期列
df['日期'] = pd.to_datetime(df['日期'])
# 添加月份和年份列
df['月份'] = df['日期'].dt.month
df['年份'] = df['日期'].dt.year
df['季度'] = df['日期'].dt.quarter
return df
# 缓存计算指标
@st.cache_data
def calculate_metrics(df):
metrics = {
'总销售额': df['销售额'].sum(),
'总利润': df['利润'].sum(),
'总订单数': len(df),
'平均订单金额': df['销售额'].mean(),
'利润率': (df['利润'].sum() / df['销售额'].sum()) * 100
}
return metrics
# 主标题
st.title("📊 销售数据分析仪表板")
st.markdown("---")
# 加载数据
df = load_data()
metrics = calculate_metrics(df)
# 侧边栏 - 筛选器
st.sidebar.header("🔍 数据筛选")
# 日期范围筛选
st.sidebar.subheader("日期范围")
min_date = df['日期'].min().to_pydatetime()
max_date = df['日期'].max().to_pydatetime()
selected_dates = st.sidebar.date_input(
"选择日期范围",
[min_date, max_date],
min_value=min_date,
max_value=max_date
)
if len(selected_dates) == 2:
start_date, end_date = selected_dates
filtered_df = df[(df['日期'] >= pd.Timestamp(start_date)) &
(df['日期'] <= pd.Timestamp(end_date))]
else:
filtered_df = df
# 产品类别筛选
st.sidebar.subheader("产品类别")
categories = filtered_df['类别'].unique()
selected_categories = st.sidebar.multiselect(
"选择类别",
categories,
default=categories
)
filtered_df = filtered_df[filtered_df['类别'].isin(selected_categories)]
# 产品筛选
st.sidebar.subheader("产品")
products = filtered_df['产品'].unique()
selected_products = st.sidebar.multiselect(
"选择产品",
products,
default=products
)
if selected_products:
filtered_df = filtered_df[filtered_df['产品'].isin(selected_products)]
# 显示筛选结果
st.sidebar.metric(
"筛选后记录数",
len(filtered_df)
)
# 主要指标卡片
col1, col2, col3, col4, col5 = st.columns(5)
with col1:
st.metric("总销售额", f"¥{metrics['总销售额']:,.0f}",
delta=None, delta_color="normal")
with col2:
st.metric("总利润", f"¥{metrics['总利润']:,.0f}",
delta=None, delta_color="normal")
with col3:
st.metric("订单数", f"{metrics['总订单数']:,}",
delta=None, delta_color="normal")
with col4:
st.metric("平均订单金额", f"¥{metrics['平均订单金额']:,.0f}",
delta=None, delta_color="normal")
with col5:
st.metric("利润率", f"{metrics['利润率']:.1f}%",
delta=None, delta_color="normal")
st.markdown("---")
# 标签页
tab1, tab2, tab3, tab4 = st.tabs(["📈 趋势分析", "📊 产品分析", "🎯 地区分析", "📋 详细数据"])
# 标签页1:趋势分析
with tab1:
st.header("销售趋势分析")
# 销售趋势图
sales_by_month = filtered_df.groupby('月份')['销售额'].sum().reset_index()
fig_month = px.line(
sales_by_month,
x='月份',
y='销售额',
title='月度销售趋势',
markers=True
)
fig_month.update_layout(
xaxis_title='月份',
yaxis_title='销售额 (¥)',
hovermode='x unified'
)
st.plotly_chart(fig_month, use_container_width=True)
# 季度对比
col1, col2 = st.columns(2)
with col1:
sales_by_quarter = filtered_df.groupby('季度')['销售额'].sum().reset_index()
fig_quarter = px.bar(
sales_by_quarter,
x='季度',
y='销售额',
title='季度销售额对比',
color='季度',
color_discrete_sequence=px.colors.qualitative.Set3
)
st.plotly_chart(fig_quarter, use_container_width=True)
with col2:
# 利润趋势
profit_by_month = filtered_df.groupby('月份')['利润'].sum().reset_index()
fig_profit = px.line(
profit_by_month,
x='月份',
y='利润',
title='月度利润趋势',
markers=True,
color_discrete_sequence=['green']
)
st.plotly_chart(fig_profit, use_container_width=True)
# 下载趋势数据
csv = sales_by_month.to_csv(index=False)
st.download_button(
label="下载月度销售数据",
data=csv,
file_name='月度销售数据.csv',
mime='text/csv'
)
# 标签页2:产品分析
with tab2:
st.header("产品销售分析")
# 销售额排名
sales_by_product = filtered_df.groupby('产品')['销售额', '利润'].sum().sort_values('销售额', ascending=False)
# 产品销售额柱状图
fig_product = px.bar(
sales_by_product.reset_index(),
x='产品',
y='销售额',
title='产品销售额排名',
color='销售额',
color_continuous_scale='Blues'
)
fig_product.update_layout(
xaxis_title='产品',
yaxis_title='销售额 (¥)',
xaxis_tickangle=-45
)
st.plotly_chart(fig_product, use_container_width=True)
# 产品利润分析
fig_profit_product = px.bar(
sales_by_product.reset_index(),
x='产品',
y='利润',
title='产品利润排名',
color='利润',
color_continuous_scale='Greens'
)
fig_profit_product.update_layout(
xaxis_title='产品',
yaxis_title='利润 (¥)',
xaxis_tickangle=-45
)
st.plotly_chart(fig_profit_product, use_container_width=True)
# 产品类别分析
category_sales = filtered_df.groupby('类别')['销售额', '利润'].sum()
fig_category = px.pie(
category_sales.reset_index(),
values='销售额',
names='类别',
title='产品类别销售额占比',
hole=0.3
)
st.plotly_chart(fig_category, use_container_width=True)
# 标签页3:地区分析(模拟)
with tab3:
st.header("地区分析")
# 由于原始数据没有地区信息,我们添加模拟地区
# 模拟5个城市
cities = ['北京', '上海', '广州', '深圳', '杭州']
np.random.seed(42) # 确保每次生成相同的模拟数据
# 为每条记录分配城市
filtered_df['城市'] = np.random.choice(cities, size=len(filtered_df))
# 城市销售额
sales_by_city = filtered_df.groupby('城市')['销售额', '利润'].sum().sort_values('销售额', ascending=False)
# 城市销售额地图(使用模拟坐标)
city_coords = {
'北京': (39.9042, 116.4074),
'上海': (31.2304, 121.4737),
'广州': (23.1291, 113.2644),
'深圳': (22.5431, 114.0579),
'杭州': (30.2741, 120.1551)
}
# 创建地图数据
map_data = []
for city in sales_by_city.index:
lat, lon = city_coords[city]
map_data.append({
'city': city,
'lat': lat,
'lon': lon,
'sales': sales_by_city.loc[city, '销售额']
})
map_df = pd.DataFrame(map_data)
# 显示地图
st.map(
map_df,
latitude='lat',
longitude='lon',
size='sales',
zoom=4,
use_container_width=True
)
# 城市排名
st.subheader("城市销售额排名")
st.dataframe(sales_by_city.style.format({
'销售额': '¥{:,.0f}',
'利润': '¥{:,.0f}'
}))
# 标签页4:详细数据
with tab4:
st.header("详细销售数据")
# 数据展示选项
show_options = st.multiselect(
"显示列",
df.columns.tolist(),
default=['日期', '产品', '类别', '数量', '单价', '销售额', '利润']
)
# 数据表
if len(show_options) > 0:
st.dataframe(
filtered_df[show_options].sort_values('日期', ascending=False),
use_container_width=True,
hide_index=True
)
# 数据导出
st.subheader("导出数据")
export_format = st.selectbox(
"选择导出格式",
["CSV", "Excel"]
)
if st.button("导出数据"):
if export_format == "CSV":
csv = filtered_df.to_csv(index=False)
st.download_button(
label="下载 CSV 文件",
data=csv,
file_name='销售数据.csv',
mime='text/csv'
)
else:
# Excel 导出
from io import BytesIO
output = BytesIO()
with pd.ExcelWriter(output, engine='openpyxl') as writer:
filtered_df.to_excel(writer, index=False, sheet_name='销售数据')
excel_data = output.getvalue()
st.download_button(
label="下载 Excel 文件",
data=excel_data,
file_name='销售数据.xlsx',
mime='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
# 页脚
st.markdown("---")
st.markdown("💡 提示:使用侧边栏筛选器可以查看特定时间段或类别的数据")
解释:
- 使用缓存机制优化数据加载
- 实现多维度数据筛选(日期、类别、产品)
- 创建多个标签页组织不同分析维度
- 使用 Plotly 创建交互式图表
- 提供数据导出功能
- 响应式布局适配不同屏幕
完整代码
项目的完整代码包括:
- requirements.txt:定义依赖
- generate_data.py:生成模拟数据
- app.py:主应用文件
运行和测试
bash
# 创建项目目录
mkdir sales-dashboard
cd sales-dashboard
# 创建依赖文件
echo "streamlit>=1.0.0
pandas>=1.0.0
numpy>=1.0.0
plotly>=5.0.0
matplotlib>=3.0.0
seaborn>=0.11.0
openpyxl>=3.0.0" > requirements.txt
# 生成数据
python generate_data.py
# 运行应用
streamlit run app.py
扩展练习
挑战
- 添加用户认证功能,不同用户看到不同的数据
- 实现数据实时更新功能(连接数据库)
- 添加预警功能,当销售异常时发送通知
- 实现数据预测功能,使用机器学习模型预测未来销售
- 添加数据钻取功能,点击图表查看详细数据
进阶主题
主题1:自定义组件
什么时候需要:需要创建特殊的 UI 组件或集成第三方库。
深入理解 :
自定义组件允许你扩展 Streamlit 的功能,创建可重用的 UI 元素。你可以使用任何前端框架(React、Vue、Angular)来创建自定义组件。
高级示例:
python
import streamlit as st
import streamlit.components.v1 as components
# 创建自定义组件
def custom_slider(label, min_value, max_value, value):
# 使用 HTML 和 JavaScript 创建滑块
component_value = components.html(
f"""
<div id="slider-container">
<label>{label}</label>
<input type="range" min="{min_value}" max="{max_value}"
value="{value}" id="custom-slider">
<span id="slider-value">{value}</span>
</div>
<script>
const slider = document.getElementById('custom-slider');
const valueDisplay = document.getElementById('slider-value');
slider.addEventListener('input', function() {{
valueDisplay.textContent = this.value;
// 通过 Streamlit 的 setComponentValue 更新值
Streamlit.setComponentValue(this.value);
}});
</script>
<style>
#slider-container {{
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}}
#custom-slider {{
width: 100%;
}}
</style>
""",
height=100
)
return component_value
# 使用自定义组件
value = custom_slider("自定义滑块", 0, 100, 50)
st.write(f"选择的值: {value}")
性能考虑:
- 自定义组件会增加加载时间
- 需要处理组件和 Python 之间的数据传递
- 考虑组件的响应式设计
主题2:多页面应用
什么时候需要:应用需要多个独立页面或视图。
深入理解 :
Streamlit 1.28+ 支持多页面应用,你可以使用 st.navigation() 创建带有导航栏的多页面应用。
高级示例:
python
# app.py
import streamlit as st
import streamlit_shadcn as stc
# 创建页面路由
pages = {
"主页": [
st.Page("pages/home.py", title="主页", icon="🏠"),
st.Page("pages/about.py", title="关于", icon="ℹ️"),
],
"数据分析": [
st.Page("pages/dashboard.py", title="仪表板", icon="📊"),
st.Page("pages/reports.py", title="报告", icon="📋"),
],
"设置": [
st.Page("pages/settings.py", title="设置", icon="⚙️"),
],
}
# 创建导航
navigation = st.navigation(pages)
# 根据选择的页面导航
if navigation.title == "主页":
import pages.home as home
home.run()
elif navigation.title == "关于":
import pages.about as about
about.run()
# ... 其他页面
子页面示例(pages/home.py):
python
import streamlit as st
def run():
st.title("欢迎来到数据分析平台")
st.write("这是一个多页面应用的示例")
st.subheader("快速开始")
st.write("选择上方的导航栏开始探索")
st.subheader("最近活动")
recent_activities = [
"创建了销售仪表板",
"更新了数据源",
"修复了显示错误"
]
for activity in recent_activities:
st.write(f"- {activity}")
性能考虑:
- 每个页面都是独立的 Python 脚本
- 路由会影响应用加载速度
- 考虑使用缓存减少重复计算
主题3:性能优化
什么时候需要:应用加载慢或运行卡顿。
深入理解 :
对于大型数据集和复杂计算,需要优化性能以确保良好的用户体验。
高级示例:
python
import streamlit as st
import pandas as pd
import numpy as np
import time
from functools import lru_cache
# 使用 LRU 缓存
@st.cache_data(max_entries=10, ttl=3600)
def load_large_dataset(filename):
"""加载大型数据集"""
st.write("正在加载数据集...")
time.sleep(2) # 模拟加载时间
return pd.read_csv(filename)
# 使用性能分析
def profile_function(func):
"""性能装饰器"""
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
st.write(f"执行时间: {execution_time:.2f}秒")
return result
return wrapper
# 优化后的数据展示
@st.cache_data
def display_optimized_data(df, n_rows=1000):
"""优化的数据展示"""
# 使用采样减少显示的数据量
if len(df) > n_rows:
sample_df = df.sample(n=n_rows, random_state=42)
st.warning(f"数据量过大,仅显示 {n_rows} 条样本")
return sample_df
return df
# 虚拟化数据
@st.cache_data
def get_virtual_data(offset, limit, total):
"""虚拟化数据加载"""
# 模拟从数据库分页查询
data = []
for i in range(offset, min(offset + limit, total)):
data.append({
'id': i,
'value': np.random.rand(),
'category': f'Category_{i % 10}'
})
return pd.DataFrame(data)
# 使用
if st.button("显示数据"):
# 优化数据展示
df = load_large_dataset('large_data.csv')
display_df = display_optimized_data(df)
st.dataframe(display_df)
# 虚拟化示例
offset = st.number_input("偏移量", 0, 10000, 0)
limit = st.number_input("每页数量", 10, 1000, 100)
if st.button("获取数据"):
virtual_df = get_virtual_data(offset, limit, 10000)
st.dataframe(virtual_df)
性能考虑:
- 合理使用缓存,避免内存泄漏
- 使用采样减少数据传输量
- 分页加载大数据集
- 延迟加载非关键资源
- 使用异步操作提高响应性
学习资源
官方文档
- Streamlit 官方文档 - 完整的 API 参考、教程和示例
- API 参考 - 所有组件和函数的详细说明
- 组件目录 - 所有可用组件的列表