将字符串拆分为列表并将列表项转换为整数

7

我有一个pandas数据框,其中有一列values,如下所示:

0       16 0
1    7 1 2 0
2          5
3          1
4         18

我希望创建另一列名为modified_values,该列包含在分割每个值后将获得的所有不同数字的列表。新列应如下所示:
0       [16, 0]
1    [7, 1, 2, 0]
2          [5]
3          [1]
4         [18]

注意:此列表中的值应该是int,而不是strings

我知道的事情:

1)我可以以矢量化的方式拆分列,如下所示:df.values.str.split(" ")。这将给我列表,但列表内的对象将是字符串。我可以在其上添加另一个操作,如下所示:df.values.str.split(" ").apply(func to convert values to int),但那样就不会是矢量化的了。

2)我可以直接这样做:df['modified_values']= df['values'].apply(func that splits as well as converts to int)

第二种方法肯定比第一种慢得多,但我想知道是否可以以矢量化的方式实现相同的效果。

2个回答

9

没有本地的“向量化”解决方案

我之所以要强调这点,是因为很容易犯一个错误,就是假设pd.Series.str方法是向量化的。实际上并不是这样的。它们提供了方便和错误处理,但代价是效率低下。对于只包含干净数据(例如没有NaN值)的情况,列表推导可能是您最好的选择:

df = pd.DataFrame({'A': ['16 0', '7 1 2 0', '5', '1', '18']})

df['B'] = [list(map(int, i.split())) for i in df['A']]

print(df)

         A             B
0     16 0       [16, 0]
1  7 1 2 0  [7, 1, 2, 0]
2        5           [5]
3        1           [1]
4       18          [18]

性能基准测试

为了说明与pd.Series.str相关的性能问题,您可以看到当您向Pandas传递更多操作时,数据框性能会变得更差:

df = pd.concat([df]*10000)

%timeit [list(map(int, i.split())) for i in df['A']]            # 55.6 ms
%timeit [list(map(int, i)) for i in df['A'].str.split()]        # 80.2 ms
%timeit df['A'].str.split().apply(lambda x: list(map(int, x)))  # 93.6 ms

pd.Series 中以列表形式作为元素也是反Pandas的

此处所述,在Series中持有列表会产生两层指针,不建议这样做:

不要这样做。Pandas从未被设计用来在series / columns中保存列表。你可以想出昂贵的解决方法,但这些方法并不推荐。

在series中持有列表的主要原因是您将失去使用NumPy数组的向量化功能,这些数组保存在连续的内存块中。您的series将具有object dtype,表示指针序列,类似于list。您将失去在内存和性能方面的优势,以及访问优化的Pandas方法。

另请参见NumPy相对于常规Python列表的优势是什么? Pandas的论点与NumPy相同。


很好地消除了“.str方法在这里很好用”的误解,但或许值得提到的是,结果结构也并不真正符合pandas的标准。 - roganjosh
1
@roganjosh,没错,说得好。这需要进行复制粘贴编辑 :) - jpp
1
@jpp 感谢你详细的回答和对 str 的必要澄清。 - enterML
双重for推导比map更快。而Numba则快250倍,但存在NaN值。请查看我的回答。 - keiv.fly
@keiv.fly,关于嵌套列表推导式的问题,你说得很好!当然,numba 的结果在技术上是不同的(但实际上更好,因为没有 list)。 - jpp

2
双重for推导式比jpp答案中的map推导式快33%。使用Numba技巧比jpp答案中的map推导式快250倍,但你会得到一个包含浮点数和nan的pandas DataFrame而不是列表序列。Numba已包含在Anaconda中。
基准测试:
%timeit pd.DataFrame(nb_calc(df.A))            # numba trick       0.144 ms
%timeit [int(x) for i in df['A'] for x in i.split()]            # 23.6   ms
%timeit [list(map(int, i.split())) for i in df['A']]            # 35.6   ms
%timeit [list(map(int, i)) for i in df['A'].str.split()]        # 50.9   ms
%timeit df['A'].str.split().apply(lambda x: list(map(int, x)))  # 56.6   ms

Numba函数的代码:

@numba.jit(nopython=True, nogil=True)
def str2int_nb(nb_a):
    n1 = nb_a.shape[0]
    n2 = nb_a.shape[1]
    res = np.empty(nb_a.shape)
    res[:] = np.nan
    j_res_max = 0
    for i in range(n1):
        j_res = 0
        s = 0
        for j in range(n2):
            x = nb_a[i,j]
            if x == 32:
                res[i,j_res]=np.float64(s)
                s=0
                j_res+=1
            elif x == 0:
                break
            else:
                s=s*10+x-48
        res[i,j_res]=np.float64(s)
        if j_res>j_res_max:
            j_res_max = j_res

    return res[:,:j_res_max+1]

def nb_calc(s):
    a_temp = s_a.values.astype("U")
    nb_a = a_temp.view("uint32").reshape(len(s_a),-1).astype(np.int8)
    str2int_nb(nb_a)

Numba不支持字符串。因此我首先将其转换为int8数组,然后再处理它。转换为int8实际上占用了执行时间的3/4。

我的Numba函数的输出如下所示:

      0    1    2    3
-----------------------
0  16.0  0.0  NaN  NaN
1   7.0  1.0  2.0  0.0
2   5.0  NaN  NaN  NaN
3   1.0  NaN  NaN  NaN
4  18.0  NaN  NaN  NaN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接