【问题标题】:What's the fastest way to filter a table of integers in Python?在 Python 中过滤整数表的最快方法是什么?
【发布时间】:2011-06-05 04:17:46
【问题描述】:

我有一个包含 7 列和约 900k 行的数据集。所有列都不唯一,所有值都是整数。

过滤的两个重要条件:

  • 当我对其余列应用条件时,我非常想查看一列中有哪些值。
  • 对于输出,我只对不同的值感兴趣。

这里的示例是用于性能基准测试的 SQL 查询:

SELECT DISTINCT
col_2
FROM dataset
WHERE
c_1 in (1,9,5,6,8,18,14,7,15) AND
c_3 in (1) AND
c_4 in (61) AND
c_5 in (3) AND
c_6 in (0) AND
c_7 in (0)

我尝试的第一种方法是使用 SQLite 进行索引的 SQL,这并没有太糟糕,但是随着过滤器返回大量行,性能下降了。

然后我在 Python 中尝试了普通的普通列表推导。性能比 SQL 的情况要差一些。

有没有更好的方法来做到这一点?我在思考 numpy 的方向,也许使用比列表和 SQL 表更高效的数据结构?

我对这里的速度和性能非常感兴趣,而不是效率。

欢迎提出任何建议!

【问题讨论】:

  • 听起来确实像数据库并不是存储它的最佳方式。你有没有想过使用Cython?当您添加静态类型注释时,存储效率可能会更高(您可以使用机器字)并且速度非常快(出于相同的原因)。
  • 当您尝试 SQLite 时,您的数据库是在内存中还是在磁盘上?如果它在文件中,请尝试使用:memory:,对于此类任务,使用 sqlite 的内存数据库通常比 python 快得多。您的所有列都编入索引了吗?
  • 我同意数据库解决方案在这里并不是最好的选择,我更多地将它用作基准测试工具。我正在整理一个 c 扩展作为实验。但是我想知道在 Numpy 中是否有一种有效的方法可以做到这一点,这对我来说是未知的领域。
  • @Giuseppe,很好。我会试试的。虽然我认为它仍然不够快。
  • 这些列中的整数分布情况如何?

标签: python sql performance list numpy


【解决方案1】:

这里是 numpy 版本,大约需要 1 秒。

x = numpy.random.randint(0, 100, (7, 900000))

def filter(data, filters):
    indices = []
    for i, filter in enumerate(filters):
        indices.append(numpy.any([data[i] == x for x in filter], 0))

    indices = numpy.all(indices, 0)
    return data[indices]

# Usage:
filter(x, [(1,9,5,6,8,18,14,7,15), (1,), (61,), (3,), (0,), (0,)])

%timeit filter(x, [(1,9,5,6,8,18,14,7,15), (1,), (61,), (3,), (0,), (0,)])
1 loops, best of 3: 903 ms per loop

【讨论】:

  • 我也玩过过滤功能。就像上面的 itertools 答案一样,列表推导更容易阅读。
  • Numpy 的矢量化方法从性能的角度来看很难被击败,但它确实需要程序员(和读者)以矢量的方式思考。一个中间解决方案是使用类似 numpexr 或 weave.inline 的东西。
【解决方案2】:

按照您所说的,每列大约有 20 个左右不同的值,除了一个 400 的值。如果内存和加载时间不是问题,那么我建议为每列的每个值创建集合。

这是生成示例数据集的内容。

#!/usr/bin/python
from random import sample, choice
from cPickle import dump

# Generate sample dataset
value_ceiling = 1000
dataset_size = 900000
dataset_filename = 'dataset.pkl'

# number of distinct values per column
col_distrib = [400,20,20,20,20,20,20]

col_values = [ sample(xrange(value_ceiling),x) for x in col_distrib ]

dataset = []
for _ in xrange(dataset_size):
  dataset.append(tuple([ choice(x) for x in col_values ]))

dump(dataset,open(dataset_filename,'wb'))

这里有一些东西可以加载数据集并为每列的每个值创建查找集、搜索方法和示例搜索的创建。

#/usr/bin/python

from random import sample, choice
from cPickle import load

dataset_filename = 'dataset.pkl'

class DataSearch(object):
  def __init__(self,filename):
    self.data = load(open(filename,'rb'))
    self.col_sets = [ dict() for x in self.data[0] ]
    self.process_data()
  def process_data(self):
    for row in self.data:
      for i,v in enumerate(row):
        self.col_sets[i].setdefault(v,set()).add(row)
  def search(self,*args):
    # args are integers, sequences of integers, or None in related column positions.
    results = []
    for i,v in enumerate(args):
      if v is None:
        continue
      elif isinstance(v,int):
        results.append(self.col_sets[i].get(v,set()))
      else: # sequence
        r = [ self.col_sets[i].get(x,set()) for x in v ]
        r = reduce(set.union,r[1:],r[0])
        results.append(r)
    #
    results.sort(key=len)
    results = reduce(set.intersection,results[1:],results[0])
    return results
  def sample_search(self,*args):
    search = []
    for i,v in enumerate(args):
      if v is None:
        search.append(None)
      else:
        search.append(sample(self.col_sets[i].keys(),v))
    return search

d = DataSearch(dataset_filename)

并使用它:

>>> d.search(*d.sample_search(1,1,1,5))
set([(117, 557, 273, 437, 639, 981, 587), (117, 557, 273, 170, 53, 640, 467), (117, 557, 273, 584, 459, 127, 649)])
>>> d.search(*d.sample_search(1,1,1,1))
set([])
>>> d.search(*d.sample_search(10,None,1,1,1,1))
set([(801, 334, 414, 283, 107, 990, 221)])
>>> d.search(*d.sample_search(10,None,1,1,1,1))
set([])
>>> d.search(*d.sample_search(10,None,1,1,1,1))
set([(193, 307, 547, 549, 901, 940, 343)])
>>> import timeit
>>> timeit.Timer('d.search(*d.sample_search(10,None,1,1,1,1))','from __main__ import d').timeit(100)
1.787431001663208

1.8 秒完成 100 次搜索是否足够快?

【讨论】:

  • 我完全忘记了套装,确实很快。非常感谢!
【解决方案3】:

这是我想出的:

513 $ cat filtarray.py
#!/usr/bin/python2
#
import numpy
import itertools

a = numpy.fromiter(xrange(7*900000), int)
a.shape = (900000,7)
# stuff a known match
a[33][0] = 18
a[33][2] = 1
a[33][3] = 61
# filter it, and make list, but that is not strictly necessary.
res = list(itertools.ifilter(lambda r: r[0] in (1,9,5,6,8,18,14,7,15) and r[2] == 1 and r[3] == 61, a))
print res

在 Intel E8400 上运行:

512 $ time python filtarray.py 
[array([ 18, 232,   1,  61, 235, 236, 237])]
python filtarray.py  5.36s user 0.05s system 99% cpu 5.418 total

这样更快吗?

【讨论】:

  • 是的,我也试过了。列表推导虽然更快,而且恕我直言,可读性略高。不管怎样,谢谢
猜你喜欢
  • 2015-02-27
  • 2011-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-16
  • 2021-04-12
相关资源
最近更新 更多