Есть ли более питонический способ создания серии с памятью

#python #python-3.x

#python #python-3.x

Вопрос:

Это принимает a numpy.array и возвращает a pandas.Series .

Есть ли более питонический способ написания этого, а for не цикл с if s?

 def channel_memory(elements, lower_bound, upper_bound):
    signal = 0;
    signals = [];
    for element in elements:
        if element is np.nan:
            pass
        elif signal != 1 and element >= upper_bound:
            signal = 1
        elif signal != -1 and element <= lower_bound:
            signal = -1
        signals.append(signal)

    return pd.Series(signals)
  

Ответ №1:

Следует отметить, что некоторые проверки не так уж необходимы. Если элемент np.nan — ни одно из сравнений не будет истинным, поэтому предыдущее значение будет сохранено. То же самое касается проверок signal != /-1 — если signal is 1 и новый элемент больше, чем граница, ничего не изменится. Итак, одним из способов «улучшить» код может быть:

 def channel_memory(elements, lower_bound, upper_bound):
    signal = 0
    signals = []
    for element in elements:
        if element >= upper_bound:
            signal = 1
        elif element <= lower_bound:
            signal = -1
        signals.append(signal)

    return pd.Series(signals)
  

Следующий альтернативный способ может быть длиннее по длине кода, но я чувствую, что он более четко передает, что происходит в коде с использованием итераторов. Сначала мы добавляем 0 s до тех пор, пока число находится между границами. Как только он покинул границы, мы продолжаем добавлять одно и то же значение до тех пор, пока не произойдет изменение в привязке, пока итератор в списке не будет исчерпан:

 def channel_memory(elements, lower_bound, upper_bound):
    signals = []
    elements = iter(elements)
    element = next(elements)
    while lower_bound < element < upper_bound:
        signals.append(0)
        element = next(elements)

    while True:
        try:
            while element > lower_bound:
                signals.append(1)
                element = next(elements)

            while element < upper_bound:
                signals.append(-1)
                element = next(elements)
        except StopIteration:
            break

    return pd.Series(signals)
  

Ответ №2:

В дополнение к упрощениям, которые были предложены Tomerikoo, я бы предложил использовать функцию генератора для yield значений сигналов.

Тогда вам не нужен временный список, и, более того, я бы рассмотрел этот Pythonic 🙂

 def channel_memory(elements, lower_bound, upper_bound):

    def signals():
        signal =  0
        for element in elements:
            if element >= upper_bound:
                signal = 1
            elif element <= lower_bound:
                signal = -1
            yield signal
    
    return pd.Series(signals())
  

Комментарии:

1. Как вы думаете, это будет быстрее, чем первый ответ Tomerikoo? Первый ответ Tomerikoo кажется (мне) наиболее читаемым.

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

3. Кстати, я вижу, что решение на основе списков может выглядеть более читабельным, если вы не знакомы с генераторами и yield ключевым словом. Я сам был в такой же ситуации несколько лет назад. Но генераторы часто используются в Python и позволяют вам делать много вещей, таких как создание итераций, которые не помещаются в память в виде временного списка, или даже бесконечных итераций. Поэтому я бы рекомендовал каждому программисту на Python ознакомиться с yield ключевым словом 🙂

Ответ №3:

Если вы уже используете pandas, почему бы не использовать его в полной мере?

 def channel_memory(elements, lower_bound, upper_bound):
    return pd.Series(elements).fillna(method = 'ffill').fillna(0).clip(lower = lower_bound, upper = upper_bound)
  

PS Мне это тоже кажется наиболее питоническим.

Как это работает?

fillna(method = 'ffill') продолжает заполнять значения nan последним значением, отличным от nan, с которым он столкнулся. Так что обслуживает часть памяти вашей логики. fillna(0) затем заполняет любое количество начальных nan значений нулями (то, что вы использовали в качестве начального значения сигнала. Это, конечно, может быть любое значение). .clip() затем обрезает все значения до диапазона (1,-1) . Который эффективно эмулирует:

Запускайте сигнал с 0 , пока не встретится значение, отличное от nan. затем начинается заполнение значениями в диапазоне (-1, 1) на основе порога. В конечном итоге перевод на вашу логику.

Комментарии:

1. Это просто «обрезает» верхние и нижние значения? Как это сохраняет память о предыдущем значении сигнала?

2. Обновлено! fillna() в сочетании с этим вы ищете, чтобы сохранить память о предыдущем значении сигнала.

3. Я думаю, что fillna() просто заполняет значения NaN, я не думаю, что это совсем то, что делает моя логика. Пожалуйста, объясните подробнее, если вы считаете, что это так