Python - эффективный способ добавления строк в dataframe
Из этого question и других кажется, что не рекомендуется использовать concat
или append
для сборки pandas dataframe, потому что это каждый раз обрабатывая весь блок данных.
Мой проект включает в себя получение небольшого количества данных каждые 30 секунд. Это может зайти на 3-х дневный уик-энд, поэтому кто-то может легко ожидать более 8000 строк, которые будут созданы по одной строке за раз. Каким будет наиболее эффективный способ добавления строк в этот фреймворк?
Ответы
Ответ 1
Редактирование выбранного ответа здесь, так как он был полностью ошибочным. Далее следует объяснение того, почему вы не должны использовать настройку с увеличением. "Настройка с расширением" на самом деле хуже, чем добавление.
tl;dr здесь заключается в том, что не существует эффективного способа сделать это с помощью DataFrame, поэтому, если вам нужна скорость, вам следует вместо этого использовать другую структуру данных. См. другие ответы для лучших решений..
Подробнее о настройке с расширением
Вы можете добавить строки в DataFrame на месте, используя loc
для несуществующего индекса, но он также выполняет копирование всех данных (см. это обсуждение). Вот как это будет выглядеть из документации Pandas:
In [119]: dfi
Out[119]:
A B C
0 0 1 0
1 2 3 2
2 4 5 4
In [120]: dfi.loc[3] = 5
In [121]: dfi
Out[121]:
A B C
0 0 1 0
1 2 3 2
2 4 5 4
3 5 5 5
Для чего-то подобного описанному варианту использования настройка с увеличением фактически на 50% дольше, чем append
:
С append()
8000 строк заняли 6,59 с (0,8 мс на строку)
%%timeit df = pd.DataFrame(columns=["A", "B", "C"]); new_row = pd.Series({"A": 4, "B": 4, "C": 4})
for i in range(8000):
df = df.append(new_row, ignore_index=True)
# 6.59 s ± 53.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
С помощью .loc()
8000 строк заняли 10 с (1,25 мс на строку)
%%timeit df = pd.DataFrame(columns=["A", "B", "C"]); new_row = pd.Series({"A": 4, "B": 4, "C": 4})
for i in range(8000):
df.loc[i] = new_row
# 10.2 s ± 148 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
А как насчет более длинного DataFrame?
Как и в случае со всем профилированием в ориентированном на данные коде, YMMV и вы должны проверить это для своего варианта использования. Одной из характеристик поведения append
копирования и записи и "установки с увеличением" является то, что оно будет становиться все медленнее и медленнее при больших значениях DataFrame
:
%%timeit df = pd.DataFrame(columns=["A", "B", "C"]); new_row = pd.Series({"A": 4, "B": 4, "C": 4})
for i in range(16000):
df.loc[i] = new_row
# 23.7 s ± 286 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Построение строки в 16 КБ DataFrame
с помощью этого метода занимает в 2,3 раза больше, чем строки в 8 КБ.
Ответ 2
Я использовал этот ответ df.loc[i] = [new_data]
, но у меня> 500 000 строк, и это было очень медленно.
Хотя приведенные ответы хороши для вопроса OP, я нашел более эффективным, когда имеешь дело с большим количеством строк заранее (вместо хитрости, описанной в OP), использовать csvwriter для добавления данных в объект CSV в памяти, затем, наконец, используйте pandas.read_csv(csv)
для генерации желаемого вывода DataFrame.
from io import BytesIO
from csv import writer
import pandas as pd
output = BytesIO()
csv_writer = writer(output)
for row in iterable_object:
csv_writer.writerow(row)
output.seek(0) # we need to get back to the start of the BytesIO
df = pd.read_csv(output)
return df
Таким образом, ~ 500 000 строк были в 1000 раз быстрее, а с увеличением числа строк улучшение скорости будет только увеличиваться (the df.loc[1] = [data]
будет сравнительно медленнее)
Надеюсь, что это помогает кому-то, кто нуждается в эффективности при работе с большим количеством строк, чем OP
Ответ 3
Вам нужно разделить проблему на две части:
- Эффективное принятие данных (сбор) каждые 30 секунд.
- Обработка данных после их сбора.
Если ваши данные критические (т.е. вы не можете их потерять), отправьте их в очередь, а затем прочитайте их из очереди в пакетах.
Очередь обеспечит надежное (гарантированное) принятие и что ваши данные не будут потеряны.
Вы можете считывать данные из очереди и выгружать их в базу данных.
Теперь ваше приложение Python просто читает из базы данных и анализирует ли какой-либо промежуток времени для приложения - возможно, вы хотите делать ежечасные средние значения; в этом случае вы будете запускать свой script каждый час, чтобы вытащить данные из базы данных и, возможно, записать результаты в другую базу данных/таблицу/файл.
В нижней строке - разделить сбор и анализ частей вашего приложения.
Ответ 4
Предполагая, что ваш фрейм данных проиндексирован, вы можете:
Сначала проверьте, какое значение имеет следующий индекс, чтобы создать новую строку:
myindex = df.shape[0]+1
Затем используйте "at" для записи в каждый желаемый столбец
df.at[myindex,'A']=val1
df.at[myindex,'B']=val2
df.at[myindex,'C']=val3
Ответ 5
Ответ sundance может быть правильным с точки зрения использования, но эталонный тест просто неверен. Как правильно заметил Муби, в этом примере индекс 3 уже существует, что делает доступ более быстрым, чем при отсутствии индекса. Посмотри на это:
%%timeit
test = pd.DataFrame({"A": [1,2,3], "B": [1,2,3], "C": [1,2,3]})
for i in range(0,1000):
testrow = pd.DataFrame([0,0,0])
pd.concat([test[:1], testrow, test[1:]])
2,15 с ± 88 мс на цикл (среднее ± стандартное отклонение из 7 циклов, по 1 циклу каждый)
%%timeit
test = pd.DataFrame({"A": [1,2,3], "B": [1,2,3], "C": [1,2,3]})
for i in range(0,1000):
test2 = pd.DataFrame({'A': 0, 'B': 0, 'C': 0}, index=[i+0.5])
test.append(test2, ignore_index=False)
test.sort_index().reset_index(drop=True)
972 мс ± 14,4 мс на цикл (среднее ± стандартное отклонение из 7 циклов, по 1 циклу каждый)
%%timeit
test = pd.DataFrame({"A": [1,2,3], "B": [1,2,3], "C": [1,2,3]})
for i in range(0,1000):
test3 = [0,0,0]
test.loc[i+0.5] = test3
test.reset_index(drop=True)
1,13 с ± 46 мс на цикл (среднее ± стандартное отклонение из 7 циклов, по 1 циклу каждый)
Конечно, это чисто синтетически, и я, правда, не ожидал этих результатов, но кажется, что с несуществующими индексами .loc
и .append
работают довольно схожим образом. Просто оставлю это здесь.