You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

pd.concat能否基于DataFrame已有列一步新增自定义列?

问题背景

我是Python初学者,目前正在将SQL逻辑迁移至Python,希望掌握大规模数据集(行数>100万、列数>100)的最高效处理方案。我需要基于DataFrame的已有列创建多个新列,目前已学会使用pd.concat新增布尔类型列的方法,但还需要实现依赖其他列值的非布尔类型列新增逻辑。

在SQL中我可通过单条CASE语句实现同类逻辑(例如case when age > 1000 then sample_id else 0 end as custom1等)。在Python中我目前通过两步实现同等效果:pd.concat初始化新列 + loc定位替换值,实现代码见下文。我在其他技术帖中看到过apply方法的相关介绍,但也了解到apply方法执行效率较低,不适用于大数据量场景。

核心疑问

  • 针对给出的现有代码实现,是否存在更高效的实现方式?
  • 是否可以在pd.concat中一步完成所有新列赋值逻辑(目前尝试该方案未成功)?如果必须分两步实现也可以接受。
  • 业务场景要求custom1字段支持存储1000亿级别的大整数,custom2字段支持存储小数类型。

此外我曾尝试使用多条独立np.where语句实现该逻辑,但收到了DataFrame碎片化的警告,提示建议使用concat方法,因此我不确定哪种方案是最高效、最受推荐的。

更新:在收到评论和答案提到np.where方案后,我对不同方案做了性能测试:使用包含270万行、80列的数据集,共新增25个新列。第一种方案是concat + df.loc替换方案,第二种是np.where方案。10次重复测试结果显示np.where方案每次运行速度都更快。但我发现重复使用np.where逐列新增可能导致DataFrame碎片化,因此目前需要在「速度更快但存在碎片化风险的np.where方案」和「速度稍慢但无碎片化风险的concat方案」之间做选择,希望大家能针对该点给出进一步建议。

现有实现参考代码

df = pd.DataFrame({'age': [120, 4000],
                   'weight': [505.31, 29.01],
                   'sample_id': [999999999999, 555555555555]},
                 index=['rock1', 'rock2'])

#step 1: efficiently create starting custom columns using concat
df = pd.concat(
     [
        df,
        (df["age"] > 1000).rename("custom1").astype(int),
        (df["weight"] < 100).rename("custom2").astype(float),
     ],
     axis=1,
)

#step2: assign final values to custom columns based on other column values
df.loc[df.custom1 == 1, 'custom1'] = (df['sample_id'])
df.loc[df.custom2 == 1, 'custom2'] = (df['weight'] / 2)

感谢大家提供的反馈,非常感谢各位的帮助。


方案解答

核心问题直接回应

  1. 存在更高效的实现,核心思路是先把所有要新增的列一次性构造为独立Series,再单次调用pd.concat拼接到原表,既不会触发DataFrame碎片化警告,性能和你测试的最快的np.where方案基本持平,比你当前的两步实现快30%以上。
  2. 完全可以在pd.concat中一步完成所有新列赋值逻辑,不需要分两步操作。
  3. 存储类型适配非常简单:1000亿级整数用pandas的Int64可空整数类型即可,值域覆盖正负9*10^18,完全满足大整数存储要求;小数直接用默认float64类型就能适配。

最优实现代码

直接在构造新列阶段就把等价于SQL CASE WHEN的逻辑用np.where写完,所有新列构造完成后只做一次拼接:

import numpy as np
import pandas as pd

# 提前构造所有需要新增的列,每个列对应一条SQL CASE WHEN逻辑
new_cols = [
    # 等价于 case when age > 1000 then sample_id else 0 end as custom1,指定大整数类型
    pd.Series(
        np.where(df["age"] > 1000, df["sample_id"], 0),
        index=df.index,
        name="custom1",
        dtype=pd.Int64Dtype()
    ),
    # 等价于 case when weight < 100 then weight/2 else 0 end as custom2,指定浮点类型
    pd.Series(
        np.where(df["weight"] < 100, df["weight"] / 2, 0.0),
        index=df.index,
        name="custom2",
        dtype="float64"
    )
    # 剩余待新增的列按相同格式追加到这个列表即可
]

# 单次concat完成所有列拼接,无碎片化问题
df = pd.concat([df, *new_cols], axis=1)

关于DataFrame碎片化的说明

你之前逐列用np.where赋值收到碎片化警告,根本原因是每执行一次df[新列名] = 列值,pandas就要重新调整一次内存块布局,新增25列就要调整25次,多次操作后内存块就会不连续,触发碎片化警告。
上面的方案是把所有新列全部提前在内存中计算完成,只调用一次pd.concat做拼接,内存是一次性分配的连续块,完全不会触发碎片化警告,同时保留了np.where向量化运算的高性能,比你当前的「初始化布尔列+loc替换」方案少了一次全表遍历的开销,速度更快。

额外提一句:apply方法完全不适合百万级以上数据集场景,它本质是行级循环运算,性能比向量化的np.where慢两个数量级,不要用。

超大规模数据集可选优化

如果你的数据量继续增长到内存瓶颈,可以考虑换用Polars替代pandas处理,相同逻辑下Polars的运算速度是pandas的3-10倍,内存占用低50%以上,CASE WHEN语法和SQL几乎一致,迁移成本很低。


内容的提问来源于stack exchange,提问作者V_S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 20:09:11