Быстрый, гибкий, простой и интуитивно понятный: как ускорить ваши проекты Pandas

Быстрый, гибкий, простой и интуитивно понятный: как ускорить ваши проекты Pandas

Если вы работаете с большими наборами данных, вы, вероятно, помните момент «ага» на пути к Python, когда вы обнаружили библиотекуPandas. Pandas меняет правила игры дляdata science and analytics, особенно если вы пришли к Python, потому что искали что-то более мощное, чем Excel и VBA.

Так что же такого в Пандах, которые так любят ученых, аналитиков и инженеров, как я? Ну, документация Pandas говорит, что она использует:

«fast,flexible и выразительные структуры данных, предназначенные для работы с« реляционными »или« помеченными »данными какeasy, так иintuitive».

Быстро, гибко, легко и интуитивно понятно? Это звучит великолепно! Если ваша работа связана с созданием сложных моделей данных, вы не хотите тратить половину своих часов разработки на ожидание, пока модули перелистывают большие наборы данных. Вы хотите посвятить свое время и силы интерпретации ваших данных, а не кропотливо возиться с менее мощными инструментами.

Но я слышал, что панды медленнее ...

Когда я впервые начал использовать Pandas, мне посоветовали, что, хотя это был отличный инструмент для распространения данных, Pandas был слишком медленным для использования в качестве инструмента статистического моделирования. Начиная это оказалось правдой. Я потратил больше нескольких минут, перебирая свои большие пальцы, ожидая, пока Панды перетрут данные.

Но потом я узнал, что Pandas построен поверх структуры массива NumPy, и поэтому многие его операции выполняются на C, либо через NumPy, либо через собственныеlibrary модулей расширения Python от Pandas, написанные на Cython. и скомпилирован в C. Так разве Панды тоже не должны быть быстрыми?

Так и должно быть, если вы используете его так, как было задумано!

Парадокс в том, что то, что в противном случае могло бы «выглядеть» как кодPythonic, может быть неоптимальным в Pandas с точки зрения эффективности. Как и NumPy,Pandas is designed for vectorized operations, которые работают со всеми столбцами или наборами данных за один проход. Размышление о каждой «клетке» или ряду в отдельности должно быть последним средством, а не первым

Этот урок

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

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

В этом уроке вы расскажете следующее:

  • Преимущества использования данныхdatetime с временными рядами

  • Самый эффективный способ выполнения пакетных расчетов

  • Экономия времени благодаря хранению данных в HDFStore

Чтобы продемонстрировать эти темы, я возьму пример из моей повседневной работы, которая рассматривает временные ряды потребления электроэнергии. После загрузки данных вы будете постепенно переходить к более эффективным способам достижения конечного результата. Одна из поговорок, которая верна для большинства панд, заключается в том, что существует несколько способов добраться от А до Б. Однако это не означает, что все доступные параметры будут одинаково хорошо масштабироваться для более крупных и требовательных наборов данных.

Предполагая, что вы уже знаете, как выполнять базовые операцииdata selection in Pandas, приступим.

Задача под рукой

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

Давайте прочитаем наши данные изCSV file, в котором есть два столбца: один для даты и времени, а второй для потребляемой электроэнергии в киловатт-часах (кВтч):

CSV data

Строки содержат электроэнергию, использованную за каждый час, так что имеется365 x 24 = 8760 строк за весь год. В каждой строке указывается использование для «часового старта» в то время, поэтому 01.01.13 в 0:00 указывается использование для первого часа 1 января.

Экономия времени с данными даты и времени

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

>>>

>>> 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]

(Обратите внимание, что в этом случае вы также можете использовать PandasPeriodIndex.)

Теперь у вас есть DataFrame с именем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.

Результат? 1.6 seconds for 8760 rows of data. «Отлично, - скажете вы, - это совсем не время». Но что, если вы столкнетесь с большими наборами данных, скажем, один год использования электроэнергии с интервалом в одну минуту? Это в 60 раз больше данных, так что вам придется ждать около полутора минут. Это начинает звучать менее терпимо.

В действительности, я недавно проанализировал 10-часовые почасовые данные об электроэнергии с 330 сайтов. Как вы думаете, я ждал 88 минут, чтобы преобразовать даты? Точно нет!

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

>>>

>>> @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 seconds, which is 50 times faster! Таким образом, вы сэкономили около 86 минут времени на обработку моих 330 сайтов. Неплохое улучшение!

Еще одна тонкая деталь: даты в CSV не вISO 8601 format: вам понадобитсяYYYY-MM-DD HH:MM. Если вы не укажете формат, Pandas будет использовать пакетdateutil для преобразования каждой строки в дату.

И наоборот, если необработанные данные datetime уже находятся в формате ISO 8601, Pandas может немедленно использоватьfast route для анализа дат. Это одна из причин, почему явное представление о формате так полезно здесь. Другой вариант - передать параметрinfer_datetime_format=True, но, как правило, он имеет смысл быть явным.

Note: Pandas ’read_csv() также позволяет анализировать даты как часть шага ввода-вывода файла. См. Параметрыparse_dates,infer_datetime_format иdate_parser.

Простой цикл по данным Pandas

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

Тип тарифа Центов за кВтч Временной диапазон

Peak

28

С 17:00 до 24:00

плечо

20

С 7:00 до 17:00

Вне пик

12

С 0:00 до 7:00

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

>>>

>>> 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
# ...

Но наш расчет стоимости зависит от времени суток. Здесь вы увидите, что многие люди используют Панды так, как это не было задумано: написав цикл для выполнения условного вычисления.

В оставшейся части этого руководства вы начнете с далеко не идеального базового решения и перейдете к Pythonic-решению, полностью использующему Pandas.

Но что такое Pythonic в случае с Pandas? Ирония заключается в том, что это те, кто имеет опыт работы с другими (менее удобными для пользователя) языками программирования, такими как C ++ или Java, которые особенно подвержены этому, потому что они инстинктивно «думают в циклах».

Давайте посмотрим наloop approach, который не является питоническим и который многие люди используют, когда не знают, как 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

Вот цикл, который не является Pythonic, во всей его красе:

>>>

>>> # 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, dozс. "

Однако этот цикл неуклюжий. Вы можете считать вышеупомянутое «антипаттерном» в Пандах по нескольким причинам. Во-первых, он должен инициализировать список, в который будут записываться результаты.

Во-вторых, он использует непрозрачный объектrange(0, len(df)) для перебора, а затем после примененияapply_tariff() он должен добавить результат в список, который используется для создания нового столбца DataFrame. Он также выполняет то, что называетсяchained indexing с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 - это структура данных из модуля Pythoncollections, которая ведет себя как кортеж Python, но имеет поля, доступные с помощью поиска по атрибутам.

.iterrows() дает пары (кортежи) из (index,Series) для каждой строки в DataFrame.

Хотя.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 раз быстрее!

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

Панды ’.apply()

Вы можете дополнительно улучшить эту операцию, используя метод.apply() вместо.iterrows(). Метод.apply() Pandas принимает функции (вызываемые объекты) и применяет их вдоль оси DataFrame (все строки или все столбцы). В этом примереlambda function поможет вам передать два столбца данных в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? Одна хитрость состоит в том, чтобы выбрать и сгруппировать части DataFrame на основе ваших условий, а затем применить векторизованную операцию к каждой выбранной группе.

В следующем примере вы увидите, как выбрать строки с помощью метода.isin() Pandas, а затем применить соответствующий тариф в векторизованной операции. Перед тем, как вы это сделаете, будет немного удобнее, если вы установите столбецdate_time в качестве индекса DataFrame:

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]

Эти значения определяют, какие индексы DataFrame (datetime) попадают в указанный диапазон часов. Затем, когда вы передаете эти логические массивы в индексатор.loc DataFrame, вы получаете фрагмент DataFrame, который включает только строки, соответствующие этим часам. После этого нужно просто умножить срез на соответствующий тариф, что является быстрой векторизованной операцией.

Как это соотносится с нашими циклами операций выше? Во-первых, вы можете заметить, что вам больше не нуженapply_tariff(), потому что при выборе строк применяется вся условная логика. Таким образом, существует огромное сокращение количества строк кода, которые вы должны написать, и кода Python, который вызывается.

Как насчет времени обработки? В 315 раз быстрее, чем цикл, который не был Pythonic, примерно в 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 для каждого DataFrame, а затем интегрировать результаты обратно в структуры данных Pandas.

Не забывайте NumPy!

Одна вещь, которую не следует забывать при использовании Pandas, - это то, что серии Pandas и DataFrames созданы поверх библиотеки NumPy. Это дает вам еще большую гибкость вычислений, поскольку Pandas без проблем работает с массивами и операциями NumPy.

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

  1. Используйте векторизованные операции: методы и функции Pandas без циклов for.

  2. Используйте метод.apply() с вызываемым объектом.

  3. Используйте.itertuples(): перебирать строки DataFrame какnamedtuples из модуля Pythoncollections.

  4. Используйте.iterrows(): перебирать строки DataFrame как пары (index,pd.Series). Хотя серия Pandas представляет собой гибкую структуру данных, создание каждой строки в серию и последующий доступ к ней могут быть дорогостоящими.

  5. Используйте «поэлементно» для циклов, обновляя каждую ячейку или строку по одной за раз с помощьюdf.loc илиdf.iloc. (Или.at /.iat для быстрого скалярного доступа.)

Don’t Take My Word For It: Приведенный выше порядок приоритета является рекомендуемымstraight from a core Pandas developer.

Вот «порядок приоритета» выше в работе, с каждой функцией, которую вы построили здесь:

функция Время выполнения (секунды)

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

Очень полезная вещь, которую вы можете сделать здесь, - это предварительная обработка, а затем сохранение ваших данных в обработанной форме для использования при необходимости. Но как вы можете хранить данные в правильном формате без необходимости повторной обработки? Если бы вы сохранили как CSV, вы просто потеряли бы свои объекты даты и времени и должны были бы повторно обработать их при повторном доступе.

У Pandas есть встроенное решение для этого, которое используетHDF5, высокопроизводительный формат хранения, разработанный специально для хранения табличных массивов данных. КлассHDFStore в Pandas позволяет хранить DataFrame в файле HDF5, чтобы к нему можно было получить эффективный доступ, при этом сохраняя типы столбцов и другие метаданные. Это класс, похожий на словарь, поэтому вы можете читать и писать так же, как и для объекта Pythondict.

Вот как вы собираетесь сохранить предварительно обработанный DataFrame потребления электроэнергии,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 - этоfast,flexible,easy иintuitive, подумайте о том, как вы используете библиотеку.

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

  • По возможности старайтесь использоватьvectorized operations, а не подходить к проблемам с менталитетомfor x in df.... Если в вашем коде много циклов for, он может лучше подходить для работы с собственными структурами данных Python, потому что в противном случае Pandas идет с большими накладными расходами.

  • Если у вас есть более сложные операции, где векторизация просто невозможна или слишком сложна для эффективной работы, используйте метод.apply().

  • Если вам действительно нужно перебрать ваш массив (что действительно происходит), используйте.iterrows() или.itertuples(), чтобы улучшить скорость и синтаксис.

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

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

  • Интеграция NumPy в операции Pandas часто может повысить скорость и упростить синтаксис.

Related