Python 数据处理工作流:marimo、PyCharm 与数据存储

经过较长时间的实践和调整,我们逐渐形成了一套相对成熟的 Python 数据处理流程。这套流程覆盖了数据读取、清洗、转换、分析、可视化以及结果保存等环节。

在开发环境方面,我主要使用 marimo 进行交互式数据分析,并以 PyCharm 作为补充,用于脚本开发、代码检查、项目重构和数据库操作。Python 环境和项目依赖则交由 uv 管理。

这套工作流并不追求使用尽可能多的工具。相反,它更关注如何让不同工具各司其职,并尽量保证分析过程清晰、可复现且易于维护。

开发环境

marimo 笔记本

相较于 Jupyter Notebook,我现在更喜欢使用 marimo 进行交互式数据分析。

marimo 是一个响应式 Python 笔记本。某个变量发生变化后,依赖它的单元格会自动更新,或者被标记为需要重新运行。这种执行方式使代码、程序状态和输出结果能够保持较好的一致性。

传统笔记本的一个常见问题是隐藏状态。我们可能修改了一段代码,却忘记重新运行相关单元格;也可能删除了变量定义,但旧变量仍然保留在内存中。此时,页面中显示的代码并不一定能够完整解释当前输出。

这类问题在短时间的数据探索中可能并不明显,但随着笔记本变长,或者项目需要在几个月后重新打开,排查成本就会明显增加。

marimo 根据变量依赖关系组织执行过程,在很大程度上减少了这种不确定性。它的笔记本还会保存为普通的 .py 文件,因此更适合使用 Git 进行版本管理,也更方便与其他 Python 工具配合。

严格命名带来的好处

marimo 对变量定义有相对严格的要求。一个全局变量通常不应在多个单元格中被重复定义,因此我们需要更加认真地命名变量。

在临时分析中,人们经常反复使用:

python 复制代码
df

而在 marimo 中,更适合根据处理阶段分别命名:

python 复制代码
raw_df
cleaned_df
summary_df

这可能使变量名变长,但也让变量的含义更加明确。

从短期看,严格命名增加了一点输入成本;从长期维护的角度看,这种成本通常是值得的。清晰的变量名能够帮助我们理解数据所处的处理阶段,避免意外覆盖,也能减少后续检查未知状态的时间。

使用 uv 管理项目

我推荐使用 uv 管理 Python 项目及其依赖。相比手动创建虚拟环境并逐个安装包,uv 更适合以项目为中心的工作方式。

一个简单的初始化过程如下:

bash 复制代码
uv init
uv add marimo pandas numpy scipy matplotlib
uv run marimo edit

其中:

  • pyproject.toml 用于描述项目及其依赖;
  • .venv 保存项目的虚拟环境;
  • uv.lock 记录解析后的准确依赖版本;
  • uv run 确保命令在对应项目环境中运行。

使用下面的命令即可启动 marimo:

bash 复制代码
uv run marimo edit

也可以直接打开指定笔记本:

bash 复制代码
uv run marimo edit analysis.py

uv run 会在运行命令前检查项目环境是否与依赖声明保持同步。这种方式减少了手动激活环境和维护依赖版本的负担,也让项目更容易在另一台计算机上重新构建。

marimo 的基本使用方式与 Jupyter 并没有特别大的差异,依然由代码单元格、Markdown 和运行结果组成。主要区别在于,marimo 更强调变量依赖、确定性执行和可复现性。

具体可以参考:

PyCharm

PyCharm 是 JetBrains 开发的 Python 集成开发环境。在代码补全、静态检查、调试、项目导航、重构、测试和版本控制等方面,PyCharm 都具有比较成熟的支持。

从 PyCharm 2025.1 开始,原来的 Community Edition 和 Professional Edition 被整合为统一的 PyCharm 产品。核心功能可以免费使用,其中也包括 Jupyter Notebook 支持;部分高级功能仍需要 Pro 订阅。

我主要在两种情况下使用 PyCharm。

脚本与模块开发

当一段数据处理逻辑仍处于探索阶段时,把它放在 marimo 中通常比较方便。我们可以直接观察中间结果,并快速修改参数。

但当这段逻辑逐渐稳定,并开始被多个分析重复使用时,就更适合将其整理成独立函数或模块。例如:

text 复制代码
project/
├── notebooks/
├── src/
│   └── project_name/
│       ├── readers.py
│       ├── cleaning.py
│       ├── analysis.py
│       ├── plotting.py
│       └── storage.py
├── tests/
├── pyproject.toml
└── uv.lock

这种组织方式可以将数据读取、清洗、计算、绘图和保存分别放在不同模块中。

marimo 负责交互式分析和结果展示,PyCharm 则负责维护底层代码。二者不是相互替代的关系,而是分别承担探索和工程化开发的任务。

数据库操作

PyCharm 还可以用于连接和操作数据库。在支持相应功能的版本中,可以直接查看数据表、执行 SQL 查询、修改记录和管理表结构。

对于 SQLite 这类本地数据库,使用 Python 标准库中的 sqlite3 已经能够完成绝大部分操作。不过,在检查表结构、编写 SQL 查询或者查看少量数据时,图形化数据库界面往往更加直观。

需要注意的是,数据库工具的具体可用范围可能取决于当前 PyCharm 版本和订阅状态。统一版 PyCharm 的核心功能可以免费使用,但部分高级数据库功能可能仍属于 Pro 功能。

PyCharm 的官方说明可以参考:

数据读取与清洗

数据读取和清洗通常使用 Python 标准库、pathlib 和 pandas 完成。

对于大多数表格型数据,我倾向于先将其直接或间接转换为 pandas 的 DataFrame。DataFrame 提供了成熟的索引、筛选、类型转换、缺失值处理、分组和聚合能力,适合作为数据处理流程中的通用中间形式。

对于规则明确的文本文件,可以使用 Python 自带的 open()

python 复制代码
with open(path, encoding="utf-8") as file:
    content = file.read()

对于 CSV、Excel 等表格文件,则通常直接使用 pandas:

python 复制代码
import pandas as pd

df = pd.read_csv("data.csv")

或者:

python 复制代码
df = pd.read_excel("data.xlsx")

如果原始文件结构并不标准,可以先编写专门的读取函数,将不同来源的数据统一转换成 DataFrame:

python 复制代码
df = custom_reader(path)

这样做的好处是,后面的清洗和分析代码不需要了解原始文件的具体排版。无论数据来自文本文件、电子表格还是其他格式,只要读取函数最终返回结构一致的 DataFrame,后续流程就可以保持统一。

使用 Path 管理文件

在处理大量文件时,推荐使用 pathlib.Path,而不是通过字符串拼接路径。

例如:

python 复制代码
from pathlib import Path

data_folder = Path("data")

for file in data_folder.rglob("*.csv"):
    print(file.name)

Path 提供了一组直观的属性:

python 复制代码
file.name
file.stem
file.suffix
file.parent

它不仅让代码更容易阅读,也减少了 Windows、Linux 和 macOS 路径分隔符不同所带来的问题。

清洗不仅是删除缺失值

数据清洗经常被简单地理解为删除空值,但实际工作通常还包括:

  • 统一列名;
  • 去除多余空格;
  • 规范特殊字符;
  • 转换数据类型;
  • 检查重复记录;
  • 处理异常值;
  • 统一单位;
  • 检查排序和索引;
  • 记录数据来源;
  • 验证关键字段是否存在。

例如,可以统一处理列名:

python 复制代码
df = df.rename(
    columns=lambda column: (
        column.strip()
        .replace(" ", "_")
        .replace("/", "_")
    )
)

数值列则可以进行显式转换:

python 复制代码
df["value"] = pd.to_numeric(
    df["value"],
    errors="coerce",
)

清洗规则最好保持明确和稳定。如果某个规则会改变原始数据的含义,应当将其写入代码和文档,而不是依赖人工记忆。

同时,原始数据应尽量保持不变。比较稳妥的目录结构是:

text 复制代码
data/
├── raw/
├── interim/
└── processed/

其中:

  • raw 保存未经修改的原始文件;
  • interim 保存处理中间结果;
  • processed 保存最终结构化数据。

原始数据最好被视为只读输入。所有清洗和转换结果都应保存到新的位置,从而保证分析过程可以追溯和重新执行。

数据分析与数值计算

pandas

pandas 主要用于处理表格数据,适合:

  • 数据筛选;
  • 缺失值处理;
  • 分组聚合;
  • 多表合并;
  • 长宽表转换;
  • 时间序列处理;
  • 描述性统计。

DataFrame 的优势是直观、通用,并且与大量输入输出格式兼容。因此,即使最终数据不以 DataFrame 保存,它也非常适合作为读取和清洗阶段的中间表示。

NumPy

NumPy 提供了 Python 科学计算的基础数组结构,适合:

  • 向量化运算;
  • 数组索引;
  • 矩阵计算;
  • 数值变换;
  • 随机数生成;
  • 底层数据结构转换。

很多 pandas、SciPy、xarray 和 Matplotlib 操作实际上都建立在 NumPy 数组之上。

SciPy

SciPy 在 NumPy 的基础上提供了更完整的科学计算能力,包括:

  • 插值;
  • 优化;
  • 数值积分;
  • 曲线拟合;
  • 信号处理;
  • 统计分析;
  • 线性代数。

在数据分析中,如果任务已经超出简单的表格运算,通常可以先检查 SciPy 是否已经提供相应的标准实现。使用成熟函数通常比自行实现底层算法更加可靠,也更容易验证。

数据可视化

数据可视化既用于结果展示,也用于发现数据质量问题。

Matplotlib

对于静态绘图和学术图表,我主要使用 Matplotlib。它的优势在于控制能力强,可以精细调整:

  • 坐标轴;
  • 刻度;
  • 字体;
  • 图例;
  • 颜色;
  • 线型;
  • 子图布局;
  • 输出尺寸与分辨率。

Matplotlib 比较适合生成论文、报告和演示文稿中的最终图形。

随着项目增长,建议将重复的绘图逻辑封装成函数,避免不同分析使用不一致的样式。例如,可以统一设置字体、图片尺寸、颜色和导出格式。

Plotly

如果需要交互式图表,可以考虑使用 Plotly。它支持缩放、悬停提示、选择和动态图例,适合数据探索和网页展示。

Matplotlib 与 Plotly 的定位有所不同:

  • Matplotlib 更适合静态图和精细排版;
  • Plotly 更适合交互探索和在线展示。

在实际工作中,可以先用交互图检查数据,再使用 Matplotlib 制作最终图表。

数据存储

将处理后的数据保存为合适的结构化格式,是完整数据流程中非常重要的一步。

保存数据时,不仅要考虑"能否写入文件",还应考虑:

  • 是否保留数据类型;
  • 是否支持元数据;
  • 是否支持多维数据;
  • 是否方便查询;
  • 是否便于交换;
  • 是否适合长期维护;
  • 能否从保存结果重新生成分析。

不同存储格式解决的问题并不相同。

CSV

CSV 是最简单、最通用的表格格式之一。

它的优点包括:

  • 易于生成;
  • 易于人工查看;
  • 几乎所有数据软件都能读取;
  • 适合作为交换格式。

但 CSV 也存在明显限制:

  • 数据类型信息有限;
  • 不支持复杂层级;
  • 元数据表达能力弱;
  • 多表关系需要通过多个文件维护;
  • 单位和说明通常需要另外保存。

因此,CSV 很适合数据交换和简单结果导出,但不一定适合作为复杂项目唯一的长期存储格式。

SQLite

SQLite 是一种轻量级关系型数据库,不需要单独运行数据库服务器,数据库可以保存在单个文件中。

它适合保存:

  • 结构化记录;
  • 对象之间的关系;
  • 实验或业务元数据;
  • 文件索引;
  • 处理状态;
  • 汇总结果;
  • 需要按条件查询的数据。

SQLite 支持标准 SQL 查询,比大量分散的 CSV 文件更容易维护数据关系。例如,可以快速查找满足多个条件的记录,而不需要逐个读取文件。

不过,SQLite 更擅长管理行列式记录和关系数据。对于大型数组、多维数据或包含丰富坐标的数据,它未必是最自然的存储形式。

xarray 与 NetCDF

对于具有明确坐标、维度和多个变量的科学数据,xarray 是一个值得考虑的选择。

pandas 的 DataFrame 更强调行和列,而 xarray 可以更明确地表达:

  • 维度;
  • 坐标;
  • 数据变量;
  • 单位;
  • 属性;
  • 多维关系。

xarray 中的 Dataset 可以包含多个共享坐标的数据变量,并允许为数据和坐标附加元数据。

数据可以保存为 NetCDF:

python 复制代码
dataset.to_netcdf("processed_data.nc")

之后再读取:

python 复制代码
import xarray as xr

dataset = xr.open_dataset("processed_data.nc")

NetCDF 能够保留坐标、变量、维度和属性,因此比 CSV 更适合保存结构较丰富的科学数据。

如果数据具有层级结构,还可以使用 xarray 的 DataTree。DataTree 可以将多个 Dataset 组织成类似文件系统的树形结构,并保存到支持分组的 NetCDF 文件中。

需要注意的是,写入 NetCDF 的属性应尽量使用字符串、整数和浮点数等简单类型。复杂 Python 对象通常不适合直接序列化。

如何选择存储方式

不同格式并没有绝对的优劣,关键在于数据的性质和使用方式。

数据类型 推荐形式
简单二维表格 CSV、Parquet
需要频繁条件查询的结构化记录 SQLite
带坐标和元数据的多维数组 xarray + NetCDF
多组层级化科学数据 DataTree + NetCDF
临时交换和人工查看 CSV
大型分析型表格 Parquet

在较复杂的项目中,也可以组合使用不同格式。例如:

  • 使用 NetCDF 保存数组和坐标;
  • 使用 SQLite 保存记录、索引和关系;
  • 使用 CSV 导出需要共享的简单结果;
  • 使用 Parquet 保存较大的分析型表格。

没有必要强迫所有数据进入同一种格式。

从笔记本走向项目

交互式笔记本非常适合探索,但随着项目变大,将所有逻辑都放在笔记本中会逐渐产生维护压力。

一种实用的演进方式是:

  1. 在 marimo 中探索数据和验证思路;
  2. 将稳定的逻辑整理为函数;
  3. 在 PyCharm 中将函数拆分到独立模块;
  4. 为关键逻辑添加测试;
  5. 在 marimo 中导入这些模块并完成交互分析;
  6. 使用 uv 管理项目依赖和运行环境;
  7. 将原始数据、处理结果与代码分别管理。

这种方式保留了笔记本的灵活性,同时也获得了普通 Python 项目的可维护性。

可复现性比"能够运行"更重要

一段代码在当前计算机上成功运行,并不意味着它已经构成了可靠的数据工作流。

一个相对完整的流程还应回答:

  • 数据来自哪里?
  • 使用了哪些依赖版本?
  • 数据经过了哪些处理步骤?
  • 中间结果如何产生?
  • 参数是否被记录?
  • 原始数据是否保持不变?
  • 更换计算机后能否重新运行?
  • 几个月后是否还能理解代码?
  • 最终结果能否从保存的数据重新生成?

为了提高可复现性,我通常遵循以下原则:

  • 原始数据只读保存;
  • 使用项目环境隔离依赖;
  • pyproject.tomluv.lock 纳入版本控制;
  • 使用明确的变量名;
  • 将重复逻辑封装为函数;
  • 将稳定代码移出笔记本;
  • 保存必要的元数据和单位;
  • 在关键步骤增加输入验证;
  • 避免依赖手工执行顺序;
  • 尽量让结果可以从代码和原始数据重新生成。

工具之间的职责分工

目前,这套工作流中的工具大致承担以下职责:

类别 工具 主要用途
项目和依赖管理 uv 管理Python版本、依赖、虚拟环境和锁文件
交互式分析 marimo 数据探索、过程检查、交互分析和结果展示
工程化开发 PyCharm 脚本开发、调试、重构、测试和数据库操作
路径管理 pathlib 文件遍历和跨平台路径处理
表格数据处理 pandas 读取、清洗、转换、合并和统计
数值计算 NumPy、SciPy 数组运算、拟合、优化和科学计算
静态可视化 Matplotlib 学术绘图和精细排版
交互可视化 Plotly 数据探索和交互展示
多维数据管理 xarray 管理坐标、维度、变量和元数据
关系型数据管理 SQLite 保存结构化记录、关系和索引
表格交换 CSV、Parquet 数据交换和分析型表格存储
科学数据存储 NetCDF 保存多维数据、坐标与元数据

总结

这套 Python 数据处理工作流的重点并不是某一个具体工具,而是让不同工具承担适合自己的任务。

marimo 提供了响应式、可复现的交互分析环境;PyCharm 提供了成熟的代码开发、调试和重构能力;uv 让项目依赖和运行环境更加可控;pandas、NumPy 和 SciPy 构成了数据处理与数值计算的基础;Matplotlib 和 Plotly 分别服务于静态与交互式可视化;xarray、NetCDF 和 SQLite 则用于保存不同类型的结构化数据。

随着项目不断发展,工作流也会持续调整。但有几个目标始终不会改变:数据来源应当清楚,处理过程应当能够检查,运行环境应当可以重建,最终结果应当能够复现。

相比一次性得到某个结果,我更希望建立一套在较长时间后仍然能够理解、维护和继续使用的数据处理体系。

相关推荐
Java面试题总结1 小时前
IntelliJ IDEA 从卡顿到起飞,只用改这些。。。
java·ide·intellij-idea
z落落1 小时前
C# Task WaitAll、WaitAny、WhenAll、WhenAny
开发语言·c#
兰令水1 小时前
hot100【acm版】【2026.7.11/12打卡-java版本】
java·开发语言·数据结构·算法·职场和发展
从零开始的代码生活_1 小时前
C++ string 详解:常用接口、字符串算法与深拷贝实现
开发语言·c++·后端·学习·算法
m0_738120722 小时前
PHP代码审计基础——面向对象(四)
android·开发语言·网络·安全·github·php
2601_963771372 小时前
10 Best PHP Media CMS and Scripts for Web Creators in 2026
开发语言·前端·php
VIP_CQCRE2 小时前
用 Ace Data Cloud 快速接入 AI 视频生成:HappyHorse Videos API 实战指南
人工智能·python·api·ai视频生成·acedatacloud
阿里嘎多学长2 小时前
2026-07-09 GitHub 热点项目精选
开发语言·程序员·github·代码托管
带娃的IT创业者2 小时前
监控并非安全:当隐私成为技术的祭品
java·开发语言·安全·数据安全·监控·隐私保护·加密技术
玖玥拾3 小时前
C# 语言进阶(十四)Unity UI界面开发 + 网络消息联动
服务器·开发语言·网络·ui·unity·c#·游戏引擎