Оптимизация цикла панд

1

Есть ли лучший способ (по производительности) для выполнения следующего цикла в пандах (если df является DataFrame)?

for i in range(len(df)):
    if df['signal'].iloc[i] == 0:   # if the signal is negative
        if df['position'].iloc[i - 1] - 0.02 < -1:   # if the row above - 0.1 < -1 set the value of current row to -1
            df['position'].iloc[i] = -1
        else:   # if the new col value above -0.1 is > -1 then subtract 0.1 from that value
            df['position'].iloc[i] = df['position'].iloc[i - 1] - 0.02
    elif df['signal'].iloc[i] == 1:     # if the signal is positive
        if df['position'].iloc[i - 1] + 0.02 > 1:     # if the value above + 0.1 > 1 set the current row to 1
            df['position'].iloc[i] = 1
        else:   # if the row above + 0.1 < 1 then add 0.1 to the value of the current row
            df['position'].iloc[i] = df['position'].iloc[i - 1] + 0.02

Я буду благодарен за любые советы, потому что я только начал идти по маршруту Пандаса и, очевидно, может пропустить что-то важное.

Исходные данные CSV:

Date,sp500,sp500 MA,UNRATE,UNRATE MA,signal,position
2000-01-01,,,4.0,4.191666666666665,1,0
2000-01-02,,,4.0,4.191666666666665,1,0
2000-01-03,102.93,95.02135,4.0,4.191666666666665,1,0
2000-01-04,98.91,95.0599,4.0,4.191666666666665,1,0
2000-01-05,99.08,95.11245000000001,4.0,4.191666666666665,1,0
2000-01-06,97.49,95.15450000000001,4.0,4.191666666666665,1,0
2000-01-07,103.15,95.21575000000001,4.0,4.191666666666665,1,0
2000-01-08,103.15,95.21575000000001,4.0,4.191666666666665,1,0
2000-01-09,103.15,95.21575000000001,4.0,4.191666666666665,1,0

Желаемый результат:

Date,sp500,sp500 MA,UNRATE,UNRATE MA,signal,position
2000-01-01,,,4.0,4.191666666666665,1,0.02
2000-01-02,,,4.0,4.191666666666665,1,0.04
2000-01-03,102.93,95.02135,4.0,4.191666666666665,1,0.06
2000-01-04,98.91,95.0599,4.0,4.191666666666665,1,0.08
2000-01-05,99.08,95.11245000000001,4.0,4.191666666666665,1,0.1
2000-01-06,97.49,95.15450000000001,4.0,4.191666666666665,1,0.12
2000-01-07,103.15,95.21575000000001,4.0,4.191666666666665,1,0.14
2000-01-08,103.15,95.21575000000001,4.0,4.191666666666665,1,0.16
2000-01-09,103.15,95.21575000000001,4.0,4.191666666666665,1,0.18

Обновить Все ответы ниже (к моменту написания этого) дают значение постоянной position 0.02, которое отличается от моего метода наивного цикла. Другими словами, я ищу решение, которое дало бы 0.02, 0.04, 0.06, 0.08 т.д. Для столбца position.

  • 2
    если вы зацикливаетесь на пандах, вы почти всегда делаете это неправильно
  • 0
    @SuperStew SuperStew да, у меня было такое чувство кишки
Показать ещё 9 комментариев
Теги:
pandas
performance

4 ответа

1
Лучший ответ

Спасибо, что добавили данные и примеры. Во-первых, я уверен, что вы не можете векторизовать это, поскольку каждый расчет зависит от результата предыдущего. Так что это лучшее, что я смог сделать.

Ваш метод пришел около 0.116999 секунд на моей машине

Это произошло около 0.0039999 секунд

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

def myfunc(pos_pre, signal):
    if signal == 0:  # if the signal is negative
        # if the new col value above -0.2 is > -1 then subtract 0.2 from that value
        pos = pos_pre - 0.02
        if pos < -1:  # if the row above - 0.2 < -1 set the value of current row to -1
            pos = -1

    elif signal == 1:
        # if the row above + 0.2 < 1 then add 0.2 to the value of the current row
        pos = pos_pre + 0.02
        if pos > 1:  # if the value above + 0.1 > 1 set the current row to 1
            pos = 1

    return pos


''' set first position value because you aren't technically calculating it correctly in your method since there is no 
position minus 1... IE: it will always be 0.02'''
new_pos = [0.02]

# skip index zero since there is no position 0 minus 1
for i in range(1, len(df)):
    new_pos.append(myfunc(pos_pre=new_pos[i-1], signal=df['signal'].iloc[i]))

df['position'] = new_pos

Выход:

df.position
0    0.02
1    0.04
2    0.06
3    0.08
4    0.10
5    0.12
6    0.14
7    0.16
8    0.18
2

Не используйте петлю. Pandas специализируется на векторизованных операциях, например, для signal == 0:

pos_shift = df['position'].shift() - 0.02
m1 = df['signal'] == 0
m2 = pos_shift < -1

df.loc[m1 & m2, 'position'] = -1
df['position'] = np.where(m1 & ~m2, pos_shift, df['position'])

Вы можете написать что-то подобное для signal == 1.

  • 0
    Благодарю. Это выглядит потрясающе, но я только что заметил, что результаты вашей версии немного отличаются от моих исходных кодов.
  • 0
    @varnie, поэтому было бы очень удобно, если бы вы отредактировали свой вопрос, включив несколько примеров ввода и вывода :)
Показать ещё 7 комментариев
0

Есть, скорее всего, лучшие способы, но этот тоже должен работать:

df['previous'] = df.signal.shift()

def get_signal_value(row):
    if row.signal == 0:
        compare = row.previous - 0.02
        if compare < -1:
            return -1
        else:
            return compare
    elif row.signal == 1: 
        compare = row.previous + 0.01
        if compare > 1:
            return 1
        else:
            return compare

df['new_signal'] = df.apply(lambda row: get_signal_value(row), axis=1)
0

Ага. При поиске производительности вы всегда должны работать с базовыми массивами numpy:

signal = df['signal'].values
position = df['position'].values
for i in range(len(df)):
    if signal[i] == 0:
        if position[i-1]-0.02 < -1:
            position[i] = -1
        else:
            position[i] = position[i-1]-0.02
    elif signal[i] == 1:
        if position[i-1]+0.02 > 1:
            position[i] = 1
        else:
            position[i] = position[i-1]+0.02

Вы будете удивлены приростом производительности, часто в 10 раз и более.

  • 3
    Это повторяется так же, как вопрос. Основным преимуществом работы с массивами numpy является использование векторизованных операций.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню