python - 使用 numpy 和 numba 提高 groupby-transform 操作的性能
问题描述
我正在尝试创建一个函数,该函数返回多个浮点数据字段的 z 分数,这些数据由同一二维数组中的分类 str/object 数据分组(最初来自 parquet 文件,通过pandas.read_parquet
)。
我正在寻找与使用 pandas 的以下操作等效的功能:
def z_score_series(series: pd.Series) -> np.ndarray:
values = series.to_numpy()
return (values - values.mean()) / values.std()
df = pd.read_parquet(<filepath>, columns=<my_columns>, read_dictionary=['x', 'y', 'z'])
df.groupby(['x', 'y', 'z'])['a','b','c'].transform(z_score_series)
此操作使用我的约 270,000 行数据需要 43 秒才能完成,对于我希望用来对数据进行分组的每个附加字段需要更长的时间。我希望简单地提高此操作的速度。
我尝试使用 numpy + numba(最新版本)来提高性能。我知道我分组的字段将始终是分类数据,而我希望计算数字(float32)的 z 分数的字段。
from numba import prange
@numba.njit(fastmath=True)
def z_score(x):
return (x - x.mean())/x.std()
@numba.njit(parallel=True, fastmath=True)
def transform(unq_idx, values):
out = np.zeros(values.shape)
for x in prange(np.max(unq_idx)):
mask = np.where(unq_idx == x)
for field in prange(values.shape[0]):
out[field][mask] = z_score(values[field][mask])
return out
def get_z_score(df, groupby_cols, calc_cols):
coded_arr = np.array([df[x].values.codes for x in groupby_cols])
_, unq_idx = np.unique(coded_arr, axis=1, return_inverse=True)
calc_arr = np.array([df[x].to_numpy() for x in calc_cols])
out = transform(unq_idx, calc_arr)
return out
df = pd.read_parquet(<filepath>, columns=<my_columns>, read_dictionary=['x', 'y', 'z'])
out = get_z_score(df, ['x', 'y', 'z'], ['a', 'b', 'c'])
The variables in the ```get_z_score``` function just for reference.
coded_arr:
array([[ 0, 0, 0, ..., 168, 168, 168],
[ 0, 0, 0, ..., 0, 0, 0],
[ 0, 1, 2, ..., 4, 0, 0]], dtype=int16)
unq_idx:
array([ 0, 1, 2, ..., 30488, 30484, 30484], dtype=int64)
calc_arr:
array([[ 2.8231514e+07, 6.4926816e+07, 1.3972318e+07, ...,
1.8851726e+08, 2.0333277e+08, 2.5717517e+08],
[ 2.9859206e+07, 4.7409168e+07, 1.2298259e+07, ...,
2.8027318e+08, 1.9047000e+08, 2.3501864e+08],
[-6.1378721e-02, 7.5533399e-03, 2.3085000e-02, ...,
nan, nan, nan]], dtype=float32)
使用上面的代码,当增加分组字段的数量时,我可以将等效操作从 43 秒减少到约 3.5 秒,从而显着提高性能。但是,我不相信这种实现是最有效的,并且使用我没有想到的 numpy 函数/技术可以实现更高的效率(我对 numpy 比较陌生)。
分析代码表明这是用于创建输入 z-score 函数的一维向量的索引的计算,它占用了 90% 的代码运行时间,np.where(unq_idx == x)
所以这是我认为最大的改进可能是制成。
我尝试用一个简单的布尔掩码替换它:mask = unq_idx == x
,但这又是一个慢得多的操作(我假设由于必须扫描整个布尔向量来寻找 Trues 而不是直接使用相应的索引np.where
)。
所以这个问题可以归结为如何从二维 numpy 数组的一组唯一行中更快地循环、创建和使用索引数组。使用 prange 和 numba 的并行功能会有所帮助,但我想会有一个矢量化或更好的操作可以与使用更多线程相结合。如果您认为这可以通过完全不同的方式(不同的模块?)来实现,或者我的代码可以从原来的位置改进,请告诉我。
谢谢
编辑:这是一些大致相似的随机数据,它们需要大致相似的时间来执行操作。我使用的是 8 个逻辑核心,Intel i7-4770 (3.4GHz)
np.random.seed(0)
df = pd.DataFrame(
{'x': map(chr, np.random.randint(1,122, 270000)),
'y': map(chr, np.random.randint(97,97+26, 270000)),
'z': map(chr, np.random.randint(97,97+10, 270000)),
'a': np.random.uniform(-1e+8, 1e+8, size=270000),
'b':np.random.uniform(-1e+8, 1e+8, size=270000),
'c':np.random.uniform(-1, 1, size=270000)}
)
df[['x','y','z']] = df[['x','y','z']].astype('category')
df[['a','b','c']] = df[['a','b','c']].astype(np.float32)
%timeit get_z_score(df, ['x', 'y', 'z'], ['a', 'b', 'c'])
3.3 s ± 42 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit df.groupby(['x', 'y', 'z'])[['a', 'b', 'c']].transform(z_score_series)
42.9 s ± 411 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
解决方案
推荐阅读
- css - 为什么 CSS 中的“direction: rtl”在基于 Chromium 的浏览器上不起作用?
- javascript - 渐变文本颜色,使用 Material-UI
- javascript - TSC 重新编译未更改的 TypeScript 文件
- amazon-web-services - AWS - 在 Terraform 中运行带有 ALB 和超过 5 个端口的 ECS EC2 集群运行容器
- python - ipykernel_launcher.py:错误:需要以下参数:
- c - 无法阻止数组在凯撒算法中溢出
- java - StoredProcedureQuery 的 getResultList() 方法耗时过长
- angular - PrimeNg 多选不显示任何数据
- ios - 我在哪里可以找到有关 LLDB 命令原始输入的文档?
- reactjs - 开玩笑告诉我把链接放在路由器里面