Как импортировать несколько листов Excel в панды с многопроцессорной обработкой? - PullRequest
3 голосов
/ 04 апреля 2019

Я пытаюсь использовать многопроцессорную обработку на 12-ядерном компьютере для чтения файла Excel - файл размером 60 МБ с 15 листами и 10000 строк каждый. Импорт всех листов с помощью pandas.read_csv без распараллеливания занимает около 33 секунд.

Если я использую pool.map (), он работает, но занимает больше времени, чем непараллельная версия: 150 секунд против 33!

Если я использую pool.map_async (), это займет 36 секунд, но я не могу получить доступ (и поэтому не могу проверить) к выводу!

Мои вопросы:

  • что я делаю не так? и pool.map, и pool.map_async занимают примерно в то же время, даже если я установил nrows = 10 в read_single_sheet функция; в то же время, читает ли он 10 строк или 10 000 - как это возможно?
  • Как я могу получить результаты pool.map_async ()? я пытался output = [p.get() for p in dataframes] но это не работает:

MapResult объект не повторяется

  • Является ли это скорее IO-привязанным, чем CPU проблема? Тем не менее, почему pool.map занимает так много времени?

Чтение тех же данных из CSV (каждый лист Excel, сохраненный в отдельном CSV) занимает 2 секунды на моем аппарате. Тем не менее, CSV не очень хороший вариант для того, что мне нужно делать. Я часто имею от 10 до 20 вкладок среднего размера; Преобразование их вручную часто может занять больше времени, чем ожидание чтения пандами, плюс, если я получу обновленные версии, мне придется снова выполнить ручное преобразование.

Я знаю, что мог бы использовать скрипт VBA в Excel для автоматического сохранения каждого листа в CSV, но типы данных чаще всего выводятся правильно при чтении из Excel - не так с CSV, особенно для дат (мои даты никогда не бывают равны ISO гггг- mm-dd): мне нужно будет определить поля даты, указать формат и т. д. - просто чтение из Excel часто будет быстрее. Тем более, что эти задачи, как правило, одноразовые: я импортирую данные один раз, может быть, два или три раза, если получаю обновление, сохраняю их в SQL, а затем все мои скрипты Python читают из SQL.

Код, который я использую для чтения файла:

import numpy as np
import pandas as pd
import time
import multiprocessing
from multiprocessing import Pool
def parallel_read():
    pool = Pool(num_cores)
    # reads 1 row only, to retrieve column names and sheet names
    mydic = pd.read_excel('excel_write_example.xlsx', nrows=1, sheet_name=None)
    sheets =[]
    for d in mydic:
        sheets.extend([d])
    dataframes  = pool.map( read_single_sheet , sheets  )
    return dataframes

def parallel_read_async():
    pool = Pool(num_cores)
    # reads 1 row only, to retrieve column names and sheet names
    mydic = pd.read_excel('excel_write_example.xlsx', nrows=1, sheet_name=None)
    sheets =[]
    for d in mydic:
        sheets.extend([d])
    dataframes  = pool.map_async( read_single_sheet , sheets  ) 
    output = None
    # this below doesn`t work - can`t understand why
    output = [p.get() for p in dataframes]
    return output

def read_single_sheet(sheet):
    out = pd.read_excel('excel_write_example.xlsx', sheet_name=sheet )
    return out

num_cores = multiprocessing.cpu_count() 

if __name__=='__main__':
    start=time.time()
    out_p = parallel_read()
    time_par = time.time() -start

    out_as = parallel_read_async()
    time_as = time.time() - start - time_par

Код, который я использовал для создания Excel:

import numpy as np
import pandas as pd

sheets = 15
rows= int(10e3)

writer = pd.ExcelWriter('excel_write_example.xlsx')

def create_data(sheets, rows):
    df = {} # dictionary of dataframes
    for i in range(sheets):
        df[i] = pd.DataFrame(data= np.random.rand(rows,30) )
        df[i]['a'] = 'some long random text'
        df[i]['b'] = 'some more random text'
        df[i]['c'] = 'yet more text'
    return df

def data_to_excel(df, writer):
    for d in df:
        df[d].to_excel(writer, sheet_name = str(d), index=False)
    writer.close()

df = create_data(sheets, rows)
data_to_excel(df, writer)

Ответы [ 3 ]

2 голосов
/ 04 апреля 2019

Я публикую это как ответ, потому что, хотя он не отвечает на вопрос о том, как это сделать в Python, он все же предоставляет реальную альтернативу для существенного ускорения времени чтения, поэтому он можетинтерес к любому пользователю Python ;кроме того, он опирается только на программное обеспечение с открытым исходным кодом и требует, чтобы пользователь выучил только пару команд на R.

Мое решение: сделать это на R!

Я написал об этом здесь , который также показывает мой (очень минимальный) код;в основном, в том же файле R readxl заняла 5,6 секунды.Напомним:

  • Python от xlsx: 33 секунды
  • Python от CSV: ок.2 секунды
  • R из xlsx: 5,6 секунды

Ссылка также содержит ответ, показывающий, что распараллеливание может еще больше ускорить процесс.

Я считаю, что ключРазница в том, что pandas.read_cs v опирается на код C, а pandas.read_excel полагается на больше кода Python.R readxl, вероятно, основан на C. Возможно, можно использовать синтаксический анализатор C для импорта файлов xlsx в Python, но на данный момент такой синтаксический анализатор недоступен.

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

Так что изменения в рабочем процессе минимальны: начальная загрузка данных, которая, по крайней мере в моем случае, имеет тенденцию быть одноразовой, находится в R, а все остальное продолжает оставаться вPython.

Я также заметил, что экспорт тех же листов в SQL намного быстрее с R и DBI::dbWriteTable, чем с pandas (4,25 с против 18,4 с).

1 голос
/ 04 апреля 2019

Здесь происходит пара вещей:

  • 36 секунд, которые parallel_read_async, кажется, занимают, фактически полностью заняты вызовом pd.read_excel('excel_write_example.xlsx', nrows=1, sheet_name=None). map_async немедленно возвращается, давая вам объект MapResult, и вы немедленно вызываете исключение, пытаясь выполнить итерации по нему, поэтому в этой версии по существу не выполняется никакая работа с помощью функции read_single_sheet.
  • Кроме того, pd.read_excel с sheet_name=None занимает ровно столько же времени, сколько с sheet_name='1' и т. Д., Поэтому в вашей функции parallel_read каждый процесс выполняет работу по разбору каждой строки каждого листа. Вот почему это занимает гораздо больше времени.

И теперь, когда я выписал, я помню, что моя компания столкнулась с этой же проблемой, и в результате мы создали собственный синтаксический анализатор xlsx. С xlrd, который использует pandas, просто нет возможности открыть файл xlsx без его полного анализа.

Если у вас есть возможность производить (или получать?) Файлы xls, с ними работать намного быстрее. Кроме того, вариант экспорта в CSV может быть лучшим выбором, если скорость непараллельной обработки неприемлема.

0 голосов
/ 04 апреля 2019

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

import io
import xlrd
from functools import partial

def read_sheet(buff, sheetname):
    # reads 1 row only, to retrieve column names and sheet names
    df = pd.read_excel(buff, sheetname=sheetname)
    return df

if __name__=='__main__':
    start=time.time()
    time_par = time.time() -start
    xl = xlrd.open_workbook("myfile.xls")  # you fill in this
    sheets = xl.book.sheet_names()
    buff = io.BytesIO()
    xl.dump(buff)
    buff.seek(0)
    target = partial(read_sheet, buff)
    with Pool(num_processes) as p:
        dfs = p.map(target, sheetnames)
    time_as = time.time() - start - time_par

...