From 3a17b2c97409f897179cbd3c1e09e0b1ece66130 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Thu, 22 Aug 2024 14:31:41 -0400 Subject: [PATCH 1/4] Fix for GLM crashing Added try:except loops to surround the matrix algebra part of GLM analysis and provide error messages if this fails, most likely due to problematic structural design. --- src/MetaWinAnalysisFunctions.py | 389 ++++++++++++++++---------------- src/MetaWinConstants.py | 2 +- src/MetaWinLanguage.py | 6 + 3 files changed, 207 insertions(+), 190 deletions(-) diff --git a/src/MetaWinAnalysisFunctions.py b/src/MetaWinAnalysisFunctions.py index 1525a3b..ab77d64 100644 --- a/src/MetaWinAnalysisFunctions.py +++ b/src/MetaWinAnalysisFunctions.py @@ -1347,107 +1347,113 @@ def complex_meta_analysis(data, options, decimal_places: int = 4, alpha: float = if check_data_for_glm(output_blocks, n, tmp_x_data, [c.label for c in categorical_vars]): output_blocks.append([get_text("{} studies will be included in this analysis").format(n)]) - qm, qe, beta, sigma_b = calculate_glm(e_data, x_data, w_matrix) - pooled_var = pooled_var_glm(qe, n, w_matrix, x_data) - if options.random_effects: - output_blocks.append([get_text("Estimate of pooled variance") + ": " + - format(pooled_var, inline_float(decimal_places))]) - ws_data = numpy.reciprocal(v_data + pooled_var) - w_matrix = numpy.diag(ws_data) + try: qm, qe, beta, sigma_b = calculate_glm(e_data, x_data, w_matrix) - else: - ws_data = w_data - median_e = median_effect(e_data, ws_data) + pooled_var = pooled_var_glm(qe, n, w_matrix, x_data) + if options.random_effects: + output_blocks.append([get_text("Estimate of pooled variance") + ": " + + format(pooled_var, inline_float(decimal_places))]) + ws_data = numpy.reciprocal(v_data + pooled_var) + w_matrix = numpy.diag(ws_data) + qm, qe, beta, sigma_b = calculate_glm(e_data, x_data, w_matrix) + else: + ws_data = w_data + median_e = median_effect(e_data, ws_data) - qt = qm + qe - df = n-1 - dfm = numpy.shape(x_data)[1] - 1 - dfe = n - dfm - 1 - pqt = 1 - scipy.stats.chi2.cdf(qt, df=df) - pqe = 1 - scipy.stats.chi2.cdf(qe, df=dfe) - pqm = 1 - scipy.stats.chi2.cdf(qm, df=dfm) + qt = qm + qe + df = n-1 + dfm = numpy.shape(x_data)[1] - 1 + dfe = n - dfm - 1 + pqt = 1 - scipy.stats.chi2.cdf(qt, df=df) + pqe = 1 - scipy.stats.chi2.cdf(qe, df=dfe) + pqm = 1 - scipy.stats.chi2.cdf(qm, df=dfm) - if ((options.bootstrap_mean is not None) or (options.randomize_model is not None)) and (sender is not None): - if options.randomize_model is not None: - total_steps = options.randomize_model + if ((options.bootstrap_mean is not None) or (options.randomize_model is not None)) and (sender is not None): + if options.randomize_model is not None: + total_steps = options.randomize_model + else: + total_steps = 0 + if options.bootstrap_mean is not None: + total_steps += options.bootstrap_mean + progress_bar = MetaWinWidgets.progress_bar(sender, get_text("Resampling Progress"), + get_text("Conducting Bootstrap Analysis"), + total_steps) else: - total_steps = 0 - if options.bootstrap_mean is not None: - total_steps += options.bootstrap_mean - progress_bar = MetaWinWidgets.progress_bar(sender, get_text("Resampling Progress"), - get_text("Conducting Bootstrap Analysis"), - total_steps) - else: - progress_bar = None + progress_bar = None - # basic global calcs - mean_e, var_e, _, _, _, _ = mean_effect_var_and_q(e_data, ws_data) - mean_v = numpy.sum(v_data) / n - if norm_ci: - lower_ci, upper_ci = scipy.stats.norm.interval(confidence=1 - alpha, loc=mean_e, scale=math.sqrt(var_e)) - else: - lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=df, loc=mean_e, scale=math.sqrt(var_e)) - lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci = bootstrap_means(options.bootstrap_mean, boot_data, - mean_e, pooled_var, - options.random_effects, alpha, - progress_bar=progress_bar) - mean_data = mean_data_tuple(get_text("Global"), 0, n, mean_e, median_e, var_e, mean_v, lower_ci, upper_ci, - lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci) + # basic global calcs + mean_e, var_e, _, _, _, _ = mean_effect_var_and_q(e_data, ws_data) + mean_v = numpy.sum(v_data) / n + if norm_ci: + lower_ci, upper_ci = scipy.stats.norm.interval(confidence=1 - alpha, loc=mean_e, scale=math.sqrt(var_e)) + else: + lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=df, loc=mean_e, scale=math.sqrt(var_e)) + lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci = bootstrap_means(options.bootstrap_mean, boot_data, + mean_e, pooled_var, + options.random_effects, alpha, + progress_bar=progress_bar) + mean_data = mean_data_tuple(get_text("Global"), 0, n, mean_e, median_e, var_e, mean_v, lower_ci, upper_ci, + lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci) - i2, i2_lower, i2_upper = calc_i2(qt, n, alpha) - i2_data = [i2_values(get_text("Total"), i2, i2_lower, i2_upper)] + i2, i2_lower, i2_upper = calc_i2(qt, n, alpha) + i2_data = [i2_values(get_text("Total"), i2, i2_lower, i2_upper)] - # aic = calc_aic(qe, n, dfm + 1) + # aic = calc_aic(qe, n, dfm + 1) - global_values = simple_ma_values(mean_data, pooled_var, qt, n-1, pqt, i2) + global_values = simple_ma_values(mean_data, pooled_var, qt, n-1, pqt, i2) - # randomization test - if options.randomize_model: - if progress_bar is not None: - progress_bar.setLabelText(get_text("Conducting Randomization Analysis")) - nreps = options.randomize_model - # decimal places to use for randomization-based p-value - rand_p_dec = max(decimal_places, math.ceil(math.log10(nreps+1))) - cnt_q = 1 - rng = numpy.random.default_rng() - for rep in range(nreps): - rand_e_data = rng.permutation(e_data) - rand_qm, _, _, _ = calculate_glm(rand_e_data, x_data, w_matrix) - if rand_qm >= qm: - cnt_q += 1 + # randomization test + if options.randomize_model: if progress_bar is not None: - progress_bar.setValue(progress_bar.value() + 1) - p_random = cnt_q / (nreps + 1) - p_random_str = format(p_random, inline_float(rand_p_dec)) - else: - p_random_str = "" - - # output - output_blocks.append(["

{}

".format(get_text("Model Results"))]) - output_blocks.append(["

{}

".format(get_text("Predictors"))]) - - predictor_table_data = [] - for b in range(len(beta)): - se = math.sqrt(sigma_b[b, b]) - p = prob_z_score(beta[b]/se) - predictor_table_data.append(predictor_test_tuple("β{} ({})".format(b, predictor_labels[b]), beta[b], se, p)) - output_blocks.append(predictor_table(predictor_table_data, decimal_places)) - - global_het_data = heterogeneity_test_tuple(get_text("Total"), qt, df, pqt, "") - if has_model: - output_blocks.append(["

{}

".format(get_text("Heterogeneity"))]) - model_het = heterogeneity_test_tuple(get_text("Model"), qm, dfm, pqm, p_random_str) - error_het = heterogeneity_test_tuple(get_text("Error"), qe, dfe, pqe, "") - model_table = [model_het, error_het, global_het_data] - output_blocks.append(heterogeneity_table(model_table, decimal_places, total_line=True, - randomization=options.randomize_model)) + progress_bar.setLabelText(get_text("Conducting Randomization Analysis")) + nreps = options.randomize_model + # decimal places to use for randomization-based p-value + rand_p_dec = max(decimal_places, math.ceil(math.log10(nreps+1))) + cnt_q = 1 + rng = numpy.random.default_rng() + for rep in range(nreps): + rand_e_data = rng.permutation(e_data) + rand_qm, _, _, _ = calculate_glm(rand_e_data, x_data, w_matrix) + if rand_qm >= qm: + cnt_q += 1 + if progress_bar is not None: + progress_bar.setValue(progress_bar.value() + 1) + p_random = cnt_q / (nreps + 1) + p_random_str = format(p_random, inline_float(rand_p_dec)) + else: + p_random_str = "" + + # output + output_blocks.append(["

{}

".format(get_text("Model Results"))]) + output_blocks.append(["

{}

".format(get_text("Predictors"))]) + + predictor_table_data = [] + for b in range(len(beta)): + se = math.sqrt(sigma_b[b, b]) + p = prob_z_score(beta[b]/se) + predictor_table_data.append(predictor_test_tuple("β{} ({})".format(b, predictor_labels[b]), beta[b], se, p)) + output_blocks.append(predictor_table(predictor_table_data, decimal_places)) + + global_het_data = heterogeneity_test_tuple(get_text("Total"), qt, df, pqt, "") + if has_model: + output_blocks.append(["

{}

".format(get_text("Heterogeneity"))]) + model_het = heterogeneity_test_tuple(get_text("Model"), qm, dfm, pqm, p_random_str) + error_het = heterogeneity_test_tuple(get_text("Error"), qe, dfe, pqe, "") + model_table = [model_het, error_het, global_het_data] + output_blocks.append(heterogeneity_table(model_table, decimal_places, total_line=True, + randomization=options.randomize_model)) - output_blocks.append(["

{}

".format(get_text("Global Results"))]) + output_blocks.append(["

{}

".format(get_text("Global Results"))]) - new_cites = create_global_output(output_blocks, effect_sizes.label, mean_data, global_het_data, pooled_var, - i2_data, options.bootstrap_mean, decimal_places, alpha, - options.log_transformed) - citations.extend(new_cites) + new_cites = create_global_output(output_blocks, effect_sizes.label, mean_data, global_het_data, pooled_var, + i2_data, options.bootstrap_mean, decimal_places, alpha, + options.log_transformed) + citations.extend(new_cites) + except numpy.linalg.LinAlgError as error_msg: + if str(error_msg) == "Singular matrix": + output_blocks.append([get_text("Analysis Error Encountered"), get_text("AE-singular")]) + else: + output_blocks.append([get_text("Analysis Error Encountered"), get_text("AE-unknown")]) return output_blocks, reg_ma_values(global_values, model_het, error_het, predictor_table_data), citations @@ -2122,113 +2128,118 @@ def phylogenetic_meta_analysis(data, options, tree, decimal_places: int = 4, alp # global_values = None citations = [] if check_data_for_glm(output_blocks, n, tmp_x_data, [c.label for c in categorical_vars]): - output_blocks.append([get_text("{} studies will be included in this analysis").format(n)]) - qm, qe, beta, sigma_b = calculate_glm(e_data, x_data, w_matrix) - pooled_var = pooled_var_glm(qe, n, w_matrix, x_data) - if options.random_effects: - output_blocks.append([get_text("Estimate of pooled variance") + ": " + - format(pooled_var, inline_float(decimal_places))]) - vs_data = v_data + pooled_var - # ws_data = numpy.reciprocal(v_data + pooled_var) - v_matrix = numpy.diag(vs_data) # convert w to n x n matrix with w on the diagonal - # add phylogenetic covariances to v_matrix - for i in range(n): - for j in range(i): - v_matrix[i, j] = p_matrix[i, j] * math.sqrt(vs_data[i]) * math.sqrt(vs_data[j]) - v_matrix[j, i] = v_matrix[i, j] - w_matrix = numpy.linalg.inv(v_matrix) - + try: + output_blocks.append([get_text("{} studies will be included in this analysis").format(n)]) qm, qe, beta, sigma_b = calculate_glm(e_data, x_data, w_matrix) - # else: - # ws_data = w_data - # median_e = median_effect(e_data, ws_data) - - qt = qm + qe - df = n-1 - dfm = numpy.shape(x_data)[1] - 1 - dfe = n - dfm - 1 - pqt = 1 - scipy.stats.chi2.cdf(qt, df=df) - pqe = 1 - scipy.stats.chi2.cdf(qe, df=dfe) - pqm = 1 - scipy.stats.chi2.cdf(qm, df=dfm) - - # aic = calc_aic(qe, n, dfm + 2) - # print("AIC:", round(aic, 4)) - - - """ - everything to here has been cross-checked fairly well - """ - - - # basic global calcs - """ need to pull the values from already calculated model """ - - # mean_e, var_e, _, _, _, _ = mean_effect_var_and_q(e_data, ws_data) - # mean_v = numpy.sum(v_data) / n - # lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=df, loc=mean_e, scale=math.sqrt(var_e)) - # lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci = bootstrap_means(options.bootstrap_mean, boot_data, - # mean_e, pooled_var, - # options.random_effects, alpha) - # mean_data = mean_data_tuple("Global", 0, n, mean_e, var_e, mean_v, lower_ci, upper_ci, lower_bs_ci, - # upper_bs_ci, lower_bias_ci, upper_bias_ci) + pooled_var = pooled_var_glm(qe, n, w_matrix, x_data) + if options.random_effects: + output_blocks.append([get_text("Estimate of pooled variance") + ": " + + format(pooled_var, inline_float(decimal_places))]) + vs_data = v_data + pooled_var + # ws_data = numpy.reciprocal(v_data + pooled_var) + v_matrix = numpy.diag(vs_data) # convert w to n x n matrix with w on the diagonal + # add phylogenetic covariances to v_matrix + for i in range(n): + for j in range(i): + v_matrix[i, j] = p_matrix[i, j] * math.sqrt(vs_data[i]) * math.sqrt(vs_data[j]) + v_matrix[j, i] = v_matrix[i, j] + w_matrix = numpy.linalg.inv(v_matrix) + + qm, qe, beta, sigma_b = calculate_glm(e_data, x_data, w_matrix) + # else: + # ws_data = w_data + # median_e = median_effect(e_data, ws_data) + + qt = qm + qe + df = n-1 + dfm = numpy.shape(x_data)[1] - 1 + dfe = n - dfm - 1 + pqt = 1 - scipy.stats.chi2.cdf(qt, df=df) + pqe = 1 - scipy.stats.chi2.cdf(qe, df=dfe) + pqm = 1 - scipy.stats.chi2.cdf(qm, df=dfm) - # i2, i2_lower, i2_upper = calc_i2(qt, n, alpha) - # i2_data = [i2_values(get_text("Total"), i2, i2_lower, i2_upper)] + # aic = calc_aic(qe, n, dfm + 2) + # print("AIC:", round(aic, 4)) - # randomization test - p_random_str = "" # temp - # if options.randomize_model and has_model: - # nreps = options.randomize_model - # # decimal places to use for randomization-based p-value - # rand_p_dec = max(decimal_places, math.ceil(math.log10(nreps+1))) - # cnt_q = 1 - # rng = numpy.random.default_rng() - # for rep in range(nreps): - # rand_x_data = rng.permutation(x_data) - # rand_qm, _, _, _ = calculate_glm(e_data, rand_x_data, w_matrix) - # if rand_qm >= qm: - # cnt_q += 1 - # p_random = cnt_q / (nreps + 1) - # p_random_str = format(p_random, inline_float(rand_p_dec)) - # else: - # p_random_str = "" - - # output - output_blocks.append(["

{}

".format(get_text("Model Results"))]) - output_blocks.append(["

{}

".format(get_text("Predictors"))]) - predictor_table_data = [] - for b in range(len(beta)): - se = math.sqrt(sigma_b[b, b]) - p = prob_z_score(beta[b]/se) - predictor_table_data.append(predictor_test_tuple("β{} ({})".format(b, predictor_labels[b]), beta[b], se, p)) - output_blocks.append(predictor_table(predictor_table_data, decimal_places)) + """ + everything to here has been cross-checked fairly well + """ - global_het_data = heterogeneity_test_tuple(get_text("Total"), qt, df, pqt, "") - if has_model: - output_blocks.append(["

{}

".format(get_text("Heterogeneity"))]) - model_het = heterogeneity_test_tuple(get_text("Model"), qm, dfm, pqm, p_random_str) - error_het = heterogeneity_test_tuple(get_text("Error"), qe, dfe, pqe, "") - model_table = [model_het, error_het, global_het_data] - output_blocks.append(heterogeneity_table(model_table, decimal_places, total_line=True, - randomization=options.randomize_model)) - else: + # basic global calcs """ need to pull the values from already calculated model """ - mean_e = beta[0] - var_e = sigma_b[0, 0] - mean_v = numpy.sum(v_data) / n - lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=df, loc=mean_e, scale=math.sqrt(var_e)) - mean_data = mean_data_tuple(get_text("Global"), 0, n, mean_e, None, var_e, mean_v, lower_ci, upper_ci, - 0, 0, 0, 0) - i2, i2_lower, i2_upper = calc_i2(qt, n, alpha) - i2_data = [i2_values(get_text("Total"), i2, i2_lower, i2_upper)] - - output_blocks.append(["

{}

".format(get_text("Global Results"))]) - new_cites = create_global_output(output_blocks, effect_sizes.label, mean_data, global_het_data, pooled_var, - i2_data, options.bootstrap_mean, decimal_places, alpha, - options.log_transformed, inc_median=False) - citations.extend(new_cites) + # mean_e, var_e, _, _, _, _ = mean_effect_var_and_q(e_data, ws_data) + # mean_v = numpy.sum(v_data) / n + # lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=df, loc=mean_e, scale=math.sqrt(var_e)) + # lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci = bootstrap_means(options.bootstrap_mean, boot_data, + # mean_e, pooled_var, + # options.random_effects, alpha) + # mean_data = mean_data_tuple("Global", 0, n, mean_e, var_e, mean_v, lower_ci, upper_ci, lower_bs_ci, + # upper_bs_ci, lower_bias_ci, upper_bias_ci) + + # i2, i2_lower, i2_upper = calc_i2(qt, n, alpha) + # i2_data = [i2_values(get_text("Total"), i2, i2_lower, i2_upper)] + + # randomization test + p_random_str = "" # temp + # if options.randomize_model and has_model: + # nreps = options.randomize_model + # # decimal places to use for randomization-based p-value + # rand_p_dec = max(decimal_places, math.ceil(math.log10(nreps+1))) + # cnt_q = 1 + # rng = numpy.random.default_rng() + # for rep in range(nreps): + # rand_x_data = rng.permutation(x_data) + # rand_qm, _, _, _ = calculate_glm(e_data, rand_x_data, w_matrix) + # if rand_qm >= qm: + # cnt_q += 1 + # p_random = cnt_q / (nreps + 1) + # p_random_str = format(p_random, inline_float(rand_p_dec)) + # else: + # p_random_str = "" + + # output + output_blocks.append(["

{}

".format(get_text("Model Results"))]) + output_blocks.append(["

{}

".format(get_text("Predictors"))]) + + predictor_table_data = [] + for b in range(len(beta)): + se = math.sqrt(sigma_b[b, b]) + p = prob_z_score(beta[b]/se) + predictor_table_data.append(predictor_test_tuple("β{} ({})".format(b, predictor_labels[b]), beta[b], se, p)) + output_blocks.append(predictor_table(predictor_table_data, decimal_places)) + + global_het_data = heterogeneity_test_tuple(get_text("Total"), qt, df, pqt, "") + if has_model: + output_blocks.append(["

{}

".format(get_text("Heterogeneity"))]) + model_het = heterogeneity_test_tuple(get_text("Model"), qm, dfm, pqm, p_random_str) + error_het = heterogeneity_test_tuple(get_text("Error"), qe, dfe, pqe, "") + model_table = [model_het, error_het, global_het_data] + output_blocks.append(heterogeneity_table(model_table, decimal_places, total_line=True, + randomization=options.randomize_model)) + else: + """ need to pull the values from already calculated model """ + mean_e = beta[0] + var_e = sigma_b[0, 0] + mean_v = numpy.sum(v_data) / n + lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=df, loc=mean_e, scale=math.sqrt(var_e)) + mean_data = mean_data_tuple(get_text("Global"), 0, n, mean_e, None, var_e, mean_v, lower_ci, upper_ci, + 0, 0, 0, 0) + + i2, i2_lower, i2_upper = calc_i2(qt, n, alpha) + i2_data = [i2_values(get_text("Total"), i2, i2_lower, i2_upper)] + + output_blocks.append(["

{}

".format(get_text("Global Results"))]) + new_cites = create_global_output(output_blocks, effect_sizes.label, mean_data, global_het_data, pooled_var, + i2_data, options.bootstrap_mean, decimal_places, alpha, + options.log_transformed, inc_median=False) + citations.extend(new_cites) + except numpy.linalg.LinAlgError as error_msg: + if str(error_msg) == "Singular matrix": + output_blocks.append([get_text("Analysis Error Encountered"), get_text("AE-singular")]) + else: + output_blocks.append([get_text("Analysis Error Encountered"), get_text("AE-unknown")]) return output_blocks, citations diff --git a/src/MetaWinConstants.py b/src/MetaWinConstants.py index 432acfc..e88ed3d 100644 --- a/src/MetaWinConstants.py +++ b/src/MetaWinConstants.py @@ -13,7 +13,7 @@ MAJOR_VERSION = 3 MINOR_VERSION = 0 -PATCH_VERSION = 15 +PATCH_VERSION = 16 # validity check when fetching value from data matrix VALUE_NUMBER = 0 diff --git a/src/MetaWinLanguage.py b/src/MetaWinLanguage.py index 2d0402b..360910b 100644 --- a/src/MetaWinLanguage.py +++ b/src/MetaWinLanguage.py @@ -14,6 +14,12 @@ "About MetaWin": "About MetaWin", "Additional Options": "Additional Options", "Analysis": "Analysis", + "Analysis Error Encountered": "Analysis Error Encountered", + "AE-singular": "The design matrix was singular and could not be inverted. This is most likely " + "caused by two or more categorical variables lacking independence, for example " + "if two data columns have identical values or perfectly reversed values.", + "AE-unknown": "An unexpected linear algebra error was encountered and the analysis could not " + "be completed. There is likely a problem with the data structure.", "Analysis Options": "Analysis Options", "available": "available", "Automatically check for udpates": "Automatically check for udpates", From 0698fe8a16d3f925b18cf28c6ad0a39b6b148907 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Thu, 22 Aug 2024 14:40:22 -0400 Subject: [PATCH 2/4] emphasized header of error output added a function to add html strong elements around text for output --- src/MetaWinAnalysisFunctions.py | 10 +++++----- src/MetaWinUtils.py | 4 ++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/MetaWinAnalysisFunctions.py b/src/MetaWinAnalysisFunctions.py index ab77d64..2b284bf 100644 --- a/src/MetaWinAnalysisFunctions.py +++ b/src/MetaWinAnalysisFunctions.py @@ -14,7 +14,7 @@ import MetaWinConstants from MetaWinConstants import mean_data_tuple from MetaWinUtils import create_output_table, inline_float, interval_to_str, get_citation, exponential_label, \ - prob_z_score + prob_z_score, strong_text import MetaWinCharts import MetaWinWidgets from MetaWinLanguage import get_text @@ -1451,9 +1451,9 @@ def complex_meta_analysis(data, options, decimal_places: int = 4, alpha: float = citations.extend(new_cites) except numpy.linalg.LinAlgError as error_msg: if str(error_msg) == "Singular matrix": - output_blocks.append([get_text("Analysis Error Encountered"), get_text("AE-singular")]) + output_blocks.append([strong_text(get_text("Analysis Error Encountered")), get_text("AE-singular")]) else: - output_blocks.append([get_text("Analysis Error Encountered"), get_text("AE-unknown")]) + output_blocks.append([strong_text(get_text("Analysis Error Encountered")), get_text("AE-unknown")]) return output_blocks, reg_ma_values(global_values, model_het, error_het, predictor_table_data), citations @@ -2237,9 +2237,9 @@ def phylogenetic_meta_analysis(data, options, tree, decimal_places: int = 4, alp citations.extend(new_cites) except numpy.linalg.LinAlgError as error_msg: if str(error_msg) == "Singular matrix": - output_blocks.append([get_text("Analysis Error Encountered"), get_text("AE-singular")]) + output_blocks.append([strong_text(get_text("Analysis Error Encountered")), get_text("AE-singular")]) else: - output_blocks.append([get_text("Analysis Error Encountered"), get_text("AE-unknown")]) + output_blocks.append([strong_text(get_text("Analysis Error Encountered")), get_text("AE-unknown")]) return output_blocks, citations diff --git a/src/MetaWinUtils.py b/src/MetaWinUtils.py index d06cfcd..0cdb19f 100644 --- a/src/MetaWinUtils.py +++ b/src/MetaWinUtils.py @@ -42,6 +42,10 @@ def interval_to_str(lower_value, upper_value, decimal_places: int = 4) -> str: format(upper_value, inline_float(decimal_places)) +def strong_text(text: str) -> str: + return "" + text + "" + + def strip_html(x: str) -> str: """ remove any stray html tags from within string From c7baf4d79a2b30a262779403e41ef9cd55eb89cb Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Thu, 22 Aug 2024 14:43:25 -0400 Subject: [PATCH 3/4] cleaned up some overly long lines --- src/MetaWinAnalysisFunctions.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/MetaWinAnalysisFunctions.py b/src/MetaWinAnalysisFunctions.py index 2b284bf..26ad8c3 100644 --- a/src/MetaWinAnalysisFunctions.py +++ b/src/MetaWinAnalysisFunctions.py @@ -650,7 +650,8 @@ def simple_meta_analysis(data, options, decimal_places: int = 4, alpha: float = forest_data = [mean_data] for i in range(n): # individual study data has to use normal dist - tmp_lower, tmp_upper = scipy.stats.norm.interval(confidence=1-alpha, loc=e_data[i], scale=math.sqrt(v_data[i])) + tmp_lower, tmp_upper = scipy.stats.norm.interval(confidence=1-alpha, loc=e_data[i], + scale=math.sqrt(v_data[i])) study_data = mean_data_tuple(study_names[i], plot_order, 0, e_data[i], None, 0, 0, tmp_lower, tmp_upper, None, None, None, None) forest_data.append(study_data) @@ -822,7 +823,8 @@ def grouped_meta_analysis(data, options, decimal_places: int = 4, alpha: float = if norm_ci: lower_ci, upper_ci = scipy.stats.norm.interval(confidence=1 - alpha, loc=mean_e, scale=math.sqrt(var_e)) else: - lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=n-1, loc=mean_e, scale=math.sqrt(var_e)) + lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=n-1, loc=mean_e, + scale=math.sqrt(var_e)) lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci = bootstrap_means(options.bootstrap_mean, boot_data, mean_e, pooled_var, options.random_effects, alpha, @@ -990,7 +992,8 @@ def cumulative_meta_analysis(data, options, decimal_places: int = 4, alpha: floa if norm_ci: lower_ci, upper_ci = scipy.stats.norm.interval(confidence=1-alpha, loc=mean_e, scale=math.sqrt(var_e)) else: - lower_ci, upper_ci = scipy.stats.t.interval(confidence=1-alpha, df=df, loc=mean_e, scale=math.sqrt(var_e)) + lower_ci, upper_ci = scipy.stats.t.interval(confidence=1-alpha, df=df, loc=mean_e, + scale=math.sqrt(var_e)) lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci = bootstrap_means(options.bootstrap_mean, tmp_boot, mean_e, pooled_var, options.random_effects, alpha, @@ -1117,7 +1120,8 @@ def regression_meta_analysis(data, options, decimal_places: int = 4, alpha: floa if norm_ci: lower_ci, upper_ci = scipy.stats.norm.interval(confidence=1 - alpha, loc=mean_e, scale=math.sqrt(var_e)) else: - lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=n-1, loc=mean_e, scale=math.sqrt(var_e)) + lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=n-1, loc=mean_e, + scale=math.sqrt(var_e)) lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci = bootstrap_means(options.bootstrap_mean, boot_data, mean_e, pooled_var, options.random_effects, alpha, @@ -1387,7 +1391,8 @@ def complex_meta_analysis(data, options, decimal_places: int = 4, alpha: float = if norm_ci: lower_ci, upper_ci = scipy.stats.norm.interval(confidence=1 - alpha, loc=mean_e, scale=math.sqrt(var_e)) else: - lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=df, loc=mean_e, scale=math.sqrt(var_e)) + lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=df, loc=mean_e, + scale=math.sqrt(var_e)) lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci = bootstrap_means(options.bootstrap_mean, boot_data, mean_e, pooled_var, options.random_effects, alpha, @@ -1431,7 +1436,8 @@ def complex_meta_analysis(data, options, decimal_places: int = 4, alpha: float = for b in range(len(beta)): se = math.sqrt(sigma_b[b, b]) p = prob_z_score(beta[b]/se) - predictor_table_data.append(predictor_test_tuple("β{} ({})".format(b, predictor_labels[b]), beta[b], se, p)) + predictor_table_data.append(predictor_test_tuple("β{} ({})".format(b, predictor_labels[b]), + beta[b], se, p)) output_blocks.append(predictor_table(predictor_table_data, decimal_places)) global_het_data = heterogeneity_test_tuple(get_text("Total"), qt, df, pqt, "") @@ -1699,7 +1705,8 @@ def nested_meta_analysis(data, options, decimal_places: int = 4, alpha: float = if norm_ci: lower_ci, upper_ci = scipy.stats.norm.interval(confidence=1 - alpha, loc=mean_e, scale=math.sqrt(var_e)) else: - lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=n-1, loc=mean_e, scale=math.sqrt(var_e)) + lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=n-1, loc=mean_e, + scale=math.sqrt(var_e)) lower_bs_ci, upper_bs_ci, lower_bias_ci, upper_bias_ci = bootstrap_means(options.bootstrap_mean, boot_data, mean_e, 0, False, alpha, progress_bar=progress_bar) @@ -2207,7 +2214,8 @@ def phylogenetic_meta_analysis(data, options, tree, decimal_places: int = 4, alp for b in range(len(beta)): se = math.sqrt(sigma_b[b, b]) p = prob_z_score(beta[b]/se) - predictor_table_data.append(predictor_test_tuple("β{} ({})".format(b, predictor_labels[b]), beta[b], se, p)) + predictor_table_data.append(predictor_test_tuple("β{} ({})".format(b, predictor_labels[b]), + beta[b], se, p)) output_blocks.append(predictor_table(predictor_table_data, decimal_places)) global_het_data = heterogeneity_test_tuple(get_text("Total"), qt, df, pqt, "") @@ -2223,7 +2231,8 @@ def phylogenetic_meta_analysis(data, options, tree, decimal_places: int = 4, alp mean_e = beta[0] var_e = sigma_b[0, 0] mean_v = numpy.sum(v_data) / n - lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=df, loc=mean_e, scale=math.sqrt(var_e)) + lower_ci, upper_ci = scipy.stats.t.interval(confidence=1 - alpha, df=df, loc=mean_e, + scale=math.sqrt(var_e)) mean_data = mean_data_tuple(get_text("Global"), 0, n, mean_e, None, var_e, mean_v, lower_ci, upper_ci, 0, 0, 0, 0) @@ -2231,8 +2240,8 @@ def phylogenetic_meta_analysis(data, options, tree, decimal_places: int = 4, alp i2_data = [i2_values(get_text("Total"), i2, i2_lower, i2_upper)] output_blocks.append(["

{}

".format(get_text("Global Results"))]) - new_cites = create_global_output(output_blocks, effect_sizes.label, mean_data, global_het_data, pooled_var, - i2_data, options.bootstrap_mean, decimal_places, alpha, + new_cites = create_global_output(output_blocks, effect_sizes.label, mean_data, global_het_data, + pooled_var, i2_data, options.bootstrap_mean, decimal_places, alpha, options.log_transformed, inc_median=False) citations.extend(new_cites) except numpy.linalg.LinAlgError as error_msg: From 97442acfed789c764154c722566e82054e28c08c Mon Sep 17 00:00:00 2001 From: msrosenberg Date: Fri, 25 Oct 2024 09:51:39 -0400 Subject: [PATCH 4/4] fix for blank cells when column filtering --- requirements.txt | 8 ++++---- src/MetaWinConstants.py | 2 +- src/MetaWinData.py | 3 +++ src/MetaWinFilter.py | 5 +++++ src/MetaWinLanguage.py | 1 + src/MetaWinMain.py | 12 ++++++++++-- 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 01d1af6..7790ce1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy~=1.26.1 -PyQt6~=6.6.1 -scipy~=1.11.3 -matplotlib~=3.8.0 \ No newline at end of file +numpy>=1.26.1 +PyQt6>=6.6.1 +scipy>=1.11.3 +matplotlib>=3.8.0 \ No newline at end of file diff --git a/src/MetaWinConstants.py b/src/MetaWinConstants.py index e88ed3d..6903f97 100644 --- a/src/MetaWinConstants.py +++ b/src/MetaWinConstants.py @@ -13,7 +13,7 @@ MAJOR_VERSION = 3 MINOR_VERSION = 0 -PATCH_VERSION = 16 +PATCH_VERSION = 17 # validity check when fetching value from data matrix VALUE_NUMBER = 0 diff --git a/src/MetaWinData.py b/src/MetaWinData.py index 74799b4..91921b5 100644 --- a/src/MetaWinData.py +++ b/src/MetaWinData.py @@ -6,6 +6,7 @@ from MetaWinUtils import format_number from MetaWinConstants import VALUE_STRING, VALUE_NUMBER +from MetaWinLanguage import get_text class MetaWinValue: @@ -76,6 +77,8 @@ def not_filtered(self) -> bool: d = self.data.value(self.position(), c) if d is not None: d = d.value + else: + d = f"[{get_text("blanks")}]" if str(d) in col.group_filter: return False return True diff --git a/src/MetaWinFilter.py b/src/MetaWinFilter.py index 4fe14ec..35126fe 100644 --- a/src/MetaWinFilter.py +++ b/src/MetaWinFilter.py @@ -25,11 +25,16 @@ def init_ui(self, data, column): group_layout = QVBoxLayout() group_data = [] c = data.col_number(column) + has_none = False for r in range(data.nrows()): g = data.value(r, c) if g is not None: group_data.append(str(g.value)) + else: + has_none = True self.group_names = sorted(set(group_data)) + if has_none: + self.group_names.append(f"[{get_text("blanks")}]") for group in self.group_names: new_check_box = QCheckBox(group) group_layout.addWidget(new_check_box) diff --git a/src/MetaWinLanguage.py b/src/MetaWinLanguage.py index 360910b..c994dbb 100644 --- a/src/MetaWinLanguage.py +++ b/src/MetaWinLanguage.py @@ -27,6 +27,7 @@ "Bar Color": "Bar Color", "Basic Meta-Analysis": "Basic Meta-Analysis", "Between": "Between", + "blanks": "blanks", "Bootstrap Mean Effect Size(s)": "Bootstrap Mean Effect Size(s)", "bootstrap_caption": " Confidence intervals from a boostrap ({:,} iterations) procedure, " "following {}, are indicated by {}; the bias-corrected bootstrap interval " diff --git a/src/MetaWinMain.py b/src/MetaWinMain.py index 499c48d..fc9c8e1 100644 --- a/src/MetaWinMain.py +++ b/src/MetaWinMain.py @@ -513,9 +513,17 @@ def refresh_data(self) -> None: else: new_item = QTableWidgetItem(dat.value) if not not_filtered: + # if dat is None: + # new_item.setBackground(QColor(self.filtered_row_color)) + # elif str(dat.value) in column.group_filter: + # new_item.setBackground(QColor(self.filtered_col_color)) + # else: + # new_item.setBackground(QColor(self.filtered_row_color)) if dat is None: - new_item.setBackground(QColor(self.filtered_row_color)) - elif str(dat.value) in column.group_filter: + dat_value = f"[{get_text("blanks")}]" + else: + dat_value = str(dat.value) + if dat_value in column.group_filter: new_item.setBackground(QColor(self.filtered_col_color)) else: new_item.setBackground(QColor(self.filtered_row_color))