Условное среднее и сумма предыдущих N строк в кадре данных pandas

Обеспокоена эта примерная структура данных pandas:

      Measurement  Trigger  Valid
   0          2.0    False   True
   1          4.0    False   True
   2          3.0    False   True
   3          0.0     True  False
   4        100.0    False   True
   5          3.0    False   True
   6          2.0    False   True
   7          1.0     True   True

Всякий раз, когда Trigger равно True, я хочу рассчитать сумму и среднее значение последних 3 (начиная с текущего) действительных измерений. Измерения считаются действительными, если столбец Valid равен True. Итак, давайте проясним, используя два примера в приведенном выше кадре данных:

  1. Index 3: следует использовать индексы 2,1,0. Ожидается Sum = 9.0, Mean = 3.0
  2. Index 7: следует использовать индексы 7,6,5. Ожидается Sum = 6.0, Mean = 2.0

Я пробовал pandas.rolling и создавал новые, смещенные столбцы, но безуспешно. См. следующий отрывок из моих тестов (которые должны запускаться напрямую):

import unittest
import pandas as pd
import numpy as np
from pandas.util.testing import assert_series_equal

def create_sample_dataframe_2():
    df = pd.DataFrame(
        {"Measurement" : [2.0,   4.0,   3.0,   0.0,   100.0, 3.0,   2.0,   1.0 ],
         "Valid"       : [True,  True,  True,  False, True,  True,  True,  True],
         "Trigger"     : [False, False, False, True,  False, False, False, True],
         })
    return df

def expected_result():
    return pd.DataFrame({"Sum" : [np.nan, np.nan, np.nan, 9.0, np.nan, np.nan, np.nan, 6.0],
                         "Mean" :[np.nan, np.nan, np.nan, 3.0, np.nan, np.nan, np.nan, 2.0]})

class Data_Preparation_Functions(unittest.TestCase):

    def test_backsummation(self):
        N_SUMMANDS = 3
        temp_vars = []

        df = create_sample_dataframe_2()
        for i in range(0,N_SUMMANDS):
            temp_var = "M_{0}".format(i)
            df[temp_var] = df["Measurement"].shift(i)
            temp_vars.append(temp_var)

        df["Sum"]  = df[temp_vars].sum(axis=1)
        df["Mean"] = df[temp_vars].mean(axis=1)
        df.loc[(df["Trigger"]==False), "Sum"] = np.nan
        df.loc[(df["Trigger"]==False), "Mean"] = np.nan

        assert_series_equal(expected_result()["Sum"],df["Sum"])
        assert_series_equal(expected_result()["Mean"],df["Mean"])

    def test_rolling(self):
        df = create_sample_dataframe_2()
        df["Sum"]  = df[(df["Valid"] == True)]["Measurement"].rolling(window=3).sum()
        df["Mean"] = df[(df["Valid"] == True)]["Measurement"].rolling(window=3).mean()

        df.loc[(df["Trigger"]==False), "Sum"] = np.nan
        df.loc[(df["Trigger"]==False), "Mean"] = np.nan
        assert_series_equal(expected_result()["Sum"],df["Sum"])
        assert_series_equal(expected_result()["Mean"],df["Mean"])


if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(Data_Preparation_Functions)
    unittest.TextTestRunner(verbosity=2).run(suite)

Любая помощь или решение приветствуется. Спасибо и ура!

РЕДАКТИРОВАТЬ: Уточнение: это результирующий кадр данных, который я ожидаю:

      Measurement  Trigger  Valid   Sum   Mean
   0          2.0    False   True   NaN    NaN
   1          4.0    False   True   NaN    NaN
   2          3.0    False   True   NaN    NaN
   3          0.0     True  False   9.0    3.0
   4        100.0    False   True   NaN    NaN
   5          3.0    False   True   NaN    NaN
   6          2.0    False   True   NaN    NaN
   7          1.0     True   True   6.0    2.0

EDIT2: Еще одно уточнение:

Я действительно не просчитался, а скорее не изложил свои намерения настолько ясно, насколько мог бы. Вот еще одна попытка использования того же фрейма данных:

Требуемый фрейм данных, соответствующие поля выделены

Давайте сначала посмотрим на столбец Trigger: мы находим первое True в индексе 3 (зеленый прямоугольник). Таким образом, индекс 3 — это точка, с которой мы начинаем поиск. В индексе 3 нет допустимых измерений (столбец Valid равен False; красный прямоугольник). Итак, начинаем двигаться дальше в прошлое, пока не накопим три строки, где Valid это True. Это происходит для индексов 2,1 и 0. Для этих трех индексов мы вычисляем сумму и среднее значение столбца Measurement (синий прямоугольник):

  • СУММА: 2,0 + 4,0 + 3,0 = 9,0
  • СРЕДНИЙ: (2,0 + 4,0 + 3,0) / 3 = 3,0

Теперь мы начинаем следующую итерацию этого маленького алгоритма: снова ищем следующее True в столбце Trigger. Находим его по индексу 7 (зеленый прямоугольник). Существует также допустимое измерение в индексе 7, поэтому мы включаем его на этот раз. Для нашего расчета используем индексы 7,6 и 5 (зеленый прямоугольник), и таким образом получаем:

  • СУММА: 1,0 + 2,0 + 3,0 = 6,0
  • СРЕДНЕЕ: (1,0 + 2,0 + 3,0) / 3 = 2,0

Я надеюсь, что это проливает больше света на эту небольшую проблему.


person bolla    schedule 13.10.2017    source источник
comment
Я рассмотрел такой вопрос, в основном, чтобы обнаружить cumsum, теперь я иду, чтобы выяснить это!   -  person ileadall42    schedule 13.10.2017


Ответы (1)


Вот вариант, возьмите 3-периодное скользящее среднее и сумму

df['RollM'] = df.Measurement.rolling(window=3,min_periods=0).mean()

df['RollS'] = df.Measurement.rolling(window=3,min_periods=0).sum()

Теперь установите False Triggers равным NaN

df.loc[df.Trigger == False,['RollS','RollM']] = np.nan

урожаи

   Measurement  Trigger  Valid     RollM  RollS
0          2.0    False   True       NaN    NaN
1          4.0    False   True       NaN    NaN
2          3.0    False   True       NaN    NaN
3          0.0     True  False  2.333333    7.0
4        100.0    False   True       NaN    NaN
5          3.0    False   True       NaN    NaN
6          2.0    False   True       NaN    NaN
7          1.0     True   True  2.000000    6.0

Изменить, обновить, чтобы отразить действительный аргумент

df['mean'],df['sum'] = np.nan,np.nan

roller = df.Measurement.rolling(window=3,min_periods=0).agg(['mean','sum'])

df.loc[(df.Trigger == True) & (df.Valid == True),['mean','sum']] = roller

df.loc[(df.Trigger == True) & (df.Valid == False),['mean','sum']] = roller.shift(1)

Урожайность

  Measurement  Trigger  Valid  mean  sum
0          2.0    False   True   NaN  NaN
1          4.0    False   True   NaN  NaN
2          3.0    False   True   NaN  NaN
3          0.0     True  False   3.0  9.0
4        100.0    False   True   NaN  NaN
5          3.0    False   True   NaN  NaN
6          2.0    False   True   NaN  NaN
7          1.0     True   True   2.0  6.0
person DJK    schedule 13.10.2017
comment
Это не совсем то, что я ищу. Чтобы уточнить, я добавил ожидаемый результат в табличном формате в исходный пост. - person bolla; 13.10.2017
comment
Можете ли вы еще объяснить, как это неправильно? Он соответствует вашему образцу, за исключением чисел в индексе три, я полагаю, вы пропустили их расчет и сделали предыдущие три, не включая текущее значение @bolla - person DJK; 13.10.2017