Подтвердить что ты не робот

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

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

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

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

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

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

Вот пример кода. Мой собственный код немного сложнее, поскольку текстовые файлы более сложны, так что я должен использовать около 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
4b9b3361

Ответ 1

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

Более быстрый способ сделать это - прочитать содержимое входного файла в примитивную структуру данных, такую ​​как список списков или список dicts, а затем преобразовать их в DataFrame.

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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 into a single df

if __name__ == '__main__':
    main()

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

echo %NUMBER_OF_PROCESSORS%

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

Ответ 4

Общие соображения 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

Ответ 5

Прежде всего, если вы читаете файл несколько раз, похоже, что это будет узким местом. Попробуйте прочитать файл в 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 диске.

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

Ответ 6

Вы можете импортировать модель многопроцессорности и использовать пул рабочих процессов для одновременного открытия нескольких файлов в виде файлов, что ускоряет загрузку части вашего кода. Чтобы проверить время, либо импортируйте функцию 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)

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

Ответ 7

Сначала используйте профилировщик для script (см. этот вопрос). Проанализируйте, какая именно часть потребляет больше времени. Посмотрите, можете ли вы его оптимизировать.

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

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

Ответ 8

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

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

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

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

Ответ 9

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

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

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

Ответ 10

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

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

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

Вопрос: 2. Снова прочитайте файл и заполните данные с помощью значений.

Вы перезаписываете 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