使用 Pandas 提高查询效率的多种方法
Pandas 是 Python 中强大的数据处理库,但在处理大数据集时可能会遇到性能问题。以下是提高 Pandas 查询效率的多种方法:
1. 选择合适的数据类型
# 将对象类型转换为更高效的类型
df['category_col'] = df['category_col'].astype('category')
df['int_col'] = df['int_col'].astype('int32') # 而不是默认的 int64
df['float_col'] = df['float_col'].astype('float32') # 而不是默认的 float64
# 使用日期时间类型
df['date_col'] = pd.to_datetime(df['date_col'])
2. 使用向量化操作
# 避免使用 apply() 和循环,使用向量化操作
# 慢
df['new_col'] = df['col1'].apply(lambda x: x * 2)
# 快
df['new_col'] = df['col1'] * 2
# 更复杂的向量化操作
df['result'] = np.where(df['col1'] > df['col2'], df['col1'], df['col2'])
3. 使用查询方法优化
# 使用 query() 方法进行条件筛选(语法更简洁,有时更快)
result = df.query('col1 > 100 & col2 < 50')
# 使用 eval() 进行表达式求值(对于复杂表达式更高效)
df.eval('result = col1 + col2 * col3', inplace=True)
# 使用 isin() 而不是多个 OR 条件
values = [1, 2, 3, 4, 5]
result = df[df['col'].isin(values)]
# 使用 between() 进行范围查询
result = df[df['col'].between(10, 100)]
4. 索引优化
# 为常用查询列设置索引
df.set_index('frequently_queried_column', inplace=True)
# 使用多级索引
df.set_index(['col1', 'col2'], inplace=True)
# 使用 sort_index() 提高索引查询速度
df.sort_index(inplace=True)
# 使用 loc 和 iloc 进行高效索引
result = df.loc[df.index > '2023-01-01']
5. 内存优化
# 检查内存使用情况
print(df.memory_usage(deep=True))
# 使用更节省内存的数据类型
df = df.astype({col: 'category' for col in df.select_dtypes(include='object').columns})
# 使用稀疏数据结构(对于包含大量零值的数据)
df = df.astype(pd.SparseDtype("float", 0))
6. 分批处理大数据集
# 使用 chunksize 分批读取大数据文件
chunk_size = 10000
for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
process_chunk(chunk)
# 或者手动分批处理
batch_size = 5000
for i in range(0, len(df), batch_size):
batch = df.iloc[i:i+batch_size]
process_batch(batch)
7. 使用更高效的数据存储格式
# 使用 Parquet 格式(列式存储,压缩率高)
df.to_parquet('data.parquet')
df = pd.read_parquet('data.parquet')
# 使用 Feather 格式(快速读写)
df.to_feather('data.feather')
df = pd.read_feather('data.feather')
# 使用 HDF5 格式(适合大型科学数据集)
df.to_hdf('data.h5', key='df', mode='w')
df = pd.read_hdf('data.h5', key='df')
8. 使用并行处理
# 使用 pandarallel(简化并行处理)
from pandarallel import pandarallel
pandarallel.initialize()
# 并行 apply
df['new_col'] = df['col'].parallel_apply(lambda x: process_value(x))
# 使用 Dask(处理比内存大的数据集)
import dask.dataframe as dd
ddf = dd.from_pandas(df, npartitions=4)
result = ddf[dd['col'] > 100].compute()
9. 避免常见性能陷阱
# 避免链式赋值(会创建副本)
# 不好
df[df['col'] > 100]['new_col'] = 1
# 好
df.loc[df['col'] > 100, 'new_col'] = 1
# 避免在循环中追加数据
# 不好
results = []
for i in range(1000):
results.append(process_data(i))
result_df = pd.DataFrame(results)
# 好
# 先处理所有数据,然后一次性创建 DataFrame
# 使用 copy() 谨慎(只有在必要时才创建副本)
df_copy = df.copy() # 这会创建完整副本,消耗内存
10. 使用内置的聚合函数
# 使用内置聚合函数而不是自定义函数
# 慢
df.groupby('group_col')['value_col'].apply(lambda x: x.sum())
# 快
df.groupby('group_col')['value_col'].sum()
# 使用 agg() 进行多个聚合
df.groupby('group_col').agg({
'col1': ['mean', 'min', 'max'],
'col2': 'sum'
})
11. 使用 Cython 或 Numba 加速
对于极其性能关键的代码部分,可以考虑使用 Cython 或 Numba 进行加速:
# 使用 Numba 加速数值计算
from numba import jit
@jit(nopython=True)
def calculate_value(x, y):
# 复杂的数值计算
return x * y + x**2 - y**2
df['result'] = calculate_value(df['col1'].values, df['col2'].values)
12. 监控和优化性能
# 使用 %timeit 魔术命令测试代码性能
%timeit df[df['col'] > 100]
# 使用 line_profiler 分析代码行级性能
# 安装: pip install line_profiler
# 使用: %load_ext line_profiler
# 然后: %lprun -f function_name function_call()
# 使用 memory_profiler 分析内存使用
# 安装: pip install memory_profiler
# 使用: %load_ext memory_profiler
# 然后: %mprun -f function_name function_call()
实际应用示例
结合您之前的代码,这里是一个优化后的版本:
def optimized_customer_flows(self, data_df):
# 填充缺失值并优化数据类型
data_df_fill = data_df.fillna(0)
# 优化数据类型
for col in data_df_fill.select_dtypes(include=['object']).columns:
data_df_fill[col] = data_df_fill[col].astype('category')
# 定义批次大小
batch_size = 3000
# 计算总批次数
total_rows = len(data_df_fill)
num_batches = (total_rows + batch_size - 1) // batch_size
print(f"客户流量总数据量: {total_rows}, 将分为 {num_batches} 批处理")
# 预先计算一些映射关系,避免在循环中重复计算
site_map = {site: column_target(self.get_site(site), site) for site in data_df_fill["来源地"].unique()}
product_map = {product: column_target(self.get_product(product), product) for product in data_df_fill["产品"].unique()}
customer_map = {customer: column_target(self.get_customer(customer), customer) for customer in data_df_fill["客户"].unique()}
period_map = {period: column_target(self.get_period(period), period) for period in pd.concat([data_df_fill["到达周期"], data_df_fill["发货周期"]]).unique()}
# 分批处理数据
for batch_num in range(num_batches):
# 计算当前批次的起始和结束索引
start_idx = batch_num * batch_size
end_idx = min((batch_num + 1) * batch_size, total_rows)
# 获取当前批次的数据
batch_df = data_df_fill.iloc[start_idx:end_idx]
# 使用列表推导式提高处理效率
flows_ls = [
{
"source": site_map[row["来源地"]],
"product": product_map[row["产品"]],
'customer': customer_map[row["客户"]],
"mode": column_target(str(ObjectId()), row["运输模式"]),
"arrivingPeriodName": period_map[row["到达周期"]],
"departingPeriodName": period_map[row["发货周期"]],
"arrivingPeriod": period_map[row["到达周期"]],
"departingPeriod": period_map[row["发货周期"]],
"flowUnits": compute_column(row["流量"]),
"flowWeight": compute_column(row["流量重量"]),
"flowCubic": compute_column(row["流量体积"]),
"serviceHours": compute_column(row["服务小时数"]),
"serviceDistance": compute_column(row["服务距离"]),
"transportationPolicyCost": compute_column(row["运输成本"]),
"inTransitInventory": compute_column(row["在途库存"]),
"inTransitInventoryHoldingCost": compute_column(row["在途库存持有成本"]),
"co2": 0,
"co2Cost": 0,
"totalCost": compute_column(row["总成本"]),
"totalTransportationCost": compute_column(row["运输总成本"]),
"outboundWarehousingPolicyCost": compute_column(0),
"sourcingPolicyCost": compute_column(0),
"dutyCost": compute_column(0),
"leadTimeCost": compute_column(0),
"totalSourcingCost": compute_column(1),
"totalOutboundWarehousingCost": compute_column(0),
**self.add_fields({}, self.get_scenario(row["场景"]), row["场景"])
}
for _, row in batch_df.iterrows()
]
# 写入当前批次数据
table_name = "output_customer_flows"
self.handle_data(table_name=table_name, data=flows_ls, conn=self.data.db)
print(f"已处理第 {batch_num + 1}/{num_batches} 批数据,本批处理 {len(flows_ls)} 条记录")
print(f"客户流量处理完成,总共处理了 {total_rows} 条记录")
这些方法可以根据您的具体数据和查询模式进行组合使用,以最大程度地提高 Pandas 的查询效率。
2790

被折叠的 条评论
为什么被折叠?



