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 работают довольно схожим образом. Просто оставлю это здесь.