Git Product home page Git Product logo

runningmedian's Introduction

Arduino CI Arduino-lint JSON check GitHub issues

License: MIT GitHub release PlatformIO Registry

RunningMedian

Arduino library to determine the running median by means of a circular buffer.

Description

Running Median looks like a running average with a small but important twist. Running average averages the last N samples while the running median takes the last N samples, sort them and take the middle one, or the average of the middle two in case the internal buffer size is even.

Important differences between running average and running median:

  • Running median will return real data (e.g. a real sample from a sensor) if one uses an odd size of the buffer (therefore preferred). Running average may return a value that is never sampled.
  • Running median will give zero weight to outliers, and 100% to the middle sample, whereas running average gives the same weight to all samples.
  • Running median will give often constant values for some time.
  • As one knows the values in the buffer one can predict the maximum change of the running median in the next steps in advance.
  • Running median is slower as one needs to keep the values in timed order to remove the oldest and keep them sorted to be able to select the median.

Note: MEDIAN_MAX_SIZE

The maximum size of the internal buffer is defined by MEDIAN_MAX_SIZE and is set to 255 (since version 0.3.1). The memory allocated currently is in the order of 5 bytes per element plus some overhead, so 255 elements take ~1300 bytes. For an UNO this is quite a bit.

With larger sizes the performance penalty to keep the internal array sorted is large. For most applications a value much lower e.g. 19 is working well, and is performance wise O(100x) faster in sorting than 255 elements.

Note: Configurable Options

There are several options that can be configured via defines at compile time, those being:

  • RUNNING_MEDIAN_USE_MALLOC: bool
    • true (default): Dynamic memory allocation is used for the buffer.
    • false: Static buffers of size MEDIAN_MAX_SIZE are used.
  • MEDIAN_MIN_SIZE: uint8_t
    • Dynamic / Static: The buffer stores at least this many items.
    • should be minimal 3.
  • MEDIAN_MAX_SIZE: uint8_t
    • Dynamic: Not used.
    • Static: The buffer stores at most this many items.

Related

Interface

#include "RunningMedian.h"

Constructor

  • RunningMedian(const uint8_t size) Constructor, dynamically allocates memory.
  • ~RunningMedian() Destructor.
  • uint8_t getSize() returns size of internal array.
  • uint8_t getCount() returns current used elements, getCount() <= getSize().
  • bool isFull() returns true if the internal buffer is 100% filled.

Base functions

  • clear() resets internal buffer and variables, effectively empty the buffer.
  • add(const float value) adds a new value to internal buffer, optionally replacing the oldest element if the buffer is full.
  • float getMedian() returns the median == middle element.
  • float getAverage() returns average of all the values in the internal buffer.
  • float getAverage(uint8_t nMedian) returns average of the middle n values. This effectively removes noise from the outliers in the samples. The function is improved in 0.3.8 to correct a bias, see #22.
  • float getMedianAverage(uint8_t nMedian) almost same as above, except it compensates for alignment bias, see #22. This is done by adjusting the nMedian parameter (-1 or +1) if needed.
  • float getHighest() get the largest values in the buffer.
  • float getLowest() get the smallest value in the buffer.
  • float getQuantile(const float quantile) returns the Quantile value from the buffer. This value is often interpolated.

getMedianAverage(nMedian)

getAverage(nMedian) and getMedianAverage(uint8_t nMedian) differ. When nMedian is odd and count is even or vice versa, the middle N are not perfectly in the middle. By auto-adjusting nMedian (-1 +1) this balance is restored.

Assume an internal size of 7 elements [0..6] then

  • getAverage(4) will average element 1, 2, 3, 4
  • getMedianAverage(4) will adjust nMedian and average element 2, 3, 4.

The example RunningMedian_getMedianAverage.ino shows the difference.

The implementation of getMedianAverage(uint8_t nMedian) is experimental and might change in the future. Idea is taking top and bottom elements only for 50% if needed, however that implies at least 2 extra float multiplications.

It is possible that the name getMedianAverage(uint8_t nMedian) will change in the future to be more descriptive.

Less used functions

  • float getElement(const uint8_t n) returns the n'th element from the values in time order.
  • float getSortedElement(const uint8_t n) returns the n'th element from the values in size order (sorted ascending).
  • float predict(const uint8_t n) predict the maximum change of median after n additions, n must be smaller than getSize()/2.

SearchMode optimization

Since 0.3.7 the internal sort has been optimized. It is now possible to select between LINEAR (=0) and BINARY (=1) insertion sort. Pre-0.3.7 used linear insertion sort, and the new linear version is slightly optimized. For larger internal arrays the performance gain of BINARY mode is substantial.

  • void setSearchMode(uint8_t searchMode = 0) 0 = linear, 1 = binary - see table below. Other values will set the searchMode to linear.
  • uint8_t getSearchMode() returns the set mode
searchMode value notes
LINEAR 0 fastest for smaller internal buffers (default)
BINARY 1 faster for larger internal buffers

Depends on the board / clock used where the methods are equally fast.

Give it a try, and let me know your.

Operation

See examples.

Future

Must

  • improve documentation.

Should

Could

  • check for optimizations.
    • get the median without (full) sorting. QuickSelect()
  • move all code to .cpp file

Support

If you appreciate my libraries, you can support the development and maintenance. Improve the quality of the libraries by providing issues and Pull Requests, or donate through PayPal or GitHub sponsors.

Thank you,

runningmedian's People

Contributors

dralois avatar qwazwsx avatar robtillaart avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

runningmedian's Issues

Maximum of Size = 19

I successfully use this library on a ESP8266. Now I realized, the the maximum of the size to allocate with RunningMedian samples = RunningMedian(x); is x=19. If I try more, the size inquired with samples.getSize(); stays at 19.
How can this be explained? I there a way of taking more samples?

Peter Kowald

I changed this routine because it allows bias in to the calculation as the data set grows.

// this was called getAverage(uint8_t nMedians), but I changed it for clarity 
float RunningMedian::getMedianAverage(uint8_t nMedians) //nMedians is teh spread 
{
  if ((_count == 0) || (nMedians == 0)) return NAN;

//  when filling the array for first time
   if (_count < nMedians)
  {
     nMedians = _count;
     if(_count == 1) // <<<<<<< bail early as just return the ONLY value there is //
     {
        return _values[0]; 
     }
  }

  // this is designed to eliminate the bias when the nMedians could fall to the left or right of the centre //
  // so if the count and nMedians are not either BOTH odd value or Both even value we reduce the spread by 1 to make them the  same both odds or both evens //
  if((_count & 0x01) != (nMedians & 0x01)) //    
  {
     --nMedians;  
  }

  uint8_t start = ((_count - nMedians) / 2);
  uint8_t stop = start + nMedians;

  if (_sorted == false) sort();

  float sum = 0;
  for (uint8_t i = start; i < stop; i++)
  {
    sum += _values[_sortIdx[i]];
  }
  return sum / nMedians;
}

// updated code tags for readability

investigate if size can be set up to 255

Currently the max size is 250, This was chosen to stay safe. Users might expect the be able to set up to 255 so

  1. test if 255 is possible
  2. if not get an appropriate error message. (compile time and or runtime ???)
  3. Consider using uint16_t for the size and count etc

Multiple Sensors

Hello,

thanks for providing the lib. I wanna use it for multiple sensors. How can I sample multiple sensors the same time? I was thinking about duplicating the library and rename it, but there has to be an easier way. It's not really an issue, but I didn't found any other way to get in touch with u.

binary insertion sort?

Not an issue just a question relating to #10 and further development of the sorting algorithm...

I was wondering if a binary insertion sort may improve performance even further for single valued insertion into a sorted array? I'd implemented this in another library for finding write boundaries in a flash file system with ordered fixed width records and found it ran well - I often needed to find this (quickly) after a power cycle.

I'd also just like to say a big thanks for making this code available.

_size=255 and bubble sort

Whatever the problem, bubble sort is usually not the answer. Hopefully I haven't done anything wrong, but I bench marked an int16_t version of RunningMedian on an Arduino Uno., and I compared bubble sort (with early termination) to insert sort and quick sort. With random data, _size=255 and no oversampling, insert sort was almost 30 times faster than bubble sort and almost 5 times faster than quick sort. (With significant oversampling, it is likely that quick sort is the better choice.)

void RunningMedian::sort()
{
	uint8_t i;							// loop index
	uint8_t j;							// loop index
	uint8_t t;							// swap buffer

	for(i=1;i<_cnt;i++)
	{
		t=_p[i];
		for(j=i;j>=1&&_ar[t]<_ar[_p[j-1]];j--)
			_p[j]=_p[j-1];
		_p[j]=t;
	}
	_sorted=true;
}

Update readme.md

  • check for minor errors typos
  • completeness of the interface
  • caveats and notes etc

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.