Повышение скорости за счет создания динамических столбцов в Dataframe - PullRequest
0 голосов
/ 08 октября 2018

Я создаю фрейм данных со следующей информацией:

import numpy as np
import pandas as pd
from time import time

start_time = time()

columns = 60

Data = pd.DataFrame(np.random.randint(low=0, high=10, size=(700000, 3)), columns=['a', 'b', 'c'])
Data['f'] = (Data.index % 60) + 1
Data['column_-1'] = 100
for i in range(columns):
    Data['column_' + str(i)] = np.where(  # condition 1
        Data['f'] == 1,
        1000 + i,
        np.where(  # condition2
            i < Data['f'],
            0,
            np.where(  # condition3
                Data['a'] > Data['b'],
                Data['column_' + str(-1)] * Data['c'],
                Data['column_' + str(-1)]
            )
        )
    )

elapsed_time = time() - start_time
print("Elapsed time: %.10f seconds." % elapsed_time)

Истекшее время: 1,0710000992 секунды.

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

Ответы [ 2 ]

0 голосов
/ 09 октября 2018

Просто заметьте, что вы выполняете избыточные вычисления в цикле.Это влияет на вашу производительность.Для демонстрации я перейду к использованию numpy.select, так как проще сказать, что происходит:

Инициализация:

columns = 10
Data = pd.DataFrame(np.random.randint(low=0, high=10, size=(100000, 3)), columns=['a', 'b', 'c'])
Data['f'] = (Data.index % 60) + 1
Data['column_-1'] = 100

Время:

Резервные вычисления

%%timeit
case_f = Data['f'] == 1
case_ab =  Data['a'] > Data['b']
val_ab = Data['column_-1'] * Data['c']
for i in range(columns):
    Data['column_' + str(i)] = np.select(
        [Data['f'] == 1, i < Data['f'], Data['a'] > Data['b']],
        [1000 + i, 0, Data['column_-1'] * Data['c']],
        default=Data['column_-1']
    )

Результаты:

28.6 ms ± 1.2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Вытаскивание вычислений из циклов

%%timeit
case_f = Data['f'] == 1
case_ab =  Data['a'] > Data['b']
val_ab = Data['column_-1'] * Data['c']
for i in range(columns):
    Data['column_' + str(i)] = np.select(
        [case_f, i < Data['f'], case_ab],
        [1000 + i, 0, val_ab],
        default=Data['column_-1']
    )

Результаты:

16.1 ms ± 282 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Это приводит к экономии времени примерно на 40-45% само по себе.

0 голосов
/ 09 октября 2018

Используя профилировщик, вы можете видеть, что большая часть времени проводится в np.where.К сожалению, сейчас мы мало что можем с этим поделать.

Следующим по величине временным поглотителем, похоже, являются преобразования Панд, которые являются медленными.Таким образом, мы можем сэкономить время, делая код более упорядоченным (и более читабельным):

import numpy as np
import pandas as pd

def make_data():
    data_raw = np.random.randint(low=0, high=10, size=(700000, 3))
    Data = pd.DataFrame(data_raw, columns=['a', 'b', 'c'])
    Data['f'] = (Data.index % 60) + 1 
    Data['column_-1'] = 100
    return Data

def run1(Data):
    """ Original """
    for i in range(COLUMNS):
        Data['column_' + str(i)] = np.where( # Condition 1
                                                Data['f'] == 1,
                                                1000 + i,
                                                np.where( # Condition 2
                                                        i < Data['f'],
                                                        0,
                                                        np.where( # Condition 3
                                                                Data['a'] > Data['b'],
                                                                Data['column_' + str(-1)] * Data['c'],
                                                                Data['column_' + str(-1)]
                                                                )
                                                        )
                                           )

def run2(Data):
    """ Cleaned up """
    f = Data['f'].values
    a = Data['a'].values
    b = Data['b'].values
    c = Data['c'].values

    for i in range(COLUMNS):
        col = f'column_{i}'
        colm1 = f'column_{i-1}'

        colm1 = Data[colm1].values

        Data[col] = np.where(f == 1, 1000 + i,
                        np.where(f > i, 0,
                            np.where(a > b, colm1*c, colm1)))

%timeit run1(make_data())
# 1.31 s ± 101 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit run2(make_data())
# 1.22 s ± 26.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop 

Но мы все еще используем np.where 3 раза.Numpy-функции очень стремятся, и np.where будет заканчивать циклическим циклом данных при каждом запуске.

Так что давайте сделаем лучше!Мы можем «сплющить» все это и сделать все это за один цикл:

def run3(Data):
    def _run3(f, a, b, c, x, i):
        results = np.zeros_like(x)
        for k, (fval, aval, bval, cval, xval) in enumerate(zip(f, a, b, c, x)):
            if fval == 1:
                results[k] = i + 1000
            elif fval > i:
                results[k] = 0
            elif aval > bval:
                results[k] = xval*cval
            else:
                results[k] = xval
        return results

    fabc = Data[['f', 'a', 'b', 'c']].values.astype(np.dtype('int64'))
    f, a, b, c = [fabc[:,j] for j in range(4)]
    col = 'column_-1'
    for i in range(COLUMNS):
        colm1 = col
        col = f'column_{i}'
        x = Data[colm1].values
        Data[col] = _run3(f, a, b, c, x, i)

%timeit run3(make_data())
# 34.3 s ± 1.4 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

О ... неважно.Это то, что люди имеют в виду, когда говорят «Python медленный».Циклирование в C в 3 раза в 25 раз быстрее, чем в Python один раз!

Итак, давайте сделаем цикл в C:

import numba

@numba.jit(nopython=True)
def _run4(f, a, b, c, x, i):
    results = np.zeros_like(x)
    for k in range(len(x)):
        fval = f[i]
        aval = a[i]
        bval = b[i]
        cval = c[i]
        xval = x[i]
        if fval == 1:
            results[k] = i + 1000
        elif fval > i:
            results[k] = 0
        elif aval > bval:
            results[k] = xval*cval
        else:
            results[k] = xval
    return results

def run4(Data):
    fabc = Data[['f', 'a', 'b', 'c']].values.astype(np.dtype('int64'))
    f, a, b, c = [fabc[:,j] for j in range(4)]
    col = 'column_-1'
    for i in range(COLUMNS):
        colm1 = col
        col = f'column_{i}'
        x = Data[colm1].values
        Data[col] = _run4(f, a, b, c, x, i)

%timeit run4(make_data())
# 496 ms ± 70.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

И это, вероятно, достаточно быстро на сегодняшний день.+ лучший алгоритм - огромные накладные расходы = быстрая.

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