I am trying to make a sort of 'number line' plot (kind of like a flattened histogram) where the full range of each of my variables is shown as a line that is colored based on what values are actually represented in a dataset. I've been fairly successful with the numerical variables, but things are getting trickier with the categorical ones.
How do I make the categorical labels centered between two ticks and make the colored lines stop and end correctly so that they, too, are centered around the label?
Here is some sample data to use:
#Imports
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.colors as mcolors
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from datetime import date, datetime, timedelta
import datetime
from google.colab import files
import scipy
%matplotlib inline
pd.options.display.max_columns = 100
# Import sample data
gID = '0'
docID = '10f0BkWqf4rI9MFkR6NduntKj1oZ6aXXzezCXb06v1CU'
sample_data = pd.read_csv('https://docs.google.com/spreadsheets/d/' + docID + '/export?gid=' + gID + '&format=csv')
sample_data
And here is the function I made for the numerical values, which works pretty well:
def numberLine(data, interval): # data = the list for which you want each value in expanded
rounded = []
for val in data:
round_item = 0
count = 0
while val > (interval - 1): # keep adding up the interval until you can't add another without going over val
val -= interval
count += 1 # count how many intervals were required
round_item = count * interval # make list of vals 'rounded' to their lowest nearest interval
rounded.append(round_item)
x = [] # Make x, y list with results
placeholder = []
for val in rounded: # for every 'rounded' value, make a list of every number (with a resolution of the interval/100) in the interval
placeholder = np.arange(val, (val + interval),(interval/100)).tolist()
x = x + placeholder
y = np.zeros(len(x)) # set the y values to all zeros for number line
limit = max(x) # length of x-axis scale, but you don't have to use this if you want to set it manually while plotting
return x,y,limit
Here's what I've got for the categorical variables: (this is the part that doesn't work exactly right, though it runs and the gist is here)
def numberLine_cat(data,bounds):
dat = list(data) # make data column into a list and capitalize first letter
#dat = [data.capitalize() for item in dat]
#bounds = [bounds.capitalize() for item in bounds]
placeholder = 0
count = 0
final = []
for val in bounds: # for the entire set of possible categories, find those which data includes
if val in dat: # each category becomes a 25-value set (ie cat1 = 0-100, cat2 = 100,200).
placeholder = np.arange(((count * 10)+ 5), ((count*10)+15), .1).tolist() # if numberline looks 'dotted', change '1' to a fraction of tot numbers
final = placeholder + final
count += 1
elif val not in dat:
count +=1
x = final
y = np.zeros(len(final))
limit = len(bounds)*10 # length of the x-axis scale is the total # categories * 100
return x, y, limit
And here is how to plot it, using code based off of https://matplotlib.org/examples/ticks_and_spines/tick-locators.html
# Define setup(ax, lim)
def setup(ax,lim): #ax stays the way it is, lim is the x axis max limit
ax.spines['right'].set_color('none')
ax.spines['left'].set_color('none')
ax.yaxis.set_major_locator(ticker.NullLocator())
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.tick_params(which='major', width=1.00)
ax.tick_params(which='major', length=5)
ax.tick_params(which='minor', width=0.75)
ax.tick_params(which='minor', length=2.5)
ax.set_xlim(0, lim)
ax.set_ylim(0, 1)
ax.patch.set_alpha(0.0)
x, y, limit = numberLine(sample_data['Precipitation'],50);
x1, y1,limit1 = numberLine(sample_data['Temperature'],15)
species_bounds = ['dog','tree','mouse','elephant','dinosaur','turtle','human','dolphin','flower','elk','moose']
x2, y2, limit2 = numberLine_cat(sample_data['Species'],species_bounds)
#@title
fig = plt.figure(figsize=(10, 2))
n = 3
ticklabelpad = mpl.rcParams['xtick.major.pad']
# Precip test
ax = plt.subplot(n, 1, 1) # Not sure why this is necessary..
setup(ax, limit)
ax.xaxis.set_major_locator(ticker.AutoLocator())
ax.xaxis.set_minor_locator(ticker.AutoMinorLocator())
#ax.text(0.0, 0.1, "AutoLocator()", fontsize=14, transform=ax.transAxes)
ax.scatter(x,y,linewidth = '2', clip_on=False); # can add zorder > 0 to hide axis within points
ax.annotate('Precip. (mm)', xy=(-.13,0.15), xytext=(-1,-ticklabelpad), ha='left', va='top', xycoords='axes fraction', textcoords='offset points')
# Temperature test
ax = plt.subplot(n, 1, 2) # Not sure why this is necessary..
setup(ax, limit1)
ax.xaxis.set_major_locator(ticker.AutoLocator())
ax.xaxis.set_minor_locator(ticker.AutoMinorLocator())
#ax.text(0.0, 0.1, "AutoLocator()", fontsize=14, transform=ax.transAxes)
ax.scatter(x1,y1,linewidth = '2', clip_on=False); # can add zorder > 0 to hide axis within points
ax.annotate('Temp (C)', xy=(-.1,0.15), xytext=(-1,-ticklabelpad), ha='left', va='top', xycoords='axes fraction', textcoords='offset points')
# Animal test
ax = plt.subplot(n, 1, 3) # Not sure why this is necessary..
setup(ax, limit2)
ax.xaxis.set_major_locator(ticker.AutoLocator())
ax.xaxis.set_minor_locator(ticker.AutoMinorLocator())
ax.set_xticklabels(species_bounds)
#ax.text(0.0, 0.1, "AutoLocator()", fontsize=14, transform=ax.transAxes)
ax.scatter(x2,y2,clip_on=False); # can add zorder > 0 to hide axis within points
ax.annotate('Animals', xy=(-.1,0.15), xytext=(-1,-ticklabelpad), ha='left', va='top', xycoords='axes fraction', textcoords='offset points')
And here's what it looks like:(The last line should just have dog, cat, tree, elephant, human, dolphin, and flower highlighted)

Thank you so much, to anyone who wants to have a go at this! (This is my first post also, so if you have any tips, I'm all ears.)