将 Pandas 换为交互式表格的 Python 库
你是不是也有过这种感觉:本地写个脚本,用 Pandas 把数据一读一算,自己看着很爽;结果产品、运营一过来,“能不能给我一个能点一点、筛一筛的界面啊?” 然后你只好一顿 print(df.head()) 截图发给别人,来回问一句改一次 SQL,非常崩溃。
我这两年基本把“看数据”这件事,慢慢从“Pandas 打印表格”换成“交互式表格”了,其实不复杂,就是把原来的 DataFrame 换个展示方式而已。
下面就按“聊天”的节奏,带你把这个事情理一理,顺手上几段可以直接抄走用的 Python 代码。
Pandas 真心好用,这个不用夸了,随便写几行:
import pandas as pddf = pd.read_csv("orders.csv")
print(df.head())
print(df.describe())
控制台 / Notebook 里啪一下就出来一大坨表格,看统计也方便。
但问题也很明显:
看的人只能“看”,不能点表头排序、不能在某列上直接过滤 想改一两行数据试试效果,要么改源文件,要么写代码改,再重新跑 想给不写代码的人用,只能截图、导出 Excel、再发来发去
所以很多时候,你脑子里真正想要的是:有个表格界面,能点、能筛、能编辑,底层还是 DataFrame 在跑就行。
于是就轮到各种“交互式表格库”登场了。
我习惯按使用场景来选库,大概分三种:Notebook 里玩、写 Web 小应用、写桌面/内部工具。每类挑一两个说,不搞库大杂烩。
1)Jupyter 里直接变成可点可改的表格:qgrid / mitosheet 这一挂
如果你平时就是在 Jupyter / JupyterLab 里写代码,想要的是“DataFrame 还能点一点”,最顺滑的是这种基于 widget 的库。
举个 qgrid 的简单例子(它本质上还是操作 Pandas 的 df):
import pandas as pd
import qgriddf = pd.read_csv("orders.csv")
# 创建一个可交互的表格组件
grid = qgrid.show_grid(
df,
show_toolbar=True, # 上面有一排筛选、导出的小工具
editable=True# 允许在表格里直接改值
)
grid
在 Notebook 里跑完这段,grid 就是一张能点击表头排序、能像 Excel 一样改数据、还能多条件过滤的表格。 你改完之后,还可以把改过的数据拿回来:
df_edited = grid.get_changed_df()
这样你原来所有基于 df 的分析、画图代码,几乎不用改,只要把数据源换成 df_edited 就行。
如果你更偏爱“Excel 一样的感觉”,可以试试类似 mitosheet 这样的东西,直接在 Notebook 里拉出一个类似表格编辑器的界面,背后帮你生成 Pandas 代码。这个就比较适合数据分析同学,写代码少一点,效果也不错。
2)给别人用的 Web 小工具:Streamlit 这一类
当你想把脚本变成“同事也能点”的小页面,Streamlit 这种库就很香。它的 st.dataframe / st.data_editor 天然就是交互式表格。
先看最小可用版本:
# streamlit_app.py
import streamlit as st
import pandas as pddf = pd.read_csv("orders.csv")
st.title("订单数据小看板")
# 可交互表格:支持排序、拖动列宽、局部滚动之类
st.dataframe(df)
# 新版本可以用 data_editor,表格里直接编辑
edited_df = st.data_editor(df, num_rows="dynamic")
st.write("你刚刚编辑后的前 5 行:")
st.dataframe(edited_df.head())
然后命令行跑:
streamlit run streamlit_app.py
浏览器里就能看到一个带表格的小页面,别人只要能访问你这台机器/服务器,就能上来点点看看。
稍微认真一点,你可以加一些筛选条件,让“交互式表格”真正参与业务逻辑,而不只是展示:
import streamlit as st
import pandas as pddf = pd.read_csv("orders.csv")
st.title("订单分析小工具")
# 左侧筛选栏
with st.sidebar:
st.header("筛选条件")
city = st.multiselect(
"城市",
options=sorted(df["city"].dropna().unique()),
default=[]
)
min_amount = st.number_input("最小金额", value=0.0)
max_amount = st.number_input("最大金额", value=float(df["amount"].max()))
# 根据筛选条件过滤 DataFrame
filtered = df.copy()
if city:
filtered = filtered[filtered["city"].isin(city)]
filtered = filtered[
(filtered["amount"] >= min_amount) &
(filtered["amount"] <= max_amount)
]
st.subheader("筛选后的结果")
st.dataframe(filtered)
st.subheader("简单统计")
st.write(filtered.describe())
这一套下来,原来“我发你一份 Excel,你自己筛一筛”的活,就变成“一句链接,你自己点一点击”的体验了,而且所有逻辑都还是你熟悉的 Pandas。
3)对交互要求更复杂:Dash / Bokeh 的 DataTable
有时候产品同学想要的东西比较狠:分页、冻结列、大量列宽调节、复杂条件格式,那就得上稍微重型一点的东西,比如 Dash 的 DataTable。
简化版的 Dash 示例:
from dash import Dash, dash_table, html
import pandas as pddf = pd.read_csv("orders.csv")
app = Dash(__name__)
app.layout = html.Div([
html.H1("订单数据交互表格"),
dash_table.DataTable(
id="table",
columns=[{"name": c, "id": c} for c in df.columns],
data=df.to_dict("records"),
page_size=20, # 分页
filter_action="native", # 表头条件过滤
sort_action="native", # 排序
editable=True# 允许编辑
)
])
if __name__ == "__main__":
app.run(debug=True)
这个写法跟普通 Web 开发更像一点,适合你本来就有个 Dash / Flask / Django 项目,要在里头塞一个“富交互表格组件”的场景。
Bokeh 里也有 DataTable,写法类似,就不一一展开了。
从“print(df.head())”到“交互式小工具”:一个完整迁移过程
说归说,很多人卡在“我现在有一个几百行的 Pandas 脚本,怎么不太痛苦地改成交互式的?”
给你看一个真实一点的改造思路,场景假设是这样:
原来你有个
report.py,每天跑一下,把当天订单的数据算一算,输出一些统计。
原始版大概长这样:
# report.py
import pandas as pddf = pd.read_sql("SELECT * FROM orders WHERE created_at >= CURDATE()", conn)
print("总订单数:", len(df))
print("各城市总金额:")
print(df.groupby("city")["amount"].sum().sort_values(ascending=False))
print("大额订单(> 1000):")
print(df[df["amount"] > 1000].head())
你自己看没问题,但是别人根本用不上。
1. 把入口从“脚本执行”变成“Web 页面”
直接套一层 Streamlit 外壳,把 print 换成 st.write / st.dataframe:
# report_app.py
import streamlit as st
import pandas as pd
from sqlalchemy import create_engineengine = create_engine("mysql+pymysql://user:pwd@host/db")
st.title("每日订单报告")
@st.cache_data(ttl=600)
defload_data():
sql = "SELECT * FROM orders WHERE created_at >= CURDATE()"
return pd.read_sql(sql, engine)
df = load_data()
st.write("总订单数:", len(df))
city_amount = df.groupby("city")["amount"].sum().sort_values(ascending=False)
st.write("各城市总金额:")
st.dataframe(city_amount.reset_index())
big_orders = df[df["amount"] > 1000]
st.write("大额订单(> 1000):")
st.dataframe(big_orders)
这里 @st.cache_data 是为了避免每次刷新都查数据库,用缓存顶一会儿。
2. 再加一点交互:让别人自己调阈值、按时间过滤
你稍微再动一动,把“> 1000”这种硬编码丢到侧边栏去:
import streamlit as st
import pandas as pd
from sqlalchemy import create_engineengine = create_engine("mysql+pymysql://user:pwd@host/db")
st.title("每日订单报告")
with st.sidebar:
st.header("筛选条件")
min_amount = st.number_input("大额订单阈值", value=1000.0, step=100.0)
days = st.slider("最近几天", min_value=1, max_value=30, value=1)
@st.cache_data(ttl=600)
defload_data(days: int):
sql = f"""
SELECT * FROM orders
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL {days} DAY)
"""
return pd.read_sql(sql, engine)
df = load_data(days)
st.write("总订单数:", len(df))
city_amount = df.groupby("city")["amount"].sum().sort_values(ascending=False)
st.subheader("各城市总金额")
st.dataframe(city_amount.reset_index())
big_orders = df[df["amount"] > min_amount]
st.subheader(f"大额订单(> {min_amount})")
st.dataframe(big_orders)
你看,整套逻辑还是用 Pandas 在算,只不过“表格怎么给人看”从 print 变成了交互式组件,加几个控件就有了一个能给业务方玩半天的小工具。