将MySQL结果集转换为NumPy数组的最有效方法是什么?

the*_*att 14 python etl numpy mysql-python

我正在使用MySQLdb和Python.我有一些基本的查询,例如:

c=db.cursor()
c.execute("SELECT id, rating from video")
results = c.fetchall()
Run Code Online (Sandbox Code Playgroud)

我需要"结果"作为NumPy数组,而且我希望我的内存消耗更经济.似乎逐行复制数据会非常低效(需要双倍的内存).有没有更好的方法将MySQLdb查询结果转换为NumPy数组格式?

我想要使​​用NumPy数组格式的原因是因为我希望能够轻松地对数据进行切片和切块,并且在这方面看起来python对于多维数组似乎并不友好.

e.g. b = a[a[:,2]==1] 
Run Code Online (Sandbox Code Playgroud)

谢谢!

sir*_*ark 23

该解决方案使用Kieth的fromiter技术,但更直观地处理SQL结果的二维表结构.此外,它通过避免python数据类型中的所有重新整形和展平来改进Doug​​的方法.使用结构化数组,我们可以直接从MySQL结果读取到numpy,几乎完全删除python数据类型.我说"差不多"因为fetchall迭代器仍然会产生python元组.

但有一点需要注意,但这不是一个大问题.您必须事先知道列的数据类型和行数.

知道列类型应该是显而易见的,因为你可能知道查询是什么,否则你总是可以使用curs.description,以及MySQLdb.FIELD_TYPE.*常量的映射.

知道行数意味着您必须使用客户端游标(这是默认值).我对MySQLdb和MySQL客户端库的内部结构还不太了解,但我的理解是,当使用客户端游标时,整个结果被提取到客户端内存中,尽管我怀疑实际上存在一些缓冲和缓存.这意味着为结果使用双内存,一次用于光标复制,一次用于数组复制,因此如果结果集很大,最好关闭光标以释放内存.

严格来说,您不必提前提供行数,但这样做意味着数组内存预先分配一次,并且不会随着更多行从迭代器进入而不断调整大小,这意味着提供巨大的性能提升.

有了这个,一些代码

import MySQLdb
import numpy

conn = MySQLdb.connect(host='localhost', user='bob', passwd='mypasswd', db='bigdb')
curs = conn.cursor() #Use a client side cursor so you can access curs.rowcount
numrows = curs.execute("SELECT id, rating FROM video")

#curs.fetchall() is the iterator as per Kieth's answer
#count=numrows means advance allocation
#dtype='i4,i4' means two columns, both 4 byte (32 bit) integers
A = numpy.fromiter(curs.fetchall(), count=numrows, dtype=('i4,i4'))

print A #output entire array
ids = A['f0'] #ids = an array of the first column
              #(strictly speaking it's a field not column)
ratings = A['f1'] #ratings is an array of the second colum
Run Code Online (Sandbox Code Playgroud)

有关如何指定列数据类型和列名的详细信息,请参阅dtype的numpy文档和上面有关结构化数组的链接.


Kei*_*ith 15

fetchall方法实际上返回一个迭代器,numpy有fromiter方法从一个interator初始化一个数组.因此,根据表中的数据,您可以轻松地将两者合并,或使用适配器生成器.


dou*_*oug 6

NumPy的fromiter方法在这里看起来最好(如Keith的回答,在此之前).

使用fromiter重新调整通过调用MySQLdb游标方法返回到NumPy数组的结果集很简单,但是有一些细节可能值得一提.

import numpy as NP
import MySQLdb as SQL

cxn = SQL.connect('localhost', 'some_user', 'their_password', 'db_name')
c = cxn.cursor()
c.execute('SELECT id, ratings from video')

# fetchall() returns a nested tuple (one tuple for each table row)
results = cursor.fetchall()

# 'num_rows' needed to reshape the 1D NumPy array returend by 'fromiter' 
# in other words, to restore original dimensions of the results set
num_rows = int(c.rowcount)

# recast this nested tuple to a python list and flatten it so it's a proper iterable:
x = map(list, list(results))              # change the type
x = sum(x, [])                            # flatten

# D is a 1D NumPy array
D = NP.fromiter(iterable=x, dtype=float, count=-1)  

# 'restore' the original dimensions of the result set:
D = D.reshape(num_rows, -1)
Run Code Online (Sandbox Code Playgroud)

请注意,fromiter返回1D NumPY数组,

(当然,这是有道理的,因为您可以通过传递count参数来使用fromiter返回单个MySQL Table行的一部分).

但是,您必须恢复2D形状,因此对游标方法rowcount的谓词调用.以及随后的调用重塑最后一行.

最后,参数count的默认参数是'-1',它只检索整个iterable