Interactively Re-color Bars in Matplotlib Bar Chart using Confidence Intervals

2024/10/6 5:52:04

Trying to shade the bars in this chart based on the confidence that a selected y-value (represented by the red line) lies within a confidence interval. See recolorBars() method in the class example below.

While I understand colormaps, Normalize(), and ScalarMappable(), I'm stumped on which values to pass to Normalize() to create a color and shade for each bar.

Here's what my chart looks like when first generated. Interactive Bar Chart Screenshot

To generate the chart pictured above, call chart = interactiveChart(). The interactivity is based on click events, which trigger the setAxHLine() callback to set a red horizontal bar at the selected Y-val position. Eventually this method will also trigger the recolorBars() method.

Tested Code Example: (Notice that this is set up to run in a Jupyter notebook.)

%matplotlib notebook
# setup the environment 
import pandas as pd
import numpy as np
import statsmodels.stats.api as sms # for confidence intervals
from scipy.stats import sem # another confidence interval shorthand 
import matplotlib.cm as cm
import matplotlib.colors as col
import matplotlib.pyplot as plt
import mpl_toolkits.axes_grid1.inset_locator as mpl_il
from matplotlib.widgets import Button, Slider
# from matplotlib.ticker import FormatStrFormatter, ScalarFormatterclass interactiveBarChart:"""A base class that can be used for creating clicable charts and solvingthe challenges of interpreting plots with confidence intervals."""# basic greys: lighter for regular, darker for emphasisgreys = ['#afafaf','#7b7b7b'] # ticks and boxes, arrows, legend ticks and text# horizontal bar: nice redhorzo_bar = '#004a80'# set bar colormapcmap = cm.get_cmap('RdBu')# instantiate the classdef __init__(self): """Initialize the data and a new figure."""# seed for data.np.random.seed(12345)# get some data to plotself.df = pd.DataFrame(np.c_[np.random.normal(33500,150000,3650), # np.c_ class to transpose arraynp.random.normal(41000,90000,3650), np.random.normal(41000,120000,3650), np.random.normal(48000,55000,3650)], columns=[1992,1993,1994,1995])# get mean values to plotself.means = self.df.mean()        # calculate confidence interval high and lowself.c_i = [ sms.DescrStatsW(self.df[i]).tconfint_mean() for i in self.df.columns ]# calculate the interval whole numberself.intervals = [ invl[-1] - invl[0] for invl in self.c_i ] # plot the bar chart and make a reference to the rectanglesself.rects = plt.bar(range(len(self.df.columns)), self.means,yerr=self.df.sem().values*1.96,align='center', alpha=0.8, color=self.greys[0],error_kw=dict(ecolor='gray', lw=2, capsize=7, capthick=2))# set up a starting axhlineself.horzo_slider = plt.axhline(y=40000, xmin=-.1, clip_on=False, zorder=1, color='#e82713')## TICKS AND TEXT AND SPINESplt.title('Confidence Interval Interactivity: Click the Chart To Recolor', color=self.greys[1])plt.xticks(range(len(self.df.columns)), self.df.columns)        # do some formatting self.formatArtists(plt.gca())## EVENT HANDLING# reference the axes and setup pick eventsplt.gcf().canvas.mpl_connect('button_press_event', self.setAxHLine)def formatArtists(self, ax):"""Does some recoloring and formatting of the ticks, labels, and spines.Receives the axes of the current figure."""# recolor the ticksax.xaxis.set_tick_params(which='major', colors=self.greys[1])ax.yaxis.set_tick_params(which='major', colors=self.greys[1])# recolor the spinesfor pos in ['top', 'right', 'bottom', 'left']:ax.spines[pos].set_edgecolor(self.greys[0])## EVENT HANDLERSdef setAxHLine(self, event): """Handle the logic for handling bar coloring when the slider is moved up or down over the confidence intervals."""# remove first axhlineself.horzo_slider.remove()self.horzo_slider = plt.axhline(y=event.ydata, xmin=-.1, clip_on=False, zorder=1, color='#e82713')# self.recolorBars(event)def recolorBars(self, event):"""Handles all recoloring of the bars based on the confidence that the selected y-value is within a given interval on the chart.This function is called on a button press event and receives that data as an argument."""        # get the yval y = event.ydata# how to determine the shades ?
#         abs_diffs = [ abs((mean + conf)-y|) for mean, conf in zip(self.means, self.intervals) ]# how to pass in the map to get the colors to apply to the bars?        
#        colors = [ cm.ScalarMappable(norm=col.Normalize(vmin=i[0] , vmax=i[-1]), cmap=self.cmap) for i in self.c_i ]# apply the colors in a list comprehension# [ rect.set_color(color) for rect, color in zip(self.rects, colors) ]def showPlot(self):"""Convenience if not using the inline display setup %matplotlib notebook"""plt.show()
Answer

This is how I would handle this:

def recolorBars(self, event):      y = event.ydatafor i, rect in enumerate(self.rects):t, p, _ = sms.DescrStatsW(self.df[self.df.columns[i]]).ttest_mean(y)rect.set_color(self.cpick.to_rgba((1 - p) * t / abs(t)))

When iterating through the bars, first test the value against the sample mean, then set the color based on p-value and test statistic t: (1 - p) * t

Also, you must define cpick at the same time as cmap and set it to (-1, 1) using:

cpick = cm.ScalarMappable(cmap=cmap)
cpick.set_array(np.linspace(-1, 1))

The modifications above got me this result

https://en.xdnf.cn/q/119982.html

Related Q&A

Unlock password protected Workbook using VBA or Python

I have a workbook name m.xlsx, but its password protected and Ive forgotten the password. How can I open it or un-protect it?The following code does not work:Unprotect workbook without password I need…

How do I make a variable detect if it is greater than or less than another one?

I am currently learning Python, and I decided to build a small "Guess the Number" type of game. I am using the random feature, and trying to make it so it will detect if the users input is eq…

Python Regular Expression from File

I want to extract lines following some sequence from a file. E.g. a file contains many lines and I want line in sequencejourney (a,b) from station south chennai to station punjab chandigarh journey (c,…

Changing the words keeping its meaning intact [closed]

Its difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying thi…

How to create index for a SQLite3 database using SQLAlchemy?

I have multiple SQLite3 databases for which the models are not available. def index_db(name, tempdb):print(f{name.ljust(padding)} Indexing file: {tempdb})if tempdb.endswith(primary.sqlite):conn = sqlit…

Implementing ast.literal_eval on a numpy array

With the following expression, you can convert a string to a python dict.>>> import ast >>> a = ast.literal_eval("{muffin : lolz, foo : kitty}") >>> a {muffin: lolz…

Best way to make argument parser accept absolute number and percentage?

I am trying to write a Nagios style check to use with Nagios. I have working script that takes in something like -w 15 -c 10 and interprets that as "Warning at 15%, Critical at 10%". But I ju…

Python calculating prime numbers

I have to define a function called is_prime that takes a number x as input, then for each number n from 2 to x - 1, test if x is evenly divisible by n. If it is, return False. If none of them are, then…

Why am I getting a column does not exist error when it does exist? I am modifying the Flask tutorial

I have a column named ticker_symbol, but I am getting a error when I run the error that there is no such column. Here is my auth.py code so far. It is similar to the Flask tutorial code. I get my get_d…

Update Key Value In Python In JSON File

How do I change a value in a json file with python? I want to search and find "class": "DepictionScreenshotsView" and replace it with "class": ""JSON File:{&quo…