Python 处理 100行×75万列超宽数据字符串转数值的6种策略实测对比标签Python数据分析性能优化pandasnumpypolars一句话结论字符串→数值转换只做一次Polars在大规模下完胜结果缓存为Parquet(float32)后续读取快 8~700 倍。一、问题背景在测试/测量领域一次全面测试可能产生75万个测试项的测量数据。100次采样就是100行 × 75万列的超宽表。更棘手的是——仪器导出的原始数据是字符串格式必须转成数值才能计算、图等参数。这个数据形状非常极端指标值行列数100 行 × 750,000 列总单元格75,000,0007500万CSV文件大小577.9 MB字符串内存实测1065 MBfloat64 内存572 MBfloat32 内存286 MB直接用 pandas 的read_csv 逐列to_numeric转换在真实 75 万列规模下10 分钟都无法完成。这不是 pandas 不努力而是这种超宽字符串的场景已经远远超出了它的舒适区。本文用真实数据实测 6 种策略给出耗时、内存、代码和踩坑记录。二、测试环境项目配置系统macOS (Apple Silicon)Python3.13.12pandas2.xpolars1.xnumpy2.xpyarrow17数据文件raw_100x750000.csv577.9 MB三、6种策略实测策略 S0pandas 逐列转换基准最直觉的写法dfpd.read_csv(csv_path,dtypestr)# 全部读为字符串forcolindf.columns:# 75万列循环df[col]pd.to_numeric(df[col])# 逐列转换实测结果超时10 分钟未完成原因dtypestr时 7500 万单元格各持一个 Python str 对象for col in df.columns循环 75 万次pandas 每列调度开销巨大字符串 float 同时存在内存峰值不可控策略 S1分块读取 批量转换 Parquet 缓存思路每次只读 5000 列转完释放降低内存峰值。all_colspd.read_csv(csv_path,nrows0).columns.tolist()chunk_size5000chunks[]forstartinrange(0,len(all_cols),chunk_size):col_subsetall_cols[start:startchunk_size]df_chunkpd.read_csv(csv_path,usecolscol_subset,dtypestr)arr_floatdf_chunk.to_numpy(dtypestr).astype(np.float32)chunks.append(pd.DataFrame(arr_float,columnscol_subset))deldf_chunk gc.collect()df_finalpd.concat(chunks,axis1)df_final.to_parquet(converted.parquet)实测结果超时10 分钟未完成原因很意外每次pd.read_csv(usecols...)都要重新扫描整个 CSV 文件。75 万列 / 5000 列 150 次全文件读取 ≈86 GB 的 I/O。分块省内存的代价是 I/O 爆炸。教训对超宽 CSV 做列分块如果每次都要重读文件不如一次性读完。策略 S2Polars 一行搞定转换阶段最佳importpolarsaspl colspl.read_csv(csv_path,n_rows0).columns schema{c:pl.Utf8forcincols}df(pl.read_csv(csv_path,schema_overridesschema).with_columns(pl.all().cast(pl.Float32)))df.write_parquet(data.parquet)实测结果105.4 秒峰值堆内存 116 MB为什么 Polars 在大规模下反超 NumPy字符串用 Arrow 连续内存存储不是每个单元格一个 Python 对象类型转换用 Rust 表达式引擎并行执行7500 万个字符串在 Polars 里只占用 Arrow buffer内存极低这是 75 万列真实数据下最快的转换方案。策略 S3NumPy 直接操作小规模无敌大规模翻车withopen(csv_path,r)asf:f.readline()# 跳过表头linesf.readlines()str_arrnp.array([line.strip().split(,)forlineinlines],dtypestr)float_arrstr_arr.astype(np.float32)np.save(data.npy,float_arr)实测结果196.7 秒峰值堆内存 6646 MB6.4 GB在 1 万列的演示测试中NumPy 是最快的。但到了 75 万列[line.strip().split(,) for line in lines]会创建7500 万个 Python 字符串对象光是对象头就占数 GB 内存。内存暴增拖累了速度。策略 S4-S6转换后的读取方式转换完成后数据已缓存为 Parquet(float32) 或.npy。日常读取有三种选择# S4: 全量读 Parquetdfpd.read_parquet(data.parquet)# S5: 只读需要的列超宽表杀手锏sample_colsall_cols[::7500][:100]dfpd.read_parquet(data.parquet,columnssample_cols)# S6: memmap 零内存加载arrnp.load(data.npy,mmap_moder)col_meansarr.mean(axis0)读取方式耗时堆内存说明S4 全量读 Parquet48.6s363 MB需要全部列S5 列子集读 Parquet6.3s225 MB只读 100 列快 7.8 倍S6 memmap 加载0.105s295 MB数据在磁盘快 460 倍策略 S7数据类型内存对比实测 75 万列同一份数据的内存占用类型内存相对倍率字符串object1065 MB3.7xfloat64572 MB2.0xfloat32286 MB1.0x字符串 → float32减少 73%内存。测量数据dB 值、float32 精度完全够用。四、结果汇总策略耗时堆内存状态结论S0 pandas 逐列转换600s-超时 ❌75 万列不可行S1 pandas 分块读取600s-超时 ❌I/O 重复读取致命S2 Polars 一行转换105.4s116 MB✅转换阶段最佳S3 NumPy 批量转换196.7s6646 MB✅内存代价高S4 Parquet 全量读48.6s363 MB✅日常使用S5 Parquet 列子集读6.3s225 MB✅只读需要列S6 memmap 零内存0.105s295 MB✅最快加载五、推荐方案┌─────────────────────────────────────────────────────────────┐ │ 推荐处理流程 │ │ │ │ CSV(字符串) │ │ │ │ │ ▼ │ │ ┌──────────────┐ 首次转换只做一次 │ │ │ Polars cast │ ← 75万列下最快内存最低 │ │ │ Float32 │ │ │ └──────┬───────┘ │ │ │ │ │ ▼ │ │ ┌──────────────┐ │ │ │ Parquet(f32) │ ← 持久化缓存 │ │ └──────┬───────┘ │ │ │ │ │ ┌────┼────┬────────────┐ │ │ ▼ ▼ ▼ ▼ │ │ 全量 列子集 memmap 统计计算 │ │ 读取 读取 零内存 numpy向量化 │ └─────────────────────────────────────────────────────────────┘场景推荐方案优先级首次 CSV → 数值转换Polarscast(Float32)⭐⭐⭐⭐⭐转换后持久化Parquet(float32)⭐⭐⭐⭐⭐日常读取使用read_parquet(columns[...])⭐⭐⭐⭐⭐内存不足时numpy memmap.npy⭐⭐⭐⭐纯数值计算numpy ndarray⭐⭐⭐⭐必须使用 pandas API先转 Parquet再读 pandas⭐⭐⭐核心原则转换只做一次 → 缓存 Parquet(float32) → 后续零转换六、踩坑记录坑1pandasread_csv默认类型推断不指定dtypestr时pandas 会对 75 万列逐列推断类型耗时远超想象。坑2分块读取 CSV 导致 I/O 爆炸如果分块是每次read_csv(usecols...)重新读文件I/O 会按分块数翻倍。75 万列分 5000 列一块就是 150 次全文件扫描。坑3NumPy 在小规模和大规模表现相反1 万列时 NumPy 最快75 万列时反而被 Polars 反超原因是 Python str 对象的内存爆炸。坑4float64 浪费内存测量数据通常 0.01 dB 精度足够默认用 float32内存减半。坑5Parquet 列子集读取的列名问题不要用pd.read_parquet(parquet_path, columns[])去读列名。正确做法importpyarrow.parquetaspq all_colspq.read_schema(parquet_path).names七、总结维度最优策略原因转换速度Polars105.4sArrow 后端内存效率碾压转换内存Polars仅 116 MB比 NumPy 低 57 倍读取速度memmap0.1s 加载 75 万列实际场景Parquet 列子集只读需要的列跳过其余 74.99 万列内存不足memmap float32数据在磁盘堆内存≈0一行结论对于 100 行×75 万列的字符串型超宽数据用Polars 一次性转 float32 → 存 Parquet → 后续按需列子集读取是工程上最平衡、最省心的方案。如果纯追求读取速度或内存最小化就用numpy memmap。如果觉得有用点赞收藏关注三连~专注于//数据处理与工程优化