Skip to content

Instantly share code, notes, and snippets.

@tmthydvnprt
Last active December 4, 2015 02:57
Show Gist options
  • Select an option

  • Save tmthydvnprt/cab1e97381bef535ab56 to your computer and use it in GitHub Desktop.

Select an option

Save tmthydvnprt/cab1e97381bef535ab56 to your computer and use it in GitHub Desktop.
Text Plotting
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