Эффективно агрегировать результаты в Python структуру данных - PullRequest
1 голос
/ 03 апреля 2020

Я часто зацикливаюсь на длинном списке INPUT (или на фрейме данных, или на словаре). На итерации я делаю некоторые расчеты с входными данными, затем выкладываю результаты в некоторую структуру данных OUTPUT . Часто конечный результат представляет собой фрейм данных (поскольку с ним удобно иметь дело).

Ниже приведены два метода, которые l oop в длинном списке, и объединяют некоторые фиктивные результаты в фрейм данных. Подход 1 очень медленный (~ 3 секунды за цикл), тогда как Подход 2 очень быстрый (~ 18 мс за цикл). Подход 1 не очень хорош, потому что он медленный. Подход 2 быстрее, но он также не идеален, потому что он эффективно «кэширует» данные в локальном файле (а затем полагается на pandas для очень быстрого считывания этого файла). В идеале мы все делаем в памяти.

Какие подходы могут предложить люди для эффективного агрегирования результатов? Бонус: А что, если мы не знаем точный размер / длину нашей структуры вывода ( например, фактический выходной размер может превышать первоначальную оценку размера)? Любые идеи приветствуются.

import time
import pandas as pd

def run1(long_list):    
    my_df = pd.DataFrame(columns=['A','B','C'])
    for el in long_list:
        my_df.loc[(len)] = [el, el+1, 1/el]  # Dummy calculations
    return my_df

def run2(long_list):   
    with open('my_file.csv', 'w') as f:
        f.write('A,B,C\n')
        for el in long_list:
            f.write(f'{el},{el+1},{1/el}\n')  # Dummy calculations
    return pd.read_csv('my_file.csv')

long_list = range(1, 2000)

%timeit df1 = run1(long_list)  #  3  s ± 349 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit df2 = run2(long_list)  # 18 ms ± 697 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Ответы [ 2 ]

2 голосов
/ 03 апреля 2020

Это можно сделать, создав, а затем отбросив фиктивный столбец ввода и выполнив все вычисления непосредственно в pandas:

def func(long_list):
    my_df = pd.DataFrame(long_list, columns=['input'])
    my_df = my_df.assign(
        A=my_df.input,
        B=my_df.input+1,
        C=1/my_df.input)
    return my_df.drop('input', axis=1)

Сравнение времени:

%timeit df1 = run1(long_list)
%timeit df2 = run2(long_list)
%timeit df3 = func(long_list)
3.81 s ± 6.99 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
5.54 ms ± 28.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.19 ms ± 3.95 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Плюсы:

  • Все в памяти
  • Действительно быстро
  • Легко читается

Минусы:

  • Вероятно, не так быстро, как векторизация Numpy операций
1 голос
/ 03 апреля 2020

Вы можете напрямую построить DataFrame из списка списков:

def run3(long_list):
   return pd.DataFrame([[el, el+1, 1/el] for el in long_list],
                       columns=['A','B','C'])

Он должен быть намного быстрее, чем первый, и еще быстрее, чем второй, потому что он не использует диск io.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...