Узнайте, как читать большие CSV-файлы, чтобы свести к минимуму использование памяти и время загрузки.

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

При работе с большими файлами CSV есть две основные проблемы:

  • Объем памяти, используемый при загрузке больших файлов CSV.
  • Количество времени, потраченное на загрузку больших CSV-файлов.

В идеале вы хотите свести к минимуму объем памяти вашего DataFrame, а также время его загрузки. В этой статье я расскажу вам об использовании примера набора данных.

Наш набор данных

В этой статье я буду использовать данные Японской торговой статистики, доступные по адресу https://www.kaggle.com/datasets/4e614ec846ab778f6a2ff166232d5a65f5e6786b4f5781690588bd2cccd71cb6?resource=download.

Тип лицензии: CC BY-SA 4.0

Этот набор данных содержит данные о торговле с 1988 по 2020 год. Он содержит более 100 миллионов строк, а файл CSV занимает колоссальные 4,5 ГБ. Таким образом, это идеальный набор данных для иллюстрации концепций, изложенных в этой статье.

Загрузка CSV-файла в Pandas DataFrame

Давайте сначала загрузим весь файл CSV с более чем 100 миллионами строк. Мне интересно посмотреть, сколько времени потребуется для загрузки DataFrame, а также его объем памяти:

import time
import pandas as pd

start = time.time()

df = pd.read_csv("custom_1988_2020.csv")

print(time.time() - start, ' seconds')
display(df)
display(df.info())

Вывод такой, как показано:

Общий объем памяти составляет колоссальные 6,8 ГБ (!), и мне потребовалось 30 секунд, чтобы загрузить его в Pandas DataFrame.

Для справки, я использую Mac Studio с 32 ГБ памяти.

Изучение столбцов

Давайте рассмотрим столбцы в кадре данных:

df.columns

Теперь вы должны понимать, что этот CSV-файл не имеет заголовка, и, следовательно, Pandas предположит, что первая строка в CSV-файле содержит заголовок:

Index(['198801', '1', '103', '100', '000000190', '0', '35843', '34353'], dtype='object')

Загрузка с заголовками

Поскольку у CSV-файла нет заголовка, по крайней мере, вы можете использовать параметр header, чтобы сообщить Pandas, что в CSV-файле нет заголовка:

# loading with no headers specified
df = pd.read_csv("custom_1988_2020.csv", header=None)
display(df)

Панды теперь будут автоматически называть столбцы, начиная с 0, затем 1 и так далее.

Из описания набора данных на https://www.kaggle.com/datasets/4e614ec846ab778f6a2ff166232d5a65f5e6786b4f5781690588bd2cccd71cb6?resource=download мы знаем значение различных столбцов:

  • гм (год + месяц)
  • exp_imp (экспорт: 1, импорт: 2)
  • hs9 (код СС)
  • Таможня
  • Страна
  • Q1
  • Q2(количество)
  • Стоимость (в тысячах иен)

Давайте назовем столбцы, используя параметр names:

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'])
display(df)

DataFrame теперь имеет следующие имена столбцов — «YearMonth», «ExportImport», «HSCode», «Customs', 'Страна', 'Q1', 'Q2_Quantity', 'Значение'.

Загрузка определенных столбцов

Поскольку файл CSV такой большой, следующий вопрос, который вы хотите задать себе, — действительно ли вам нужны все столбцы? Чтобы загрузить определенные столбцы, вы можете использовать параметр usecols, чтобы указать столбцы, которые вы хотите загрузить:

start = time.time()

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 usecols = ["YearMonth", "Value"])

print(time.time() - start, ' seconds')
display(df)
display(df.info())

Как вы можете видеть из приведенного выше вывода, объем памяти был уменьшен до 1,7 ГБ, а время, необходимое для ее загрузки, теперь уменьшено до 17 секунд.

Параметр usecols также поддерживает индекс позиции столбца. Приведенное выше также можно переписать, используя номера столбцов — 0 и 7:

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 usecols = [0, 7])

print(time.time() - start, ' seconds')
display(df)

Обратите внимание, что вы не можете использовать -1 для указания последнего столбца, например:

usecols = [0, -1])   # -1 is not supported

Параметр usecols также поддерживает лямбда-функцию. Например, если вы хотите получить все столбцы, кроме столбца Country, вы можете использовать следующее лямбда-выражение:

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 usecols = lambda column: column not in ['Country'])
display(df)

Столбец Страна теперь будет исключен из результатов.

Использование лямбда-функции в параметре usecols позволяет делать некоторые интересные вещи, такие как загрузка столбцов, имя которых содержит «Q», например:

usecols = lambda column: "Q" in column

Или чья длина имени столбца превышает, скажем, семь символов:

usecols = lambda column: len(column) > 7

Загрузка первых n строк

Во многих случаях вам не нужны все строки во всем CSV-файле. Возможно, первых 100 строк будет достаточно. Для этого вы можете использовать параметр nrows, чтобы указать первые n строк, которые вы хотите загрузить:

start = time.time()

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 nrows=100)

print(time.time() - start, ' seconds')
display(df[:15])
display(df.info())

Из приведенного выше результата вы видите, что теперь загрузка первых 100 строк занимает всего 0,1 секунды, а результирующий DataFrame занимает всего 6,4 КБ.

Пропуск строк

Также бывают случаи, когда вы можете захотеть пропустить определенные строки в файле CSV. Для этого используйте параметр skiprows:

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 skiprows=2,
                 nrows=100
)
display(df[:15])

Результат выше показывает, что первые две строки CSV-файла пропущены:

Вы также можете пропустить определенные строки:

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 skiprows=[0,2,4],
                 nrows=100
)
display(df[:15])

Приведенный выше результат показывает, что строки 0, 2 и 4 были пропущены:

Вы также можете использовать объект range, чтобы указать диапазон пропущенных строк:

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 skiprows=range(5,10),
                 nrows=100
)
display(df[:15])

Приведенный выше результат показывает, что строки с 5 по 9 были пропущены. Значение параметра skiprows также можно записать с помощью лямбда-функции, например:

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 skiprows=lambda x: 5 <= x < 10,
                 nrows=100
)

Используя лямбда-функцию, вы можете пропустить все четные строки:

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 skiprows=lambda x: x % 2 == 0,
                 nrows=100
)

print(time.time() - start, ' seconds')
display(df[:15])

Приведенный выше результат показывает, что все четные строки были пропущены:

Загрузка определенных строк

До этого момента вы научились загружать первые n строк, а также пропускать определенные строки в CSV-файле. Как насчет загрузки определенных строк в файле CSV? Нет параметра, позволяющего вам это сделать, но вы можете использовать параметр skiprows, чтобы получить то, что вы хотите.

Используя лямбда-функцию в параметре skiprows, вы можете указать, какие строки не пропускать (что по сути означает, какие строки вы хотите загрузить):

start = time.time()

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 skiprows=lambda x: x not in [1,3],
                 nrows=100
)

print(time.time() - start, ' seconds')
display(df[:15])
display(df.info())

Приведенный выше результат показывает, что строки с номерами 1 и 3 были сохранены:

Недостатком этого подхода является то, что необходимо сканировать весь CSV-файл, поэтому для загрузки только двух строк требуется 20 секунд.

Загрузка последних n строк

Последняя проблема, которую я хочу обсудить, — как загрузить последние n строк из CSV-файла. Хотя загрузить первые n строк несложно, загрузка последних n строк не так проста. Но вы можете использовать то, что вы узнали до этого момента, чтобы решить эту проблему.

Во-первых, подсчитайте, сколько строк в CSV-файле:

# read the last n rows
start = time.time()

row_count = sum(1 for l in open('custom_1988_2020.csv')) 

print(time.time() - start, ' seconds')
row_count

Поскольку в файле CSV более 100 миллионов строк, подсчет количества строк занял около 10 секунд. Также помните, что для этого CSV-файла нет заголовка. Итак, 113607322 — это фактическое количество строк записей.

Затем, чтобы загрузить последние 20 строк, используйте параметр skiprows и передайте ему лямбда-функцию, чтобы пропустить все строки, кроме последних 20:

# read the last n rows
start = time.time()

df = pd.read_csv("custom_1988_2020.csv", 
                 header=None, 
                 names=['YearMonth', 'ExportImport', 'HSCode', 'Customs', 
                        'Country', 'Q1', 'Q2_Quantity', 'Value'],                 
                 skiprows=lambda x: 0 <= x < row_count - 20,
                 nrows=100)

print(time.time() - start, ' seconds')
display(df)
display(df.info())

Результат показывает последние 20 строк, загруженных в Pandas DataFrame.

Как и в предыдущем разделе, недостатком является то, что весь файл CSV должен быть просканирован в процессе загрузки (отсюда 22 секунды для загрузки DataFrame).

Если вам нравится читать мои статьи и они помогли вашей карьере/учебе, рассмотрите возможность регистрации в качестве участника Medium. Это 5 долларов в месяц, и это дает вам неограниченный доступ ко всем статьям (включая мои) на Medium. Если вы зарегистрируетесь по следующей ссылке, я получу небольшую комиссию (без дополнительных затрат для вас). Ваша поддержка означает, что я смогу уделять больше времени написанию подобных статей.



Краткое содержание

В этой статье я рассмотрел множество методов загрузки Pandas DataFrame из CSV-файла. Часто нет необходимости загружать весь файл CSV в DataFrame. Вы не только экономите время, загружая только то, что вам нужно, но и экономите память, необходимую для хранения вашего DataFrame в памяти. В следующей статье я покажу вам методы уменьшения объема памяти вашего DataFrame. Следите за обновлениями!