Numpy чтение файла с фильтрацией строк на лету
у меня есть большой массив чисел, написанных в CSV-файле, и мне нужно загрузить только фрагмент этого массива. Концептуально я хочу позвонить np.genfromtxt()
а затем строка-срез результирующего массива, но
- файл настолько велик, что может не поместиться в памяти
- количество соответствующих строк может быть небольшим, поэтому нет необходимости анализировать каждую строку.
MATLAB имеет функцию textscan()
это может взять дескриптор файла и прочитать только часть файла. Есть есть что-нибудь подобное в NumPy?
на данный момент я определил следующую функцию, которая читает только строки, удовлетворяющие данному условию:
def genfromtxt_cond(fname, cond=(lambda str: True)):
res = []
with open(fname) as file:
for line in file:
if cond(line):
res.append([float(s) for s in line.split()])
return np.array(res, dtype=np.float64)
есть несколько проблем с этим решением:
- не общие: поддерживает только тип поплавка, в то время как
genfromtxt
обнаруживает типы,которые могут варьироваться от столбца к столбцу; также отсутствуют значения, преобразователи, пропуск и т. д.; - не эффективный: когда условие трудно, каждая строка может быть в два раза разбирается, и используемые структуры данных и чтение bufferization может быть неоптимальной;
- требует написания кода.
есть ли стандартная функция, реализующая фильтрацию, или какой-то аналог MATLAB's textscan
?
3 ответов
я могу придумать два подхода, которые обеспечивают некоторые из функций, которые вы просите:
для чтения файла либо в кусках / или шагами n-строк / etc.:
Вы можете пройтиgenerator
to и NumPy.genfromtxt а так же и NumPy.loadtxt. Таким образом, вы можете загрузить большой набор данных из памяти текстового файла-эффективно, сохраняя все удобные функции разбора двух функции.для чтения данных только из строк, соответствующих критерию, который может быть выражен как регулярное выражение:
Вы можете использовать и NumPy.fromregex и использоватьregular expression
to точно определите, какие токены из данной строки во входном файле должны быть загружены. Строки, не соответствующие шаблону будут игнорироваться.
чтобы проиллюстрировать два подхода, я собираюсь использовать пример из моего исследования контекст.
Мне часто нужно загружать файлы со следующей структурой:
6
generated by VMD
CM 5.420501 3.880814 6.988216
HM1 5.645992 2.839786 7.044024
HM2 5.707437 4.336298 7.926170
HM3 4.279596 4.059821 7.029471
OD1 3.587806 6.069084 8.018103
OD2 4.504519 4.977242 9.709150
6
generated by VMD
CM 5.421396 3.878586 6.989128
HM1 5.639769 2.841884 7.045364
HM2 5.707584 4.343513 7.928119
HM3 4.277448 4.057222 7.022429
OD1 3.588119 6.069086 8.017814
эти файлы могут быть огромными (GBs), и меня интересуют только числовые данные. Все блоки данных имеют одинаковый размер -- 6
в этом примере-и они всегда отделены двумя линиями. Так что stride
блоков 8
.
используя первый подход:
сначала я собираюсь определить генератор, который отфильтровывает нежелательные строки:
def filter_lines(f, stride):
for i, line in enumerate(f):
if i%stride and (i-1)%stride:
yield line
затем я открываю файл, создать filter_lines
-генератор (здесь мне нужно знать stride
), и передайте этот генератор genfromtxt
:
with open(fname) as f:
data = np.genfromtxt(filter_lines(f, 8),
dtype='f',
usecols=(1, 2, 3))
это работает, как ветер. Обратите внимание, что я могу использовать usecols
чтобы избавиться от первого столбца данных. Таким же образом, вы можете использовать все другие возможности genfromtxt
-- обнаруживать типы, меняя типы от столбца к столбцу, отсутствующие значения, конвертеры, etc.
в этом примере data.shape
был (204000, 3)
в то время как исходный файл состоял из 272000
строки.
generator
используется для фильтрации однородно полосатых линий, но можно также представить, что он фильтрует неоднородные блоки линий на основе (простых) критериев.
используя второй подход:
здесь regexp
Я собираюсь использовать:
regexp = r'\s+\w+' + r'\s+([-.0-9]+)' * 3 + r'\s*\n'
группы -- т. е. внутри ()
-- определите токены, которые будут извлечены из данной строки.
Следующий, fromregex
выполняет задание и игнорирует строки, не соответствующие шаблону:
data = np.fromregex(fname, regexp, dtype='f')
результат точно такой же, как в первом варианте.
если вы передадите список типов (условие формата), используйте блок try и используйте yield для использования genfromtxt в качестве генератора, мы сможем реплицировать textscan()
.
def genfromtext(fname, formatTypes):
with open(fname, 'r') as file:
for line in file:
try:
line = line.split(',') # Do you care about line anymore?
r = []
for type, cell in zip(formatTypes, line):
r.append(type(cell))
except:
pass # Fail silently on this line since we hit an error
yield r
Edit:я забыл блок except. Теперь он работает нормально, и вы можете использовать genfromtext в качестве генератора (используя случайный журнал CSV, который у меня есть):
>>> a = genfromtext('log.txt', [str, str, str, int])
>>> a.next()
['10.10.9.45', ' 2013/01/17 16:29:26', '00:00:36', 0]
>>> a.next()
['10.10.9.45', ' 2013/01/17 16:22:20', '00:08:14', 0]
>>> a.next()
['10.10.9.45', ' 2013/01/17 16:31:05', '00:00:11', 3]
Я, вероятно, должен отметить, что я использую zip
для того чтобы застегнуть совместно линию разделения запятой и formatSpec которая tuplify два списка (остановка, когда в одном из списков заканчиваются элементы), поэтому мы можем перебирать их вместе, избегая цикла, зависящего от len(line)
или что-то подобное.
попытка продемонстрировать комментарий к OP.
def fread(name, cond):
with open(name) as file:
for line in file:
if cond(line):
yield line.split()
def a_genfromtxt_cond(fname, cond=(lambda str: True)):
"""Seems to work without need to convert to float."""
return np.array(list(fread(fname, cond)), dtype=np.float64)
def b_genfromtxt_cond(fname, cond=(lambda str: True)):
r = [[int(float(i)) for i in l] for l in fread(fname, cond)]
return np.array(r, dtype=np.integer)
a = a_genfromtxt_cond("tar.data")
print a
aa = b_genfromtxt_cond("tar.data")
print aa
выход
[[ 1. 2.3 4.5]
[ 4.7 9.2 6.7]
[ 4.7 1.8 4.3]]
[[1 2 4]
[4 9 6]
[4 1 4]]