Как пользователь Python, я часто взаимодействую с файлами Excel для работы с данными, поскольку профессионалы предпочитают обмениваться информацией в форматах Excel или CSV. Однако скорость работы Python с файлами Excel довольно низкая.

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

Экспериментальная установка

Предположим, нам нужно загрузить 10 файлов Excel, каждый из которых содержит 20 000 строк и 25 столбцов, общим объемом около 70 МБ. Такой сценарий типичен при передаче транзакционных данных из ERP-системы, например SAP, в Python для анализа.

Прежде всего сгенерируем эти фиктивные данные и настроим среду, импортировав необходимые библиотеки (позже рассмотрим особенности использования таких библиотек, как Pickle и Joblib).

import pandas as pd
import numpy as np
from joblib import Parallel, delayed
import time

for file_number in range(10):
values = np.random.uniform(size=(20000,25))
pd.DataFrame(values).to_csv(f"Dummy {file_number}.csv")
pd.DataFrame(values).to_excel(f"Dummy {file_number}.xlsx")
pd.DataFrame(values).to_pickle(f"Dummy {file_number}.pickle")

5 способов загрузки данных в Python

1. Загрузка файлов Excel с помощью Pandas

Начнем с самого простого метода загрузки файлов Excel. Инициализируем DataFrame (датафрейм) Pandas и последовательно добавим в него каждый файл Excel. Такой подход обеспечивает ясный и прямой способ компиляции данных из нескольких источников в единую структуру для анализа.

start = time.time()
df = pd.read_excel("Dummy 0.xlsx")
for file_number in range(1,10):
df.append(pd.read_excel(f"Dummy {file_number}.xlsx"))
end = time.time()
print("Excel:", end - start)

Запуск занимает около 50 секунд. Довольно медленно.

2. Загрузка с помощью Pandas файлов Excel, преобразованных в формат CSV

После преобразования файлов Excel в формат CSV время загрузки значительно уменьшилось и составило всего 0,63 секунды, что почти в 10 раз быстрее, чем раньше. Python обычно обрабатывает CSV-файлы гораздо быстрее, чем файлы Excel, часто в 100 раз быстрее. Таким образом, использование CSV-файлов может быть весьма эффективной стратегией работы с большими наборами данных.

Однако заметным недостатком этого метода является то, что файлы CSV обычно имеют больший размер по сравнению с файлами .xlsx. Например, в нашем примере файлы CSV имеют размер 9,5 МБ каждый, в то время как файлы .xlsx — всего 6,4 МБ.

3. Более рациональное создание датафреймов в Pandas 

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

1. Загрузим каждый файл Excel или CSV в отдельный датафрейм.

2. Сохраним эти датафреймы в списке.

3. Объединим все датафреймы из списка в один датафрейм.

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

start = time.time()
df = []
for file_number in range(10):
temp = pd.read_csv(f"Dummy {file_number}.csv")
df.append(temp)
df = pd.concat(df, ignore_index=True)
end = time.time()
print("CSV2:", end - start)

Мы добились небольшого сокращения времени загрузки. На личном опыте я убедился в особой полезности этого метода при работе с большими датафреймами размером более 100 МБ.

4. Распараллеливание импорта CSV с помощью Joblib

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

Этот подход использует возможности библиотеки Joblib, которая упрощает параллельную обработку в Python. Joblib позволяет распределить задачи загрузки файлов между несколькими ядрами или потоками, значительно сократив общее время загрузки.

start = time.time()
def loop(file_number):
return pd.read_csv(f"Dummy {file_number}.csv")
df = Parallel(n_jobs=-1, verbose=10)(delayed(loop)(file_number) for file_number in range(10))
df = pd.concat(df, ignore_index=True)
end = time.time()
print("CSV//:", end - start)

Нам удалось удвоить скорость по сравнению с одноядерной версией. Однако важно отметить, что увеличение количества ядер не приводит к линейному росту производительности. Например, при использовании 8 ядер на Mac Air с чипом M1 я наблюдал 2-кратный, а не 8-кратный прирост скорости.

Упрощенное распараллеливание в Python с помощью Joblib

Joblib — библиотека Python, предназначенная для упрощенной параллельной обработки данных. Она работает аналогично списковому включению (list comprehension), но с существенным отличием: каждая итерация выполняется в отдельном потоке. Такой подход позволяет обрабатывать задачи одновременно. Вот как можно его реализовать:

def loop(file_number):
return pd.read_csv(f"Dummy {file_number}.csv")
df = Parallel(n_jobs=-1, verbose=10)(delayed(loop)(file_number) for file_number in range(10))

#равнозначно следующему:
df = [loop(file_number) for file_number in range(10)]

5. Использование файлов Pickle 

Чтобы еще больше ускорить процесс хранения и извлечения данных, воспользуемся файлами Pickle — специфическим форматом Python. Предназначенный для сериализации и десериализации объектов, Pickle позволяет ускорить загрузку данных по сравнению с файлами CSV.

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

start = time.time()
def loop(file_number):
return pd.read_pickle(f"Dummy {file_number}.pickle")
df = Parallel(n_jobs=-1, verbose=10)(delayed(loop)(file_number) for file_number in range(10))
df = pd.concat(df, ignore_index=True)
end = time.time()
print("Pickle//:", end - start)

Мы успешно сократили время обработки на 80%!

В целом, работа с файлами Pickle намного быстрее, чем с файлами CSV. Однако файлы Pickle обычно занимают больше места на жестком диске (хотя в данном примере это не так).

В действительности системы обычно не экспортируют данные непосредственно в формат Pickle. Рекомендую использовать файлы Pickle в следующих сценариях:

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

2. Для частого доступа к файлам. Если вы многократно загружаете один и тот же файл(ы), то после первоначальной загрузки его удобно сохранить в виде Pickle. Последующие процессы могут напрямую загружаться из Pickle-файла, минуя более медленную загрузку CSV-файлов.

Пример: для управления транзакционными данными, обновляемыми ежемесячно, можно после первой загрузки конвертировать данные за каждый месяц из CSV-формата в Pickle. Это позволит быстро получить доступ к историческим данным в формате Pickle, оптимизируя рабочий процесс каждый месяц по мере поступления новых данных.

Бонус: параллельная загрузка файлов Excel

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

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

start = time.time()
def loop(file_number):
return pd.read_excel(f"Dummy {file_number}.xlsx")
df = Parallel(n_jobs=-1, verbose=10)(delayed(loop)(file_number) for file_number in range(10))
df = pd.concat(df, ignore_index=True)
end = time.time()
print("Excel//:", end - start)

Нам удалось сократить время загрузки на 70% — с 50 секунд до 13 секунд.

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

Подведем итоги

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

  • Файлы Excel: первоначально загрузка занимала 50 секунд.
  • Файлы CSV: скорость загрузки оптимизирована до 0,63 секунды.
  • Более рациональная загрузка CSV: улучшение результата до 0,62 секунды.
  • Параллельная загрузка CSV-файлов: увеличение скорости до 0,34 секунды.
  • Параллельная загрузка Pickle-файлов: значительное повышение скорости до 0,07 секунды — менее десятой доли секунды.
  • Параллельная загрузка Excel-файлов: увеличение скорости до 13,5 секунды.

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

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Parvez Shah Shaik: Do You Read Excel Files with Python? There is a 1000x Faster Way

Предыдущая статьяЭволюция монолитных систем
Следующая статьяВажные вопросы для собеседования по корутинам для опытных разработчиков Android