Pandas 和 NumPy 的设计核心就是向量化操作——对整个数组或 Series 执行操作,而不是逐个元素循环。向量化操作在底层使用 C 语言循环,速度远超 Python 级别的循环。
for i in range(len(df)):
df.loc[i, 'new'] = df.loc[i, 'a'] + df.loc[i, 'b']
df['new'] = df['a'] + df['b']
几乎所有的数学运算、比较、字符串操作都可以向量化。
import pandas as pd
import numpy as np
df = pd.DataFrame({'a': np.random.randn(1000000), 'b': np.random.randn(1000000)})
# 向量化加法
df['c'] = df['a'] + df['b']
# 向量化条件
df['d'] = np.where(df['a'] > 0, 1, 0)
# 向量化字符串(需要 .str 访问器,仍比循环快)
df['e'] = df['a'].astype(str).str[:5]
apply 虽然比显式循环快,但仍是 Python 级别的循环,对性能有影响。尽可能用内置聚合函数或向量化方法替代。
# 慢:用 apply 计算两列最大值
df['max'] = df.apply(lambda row: max(row['a'], row['b']), axis=1)
# 快:直接用 numpy 或 pandas 方法
df['max'] = np.maximum(df['a'], df['b'])
Pandas 默认使用 int64 和 float64,但对于数值范围小的列,可以降级为 int8/16/32 或 float32,大幅节省内存。对于重复值多的字符串列,使用 category 类型。
# 查看当前内存
print(df.memory_usage(deep=True))
# 转换数据类型
df['small_int'] = df['small_int'].astype('int8')
df['float_col'] = df['float_col'].astype('float32')
df['category_col'] = df['category_col'].astype('category')
# 对于数值列,可以使用 pd.to_numeric 并 downcast
df['col'] = pd.to_numeric(df['col'], downcast='integer') # 或 'float'
读取大文件时,指定数据类型、只读必要列、分块读取可以显著减少内存和加快速度。
# 指定 dtypes 和 usecols
dtypes = {'user_id': 'int32', 'age': 'int8', 'gender': 'category'}
df = pd.read_csv('large_file.csv', usecols=['user_id', 'age', 'gender'], dtype=dtypes)
# 分块读取
chunk_iter = pd.read_csv('huge_file.csv', chunksize=100000)
for chunk in chunk_iter:
process(chunk) # 逐块处理
join 参数控制连接方式,避免不必要的数据复制。# 先筛选再合并
filtered = df1[df1['date'] > '2024-01-01']
result = filtered.merge(df2, on='key')
pd.eval() 和 df.query() 使用 numexpr 引擎,对大型 DataFrame 的复杂表达式求值比纯 Python 快。
# 使用 query 筛选
df_filtered = df.query('a > 0 and b < 0')
# 使用 eval 创建新列
df.eval('c = a + b * 2', inplace=True)
链式索引(如 df[df.a>0]['b'])不仅可能产生视图/副本问题,而且通常较慢。应使用 .loc 进行赋值和选择。
# 慢且危险
df[df.a > 0]['b'] = 1
# 快且安全
df.loc[df.a > 0, 'b'] = 1
inplace=True 可以避免复制,但很多函数(如 drop())在 inplace 时并不会节省内存,因为内部仍需复制。推荐使用赋值方式,更清晰。
# 通常不推荐 inplace
df.drop('col', axis=1, inplace=True)
# 推荐赋值
df = df.drop('col', axis=1)
对于无法向量化的复杂操作,可以考虑使用并行库加速,例如:
swifter:自动判断是否向量化,否则并行 applypandarallel:简单的并行 applydask:分布式计算,处理超大数据集# 安装 pip install swifter
import swifter
df['new'] = df.swifter.apply(lambda x: complex_func(x), axis=1)
在优化前,先用工具找出瓶颈。
%timeit(Jupyter 中)测试单行代码。df.info() 和 df.memory_usage() 查看内存。line_profiler 分析函数逐行耗时。pandas_profiling 生成数据报告。# 在 Jupyter 中
%timeit df['a'] + df['b']
import pandas as pd
import numpy as np
# 生成 100 万行数据
df = pd.DataFrame({
'group': np.random.choice(['A','B','C','D'], 1_000_000),
'value1': np.random.randn(1_000_000),
'value2': np.random.randn(1_000_000),
'flag': np.random.randint(0, 2, 1_000_000)
})
# 慢方法:循环
def slow_way(df):
result = []
for i in range(len(df)):
if df.loc[i, 'flag'] == 1:
result.append(df.loc[i, 'value1'] + df.loc[i, 'value2'])
else:
result.append(df.loc[i, 'value1'])
df['result'] = result
# 较快:apply
def apply_way(df):
df['result'] = df.apply(lambda row: row['value1'] + row['value2'] if row['flag'] == 1 else row['value1'], axis=1)
# 最快:向量化
def vectorized_way(df):
df['result'] = np.where(df['flag'] == 1, df['value1'] + df['value2'], df['value1'])
# 测试性能(Jupyter 中)
# %timeit slow_way(df.copy()) # ~ 几秒
# %timeit apply_way(df.copy()) # ~ 几百毫秒
# %timeit vectorized_way(df.copy()) # ~ 几毫秒