""" Watch over a stream of numbers, incrementally learning their median. Implemented via nested lists. New numbers are added to `lst[i]` and when it fills up, it posts its median to `lst[i+1]`. Wen `lst[i+1]` fills up, it posts the medians of its medians to `lst[i+2]`. Etc. When a remedian is queried for the current median, it returns the median of the last list with any numbers. This approach is quite space efficient . E.g. four nested lists, each with 64 items, require memory for 4*64 items yet can hold the median of the median of the median of the median of over 17 million numbers. Example usage: z=remedian() for i in range(1000): z + i if not i % 100: print(i, z.median()) Based on [The Remedian:A Robust Averaging Method for Large Data Sets](http://web.ipac.caltech.edu/staff/fmasci/home/astro_refs/Remedian.pdf). by Peter J. Rousseeuw and Gilbert W. Bassett Jr. Journal of the American Statistical Association March 1990, Vol. 85, No. 409, Theory and Methods The code [remedianeg.py](remedianeg.py) compares this rig to just using Python's built-in sort then reporing the middle number. Assuming lists of length 64 and use of pypy3: - Remedian is getting nearly as fast (within 20%) as raw sort after 500 items; - While at the same time, avoids having to store all the numbers in RAM; - Further, remedian's computed median is within 1% (or less) of the medians found via Python's sort. _____ ## Programmer's Guide """ # If `ordered` is `False`, do not sort `lst` def median(lst,ordered=False): assert lst,"median needs a non-empty list" n = len(lst) p = q = n//2 if n < 3: p,q = 0, n-1 else: lst = lst if ordered else sorted(lst) if not n % 2: # for even-length lists, use mean of mid 2 nums q = p -1 return lst[p] if p==q else (lst[p]+lst[q])/2 class remedian: # Initialization def __init__(i,inits=[], k=64, # after some experimentation, 64 works ok about = None): i.all,i.k = [],k i.more,i._median=None,None [i + x for x in inits] # When full, push the median of current values to next list, then reset. def __add__(i,x): i._median = None i.all.append(x) if len(i.all) == i.k: i.more = i.more or remedian(k=i.k) i.more + i._medianPrim(i.all) i.all = [] # reset # If there is a next list, ask its median. Else, work it out locally. def median(i): return i.more.median() if i.more else i._medianPrim(i.all) # Only recompute median if we do not know it already. def _medianPrim(i,all): if i._median == None: i._median = median(all,ordered=False) return i._median