我们从数据结构(Series/DataFrame)起步,跨越了I/O读写、清洗、分析、绘图的重重关卡。今天,作为这一阶段的收官之作,我们将攻克数据分析的“最后一块高地”——性能优化!
当数据量从“几千行”膨胀到“几百万行”,或者内存从“够用”变成“爆满”时,优秀的性能优化技巧能让你的代码从“龟速爬行”变成“猎豹冲刺”。DataFrame的“瘦身”计划:数据类型优化
在 Pandas 中,数据类型(dtype)直接决定了内存占用和计算速度。默认情况下,Pandas 往往会分配比实际需要更大的空间(例如用 64位 整数存储很小的数字)。🔹 核心原理
int64 vs int32:将整数类型降级,内存可减半。object vs category:将重复率高的字符串转换为类别类型,内存可压缩 90% 以上!
🛠️ 实战演示
import pandas as pdimport gc# 1. 构造一个“臃肿”的大数据框(模拟 100万 行数据)data = { 'A': [1, 2, 3, 4, 5] * 200000, # 大量小整数 'B': [10.5, 20.5, 30.5, 40.5, 50.5] * 200000, # 大量浮点数 'C': ['Category1', 'Category2', 'Category3'] * 333334 # 大量重复字符串}df = pd.DataFrame(data)print("🔹 原始数据占用内存:")print(df.memory_usage(deep=True) / 1024**2, "MB")# deep=True 关键:包含字符串等对象占用的真实内存
优化方案
# 2. 数据类型转换(瘦身开始!)# 整数列:int64 -> int32df['A'] = df['A'].astype('int32')# 浮点列:float64 -> float32df['B'] = df['B'].astype('float32')# 字符串列:object -> categorydf['C'] = df['C'].astype('category')print("\n🔹 优化后数据占用内存:")print(df.memory_usage(deep=True) / 1024**2, "MB")
💡 效果:对于包含大量重复字符串的列,category 类型通常能节省 50%-90% 的内存!内存管理:及时“断舍离”
# 1. 删除不再需要的列del df['C'] # 假设列 C 处理完了,不再需要# 2. 强制垃圾回收(清理碎片)gc.collect()print("\n 删除列并回收垃圾后的内存:")print(df.memory_usage(deep=True) / 1024**2, "MB")
驾驭大数据:分块与并行
当数据量大到连 Pandas 都读不进来(MemoryError)时,我们需要改变策略。🔹 策略一:分块读取(Chunking)
就像吃大饼,一口一口吃。利用 chunksize 参数,Pandas 会返回一个迭代器。# 假设存在一个超大的 CSV 文件# pd.read_csv 加上 chunksize 参数后,不会一次性读入内存,而是返回一个迭代器chunk_size = 100000 # 每次读取 10万 行chunk_results = []print("🔄 开始分块读取处理...")for chunk in pd.read_csv('large_data.csv', chunksize=chunk_size): # 在内存中处理每一块 # 例如:只保留大于 100 的行 processed_chunk = chunk[chunk['value'] > 100] # 将处理后的块存入列表(或其他文件) chunk_results.append(processed_chunk)# 最后合并结果final_df = pd.concat(chunk_results)
🔹 策略二:Dask(并行计算利器)
Dask 是一个灵活的并行计算库,API 与 Pandas 高度相似,但支持多核和集群计算。import dask.dataframe as dd# Dask 的读取方式与 Pandas 几乎一样,但它默认是“延迟计算”的ddf = dd.read_csv('large_data.csv')# 进行计算(例如求均值)# 注意:Dask 需要调用 .compute() 才会真正执行并返回结果mean_value = ddf['value'].mean().compute()print(f"计算结果: {mean_value}")
💻 综合实战:全流程优化流水线
import pandas as pdimport numpy as npimport gcprint("🏁 开始性能优化综合实战...")# 1. 创建测试数据df = pd.DataFrame({ 'ID': np.arange(1000000), 'Score': np.random.randint(50, 100, 1000000), 'Group': np.random.choice(['A', 'B', 'C'], 1000000)})# 2. 内存优化前original_mem = df.memory_usage(deep=True).sum() / 1024**2print(f"🔹 优化前内存: {original_mem:.2f} MB")# 3. 优化数据类型df['Score'] = df['Score'].astype('int8') # 50-100 的范围,int8 绰绰有余df['Group'] = df['Group'].astype('category')# 4. 内存优化后optimized_mem = df.memory_usage(deep=True).sum() / 1024**2print(f"🔹 优化后内存: {optimized_mem:.2f} MB")print(f"📉 节省内存: {((original_mem - optimized_mem) / original_mem * 100):.1f}%")# 5. 清理中间变量del dfgc.collect()print("🧹 垃圾回收完成,内存已释放!")
🖥️ 预期输出:控制台将展示优化前后的内存对比,对于特定数值范围(如 50-100),使用 int8 能带来极致的压缩效果。性能优化避坑
痛点 | 解决方案 |
|---|
内存计算不准 | 务必使用 df.memory_usage(deep=True),否则无法统计 object 类型的真实占用 |
类型转换报错 | 降级类型前检查数据范围(如 df.max()),防止溢出(如负数溢出) |
分块处理逻辑混乱 | 确保每一块的处理逻辑是独立的,最后再 concat 或 append 到文件 |
Dask 不执行 | 忘记加 .compute() 会导致程序什么都没干就结束了(Dask 是延迟执行的) |
【一起学Python】每天进步一点点,365天后遇见更优秀的自己!
👉 关注公众号,不错过每天的进阶内容!
🎯 百日金句:“性能优化是代码的雕琢,也是思维的升华;在数据的浩瀚中,高效与优雅并行。” ✨