def plot_heated_stacked_area(df: pd.DataFrame, lines: str, heat: str, backtest: str = None, reset_y_lim: bool = False, figsize: Tuple[int, int] = (16, 9), color_map: str = 'afmhot', ax: Axis = None, upper_lower_missing_scale: float = 0.05) -> Axis: color_function = plt.get_cmap(color_map) x = df.index y = df[lines].values c = df[heat].values b = df[backtest].values if backtest is not None else None # assert enough data assert len(y.shape) > 1 and len( c.shape) > 1, "lines and heat need to be 2 dimensions!" # make sure we have one more line as heats if c.shape[1] == y.shape[1] + 1: lower = np.full((c.shape[0], 1), y.min() * (1 - upper_lower_missing_scale)) upper = np.full((c.shape[0], 1), y.max() * (1 + upper_lower_missing_scale)) y = np.hstack([lower, y, upper]) # check for matching columns assert y.shape[1] - 1 == c.shape[ 1], f'unexpeced shapes: {y.shape[1] - 1} != {c.shape[1]}' _, ax = plt.subplots(figsize=figsize) if ax is None else (None, ax) ax.plot(x, y, color='k', alpha=0.0) for ci in range(c.shape[1]): for xi in range(len(x)): ax.fill_between(x[xi - 1:xi + 1], y[xi - 1:xi + 1, ci], y[xi - 1:xi + 1, ci + 1], facecolors=color_function(c[xi - 1:xi + 1, ci])) if ci > 0: # todo annotate all first last and only convert date if it is actually a date ax.annotate(f'{y[-1, ci]:.2f}', xy=(mdates.date2num(x[-1]), y[-1, ci]), xytext=(4, -4), textcoords='offset pixels') # reset limits ax.autoscale(tight=True) if reset_y_lim: ax.set_ylim(bottom=y[:, 1].min(), top=y[:, -1].max()) # backtest if backtest: ax.plot(x, b) return ax
def ppk_plot(data: (List[int], List[float], pd.Series, np.array), upper_control_limit: (int, float), lower_control_limit: (int, float), threshold_percent: float = 0.001, ax: Axis = None): """ Shows the statistical distribution of the data along with CPK and limits. :param data: a list, pandas.Series, or numpy.array representing the data set :param upper_control_limit: an integer or float which represents the upper control limit, commonly called the UCL :param lower_control_limit: an integer or float which represents the upper control limit, commonly called the UCL :param threshold_percent: the threshold at which % of units above/below the number will display on the plot :param ax: an instance of matplotlig.axis.Axis :return: None """ data = coerce(data) mean = data.mean() std = data.std() if ax is None: fig, ax = plt.subplots() ax.hist(data, density=True, label='data', alpha=0.3) x = np.linspace(mean - 4 * std, mean + 4 * std, 100) pdf = stats.norm.pdf(x, mean, std) ax.plot(x, pdf, label='normal fit', alpha=0.7) bottom, top = ax.get_ylim() ax.axvline(mean, linestyle='--') ax.text(mean, top * 1.01, s='$\mu$', ha='center') ax.axvline(mean + std, alpha=0.6, linestyle='--') ax.text(mean + std, top * 1.01, s='$\sigma$', ha='center') ax.axvline(mean - std, alpha=0.6, linestyle='--') ax.text(mean - std, top * 1.01, s='$-\sigma$', ha='center') ax.axvline(mean + 2 * std, alpha=0.4, linestyle='--') ax.text(mean + 2 * std, top * 1.01, s='$2\sigma$', ha='center') ax.axvline(mean - 2 * std, alpha=0.4, linestyle='--') ax.text(mean - 2 * std, top * 1.01, s='-$2\sigma$', ha='center') ax.axvline(mean + 3 * std, alpha=0.2, linestyle='--') ax.text(mean + 3 * std, top * 1.01, s='$3\sigma$', ha='center') ax.axvline(mean - 3 * std, alpha=0.2, linestyle='--') ax.text(mean - 3 * std, top * 1.01, s='-$3\sigma$', ha='center') ax.fill_between(x, pdf, where=x < lower_control_limit, facecolor='red', alpha=0.5) ax.fill_between(x, pdf, where=x > upper_control_limit, facecolor='red', alpha=0.5) lower_percent = 100.0 * stats.norm.cdf(lower_control_limit, mean, std) lower_percent_text = f'{lower_percent:.02f}% < LCL' if lower_percent > threshold_percent else None higher_percent = 100.0 - 100.0 * stats.norm.cdf(upper_control_limit, mean, std) higher_percent_text = f'{higher_percent:.02f}% > UCL' if higher_percent > threshold_percent else None left, right = ax.get_xlim() bottom, top = ax.get_ylim() cpk = calc_ppk(data, upper_control_limit=upper_control_limit, lower_control_limit=lower_control_limit) lower_sigma_level = (mean - lower_control_limit) / std if lower_sigma_level < 6.0: ax.axvline(lower_control_limit, color='red', alpha=0.25, label='limits') ax.text(lower_control_limit, top * 0.95, s=f'$-{lower_sigma_level:.01f}\sigma$', ha='center') else: ax.text(left, top * 0.95, s=f'limit > $-6\sigma$', ha='left') upper_sigma_level = (upper_control_limit - mean) / std if upper_sigma_level < 6.0: ax.axvline(upper_control_limit, color='red', alpha=0.25) ax.text(upper_control_limit, top * 0.95, s=f'${upper_sigma_level:.01f}\sigma$', ha='center') else: ax.text(right, top * 0.95, s=f'limit > $6\sigma$', ha='right') strings = [f'Ppk = {cpk:.02f}'] strings.append(f'$\mu = {mean:.3g}$') strings.append(f'$\sigma = {std:.3g}$') if lower_percent_text: strings.append(lower_percent_text) if higher_percent_text: strings.append(higher_percent_text) props = dict(boxstyle='round', facecolor='white', alpha=0.75, edgecolor='grey') ax.text(right - (right - left) * 0.05, 0.85 * top, '\n'.join(strings), bbox=props, ha='right', va='top') ax.legend(loc='lower right')