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

如何在Python中高效优化每行含字符串数组的Pandas序列的序数编码?

高效序数编码优化:处理含空列表的Pandas序列

问题背景

我有一个Pandas序列,每行存储一个字符串数组,示例如下:

0 []
1 []
2 []
3 []
4 [0007969760, 0007910220, 0007910309]
...
243223 []
243224 [0009403370]
243225 [0009403370, 0007190939]
243226 []
243227 []
名称: Item History, 长度: 243228, 数据类型: object

我的目标是在此基础上执行简单的序数编码,同时尽可能兼顾时间与内存效率,且需满足以下要求:

  • 空列表需用唯一整数标记(例如,若有100个唯一字符串,空列表可编码为[101]);
  • 编码规则需可保存,以便未来对其他列表执行相同编码;
  • 若未来列表包含初始数据中未出现的字符串,需用单独整数标记该未知项。

为何弃用sklearn的OrdinalEncoder?

一方面它不支持未知项处理,另一方面逐行应用时速度极慢——我们需先在所有不同字符串的合并数组上拟合,再用Series.apply(lambda x: oe.transform(x))逐行转换,因为它会为每行构建映射表,虽单次调用仅约0.01秒,但面对海量数据仍过慢。

现有尝试与性能

我尝试将字典推导移到逐行处理之外,先构建映射表再遍历行,实现了如下原生Python函数:

def encode_labels(X, table, noHistory, unknownItem):
    res = np.empty(len(X), dtype=np.ndarray)
    for i in range(len(X)):
        if len(X[i]) == 0:
            res[i] = np.array([noHistory])
        else:
            res[i] = np.empty(len(X[i]), dtype=np.ndarray)
            for j in range(len(X[i])):
                try:
                    res[i][j] = table[X[i][j]]
                except KeyError:
                    res[i][j] = unknownItem
    return res

该方案比逐行.apply()快很多,但仍非最优。我将其Cython化并做了其他优化,获得了一定速度提升,但未达到数量级的改进:

%%cython
cimport numpy as cnp
import numpy as np
from cpython cimport array
import array

cpdef list encode_labels_cy(cnp.ndarray X, dict table, int noHistory, int unknownItem, array.array rowLengths):
    cdef int[:] crc = rowLengths
    cdef list flattenedX = []
    cdef Py_ssize_t i, j
    cdef list row = []
    for row in X:
        if len(row)==0:
            flattenedX.append('ZZ')
        else:
            flattenedX.extend(row)
    cdef Py_ssize_t lenX = len(flattenedX)
    cdef array.array res = array.array('i', [0]*lenX)
    cdef int[:] cres = res
    i=0
    while i < lenX:
        try:
            cres[i] = table[flattenedX[i]]
        except KeyError:
            cres[i] = unknownItem
        i += 1
    cdef list pyres = []
    cdef Py_ssize_t s = 0
    for k in crc:
        pyres.append(res[s:s+k])
        s+= k
    return pyres

性能测试结果(基于5000行样本)

# classes是{字符串:整数}的映射字典,noHistory和unknownItem为对应标记值
%timeit encode_labels(X.values, classes, noHistory, unknownItem)
%timeit encode_labels_cy(X.values, classes, noHistory, unknownItem, array.array('i', [1 if x == 0 else x for x in [len(j) for j in X]]))

输出:

50.4 ms ± 2.76 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
11.2 ms ± 1.11 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

更新:我用ctypes实现了一个版本,速度比逐行.apply()和原生Python版本快,但仍慢于Cython(这与我的预期不符!)

优化诉求

我想请教:如何进一步提升编码速度,同时尽可能降低内存占用?方案不局限于纯Python,Cython、ctypes等均可。由于该代码是神经网络预处理的一部分,还有GPU可用,若能利用GPU加速更佳。多进程也是可选方向,但该方式需为每个进程复制字符串-整数映射表,既生成缓慢又占用大量内存。

测试数据生成代码

可生成与我数据集格式类似的输入:

import numpy as np
import pandas as pd
a = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
X = pd.Series([[a[np.random.randint(0, 26)] for i in range(np.random.randint(0, 10))] for j in range(5000)])
classes = dict(zip(a, np.arange(0, 26)))
unknownItem = 26
noHistory = 27

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 20:38:14