在列上使用 `split` 太慢了 - 我怎样才能获得更好的性能?

Using `split` on columns too slow - how can I get better performance?

我有一个通话记录数据集(大约 10Gb)。我想将包含 IP 地址的列分成四个新列。我正在尝试使用:

df['ip'].fillna('0.0.0.0', inplace=True)
df = df.join(df['ip'].apply(lambda x: Series(x.split('.'))))

但是太慢了...fillna 很快,大约 10 秒,但随后它会在拆分中停留大约 5 个小时...

有什么更好的方法吗?

示例数据(如果您提供此数据,您的问题更有可能得到解答):

import pandas as pd
import random

def make_ip():
    return '.'.join(str(random.randint(0, 255)) for n in range(4))

df = pd.DataFrame({'ip': [make_ip() for i in range(20)]})

df
Out[4]: 
                 ip
0     153.1.219.147
1   110.170.184.123
2     91.100.92.150
3      61.148.99.64
4      94.175.253.3
5     30.29.220.218
6     7.118.167.173
7       71.99.78.94
8   240.122.200.194
9       48.16.177.0
10    81.155.96.173
11     202.91.134.9
12   90.155.159.176
13     169.74.28.73
14   149.133.115.45
15   168.196.41.132
16   145.195.15.234
17     12.200.28.27
18    146.255.29.80
19   228.226.143.45

使用 pandas' 内置 str 方法进行高效的字符串操作,并直接添加它们以避免连接缓慢:

df[['ip0', 'ip1', 'ip2', 'ip3']] = df.ip.str.split('.', return_type='frame')

df
Out[8]: 
                 ip  ip0  ip1  ip2  ip3
0     153.1.219.147  153    1  219  147
1   110.170.184.123  110  170  184  123
2     91.100.92.150   91  100   92  150
3      61.148.99.64   61  148   99   64
4      94.175.253.3   94  175  253    3
5     30.29.220.218   30   29  220  218
6     7.118.167.173    7  118  167  173
7       71.99.78.94   71   99   78   94
8   240.122.200.194  240  122  200  194
9       48.16.177.0   48   16  177    0
10    81.155.96.173   81  155   96  173
11     202.91.134.9  202   91  134    9
12   90.155.159.176   90  155  159  176
13     169.74.28.73  169   74   28   73
14   149.133.115.45  149  133  115   45
15   168.196.41.132  168  196   41  132
16   145.195.15.234  145  195   15  234
17     12.200.28.27   12  200   28   27
18    146.255.29.80  146  255   29   80
19   228.226.143.45  228  226  143   45

原来Pandas中的str.split(在core/strings.py中为str_split)其实很慢;它没有任何效率提高,并且仍然通过使用 Python 进行迭代,没有提供任何加速。

实际上,请参见下文。 Pandas这方面的表现简直惨不忍睹;这不仅仅是 Python vs C 迭代,因为用 Python 列表做同样的事情是最快的方法!

不过,有趣的是,有一个更快的技巧解决方案:将系列写成文本,然后使用“.”再次读入。作为分隔符:

df[['ip0', 'ip1', 'ip2', 'ip3']] = \
    pd.read_table(StringIO(df['ip'].to_csv(None,index=None)),sep='.')

为了比较,我使用了 Marius 的代码并生成了 20,000 个 ips:

import pandas as pd
import random
import numpy as np
from StringIO import StringIO

def make_ip():
    return '.'.join(str(random.randint(0, 255)) for n in range(4))

df = pd.DataFrame({'ip': [make_ip() for i in range(20000)]})

%timeit df[['ip0', 'ip1', 'ip2', 'ip3']] = df.ip.str.split('.', return_type='frame')
# 1 loops, best of 3: 3.06 s per loop

%timeit df[['ip0', 'ip1', 'ip2', 'ip3']] = df['ip'].apply(lambda x: pd.Series(x.split('.')))
# 1 loops, best of 3: 3.1 s per loop

%timeit df[['ip0', 'ip1', 'ip2', 'ip3']] = \
    pd.read_table(StringIO(df['ip'].to_csv(None,index=None)),sep='.',header=None)
# 10 loops, best of 3: 46.4 ms per loop

好的,所以我想将所有这些与仅使用 Python 列表和 Python 拆分进行比较,这应该比使用更高效的 Pandas 慢:

iplist = list(df['ip'])
%timeit [ x.split('.') for x in iplist ]
100 loops, best of 3: 10 ms per loop

什么!?显然,对大量字符串进行简单字符串操作的最佳方法是完全抛弃Pandas使用 Pandas 会使过程慢 400 倍。 如果你想使用 Pandas,你也可以转换为 Python 列表返回:

%timeit df[['ip0', 'ip1', 'ip2', 'ip3']] = \
    pd.DataFrame([ x.split('.') for x in list(df['ip']) ])
# 100 loops, best of 3: 18.4 ms per loop

这里有一些非常的错误。