Pandas pd.Series.isin 集合与数组的性能

2023-11-23

一般来说,在 Python 中,可哈希集合的成员资格最好通过以下方式进行测试set。我们知道这一点是因为散列的使用为我们提供了 O(1) 查找复杂度,而对于list or np.ndarray.

在 Pandas 中,我经常需要检查非常大的集合中的成员资格。我认为同样的情况也适用,即检查系列中的每个项目是否属于某个系列set比使用更有效list or np.ndarray。然而,情况似乎并非如此:

import numpy as np
import pandas as pd

np.random.seed(0)

x_set = {i for i in range(100000)}
x_arr = np.array(list(x_set))
x_list = list(x_set)

arr = np.random.randint(0, 20000, 10000)
ser = pd.Series(arr)
lst = arr.tolist()

%timeit ser.isin(x_set)                   # 8.9 ms
%timeit ser.isin(x_arr)                   # 2.17 ms
%timeit ser.isin(x_list)                  # 7.79 ms
%timeit np.in1d(arr, x_arr)               # 5.02 ms
%timeit [i in x_set for i in lst]         # 1.1 ms
%timeit [i in x_set for i in ser.values]  # 4.61 ms

用于测试的版本:

np.__version__  # '1.14.3'
pd.__version__  # '0.23.0'
sys.version     # '3.6.5'

源代码为pd.Series.isin,我相信,利用numpy.in1d,这可能意味着很大的开销set to np.ndarray转换。

否定构建输入的成本,对 Pandas 的影响:

  • 如果你知道你的元素x_list or x_arr是独一无二的,不必费心转换为x_set。对于 Pandas 来说,这将是昂贵的(转换和成员测试)。
  • 使用列表推导式是从 O(1) 集合查找中受益的唯一方法。

我的问题是:

  1. 我上面的分析正确吗?这似乎是一个显而易见但未记录的结果pd.Series.isin已实施。
  2. 是否有解决方法,不使用列表理解或pd.Series.apply, which does利用 O(1) 集合查找?或者这是不可避免的设计选择和/或以 NumPy 作为 Pandas 骨干的必然结果?

Update:在较旧的设置(Pandas / NumPy 版本)上我明白了x_set跑赢大盘x_arr with pd.Series.isin。所以还有一个问题:是否有任何从旧到新的根本变化会导致性能set恶化?

%timeit ser.isin(x_set)                   # 10.5 ms
%timeit ser.isin(x_arr)                   # 15.2 ms
%timeit ser.isin(x_list)                  # 9.61 ms
%timeit np.in1d(arr, x_arr)               # 4.15 ms
%timeit [i in x_set for i in lst]         # 1.15 ms
%timeit [i in x_set for i in ser.values]  # 2.8 ms

pd.__version__  # '0.19.2'
np.__version__  # '1.11.3'
sys.version     # '3.6.0'

这可能并不明显,但是pd.Series.isin uses O(1)-查找每个元素。

经过分析证明上述陈述后,我们将利用其见解来创建一个 Cython 原型,它可以轻松击败最快的开箱即用解决方案。


我们假设“集合”有n元素和“系列”有m元素。那么运行时间为:

 T(n,m)=T_preprocess(n)+m*T_lookup(n)

对于纯 python 版本,这意味着:

  • T_preprocess(n)=0- 无需预处理
  • T_lookup(n)=O(1)- python 集合的众所周知的行为
  • 结果是T(n,m)=O(m)

会发生什么pd.Series.isin(x_arr)?显然,如果我们跳过预处理并在线性时间内搜索,我们将得到O(n*m),这是不可接受的。

借助调试器或分析器(我使用 valgrind-callgrind+kcachegrind)很容易看到发生了什么:工作的马是函数__pyx_pw_6pandas_5_libs_9hashtable_23ismember_int64。其定义可以查到here:

  • 在预处理步骤中,哈希映射(pandas 使用来自 klib 的卡什)创建于n元素来自x_arr,即在运行时O(n).
  • m查找发生在O(1)每个或O(m)总共在构造的哈希图中。
  • 结果是T(n,m)=O(m)+O(n)

我们必须记住 - numpy-array 的元素是原始 C 整数,而不是原始集合中的 Python 对象 - 所以我们不能按原样使用该集合。

将 Python 对象集转换为 C 整数集的另一种方法是将单个 C 整数转换为 Python 对象,从而能够使用原始集。这就是发生在[i in x_set for i in ser.values]-变体:

  • 没有预处理。
  • m 次查找发生在O(1)每次或O(m)总共,但由于需要创建 Python 对象,查找速度较慢。
  • 结果是T(n,m)=O(m)

显然,您可以使用 Cython 稍微加快此版本的速度。

但理论已经足够了,让我们看看不同情况下的运行时间ns 与固定ms:

enter image description here

我们可以看到:预处理的线性时间在大型的 numpy 版本中占主导地位n是。从 numpy 转换为纯 python 的版本(numpy->python)与纯 python 版本具有相同的恒定行为,但由于必要的转换而速度较慢 - 这一切都符合我们的分析。

这在图中看不清楚:如果n < mnumpy 版本变得更快 - 在这种情况下,查找速度更快khash-lib 起着最重要的作用,而不是预处理部分。

我从这个分析中得出的结论是:

  • n < m: pd.Series.isin应该采取,因为O(n)- 预处理并不那么昂贵。

  • n > m:(可能是 cythonized 版本)[i in x_set for i in ser.values]应采取,因此O(n)避免了。

  • 显然存在一个灰色地带n and m近似相等,未经测试很难判断哪种解决方案最好。

  • 如果你能控制它:最好的办法是构建set直接作为 C 整数集 (khash (已经被熊猫包裹了)或者甚至可能是一些 C++ 实现),从而消除了预处理的需要。我不知道 pandas 中是否有可以重用的东西,但在 Cython 中编写该函数可能不是什么大问题。


问题是最后一个建议不能开箱即用,因为 pandas 和 numpy 在它们的界面中都没有集合的概念(至少就我有限的知识而言)。但拥有原始 C 集接口将是两全其美:

  • 不需要预处理,因为值已经作为一组传递
  • 不需要转换,因为传递的集合由原始 C 值组成

我编写了一个快速而肮脏的代码khash 的 Cython 包装器(受到 pandas 中包装器的启发),可以通过以下方式安装pip install https://github.com/realead/cykhash/zipball/master然后与 Cython 一起使用以获得更快的速度isin版本:

%%cython
import numpy as np
cimport numpy as np

from cykhash.khashsets cimport Int64Set

def isin_khash(np.ndarray[np.int64_t, ndim=1] a, Int64Set b):
    cdef np.ndarray[np.uint8_t,ndim=1, cast=True] res=np.empty(a.shape[0],dtype=np.bool)
    cdef int i
    for i in range(a.size):
        res[i]=b.contains(a[i])
    return res

作为进一步的可能性,c++unordered_map可以被包装(参见清单 C),它的缺点是需要 c++ 库并且(正如我们将看到的)速度稍慢。

比较这些方法(参见清单 D 创建时序):

enter image description here

khash 的速度大约比 20 倍快numpy->python,比纯 python 快约 6 倍(但无论如何,纯 python 都不是我们想要的),甚至比 cpp 版本快约 3 倍。


Listings

1) 使用 valgrind 进行分析:

#isin.py
import numpy as np
import pandas as pd

np.random.seed(0)

x_set = {i for i in range(2*10**6)}
x_arr = np.array(list(x_set))


arr = np.random.randint(0, 20000, 10000)
ser = pd.Series(arr)


for _ in range(10):
   ser.isin(x_arr)

and now:

>>> valgrind --tool=callgrind python isin.py
>>> kcachegrind

导致以下调用图:

enter image description here

B:用于生成运行时间的 ipython 代码:

import numpy as np
import pandas as pd
%matplotlib inline
import matplotlib.pyplot as plt

np.random.seed(0)

x_set = {i for i in range(10**2)}
x_arr = np.array(list(x_set))
x_list = list(x_set)

arr = np.random.randint(0, 20000, 10000)
ser = pd.Series(arr)
lst = arr.tolist()

n=10**3
result=[]
while n<3*10**6:
    x_set = {i for i in range(n)}
    x_arr = np.array(list(x_set))
    x_list = list(x_set)

    t1=%timeit -o  ser.isin(x_arr) 
    t2=%timeit -o  [i in x_set for i in lst]
    t3=%timeit -o  [i in x_set for i in ser.values]

    result.append([n, t1.average, t2.average, t3.average])
    n*=2

#plotting result:
for_plot=np.array(result)
plt.plot(for_plot[:,0], for_plot[:,1], label='numpy')
plt.plot(for_plot[:,0], for_plot[:,2], label='python')
plt.plot(for_plot[:,0], for_plot[:,3], label='numpy->python')
plt.xlabel('n')
plt.ylabel('running time')
plt.legend()
plt.show()

C:cpp 包装器:

%%cython --cplus -c=-std=c++11 -a

from libcpp.unordered_set cimport unordered_set

cdef class HashSet:
    cdef unordered_set[long long int] s
    cpdef add(self, long long int z):
        self.s.insert(z)
    cpdef bint contains(self, long long int z):
        return self.s.count(z)>0

import numpy as np
cimport numpy as np

cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)

def isin_cpp(np.ndarray[np.int64_t, ndim=1] a, HashSet b):
    cdef np.ndarray[np.uint8_t,ndim=1, cast=True] res=np.empty(a.shape[0],dtype=np.bool)
    cdef int i
    for i in range(a.size):
        res[i]=b.contains(a[i])
    return res

D:使用不同的集合包装器绘制结果:

import numpy as np
import pandas as pd
%matplotlib inline
import matplotlib.pyplot as plt
from cykhash import Int64Set

np.random.seed(0)

x_set = {i for i in range(10**2)}
x_arr = np.array(list(x_set))
x_list = list(x_set)


arr = np.random.randint(0, 20000, 10000)
ser = pd.Series(arr)
lst = arr.tolist()

n=10**3
result=[]
while n<3*10**6:
    x_set = {i for i in range(n)}
    x_arr = np.array(list(x_set))
    cpp_set=HashSet()
    khash_set=Int64Set()

    for i in x_set:
        cpp_set.add(i)
        khash_set.add(i)


    assert((ser.isin(x_arr).values==isin_cpp(ser.values, cpp_set)).all())
    assert((ser.isin(x_arr).values==isin_khash(ser.values, khash_set)).all())


    t1=%timeit -o  isin_khash(ser.values, khash_set)
    t2=%timeit -o  isin_cpp(ser.values, cpp_set) 
    t3=%timeit -o  [i in x_set for i in lst]
    t4=%timeit -o  [i in x_set for i in ser.values]

    result.append([n, t1.average, t2.average, t3.average, t4.average])
    n*=2

#ploting result:
for_plot=np.array(result)
plt.plot(for_plot[:,0], for_plot[:,1], label='khash')
plt.plot(for_plot[:,0], for_plot[:,2], label='cpp')
plt.plot(for_plot[:,0], for_plot[:,3], label='pure python')
plt.plot(for_plot[:,0], for_plot[:,4], label='numpy->python')
plt.xlabel('n')
plt.ylabel('running time')
ymin, ymax = plt.ylim()
plt.ylim(0,ymax)
plt.legend()
plt.show()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Pandas pd.Series.isin 集合与数组的性能 的相关文章

随机推荐

  • 用于检测模板特化的模板元函数

    灵感来自这个问题 我想知道是否可以引入一些编译时检查来检测是否有两个给定的模板实例化 template
  • 如何在 sails.js 中配置 https

    我正在尝试设置本地 HTTPS 服务器以在 Sails js 中进行测试 我无法找到任何指针如何在 sails js 中执行此操作 对于快递来说 var express require express var https require h
  • 如何在Python中将函数作为函数参数传递

    这是我目前拥有的并且运行良好 def iterate seed num x seed orbit x for i in range num x 2 x 1 x orbit append x return orbit 现在 如果我想将第 5
  • 如何创建一个可以固定行和列滚动的自定义控件?

    我试图弄清楚如何制作一个自定义控件 使用户可以向各个方向滚动 但具有固定的行和列 网格不适合我想要做的事情 因为它逐列滚动 我需要水平滚动逐像素平滑 我没有使用列 只有视觉网格线 垂直滚动不仅应该滚动右侧的区域 还应该滚动左侧的固定区域 与
  • Git - 不包括 {} 的颜色词

    我使用 git 和 color words 来查看我的差异 在我的差异中 它表明我删除了 b ljcount b nbsp nbsp nbsp Changes 我补充说 b skills limits b nbsp nbsp nbsp Ch
  • 避免 C/C++ 中内存泄漏的方法

    我可以使用哪些技巧来避免应用程序中的内存泄漏 在我当前的项目中 我使用一个工具 INSURE 来查找内存泄漏并生成报告 除了该工具之外 还有任何方法可以识别内存泄漏并克服它 有三种主要方法可以做到这一点 第一个是不会造成内存泄漏首先 防御性
  • AFL 警告:最后一个新路径:还没有(奇怪,请检查语法!)

    我有这个警告 最后一个新路径 还没有 奇怪 检查语法 在我尝试模糊文件后呈红色 我不知道为什么会发生这种情况 我用谷歌搜索也没有答案 我的命令是这样的 afl fuzz i testcases o findings tcpdump 4 6
  • 测试中模拟 EJB 注入

    每当我想测试一个使用资源注入的类时 我最终都会包含一个仅在测试中使用的构造函数 public class A EJB B b Used in tests to inject EJB mock protected A B b this b b
  • SFTP 路径格式与本地路径格式

    我正在编写一些 Java 代码 使用 JSch 库 通过 SFTP 到远程 Windows 计算机 并将文件复制到我的本地 Windows 文件夹 当指定远程计算机上的文件路径时 我被迫以以下格式指定路径 C temp myfile txt
  • Android MediaPlayer:基于 URI 播放 Raw 音频资源

    我试图解决的问题是在一个需要播放音频文件的活动中 大多数文件将由用户创建 并保存到外部存储中 因此使用以下代码播放 基于 Google 的示例代码 MediaPlayer mPlayer new MediaPlayer mPlayer se
  • python manage.py runserver、shell、dbshel​​l 在 git-bash 上冻结

    我试图在 Windows 的 git bash 上的 python virtualenv 中运行交互式 shell 但它没有运行 奇怪的是 它似乎没有做任何事情 只是光标在下一行上闪烁 没有给出任何输出 python manage py s
  • 如何在 EPPlus 中将数据透视表报表布局设置为表格?

    查看 EPPlus 附带的示例 我已成功创建数据透视表 但无法为其设置正确的报告布局 我希望它是 表格 而不是 轮廓 或其他什么 对我来说 EPPlus 现在似乎不支持这一点 但也许我错过了一些东西 事实证明 这比我想象的要容易得多 通过将
  • Android WebView:按钮响应非常滞后

    我制作了一个小网络应用程序来使用 Android 的 WebView 功能 我有一些用作按钮的 div 带有onclick属性 尝试该应用程序后 在设备的浏览器中 我立即注意到点击按钮后有很大的延迟 当我点击按钮和浏览器在其周围显示橙色突出
  • Python:停止正在等待用户输入的线程

    我试图让我的脚本在用户按下返回键时触发用户输入 然后主程序将检查 txUpdated 标志并使用该输入 我有一个在 python 中运行的线程 它只是等待用户输入 class InputThread threading Thread def
  • Eclipse IDE 支持 JSF 2.0 吗?

    我安装了 WTP 3 1 插件 还安装了 Glassfish v3 插件 我可以注册我的服务器 当我创建动态 Web 项目时 我可以看到可用的最大动态 Web 模块版本是 2 5 然后 我选择 Glassfish v3 的默认配置 但是当我
  • pandas if else 条件多列[重复]

    这个问题在这里已经有答案了 假设我有以下 df import pandas as pd data dic a 0 0 1 2 b 0 3 4 5 c 6 7 8 9 df pd DataFrame data dic Result a b c
  • 如何使用 R 调用/执行 imageJ 宏?

    我在 imageJ 中编写了一个宏 它会输出一个数据帧 然后在 R 中对其进行分析 我希望能够在 R 中完成整个过程 而不必先在 imageJ 中手动运行该宏 目前 宏会提示用户输入和输出目录 然后执行操作 我想 R 中一定有一个函数可以让
  • 在 Eclipse 中生成 JavaDocs 时出现“找不到模块”消息

    我正在尝试在我的应用程序中生成 JavaDocs 但是 当我尝试时 我收到以下消息 application src module info java 5 error module not found javafx base requires
  • 无法使用 exoPlayer 2.11 播放 MKV Matroska 视频

    当我尝试播放时在我的视频播放器中MKV Matroska文件保持静止 视频未播放 我跟着CodeLabs and ExoPlayer开发并构建可以播放的播放器 MP4但无法播放 MKV 这是我的播放器 exoplayer 2 11 5 pr
  • Pandas pd.Series.isin 集合与数组的性能

    一般来说 在 Python 中 可哈希集合的成员资格最好通过以下方式进行测试set 我们知道这一点是因为散列的使用为我们提供了 O 1 查找复杂度 而对于list or np ndarray 在 Pandas 中 我经常需要检查非常大的集合