def gbu_report(scores_dev, output_filename, titles, figsize=(8, 6)): colors = plt.cm.tab20.colors # Plotting pdf = PdfPages(output_filename) # Figure for eval plot fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111) for d_scores, title, color in zip(scores_dev, titles, colors): # Load the score files and fill in the angle associated to each camera impostors_dev, genuines_dev = get_split_dataframe(d_scores) # loading the dask dataframes i_dev = impostors_dev["score"].compute().to_numpy() g_dev = genuines_dev["score"].compute().to_numpy() fmr, fnmr = bob.measure.roc(i_dev, g_dev, n_points=40) # in % fnmr = 1 - fnmr fnmr *= 100 fmr *= 100 # plot.plot(roc_curve) plt.semilogx(fmr, fnmr, marker="o", label=title) pass pass # Plot finalization # plt.title(f"FMR vs FNMR . at Dev. FMR@{fmr_threshold}") ax.set_xlabel("FMR%", fontsize=18) ax.set_ylabel("1 - FNMR%", fontsize=18) x_ticks = [0.0001, 0.01, 1, 100] ax.set_xticks(x_ticks) ax.set_xticklabels([f"{x}%" for x in x_ticks], fontsize=14) y_ticks = [0, 20, 40, 60, 80, 100] ax.set_yticks(y_ticks) ax.set_yticklabels([f"{x}%" for x in y_ticks], fontsize=14) plt.legend() plt.grid() pdf.savefig(fig) pdf.close()
def fit(self, input_score_file_name): """ Fit the calibrator Parameters ---------- input_score_file_name: str Reference score file used to fit the calibrator (E.g `scores-dev.csv`). """ def impostor_threshold(impostor_scores): """ score > Q3 + 1.5*IQR """ q1 = np.quantile(impostor_scores, 0.25) q3 = np.quantile(impostor_scores, 0.75) if self.score_selection_method == "q3-outlier": outlier_zone = q3 + self.quantile_factor * (q3 - q1) return impostor_scores[(impostor_scores > q3) & (impostor_scores <= outlier_zone)] elif self.score_selection_method == "q1-median": median = np.median(impostor_scores) return impostor_scores[(impostor_scores > q1) & (impostor_scores <= median)] elif self.score_selection_method == "median-q3": median = np.median(impostor_scores) return impostor_scores[(impostor_scores < q3) & (impostor_scores >= median)] else: return impostor_scores def genuine_threshold(genuine_scores): """ score <= Q3 - 1.5*IQR """ q1 = np.quantile(genuine_scores, 0.25) q3 = np.quantile(genuine_scores, 0.75) if self.score_selection_method == "q3-outlier": outlier_zone = q1 - self.quantile_factor * (q3 - q1) return genuine_scores[(genuine_scores < q1) & (genuine_scores >= outlier_zone)] elif self.score_selection_method == "q1-median": median = np.median(genuine_scores) return genuine_scores[(genuine_scores < q3) & (genuine_scores <= median)] elif self.score_selection_method == "median-q3": median = np.median(genuine_scores) return genuine_scores[(genuine_scores > q1) & (genuine_scores <= median)] else: return genuine_scores impostors, genuines = get_split_dataframe(input_score_file_name) self._categorical_fitters = [] def get_sigmoid_score(X, regressor): return expit(X * regressor.coef_ + regressor.intercept_).ravel() for value in self.field_values: # Filtering genunines and impostors per group impostors_per_group = ( impostors[(impostors[f"probe_{self.field_name}"] == value) & (impostors[f"bio_ref_{self.field_name}"] == value)] ["score"].compute().to_numpy()) genuines_per_group = ( genuines[(genuines[f"probe_{self.field_name}"] == value )]["score"].compute().to_numpy()) impostors_per_group = impostor_threshold(impostors_per_group) genuines_per_group = genuine_threshold(genuines_per_group) # Training the regressor y = np.hstack(( np.zeros(len(impostors_per_group)), np.ones(len(genuines_per_group)), )) X = np.expand_dims(np.hstack( (impostors_per_group, genuines_per_group)), axis=1) fitter = self.fit_estimator().fit(X, y) self._categorical_fitters.append(fitter) return self
def arface_report( scores_dev, scores_eval, output_filename, titles, fmr_threshold=1e-3, figsize=(16, 8), y_abs_max=32, # Max absolute value for y axis colors=plt.cm.tab10.colors, ): occlusion_illumination = [ "illumination", "occlusion", "both", ] occlusions = [ "scarf", "sunglasses", ] # style_iterator = _get_colors_markers() # colors = plt.cm.tab20.colors eval_fmr_fnmr_occlusion_illumination = dict() eval_fmr_fnmr_occlusion = dict() for ( d_scores, e_scores, title, ) in zip(scores_dev, scores_eval, titles): eval_fmr_fnmr_occlusion_illumination[title] = [] eval_fmr_fnmr_occlusion[title] = [] # Load the score files and fill in the angle associated to each camera impostors_dev, genuines_dev = get_split_dataframe(d_scores) impostors_eval, genuines_eval = get_split_dataframe(e_scores) # loading the dask dataframes impostors_dev = impostors_dev.compute() genuines_dev = genuines_dev.compute() impostors_eval = impostors_eval.compute() genuines_eval = genuines_eval.compute() # Computing the threshold combining all distances threshold = bob.measure.far_threshold( impostors_dev["score"].to_numpy(), genuines_dev["score"].to_numpy(), fmr_threshold, ) def compute_fmr_fnmr(impostor_scores, genuine_scores): eval_fmr, eval_fnmr = bob.measure.farfrr( impostor_scores["score"].to_numpy(), genuine_scores["score"].to_numpy(), threshold, ) return eval_fmr, eval_fnmr # EVALUATING OCCLUSION AND ILLUMINATION # All illumination i_eval = impostors_eval.loc[ (impostors_eval.probe_illumination != "front") & (impostors_eval.probe_occlusion == "none")] g_eval = genuines_eval.loc[ (genuines_eval.probe_illumination != "front") & (genuines_eval.probe_occlusion == "none")] eval_fmr_fnmr_occlusion_illumination[title].append( compute_fmr_fnmr(i_eval, g_eval)) # All occlusions i_eval = impostors_eval.loc[ (impostors_eval.probe_occlusion != "none") & (impostors_eval.probe_illumination == "front")] g_eval = genuines_eval.loc[ (genuines_eval.probe_occlusion != "none") & (genuines_eval.probe_illumination == "front")] eval_fmr_fnmr_occlusion_illumination[title].append( compute_fmr_fnmr(i_eval, g_eval)) # BOTH overall_fmr_fnmr = compute_fmr_fnmr(impostors_eval, genuines_eval) eval_fmr_fnmr_occlusion_illumination[title].append(overall_fmr_fnmr) # EVALUATING DIFFERENT TYPES OF OCCLUSION for occlusion in occlusions: i_eval = impostors_eval.loc[impostors_eval.probe_occlusion == occlusion] g_eval = genuines_eval.loc[genuines_eval.probe_occlusion == occlusion] eval_fmr_fnmr_occlusion[title].append( compute_fmr_fnmr(i_eval, g_eval)) pass # Plotting # # EFFECT OF OCCLUSION TYPES # Figure for eval plot fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111) width = 0.8 / len(titles) X = np.arange(len(occlusions)) for i, (title, color) in enumerate(zip(titles, colors)): fmrs = [-1 * fmr * 100 for fmr, _ in eval_fmr_fnmr_occlusion[title]] fnmrs = [fnmr * 100 for _, fnmr in eval_fmr_fnmr_occlusion[title]] x_axis = X + (i + 1) * width - width / 2 ax.bar( x_axis, fmrs, width, label=title, color=color, alpha=1, hatch="\\", ) ax.bar( x_axis, fnmrs, width, color=color, alpha=0.5, hatch="/", ) # Writting the texts on top of the bar plots for i, fnmr, fmr in zip(x_axis, fnmrs, fmrs): plt.text(i - width / 2, fnmr + 0.5, str(round(fnmr, 1)), fontsize=15) plt.text(i - width / 2, fmr - 2.3, str(round(abs(fmr), 1)), fontsize=15) # Plot finalization plt.title(f"FMR vs FNMR. at Dev. FMR@{fmr_threshold*100}%", fontsize=16) ax.set_xlabel("Occlusion types", fontsize=14) ax.set_ylabel("FMR(%) vs FNMR(%) ", fontsize=18) ax.set_xticks(X + 0.5) ax.set_xticklabels(occlusions, fontsize=14) yticks = np.array([ -y_abs_max / 4, 0, y_abs_max / 4, y_abs_max / 2, y_abs_max, ]) ax.set_yticks(yticks) ax.set_yticklabels([abs(int(y)) for y in yticks], fontsize=16) plt.axhline(0, linestyle="-", color="k") plt.ylim([-y_abs_max / 4, y_abs_max + 1]) plt.legend() plt.grid() plt.savefig(fig) # EFFECT OF ILLUMINATION AND OCCLUSION # Figure for eval plot fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111) X = np.arange(len(occlusion_illumination)) for i, (title, color) in enumerate(zip(titles, colors)): fmrs = [ -1 * fmr * 100 for fmr, _ in eval_fmr_fnmr_occlusion_illumination[title] ] fnmrs = [ fnmr * 100 for _, fnmr in eval_fmr_fnmr_occlusion_illumination[title] ] x_axis = X + (i + 1) * width - width / 2 ax.bar( x_axis, fmrs, width, label=title, color=color, alpha=1, hatch="\\", ) ax.bar( x_axis, fnmrs, width, color=color, alpha=0.5, hatch="/", ) # Writting the texts on top of the bar plots for i, fnmr, fmr in zip(x_axis, fnmrs, fmrs): plt.text( i - width / 2, fnmr + 0.4, str(int(fnmr)) if fnmr == 0 else str(round(fnmr, 1)), fontsize=15, ) plt.text( i - width / 2, fmr - 0.9, str(int(fmr)), fontsize=15, ) plt.title(f"FMR vs FNMR. at Dev. FMR@{fmr_threshold*100}%", fontsize=16) ax.set_xlabel("Occlusion and illumination", fontsize=14) ax.set_ylabel("FMR(%) vs FNMR(%) ", fontsize=18) ax.set_xticks(X + 0.5) ax.set_xticklabels(occlusion_illumination, fontsize=14) yticks = np.array([-y_abs_max / 4, 0, y_abs_max / 4, y_abs_max / 2]) ax.set_yticks(yticks) ax.set_yticklabels([abs(int(y)) for y in yticks], fontsize=16) plt.axhline(0, linestyle="-", color="k") plt.ylim([-y_abs_max / 4, y_abs_max / 2]) plt.legend() plt.grid() plt.savefig(fig)
def multipie_expression_report( scores_dev, scores_eval, output_filename, titles, fmr_threshold=1e-3, figsize=(16, 8), y_abs_max=60, # Max absolute value for y axis colors=plt.cm.tab10.colors, ): # "all", expressions = [ "neutral", "smile", "surprise", "squint", "disgust", "scream", ] # style_iterator = _get_colors_markers() # colors = plt.cm.tab20.colors eval_fmr_fnmr_expressions = dict() for ( d_scores, e_scores, title, ) in zip(scores_dev, scores_eval, titles): eval_fmr_fnmr_expressions[title] = [] # Load the score files and fill in the angle associated to each camera impostors_dev, genuines_dev = get_split_dataframe(d_scores) impostors_eval, genuines_eval = get_split_dataframe(e_scores) # loading the dask dataframes impostors_dev = impostors_dev.compute() genuines_dev = genuines_dev.compute() impostors_eval = impostors_eval.compute() genuines_eval = genuines_eval.compute() # Computing the threshold combining all distances threshold = bob.measure.far_threshold( impostors_dev["score"].to_numpy(), genuines_dev["score"].to_numpy(), fmr_threshold, ) def compute_fmr_fnmr(impostor_scores, genuine_scores): eval_fmr, eval_fnmr = bob.measure.farfrr( impostor_scores["score"].to_numpy(), genuine_scores["score"].to_numpy(), threshold, ) return eval_fmr, eval_fnmr # All expressions # i_eval = impostors_eval # g_eval = genuines_eval # eval_fmr_fnmr_expressions[title].append(compute_fmr_fnmr(i_eval, g_eval)) # EVALUATING DIFFERENT TYPES OF EXPRESSION for expression in expressions: i_eval = impostors_eval.loc[ impostors_eval.probe_expression == expression ] g_eval = genuines_eval.loc[ genuines_eval.probe_expression == expression ] eval_fmr_fnmr_expressions[title].append( compute_fmr_fnmr(i_eval, g_eval) ) pass # Plotting pdf = PdfPages(output_filename) # Figure for eval plot fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111) width = 0.8 / len(titles) X = np.arange(len(expressions)) for i, (title, color) in enumerate(zip(titles, colors)): fmrs = [-1 * fmr * 100 for fmr, _ in eval_fmr_fnmr_expressions[title]] fnmrs = [fnmr * 100 for _, fnmr in eval_fmr_fnmr_expressions[title]] x_axis = X + (i + 1) * width - width / 2 ax.bar( x_axis, fmrs, width, label=title, color=color, alpha=1, hatch="\\", ) ax.bar( x_axis, fnmrs, width, color=color, alpha=0.5, hatch="/", ) # str(int(fnmr)) if fnmr == 0 else str(round(fnmr, 1)) # Writting the texts on top of the bar plots for i, fnmr, fmr in zip(x_axis, fnmrs, fmrs): plt.text( i - width / 2, fnmr + 0.8, str(int(fnmr)), fontsize=10, ) plt.text( i - width / 2, fmr - 2.8, str(int(abs(fmr))), fontsize=10, ) # Plot finalization plt.title(f"FMR vs FNMR. at Dev. FMR@{fmr_threshold*100}%", fontsize=16) ax.set_xlabel("Expressions", fontsize=12) ax.set_ylabel("FMR(%) vs FNMR(%) ", fontsize=14) ax.set_xticks(X + 0.5) ax.set_xticklabels(expressions) yticks = np.array( [ -y_abs_max / 8, 0, y_abs_max / 4, y_abs_max / 2, y_abs_max, ] ) ax.set_yticks(yticks) ax.set_yticklabels([int(abs(y)) for y in yticks], fontsize=16) plt.axhline(0, linestyle="-", color="k") plt.ylim([-y_abs_max / 8, y_abs_max + 1]) plt.legend() plt.grid() pdf.savefig(fig) pdf.close()
def multipie_pose_report( scores_dev, scores_eval, output_filename, titles, figsize=(16, 8), fmr_threshold=1e-3, colors=plt.cm.tab10.colors, optimal_threshold=False, threshold_eval=False, ): """ Compute the multipie pose report, ploting the FNMR for each view point Parameters ---------- scores_dev: scores_eval: output_filename: titles: figsize=(16, 8): fmr_threshold: colors: Color palete optimal_threshold: bool If set it to `True`, it will compute one decision threshold for each subprotocol (for each pose). Default to false. threshold_eval: bool If set it to `True` it will compute the threshold using the evaluation set. Default obviouslly to `False`. """ cameras = [ "11_0", "12_0", "09_0", "08_0", "13_0", "14_0", "05_1", "05_0", "04_1", "19_0", "20_0", "01_0", "24_0", ] angles = np.linspace(-90, 90, len(cameras)) camera_to_angle = dict(zip(cameras, angles)) pdf = PdfPages(output_filename) # Figure for eval plot fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111) style_iterator = _get_colors_markers() for d_scores, e_scores, title, (linestyle, marker), color in zip( scores_dev, scores_eval, titles, style_iterator, colors ): # Load the score files and fill in the angle associated to each camera impostors_dev, genuines_dev = get_split_dataframe(d_scores) impostors_eval, genuines_eval = get_split_dataframe(e_scores) # loading the dask dataframes impostors_dev = impostors_dev.compute() genuines_dev = genuines_dev.compute() impostors_eval = impostors_eval.compute() genuines_eval = genuines_eval.compute() # Appending the angle impostors_dev["angle"] = impostors_dev["probe_camera"].map( camera_to_angle ) genuines_dev["angle"] = genuines_dev["probe_camera"].map( camera_to_angle ) impostors_eval["angle"] = impostors_eval["probe_camera"].map( camera_to_angle ) genuines_eval["angle"] = genuines_eval["probe_camera"].map( camera_to_angle ) angles = [] eval_fmr_fnmr = [] # Compute the min. HTER threshold on the Dev set threshold = None if not optimal_threshold: if threshold_eval: threshold = bob.measure.far_threshold( impostors_eval["score"].to_numpy(), genuines_eval["score"].to_numpy(), fmr_threshold, ) else: threshold = bob.measure.far_threshold( impostors_dev["score"].to_numpy(), genuines_dev["score"].to_numpy(), fmr_threshold, ) # Run the analysis per view angle for ((angle, i_dev), (_, g_dev), (_, i_eval), (_, g_eval),) in zip( impostors_dev.groupby("angle"), genuines_dev.groupby("angle"), impostors_eval.groupby("angle"), genuines_eval.groupby("angle"), ): if optimal_threshold: if threshold_eval: threshold = bob.measure.far_threshold( i_eval["score"].to_numpy(), g_eval["score"].to_numpy(), fmr_threshold, ) else: threshold = bob.measure.far_threshold( i_dev["score"].to_numpy(), g_dev["score"].to_numpy(), fmr_threshold, ) eval_fmr, eval_fnmr = bob.measure.farfrr( i_eval["score"].to_numpy(), g_eval["score"].to_numpy(), threshold, ) # eval_fnmr = (eval_fnmr + eval_fmr) / 2 angles.append(angle) eval_fmr_fnmr.append([eval_fmr, eval_fnmr]) # eval_hter = (1 / 2 * (eval_far + eval_frr)) * 100 # eval_hter = eval_frr * 100 # eval_hters.append(eval_hter) # Update plots fnrms = [fnmr * 100 for fmr, fnmr in eval_fmr_fnmr] # fmrs = [-fmr * 100 for fmr, fnmr in eval_fmr_fnmr] plt.plot( angles, fnrms, label=title, linestyle=linestyle, marker=marker, linewidth=2, color=color, ) # plt.plot( # angles, fnrms, label=title, linestyle=linestyle, marker=marker, linewidth=2, # ) if threshold_eval: plt.title(f"FNMR. at Eval. FMR@{fmr_threshold*100}%", fontsize=16) else: plt.title(f"FNMR. at Dev. FMR@{fmr_threshold*100}%", fontsize=16) plt.xlabel("Angle", fontsize=12) plt.ylabel("FNMR(%)", fontsize=14) plt.legend() plt.grid() # Plot finalization xticks = [-90, -75, -60, -45, -30, -15, 0, 15, 30, 45, 60, 75, 90] plt.xticks(xticks) ax.set_xticklabels(xticks, fontsize=10) yticks = np.array([0, 20, 40, 60, 80, 100]) ax.set_yticks(yticks) ax.set_yticklabels(yticks, fontsize=12) pdf.savefig(fig) pdf.close()
def scface_report( scores_dev, scores_eval, output_filename, titles, fmr_threshold=1e-3, figsize=(16, 8), colors=plt.cm.tab10.colors, ): distances = [ "close", "medium", "far", ] # "combined", eval_fmr_fnmr = dict() for ( d_scores, e_scores, title, ) in zip(scores_dev, scores_eval, titles): eval_fmr_fnmr[title] = [] # Load the score files and fill in the angle associated to each camera impostors_dev, genuines_dev = get_split_dataframe(d_scores) impostors_eval, genuines_eval = get_split_dataframe(e_scores) # loading the dask dataframes impostors_dev = impostors_dev.compute() genuines_dev = genuines_dev.compute() impostors_eval = impostors_eval.compute() genuines_eval = genuines_eval.compute() # Computing the threshold combining all distances # Computing the decision threshold i_dev = impostors_dev["score"].to_numpy() g_dev = genuines_dev["score"].to_numpy() threshold = bob.measure.far_threshold( i_dev, g_dev, far_value=fmr_threshold, ) def compute_fmr_fnmr(impostor_scores, genuine_scores): eval_fmr, eval_fnmr = bob.measure.farfrr( impostor_scores["score"].to_numpy(), genuine_scores["score"].to_numpy(), threshold, ) return eval_fmr, eval_fnmr for distance in distances: i_eval = impostors_eval.loc[impostors_eval.probe_distance == distance] g_eval = genuines_eval.loc[genuines_eval.probe_distance == distance] eval_fmr_fnmr[title].append(compute_fmr_fnmr(i_eval, g_eval)) # Combined # eval_fmr_fnmr[title].append(compute_fmr_fnmr(impostors_eval, genuines_eval)) pass # Plotting pdf = PdfPages(output_filename) # Figure for eval plot fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111) width = 0.8 / len(titles) X = np.arange(len(distances)) for i, (title, color) in enumerate(zip(titles, colors)): fmrs = [-1 * fmr * 100 for fmr, _ in eval_fmr_fnmr[title]] fnmrs = [fnmr * 100 for _, fnmr in eval_fmr_fnmr[title]] x_axis = X + (i + 1) * width - width / 2 ax.bar( x_axis, fmrs, width, label=title, color=color, alpha=0.75, hatch="\\", ) ax.bar(x_axis, fnmrs, width, color=color, alpha=0.5, hatch="/") # Writting the texts on top of the bar plots for i, fnmr, fmr in zip(x_axis, fnmrs, fmrs): plt.text(i - width / 2, fnmr + 1, str(round(fnmr, 1)), fontsize=10) plt.text( i - width / 2, fmr - 4.5, str(int(fmr)) if fmr <= 0.4 else str(round(abs(fmr), 1)), fontsize=10, ) ax.set_axisbelow(True) # Plot finalization plt.title( f"FMR vs FNMR. at Dev. FMR@{fmr_threshold*100}% under differente distances" ) # ax.set_xlabel("Distances", fontsize=14) ax.set_ylabel("FMR(%) vs FNMR(%) ", fontsize=14) plt.ylim([-25, 104]) ax.set_xticks(X + 0.5) ax.set_xticklabels(distances, fontsize=16) yticks = np.array([-20, 0, 20, 40, 60, 80, 100]) ax.set_yticks(yticks) ax.set_yticklabels(abs(yticks), fontsize=16) plt.axhline(0, linestyle="-", color="k") plt.legend() plt.grid() pdf.savefig(fig) pdf.close()
def mobio_report( scores_dev, scores_eval, output_filename, titles, fmr_threshold=1e-3, figsize=(16, 8), colors=plt.cm.tab10.colors, ): genders = [ "m", "f", ] gender_labels = ["male", "female"] # colors = plt.cm.tab20.colors eval_fmr_fnmr = dict() for ( d_scores, e_scores, title, ) in zip(scores_dev, scores_eval, titles): eval_fmr_fnmr[title] = [] # Load the score files and fill in the angle associated to each camera impostors_dev, genuines_dev = get_split_dataframe(d_scores) impostors_eval, genuines_eval = get_split_dataframe(e_scores) # loading the dask dataframes impostors_dev = impostors_dev.compute() genuines_dev = genuines_dev.compute() impostors_eval = impostors_eval.compute() genuines_eval = genuines_eval.compute() # Computing the threshold combining all distances # Getting only the close distance to compute the threshold threshold = bob.measure.far_threshold( impostors_dev["score"].to_numpy(), genuines_dev["score"].to_numpy(), far_value=fmr_threshold, ) def compute_fmr_fnmr(impostor_scores, genuine_scores): eval_fmr, eval_fnmr = bob.measure.farfrr( impostor_scores["score"].to_numpy(), genuine_scores["score"].to_numpy(), threshold, ) return eval_fmr, eval_fnmr # Combined eval_fmr_fnmr[title].append( compute_fmr_fnmr(impostors_eval, genuines_eval)) for gender in genders[1:]: i_eval = impostors_eval.loc[impostors_eval.probe_gender == gender] g_eval = genuines_eval.loc[genuines_eval.probe_gender == gender] eval_fmr_fnmr[title].append(compute_fmr_fnmr(i_eval, g_eval)) pass # Plotting pdf = PdfPages(output_filename) # Figure for eval plot fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111) width = 0.8 / len(titles) X = np.arange(len(genders)) for i, (title, color) in enumerate(zip(titles, colors)): fmrs = [-1 * fmr * 100 for fmr, _ in eval_fmr_fnmr[title]] fnmrs = [fnmr * 100 for _, fnmr in eval_fmr_fnmr[title]] x_axis = X + (i + 1) * width - width / 2 ax.bar( x_axis, fmrs, width, label=title, color=color, alpha=0.75, hatch="\\", ) ax.bar(x_axis, fnmrs, width, color=color, alpha=0.5, hatch="/") # Writting the texts on top of the bar plots for i, fnmr, fmr in zip(x_axis, fnmrs, fmrs): plt.text(i - width / 2, fnmr + 0.8, str(round(fnmr, 2)), fontsize=12) # print(i - width / 2) plt.text(i - width / 2, fmr - 2.2, str(round(abs(fmr), 2)), fontsize=12) ax.set_axisbelow(True) # Plot finalization plt.title(f"FMR vs FNMR . at Dev. FMR@{fmr_threshold*100}%") # ax.set_xlabel("Genders", fontsize=18) ax.set_ylabel("FMR(%) vs FNMR(%) ", fontsize=15) # plt.ylim([-5, 40]) ax.set_xticks(X + 0.5) ax.set_xticklabels(gender_labels, fontsize=14) yticks = np.array([-5, 0, 5, 15, 25, 35]) ax.set_yticks(yticks) ax.set_yticklabels(abs(yticks), fontsize=16) plt.axhline(0, linestyle="-", color="k") plt.legend() plt.grid() pdf.savefig(fig) pdf.close()