Как эффективно перебрать pFD DataFrame и увеличить массив NumPy на эти значения? - PullRequest
0 голосов
/ 30 августа 2018

Мои панды / numpy ржавые, а написанный мной код неэффективен.

Я инициализирую массив нулей в Python3.x длиной 1000. Для моей цели это просто целые числа:

import numpy as np
array_of_zeros =  np.zeros((1000, ), )

У меня также есть следующий DataFrame (который намного меньше моих фактических данных)

import pandas as pd
dict1 = {'start' : [100, 200, 300], 'end':[400, 500, 600]}
df = pd.DataFrame(dict1)
print(df)
##
##    start     end
## 0    100     400
## 1    200     500
## 2    300     600

DataFrame имеет два столбца, start и end. Эти значения представляют диапазон значений, то есть start всегда будет меньшим целым числом, чем end. Выше мы видим, что первая строка имеет диапазон 100-400, следующая - 200-500, а затем 300-600.

Моя цель состоит в том, чтобы перебирать pandas DataFrame строка за строкой и увеличивать числовой массив array_of_zeros на основе этих позиций индекса. Итак, если в кадре данных есть строка от 10 до 20, я бы хотел увеличить ноль на +1 для индексов 10-20.

Вот код, который делает то, что я хотел бы:

import numpy as np
array_of_zeros =  np.zeros((1000, ), )

import pandas as pd
dict1 = {'start' : [100, 200, 300], 'end':[400, 500, 600]}
df = pd.DataFrame(dict1)
print(df)

for idx, row in df.iterrows():
    for i in range(int(row.start), int(row.end)+1):
        array_of_zeros[i]+=1

И это работает!

print(array_of_zeros[15])
## output: 0.0
print(array_of_zeros[600])
## output: 1.0
print(array_of_zeros[400])
## output: 3.0
print(array_of_zeros[100])
## output: 1.0
print(array_of_zeros[200])
## output: 2.0

Мои вопросы: это очень неуклюжий код! Я не должен использовать так много циклов for с массивами numpy! Это решение будет очень неэффективным, если входной фрейм данных достаточно большой

Существует ли более эффективный (то есть более основанный на numpy) метод, позволяющий избежать этого цикла for?

for i in range(int(row.start), int(row.end)+1):
    array_of_zeros[i]+=1

Возможно, есть решение, ориентированное на панд?

Ответы [ 3 ]

0 голосов
/ 30 августа 2018

Вы можете использовать индексирование массива NumPy, чтобы избежать внутреннего цикла, т. Е. res[np.arange(A[i][0], A[i][1]+1)] += 1, но это неэффективно, поскольку включает создание нового массива и использование расширенной индексации.

Вместо этого вы можете использовать numba 1 для оптимизации вашего алгоритма, в том виде, в каком он есть. Приведенный ниже пример демонстрирует значительное улучшение производительности за счет перемещения критичной к производительности логики в JIT-скомпилированный код.

from numba import jit

@jit(nopython=True)
def jpp(A):
    res = np.zeros(1000)
    for i in range(A.shape[0]):
        for j in range(A[i][0], A[i][1]+1):
            res[j] += 1
    return res

Некоторые результаты бенчмаркинга:

# Python 3.6.0, NumPy 1.11.3

# check result the same
assert (jpp(df[['start', 'end']].values) == original(df)).all()
assert (pir(df) == original(df)).all()
assert (pir2(df) == original(df)).all()

# time results
df = pd.concat([df]*10000)

%timeit jpp(df[['start', 'end']].values)  # 64.6 µs per loop
%timeit original(df)                      # 8.25 s per loop
%timeit pir(df)                           # 208 ms per loop
%timeit pir2(df)                          # 1.43 s per loop

Код, используемый для бенчмаркинга:

def original(df):
    array_of_zeros = np.zeros(1000)
    for idx, row in df.iterrows():
        for i in range(int(row.start), int(row.end)+1):
            array_of_zeros[i]+=1   
    return array_of_zeros

def pir(df):
    return np.bincount(np.concatenate([np.arange(a, b + 1) for a, b in \
                       zip(df.start, df.end)]), minlength=1000)

def pir2(df):
    a = np.zeros((1000,), np.int64)
    for b, c in zip(df.start, df.end):
        np.add.at(a, np.arange(b, c + 1), 1)
    return a

1 Для потомков я включил отличный комментарий @ piRSquared о том, почему numba помогает здесь:

Преимущество

numba состоит в том, что цикл выполняется очень эффективно. Хотя может понять многое из API NumPy, часто лучше избегать создания NumPy объекты внутри цикла. Мой код создает массив NumPy для каждая строка в кадре данных. Затем объединить их перед использованием bincount. Код @ jpp numba создает очень мало дополнительных объектов и использует большую часть того, что уже есть. Разница между моими Решение NumPy и решение @ jpp numba примерно в 4-5 раз. Оба линейный и должен быть довольно быстрым.

0 голосов
/ 30 августа 2018

Мое решение

for x, y in zip(df.start, df.end):
    array_of_zeros[x:y+1]+=1
0 голосов
/ 30 августа 2018

numpy.bincount

np.bincount(np.concatenate(
    [np.arange(a, b + 1) for a, b in zip(df.start, df.end)]
), minlength=1000)

numpy.add.at

a = np.zeros((1000,), np.int64)
for b, c in zip(df.start, df.end):
  np.add.at(a, np.arange(b, c + 1), 1)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...