Какой самый эффективный способ преобразовать набор результатов MySQL в массив NumPy? - PullRequest
13 голосов
/ 15 августа 2011

Я использую MySQLdb и Python. У меня есть несколько основных запросов, таких как это:

c=db.cursor()
c.execute("SELECT id, rating from video")
results = c.fetchall()

Мне нужно, чтобы «результаты» были массивом NumPy, и я стараюсь экономно расходовать память. Кажется, что копирование данных строка за строкой было бы невероятно неэффективным (потребовалось бы вдвое больше памяти). Есть ли лучший способ преобразовать результаты запроса MySQLdb в формат массива NumPy?

Причина, по которой я стремлюсь использовать формат массива NumPy, заключается в том, что я хочу иметь возможность легко нарезать и разрезать данные, и не похоже, что python очень дружелюбен к многомерным массивам в этом отношении.

e.g. b = a[a[:,2]==1] 

Спасибо!

Ответы [ 3 ]

22 голосов
/ 15 августа 2013

В этом решении используется метод fromiter от Kieth, но он обрабатывает структуру двумерных таблиц результатов SQL более интуитивно. Кроме того, это улучшает метод Дуга, избегая всех изменений и выравниваний в типах данных Python. Используя структурированный массив , мы можем в значительной степени непосредственно читать результаты MySQL в виде кучи, полностью исключая типы данных 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.fecthall() 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

Информацию о том, как указывать типы данных и имена столбцов, см. В документации по dtype для dtype и приведенной выше ссылке на структурированные массивы.

15 голосов
/ 15 августа 2011

Метод fetchall фактически возвращает итератор, а numpy имеет метод fromiter для инициализации массива из интегратора. Таким образом, в зависимости от того, какие данные находятся в таблице, вы можете легко объединить их или использовать генератор адаптеров.

6 голосов
/ 15 августа 2011

NumPy's fromiter метод кажется здесь лучшим (как в ответе Кейта, который предшествовал этому).

Использование 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)

Обратите внимание, что fromiter возвращает массив 1D NumPY,

(Конечно, это имеет смысл, потому что вы можете использовать fromiter , чтобы возвратить только часть одной строки таблицы MySQL, передав параметр для count ).

Тем не менее, вам придется восстановить 2D-фигуру, следовательно, вызов предиката для метода курсора rowcount . и последующий вызов изменить форму в последней строке.

Наконец, аргумент по умолчанию для параметра count равен '-1', который просто извлекает всю итерацию

...