From 6fd5c886da277b572f470bd80e7755421ff16209 Mon Sep 17 00:00:00 2001 From: Fontrodona Nicolas <nicolas.fontrodona@ens-lyon.fr> Date: Mon, 15 Mar 2021 18:17:05 +0100 Subject: [PATCH] src/gc_content/stat_annot.py: major pep8 modifications --- src/gc_content/stat_annot.py | 220 ++++++++++++++++++----------------- 1 file changed, 114 insertions(+), 106 deletions(-) diff --git a/src/gc_content/stat_annot.py b/src/gc_content/stat_annot.py index 6223c48..4c45b2d 100644 --- a/src/gc_content/stat_annot.py +++ b/src/gc_content/stat_annot.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# -*- coding;: utf-8 -*- +# -*- coding: UTF-8 -*- """ Description: @@ -9,6 +9,7 @@ Description: github.com/webermarcolivier/statannot/blob/master/statannot/statannot.py. """ + import matplotlib.pyplot as plt from matplotlib import lines import matplotlib.transforms as mtransforms @@ -19,196 +20,203 @@ from seaborn.utils import remove_na def add_stat_annotation(ax, data=None, x=None, y=None, hue=None, order=None, - hue_order=None, boxPairList=None, loc='inside', - useFixedOffset=False, lineYOffsetToBoxAxesCoord=None, - lineYOffsetAxesCoord=None, lineHeightAxesCoord=0.02, - textYOffsetPoints=1, color='0.2', linewidth=1.5, - fontsize='medium', verbose=1): + hue_order=None, box_pair_list=None, loc='inside', + use_fixed_offset=False, + liney_offset2box_axes_coord=None, + liney_offset_axes_coord=None, + line_height_axes_coord=0.02, + text_y_offset_points=1, color='0.2', linewidth=1.5, + fontsize='medium', verbose=1, min_pval = 1e-16): """ User should use the same argument for the data, x, y, hue, order, \ hue_order as the seaborn boxplot function. - boxPairList can be of either form: + box_pair_list can be of either form: boxplot: [(cat1, cat2, pval), (cat3, cat4, pval)] """ - def find_x_position_box(boxPlotter, boxName): + def find_x_position_box(box_plotter, box_name): """ - boxName can be either a name "cat" or a tuple ("cat", "hue") + box_name can be either a name "cat" or a tuple ("cat", "hue") """ - if boxPlotter.plot_hues is None: - cat = boxName - hueOffset = 0 + if box_plotter.plot_hues is None: + cat = box_name + hue_offset = 0 else: - cat = boxName[0] - hue = boxName[1] - hueOffset = boxPlotter.hue_offsets[ - boxPlotter.hue_names.index(hue)] + cat = box_name[0] + chue = box_name[1] + hue_offset = box_plotter.hue_offsets[ + box_plotter.hue_names.index(chue)] - groupPos = boxPlotter.group_names.index(cat) - boxPos = groupPos + hueOffset - return boxPos + group_pos = box_plotter.group_names.index(cat) + box_pos = group_pos + hue_offset + return box_pos - def get_box_data(boxPlotter, boxName): + def get_box_data(cbox_plotter, box_name): """ - boxName can be either a name "cat" or a tuple ("cat", "hue") + box_name can be either a name "cat" or a tuple ("cat", "hue") Here we really have to duplicate seaborn code, because there is not direct access to the box_data in the BoxPlotter class. """ - cat = boxName - if boxPlotter.plot_hues is None: + cat = box_name + if cbox_plotter.plot_hues is None: box_max_l = [] - for cat in boxPlotter.group_names: - i = boxPlotter.group_names.index(cat) - group_data = boxPlotter.plot_data[i] + for cat in cbox_plotter.group_names: + i = cbox_plotter.group_names.index(cat) + group_data = cbox_plotter.plot_data[i] box_data = remove_na(group_data) - box_max_l.append(np.max(box_data)) + box_max_l.append(np.mean(box_data)) return max(box_max_l) else: - i = boxPlotter.group_names.index(cat[0]) - group_data = boxPlotter.plot_data[i] + i = cbox_plotter.group_names.index(cat[0]) + group_data = cbox_plotter.plot_data[i] box_data = [] - for hue in boxPlotter.hue_names: - hue_mask = boxPlotter.plot_hues[i] == hue + for chue in cbox_plotter.hue_names: + hue_mask = cbox_plotter.plot_hues[i] == chue box_data.append(np.max(remove_na(group_data[hue_mask]))) return max(box_data) fig = plt.gcf() - validList = ['inside', 'outside'] - if loc not in validList: + valid_list = ['inside', 'outside'] + if loc not in valid_list: raise ValueError(f"loc value should be one of the following: " - f"{', '.join(validList)}.") + f"{', '.join(valid_list)}.") # Create the same BoxPlotter object as seaborn's boxplot - boxPlotter = sns.categorical._BoxPlotter(x, y, hue, data, order, hue_order, - orient=None, width=.8, color=None, - palette=None, saturation=.75, - dodge=True, fliersize=5, - linewidth=None) + box_plotter = sns.categorical._BoxPlotter(x, y, hue, data, order, + hue_order, orient=None, width=.8, + color=None, palette=None, + saturation=.75, dodge=True, + fliersize=5, linewidth=None) ylim = ax.get_ylim() - yRange = ylim[1] - ylim[0] + y_range = ylim[1] - ylim[0] - if lineYOffsetAxesCoord is None: + if liney_offset_axes_coord is None: if loc == 'inside': - lineYOffsetAxesCoord = 0.005 - if lineYOffsetToBoxAxesCoord is None: - lineYOffsetToBoxAxesCoord = 0.1 + liney_offset_axes_coord = 0.005 + if liney_offset2box_axes_coord is None: + liney_offset2box_axes_coord = 0.1 elif loc == 'outside': - lineYOffsetAxesCoord = 0.03 - lineYOffsetToBoxAxesCoord = lineYOffsetAxesCoord + liney_offset_axes_coord = 0.03 + liney_offset2box_axes_coord = liney_offset_axes_coord else: if loc == 'inside': - if lineYOffsetToBoxAxesCoord is None: - lineYOffsetToBoxAxesCoord = 0.06 + if liney_offset2box_axes_coord is None: + liney_offset2box_axes_coord = 0.06 elif loc == 'outside': - lineYOffsetToBoxAxesCoord = lineYOffsetAxesCoord - yOffset = lineYOffsetAxesCoord * yRange - yOffsetToBox = lineYOffsetToBoxAxesCoord * yRange + liney_offset2box_axes_coord = liney_offset_axes_coord + y_offset = liney_offset_axes_coord * y_range + y_offset_to_box = liney_offset2box_axes_coord * y_range - yStack = [] - annList = [] - for box1, box2, pval in boxPairList: + y_stack = [] + ann_list = [] + max_val = [] + for box1, box2, pval in box_pair_list: - groupNames = boxPlotter.group_names + group_names = box_plotter.group_names cat1 = box1 cat2 = box2 if isinstance(cat1, tuple): - hue_names = boxPlotter.hue_names - valid = cat1[0] in groupNames and cat2[0] in groupNames and \ - cat1[1] in hue_names and cat2[1] in hue_names + hue_names = box_plotter.hue_names + valid = cat1[0] in group_names and cat2[0] in group_names and \ + cat1[1] in hue_names and cat2[1] in hue_names else: - valid = cat1 in groupNames and cat2 in groupNames + valid = cat1 in group_names and cat2 in group_names if valid: # Get position of boxes - x1 = find_x_position_box(boxPlotter, box1) - x2 = find_x_position_box(boxPlotter, box2) - box_data1 = get_box_data(boxPlotter, box1) - box_data2 = get_box_data(boxPlotter, box2) + x1 = find_x_position_box(box_plotter, box1) + x2 = find_x_position_box(box_plotter, box2) + box_data1 = get_box_data(box_plotter, box1) + box_data2 = get_box_data(box_plotter, box2) ymax1 = box_data1 ymax2 = box_data2 if pval > 1e-16: text = "p = {:.2e}".format(pval) else: - text = "p < 1e-16" + text = f"p < {min_pval:.0e}" if loc == 'inside': - yRef = max(ymax1, ymax2) + y_ref = max(ymax1, ymax2) else: - yRef = ylim[1] - if len(yStack) > 0 and isinstance(box1, str): - yRef2 = max(yRef, max(yStack)) + y_ref = ylim[1] + + if len(y_stack) > 0: + y_ref2 = max(y_ref, max(y_stack)) else: - yRef2 = yRef + y_ref2 = y_ref - if len(yStack) == 0 or not isinstance(box1, str): - y = yRef2 + yOffsetToBox + if len(y_stack) == 0: + y = y_ref2 + y_offset_to_box else: - y = yRef2 + yOffset - h = lineHeightAxesCoord * yRange - lineX, lineY = [x1, x1, x2, x2], [y, y + h, y + h, y] + y = y_ref2 + y_offset + h = line_height_axes_coord * y_range + line_x, line_y = [x1, x1, x2, x2], [y, y + h, y + h, y] if loc == 'inside': - ax.plot(lineX, lineY, lw=linewidth, c=color) + ax.plot(line_x, line_y, lw=linewidth, c=color) elif loc == 'outside': - line = lines.Line2D(lineX, lineY, lw=linewidth, c=color, + line = lines.Line2D(line_x, line_y, lw=linewidth, c=color, transform=ax.transData) line.set_clip_on(False) ax.add_line(line) if text is not None: ann = ax.annotate(text, xy=(np.mean([x1, x2]), y + h), - xytext=(0, textYOffsetPoints), + xytext=(0, text_y_offset_points), textcoords='offset points', xycoords='data', ha='center', va='bottom', - fontsize=fontsize, - clip_on=False, annotation_clip=False) - annList.append(ann) + fontsize=fontsize, clip_on=False, + annotation_clip=False) + ann_list.append(ann) - new_max_ylim = 1.1 * (y + h) + new_max_ylim = 1.1*(y + h) if new_max_ylim > ylim[1]: - ax.set_ylim((ylim[0], 1.1 * (y + h))) + ax.set_ylim((ylim[0], 1.1*(y + h))) if text is not None: plt.draw() - yTopAnnot = None - gotMatplotlibError = False - if not useFixedOffset: + y_top_annot = None + got_matplotlib_error = False + if not use_fixed_offset: try: bbox = ann.get_window_extent() bbox_data = bbox.transformed(ax.transData.inverted()) - yTopAnnot = bbox_data.ymax + y_top_annot = bbox_data.ymax except RuntimeError: - gotMatplotlibError = True + got_matplotlib_error = True - if useFixedOffset or gotMatplotlibError: + if use_fixed_offset or got_matplotlib_error: if verbose >= 1: print("Warning: cannot get the text bounding box. " "Falling back to a fixed y offset. Layout may " "be not optimal.") - # We will apply a fixed offset in points, based on the font size of the annotation. - fontsizePoints = FontProperties( - size='medium').get_size_in_points() - offsetTrans = mtransforms.offset_copy(ax.transData, - fig=fig, - x=0, - y=1.0 * fontsizePoints + textYOffsetPoints, - units='points') - yTopDisplay = offsetTrans.transform((0, y + h)) - yTopAnnot = ax.transData.inverted().transform(yTopDisplay)[ - 1] + # We will apply a fixed offset in points, + # based on the font size of the annotation. + fontsize_points = FontProperties(size='medium' + ).get_size_in_points() + offset_trans = mtransforms.offset_copy( + ax.transData, fig=fig, x=0, + y=1.0 * fontsize_points + text_y_offset_points, + units='points') + y_top_display = offset_trans.transform((0, y + h)) + y_top_annot = ax.transData.inverted().\ + transform(y_top_display)[1] else: - yTopAnnot = y + h + y_top_annot = y + h - yStack.append(yTopAnnot) + y_stack.append(y_top_annot) + max_val.append(max(y_stack)) + if hue is not None and len(y_stack) == len(box_plotter.hue_names): + y_stack = [] else: - raise ValueError("boxPairList contains an unvalid box pair.") + raise ValueError("box_pair_list contains an unvalid box pair.") - yStackMax = max(yStack) + y_stack_max = max(max_val + y_stack) if loc == 'inside': - if ylim[1] < 1.03 * yStackMax: - ax.set_ylim((ylim[0], 1.03 * yStackMax)) + if ylim[1] < 1.03 * y_stack_max: + ax.set_ylim((ylim[0], 1.03 * y_stack_max)) elif loc == 'outside': ax.set_ylim((ylim[0], ylim[1])) - return ax + return ax \ No newline at end of file -- GitLab