Быстрый, гибкий, простой и интуитивно понятный: Как ускорить ваши проекты pandas
Оглавление
- Но Я Слышал, что панды Медлительны...
- Этот урок
- Стоящая перед нами задача
- Экономия времени с помощью данных Datetime
- Простое зацикливание на данных pandas
- Зацикливание с помощью .itertuples() и .iterrows()
- панды’ .применяйте()
- Выбор Данных С помощью .isin()
- Можем Ли Мы Сделать Это Лучше?
- Не забывай о Тупице!
- Предотвратите повторную обработку с помощью HDFStore
- Выводы
Если вы работаете с большими массивами данных, вы, вероятно, помните момент “ага” в своем путешествии по Python, когда вы обнаружили библиотеку pandas. pandas кардинально меняет правила игры для науки о данных и аналитики, особенно если вы пришли к Python, потому что искали что-то более мощное, чем Excel и VBA.
Так что же такого есть в pandas, что вызывает восторг у специалистов по обработке данных, аналитиков и инженеров вроде меня? Ну, в документации pandas говорится, что он использует:
“ быстрые, гибкие и выразительные структуры данных, разработанные для того, чтобы сделать работу с “реляционными” или “помеченными” данными одновременно простой и интуитивно понятной.”
Быстрый, гибкий, простой и интуитивно понятный? Звучит заманчиво! Если ваша работа связана с созданием сложных моделей данных, вы не захотите тратить половину времени на разработку, ожидая, пока модули обработают большие массивы данных. Вы хотите посвятить свое время и интеллектуальные способности интерпретации ваших данных, а не кропотливой работе с менее мощными инструментами.
Но Я Слышал, что панды Медлительны...
Когда я впервые начал использовать pandas, мне сообщили, что, хотя это отличный инструмент для анализа данных, pandas слишком медленный для использования в качестве инструмента статистического моделирования. На начальном этапе это оказалось правдой. Я потратил больше нескольких минут, бездельничая, ожидая, пока pandas обработает данные.
Но потом я узнал, что pandas построен поверх структуры массива NumPy, и поэтому многие его операции выполняются на C, либо с помощью NumPy, либо с помощью собственной библиотеки pandas , из Python модули расширения, написанные на Cython и скомпилированные в C. Так разве панды тоже не должны быть быстрыми?
Так и должно быть, если вы используете его так, как оно было задумано!
Парадокс заключается в том, что то, что в противном случае могло бы “выглядеть” как код на Python, может быть неоптимальным в pandas с точки зрения эффективности. Как и NumPy, pandas предназначен для векторизованных операций, которые обрабатывают целые столбцы или наборы данных за один проход. Рассмотрение каждой “ячейки” или строки в отдельности, как правило, должно быть последним средством, а не первым.
Это руководство
Чтобы было понятно, это не руководство по чрезмерной оптимизации кода pandas. pandas уже создан для быстрой работы при правильном использовании. Кроме того, существует большая разница между оптимизацией и написанием чистого кода.
Это руководство по использованию pandas на языке Python для получения максимальной отдачи от его мощных и простых в использовании встроенных функций. Кроме того, вы узнаете несколько практических советов по экономии времени, так что вам не придется тратить время впустую каждый раз, когда вы будете работать со своими данными.
В этом руководстве вы узнаете следующее:
- Преимущества использования
datetimeданных с временными рядами - Наиболее эффективный способ выполнения пакетных вычислений
- Экономия времени за счет хранения данных с помощью HDFStore
Чтобы продемонстрировать эти темы, я приведу пример из своей повседневной работы, в котором рассматривается временной ряд потребления электроэнергии. После загрузки данных вы последовательно перейдете к более эффективным способам получения конечного результата. Для большинства pandas справедливо утверждение, что существует несколько способов перейти от пункта А к пункту Б. Однако это не означает, что все доступные варианты одинаково хорошо масштабируются для более крупных и требовательных наборов данных.
Предполагая, что вы уже знаете, как выполнять некоторые базовые операции выбора данных в pandas, давайте начнем.
Текущая задача
Целью этого примера будет применение тарифов на электроэнергию с учетом времени использования для определения общей стоимости потребления энергии за один год. То есть в разное время суток цена на электроэнергию меняется, поэтому задача состоит в том, чтобы умножить потребленную за каждый час электроэнергию на правильную цену за тот час, в течение которого она была потреблена.
Давайте прочитаем наши данные из CSV-файла, который содержит два столбца: один для даты и времени, а другой для потребляемой электроэнергии в киловатт-часах (кВтч):
В строках указано количество электроэнергии, использованной за каждый час, таким образом, имеется 365 x 24 = 8760 строк за весь год. Каждая строка указывает на использование “начального часа” в данный момент, таким образом, 1/1/13 0:00 указывает на использование в течение первого часа 1 января.
Экономия времени с помощью данных Datetime
Первое, что вам нужно сделать, это прочитать ваши данные из CSV-файла с помощью одной из функций ввода-вывода pandas:
>>> import pandas as pd >>> pd.__version__ '0.23.1' # Make sure that `demand_profile.csv` is in your # current working directory. >>> df = pd.read_csv('demand_profile.csv') >>> df.head() date_time energy_kwh 0 1/1/13 0:00 0.586 1 1/1/13 1:00 0.580 2 1/1/13 2:00 0.572 3 1/1/13 3:00 0.596 4 1/1/13 4:00 0.592предварительно> кодовый блок>На первый взгляд все выглядит нормально, но есть небольшая проблема. в pandas и NumPy есть понятие
dtypes(типы данных). Если аргументы не указаны,date_timeпримет значениеobjectdtype:>>> df.dtypes date_time object energy_kwh float64 dtype: object >>> type(df.iat[0, 0]) strпредварительно> кодовый блок>Это не идеально.
objectявляется контейнером не только дляstr, но и для любого столбца, который не может быть четко вписан в один тип данных. Было бы сложно и неэффективно работать с датами в виде строк. (Это также было бы неэффективно с точки зрения памяти.)Для работы с данными временных рядов вам потребуется, чтобы столбец
date_timeбыл отформатирован как массив объектов datetime. (pandas называет этоTimestamp.) pandas делает каждый шаг здесь довольно простым:>>> df['date_time'] = pd.to_datetime(df['date_time']) >>> df['date_time'].dtype datetime64[ns]предварительно> кодовый блок>(Обратите внимание, что в качестве альтернативы вы могли бы использовать панд
PeriodIndexв этом случае.)Теперь у вас есть фрейм данных под названием
df, который очень похож на наш CSV-файл. В нем есть два столбца и числовой индекс для ссылок на строки.>>> df.head() date_time energy_kwh 0 2013-01-01 00:00:00 0.586 1 2013-01-01 01:00:00 0.580 2 2013-01-01 02:00:00 0.572 3 2013-01-01 03:00:00 0.596 4 2013-01-01 04:00:00 0.592предварительно> кодовый блок>Приведенный выше код прост и понятен, но насколько он быстр? Давайте протестируем это с помощью timing decorator, который я неоригинально назвал
@timeit. Этот декоратор в значительной степени имитируетtimeit.repeat()из стандартной библиотеки Python, но позволяет возвращать результат работы самой функции и выводить среднее время ее выполнения из нескольких попыток. (timeit.repeat()в Python возвращает результаты синхронизации, а не результат функции.)Создание функции и размещение
@timeitдекоратора непосредственно над ней будет означать, что каждый раз, когда функция вызывается, она будет синхронизироваться по времени. Декоратор запускает внешний и внутренний циклы:>>> @timeit(repeat=3, number=10) ... def convert(df, column_name): ... return pd.to_datetime(df[column_name]) >>> # Read in again so that we have `object` dtype to start >>> df['date_time'] = convert(df, 'date_time') Best of 3 trials with 10 function calls per trial: Function `convert` ran in average of 1.610 seconds.предварительно> кодовый блок>Результат? 8760 строк данных обрабатываются за 1,6 секунды. “Отлично”, - скажете вы, - “это совсем немного времени”. Но что, если вы столкнетесь с большими массивами данных — скажем, об использовании электроэнергии за один год с интервалом в одну минуту? Это в 60 раз больше данных, поэтому вам придется ждать около полутора минут. Это начинает казаться менее приемлемым.
На самом деле, недавно я проанализировал почасовые данные о потреблении электроэнергии за 10 лет с 330 сайтов. Вы думаете, я ждал 88 минут, чтобы перевести даты и время? Ни в коем случае!
Как вы можете ускорить это? Как правило, pandas работает тем быстрее, чем меньше ему приходится интерпретировать ваши данные. В этом случае вы увидите значительное повышение скорости, просто сообщив pandas, как выглядят ваши данные о времени и дате, используя параметр format. Вы можете сделать это, используя
strftimeкоды, найденные здесь и введя их следующим образом:>>> @timeit(repeat=3, number=100) >>> def convert_with_format(df, column_name): ... return pd.to_datetime(df[column_name], ... format='%d/%m/%y %H:%M') Best of 3 trials with 100 function calls per trial: Function `convert_with_format` ran in average of 0.032 seconds.предварительно> кодовый блок>Новый результат? 0,032 секунды, что в 50 раз быстрее! Таким образом, вы только что сэкономили около 86 минут времени обработки для моих 330 сайтов. Неплохое улучшение!
Еще одна важная деталь заключается в том, что даты и время в CSV не указаны в формате ISO 8601: вам понадобится
YYYY-MM-DD HH:MM. Если вы не укажете формат, pandas будет использовать пакетdateutilдля преобразования каждой строки в дату.И наоборот, если исходные данные datetime уже представлены в формате ISO 8601, pandas может немедленно перейти к быстрому анализу дат. Это одна из причин, по которой здесь так полезно четко указывать формат. Другой вариант - передать параметр
infer_datetime_format=True, но, как правило, стоит указывать явно.Внимание: панды’
read_csv()также позволяет анализировать даты в составе файлового ввода-вывода шаг. Смотрите параметрыparse_dates,infer_datetime_format, иdate_parser.Простое зацикливание на данных pandas
Теперь, когда даты и время указаны в удобном формате, вы можете приступить к расчету расходов на электроэнергию. Помните, что стоимость зависит от часа, поэтому вам нужно будет условно применять коэффициент затрат к каждому часу дня. В этом примере затраты времени использования будут определены следующим образом:
Tariff Type Cents per kWh Time Range Peak 28 17:00 to 24:00 Shoulder 20 7:00 to 17:00 Off-Peak 12 0:00 to 7:00 Если бы цена была фиксированной - 28 центов за киловатт-час в течение каждого часа дня, большинство людей, знакомых с pandas, знали бы, что этот расчет можно выполнить в одной строке:
>>> df['cost_cents'] = df['energy_kwh'] * 28предварительно> кодовый блок>В результате будет создан новый столбец со стоимостью электроэнергии за этот час:
date_time energy_kwh cost_cents 0 2013-01-01 00:00:00 0.586 16.408 1 2013-01-01 01:00:00 0.580 16.240 2 2013-01-01 02:00:00 0.572 16.016 3 2013-01-01 03:00:00 0.596 16.688 4 2013-01-01 04:00:00 0.592 16.576 # ...предварительно> кодовый блок>Но наш расчет стоимости зависит от времени суток. Именно здесь вы увидите, что многие люди используют pandas не так, как предполагалось: путем написания цикла для выполнения условного расчета.
В оставшейся части этого руководства вы начнете с неидеального базового решения и перейдете к решению на Python, которое полностью использует pandas.
Но что такое Pythonic в случае с пандами? Ирония заключается в том, что именно те, кто имеет опыт работы с другими (менее удобными для пользователя) языками программирования, такими как C++ или Java, особенно подвержены этому, потому что они инстинктивно “мыслят циклами”.
Давайте рассмотрим циклический подход, который не основан на Python и который многие люди используют, когда не знают, для чего предназначен pandas. Мы снова воспользуемся
@timeit, чтобы увидеть, насколько быстр этот подход.Сначала давайте создадим функцию для применения соответствующего тарифа к данному часу:
def apply_tariff(kwh, hour): """Calculates cost of electricity for given hour.""" if 0 <= hour < 7: rate = 12 elif 7 <= hour < 17: rate = 20 elif 17 <= hour < 24: rate = 28 else: raise ValueError(f'Invalid hour: {hour}') return rate * kwhпредварительно> кодовый блок>Вот цикл, который не является питоновским, во всей его красе:
>>> # NOTE: Don't do this! >>> @timeit(repeat=3, number=100) ... def apply_tariff_loop(df): ... """Calculate costs in loop. Modifies `df` inplace.""" ... energy_cost_list = [] ... for i in range(len(df)): ... # Get electricity used and hour of day ... energy_used = df.iloc[i]['energy_kwh'] ... hour = df.iloc[i]['date_time'].hour ... energy_cost = apply_tariff(energy_used, hour) ... energy_cost_list.append(energy_cost) ... df['cost_cents'] = energy_cost_list ... >>> apply_tariff_loop(df) Best of 3 trials with 100 function calls per trial: Function `apply_tariff_loop` ran in average of 3.152 seconds.предварительно> кодовый блок>Для людей, которые выбрали pandas после того, как некоторое время назад написали “чистый Python”, этот дизайн может показаться естественным: у вас есть типичный “для каждого x, при условии y, do z.”
Однако этот цикл является громоздким. Вы можете считать вышеописанное “антипаттерном” в pandas по нескольким причинам. Во-первых, ему необходимо инициализировать список, в который будут записываться выходные данные.
Во-вторых, он использует непрозрачный объект
range(0, len(df))для выполнения цикла, а затем, после примененияapply_tariff(), он должен добавить результат в список, который используется для создания нового столбца фрейма данных. Он также выполняет то, что называется цепной индексацией сdf.iloc[i]['date_time'], что часто приводит к непредвиденным результатам.Но самая большая проблема с этим подходом - временные затраты на вычисления. На моем компьютере этот цикл занял более 3 секунд для обработки 8760 строк данных. Далее вы рассмотрите некоторые улучшенные решения для итерации по структурам pandas.
Зацикливание с помощью
.itertuples()и.iterrows()Какие еще подходы вы можете использовать? Что ж, pandas фактически сделал синтаксис
for i in range(len(df))избыточным, введя методыDataFrame.itertuples()иDataFrame.iterrows(). Это оба метода генератора, которыеyieldобрабатывают по одной строке за раз.
.itertuples()возвращает значениеnamedtupleдля каждой строки со значением индекса строки в качестве первого элемента кортежа.nametuple- это структура данных из модуляcollectionsPython, которая ведет себя как кортеж Python, но имеет поля, доступные с помощью поиска по атрибутам.
.iterrows()возвращает пары (кортежи) значений (index,Series) для каждой строки во фрейме данных.Хотя
.itertuples()обычно получается немного быстрее, давайте остановимся на pandas и используем.iterrows()в этом примере, потому что некоторые читатели, возможно, не сталкивались сnametuple. Давайте посмотрим, чего это дает:>>> @timeit(repeat=3, number=100) ... def apply_tariff_iterrows(df): ... energy_cost_list = [] ... for index, row in df.iterrows(): ... # Get electricity used and hour of day ... energy_used = row['energy_kwh'] ... hour = row['date_time'].hour ... # Append cost list ... energy_cost = apply_tariff(energy_used, hour) ... energy_cost_list.append(energy_cost) ... df['cost_cents'] = energy_cost_list ... >>> apply_tariff_iterrows(df) Best of 3 trials with 100 function calls per trial: Function `apply_tariff_iterrows` ran in average of 0.713 seconds.предварительно> кодовый блок>Были достигнуты некоторые незначительные улучшения. Синтаксис стал более понятным, и в ссылках на значения строк стало меньше беспорядка, что делает его более читабельным. С точки зрения экономии времени, это почти в 5 раз быстрее!
Однако здесь есть еще много возможностей для улучшения. Вы все еще используете некоторую форму цикла Python
for, что означает, что каждый вызов функции выполняется на Python, хотя в идеале это можно было бы сделать на более быстром языке, встроенном во внутреннюю архитектуру pandas.панды’
.apply()Вы можете еще больше улучшить эту операцию, используя метод
.apply()вместо метода.iterrows(). Метод.apply()от pandas принимает функции (вызываемые объекты) и применяет их вдоль оси фрейма данных (ко всем строкам, или все столбцы). В этом примере лямбда-функция поможет вам передать два столбца данных вapply_tariff():>>> @timeit(repeat=3, number=100) ... def apply_tariff_withapply(df): ... df['cost_cents'] = df.apply( ... lambda row: apply_tariff( ... kwh=row['energy_kwh'], ... hour=row['date_time'].hour), ... axis=1) ... >>> apply_tariff_withapply(df) Best of 3 trials with 100 function calls per trial: Function `apply_tariff_withapply` ran in average of 0.272 seconds.предварительно> кодовый блок>Синтаксические преимущества
.apply()очевидны, они заключаются в значительном сокращении количества строк и очень удобочитаемом, понятном коде. В этом случае затраченное время было примерно вдвое меньше, чем при использовании метода.iterrows().Однако это еще не “невероятно быстро”. Одна из причин заключается в том, что
.apply()будет пытаться выполнить внутренний цикл по итераторам Cython. Но в этом случаеlambda, который вы передали, не может быть обработан в Cython, поэтому он вызывается в Python, что, следовательно, не так быстро.Если бы вы использовали
.apply()для моих 10-летних ежечасных данных по 330 сайтам, вам потребовалось бы около 15 минут на обработку. Если бы этот расчет был задуман как небольшая часть более крупной модели, вы бы действительно захотели ускорить процесс. Вот где пригодятся векторизованные операции.Выбор данных С помощью
.isin()Ранее вы видели, что если бы существовала единая цена на электроэнергию, вы могли бы применить эту цену ко всем данным о потреблении электроэнергии в одной строке кода (
df['energy_kwh'] * 28). Эта конкретная операция была примером векторизованной операции, и это самый быстрый способ выполнения задач в pandas.Но как вы можете применить вычисления условий в виде векторизованных операций в pandas? Одна из хитростей заключается в том, чтобы выбрать и сгруппировать части фрейма данных на основе ваших условий, а затем применить векторизованную операцию к каждой выбранной группе.
В следующем примере вы увидите, как выбрать строки с помощью метода pandas
.isin(), а затем применить соответствующий тариф в векторизованной операции. Прежде чем вы это сделаете, вам будет немного удобнее, если вы зададите столбецdate_timeв качестве индекса фрейма данных:df.set_index('date_time', inplace=True) @timeit(repeat=3, number=100) def apply_tariff_isin(df): # Define hour range Boolean arrays peak_hours = df.index.hour.isin(range(17, 24)) shoulder_hours = df.index.hour.isin(range(7, 17)) off_peak_hours = df.index.hour.isin(range(0, 7)) # Apply tariffs to hour ranges df.loc[peak_hours, 'cost_cents'] = df.loc[peak_hours, 'energy_kwh'] * 28 df.loc[shoulder_hours,'cost_cents'] = df.loc[shoulder_hours, 'energy_kwh'] * 20 df.loc[off_peak_hours,'cost_cents'] = df.loc[off_peak_hours, 'energy_kwh'] * 12предварительно> кодовый блок>Давайте посмотрим, как это соотносится:
>>> apply_tariff_isin(df) Best of 3 trials with 100 function calls per trial: Function `apply_tariff_isin` ran in average of 0.010 seconds.предварительно> кодовый блок>Чтобы понять, что происходит в этом коде, вам нужно знать, что метод
.isin()возвращает массив логических значений, который выглядит следующим образом:[False, False, False, ..., True, True, True]предварительно> кодовый блок>Эти значения определяют, какие индексы фреймов данных (даты и время) попадают в указанный часовой диапазон. Затем, когда вы передаете эти логические массивы индексатору
.locфрейма данных, вы получаете фрагмент фрейма данных, который содержит только строки, соответствующие этим часам. После этого остается просто умножить срез на соответствующий тариф, что представляет собой быструю векторизованную операцию.Как это соотносится с нашими циклическими операциями, описанными выше? Во-первых, вы можете заметить, что вам больше не нужен
apply_tariff(), потому что при выборе строк применяется вся условная логика. Таким образом, количество строк кода, которые вы должны написать, и количество вызываемого кода на Python значительно сокращаются.А как насчет времени обработки? В 315 раз быстрее, чем в цикле, который не был выполнен на языке Python, примерно в 71 раз быстрее, чем
.iterrows()и в 27 раз быстрее, чем.apply(). Теперь вы двигаетесь с той скоростью, которая вам необходима, чтобы легко и быстро справляться с большими массивами данных.Можем Ли Мы Добиться Большего Успеха?
В
apply_tariff_isin()мы, по общему признанию, все еще выполняем некоторую “ручную работу”, вызываяdf.locиdf.index.hour.isin()по три раза каждый. Вы могли бы возразить, что это решение не масштабируемо, если бы у нас был более детальный диапазон временных интервалов. (При разной скорости для каждого часа потребовалось бы 24.isin()звонков.) К счастью, в этом случае вы можете делать все еще более программно с помощью функции pandaspd.cut():@timeit(repeat=3, number=100) def apply_tariff_cut(df): cents_per_kwh = pd.cut(x=df.index.hour, bins=[0, 7, 17, 24], include_lowest=True, labels=[12, 20, 28]).astype(int) df['cost_cents'] = cents_per_kwh * df['energy_kwh']предварительно> кодовый блок>Давайте на секунду разберемся, что здесь происходит.
pd.cut()применяется набор меток (наши затраты) в соответствии с тем, к какой ячейке относится каждый час. Обратите внимание, что параметрinclude_lowestуказывает, должен ли первый интервал включаться слева или нет. (Вы хотите включитьtime=0в группу.)Это полностью векторизованный способ достижения желаемого результата, и он выходит на первое место по срокам:
>>> apply_tariff_cut(df) Best of 3 trials with 100 function calls per trial: Function `apply_tariff_cut` ran in average of 0.003 seconds.предварительно> кодовый блок>На данный момент обработка всего набора данных из 300 сайтов занимает от более часа до менее секунды. Неплохо! Однако есть еще один, последний вариант, который заключается в использовании функций NumPy для управления базовыми массивами NumPy для каждого фрейма данных, а затем для интеграции результатов обратно в структуры данных pandas.
Не забудь про NumPy!
При использовании pandas не следует забывать о том, что серии pandas и фреймы данных разработаны на основе библиотеки NumPy. Это обеспечивает еще большую гибкость вычислений, поскольку pandas легко работает с массивами и операциями NumPy.
В следующем случае вы будете использовать функцию NumPy
digitize(). Это похоже наcut()в pandas в том смысле, что данные будут сгруппированы, но на этот раз они будут представлены массивом индексов, представляющих, к какой ячейке относится каждый час. Затем эти индексы применяются к массиву цен:@timeit(repeat=3, number=100) def apply_tariff_digitize(df): prices = np.array([12, 20, 28]) bins = np.digitize(df.index.hour.values, bins=[7, 17, 24]) df['cost_cents'] = prices[bins] * df['energy_kwh'].valuesпредварительно> кодовый блок>Как и функция
cut(), этот синтаксис удивительно лаконичен и прост для чтения. Но каково его быстродействие? Давайте посмотрим:>>> apply_tariff_digitize(df) Best of 3 trials with 100 function calls per trial: Function `apply_tariff_digitize` ran in average of 0.002 seconds.предварительно> кодовый блок>На данный момент производительность все еще улучшается, но она становится все более незначительной. Вероятно, сейчас самое подходящее время закончить работу над улучшением кода и подумать о более широкой картине.
С помощью pandas можно, если хотите, поддерживать “иерархию” предпочтительных вариантов пакетных вычислений, как вы сделали здесь. Обычно они ранжируются от самых быстрых к самым медленным (и от наиболее гибких к наименее гибким):
- Используйте векторизованные операции: методы и функции pandas без
forциклов.- Используйте метод
.apply()с вызываемым элементом.- Используйте
.itertuples(): выполните итерацию по строкам фрейма данных какnamedtuplesиз модуляcollectionsPython.- Используйте
.iterrows(): выполняйте итерацию по строкам фрейма данных в виде пар (индекс,pd.Series). Хотя серия pandas представляет собой гибкую структуру данных, объединение каждой строки в серию и последующий доступ к ней могут быть дорогостоящими.- Используйте “поэлементно” для циклов, обновляя каждую ячейку или строку по очереди с помощью
df.locилиdf.iloc. (Или,.at/.iatдля быстрого скалярного доступа.)Не верьте Мне На Слово: Порядок следования, приведенный выше, является предложением непосредственно от разработчика core pandas.
Вы можете узнать больше о своих возможностях в разделе Как выполнять итерацию по строкам в pandas и почему этого не следует делать.
Вот приведенный выше “порядок приоритета” для каждой функции, которую вы создали здесь:
Function Runtime (seconds) apply_tariff_loop()3.152 apply_tariff_iterrows()0.713 apply_tariff_withapply()0.272 apply_tariff_isin()0.010 apply_tariff_cut()0.003 apply_tariff_digitize()0.002 Предотвратите повторную обработку с помощью HDFStore
Теперь, когда вы ознакомились с быстрой обработкой данных в pandas, давайте рассмотрим, как вообще избежать времени на повторную обработку с помощью HDFStore, который недавно был интегрирован в pandas.
Часто, когда вы создаете сложную модель данных, бывает удобно выполнить некоторую предварительную обработку ваших данных. Например, если у вас есть данные о минутном потреблении электроэнергии за 10 лет, простое преобразование даты и времени в datetime может занять 20 минут, даже если вы укажете параметр формата. На самом деле вы хотите сделать это только один раз, а не каждый раз, когда запускаете свою модель для тестирования или анализа.
Очень полезная вещь, которую вы можете здесь сделать, - это предварительно обработать, а затем сохранить ваши данные в обработанном виде, чтобы использовать их при необходимости. Но как вы можете хранить данные в нужном формате без необходимости их повторной обработки? Если бы вы сохранили файл в формате CSV, вы бы просто потеряли свои объекты datetime, и вам пришлось бы повторно обрабатывать их при повторном доступе.
в pandas есть встроенное решение для этого, которое использует HDF5, высокопроизводительный формат хранения, разработанный специально для хранения табличных массивов данных. Pandas’
HDFStoreкласс позволяет сохранять ваш фрейм данных в формате HDF5, чтобы к нему можно было эффективно обращаться, сохраняя при этом типы столбцов и другие метаданные. Это класс, похожий на словарь, поэтому вы можете читать и писать точно так же, как для объекта Pythondict.Вот как вы могли бы сохранить предварительно обработанный фрагмент данных о потреблении электроэнергии,
df, в файле HDF5:# Create storage object with filename `processed_data` data_store = pd.HDFStore('processed_data.h5') # Put DataFrame into the object setting the key as 'preprocessed_df' data_store['preprocessed_df'] = df data_store.close()предварительно> кодовый блок>Теперь вы можете выключить компьютер и отдохнуть, зная, что сможете вернуться и обработанные данные будут ждать вас, когда они вам понадобятся. Повторная обработка не требуется. Вот как вы могли бы получить доступ к своим данным из файла HDF5 с сохранением типов данных:
# Access data store data_store = pd.HDFStore('processed_data.h5') # Retrieve data using key preprocessed_df = data_store['preprocessed_df'] data_store.close()предварительно> кодовый блок>Хранилище данных может содержать несколько таблиц, имя каждой из которых является ключом.
Просто замечание об использовании HDFStore в pandas: у вас должны быть установлены PyTables >= 3.0.0, поэтому после установки pandas обязательно обновите PyTables следующим образом:
pip install --upgrade tablesпредварительно> кодовый блок>Выводы
Если вы не уверены, что ваш проект pandas будет быстрым, гибким, простым и интуитивно понятный подумайте о том, чтобы переосмыслить то, как вы используете библиотеку.
Примеры, которые вы здесь рассмотрели, довольно просты, но иллюстрируют, как правильное применение функций pandas может значительно улучшить время выполнения и читаемость кода при загрузке. Вот несколько практических правил, которые вы можете применить в следующий раз, когда будете работать с большими наборами данных в pandas:
<статус завершения article-slug="fast-flexible-pandas" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/fast-flexible-pandas/bookmark/" данные-api-article-завершение-статус-url="/api/версия 1/статьи/быстрые и гибкие-pandas/завершение_статуса/"> статус завершения> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей на Python:%0A%0AFast, гибкий, простой и интуитивно понятный: как ускорить ваши проекты на pandas" email-subject="Статья на Python для вас" twitter-text="Интересная статья на #Python от @realpython:" url="https://realpython.com/fast-flexible-pandas/" url-title="Быстрый, гибкий, простой и интуитивно понятный: как ускорить ваши проекты на pandas"> кнопка поделиться> Back to Top
Старайтесь использовать векторизованные операции там, где это возможно, вместо того, чтобы подходить к проблемам с точки зрения
for x in df.... Если в вашем коде содержится многоforциклов, возможно, он лучше подходит для работы с собственными структурами данных Python, поскольку в противном случае pandas сопряжен с большими накладными расходами.Если у вас есть более сложные операции, где векторизация просто невозможна или слишком сложна для эффективного выполнения, используйте метод
.apply().Если вам все-таки приходится перебирать массив (что случается), используйте
.iterrows()или.itertuples()для повышения скорости и улучшения синтаксиса.в pandas много вариантов, и почти всегда есть несколько способов добраться из пункта А в пункт Б. Помните об этом, сравните эффективность различных маршрутов и выберите тот, который лучше всего подходит для вашего проекта.
После создания сценария очистки данных избегайте повторной обработки, сохраняя промежуточные результаты в HDFStore.
Интеграция NumPy в операции pandas часто может повысить скорость и упростить синтаксис.