一步步教你如何使用 Streamlit 构建交互式数据应用
简介
Streamlit 是一个开源的 Python 库,它彻底改变了数据科学家和机器学习工程师构建交互式 Web 应用的方式。无需掌握 HTML、CSS 或 JavaScript 等前端技术,你只需要使用 Python 代码,就能在几分钟内创建出美观、实用的数据可视化应用。本文将带你从零开始,全面掌握 Streamlit 的使用方法。
目录
- 环境搭建
- 核心概念与基础组件
- 实战教程:从数据到交互式可视化
- 常用场景与案例
- 最佳实践与性能优化
- 部署与分享
第一部分:环境搭建
安装 Streamlit
在开始使用 Streamlit 之前,我们需要先搭建开发环境。推荐使用 Python 3.8 至 Python 3.12 版本。
通过 pip 安装
打开终端或命令行窗口,执行以下命令:
pip install streamlit
通过 conda 安装
如果你使用 Anaconda 或 Miniconda:
conda install -c conda-forge streamlit
验证安装
安装完成后,运行以下命令验证 Streamlit 是否正常工作:
streamlit hello
如果一切正常,你会看到 Streamlit 自带的示例应用在浏览器中打开。
创建第一个项目
让我们创建一个简单的 Streamlit 项目来感受它的魅力。
步骤一:创建项目目录
mkdir my-streamlit-app
cd my-streamlit-app
步骤二:创建应用文件
在项目目录中创建一个名为 app.py 的文件:
# my-streamlit-app/app.py
import streamlit as st
st.title("我的第一个 Streamlit 应用")
st.write("欢迎来到 Streamlit 的世界!")
步骤三:运行应用
在终端中执行:
streamlit run app.py
你应该能看到浏览器自动打开,显示你的第一个 Streamlit 应用。
开发环境推荐
使用 Visual Studio Code
VS Code 是 Streamlit 开发的绝佳选择,它提供了:
- Python 语法高亮和智能提示
- 集成终端
- 丰富的扩展生态
推荐安装的扩展包括:
- Python 扩展
- Pylance(提供类型检查)
- GitLens(版本控制)
使用 PyCharm
PyCharm 专业版同样支持 Streamlit 开发,你可以直接右键点击 Python 文件并选择 “Run ‘Streamlit'”
使用 Jupyter Notebook
Streamlit 还支持在 Jupyter Notebook 环境中使用,这在你需要交互式开发时非常方便:
import streamlit as st
第二部分:核心概念与基础组件
Streamlit 的工作原理
Streamlit 的核心理念非常简单:每次用户与页面交互时,脚本从顶部到底部重新执行。这种模式被称为”脚本模式”,它使得构建交互式应用变得异常简单。
理解这个概念非常重要:
- 你不需要编写回调函数来处理交互
- 状态会在脚本重新运行时自动更新
- 布局和组件按照代码顺序从上到下渲染
页面配置
设置页面标题和图标
import streamlit as st
st.set_page_config(
page_title="数据分析仪表板",
page_icon="📊",
layout="wide", # 可选:centered 或 wide
initial_sidebar_state="expanded" # 可选:collapsed 或 expanded
)
参数说明:
page_title:浏览器标签页上显示的标题page_icon:页面的图标,可以使用 emoji 或 emoji shortcodelayout:页面布局,”wide” 提供更宽的内容区域initial_sidebar_state:侧边栏的初始状态
文本显示组件
标题和文本
import streamlit as st
# 多种级别的标题
st.header("一级标题")
st.subheader("二级标题")
st.title("页面主标题")
st.text("普通文本内容")
# Markdown 支持
st.markdown("**粗体文本** 和 *斜体文本*")
st.markdown("- 列表项 1\n- 列表项 2\n- 列表项 3")
st.markdown("> 引用文本")
st.markdown("[链接文字](https://example.com)")
代码展示
# 带语法高亮的代码块
code = """
def hello_world():
print("Hello, Streamlit!")
return True
"""
st.code(code, language="python")
# 更简洁的写法
st.code("print('Hello, World!')", language="python")
LaTeX 数学公式
st.latex(r'''
\sum_{i=0}^\infty x_i
\int_0^\infty f(x) \, dx
''')
# 行内公式
st.latex(r"e^{i\pi} + 1 = 0")
数据展示组件
显示数据框
import streamlit as st
import pandas as pd
# 创建示例数据
data = {
"姓名": ["张三", "李四", "王五", "赵六"],
"年龄": [25, 30, 35, 28],
"城市": ["北京", "上海", "深圳", "广州"],
"薪资": [15000, 20000, 18000, 22000]
}
df = pd.DataFrame(data)
# 基本表格展示
st.table(df)
# 可交互的数据表格(支持排序、分页)
st.dataframe(df)
# 带有数字格式化的数据表格
st.dataframe(
df.style.format({
"年龄": "{:.0f}",
"薪资": "¥{:,.0f}"
})
)
显示指标
import streamlit as st
# 单个指标卡片
st.metric(label="总用户数", value="10,000", delta="+500")
# 带变化率的指标
st.metric(
label="月活跃用户",
value="5,234",
delta="12.5%",
delta_color="normal" # 可选:normal, positive, negative
)
# 多个指标并排显示
col1, col2, col3 = st.columns(3)
col1.metric("今日收入", "¥50,000", "¥2,000")
col2.metric("新注册用户", "1,234", "-50")
col3.metric("转化率", "3.2%", "0.1%")
显示图表
import streamlit as st
import pandas as pd
import numpy as np
# 生成示例数据
chart_data = pd.DataFrame(
np.random.randn(20, 3),
columns=["产品A", "产品B", "产品C"]
)
# 折线图
st.line_chart(chart_data)
# 面积图
st.area_chart(chart_data)
# 柱状图
st.bar_chart(chart_data)
# 使用 Altair 创建更复杂的图表
import altair as alt
chart = alt.Chart(chart_data.reset_index()).mark_circle().encode(
x='index:Q',
y='产品A:Q',
tooltip=['index', '产品A']
)
st.altair_chart(chart, use_container_width=True)
输入组件
按钮
import streamlit as st
# 简单的按钮
if st.button("点击我"):
st.success("按钮被点击了!")
else:
st.info("点击按钮看看效果")
# 带图标的按钮(Streamlit 0.84.0+)
st.button("🚀 启动", help="点击启动程序")
# 禁用状态的按钮
disabled = st.button("已禁用", disabled=True)
文本输入
import streamlit as st
# 单行文本输入
name = st.text_input("请输入你的名字", placeholder="张三")
st.write(f"你好,{name}!")
# 密码输入
password = st.text_input("请输入密码", type="password")
# 多行文本输入
message = st.text_area("请输入你的留言", height=150)
st.write(f"你输入的内容:{message}")
数字输入
import streamlit as st
# 整数输入
age = st.number_input("请输入年龄", min_value=0, max_value=120, value=25, step=1)
# 浮点数输入
height = st.number_input("请输入身高(cm)", min_value=50.0, max_value=250.0, value=170.0, step=0.5)
# 显示输入结果
st.write(f"年龄:{age} 岁,身高:{height} cm")
选择组件
import streamlit as st
# 下拉选择框
option = st.selectbox(
"请选择你喜欢的编程语言",
["Python", "Java", "JavaScript", "C++", "Go"]
)
st.write(f"你选择了:{option}")
# 多选框
options = st.multiselect(
"选择你感兴趣的领域",
["数据科学", "Web开发", "移动开发", "人工智能", "区块链"],
default=["数据科学", "人工智能"]
)
st.write(f"你感兴趣:{', '.join(options)}")
# 单选框组
genre = st.radio(
"你更喜欢哪种开发方式?",
("前端开发", "后端开发", "全栈开发"),
index=0
)
日期和时间选择
import streamlit as st
from datetime import datetime, time
# 日期选择
date = st.date_input("选择日期", datetime.now())
st.write(f"选择的日期:{date}")
# 时间选择
time_input = st.time_input("选择时间", time(12, 30))
st.write(f"选择的时间:{time_input}")
# 日期范围选择
date_range = st.date_input(
"选择日期范围",
[datetime(2024, 1, 1), datetime(2024, 12, 31)],
min_value=datetime(2020, 1, 1),
max_value=datetime(2030, 12, 31)
)
st.write(f"日期范围:{date_range}")
文件上传
import streamlit as st
# 上传单个文件
uploaded_file = st.file_uploader("选择一个文件")
if uploaded_file is not None:
st.write(f"文件名:{uploaded_file.name}")
st.write(f"文件大小:{uploaded_file.size} bytes")
# 读取文件内容
content = uploaded_file.getvalue()
st.write(f"文件内容(前100字节):{content[:100]}")
# 上传图片文件
image_file = st.file_uploader("上传一张图片", type=["jpg", "png", "jpeg"])
if image_file is not None:
st.image(image_file, caption="上传的图片", use_column_width=True)
# 上传 CSV 文件并直接读取为 DataFrame
csv_file = st.file_uploader("上传 CSV 文件", type=["csv"])
if csv_file is not None:
import pandas as pd
df = pd.read_csv(csv_file)
st.dataframe(df)
滑块
import streamlit as st
# 整数滑块
age = st.slider("选择年龄", 0, 100, 25)
st.write(f"选择的年龄:{age}")
# 浮点数滑块
price = st.slider(
"设置价格范围",
0.0, 1000.0, (100.0, 500.0), step=10.0
)
st.write(f"价格范围:{price}")
# 带刻度标记的滑块
volume = st.slider(
"音量",
min_value=0,
max_value=100,
value=50,
step=5,
format="%d%%"
)
复选框
import streamlit as st
# 单独的复选框
if st.checkbox("显示详细信息"):
st.write("这里是详细信息...")
# 使用复选框控制组件可见性
show_chart = st.checkbox("显示图表")
if show_chart:
# 生成并显示图表
import pandas as pd
import numpy as np
chart_data = pd.DataFrame(np.random.randn(20, 3), columns=["A", "B", "C"])
st.line_chart(chart_data)
布局组件
侧边栏
import streamlit as st
# 在侧边栏添加组件
st.sidebar.title("导航菜单")
st.sidebar.write("选择你需要的选项")
page = st.sidebar.selectbox(
"跳转到",
["首页", "数据分析", "机器学习", "设置"]
)
st.sidebar.slider("调整参数", 0, 100, 50)
# 主内容区域保持简洁
st.title(f"{page} 页面")
st.write("这是主内容区域")
列布局
import streamlit as st
# 两列布局
col1, col2 = st.columns(2)
with col1:
st.header("列 1")
st.write("这是第一列的内容")
st.button("按钮 1")
with col2:
st.header("列 2")
st.write("这是第二列的内容")
st.button("按钮 2")
# 三列布局(可以设置不同的宽度比例)
col1, col2, col3 = st.columns([1, 2, 1])
with col1:
st.write("窄列")
with col2:
st.write("宽列(占 2 份宽度)")
with col3:
st.write("窄列")
选项卡
import streamlit as st
# 创建选项卡
tab1, tab2, tab3 = st.tabs(["📊 数据概览", "📈 趋势分析", "🔍 详细数据"])
with tab1:
st.header("数据概览")
st.write("这里是数据的基本信息...")
# 显示摘要统计
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randn(100, 3), columns=["A", "B", "C"])
st.dataframe(df.describe())
with tab2:
st.header("趋势分析")
st.write("这里展示数据的趋势变化...")
# 显示折线图
st.line_chart(df.cumsum())
with tab3:
st.header("详细数据")
st.write("这里显示原始数据...")
st.dataframe(df)
折叠器(展开/收起)
import streamlit as st
# 简单的折叠器
with st.expander("点击展开查看详情"):
st.write("这里是隐藏在折叠器中的内容")
st.image("https://via.placeholder.com/300", width=300)
# 多个折叠器
with st.expander("数据处理"):
st.write("数据处理的相关内容...")
with st.expander("模型训练"):
st.write("模型训练的相关内容...")
with st.expander("结果分析"):
st.write("结果分析的相关内容...")
容器和占位符
import streamlit as st
import time
# 创建占位符用于动态更新
placeholder = st.empty()
# 模拟动态更新
for i in range(10):
placeholder.write(f"倒计时:{10 - i} 秒")
time.sleep(1)
placeholder.success("完成!")
# 使用容器分组元素
with st.container():
st.write("这是容器内的内容")
st.image("https://via.placeholder.com/200")
# 不带边框的容器
st.markdown("---")
媒体组件
显示图片
import streamlit as st
# 从 URL 显示图片
st.image("https://via.placeholder.com/400x200", caption="示例图片")
# 从本地文件显示图片
st.image("local_image.png", width=300)
# 使用 caption 和 use_container_width
st.image(
"image.jpg",
caption="调整宽度适应容器",
use_container_width=True
)
显示视频
import streamlit as st
# 从文件上传视频
video_file = st.file_uploader("上传视频文件", type=["mp4", "mov"])
if video_file is not None:
st.video(video_file)
# 从 URL 显示视频
st.video("https://example.com/video.mp4")
显示音频
import streamlit as st
# 从文件上传音频
audio_file = st.file_uploader("上传音频文件", type=["mp3", "wav"])
if audio_file is not None:
st.audio(audio_file, format="audio/mp3")
# 从 URL 播放音频
st.audio("https://example.com/audio.mp3")
显示 YouTube 视频
import streamlit as st
# 通过 YouTube 视频 ID 嵌入
st.video("https://www.youtube.com/watch?v=VIDEO_ID")
第三部分:实战教程
项目案例:构建数据分析仪表板
现在让我们通过一个完整的项目来巩固所学知识。我们将构建一个功能完整的数据分析仪表板。
项目概述
我们将创建一个包含以下功能的仪表板:
- CSV 文件上传和数据加载
- 数据概览和基本统计
- 交互式数据筛选
- 数据可视化(折线图、柱状图、散点图)
- 数据导出功能
完整代码实现
“`python
app.py
import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime
import io
============================================================
页面配置
============================================================
st.set_page_config(
page_title=”数据分析仪表板”,
page_icon=”📊”,
layout=”wide”,
initial_sidebar_state=”expanded”
)
============================================================
自定义样式
============================================================
st.markdown(“””
“””, unsafe_allow_html=True)
============================================================
侧边栏导航
============================================================
st.sidebar.markdown(“## 📊 导航菜单”)
st.sidebar.markdown(“—“)
功能选择
page = st.sidebar.radio(
“选择功能”,
[“📁 数据上传”, “📈 数据概览”, “🔍 数据筛选”, “📊 可视化分析”, “💾 数据导出”],
index=0
)
st.sidebar.markdown(“—“)
上传设置
st.sidebar.markdown(“### ⚙️ 显示设置”)
show_raw_data = st.sidebar.checkbox(“显示原始数据”, value=False)
chart_height = st.sidebar.slider(“图表高度”, 300, 800, 400)
st.sidebar.markdown(“—“)
st.sidebar.markdown(f”最后更新: {datetime.now().strftime(‘%Y-%m-%d %H:%M:%S’)}”)
============================================================
主内容区域
============================================================
if page == “📁 数据上传”:
# ============================================================
# 页面:数据上传
# ============================================================
st.markdown(‘
📁 数据上传
‘, unsafe_allow_html=True)
st.markdown("""
### 欢迎使用数据分析仪表板!
请上传你的 CSV 文件开始分析。支持的功能包括:
- ✅ 自动数据解析
- ✅ 支持多种编码格式
- ✅ 智能列类型检测
- ✅ 数据预览
""")
st.markdown("---")
# 文件上传
uploaded_file = st.file_uploader(
"选择 CSV 文件",
type=["csv"],
help="支持 UTF-8、GBK 等编码格式的 CSV 文件"
)
if uploaded_file is not None:
try:
# 尝试不同编码读取文件
encodings = ['utf-8', 'gbk', 'gb2312', 'latin1']
df = None
for encoding in encodings:
try:
df = pd.read_csv(uploaded_file, encoding=encoding)
st.success(f"✅ 文件读取成功!(使用 {encoding} 编码)")
break
except UnicodeDecodeError:
continue
if df is not None:
# 保存到 session state
st.session_state['df'] = df
st.session_state['filename'] = uploaded_file.name
# 显示文件信息
col1, col2, col3, col4 = st.columns(4)
col1.metric("📄 文件名", uploaded_file.name)
col2.metric("📏 数据行数", f"{len(df):,}")
col3.metric("📊 数据列数", df.shape[1])
col4.metric("💾 文件大小", f"{uploaded_file.size / 1024:.1f} KB")
# 数据预览
st.markdown("### 📋 数据预览(前10行)")
st.dataframe(df.head(10))
# 数据类型
st.markdown("### 🔤 列数据类型")
type_df = pd.DataFrame({
'列名': df.columns,
'数据类型': df.dtypes.values,
'非空数量': df.notna().sum().values,
'空值数量': df.isna().sum().values
})
st.dataframe(type_df)
else:
st.error("❌ 无法读取文件,请检查文件格式和编码")
except Exception as e:
st.error(f"❌ 读取文件时出错:{str(e)}")
else:
# 显示示例数据
st.info("👆 请上传 CSV 文件以开始分析")
# 提供示例数据下载
st.markdown("### 📝 没有数据?试试示例数据")
sample_data = pd.DataFrame({
'日期': pd.date_range('2024-01-01', periods=100),
'产品': np.random.choice(['产品A', '产品B', '产品C'], 100),
'销售额': np.random.randint(1000, 10000, 100),
'数量': np.random.randint(10, 100, 100),
'客户': np.random.choice(['客户甲', '客户乙', '客户丙', '客户丁'], 100),
'地区': np.random.choice(['北京', '上海', '深圳', '广州'], 100)
})
# 创建下载按钮
csv = sample_data.to_csv(index=False)
st.download_button(
label="📥 下载示例 CSV 文件",
data=csv,
file_name="sample_data.csv",
mime="text/csv"
)
st.markdown("#### 示例数据预览")
st.dataframe(sample_data.head(10))
elif page == “📈 数据概览”:
# ============================================================
# 页面:数据概览
# ============================================================
st.markdown(‘
📈 数据概览
‘, unsafe_allow_html=True)
if 'df' in st.session_state:
df = st.session_state['df']
# 基本统计
st.markdown("### 📊 基本统计信息")
# 数值列统计
numeric_cols = df.select_dtypes(include=[np.number]).columns
if len(numeric_cols) > 0:
col1, col2, col3 = st.columns(3)
for idx, col in enumerate(numeric_cols[:3]):
with [col1, col2, col3][idx]:
st.metric(
label=col,
value=f"{df[col].mean():.2f}",
delta=f"标准差: {df[col].std():.2f}"
)
# 详细统计表
st.markdown("### 📋 详细统计表")
if len(numeric_cols) > 0:
st.dataframe(
df[numeric_cols].describe().T.style.format("{:.2f}")
)
# 分类列统计
categorical_cols = df.select_dtypes(include=['object', 'category']).columns
if len(categorical_cols) > 0:
st.markdown("### 🏷️ 分类变量统计")
for col in categorical_cols:
st.markdown(f"**{col}**(共 {df[col].nunique()} 个唯一值)")
col_chart, col_table = st.columns([1, 1])
with col_chart:
value_counts = df[col].value_counts().head(10)
fig = px.bar(
value_counts,
x=value_counts.values,
y=value_counts.index,
orientation='h',
title="Top 10 分布"
)
fig.update_layout(height=300, showlegend=False)
st.plotly_chart(fig, use_container_width=True)
with col_table:
st.dataframe(value_counts.reset_index().rename(
columns={col: col, 'count': '数量'}
))
# 缺失值分析
st.markdown("### ❓ 缺失值分析")
missing = df.isnull().sum()
missing_pct = (missing / len(df) * 100).round(2)
missing_df = pd.DataFrame({
'列名': missing.index,
'缺失数量': missing.values,
'缺失比例(%)': missing_pct.values
})
missing_df = missing_df[missing_df['缺失数量'] > 0].sort_values(
'缺失数量', ascending=False
)
if len(missing_df) > 0:
st.dataframe(missing_df)
# 可视化缺失值
fig = px.bar(
missing_df,
x='列名',
y='缺失比例(%)',
color='缺失比例(%)',
title="各列缺失值比例",
color_continuous_scale='Reds'
)
fig.update_layout(height=400)
st.plotly_chart(fig, use_container_width=True)
else:
st.success("✅ 数据中没有缺失值!")
# 显示原始数据
if show_raw_data:
st.markdown("### 📄 原始数据")
st.dataframe(df)
else:
st.warning("⚠️ 请先在「数据上传」页面加载数据")
elif page == “🔍 数据筛选”:
# ============================================================
# 页面:数据筛选
# ============================================================
st.markdown(‘
🔍 数据筛选
‘, unsafe_allow_html=True)
if 'df' in st.session_state:
df = st.session_state['df']
st.markdown("### 🎯 筛选条件")
# 创建筛选容器
filter_container = st.container()
with filter_container:
col1, col2, col3 = st.columns(3)
# 数值列筛选
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
categorical_cols = df.select_dtypes(include=['object', 'category']).columns.tolist()
filters = {}
# 数值筛选
if len(numeric_cols) > 0:
with col1:
selected_num_col = st.selectbox(
"选择数值列",
numeric_cols
)
with col2:
min_val = float(df[selected_num_col].min())
max_val = float(df[selected_num_col].max())
range_filter = st.slider(
"选择范围",
min_val, max_val, (min_val, max_val)
)
filters[selected_num_col] = ('numeric', range_filter)
# 分类筛选
if len(categorical_cols) > 0:
with col3:
selected_cat_col = st.selectbox(
"选择分类列",
categorical_cols
)
unique_values = df[selected_cat_col].unique()
selected_values = st.multiselect(
"选择要包含的值",
unique_values.tolist(),
default=unique_values.tolist()[:3] if len(unique_values) > 3 else unique_values.tolist()
)
filters[selected_cat_col] = ('categorical', selected_values)
# 日期筛选(如果有日期列)
date_cols = df.select_dtypes(include=['datetime64']).columns.tolist()
if len(date_cols) > 0:
with st.expander("📅 日期筛选"):
selected_date_col = st.selectbox("选择日期列", date_cols)
min_date = df[selected_date_col].min()
max_date = df[selected_date_col].max()
date_range = st.date_input(
"选择日期范围",
[min_date, max_date]
)
if len(date_range) == 2:
filters[selected_date_col] = ('date', date_range)
# 应用筛选
st.markdown("---")
if st.button("🔍 应用筛选", type="primary"):
filtered_df = df.copy()
for col, (filter_type, value) in filters.items():
if filter_type == 'numeric':
filtered_df = filtered_df[
(filtered_df[col] >= value[0]) &
(filtered_df[col] <= value[1])
]
elif filter_type == 'categorical':
if len(value) > 0:
filtered_df = filtered_df[filtered_df[col].isin(value)]
elif filter_type == 'date':
if len(value) == 2:
filtered_df = filtered_df[
(filtered_df[col] >= pd.Timestamp(value[0])) &
(filtered_df[col] <= pd.Timestamp(value[1]))
]
# 显示结果
st.markdown(f"### 📊 筛选结果(共 {len(filtered_df)} 条记录)")
# 对比统计
col1, col2 = st.columns(2)
col1.metric("原始数据", f"{len(df):,} 条")
col2.metric("筛选后", f"{len(filtered_df):,} 条",
delta=f"-{len(df) - len(filtered_df)} 条")
st.dataframe(filtered_df)
# 保存筛选结果到 session state
st.session_state['filtered_df'] = filtered_df
else:
st.warning("⚠️ 请先在「数据上传」页面加载数据")
elif page == “📊 可视化分析”:
# ============================================================
# 页面:可视化分析
# ============================================================
st.markdown(‘
📊 可视化分析
‘, unsafe_allow_html=True)
if 'df' in st.session_state:
df = st.session_state['df']
# 使用筛选后的数据(如果有)
if 'filtered_df' in st.session_state:
df = st.session_state['filtered_df']
st.info(f"📌 正在显示筛选后的数据(共 {len(df)} 条记录)")
# 可视化类型选择
chart_type = st.selectbox(
"选择可视化类型",
["折线图", "柱状图", "散点图", "饼图", "热力图", "箱线图"]
)
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
categorical_cols = df.select_dtypes(include=['object', 'category']).columns.tolist()
# 根据图表类型显示不同的选项
if chart_type == "折线图":
st.markdown("### 📈 折线图配置")
col1, col2 = st.columns(2)
with col1:
x_col = st.selectbox("选择 X 轴(建议选择时间或序号列)", df.columns)
with col2:
y_cols = st.multiselect(
"选择 Y 轴(选择数值列)",
numeric_cols,
default=numeric_cols[:2] if len(numeric_cols) >= 2 else numeric_cols
)
if len(y_cols) > 0:
# 创建折线图
fig = px.line(
df,
x=x_col,
y=y_cols,
title="数据趋势",
markers=True
)
fig.update_layout(height=chart_height)
st.plotly_chart(fig, use_container_width=True)
# 添加趋势分析
if len(y_cols) == 1 and pd.api.types.is_numeric_dtype(df[x_col]):
st.markdown("### 📉 趋势分析")
col1, col2, col3 = st.columns(3)
with col1:
current = df[y_cols[0]].iloc[-1]
first = df[y_cols[0]].iloc[0]
change = ((current - first) / first * 100) if first != 0 else 0
st.metric(
f"{y_cols[0]} 变化",
f"{current:.2f}",
f"{change:+.2f}%"
)
with col2:
st.metric("最大值", f"{df[y_cols[0]].max():.2f}")
with col3:
st.metric("最小值", f"{df[y_cols[0]].min():.2f}")
elif chart_type == "柱状图":
st.markdown("### 📊 柱状图配置")
col1, col2 = st.columns(2)
with col1:
category_col = st.selectbox("选择分类列", categorical_cols if categorical_cols else df.columns)
with col2:
value_col = st.selectbox("选择数值列", numeric_cols)
aggregation = st.selectbox(
"聚合方式",
["求和", "平均值", "计数", "最大值", "最小值"]
)
agg_map = {
"求和": "sum",
"平均值": "mean",
"计数": "count",
"最大值": "max",
"最小值": "min"
}
# 聚合数据
agg_data = df.groupby(category_col)[value_col].agg(agg_map[aggregation]).reset_index()
agg_data.columns = [category_col, f"{value_col}_{aggregation}"]
# 创建柱状图
fig = px.bar(
agg_data,
x=category_col,
y=f"{value_col}_{aggregation}",
color=category_col,
title=f"各{category_col}的{value_col}{aggregation}统计"
)
fig.update_layout(height=chart_height, showlegend=False)
st.plotly_chart(fig, use_container_width=True)
# 显示数据表格
st.markdown("### 📋 统计明细")
st.dataframe(agg_data.sort_values(f"{value_col}_{aggregation}", ascending=False))
elif chart_type == "散点图":
st.markdown("### 🔵 散点图配置")
col1, col2, col3 = st.columns(3
Project: https://github.com/rasbt/LLMs-from-scratch
Stars: 94948
评论区