Pandas Эффективность массива экземпляров данных в Object для массовой вставки в БД - PullRequest
1 голос
/ 31 января 2020

У меня есть Pandas фрейм данных в виде:

Time    Temperature    Voltage    Current
0.0     7.8            14         56
0.1     7.9            12         58
0.2     7.6            15         55
... So on for a few hundred thousand rows...

Мне нужно как можно быстрее массово вставить данные в базу данных PostgreSQL. Это для проекта Django, и в настоящее время я использую ORM для операций с БД и построения запросов, но открыта для предложений, если есть более эффективные способы выполнения sh задачи.

Мои данные Модель выглядит следующим образом:

class Data(models.Model):
    time = models.DateTimeField(db_index=True)
    parameter = models.ForeignKey(Parameter, on_delete=models.CASCADE)
    parameter_value = models.FloatField()

Итак, Time - это row[0] DataFrame, а затем для каждого столбца заголовка я беру значение, соответствующее ему, используя заголовок как parameter. Таким образом, row[0] примера таблицы сгенерирует 3 Data объектов в моей базе данных:

Data(time=0.0, parameter="Temperature", parameter_value=7.8)
Data(time=0.0, parameter="Voltage", parameter_value=14)
Data(time=0.0, parameter="Current", parameter_value=56)

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

Мое текущее решение - go через каждую строку, создать один Data Объект для каждой строки в time + parameter + value и добавить указанный объект в массив, чтобы я мог от Data.objects.bulk_create(all_data_objects) до Django. Конечно, я знаю, что это неэффективно и, вероятно, может быть значительно улучшено.

Используя этот код:

# Convert DataFrame to dict
df_records = df.to_dict('records')

# Start empty dta array
all_data_objects = []

# Go through each row creating objects and appending to data array
for row in df_records:
    for parameter, parameter_value in row.items():
        if parameter != "Time":
            all_data_objects.append(Data(
                    time=row["Time"],
                    parameter_value=parameter_value,
                    parameter=parameter))

# Commit data to Postgres DB
Data.objects.bulk_create(all_data)

В настоящее время вся операция, без БД Включенная операция вставки (запись на диск), то есть просто создание массива объектов Data, для файла размером 55 МБ, генерирующего около 6 миллионов отдельных Data объектов, занимает около 370 секунд. Просто линия df_records = df.to_dict('records') занимает 83i sh секунды. Времена были измерены с использованием time.time() на обоих концах каждого участка и расчета разности.

Как я могу улучшить это время?

Ответы [ 2 ]

3 голосов
/ 04 февраля 2020

Если вам действительно нужно быстрое решение Я предлагаю вам тупить таблицу напрямую, используя pandas.

Сначала давайте создадим данные для вашего примера:

import pandas as pd

data = {
    'Time': {0: 0.0, 1: 0.1, 2: 0.2},
    'Temperature': {0: 7.8, 1: 7.9, 2: 7.6},
    'Voltage': {0: 14, 1: 12, 2: 15},
    'Current': {0: 56, 1: 58, 2: 55}
}
df = pd.DataFrame(data)

Теперь вы должны преобразовать фрейм данных так, чтобы у вас были нужные столбцы с melt:

df = df.melt(["Time"], var_name="parameter", value_name="parameter_value")

В этот момент вы должны сопоставить значения parameter с чужими id. Я буду использовать params в качестве примера:

params = {"Temperature": 1, "Voltage": 2, "Current": 3}
df["parameter"] = df["parameter"].map(params)

На этом этапе фрейм данных будет выглядеть так:

   Time  parameter  parameter_value
0   0.0          1              7.8
1   0.1          1              7.9
2   0.2          1              7.6
3   0.0          2             14.0
4   0.1          2             12.0
5   0.2          2             15.0
6   0.0          3             56.0
7   0.1          3             58.0
8   0.2          3             55.0

А теперь для экспорта используйте pandas вы можете использовать:

import sqlalchemy as sa
engine = sa.create_engine("use your connection data")
df.to_sql(name="my_table", con=engine, if_exists="append", index=False)

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

from io import StringIO

output = StringIO()
df.to_csv(output, sep=';', header=False, index=False, columns=df.columns)
output.getvalue()
# jump to start of stream
output.seek(0)

# Insert df into postgre
connection = engine.raw_connection()
with connection.cursor() as cursor:
    cursor.copy_from(output, "my_table", sep=';', null="NULL", columns=(df.columns))
    connection.commit()

Мы пробовали это несколько миллионов, и это был самый быстрый способ при использовании PostgreSQL.

1 голос
/ 03 февраля 2020

Вам не нужно создавать объект данных для всех строк. SqlAlchemy также поддерживает массовую вставку следующим образом:

data.insert().values([
                    dict(time=0.0, parameter="Temperature", parameter_value=7.8),
                    dict(time=0.0, parameter="Voltage", parameter_value=14)
                ])

Подробнее см. https://docs.sqlalchemy.org/en/13/core/dml.html?highlight=insert%20values#sqlalchemy. sql .expression.ValuesBase.values ​​.

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

...