Plotting categorical variables along a number line (ticks and spines) in Python

Viewed 377

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)

number line plot generated with code, above

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.)

0 Answers
Related