diff --git a/.gitignore b/.gitignore index ee9a2c6..1649719 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ env bashplotlib.egg-info build dist - +.DS_Store diff --git a/bashplotlib/__init__.py b/bashplotlib/__init__.py index e69de29..b1ac3b1 100644 --- a/bashplotlib/__init__.py +++ b/bashplotlib/__init__.py @@ -0,0 +1,3 @@ +from .histogram import plot_hist +from .scatterplot import plot_scatter +from .barplot import plot_bar \ No newline at end of file diff --git a/bashplotlib/barplot.py b/bashplotlib/barplot.py new file mode 100644 index 0000000..ab32b04 --- /dev/null +++ b/bashplotlib/barplot.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- + +""" +Plotting terminal based barplots +""" + +from .utils.helpers import * +from .histogram import calc_bins, read_numbers + +def plot_bar(f, height=20.0, bincount=None, binwidth=None, pch="o", + colour="default", title="", xlab=None, showSummary=False, + regular=False, return_str=False): + ''' Plot bar. + + Parameters + ---------- + f : list(number), numpy.ndarray, str(filepath) + input array + height : float + the height of the histogram in # of lines + bincount : int + number of bins in the histogram + binwidth : int + width of bins in the histogram + pch : str + shape of the bars in the plot, e.g 'o' + colour : str + white,aqua,pink,blue,yellow,green,red,grey,black,default,ENDC + title : str + title at the top of the plot, None = no title + xlab : boolean + whether or not to display x-axis labels + showSummary : boolean + whether or not to display a summary + regular : boolean + whether or not to start y-labels at 0 + return_str : boolean + return string represent the plot or print it out, default: False + + Example + ------- + >>> y = np.random.rand(50) + >>> plot_bar(y, bincount=50, colour='red') + + >>> 0.971| + >>> 0.923| o o o o + >>> 0.875| o o o o o oo + >>> 0.827| o o o o o o ooo + >>> 0.779| o o o o o o o o ooo + >>> 0.731| o o o o o o o o o ooo + >>> 0.683| oo oo o o o o o o o o o ooo + >>> 0.635| oo oo o oo o o o o o oo o ooo + >>> 0.587| oo oo o oo o ooo o o oo o ooo + >>> 0.539| oo oo o oooo ooo o o oo o ooo o + >>> 0.491| ooo ooo o oooo ooo o o ooo o ooo o + >>> 0.443| ooo ooo oo ooooo oooo o o ooo o ooo o + >>> 0.395| ooo ooo ooooooooo oooo o o ooo o ooo o + >>> 0.347| oooo ooo ooooooooo ooooo o o ooo o ooo o + >>> 0.299| oooo ooo ooooooooo ooooo o o ooo o ooo o + >>> 0.251| oooo ooo ooooooooo ooooo o o ooo o ooo o o + >>> 0.203| oooo ooo ooooooooo ooooo ooo ooo o ooo o o + >>> 0.155| oooo ooo oo ooooooooo ooooo ooo ooo ooooooo oo + >>> 0.107| ooooo ooo oo ooooooooo oooooooooo ooooooooooo ooo + >>> 0.059| ooooo oooooo ooooooooo ooooooooooooooooooooooo ooo + >>> 0.011| oooooooooooooooooooooo ooooooooooooooooooooooooooo + >>> -------------------------------------------------- + ''' + if pch is None: + pch = "o" + + splot = '' + if isinstance(f, str): + f = open(f).readlines() + + # ====== Create data ====== # + min_val, max_val = None, None + n, mean, sd = 0.0, 0.0, 0.0 + + # pick mode and get data + numbers = [i for i in read_numbers(f)] + int_mode = False + if numbers[0].is_integer(): + int_mode = True + + # rescale big enough to show on bars + min_orig = min(numbers) # original + max_orig = max(numbers) + numbers = [1000 * (i - min_orig) / (max_orig - min_orig + 1e-8) for i in numbers] + + # statistics + n = len(numbers) + min_val = min(numbers) + max_val = max(numbers) + mean = sum(numbers) / n + sd = (sum([(mean - i)**2 for i in numbers]) / (n - 1)) ** 0.5 + + # bins is index + if bincount is not None: + bincount = min(bincount, n) + bins = list(calc_bins(n, 0., n + 0., bincount, binwidth)) + bins = [int(i) for i in bins] + hist = dict((i, 0) for i in range(len(bins) - 1)) + + # hist is the mean value of array with indices within bin + for idx, (i, j) in enumerate(zip(bins, bins[1:])): + arr = numbers[i:j] + hist[idx] = sum(arr) / len(arr) # calculate mean + + # ====== Start plot ====== # + min_y, max_y = min(hist.values()), max(hist.values()) + + start = max(min_y, 1) + stop = max_y + 1 + + if regular: + start = 1 + + if height is None: + height = stop - start + if height > 20: + height = 20 + + ys = list(drange(start, stop, float(stop - start) / height)) + ys.reverse() + + nlen = max(len(str(min_y)), len(str(max_y))) + 1 + + if title: + splot += print_return_str( + box_text(title, max(len(hist) * 2, len(title)), nlen), + return_str=return_str) + splot += print_return_str('', return_str=return_str) + + used_labs = set() + for y in ys: + if int_mode: + ylab = '%d' % int(y * (max_orig - min_orig + 1e-8) / 1000 + min_orig) + else: + ylab = '%.3f' % float(y * (max_orig - min_orig + 1e-8) / 1000 + min_orig) + if ylab in used_labs: + ylab = "" + else: + used_labs.add(ylab) + ylab = " " * (nlen - len(ylab)) + ylab + "|" + + splot += print_return_str(ylab, end=' ', return_str=return_str) + + for i in range(len(hist)): + if int(y) <= hist[i]: + splot += printcolour(pch, True, colour, return_str) + else: + splot += printcolour(" ", True, colour, return_str) + splot += print_return_str('', return_str=return_str) + xs = hist.keys() + + splot += print_return_str(" " * (nlen + 1) + "-" * len(xs), + return_str=return_str) + + if xlab: + xlen = len(str(float((max_y) / height) + max_y)) + for i in range(0, xlen): + splot += printcolour(" " * (nlen + 1), True, colour, return_str) + for x in range(0, len(hist)): + num = str(bins[x]) + if x % 2 != 0: + pass + elif i < len(num): + splot += print_return_str(num[i], end=' ', + return_str=return_str) + else: + splot += print_return_str(" ", end=' ', + return_str=return_str) + splot += print_return_str('', return_str=return_str) + + center = max(map(len, map(str, [n, min_val, mean, max_val]))) + center += 15 + + if showSummary: + splot += print_return_str('', return_str=return_str) + splot += print_return_str("-" * (2 + center), return_str=return_str) + splot += print_return_str("|" + "Summary".center(center) + "|", + return_str=return_str) + splot += print_return_str("-" * (2 + center), return_str=return_str) + summary = "|" + ("observations: %d" % n).center(center) + "|\n" + summary += "|" + ("min value: %f" % min_val).center(center) + "|\n" + summary += "|" + ("mean : %f" % mean).center(center) + "|\n" + summary += "|" + ("sd : %f" % sd).center(center) + "|\n" + summary += "|" + ("max value: %f" % max_val).center(center) + "|\n" + summary += "-" * (2 + center) + splot += print_return_str(summary, return_str=return_str) + + if return_str: + return splot diff --git a/bashplotlib/histogram.py b/bashplotlib/histogram.py index 4c1ad57..c4d6687 100644 --- a/bashplotlib/histogram.py +++ b/bashplotlib/histogram.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- """ @@ -87,29 +86,54 @@ def run_demo(): plot_hist(demo_file, height=35.0, bincount=40) -def plot_hist(f, height=20.0, bincount=None, binwidth=None, pch="o", colour="default", title="", xlab=None, showSummary=False, regular=False): - """ - Make a histogram - - Arguments: - height -- the height of the histogram in # of lines - bincount -- number of bins in the histogram - binwidth -- width of bins in the histogram - pch -- shape of the bars in the plot - colour -- colour of the bars in the terminal - title -- title at the top of the plot - xlab -- boolen value for whether or not to display x-axis labels - showSummary -- boolean value for whether or not to display a summary - regular -- boolean value for whether or not to start y-labels at 0 - """ +def plot_hist(f, height=20.0, bincount=None, binwidth=None, pch="o", + colour="default", title="", xlab=None, showSummary=False, + regular=False, return_str=False): + ''' Plot histogram. + 1801| oo + 1681| oo + 1561| oooo + 961| oooo + 841| oooo + 721| ooooo + 601| oooooo + 241| oooooo + 121| oooooooo + 1| oooooooooooooo + -------------- + Parameters + ---------- + f : list(number), numpy.ndarray, str(filepath) + input array + height : float + the height of the histogram in # of lines + bincount : int + number of bins in the histogram + binwidth : int + width of bins in the histogram + pch : str + shape of the bars in the plot, e.g 'o' + colour : str + white,aqua,pink,blue,yellow,green,red,grey,black,default,ENDC + title : str + title at the top of the plot, None = no title + xlab : boolean + whether or not to display x-axis labels + showSummary : boolean + whether or not to display a summary + regular : boolean + whether or not to start y-labels at 0 + return_str : boolean + return string represent the plot or print it out, default: False + ''' if pch is None: pch = "o" - + splot = '' if isinstance(f, str): f = open(f).readlines() min_val, max_val = None, None - n, mean = 0.0, 0.0 + n, mean, sd = 0.0, 0.0, 0.0 for number in read_numbers(f): n += 1 @@ -121,6 +145,12 @@ def plot_hist(f, height=20.0, bincount=None, binwidth=None, pch="o", colour="def mean /= n + for number in read_numbers(f): + sd += (mean - number)**2 + + sd /= (n - 1) + sd **= 0.5 + bins = list(calc_bins(n, min_val, max_val, bincount, binwidth)) hist = dict((i, 0) for i in range(len(bins))) @@ -151,8 +181,10 @@ def plot_hist(f, height=20.0, bincount=None, binwidth=None, pch="o", colour="def nlen = max(len(str(min_y)), len(str(max_y))) + 1 if title: - print(box_text(title, max(len(hist) * 2, len(title)), nlen)) - print() + splot += print_return_str( + box_text(title, max(len(hist) * 2, len(title)), nlen), + return_str=return_str) + splot += print_return_str('', return_str=return_str) used_labs = set() for y in ys: @@ -163,46 +195,53 @@ def plot_hist(f, height=20.0, bincount=None, binwidth=None, pch="o", colour="def used_labs.add(ylab) ylab = " " * (nlen - len(ylab)) + ylab + "|" - print(ylab, end=' ') + splot += print_return_str(ylab, end=' ', return_str=return_str) for i in range(len(hist)): if int(y) <= hist[i]: - printcolour(pch, True, colour) + splot += printcolour(pch, True, colour, return_str) else: - printcolour(" ", True, colour) - print('') + splot += printcolour(" ", True, colour, return_str) + splot += print_return_str('', return_str=return_str) xs = hist.keys() - print(" " * (nlen + 1) + "-" * len(xs)) + splot += print_return_str(" " * (nlen + 1) + "-" * len(xs), + return_str=return_str) if xlab: xlen = len(str(float((max_y) / height) + max_y)) for i in range(0, xlen): - printcolour(" " * (nlen + 1), True, colour) + splot += printcolour(" " * (nlen + 1), True, colour, return_str) for x in range(0, len(hist)): num = str(bins[x]) if x % 2 != 0: pass elif i < len(num): - print(num[i], end=' ') + splot += print_return_str(num[i], end=' ', + return_str=return_str) else: - print(" ", end=' ') - print('') + splot += print_return_str(" ", end=' ', + return_str=return_str) + splot += print_return_str('', return_str=return_str) center = max(map(len, map(str, [n, min_val, mean, max_val]))) center += 15 if showSummary: - print() - print("-" * (2 + center)) - print("|" + "Summary".center(center) + "|") - print("-" * (2 + center)) + splot += print_return_str('', return_str=return_str) + splot += print_return_str("-" * (2 + center), return_str=return_str) + splot += print_return_str("|" + "Summary".center(center) + "|", + return_str=return_str) + splot += print_return_str("-" * (2 + center), return_str=return_str) summary = "|" + ("observations: %d" % n).center(center) + "|\n" summary += "|" + ("min value: %f" % min_val).center(center) + "|\n" summary += "|" + ("mean : %f" % mean).center(center) + "|\n" + summary += "|" + ("sd : %f" % sd).center(center) + "|\n" summary += "|" + ("max value: %f" % max_val).center(center) + "|\n" summary += "-" * (2 + center) - print(summary) + splot += print_return_str(summary, return_str=return_str) + if return_str: + return splot def main(): diff --git a/bashplotlib/scatterplot.py b/bashplotlib/scatterplot.py index ddaf3cc..da962cc 100644 --- a/bashplotlib/scatterplot.py +++ b/bashplotlib/scatterplot.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- """ @@ -9,6 +8,7 @@ import csv import sys import optparse +from os.path import isfile from .utils.helpers import * from .utils.commandhelp import scatter @@ -17,7 +17,8 @@ def get_scale(series, is_y=False, steps=20): min_val = min(series) max_val = max(series) scaled_series = [] - for x in drange(min_val, max_val, (max_val - min_val) / steps): + for x in drange(min_val, max_val, (max_val - min_val) / steps, + include_stop=True): if x > 0 and scaled_series and max(scaled_series) < 0: scaled_series.append(0.0) scaled_series.append(x) @@ -27,7 +28,71 @@ def get_scale(series, is_y=False, steps=20): return scaled_series -def plot_scatter(f, xs, ys, size, pch, colour, title): +def plot_scatter(xs, ys, size=None, pch='o', + colour='red', title=None, return_str=False): + ''' Scatter plot. + ---------------------- + | * | + | * | + | * | + | * | + | * | + | * | + | * | + | * | + ----------------------- + Parameters + ---------- + xs : list, numpy.ndarray + list of x series + ys : list, numpy.ndarray + list of y series + size : int + width of plot + pch : str + any character to represent a points + colour : str, list(str) + white,aqua,pink,blue,yellow,green,red,grey,black,default,ENDC + title : str + title for the plot, None = not show + return_str : boolean + return string represent the plot or print it out, default: False + ''' + splot = '' + plotted = set() + cs = colour + + if size is None: + size = 13 + + if title: + splot += print_return_str( + box_text(title, 2 * len(get_scale(xs, False, size)) + 1), + return_str=return_str) + + # ====== Top line ====== # + splot += print_return_str(' ' + "-" * (len(get_scale(xs, False, size)) + 2), + return_str=return_str) + # ====== Main plot ====== # + for y in get_scale(ys, True, size): + splot += print_return_str("|", end=' ', return_str=return_str) + for x in get_scale(xs, False, size): + point = " " + for (i, (xp, yp)) in enumerate(zip(xs, ys)): + if xp <= x and yp >= y and (xp, yp) not in plotted: + point = pch + plotted.add((xp, yp)) + if isinstance(cs, list): + colour = cs[i] + splot += printcolour(point, True, colour, return_str) + splot += print_return_str(" |", return_str=return_str) + # ====== Bottom line ====== # + splot += print_return_str(' ' + "-" * (len(get_scale(xs, False, size)) + 2), + return_str=return_str) + if return_str: + return splot + +def _plot_scatter(f, xs, ys, size, pch, colour, title): """ Form a complex number. @@ -45,38 +110,21 @@ def plot_scatter(f, xs, ys, size, pch, colour, title): if isinstance(f, str): f = open(f) - data = [tuple(map(float, line.strip().split(','))) for line in f] - xs = [i[0] for i in data] - ys = [i[1] for i in data] + data = [tuple(line.strip().split(',')) for line in f] + xs = [float(i[0]) for i in data] + ys = [float(i[1]) for i in data] + if len(data[0]) > 2: + cs = [i[2].strip() for i in data] + else: + cs = None else: xs = [float(str(row).strip()) for row in open(xs)] ys = [float(str(row).strip()) for row in open(ys)] - - plotted = set() - - if title: - print(box_text(title, 2 * len(get_scale(xs, False, size)) + 1)) - - print("-" * (2 * len(get_scale(xs, False, size)) + 2)) - for y in get_scale(ys, True, size): - print("|", end=' ') - for x in get_scale(xs, False, size): - point = " " - for (i, (xp, yp)) in enumerate(zip(xs, ys)): - if xp <= x and yp >= y and (xp, yp) not in plotted: - point = pch - #point = str(i) - plotted.add((xp, yp)) - if x == 0 and y == 0: - point = "o" - elif x == 0: - point = "|" - elif y == 0: - point = "-" - printcolour(point, True, colour) - print("|") - print("-" * (2 * len(get_scale(xs, False, size)) + 2)) - + if isfile(colour): + cs = [str(row).strip() for row in open(colour)] + else: + cs = colour + plot_scatter(xs, ys, size=size, pch=pch, colour=cs, title=title) def main(): @@ -97,7 +145,7 @@ def main(): opts.f = sys.stdin.readlines() if opts.f or (opts.x and opts.y): - plot_scatter(opts.f, opts.x, opts.y, opts.size, opts.pch, opts.colour, opts.t) + _plot_scatter(opts.f, opts.x, opts.y, opts.size, opts.pch, opts.colour, opts.t) else: print("nothing to plot!") diff --git a/bashplotlib/utils/commandhelp.py b/bashplotlib/utils/commandhelp.py index 411b7ad..56ad495 100644 --- a/bashplotlib/utils/commandhelp.py +++ b/bashplotlib/utils/commandhelp.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- """ diff --git a/bashplotlib/utils/helpers.py b/bashplotlib/utils/helpers.py index bfffe46..3ab18ed 100644 --- a/bashplotlib/utils/helpers.py +++ b/bashplotlib/utils/helpers.py @@ -1,26 +1,25 @@ -#!/usr/bin/evn python # -*- coding: utf-8 -*- """ Various helpful function for bashplotlib """ - +from __future__ import print_function import sys isiterable = lambda x: hasattr(x, '__iter__') or hasattr(x, '__getitem__') bcolours = { - "white": '\033[97m', - "aqua": '\033[96m', - "pink": '\033[95m', - "blue": '\033[94m', - "yellow": '\033[93m', - "green": '\033[92m', - "red": '\033[91m', - "grey": '\033[90m', - "black": '\033[30m', + "white": '\033[97m', + "aqua": '\033[96m', + "pink": '\033[95m', + "blue": '\033[94m', + "yellow": '\033[93m', + "green": '\033[92m', + "red": '\033[91m', + "grey": '\033[90m', + "black": '\033[30m', "default": '\033[39m', - "ENDC": '\033[39m', + "ENDC": '\033[39m', } colour_help = ', '.join([colour for colour in bcolours if colour != "ENDC"]) @@ -32,8 +31,12 @@ def get_colour(colour): """ return bcolours.get(colour, bcolours['ENDC']) +def print_return_str(text, end='\n', return_str=False): + if not return_str: + print(text, end=end) + return text + end -def printcolour(text, sameline=False, colour=get_colour("ENDC")): +def printcolour(text, sameline=False, colour=get_colour("ENDC"), return_str=False): """ Print color text using escape codes """ @@ -41,8 +44,10 @@ def printcolour(text, sameline=False, colour=get_colour("ENDC")): sep = '' else: sep = '\n' - sys.stdout.write(get_colour(colour) + text + bcolours["ENDC"] + sep) - + if colour == 'default' or colour == 'ENDC' or colour is None: + return print_return_str(text, sep, return_str) + return print_return_str(get_colour(colour) + text + bcolours["ENDC"], + sep, return_str) def drange(start, stop, step=1.0, include_stop=False): """ @@ -68,7 +73,7 @@ def box_text(text, width, offset=0): """ Return text inside an ascii textbox """ - box = " " * offset + "-" * (width+2) + "\n" + box = " " * offset + "-" * (width + 2) + "\n" box += " " * offset + "|" + text.center(width) + "|" + "\n" - box += " " * offset + "-" * (width+2) + box += " " * offset + "-" * (width + 2) return box diff --git a/setup.py b/setup.py index a4c67ee..da6e1af 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name="bashplotlib", - version="0.6.4", + version="0.6.5", author="Greg Lamp", author_email="lamp.greg@gmail.com", url="https://github.com/glamp/bashplotlib",