Last active
December 4, 2015 02:57
-
-
Save tmthydvnprt/cab1e97381bef535ab56 to your computer and use it in GitHub Desktop.
Text Plotting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| def textplot(data, | |
| kind='line', | |
| width=100, height=None, | |
| ymax=None, ymin=None, xmax=None, xmin=None, | |
| margin=None, | |
| yticks=5, xticks=10, | |
| bar_padding=2, | |
| bar_style='#', | |
| line_styles=['*','+','-','x','o','·','•','^','˚','°','∞','∆'], gridstyle='˙', ystyle='|', xstyle='-', | |
| title=1, | |
| legend=True | |
| ): | |
| """ | |
| Print a crude ASCII art plot from a pandas series/dataframe. | |
| Examples | |
| ======== | |
| textplot(pd.Series[1,2,3,4], 100, 25, title='ASCII plot') | |
| """ | |
| output = [] | |
| # Ensure DataFrame | |
| if not isinstance(data, pd.core.frame.DataFrame): | |
| if kind is 'line': | |
| dataframe = pd.DataFrame({'1':data}) | |
| legend=False | |
| elif kind is 'bar': | |
| dataframe = pd.DataFrame({'1':data}).T | |
| else: | |
| if kind is 'bar' and data.shape[0] > data.shape[1]: | |
| dataframe = data.T | |
| else: | |
| dataframe = data | |
| # Clean data | |
| dataframe = dataframe.replace([np.inf, -np.inf], np.nan).fillna(0.0) | |
| # Configure settings | |
| a = dataframe.index[0] | |
| if isinstance(dataframe.index[0], pd.tslib.Timestamp): | |
| xtype = 'time' | |
| elif isinstance(a, int) or isinstance(a, float): | |
| xtype = 'number' | |
| else: | |
| xtype = 'string' | |
| xtime_fmt = '%m/%y' | |
| margin = max([len('{:,.0f}'.format(m)) for m in [dataframe.max().max(), dataframe.min().min()]]) if not margin else margin | |
| K = 10 ** (len(str(int(dataframe.abs().max().max()))) - 2) | |
| W = width - margin - 3 | |
| H = height if height else int(0.4 * width) | |
| if title: | |
| H -= 1 | |
| if legend: | |
| H -= 1 | |
| if isinstance(title, int) or isinstance(title, float): | |
| title = 'Figure ' + str(title) | |
| if legend and kind is not 'bar': | |
| legend = ' '.join(['{} ({})'.format(series, line_styles[i]) for i, series in enumerate(dataframe)]) | |
| if kind is 'line': | |
| # overrides | |
| bar_padding = 0 | |
| # Get start and stop index | |
| a = dataframe.index[0] | |
| b = dataframe.index[-1] | |
| # Convert to epoch if timestamp | |
| if xtype is 'time': | |
| a = (a.date() - datetime.date(1970,1,1)).total_seconds() + 24.0 * 3600.0 | |
| b = (b.date() - datetime.date(1970,1,1)).total_seconds() + 24.0 * 3600.0 | |
| # Interpolate data over display width | |
| plot_data_x = {} | |
| x_data = np.linspace(a, b, len(dataframe.index)) | |
| x_disp = np.linspace(a, b, W) | |
| for series in dataframe: | |
| f = si.interp1d(x_data, dataframe[series], kind='cubic') | |
| plot_data_x[series] = f(x_disp) | |
| plot_data_x = pd.DataFrame(plot_data_x) | |
| # Normalize data to display height | |
| ma = plot_data_x.replace([np.inf, -np.inf], np.nan).fillna(0.0).max().max() if not ymax else float(ymax) | |
| mi = plot_data_x.replace([np.inf, -np.inf], np.nan).fillna(0.0).min().min() if not ymin else float(ymin) | |
| plot_data_xy = (float(H) * (plot_data_x - mi)/(ma - mi)).replace([np.inf, -np.inf], np.nan).fillna(0.0).astype(int, raise_on_error=False) | |
| # Determine X labels | |
| xtickvalues = np.linspace(a, b, xticks) | |
| # Determine Y gridpoints | |
| ytickvalues = np.linspace(K * math.ceil(0.9 * mi / K), K * math.floor(0.9 * ma / K), yticks) | |
| ytickpoints = [int(float(H) * (yg - mi)/(ma - mi)) for yg in ytickvalues] | |
| zeroline = int(float(H) * (0.0 - mi)/(ma - mi)) | |
| elif kind is 'bar': | |
| # overrides | |
| xticks = len(dataframe.columns) | |
| # Interpolate data over display width | |
| bar_num = xticks | |
| plot_data_x = {} | |
| barwidth = (W - bar_padding*bar_num - 2*bar_padding) / float(bar_num) | |
| bar_centers = np.linspace(0 + bar_padding + barwidth/2.0, W - bar_padding - barwidth/2.0, bar_num) | |
| bars = [(b - barwidth/2.0, b + barwidth/2.0) for b in bar_centers] | |
| x_disp = np.linspace(0, W, W) | |
| for i, series in enumerate(dataframe): | |
| plot_data_x[series] = [dataframe[series]['1'] if bars[i][0] < _ < bars[i][1] else None for _ in x_disp] | |
| plot_data_x = pd.DataFrame(plot_data_x) | |
| # Normalize data to display height | |
| ma = dataframe.replace([np.inf, -np.inf], np.nan).fillna(0.0).max().max() if not ymax else float(ymax) | |
| mi = dataframe.replace([np.inf, -np.inf], np.nan).fillna(0.0).min().min() if not ymin else float(ymin) | |
| zeroline = int(float(H) * (0.0 - mi)/(ma - mi)) | |
| plot_data_xy = (float(H) * (plot_data_x - mi)/(ma - mi)).replace([np.inf, -np.inf], np.nan).astype(int, raise_on_error=False) | |
| # Determine X labels | |
| xtickvalues = list(dataframe.columns) | |
| # Determine Y gridpoints | |
| ytickvalues = np.linspace(K * math.ceil(0.9 * mi / K), K * math.floor(0.9 * ma / K), yticks) | |
| ytickpoints = [int(float(H) * (yg - mi)/(ma - mi)) for yg in ytickvalues] | |
| # Add Title / Legend | |
| output.append(title.center(width)) | |
| if legend and kind is not 'bar': | |
| output.append(legend.center(width)) | |
| # Loop over each row | |
| for h in xrange(H - 1, -1, -1): | |
| # Initialize empty line | |
| s = [' '] * W | |
| # loop over series and x values, plot point | |
| if kind is 'line': | |
| for i, series in enumerate(plot_data_xy): | |
| for x in xrange(W): | |
| if h == plot_data_xy[series][x]: | |
| s[x] = line_styles[i] | |
| if kind is 'bar': | |
| for i, series in enumerate(plot_data_xy): | |
| for x in xrange(W): | |
| # plot positive numbers | |
| if dataframe[series]['1'] > 0.0 and plot_data_xy[series][x] >= h >= zeroline: | |
| s[x] = bar_style | |
| # plot negative numbers | |
| if dataframe[series]['1'] < 0.0 and zeroline >= h >= plot_data_xy[series][x]: | |
| s[x] = bar_style | |
| # Join line | |
| s = ''.join(s) | |
| # Prepend Y axis labels and add y axis grid lines | |
| if h == H - 1: | |
| ylabels = '{:,.0f}'.format(ma).rjust(margin)[:margin] | |
| elif any([h == _ for _ in ytickpoints]): | |
| g = ytickpoints.index(h) | |
| ylabels = '{:,.0f}'.format(ytickvalues[g]).rjust(margin)[:margin] | |
| s = s.replace(' ', gridstyle) | |
| elif h == 0: | |
| ylabels = '{:,.0f}'.format(mi).rjust(margin)[:margin] | |
| else: | |
| ylabels = ' ' * margin | |
| output.append(ylabels + ystyle + s) | |
| # Add X axis labels | |
| xlabels = ' ' * int(bar_padding / 2.0) | |
| xoffset = (W - 5) / float(xticks - 1) | |
| for i, d in enumerate(xtickvalues): | |
| if kind is 'line': | |
| leftover = ((i + 1) * xoffset) - (len(xlabels) + xoffset) | |
| xwidth = round(xoffset + leftover) | |
| xlabel = datetime.date.fromtimestamp(d).strftime(xtime_fmt) if xtype == 'time' else '{:0.2f}'.format(d) | |
| xlabels += xlabel.ljust(int(round(xwidth))) | |
| elif kind is 'bar': | |
| xlabels += str(d).center(int(round(barwidth + bar_padding))) | |
| xaxis = (' ' * margin) + '|' + (xstyle * W) | |
| output.append(xaxis) | |
| output.append((' ' * (margin + 1)) + xlabels) | |
| return '\n'.join(output) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment