但凡用到科学计算,Pandas 几乎是绕不开的工具 —— 它以简洁的 API、灵活的数据操作能力成为数据处理的 “瑞士军刀”。但随着数据量增长(比如从 10 万行到 1000 万行),你可能会发现:原本流畅的代码突然变慢了,一个简单的apply操作要等好几分钟,读取大文件时进度条仿佛凝固了。这不是你的代码有问题,而是原生 Pandas 的 “单线程” 基因在多核时代遇到了瓶颈。
并行计算正是解决这个问题的核心方案。简单来说,它能让你的程序同时利用电脑的多个 CPU 核心(比如把一个大任务拆成 4 份,让 4 个核心同时处理),从而大幅提升效率。今天我们将梳理两个实用工具 ——swifter和modin.pandas,用通俗的语言解释原理,用可复现的代码展示效果,帮你快速掌握 Pandas 并行计算的关键技巧。
在解释并行工具前,我们得先明白:原生 Pandas 为什么处理大数据时会变慢?
核心原因是原生 Pandas 默认只用一个 CPU 核心。现代电脑(哪怕是普通笔记本)通常有 4 核、8 核甚至更多核心,但 Pandas 的大部分操作(比如apply、groupby、read_csv)只能 “排队” 用一个核心,其他核心都在 “摸鱼”。这就像 4 个人的活让 1 个人干,效率自然上不去。
举个直观的例子:如果用df.apply(lambda x: complex_calculation(x))处理 1000 万行数据,假设单条计算需要 1 微秒,单线程要 1000 万微秒(约 10 秒);如果能分给 4 个核心,理论上 3 秒内就能完成。
但并行计算不是 “银弹”—— 如果数据量很小(比如 1 万行以内),并行的 “任务拆分”“结果合并” 等额外开销可能比计算本身还大,这时用原生 Pandas 反而更快。所以并行计算的最佳场景是 “数据量大、计算逻辑复杂” 的任务。
apply是 Pandas 中最灵活但也最容易变慢的操作之一(尤其是自定义复杂函数时)。swifter的核心作用就是:自动判断是否需要并行,并把 apply 操作分配到多个核心上执行,而且几乎不用改你的原有代码。
swifter不会盲目并行,它会先做两件事:
如果函数适合并行,它会借助dask(一个并行计算框架)把数据分片,让多个核心同时处理;如果不适合,就自动退回原生 Pandas 的单线程模式。这种 “智能判断” 让它比手动并行更省心。
首先需要安装工具(建议用 conda 或 pip):
pip install swifter # 依赖pandas和dask,会自动安装
下面用一个例子对比:对 100 万行数据的 “数值列” 做复杂计算(比如求平方根后乘以随机数),分别用原生apply和swifter的swifter.apply。
import pandas as pd
import swifter
import numpy as np
import time
# 生成测试数据:100万行,1列数值
df = pd.DataFrame({
"value": np.random.rand(1000000) # 生成0-1之间的随机数
})
# 定义一个复杂计算函数(模拟实际场景中的自定义逻辑)
def complex_func(x):
# 这里模拟复杂操作:多次数学计算+条件判断
result = np.sqrt(x) * np.sin(x) + np.log(x + 1)
if result > 0.5:
return result * 2
else:
return result / 2
# 原生apply:记录时间
start = time.time()
df["result_pandas"] = df["value"].apply(complex_func)
time_pandas = time.time() - start
print(f"原生apply耗时:{time_pandas:.2f}秒")
# swifter.apply:记录时间
start = time.time()
df["result_swifter"] = df["value"].swifter.apply(complex_func)
time_swifter = time.time() - start
print(f"swifter.apply耗时:{time_swifter:.2f}秒")
# 验证结果是否一致(确保并行计算没出错)
print(f"结果是否一致:{df['result_pandas'].equals(df['result_swifter'])}")
在 8 核 CPU 的电脑上测试,原生 apply 可能需要 8-10 秒,而 swifter 通常能压缩到 2-3 秒(加速 3-5 倍),且结果完全一致。
如果你的需求不止于apply,而是希望所有 Pandas 操作(读取文件、groupby、merge 等)都能并行加速,那modin.pandas会更适合。它的设计理念是:用几乎和原生 Pandas 一样的 API,实现多核 / 分布式计算,你甚至不用改原来的代码,只需要换个导入方式。
原生 Pandas 把数据存在单个 “块” 里,所有操作都在这个块上串行执行;而 modin 会:
这种设计让 modin 在处理大文件(比如 10GB 以上 CSV)、复杂聚合(比如多层 groupby)时优势明显。
modin 支持多种计算引擎(默认用 Ray,也支持 Dask),先安装依赖:
pip install modin[ray] # 用Ray作为引擎,适合单机多核
# 如需分布式,可安装modin[dask]并配置Dask集群
下面用一个完整例子对比:读取 1GB CSV 文件,做分组聚合,对比原生 Pandas 和 modin 的速度。
# 注意:modin的API和pandas几乎完全一致,只是导入名不同
import pandas as pd # 原生pandas
import modin.pandas as mpd # modin.pandas
import time
# 假设存在一个1GB的CSV文件(可自行生成或用现有大文件)
file_path = "large_data.csv"
# 1. 对比读取文件速度
# 原生pandas读取
start = time.time()
df_pandas = pd.read_csv(file_path)
time_pandas_read = time.time() - start
print(f"原生pandas读取耗时:{time_pandas_read:.2f}秒")
# modin读取(API完全一样)
start = time.time()
df_modin = mpd.read_csv(file_path)
time_modin_read = time.time() - start
print(f"modin读取耗时:{time_modin_read:.2f}秒")
# 2. 对比groupby聚合速度
# 原生pandas聚合(按category列分组,求value列的均值)
start = time.time()
result_pandas = df_pandas.groupby("category")["value"].mean()
time_pandas_groupby = time.time() - start
print(f"原生pandas groupby耗时:{time_pandas_groupby:.2f}秒")
# modin聚合(代码完全一样)
start = time.time()
result_modin = df_modin.groupby("category")["value"].mean()
time_modin_groupby = time.time() - start
print(f"modin groupby耗时:{time_modin_groupby:.2f}秒")
# 验证结果是否一致(浮点数允许微小误差)
pd.testing.assert_series_equal(
result_pandas,
result_modin.to_pandas(), # modin结果可转原生pandas格式
check_exact=False,
atol=1e-6
)
print("聚合结果一致")
在 8 核 CPU 测试中,modin 读取 1GB CSV 的速度通常是原生 Pandas 的 2-3 倍,groupby 聚合则能快 3-4 倍。更重要的是:除了导入语句,其他代码和原生 Pandas 完全一样,几乎没有学习成本。
两个工具各有侧重,总结选择标准:
Pandas 并行计算的核心不是 “炫技”,而是用工具解决实际问题。swifter和modin的价值在于:在不重构代码的前提下,让多核 CPU “动起来”。
当然,并行计算不能解决所有问题 , 如果数据量超过单机内存,需要考虑分布式框架(如 Spark);如果计算逻辑有优化空间(比如用vectorize替代apply),先优化逻辑往往比并行更有效。但在 “数据量大、逻辑复杂、单机可处理” 的场景下,这两个工具会成为你的效率利器。总之在遇到 Pandas 代码变慢时,可以考虑先导入swifter或modin,也许几行代码就能让等待时间缩短一半以上。未完待续......