【问题标题】:Faster implementation of pandas apply function更快地实现 pandas 应用功能
【发布时间】:2018-06-06 20:12:32
【问题描述】:

我有一个pandas dataFrame,我想检查其中一列是否为contained

假设:

df = DataFrame({'A': ['some text here', 'another text', 'and this'], 
                'B': ['some', 'somethin', 'this']})

我想检查df.B[0]是否在df.A[0]中,df.B[1]是否在df.A[1]等中。

目前的做法

我有如下apply函数实现

df.apply(lambda x: x[1] in x[0], axis=1)

结果是Series[True, False, True]

这很好,但对于我的dataFrame shape(数百万)来说,这需要很长时间。
是否有更好(即更快)的实施方式?

不成功的方法

我尝试了pandas.Series.str.contains 的方法,但它只能接受一个字符串作为模式。

df['A'].str.contains(df['B'], regex=False)

【问题讨论】:

    标签: python string pandas apply


    【解决方案1】:

    使用np.vectorize - 绕过apply 开销,因此应该更快一些。

    v = np.vectorize(lambda x, y: y in x)
    
    v(df.A, df.B)
    array([ True, False,  True], dtype=bool)
    

    这是一个时间比较 -

    df = pd.concat([df] * 10000)
    
    %timeit df.apply(lambda x: x[1] in x[0], axis=1)
    1 loop, best of 3: 1.32 s per loop
    
    %timeit v(df.A, df.B)
    100 loops, best of 3: 5.55 ms per loop
    
    # Psidom's answer
    %timeit [b in a for a, b in zip(df.A, df.B)]
    100 loops, best of 3: 3.34 ms per loop
    

    两者都是相当有竞争力的选择!

    编辑,为 Wen 和 Max 的答案添加时间 -

    # Wen's answer
    %timeit df.A.replace(dict(zip(df.B.tolist(),[np.nan]*len(df))),regex=True).isnull()
    10 loops, best of 3: 49.1 ms per loop
    
    # MaxU's answer
    %timeit df['A'].str.split(expand=True).eq(df['B'], axis=0).any(1)
    10 loops, best of 3: 87.8 ms per loop
    

    【讨论】:

    • 这太好了,谢谢
    • @dimitris_ps 不客气。如果您 1) 传递用户定义的函数而不是 lambda,并且 2) 传递 df.*.values 而不是 df.*v,那么实际上您会获得一些速度提升。
    • 嗨,你能测试一下我的速度吗 :-)
    • @Wen 完成!我不知道它在做什么,但我喜欢它!
    • 这是np.nan感染的一个小技巧:-) stackoverflow.com/questions/46944650/…
    【解决方案2】:

    试试zip,在这种情况下它比apply快得多:

    df = pd.concat([df] * 10000)
    df.head()
    #                A         B
    #0  some text here      some
    #1    another text  somethin
    #2        and this      this
    #0  some text here      some
    #1    another text  somethin
    
    %timeit df.apply(lambda x: x[1] in x[0], axis=1)
    # 1 loop, best of 3: 697 ms per loop
    
    %timeit [b in a for a, b in zip(df.A, df.B)]
    # 100 loops, best of 3: 3.53 ms per loop
    
    # @coldspeed's np.vectorize solution
    %timeit v(df.A, df.B)
    # 100 loops, best of 3: 4.18 ms per loop
    

    【讨论】:

    • 这太好了,谢谢
    • 我希望我能接受这两个答案,我会接受 cᴏʟᴅsᴘᴇᴇᴅ 只是因为他/她的代表较低。再次感谢!
    【解决方案3】:

    更新:我们也可以尝试使用numba

    from numba import jit
    
    @jit
    def check_b_in_a(a,b):
        result = np.zeros(len(a)).astype('bool')
        for i in range(len(a)):
            t = b[i] in a[i]
            if t:
                result[i] = t
        return result
    
    In [100]: check_b_in_a(df.A.values, df.B.values)
    Out[100]: array([ True, False,  True], dtype=bool)
    

    另一个矢量化解决方案:

    In [50]: df['A'].str.split(expand=True).eq(df['B'], axis=0).any(1)
    Out[50]:
    0     True
    1    False
    2     True
    dtype: bool
    

    注意:与 Psidom 和 COLDSPEED 的解决方案相比,它要慢得多:

    In [51]: df = pd.concat([df] * 10000)
    
    # Psidom
    In [52]: %timeit [b in a for a, b in zip(df.A, df.B)]
    7.45 ms ± 270 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    # cᴏʟᴅsᴘᴇᴇᴅ
    In [53]: %timeit v(df.A, df.B)
    15.4 ms ± 217 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    # MaxU (1)    
    In [54]: %timeit df['A'].str.split(expand=True).eq(df['B'], axis=0).any(1)
    185 ms ± 2.29 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    # MaxU (2)    
    In [103]: %timeit check_b_in_a(df.A.values, df.B.values)
    22.7 ms ± 135 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    
    # Wen
    In [104]: %timeit df.A.replace(dict(zip(df.B.tolist(),[np.nan]*len(df))),regex=True).isnull()
    134 ms ± 233 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    【讨论】:

    • 看,没有循环!我也喜欢这个。
    • @cᴏʟᴅsᴘᴇᴇᴅ,嗯,它是最慢的 ;-)
    • 实际上我的速度慢了一两年。谢谢
    【解决方案4】:

    使用replacenan 感染

    df.A.replace(dict(zip(df.B.tolist(),[np.nan]*len(df))),regex=True).isnull()
    Out[84]: 
    0     True
    1    False
    2     True
    Name: A, dtype: bool
    

    修复你的代码

    df['A'].str.contains('|'.join(df.B.tolist()))
    Out[91]: 
    0     True
    1    False
    2     True
    Name: A, dtype: bool
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-02
      • 2010-12-17
      • 2014-10-17
      • 1970-01-01
      • 1970-01-01
      • 2017-05-26
      • 2015-06-13
      相关资源
      最近更新 更多