一步步教你如何使用 Streamlit 构建交互式数据应用

一步步教你如何使用 Streamlit 构建交互式数据应用

一步步教你如何使用 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 shortcode
  • layout:页面布局,”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

如果内容对您有帮助,欢迎打赏

您的支持是我继续创作的动力

前往打赏页面

评论区

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注