/// <summary> /// Finds the moving median sequence from the sequence of values /// and the specified non-negative window width. The tail values /// (such that there is not enough data in the window around them /// get handled using <see cref="TailValuesHandling"/> flag passed). /// </summary> /// <typeparam name="T">The type of elements in the sequence.</typeparam> /// <typeparam name="C">The numeric calculator for the <typeparamref name="T"/> type.</typeparam> /// <param name="values">The sequence of values.</param> /// <param name="windowWidth"> /// A non-negative window width applied to both sides of the current value. /// It means that, e.g. when the window width is 1, the window will consist /// of three values: the current value, one value to the left and one value to the right. /// </param> /// <param name="windowType"> /// The type of the window for calculation of the median. /// See <see cref="WindowType"/> /// </param> /// <param name="tailValuesHandling"> /// A flag specifying how the tail values should be handled /// (such that there is not enough data in the window around them). /// See <see cref="TailValuesHandling"/>. /// </param> /// <returns>A sequence of moving medians calculated from the source sequence.</returns> public static List <T> MovingMedian <T, C>( this IList <T> values, int windowWidth, WindowType windowType = WindowType.Symmetric, TailValuesHandling tailValuesHandling = TailValuesHandling.UseSymmetricAvailableWindow) where C : ICalc <T>, new() { Contract.Requires <ArgumentNullException>(values != null, "values"); Contract.Requires <ArgumentOutOfRangeException>(windowWidth >= 0, "The window width should be non-negative"); Contract.Requires <ArgumentException>( tailValuesHandling != TailValuesHandling.UseSymmetricAvailableWindow || windowType == WindowType.Symmetric, "Symmetric tail values handling is only available for symmetric windows."); return(MovingStatistic <T, C>( values, windowWidth, windowType, tailValuesHandling, StatisticExtensionMethods.SampleAverage <T, C>)); }
/// <summary> /// Finds the moving statistic sequence from the sequence of values /// using the specified non-negative window width and the statistic functor /// <paramref name="statistic"/> of type <c>Func<IEnumerable<T>, T>"</c>. /// /// The tail values (such that there is not enough data in the window around them /// get handled using <see cref="TailValuesHandling"/> flag passed). /// </summary> /// <typeparam name="T">The type of elements in the sequence.</typeparam> /// <typeparam name="C">The numeric calculator for the <typeparamref name="T"/> type.</typeparam> /// <param name="values">The sequence of values.</param> /// <param name="windowWidth"> /// A non-negative window width applied to both sides of the current value. /// It means that, e.g. when the window width is 1, the window will consist /// of three values: the current value, one value to the left and one value to the right. /// </param> /// <param name="windowType"> /// The type of the window for calculation of the average. /// See <see cref="WindowType"/> /// </param> /// <param name="tailValuesHandling"> /// A flag specifying how the tail values should be handled /// (such that there is not enough data in the window around them). /// See <see cref="TailValuesHandling"/>. /// </param> /// <param name="statistic"> /// A functor object taking an <c>IEnumerable<T></c> sequence of observations /// and returning a statistic such as sample average, sample median, sample variance / standard /// deviation etc. /// </param> /// <returns> /// A sequence of statictics calculated in the window /// around each value from the source sequence. /// i.e. the i-th index in the result sequence means /// that the <paramref name="statistic"/> was calculated in the /// respective window around the i-th element of the source sequence. /// </returns> public static List <T> MovingStatistic <T, C>( this IList <T> values, int windowWidth, WindowType windowType, TailValuesHandling tailValuesHandling, Func <IEnumerable <T>, T> statistic) where C : ICalc <T>, new() { Contract.Requires <ArgumentNullException>(values != null, "values"); Contract.Requires <ArgumentOutOfRangeException>(windowWidth >= 0, "The window width should be non-negative"); Contract.Requires <ArgumentException>( tailValuesHandling != TailValuesHandling.UseSymmetricAvailableWindow || windowType == WindowType.Symmetric, "Symmetric tail values handling is only available for symmetric windows."); List <T> result = new List <T>(values.Count); for (int index = 0; index < values.Count; ++index) { ListSegment <T> windowSequence = __getWindowSequence <T>( values, windowWidth, windowType, tailValuesHandling, index); if (windowSequence.IsEmpty()) { continue; } else { T statisticValue = statistic(windowSequence); result.Add(statisticValue); } } return(result); }
/// <summary> /// Having passed the list of values, /// a non-negative window width, and /// a <see cref="TailValuesHandling"/> flag, /// returns the appropriate segment of the source /// list containing the values falling in the window. /// </summary> /// <typeparam name="T">The type of elements in the sequence.</typeparam> /// <param name="values">The source sequence.</param> /// <param name="windowWidth">A non-negative window width.</param> /// <param name="currentIndex"> /// The current index in the source sequence /// pointing to the current element (for which the window sequence is calculated)</param> /// <param name="tailValuesHandling">A tail values handling flag, see <see cref="TailValuesHandling"/></param> /// <param name="windowType">The window type, see <see cref="WindowType"/>.</param> /// <returns> /// A window sequence for the current element (referenced by <paramref name="currentIndex"/>). /// The sequence can then be used for calculating the average or the median value. /// /// If the value is a tail value that should be excluded (see <see cref="TailValuesHandling.DoNotInclude"/>), /// this method returns an empty list segment. /// </returns> private static ListSegment <T> __getWindowSequence <T>( IList <T> values, int windowWidth, WindowType windowType, TailValuesHandling tailValuesHandling, int currentIndex) { Contract.Requires <ArgumentNullException>(values != null, "values"); Contract.Requires <ArgumentOutOfRangeException>(windowWidth >= 0, "The window width should be non-negative."); Contract.Requires <IndexOutOfRangeException>(currentIndex >= 0 && currentIndex < values.Count, "The index is out of range"); // Check the available number of elements to the left // and to the right of the current value. // - int amountToTheLeft = currentIndex; int amountToTheRight = values.Count - currentIndex - 1; bool isLeftTailValue = amountToTheLeft < windowWidth && windowType != WindowType.Forward; bool isRightTailValue = amountToTheRight < windowWidth && windowType != WindowType.Backward; // These are the boundary indices in the resulting // list segment. To be modified further. // - int leftIndex = currentIndex - (windowType == WindowType.Forward ? 0 : windowWidth); int rightIndex = currentIndex + (windowType == WindowType.Backward ? 0 : windowWidth); switch (tailValuesHandling) { case TailValuesHandling.DoNotInclude: if (isLeftTailValue || isRightTailValue) { return(new ListSegment <T>(values, currentIndex, 0)); } break; case TailValuesHandling.DoNotTouch: if (isLeftTailValue || isRightTailValue) { leftIndex = currentIndex; rightIndex = currentIndex; } break; case TailValuesHandling.UseAllAvailableWindow: if (isLeftTailValue) { leftIndex = currentIndex - amountToTheLeft; } if (isRightTailValue) { rightIndex = currentIndex + amountToTheRight; } break; case TailValuesHandling.UseSymmetricAvailableWindow: if (isLeftTailValue || isRightTailValue) { int minimumAmountAvaliable = Math.Min(amountToTheLeft, amountToTheRight); leftIndex = currentIndex - minimumAmountAvaliable; rightIndex = currentIndex + minimumAmountAvaliable; } break; default: throw new EnumFattenedException("Ouch!"); } return(new ListSegment <T>(values, leftIndex, rightIndex - leftIndex + 1)); }