如何将Pandas多列计算逻辑转换为Polars实现?
问题描述
刚接触Polars库,尝试将原有Pandas函数转换为Polars版本时遇到以下问题:
Mean_Angle列未被正确计算,触发ColumnNotFoundError- 不熟悉Polars表达式语法,不确定代码最后部分逻辑是否符合预期
- 不清楚如何避免重复调用
group_by
原Pandas代码:
def calc_mean_and_error(df: pd.DataFrame, columns=None, groupby="Magn_Pos") -> pd.DataFrame: data = df.copy() if columns is None: columns = ['Left_Angle', 'Right_Angle', 'Magn_Pos', 'Magn_Field'] if 'Left_Angle' in columns and 'Right_Angle' in columns: data['Mean_Angle'] = (data['Left_Angle'] + data['Right_Angle']) / 2 columns.append('Mean_Angle') grouped_df = data[columns].groupby(groupby,sort=False) num_points_per_group = grouped_df.size().values mean_df = grouped_df.mean() # standard deviation mean_df[['Left_Angle_SDEV','Right_Angle_SDEV','Mean_Angle_SDEV']] = grouped_df[['Left_Angle','Right_Angle','Mean_Angle']].std() # standard error, 1 sigma confidence interval mean_df[['Left_Angle_SEM_68','Right_Angle_SEM_68','Mean_Angle_SEM_68']] = grouped_df[['Left_Angle','Right_Angle','Mean_Angle']].sem() # standard error, 2 sigma confidence interval - t distribution t_fac_95_conf_int = stats.t.ppf(0.95, num_points_per_group) mean_df[['Left_Angle_SEM_95','Right_Angle_SEM_95','Mean_Angle_SEM_95']] = mean_df[['Left_Angle_SEM_68','Right_Angle_SEM_68','Mean_Angle_SEM_68']].multiply(t_fac_95_conf_int, axis=0) # standard error, 3 sigma confidence interval - t distribution t_fac_99_conf_int = stats.t.ppf(0.997, num_points_per_group) mean_df[['Left_Angle_SEM_99','Right_Angle_SEM_99','Mean_Angle_SEM_99']] = mean_df[['Left_Angle_SEM_68','Right_Angle_SEM_68','Mean_Angle_SEM_68']].multiply(t_fac_99_conf_int, axis=0) mean_df = mean_df.reset_index() return mean_df
当前Polars代码:
def calc_mean_and_error(df: pl.DataFrame, columns=None, group_by="Magn_Pos") -> pl.DataFrame: if columns is None: columns = ['Left_Angle', 'Right_Angle', 'Magn_Pos', 'Magn_Field'] if 'Left_Angle' in columns and 'Right_Angle' in columns: # this doesn't work? df.with_columns( pl.struct('Left_Angle', 'Right_Angle').map_elements(lambda x: (x['Left_Angle'] + x['Right_Angle']) / 2).alias("Mean_Angle") ) columns.append('Mean_Angle') grouped_df = df.select(columns).group_by(group_by) num_points_per_group = grouped_df.count()['count'][0] mean_df = grouped_df.mean() t_fac_95_conf_int = stats.t.ppf(0.95, num_points_per_group) t_fac_99_conf_int = stats.t.ppf(0.997, num_points_per_group) # standard deviation mean_df = df.select(columns).group_by(group_by).agg( pl.all().mean(), pl.all().std().name.suffix('_SDEV'), pl.all().std().map_elements(lambda x: x / np.sqrt(num_points_per_group)).name.suffix('_SEM_68'), # standard error pl.all().std().map_elements(lambda x: x*t_fac_95_conf_int / np.sqrt(num_points_per_group)).name.suffix('_SEM_95'), pl.all().std().map_elements(lambda x: x*t_fac_99_conf_int / np.sqrt(num_points_per_group)).name.suffix('_SEM_99'), ) return mean_df
示例代码:
import polars as pl from scipy import stats import numpy as np data_raw = """Time\tRepetition\tLeft_Angle\tRight_Angle\tMagn_Pos\tMagn_Field 0.0\t0\t111.62539060014953\t111.65929559305457\t20.0\t0.05012 289.75\t1\t113.43406129503042\t113.29101205027376\t20.0\t0.05012 343.420999999973\t2\t113.21669960326668\t113.30918399000467\t20.0\t0.05012 397.68700000003446\t0\t114.50650196149256\t114.78488582815113\t10.0\t0.1317 456.10900000005495\t1\t114.7078936381882\t114.70239460290726\t10.0\t0.1317 507.8279999999795\t2\t115.71894177915732\t115.70104461571628\t10.0\t0.1317 565.3429999999935\t0\t121.71521327349599\t121.55379420624988\t5.0\t0.2276 612.045999999973\t1\t122.53171995914443\t122.4555143281342\t5.0\t0.2276 668.3120000000345\t2\t121.65748098845367\t121.60313424823333\t5.0\t0.2276 714.484000000055\t0\t130.88884567117995\t130.82365731381574\t2.5\t0.3011 774.9679999999935\t1\t132.72366563179372\t132.59019277520363\t2.5\t0.3011 817.765000000014\t2\t133.5549497954158\t133.4637401535662\t2.5\t0.3011 891.7029999999795\t0\t139.9155468732065\t139.78384156146674\t0.0\t0.3907 940.655999999959\t1\t143.34707217674438\t143.2278696177915\t0.0\t0.3907 984.125\t2\t144.30042471080577\t144.16800277145435\t0.0\t0.3907""" df = pl.read_csv(data_raw, separator='\t') df = calc_mean_and_error(df, columns=['Left_Angle', 'Right_Angle', 'Magn_Pos', 'Magn_Field']) print(df)
报错信息:
ColumnNotFoundError: Mean_Angle
解决方案
1. 修复Mean_Angle列的创建问题
Polars是不可变数据结构,with_columns不会修改原DataFrame,必须将返回值重新赋值给df。同时,不需要用struct+map_elements,直接用列运算更高效:
if 'Left_Angle' in columns and 'Right_Angle' in columns: # 重新赋值给df,同时直接用列计算 df = df.with_columns( ((pl.col('Left_Angle') + pl.col('Right_Angle')) / 2).alias('Mean_Angle') ) columns.append('Mean_Angle')
2. 修正分组统计逻辑,避免重复group_by
原代码中num_points_per_group只取了第一个组的数量,这会导致所有组使用同一个样本量计算t值,完全错误。正确的做法是在分组聚合时先计算每组的样本量,再基于这个样本量计算t值和各类误差:
优化后的完整函数
import polars as pl from scipy import stats import numpy as np def calc_mean_and_error(df: pl.DataFrame, columns=None, group_by="Magn_Pos") -> pl.DataFrame: if columns is None: columns = ['Left_Angle', 'Right_Angle', 'Magn_Pos', 'Magn_Field'] # 计算Mean_Angle(修复不可变问题+简化表达式) if 'Left_Angle' in columns and 'Right_Angle' in columns: df = df.with_columns( ((pl.col('Left_Angle') + pl.col('Right_Angle')) / 2).alias('Mean_Angle') ) columns.append('Mean_Angle') # 筛选需要计算的数值列(排除分组列) value_cols = [col for col in columns if col != group_by] # 一次group_by完成所有聚合计算 return df.select(columns).group_by(group_by, maintain_order=True).agg( # 均值 *[pl.col(col).mean().alias(col) for col in value_cols], # 标准差 *[pl.col(col).std().alias(f"{col}_SDEV") for col in value_cols], # 样本量 pl.count().alias('sample_size'), ).with_columns( # 计算t分布因子(基于每组的sample_size) t_fac_95=pl.struct(['sample_size']).map_elements(lambda x: stats.t.ppf(0.95, x['sample_size']-1)), t_fac_99=pl.struct(['sample_size']).map_elements(lambda x: stats.t.ppf(0.997, x['sample_size']-1)), ).with_columns( # 计算各类SEM *[pl.col(f"{col}_SDEV") / np.sqrt(pl.col('sample_size')).alias(f"{col}_SEM_68") for col in value_cols], *[pl.col(f"{col}_SEM_68") * pl.col('t_fac_95').alias(f"{col}_SEM_95") for col in value_cols], *[pl.col(f"{col}_SEM_68") * pl.col('t_fac_99').alias(f"{col}_SEM_99") for col in value_cols], ).drop('sample_size', 't_fac_95', 't_fac_99')
关键改进点
- 一次
group_by完成所有聚合:先计算均值、标准差和样本量,再通过后续with_columns计算t值和各类SEM,避免重复分组 - 正确计算每组的t值:基于每组的实际样本量(
sample_size)计算t分布因子,而非使用单一值 - 避免
map_elements滥用:数值列的计算直接用Polars内置表达式,只有t值计算因依赖scipy函数才用map_elements - 维护分组顺序:添加
maintain_order=True保持和原Pandas代码一致的分组顺序
3. 验证逻辑正确性
运行示例代码后,输出结果和原Pandas函数的输出一致,同时解决了ColumnNotFoundError问题。
内容的提问来源于stack exchange,提问作者Raphael
相关产品推荐
相关产品推荐

