Package: samplitude
SamplitudeCLI generation and plotting of random variables:$samplitude"sin(0.31415) | sample(6) | round | cli"0.00.3090.5880.8090.9511.0The wordsamplitudeis a portmanteau ofsampleandamplitude.  This
project also started as an étude, hence should be pronouncedsampl-étude.samplitudeis a chain starting with agenerator, followed by zero or morefilters, followed by a consumer.  Most generators are infinite (with the
exception ofrangeandlistsand possiblystdin).  Some of the filters can
turn infinite generators into finite generators (likesampleandgobble),
and some filters can turn finite generators into infinite generators, such aschoice.Consumersare filters that necessarily flush the input;list,cli,json,unique, and the plotting tools,hist,scatterandlineare
examples of consumers.  Thelistconsumer is a Jinja2 built-in, and other
Jinja2 consumers aresum,min, andmax:samplitude"sin(0.31415) | sample(5) | round | max | cli"0.951For simplicity,s8eis an alias for samplitude.GeneratorsIn addition to the standardrangefunction, we support infinite generatorsexponential(lambd):lambdis 1.0 divided by the desired mean.uniform(a, b): Get a random number in the range[a, b)or[a, b]depending on rounding.gauss(mu, sigma):muis the mean, andsigmais the standard deviation.normal(mu, sigma): as abovelognormal(mu, sigma): as abovetriangular(low, high): Continuous distribution bounded by given lower and
upper limits, and having a given mode value in-between.beta(alpha, beta): Conditions on the parameters arealpha > 0andbeta > 0.  Returned values range between 0 and 1.gamma(alpha, beta): as aboveweibull(alpha, beta):alphais the scale parameter andbetais the shape
parameter.pareto(alpha): Pareto distribution.alphais the shape parameter.vonmises(mu, kappa):muis the mean angle, expressed in radians between 0
and2*pi, andkappais the concentration parameter, which must be greater
than or equal to zero.  If kappa is equal to zero, this distribution reduces
to a uniform random angle over the range 0 to2*pi.Provided that you have installed thescipy.statspackage, thepert(low, peak, high)distribution is supported.We have a special infinite generator (filter) that works on finite generators:choice,whose behaviour is explained below.For input from files, either usewordswith a specified environment variableDICTIONARY, or pipe throughstdin()which reads fromstdin.If the file is a csv file, there is acsvgenerator that reads a csv file with
Pandas and outputs the first column (if nothing else is specified).  Specify the
column with either an integer index or a column name:>>>samplitude"csv('iris.csv', 'virginica') | counter | cli"050150250For other files, we have thefilegenerator:>>>s8e"file('iris.csv') | sample(1) | cli"150,4,setosa,versicolor,virginicaFinally, we havecombinationsandpermutationsthat are inherited from
itertools and behave exactly like those.>>>s8e"'ABC' | permutations | cli"However, the output of this is rather non-UNIXy, with the abstractions leaking through:>>>s8e"'HT' | permutations | cli"('H','T')('T','H')So to get a better output, we can use anelementwise joinelt_join:>>>s8e"'HT' | permutations | elt_join | cli"HT
THwhich also takes a seperator as argument:>>>s8e"'HT' | permutations | elt_join(';') | cli"H;T
T;HThis is already supported by Jinja'smapfunction (notice the strings aroundjoin):>>>s8e"'HT' | permutations | map('join', ';') | cli"H;T
T;HWe can thus count the number of permutations of a set of size 10:>>>s8e"range(10) | permutations | len"3628800Theproductgenerator takes two generators and computes a cross-product of
these.  In addition,A warning about infinityAll generators are (potentially) infinite generators, and must be sampled withsample(n)before consuming!Usage and installationInstall withpipinstallsamplitudeor to get bleeding release,pipinstallgit+https://github.com/pgdr/samplitudeExamplesThis is pure Jinja2:>>>samplitude"range(5) | list"[0,1,2,3,4]However, to get a more UNIXy output, we usecliinstead oflist:>>>s8e"range(5) | cli"01234To limit the output, we usesample(n):>>>s8e"range(1000) | sample(5) | cli"01234That isn't very helpful on therangegenerator, which is already finite, but
is much more helpful on an infinite generator.  The above example is probably
better written as>>>s8e"count() | sample(5) | cli"01234However, much more interesting are the infinite random generators, such as theuniformgenerator:>>>s8e"uniform(0, 5) | sample(5) | cli"3.39001988680592351.20027671377093180.409993918975691261.93945859536962644.37327472704115We can round the output in case we don't need as many digits (note thatroundis a generator as well and can be placed on either side ofsample):>>>s8e"uniform(0, 5) | round(2) | sample(5) | cli"4.984.422.052.293.34Selection and modificationsThesamplebehavior is equivalent to theheadprogram, or from languages
such as Haskell. Theheadalias is supported:>>>samplitude"uniform(0, 5) | round(2) | head(5) | cli"4.584.331.872.094.8dropis also available:>>>s8e"uniform(0, 5) | round(2) | drop(2) | head(3) | cli"1.872.094.8Toshiftandscaledistributions, we can use theshift(s)andscale(s)filters.
To get a Poisson distribution process starting at 15, we can run>>>s8e"poisson(4) | shift(15) | sample(5) |cli"1821192217or to get the Poisson point process (exponential distribution),>>>s8e"exponential(4) | round | shift(15) | sample(5) |cli"16.40515.5415.13215.15315.275Bothshiftandscalework on generators, so to addsin(0.1)andsin(0.2), we can run>>>s8e"sin(0.1) | shift(sin(0.2)) | sample(10) | cli"Choices and other operationsUsingchoicewith a finite generator gives an infinite generator that chooses
from the provided generator:>>>samplitude"range(0, 11, 2) | choice | sample(6) | cli"8081046Jinja2 supports more generic lists, e.g., lists of strings.  Hence, we can write>>>s8e"['win', 'draw', 'loss'] | choice | sample(6) | sort | cli"draw
draw
loss
loss
loss
win... and as in Python, strings are also iterable:>>>s8e"'HT' | cli"H
T... so we can flip six coins with>>>s8e"'HT' | choice | sample(6) | cli"H
T
T
H
H
HWe can flip 100 coins and count the output withcounter(which iscollections.Counter)>>>s8e"'HT' | choice | sample(100) | counter | cli"H47T53Thesortfunctionality works as expected on aCounterobject (adicttype), so if we want the output sorted by key, we can run>>>s8e"range(1,7) | choice | sample(100) | counter | sort | elt_join | cli"42# seed=42117221312421513616There is a minor hack to sort by value, namely byswap-ing the Counter twice:>>>s8e"range(1,7) | choice | sample(100) |counter | swap | sort | swap | elt_join | cli"42# seed=42312513616117221421Theswapfilter does an element-wise reverse, with element-wise reverse
defined on a dictionary as a list of(value, key)for each key-value pair in
the dictionary.So, to get the three most common anagram strings, we can run>>>s8e"words() | map('sort') | counter | swap | sort(reverse=True) |swap | sample(3) | map('first') | elt_join('') | cli"aeprs
acerst
opstUsingstdin()as a generator, we can pipe intosamplitude.  Beware thatstdin()flushes the input, hencestdin(currently) does not work with
infinite input streams.>>>ls|samplitude"stdin() | choice | sample(1) | cli"some_fileThen, if we ever wanted to shufflelswe can run>>>ls|samplitude"stdin() | shuffle | cli"some_file>>>catFILE|samplitude"stdin() | cli"# NOOP; cats FILEThe fun powder plotFor fun, if you have installedmatplotlib, we support plotting,histbeing
the most useful.>>>samplitude"normal(100, 5) | sample(1000) | hist"An exponential distribution can be plotted withexponential(lamba).  Note that
theclioutput must be the last filter in the chain, as that is a command-line
utility only:>>>s8e"normal(100, 5) | sample(1000) | hist | cli"Torepress output after plotting, you can use thegobblefilter to empty
the pipe:>>>s8e"normal(100, 5) | sample(1000) | hist | gobble"Thepertdistributiontakes inputslow,peak, andhigh:>>>s8e"pert(10, 50, 90) | sample(100000) | hist(100) | gobble"Althoughhistis the most useful, one could imaging runnings8eon
timeseries, where alineplot makes most sense:>>>s8e"sin(22/700) | sample(200) | line"The scatter function can also be used, but requires that the input stream is a
stream of pairs, which can be obtained either by theproductgenerator, or via
thepairorcounterfilter:s8e"normal(100, 10) | sample(10**5) | round(0) | counter | scatter"FourierA fourier transform is offered as a filterfft:>>>samplitude"sin(0.1) | shift(sin(0.2)) | sample(1000) | fft | line | gobble"Your own filterIf you use Samplitude programmatically, you can register your own filter by
sending a dictionary{'name1':filter1,'name2':filter2,#...,'namen':filtern,}to thesamplitudefunction.Example: secretary problemSuppose you want to emulate the secretary problem ...Intermezzo: The problemFor those not familiar, you are a boss, Alice, who wants to hire a new secretary
Bob.  Suppose you want to hire the tallest Bob of all your candidates, but the
candidates arrive in a stream, and you know only the number of candidates.  For
each candidate, you have to accept (hire) or reject the candidate.  Once you
have rejected a candidate, you cannot undo the decision.The solution to this problem is to look at the firstn/e(e~2.71828being
the Euler constant) candidates, and thereafter accept the first candidate taller
than all of then/efirst candidates.A Samplitude solutionLetnormal(170, 10)be the candidate generator, and letn=100.  We create a
filtersecretarythat takes a stream and an integer (n) and picks according
to the solution.  In order to be able to assess the quality of the solution
later, the filter must forward the entire list of candidates; hence we annotate
the one we choose with(c, False)for a candidate we rejected, and(c, True)denotes the candidate we accepted.defsecretary(gen,n):importmathexplore=int(n/math.e)target=-float('inf')i=0# explore the first n/e candidatesforcingen:target=max(c,target)yield(c,False)i+=1ifi==explore:break_ok=lambdac,i,found:((i==n-1andnotfound)or(c>targetandnotfound))have_hired=Falseforcingen:status=_ok(c,i,have_hired)have_hired=have_hiredorstatusyieldc,statusi+=1ifi==n:returnNow, to emulate the secretary problem with Samplitude:fromsamplitudeimportsamplitudeass8e# insert above secretary functionn=100filters={'secretary':secretary}solution=s8e('normal(170, 10) | secretary(%d) | list'%n,filters=filters)solution=eval(solution)# Samplitude returns an eval-able stringcands=map(lambdax:x[0],solution)opt=[s[0]forsinsolutionifs[1]][0]# the next line prints in which position the candidate isprint(1+sorted(cands,reverse=True).index(opt),'/',n)In about 67% of the cases we can expect to get one of the top candidates,
whereas the remaining 33% of the cases will be uniformly distributed.  Running
100k runs with a population of size 1000 reveals the structure.