Как ускорить чтение нескольких файлов и помещение данных в фреймворк?

У меня есть несколько текстовых файлов, скажем, 50, что мне нужно прочитать в массивный dataframe. На данный момент я использую следующие шаги.

  1. Прочитайте каждый файл и проверьте, что такое метки. Информация, которая мне нужна, часто содержится в первых нескольких строках. Те же ярлыки просто повторяются для остальной части файла, при этом каждый раз данные о них различаются.
  2. Создайте кадр данных с этими ярлыками.
  3. Прочтите файл еще раз и заполните dataframe значениями.
  4. Объедините этот блок данных с основным фреймворком данных.

Это очень хорошо подходит для файлов размером 100 КБ – несколько минут, но при 50 МБ это занимает всего несколько часов, и это не практично.

Как я могу оптимизировать свой код? В частности –

  1. Как я могу определить, какие функции занимают больше всего времени, которые мне нужно оптимизировать? Это чтение файла? Это запись в dataframe? Где моя программа тратит время?
  2. Должен ли я рассматривать многопоточность или многопроцессорность?
  3. Могу ли я улучшить алгоритм?
    • Возможно, прочитайте весь файл за один раз в списке, а не за строкой,
    • Разбирайте данные в кусках / весь файл, а не по строкам,
    • Назначьте данные в dataframe в кусках / один раз, а не по строкам.
  4. Есть ли что-то еще, что я могу сделать, чтобы сделать мой код быстрее?

Вот пример кода. Мой собственный код немного сложнее, поскольку текстовые файлы более сложны, так что я должен использовать около 10 регулярных выражений и несколько циклов while для считывания данных и размещения их в нужном месте в правильном массиве. Чтобы поддерживать MWE просто, я не использовал повторные метки во входных файлах для MWE, поэтому мне хотелось бы, чтобы я дважды читал файл без причины. Надеюсь это имеет смысл!

import re import pandas as pd df = pd.DataFrame() paths = ["../gitignore/test1.txt", "../gitignore/test2.txt"] reg_ex = re.compile('^(.+) (.+)\n') # read all files to determine what indices are available for path in paths: file_obj = open(path, 'r') print file_obj.readlines() ['a 1\n', 'b 2\n', 'end'] ['c 3\n', 'd 4\n', 'end'] indices = [] for path in paths: index = [] with open(path, 'r') as file_obj: line = True while line: try: line = file_obj.readline() match = reg_ex.match(line) index += match.group(1) except AttributeError: pass indices.append(index) # read files again and put data into a master dataframe for path, index in zip(paths, indices): subset_df = pd.DataFrame(index=index, columns=["Number"]) with open(path, 'r') as file_obj: line = True while line: try: line = file_obj.readline() match = reg_ex.match(line) subset_df.loc[[match.group(1)]] = match.group(2) except AttributeError: pass df = pd.concat([df, subset_df]).sort_index() print df Number a 1 b 2 c 3 d 4 

Мои входные файлы:

test1.txt

 a 1 b 2 end 

test2.txt

 c 3 d 4 end 

9 Solutions collect form web for “Как ускорить чтение нескольких файлов и помещение данных в фреймворк?”

Прежде чем вытащить многопроцессорный молоток, первым шагом должно быть некоторое профилирование. Используйте cProfile, чтобы быстро просмотреть, какие функции занимают много времени. К сожалению, если ваши строки находятся в одном вызове функции, они будут отображаться в виде вызовов библиотеки. line_profiler лучше, но требует немного больше времени настройки.

ЗАМЕТКА. Если вы используете ipython, вы можете использовать% timeit (магическая команда для модуля timeit) и% prun (магическая команда для модуля профиля) как для ваших заявлений, так и для функций. В поиске Google будут показаны некоторые руководства.

Панда – замечательная библиотека, но я был случайной жертвой плохого использования с ужасающими результатами. В частности, будьте осторожны с операциями append () / concat (). Это может быть вашим узким местом, но вы должны профиль, чтобы быть уверенным. Обычно операции numpy.vstack () и numpy.hstack () выполняются быстрее, если вам не нужно выполнять выравнивание индекса / столбца. В вашем случае похоже, что вы сможете обойтись с помощью серии или 1-D numpy ndarrays, которые могут сэкономить время.

BTW, блок try в python намного медленнее, часто 10 раз или больше, чем проверка недопустимого состояния, поэтому убедитесь, что вам это абсолютно необходимо, когда вы вставляете его в цикл для каждой отдельной строки. Вероятно, это еще один вредитель времени; Я предполагаю, что вы застряли блок try, чтобы проверить AttributeError в случае сбоя match.group (1). Сначала я должен проверить действительное соответствие.

Даже этих небольших модификаций должно быть достаточно, чтобы ваша программа работала значительно быстрее, прежде чем пытаться что-то радикальное, как многопроцессорность. Эти библиотеки Python являются удивительными, но приносят новый набор проблем для решения.

Я использовал это много раз, так как это очень простая реализация многопроцессорности.

 import pandas as pd from multiprocessing import Pool def reader(filename): return pd.read_excel(filename) def main(): pool = Pool(4) # number of cores you want to use file_list = [file1.xlsx, file2.xlsx, file3.xlsx, ...] df_list = pool.map(reader, file_list) #creates a list of the loaded df's df = pd.concat(df_list) # concatenates all the df's into a single df if __name__ == '__main__': main() 

Используя это, вы сможете существенно увеличить скорость своей программы без лишних усилий. Если вы не знаете, сколько процессоров у вас есть, вы можете проверить, потянув за свою оболочку и набрав

 echo %NUMBER_OF_PROCESSORS% 

EDIT: чтобы сделать этот запуск еще быстрее, подумайте об изменении ваших файлов в csvs и использовании функции pandas pandas.read_csv

Общие соображения python:

Прежде всего о измерении времени вы можете использовать такой фрагмент:

 from time import time, sleep class Timer(object): def __init__(self): self.last = time() def __call__(self): old = self.last self.last = time() return self.last - old @property def elapsed(self): return time() - self.last timer = Timer() sleep(2) print timer.elapsed print timer() sleep(1) print timer() 

Затем вы можете часто тестировать код запуска и проверять diff.

Об этом, я прокомментирую inline:

 with open(path, 'r') as file_obj: line = True while line: #iterate on realdines instead. try: line = file_obj.readline() match = reg_ex.match(line) index += match.group(1) #if match: # index.extend(match.group(1)) # or extend except AttributeError: pass 

Вы предыдущий код wat не очень pythonic, вы можете попробовать / исключить. Затем попробуйте только сделать на минимально возможных линиях.

Эти же уведомления применяются ко второму блоку кода.

Если вам нужно прочитать одни и те же файлы несколько раз. вы можете хранить их в ОЗУ с помощью StringIO или проще хранить {path: content} dict, который вы читаете только один раз.

Известно, что регулярное выражение Python работает медленно, ваши данные кажутся довольно простыми, вы можете использовать методы разделения и полосы на ваших входных линиях.

  striped=[l.split() for l in [c.strip() for c in file_desc.readlines()] if l] 

Я рекомендую вам прочитать это: https://gist.github.com/JeffPaine/6213790 видео соответствующего видео здесь https://www.youtube.com/watch?v=OSGv2VnC0go

Прежде всего, если вы читаете файл несколько раз, похоже, что это будет узким местом. Попробуйте прочитать файл в 1 строковый объект, а затем несколько раз используйте cStringIO .

Во-вторых, вы не показывали никаких причин для создания индексов перед чтением во всех файлах. Даже если вы это сделаете, почему вы используете Pandas для ввода-вывода? Кажется, вы можете создать его в обычных структурах данных python (возможно, используя __slots__ ), а затем поместить его в главный фрейм данных. Если вам не нужен индекс файла X до того, как вы прочитаете файл Y (как вам кажется 2-й цикл), вам просто нужно зацикливать файлы один раз.

В-третьих, вы можете использовать простые split / strip для строк, чтобы вытащить выделенные пробелы в пространстве, или, если это сложнее (есть строковые кавычки и т. Д.), Используйте CSV модуль из стандартной библиотеки Python. Пока вы не покажете, как вы на самом деле создаете свои данные, сложно предложить исправление, связанное с этим.

То, что вы показали до сих пор, можно сделать довольно быстро с помощью простого

 for path in paths: data = [] with open(path, 'r') as file_obj: for line in file_obj: try: d1, d2 = line.strip().split() except ValueError: pass data.append(d1, int(d2))) index, values = zip(*data) subset_df = pd.DataFrame({"Number": pd.Series(values, index=index)}) 

Вот разница в таймингах, когда я запускаю на виртуальной машине с дисковым пространством, не предварительно выделенным (сгенерированные файлы имеют размер примерно 24 МБ):

 import pandas as pd from random import randint from itertools import combinations from posix import fsync outfile = "indexValueInput" for suffix in ('1', '2'): with open(outfile+"_" + suffix, 'w') as f: for i, label in enumerate(combinations([chr(i) for i in range(ord('a'), ord('z')+1)], 8)) : val = randint(1, 1000000) print >>f, "%s %d" % (''.join(label), val) if i > 3999999: break print >>f, "end" fsync(f.fileno()) def readWithPandas(): data = [] with open(outfile + "_2", 'r') as file_obj: for line in file_obj: try: d1, d2 = str.split(line.strip()) except ValueError: pass data.append((d1, int(d2))) index, values = zip(*data) subset_df = pd.DataFrame({"Numbers": pd.Series(values, index=index)}) def readWithoutPandas(): data = [] with open(outfile+"_1", 'r') as file_obj: for line in file_obj: try: d1, d2 = str.split(line.strip()) except ValueError: pass data.append((d1, int(d2))) index, values = zip(*data) def time_func(func, *args): import time print "timing function", str(func.func_name) tStart = time.clock() func(*args) tEnd = time.clock() print "%f seconds " % (tEnd - tStart) time_func(readWithoutPandas) time_func(readWithPandas) 

Результирующее время:

 timing function readWithoutPandas 4.616853 seconds timing function readWithPandas 4.931765 seconds 

Вы можете попробовать эти функции с наращиванием индекса и посмотреть, какова будет разница во времени. Почти наверняка замедление происходит от нескольких чтений на диске. И поскольку Pandas не займет времени, чтобы создать вашу фреймворк из словаря, вам лучше понять, как создать свой индекс в чистом Python, прежде чем передавать данные в Pandas. Но сделайте как чтение данных, так и создание индекса в 1 диске.

Я думаю, еще одно предостережение в том, что если вы печатаете изнутри своего кода, ожидайте, что потребуется огромное количество времени. Время, затрачиваемое на то, чтобы писать обычный текст в два раза больше времени, необходимого для чтения / записи на диск.

Вы можете импортировать модель многопроцессорности и использовать пул рабочих процессов для одновременного открытия нескольких файлов в качестве файловых объектов, ускоряя загрузку части вашего кода. Чтобы проверить время, импортируйте функцию datetime и используйте следующий код:

 import datetime start=datetime.datetime.now() #part of your code goes here execTime1=datetime.datetime.now() print(execTime1-start) #the next part of your code goes here execTime2=datetime.datetime.now() print(execTime2-execTime1) 

Что касается чтения каждого файла только один раз, рассмотрите возможность использования другого сценария многопроцессорности для создания списка строк в каждом файле, поэтому вы можете проверить соответствие без операции ввода-вывода файлов.

Во-первых, используйте профайлер для вашего скрипта ( см. Этот вопрос) . Проанализируйте, какая именно часть потребляет больше времени. Посмотрите, можете ли вы его оптимизировать.

Во-вторых, я считаю, что чтение файла ввода-вывода, скорее всего, будет узким местом. Его можно оптимизировать с использованием параллельного подхода. Я бы предложил одновременное чтение файлов и создание фрейма данных. Каждый поток может вытолкнуть вновь созданный кадр данных в очередь. Очередь мониторинга основного потока может собирать кадры данных из очереди и объединять их с кадром основных данных.

Надеюсь это поможет.

1 создайте один выходной шаблон для файлов (например, кадр данных результата должен иметь столбец A, BC)

2 читайте каждый файл, преобразуйте его в выходной шаблон (который был установлен на шаге 1) и сохраните файл, например temp_idxx.csv, это можно сделать параллельно 🙂

3 объедините эти файлы temp_idxx.csv в один массивный файл и удалите временные файлы

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

Прочитайте файлы непосредственно в кадре данных pandas, используя pd.read_csv. Чтобы создать ваш subset_df. Используйте такие методы, как skipfooter, чтобы пропустить строки в конце файла, который, как вам известно, вам не нужен. Существует еще много доступных методов, которые могут заменить некоторые из функций цикла регулярного выражения, которые вы используете, например, error_bad_lines и skip_blank_lines.

Затем используйте инструменты, предоставляемые пандами, для очистки данных, которые не нужны.

Это позволит вам читать открытые и читать файл только один раз.

Ваш код не делает то, что вы описываете.

Вопрос : 1. Прочитайте каждый файл и проверьте, что такое метки. Информация, которая мне нужна, часто содержится в первых нескольких строках.

Но вы читаете весь файл, а не только несколько строк. Этот результат дважды читается в файлах!

Вопрос : 2. Снова прочитайте файл и заполните dataframe значениями.

Вы перезаписываете df['a'|'b'|'c'|'d'] в цикле снова и снова, что бесполезно
Я верю, что это не то, что вы хотите.
Это работает для данных, заданных в вопросе, но не для того, чтобы иметь дело с n значениями.


Предложение с другой логикой:

 data = {} for path in paths: with open(path, 'r') as file_obj: line = True while line: try: line = file_obj.readline() match = reg_ex.match(line) if match.group(1) not in data: data[ match.group(1) ] = [] data[match.group(1)].append( match.group(2) ) except AttributeError: pass print('data=%s' % data) df = pd.DataFrame.from_dict(data, orient='index').sort_index() df.rename(index=str, columns={0: "Number"}, inplace=True) 

Выход :

 data={'b': ['2'], 'a': ['1'], 'd': ['4'], 'c': ['3']} <class 'pandas.core.frame.DataFrame'> Index: 4 entries, a to d Data columns (total 1 columns): Number 4 non-null object dtypes: object(1) memory usage: 32.0+ bytes Number a 1 b 2 c 3 d 4 

Таблица времени :

  Code from Q: to_dict_from_dict 4 values 0:00:00.033071 0:00:00.022146 1000 values 0:00:08.267750 0:00:05.536500 10000 values 0:01:22.677500 0:00:55.365000 

Протестировано с помощью Python: 3.4.2 – pandas: 0.19.2 – re: 2.2.1

  • Вставка новых строк в кадр данных pandas по определенным индексам
  • Создание манекена в конвейере с различными уровнями в поезде и тестовом наборе
  • В чем разница между pandas ACF и statsmodel ACF?
  • Как заменить NaN на предыдущие значения в pandas DataFrame?
  • Pandas SQL chunksize
  • Обновить индекс после сортировки данных
  • Pandas - проверьте, если строковый столбец в одном фрейме содержит пару строк из другого фрейма данных
  • Фильтрация и выбор из сводных таблиц с использованием python pandas
  • Python - лучший язык программирования в мире.