diff --git a/Docs/ChangeLog.md b/Docs/ChangeLog.md index 3987e5d..405538b 100644 --- a/Docs/ChangeLog.md +++ b/Docs/ChangeLog.md @@ -1,3 +1,10 @@ +# Version: 1.117.1 +## Date: 2024-11-5 +### Changes: +- New: 1. Added an option to calculate the mean by the domainat value in eache group for plot_mean parameter in DEV Settings. 2. Added a statistics bar plot for basic plot part. 3. Added a attributes table of Taxa-Function result. +- Fix: Fixed the bug of calculating the Functional Redundancy in the T-Test and ANOVA part. + + # Version: 1.117.0 ## Date: 2024-10-31 ### Changes: diff --git a/metax/gui/main_gui.py b/metax/gui/main_gui.py index 75ff53e..bf0bdf5 100644 --- a/metax/gui/main_gui.py +++ b/metax/gui/main_gui.py @@ -67,7 +67,7 @@ from metax.taxafunc_ploter.volcano_plot_js import VolcanoPlotJS from metax.taxafunc_ploter.volcano_plot import VolcanoPlot from metax.taxafunc_ploter.tukey_plot import TukeyPlot - from metax.taxafunc_ploter.bar_plot_js import BarPlot_js + from metax.taxafunc_ploter.bar_plot_js import BarPlot from metax.taxafunc_ploter.sankey_plot import SankeyPlot from metax.taxafunc_ploter.network_plot import NetworkPlot from metax.taxafunc_ploter.trends_plot import TrendsPlot @@ -110,7 +110,7 @@ from ..taxafunc_ploter.volcano_plot_js import VolcanoPlotJS from ..taxafunc_ploter.volcano_plot import VolcanoPlot from ..taxafunc_ploter.tukey_plot import TukeyPlot - from ..taxafunc_ploter.bar_plot_js import BarPlot_js + from ..taxafunc_ploter.bar_plot_js import BarPlot from ..taxafunc_ploter.sankey_plot import SankeyPlot from ..taxafunc_ploter.network_plot import NetworkPlot from ..taxafunc_ploter.trends_plot import TrendsPlot @@ -733,6 +733,7 @@ def show_settings_window(self): settings_widget.heatmap_params_dict_changed.connect(self.on_heatmap_params_changed) settings_widget.tf_link_net_params_dict_changed.connect(self.on_tf_link_net_params_changed) settings_widget.html_theme_changed.connect(self.on_html_theme_changed) + settings_widget.stat_mean_by_zero_dominant_changed.connect(self.on_stat_mean_by_zero_dominant_changed) # Other settings settings_widget.protein_infer_method_changed.connect(self.on_protein_infer_method_changed) @@ -764,6 +765,15 @@ def on_tf_link_net_params_changed(self, params_dict): def on_html_theme_changed(self, theme): self.html_theme = theme print(f"HTML theme changed to: {theme}") + + def on_stat_mean_by_zero_dominant_changed(self, mode): + # chcek if self.tfa exists + if not hasattr(self, 'tfa'): + print("Please load the data first.") + return + + self.tfa.stat_mean_by_zero_dominant = mode + print(f"Stat mean by zero dominant changed to: {mode}") def on_protein_infer_method_changed(self, method): #save to settings @@ -3639,9 +3649,8 @@ def plot_basic_list(self, plot_type='heatmap'): plot_percent = self.checkBox_basic_bar_plot_percent.isChecked() sub_meta = self.comboBox_3dbar_sub_meta.currentText() use_3d_for_sub_meta = self.checkBox_basic_bar_3d_for_sub_meta.isChecked() + js_bar = self.checkBox_basic_bar_interactive_js.isChecked() - width = width*100 - height = height*100 df = df.loc[(df!=0).any(axis=1)] if len(df) > 100: reply = QMessageBox.question(self.MainWindow, 'Warning', @@ -3650,14 +3659,23 @@ def plot_basic_list(self, plot_type='heatmap'): if reply == QMessageBox.No: return None self.show_message(f'Plotting {plot_type}...') - pic = BarPlot_js(self.tfa, theme=self.html_theme).plot_intensity_bar(df = df, width=width, height=height, - title= '', rename_taxa=rename_taxa, - show_legend=show_legend, font_size=font_size, - rename_sample=rename_sample, plot_mean = plot_mean, - plot_percent = plot_percent, sub_meta = sub_meta, - show_all_labels = show_all_labels, use_3d = use_3d_for_sub_meta) - - self.save_and_show_js_plot(pic, title) + if js_bar: + width = width*100 + height = height*100 + pic = BarPlot(self.tfa, theme=self.html_theme).plot_intensity_bar_js(df = df, width=width, height=height, + title= '', rename_taxa=rename_taxa, + show_legend=show_legend, font_size=font_size, + rename_sample=rename_sample, plot_mean = plot_mean, + plot_percent = plot_percent, sub_meta = sub_meta, + show_all_labels = show_all_labels, use_3d = use_3d_for_sub_meta) + + self.save_and_show_js_plot(pic, title) + else: + ax = BarPlot(self.tfa, theme=self.html_theme).plot_intensity_bar_sns(df = df, width=width, height=height, + title= '', rename_taxa=rename_taxa, + show_legend=show_legend, font_size=font_size, + rename_sample=rename_sample, plot_mean = plot_mean, + plot_percent = plot_percent, sub_meta = sub_meta) elif plot_type == 'get_table': self.show_message('Getting table...') @@ -5413,7 +5431,7 @@ def plot_network(self): try: self.show_message('Plotting network...') list_only_no_link = self.checkBox_tf_link_net_plot_list_only_no_link.isChecked() - pic, network_df = NetworkPlot( + pic, network_df, attributes_df = NetworkPlot( self.tfa, show_labels=show_labels, rename_taxa=rename_taxa, @@ -5430,6 +5448,7 @@ def plot_network(self): ) self.save_and_show_js_plot(pic, 'taxa-func link Network') self.update_table_dict('taxa-func_network', network_df) + self.update_table_dict('taxa-func_network_attributes', attributes_df) except Exception as e: error_message = traceback.format_exc() @@ -5682,7 +5701,7 @@ def plot_tflink_bar(self): params['sub_meta'] = sub_meta self.show_message('Plotting bar plot, please wait...') - pic = BarPlot_js(self.tfa, theme=self.html_theme).plot_intensity_bar(**params) + pic = BarPlot(self.tfa, theme=self.html_theme).plot_intensity_bar_js(**params) self.save_and_show_js_plot(pic, 'Intensity Bar Plot') diff --git a/metax/gui/metax_gui/main_window.ui b/metax/gui/metax_gui/main_window.ui index 81c7523..c6e3adb 100644 --- a/metax/gui/metax_gui/main_window.ui +++ b/metax/gui/metax_gui/main_window.ui @@ -7,7 +7,7 @@ 0 0 1122 - 755 + 816 @@ -245,8 +245,8 @@ 0 0 - 528 - 534 + 391 + 80 @@ -1882,7 +1882,7 @@ Show Labels - true + false @@ -2789,8 +2789,8 @@ 0 0 - 621 - 150 + 1016 + 184 @@ -3004,22 +3004,6 @@ - - - - - 0 - 0 - - - - Show Legend - - - true - - - @@ -3280,6 +3264,36 @@ + + + + + + Interactive Bar + + + true + + + + + + + + 0 + 0 + + + + Show Legend + + + true + + + + + @@ -3792,7 +3806,7 @@ 0 0 - 1020 + 878 128 @@ -7467,8 +7481,8 @@ 0 0 - 1016 - 105 + 620 + 65 @@ -9288,8 +9302,8 @@ 0 0 - 1016 - 141 + 383 + 68 @@ -9785,8 +9799,8 @@ 0 0 - 1044 - 493 + 313 + 41 diff --git a/metax/gui/metax_gui/setting_window.ui b/metax/gui/metax_gui/setting_window.ui index 653cab3..3851027 100644 --- a/metax/gui/metax_gui/setting_window.ui +++ b/metax/gui/metax_gui/setting_window.ui @@ -78,14 +78,14 @@ Plotting - - + + - HTML Global + Taxa-Functions Link Network - + @@ -325,32 +325,7 @@ - - - - - - Theme - - - - - - - - white - - - - - dark - - - - - - - + @@ -516,10 +491,10 @@ - - + + - Taxa-Functions Link Network + HTML Global @@ -699,13 +674,45 @@ - + Network Global + + + + Plot Mean + + + + + + + + + Theme + + + + + + + + white + + + + + dark + + + + + + @@ -713,6 +720,17 @@ + + + + + + Calculate Non-Zero Mean (Return 0 if Zeros > 50%) for Each Group + + + + + diff --git a/metax/gui/metax_gui/settings_widget.py b/metax/gui/metax_gui/settings_widget.py index ba2b3cc..53ebfb7 100644 --- a/metax/gui/metax_gui/settings_widget.py +++ b/metax/gui/metax_gui/settings_widget.py @@ -9,6 +9,7 @@ class SettingsWidget(QWidget): tf_link_net_params_dict_changed = pyqtSignal(dict) html_theme_changed = pyqtSignal(str) protein_infer_method_changed = pyqtSignal(str) + stat_mean_by_zero_dominant_changed = pyqtSignal(bool) def __init__(self, parent=None, update_branch="main", auto_check_update=True, QSettings=None): super().__init__(parent) @@ -65,6 +66,9 @@ def __init__(self, parent=None, update_branch="main", auto_check_update=True, QS # Protein inference method self.ui.comboBox_protein_infer_greedy_mode.currentTextChanged.connect(self.handle_protein_infer_method_changed) + # stat_mean_by_zero_dominant + self.ui.checkBox_stat_mean_by_zero_dominant.stateChanged.connect(self.handle_stat_mean_by_zero_dominant_changed) + def init_ui(self, update_mode, auto_check_update, QSettings=None): if update_mode == "main": @@ -142,6 +146,10 @@ def handle_protein_infer_method_changed(self): 'fast': 'heap', } self.protein_infer_method_changed.emit(method[protein_infer_greedy_mode]) + + def handle_stat_mean_by_zero_dominant_changed(self): + checked = self.ui.checkBox_stat_mean_by_zero_dominant.isChecked() + self.stat_mean_by_zero_dominant_changed.emit(checked) if __name__ == "__main__": import sys diff --git a/metax/gui/metax_gui/ui_main_window.py b/metax/gui/metax_gui/ui_main_window.py index 64b4e2f..0e713a3 100644 --- a/metax/gui/metax_gui/ui_main_window.py +++ b/metax/gui/metax_gui/ui_main_window.py @@ -14,7 +14,7 @@ class Ui_metaX_main(object): def setupUi(self, metaX_main): metaX_main.setObjectName("metaX_main") - metaX_main.resize(1122, 755) + metaX_main.resize(1122, 816) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -147,7 +147,7 @@ def setupUi(self, metaX_main): self.toolBox_2.setMaximumSize(QtCore.QSize(1677, 16777215)) self.toolBox_2.setObjectName("toolBox_2") self.page_2 = QtWidgets.QWidget() - self.page_2.setGeometry(QtCore.QRect(0, 0, 528, 534)) + self.page_2.setGeometry(QtCore.QRect(0, 0, 391, 80)) self.page_2.setObjectName("page_2") self.gridLayout_27 = QtWidgets.QGridLayout(self.page_2) self.gridLayout_27.setObjectName("gridLayout_27") @@ -909,7 +909,7 @@ def setupUi(self, metaX_main): sizePolicy.setHeightForWidth(self.checkBox_pca_if_show_lable.sizePolicy().hasHeightForWidth()) self.checkBox_pca_if_show_lable.setSizePolicy(sizePolicy) self.checkBox_pca_if_show_lable.setLayoutDirection(QtCore.Qt.LeftToRight) - self.checkBox_pca_if_show_lable.setChecked(True) + self.checkBox_pca_if_show_lable.setChecked(False) self.checkBox_pca_if_show_lable.setObjectName("checkBox_pca_if_show_lable") self.horizontalLayout_34.addWidget(self.checkBox_pca_if_show_lable) self.gridLayout_34.addLayout(self.horizontalLayout_34, 1, 2, 1, 1) @@ -1411,7 +1411,7 @@ def setupUi(self, metaX_main): self.scrollArea_2.setWidgetResizable(True) self.scrollArea_2.setObjectName("scrollArea_2") self.scrollAreaWidgetContents_2 = QtWidgets.QWidget() - self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 621, 150)) + self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 1016, 184)) self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2") self.gridLayout_50 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents_2) self.gridLayout_50.setObjectName("gridLayout_50") @@ -1532,15 +1532,6 @@ def setupUi(self, metaX_main): self.label_183.setFont(font) self.label_183.setObjectName("label_183") self.gridLayout_70.addWidget(self.label_183, 0, 0, 1, 1) - self.checkBox_basic_bar_show_legend = QtWidgets.QCheckBox(self.scrollAreaWidgetContents_2) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.checkBox_basic_bar_show_legend.sizePolicy().hasHeightForWidth()) - self.checkBox_basic_bar_show_legend.setSizePolicy(sizePolicy) - self.checkBox_basic_bar_show_legend.setChecked(True) - self.checkBox_basic_bar_show_legend.setObjectName("checkBox_basic_bar_show_legend") - self.gridLayout_70.addWidget(self.checkBox_basic_bar_show_legend, 4, 1, 1, 1) self.checkBox_basic_heatmap_sankey_title = QtWidgets.QCheckBox(self.scrollAreaWidgetContents_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -1683,6 +1674,22 @@ def setupUi(self, metaX_main): self.checkBox_basic_bar_3d_for_sub_meta = QtWidgets.QCheckBox(self.scrollAreaWidgetContents_2) self.checkBox_basic_bar_3d_for_sub_meta.setObjectName("checkBox_basic_bar_3d_for_sub_meta") self.gridLayout_70.addWidget(self.checkBox_basic_bar_3d_for_sub_meta, 4, 3, 1, 1) + self.horizontalLayout_104 = QtWidgets.QHBoxLayout() + self.horizontalLayout_104.setObjectName("horizontalLayout_104") + self.checkBox_basic_bar_interactive_js = QtWidgets.QCheckBox(self.scrollAreaWidgetContents_2) + self.checkBox_basic_bar_interactive_js.setChecked(True) + self.checkBox_basic_bar_interactive_js.setObjectName("checkBox_basic_bar_interactive_js") + self.horizontalLayout_104.addWidget(self.checkBox_basic_bar_interactive_js) + self.checkBox_basic_bar_show_legend = QtWidgets.QCheckBox(self.scrollAreaWidgetContents_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.checkBox_basic_bar_show_legend.sizePolicy().hasHeightForWidth()) + self.checkBox_basic_bar_show_legend.setSizePolicy(sizePolicy) + self.checkBox_basic_bar_show_legend.setChecked(True) + self.checkBox_basic_bar_show_legend.setObjectName("checkBox_basic_bar_show_legend") + self.horizontalLayout_104.addWidget(self.checkBox_basic_bar_show_legend) + self.gridLayout_70.addLayout(self.horizontalLayout_104, 4, 1, 1, 1) self.gridLayout_50.addLayout(self.gridLayout_70, 0, 0, 1, 1) self.scrollArea_2.setWidget(self.scrollAreaWidgetContents_2) self.gridLayout_41.addWidget(self.scrollArea_2, 0, 0, 1, 1) @@ -1960,7 +1967,7 @@ def setupUi(self, metaX_main): self.scrollArea_cross_heatmap_settings.setWidgetResizable(True) self.scrollArea_cross_heatmap_settings.setObjectName("scrollArea_cross_heatmap_settings") self.scrollAreaWidgetContents_3 = QtWidgets.QWidget() - self.scrollAreaWidgetContents_3.setGeometry(QtCore.QRect(0, 0, 1020, 128)) + self.scrollAreaWidgetContents_3.setGeometry(QtCore.QRect(0, 0, 878, 128)) self.scrollAreaWidgetContents_3.setObjectName("scrollAreaWidgetContents_3") self.gridLayout_38 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents_3) self.gridLayout_38.setObjectName("gridLayout_38") @@ -3840,7 +3847,7 @@ def setupUi(self, metaX_main): self.scrollArea_5.setWidgetResizable(True) self.scrollArea_5.setObjectName("scrollArea_5") self.scrollAreaWidgetContents_6 = QtWidgets.QWidget() - self.scrollAreaWidgetContents_6.setGeometry(QtCore.QRect(0, 0, 1016, 105)) + self.scrollAreaWidgetContents_6.setGeometry(QtCore.QRect(0, 0, 620, 65)) self.scrollAreaWidgetContents_6.setObjectName("scrollAreaWidgetContents_6") self.gridLayout_57 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents_6) self.gridLayout_57.setObjectName("gridLayout_57") @@ -4849,7 +4856,7 @@ def setupUi(self, metaX_main): self.scrollArea_7.setWidgetResizable(True) self.scrollArea_7.setObjectName("scrollArea_7") self.scrollAreaWidgetContents_8 = QtWidgets.QWidget() - self.scrollAreaWidgetContents_8.setGeometry(QtCore.QRect(0, 0, 1016, 141)) + self.scrollAreaWidgetContents_8.setGeometry(QtCore.QRect(0, 0, 383, 68)) self.scrollAreaWidgetContents_8.setObjectName("scrollAreaWidgetContents_8") self.gridLayout_66 = QtWidgets.QGridLayout(self.scrollAreaWidgetContents_8) self.gridLayout_66.setObjectName("gridLayout_66") @@ -5096,7 +5103,7 @@ def setupUi(self, metaX_main): self.toolBox_metalab_res_anno = QtWidgets.QToolBox(self.tab_18) self.toolBox_metalab_res_anno.setObjectName("toolBox_metalab_res_anno") self.page_3 = QtWidgets.QWidget() - self.page_3.setGeometry(QtCore.QRect(0, 0, 1044, 493)) + self.page_3.setGeometry(QtCore.QRect(0, 0, 313, 41)) self.page_3.setObjectName("page_3") self.gridLayout_45 = QtWidgets.QGridLayout(self.page_3) self.gridLayout_45.setObjectName("gridLayout_45") @@ -5779,7 +5786,6 @@ def retranslateUi(self, metaX_main): self.label_13.setText(_translate("metaX_main", "Theme")) self.checkBox_basic_hetatmap_row_cluster.setText(_translate("metaX_main", "Row Cluster")) self.label_183.setText(_translate("metaX_main", "General")) - self.checkBox_basic_bar_show_legend.setText(_translate("metaX_main", "Show Legend")) self.checkBox_basic_heatmap_sankey_title.setText(_translate("metaX_main", "Show Title")) self.label_184.setText(_translate("metaX_main", "Heatmap")) self.checkBox_basic_hetatmap_col_cluster.setText(_translate("metaX_main", "Col Cluster")) @@ -5796,6 +5802,8 @@ def retranslateUi(self, metaX_main): self.checkBox_basic_hetatmap_rename_taxa.setText(_translate("metaX_main", "Taxa")) self.label_108.setText(_translate("metaX_main", "Label Font Size")) self.checkBox_basic_bar_3d_for_sub_meta.setText(_translate("metaX_main", "3D for Sub Meta")) + self.checkBox_basic_bar_interactive_js.setText(_translate("metaX_main", "Interactive Bar")) + self.checkBox_basic_bar_show_legend.setText(_translate("metaX_main", "Show Legend")) self.label_34.setText(_translate("metaX_main", "List for Plotting")) self.pushButton_basic_heatmap_add_top.setToolTip(_translate("metaX_main", "Add conditionally filtered items to the drawing box")) self.pushButton_basic_heatmap_add_top.setText(_translate("metaX_main", "Add Top to List")) diff --git a/metax/gui/metax_gui/ui_setting_window.py b/metax/gui/metax_gui/ui_setting_window.py index 245df5c..a9108fb 100644 --- a/metax/gui/metax_gui/ui_setting_window.py +++ b/metax/gui/metax_gui/ui_setting_window.py @@ -44,9 +44,9 @@ def setupUi(self, Settings): self.page_2.setObjectName("page_2") self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2) self.gridLayout_4.setObjectName("gridLayout_4") - self.label_21 = QtWidgets.QLabel(self.page_2) - self.label_21.setObjectName("label_21") - self.gridLayout_4.addWidget(self.label_21, 2, 0, 1, 1) + self.label_4 = QtWidgets.QLabel(self.page_2) + self.label_4.setObjectName("label_4") + self.gridLayout_4.addWidget(self.label_4, 5, 0, 1, 1) self.gridLayout_7 = QtWidgets.QGridLayout() self.gridLayout_7.setObjectName("gridLayout_7") self.label_11 = QtWidgets.QLabel(self.page_2) @@ -149,18 +149,7 @@ def setupUi(self, Settings): self.doubleSpinBox_tf_link_net_gravity.setProperty("value", 0.2) self.doubleSpinBox_tf_link_net_gravity.setObjectName("doubleSpinBox_tf_link_net_gravity") self.gridLayout_7.addWidget(self.doubleSpinBox_tf_link_net_gravity, 1, 3, 1, 1) - self.gridLayout_4.addLayout(self.gridLayout_7, 3, 3, 1, 1) - self.gridLayout_5 = QtWidgets.QGridLayout() - self.gridLayout_5.setObjectName("gridLayout_5") - self.label_22 = QtWidgets.QLabel(self.page_2) - self.label_22.setObjectName("label_22") - self.gridLayout_5.addWidget(self.label_22, 0, 0, 1, 1) - self.comboBox_html_theme = QtWidgets.QComboBox(self.page_2) - self.comboBox_html_theme.setObjectName("comboBox_html_theme") - self.comboBox_html_theme.addItem("") - self.comboBox_html_theme.addItem("") - self.gridLayout_5.addWidget(self.comboBox_html_theme, 0, 1, 1, 1) - self.gridLayout_4.addLayout(self.gridLayout_5, 2, 3, 1, 1) + self.gridLayout_4.addLayout(self.gridLayout_7, 4, 3, 1, 1) self.gridLayout_6 = QtWidgets.QGridLayout() self.gridLayout_6.setObjectName("gridLayout_6") self.label_5 = QtWidgets.QLabel(self.page_2) @@ -219,10 +208,10 @@ def setupUi(self, Settings): self.lineEdit_tf_link_net_line_color = QtWidgets.QLineEdit(self.page_2) self.lineEdit_tf_link_net_line_color.setObjectName("lineEdit_tf_link_net_line_color") self.gridLayout_6.addWidget(self.lineEdit_tf_link_net_line_color, 2, 1, 1, 1) - self.gridLayout_4.addLayout(self.gridLayout_6, 4, 3, 1, 1) - self.label_4 = QtWidgets.QLabel(self.page_2) - self.label_4.setObjectName("label_4") - self.gridLayout_4.addWidget(self.label_4, 4, 0, 1, 1) + self.gridLayout_4.addLayout(self.gridLayout_6, 5, 3, 1, 1) + self.label_21 = QtWidgets.QLabel(self.page_2) + self.label_21.setObjectName("label_21") + self.gridLayout_4.addWidget(self.label_21, 3, 0, 1, 1) self.gridLayout_8 = QtWidgets.QGridLayout() self.gridLayout_8.setObjectName("gridLayout_8") self.label_2 = QtWidgets.QLabel(self.page_2) @@ -288,10 +277,30 @@ def setupUi(self, Settings): self.gridLayout_4.addLayout(self.gridLayout_8, 1, 3, 1, 1) self.label_20 = QtWidgets.QLabel(self.page_2) self.label_20.setObjectName("label_20") - self.gridLayout_4.addWidget(self.label_20, 3, 0, 1, 1) + self.gridLayout_4.addWidget(self.label_20, 4, 0, 1, 1) + self.label_27 = QtWidgets.QLabel(self.page_2) + self.label_27.setObjectName("label_27") + self.gridLayout_4.addWidget(self.label_27, 2, 0, 1, 1) + self.gridLayout_5 = QtWidgets.QGridLayout() + self.gridLayout_5.setObjectName("gridLayout_5") + self.label_22 = QtWidgets.QLabel(self.page_2) + self.label_22.setObjectName("label_22") + self.gridLayout_5.addWidget(self.label_22, 0, 0, 1, 1) + self.comboBox_html_theme = QtWidgets.QComboBox(self.page_2) + self.comboBox_html_theme.setObjectName("comboBox_html_theme") + self.comboBox_html_theme.addItem("") + self.comboBox_html_theme.addItem("") + self.gridLayout_5.addWidget(self.comboBox_html_theme, 0, 1, 1, 1) + self.gridLayout_4.addLayout(self.gridLayout_5, 3, 3, 1, 1) self.label = QtWidgets.QLabel(self.page_2) self.label.setObjectName("label") self.gridLayout_4.addWidget(self.label, 1, 0, 1, 1) + self.gridLayout_12 = QtWidgets.QGridLayout() + self.gridLayout_12.setObjectName("gridLayout_12") + self.checkBox_stat_mean_by_zero_dominant = QtWidgets.QCheckBox(self.page_2) + self.checkBox_stat_mean_by_zero_dominant.setObjectName("checkBox_stat_mean_by_zero_dominant") + self.gridLayout_12.addWidget(self.checkBox_stat_mean_by_zero_dominant, 0, 0, 1, 1) + self.gridLayout_4.addLayout(self.gridLayout_12, 2, 3, 1, 1) self.toolBox.addItem(self.page_2, "") self.page_3 = QtWidgets.QWidget() self.page_3.setGeometry(QtCore.QRect(0, 0, 748, 340)) @@ -326,7 +335,7 @@ def retranslateUi(self, Settings): self.radioButton_update_stable.setText(_translate("Settings", "Stable")) self.radioButton_update_beta.setText(_translate("Settings", "Beta")) self.toolBox.setItemText(self.toolBox.indexOf(self.page), _translate("Settings", "General")) - self.label_21.setText(_translate("Settings", "HTML Global")) + self.label_4.setText(_translate("Settings", "Taxa-Functions Link Network")) self.label_11.setText(_translate("Settings", "Line Width")) self.label_15.setToolTip(_translate("Settings", "The larger the value the greater the repulsion")) self.label_15.setText(_translate("Settings", "Repulsion")) @@ -348,9 +357,6 @@ def retranslateUi(self, Settings): self.comboBox_tf_link_net_label_position.setItemText(6, _translate("Settings", "insideRight")) self.label_19.setToolTip(_translate("Settings", "The gravitational factor towards the centre to which the node is subjected. The larger the value the closer the node is to the centre.")) self.label_19.setText(_translate("Settings", "Gravity")) - self.label_22.setText(_translate("Settings", "Theme")) - self.comboBox_html_theme.setItemText(0, _translate("Settings", "white")) - self.comboBox_html_theme.setItemText(1, _translate("Settings", "dark")) self.label_5.setText(_translate("Settings", "Taxa Shape")) self.lineEdit_tf_link_net_func_focus_color.setText(_translate("Settings", "#B24745")) self.lineEdit_tf_link_net_taxa_focus_color.setText(_translate("Settings", "#6A6599")) @@ -377,7 +383,7 @@ def retranslateUi(self, Settings): self.comboBox_tf_link_net_taxa_sahpe.setItemText(6, _translate("Settings", "arrow")) self.label_13.setText(_translate("Settings", "Line Color")) self.lineEdit_tf_link_net_line_color.setText(_translate("Settings", "#9aa7b1")) - self.label_4.setText(_translate("Settings", "Taxa-Functions Link Network")) + self.label_21.setText(_translate("Settings", "HTML Global")) self.label_2.setText(_translate("Settings", "Linkage Method")) self.comboBox_heatmap_linkage_method.setCurrentText(_translate("Settings", "average")) self.comboBox_heatmap_linkage_method.setItemText(0, _translate("Settings", "average")) @@ -401,7 +407,12 @@ def retranslateUi(self, Settings): self.label_25.setText(_translate("Settings", "X Labels Rotation")) self.label_26.setText(_translate("Settings", "Y Labels Rotation")) self.label_20.setText(_translate("Settings", "Network Global")) + self.label_27.setText(_translate("Settings", "Plot Mean")) + self.label_22.setText(_translate("Settings", "Theme")) + self.comboBox_html_theme.setItemText(0, _translate("Settings", "white")) + self.comboBox_html_theme.setItemText(1, _translate("Settings", "dark")) self.label.setText(_translate("Settings", "HeatMap")) + self.checkBox_stat_mean_by_zero_dominant.setText(_translate("Settings", "Calculate Non-Zero Mean (Return 0 if Zeros > 50%) for Each Group")) self.toolBox.setItemText(self.toolBox.indexOf(self.page_2), _translate("Settings", "Plotting")) self.label_23.setText(_translate("Settings", "Protein Infer")) self.comboBox_protein_infer_greedy_mode.setItemText(0, _translate("Settings", "fast")) diff --git a/metax/taxafunc_analyzer/analyzer.py b/metax/taxafunc_analyzer/analyzer.py index 62536a4..10cbe13 100644 --- a/metax/taxafunc_analyzer/analyzer.py +++ b/metax/taxafunc_analyzer/analyzer.py @@ -83,6 +83,8 @@ def __init__( self.split_func_status:bool = False self.split_func_sep:str = '' + + self.stat_mean_by_zero_dominant:bool = False # only for BasicStats.get_stats_mean_df_by_group() # load function self.BasicStats = BasicStats(self) diff --git a/metax/taxafunc_analyzer/analyzer_utils/basic_stats.py b/metax/taxafunc_analyzer/analyzer_utils/basic_stats.py index d79f70b..5772024 100644 --- a/metax/taxafunc_analyzer/analyzer_utils/basic_stats.py +++ b/metax/taxafunc_analyzer/analyzer_utils/basic_stats.py @@ -7,7 +7,46 @@ def __init__(self, tfa): self.tfa = tfa # get a mean df by group - def get_stats_mean_df_by_group(self, df: pd.DataFrame = None, condition: list = None) -> pd.DataFrame: + def get_stats_mean_df_by_group(self, df: pd.DataFrame, condition: list|None = None, zero_dominant: bool|None = None) -> pd.DataFrame: + """ + Calculate the mean values of groups of samples in a DataFrame, optionally considering only non-zero values. + + Args: + df (pd.DataFrame): The input DataFrame containing the sample data. Defaults to None. + condition (list, optional): A list of conditions to filter the samples. Defaults to None.eg. ['V1', 'PBS'] + zero_domainant (bool, optional): If True, calculate the mean of non-zero values in each group if the number of zero values is less than half of the total number of values; otherwise, return 0. Defaults to False. + + Returns: + pd.DataFrame: A DataFrame containing the mean values of the groups. + """ + + def get_mean_by_zero_dominant(df: pd.DataFrame) -> pd.Series: + """ + Optimized function to calculate the mean of non-zero values in each row if the number of zero values + is less than half of the total values; otherwise, return 0. + + Args: + df (pd.DataFrame): Input DataFrame. + + Returns: + pd.Series: A Series with mean values based on the zero-dominant condition. + """ + # 计算每行的零值数量 + zero_counts = (df == 0).sum(axis=1) + # 判断每行零值是否超过一半,超过的行直接设为0 + mean_series = pd.Series(0, index=df.index) + non_zero_rows = zero_counts <= (df.shape[1] / 2) + # 对非零主导的行计算非零均值 + mean_series[non_zero_rows] = df[non_zero_rows].replace(0, pd.NA).mean(axis=1, skipna=True) + return mean_series + + if zero_dominant is None: + zero_dominant = self.tfa.stat_mean_by_zero_dominant + print(f"Caculating mean by zero_dominant: [{zero_dominant}]") + + mean_method = get_mean_by_zero_dominant if zero_dominant else lambda x: x.mean(axis=1) + + data = df.copy() # extract samples that are in the data only columns_list = data.columns.tolist() @@ -18,6 +57,7 @@ def get_stats_mean_df_by_group(self, df: pd.DataFrame = None, condition: list = group_order = list(OrderedDict.fromkeys(self.tfa.get_group_of_a_sample(sample) for sample in data.columns)) print("input group order:", group_order) + samples_used =[] group_means = pd.DataFrame() for group in group_order: @@ -30,11 +70,14 @@ def get_stats_mean_df_by_group(self, df: pd.DataFrame = None, condition: list = samples_used.extend(valid_samples) group_data = data[valid_samples] # calculate the mean of the samples in the group - group_mean = group_data.mean(axis=1) + group_mean = mean_method(group_data) + # add the group mean to the group_means dataframe group_means[group] = group_mean group_means = group_means[group_order] - print("samples used:", samples_used) + # convert to float + group_means = group_means.astype(float) + # print("samples used:", samples_used) return group_means def get_stats_peptide_num_in_taxa(self) -> pd.DataFrame: diff --git a/metax/taxafunc_analyzer/analyzer_utils/cross_test.py b/metax/taxafunc_analyzer/analyzer_utils/cross_test.py index 6ae5e51..ecc9b9b 100644 --- a/metax/taxafunc_analyzer/analyzer_utils/cross_test.py +++ b/metax/taxafunc_analyzer/analyzer_utils/cross_test.py @@ -631,18 +631,28 @@ def get_stats_diff_taxa_but_func(self, group_list: list|None = None, p_value: fl if p_value < 0 or p_value > 1: raise ValueError("p_value must be between 0 and 1") # 获取pvalue大于0.05的Taxon items - not_significant_taxa = df_taxa_test_res[df_taxa_test_res[p_col_name] >= p_value].index.get_level_values('Taxon').tolist() + not_significant_taxa_list = df_taxa_test_res[df_taxa_test_res[p_col_name] >= p_value].index.get_level_values('Taxon').tolist() + significant_taxa_list = df_taxa_test_res[df_taxa_test_res[p_col_name] < p_value].index.get_level_values('Taxon').tolist() print(f"Under {p_col_name} = {p_value}: \n \ - Significant Taxa: [{len(df_taxa_test_res) - len(not_significant_taxa)}], Not Significant Taxa: [{len(not_significant_taxa)}]") + Significant Taxa: [{len(significant_taxa_list)}], Not Significant Taxa: [{len(not_significant_taxa_list)}]") # 获取pvalue小于0.05的Function items - not_significant_func = df_func_test_res[df_func_test_res[p_col_name] >= p_value].index.get_level_values(self.tfa.func_name).tolist() + not_significant_func_list = df_func_test_res[df_func_test_res[p_col_name] >= p_value].index.get_level_values(self.tfa.func_name).tolist() + significant_func_list = df_func_test_res[df_func_test_res[p_col_name] < p_value].index.get_level_values(self.tfa.func_name).tolist() print(f"Under {p_col_name} = {p_value}: \n \ - Significant Function: [{len(df_func_test_res) - len(not_significant_func)}], Not Significant Function: [{len(not_significant_func)}]") + Significant Function: [{len(significant_func_list)}], Not Significant Function: [{len(not_significant_func_list)}]") # 选择这些Taxon在df_taxa_func_test_res中的行 and pvalue < 0.05 - df_filtered_taxa_not_significant = df_taxa_func_test_res.loc[df_taxa_func_test_res.index.get_level_values('Taxon').isin(not_significant_taxa) & (df_taxa_func_test_res[p_col_name] < p_value)] + df_filtered_taxa_not_significant = df_taxa_func_test_res.loc[ + df_taxa_func_test_res.index.get_level_values('Taxon').isin(not_significant_taxa_list) & + (df_taxa_func_test_res[p_col_name] < p_value) & + (df_taxa_func_test_res.index.get_level_values(self.tfa.func_name).isin(significant_func_list)) + ] print(f"Taxa not significant but related function significant with {p_col_name} < {p_value}: [{len(df_filtered_taxa_not_significant)}]") - df_filtered_func_not_significant = df_taxa_func_test_res.loc[df_taxa_func_test_res.index.get_level_values(self.tfa.func_name).isin(not_significant_func) & (df_taxa_func_test_res[p_col_name] < p_value)] + df_filtered_func_not_significant = df_taxa_func_test_res.loc[ + df_taxa_func_test_res.index.get_level_values(self.tfa.func_name).isin(not_significant_func_list) & + (df_taxa_func_test_res[p_col_name] < p_value) & + (df_taxa_func_test_res.index.get_level_values('Taxon').isin(significant_taxa_list)) + ] # reset_index for df_filtered_func_not_significant df_filtered_func_not_significant = df_filtered_func_not_significant.swaplevel(0, 1).sort_index() print(f"Function not significant but related taxa significant with {p_col_name} < {p_value}: [{len(df_filtered_func_not_significant)}]") diff --git a/metax/taxafunc_ploter/bar_plot_js.py b/metax/taxafunc_ploter/bar_plot_js.py index 5be6694..fddfafb 100644 --- a/metax/taxafunc_ploter/bar_plot_js.py +++ b/metax/taxafunc_ploter/bar_plot_js.py @@ -1,9 +1,10 @@ from pyecharts import options as opts from pyecharts.charts import Bar from .get_distinct_colors import GetDistinctColors +from matplotlib import pyplot as plt -class BarPlot_js: +class BarPlot: def __init__(self, tfobj, theme='white'): self.tfa = tfobj self.get_distinct_colors = GetDistinctColors().get_distinct_colors @@ -41,18 +42,20 @@ def _add_group_name_to_sample(self, df): df.columns = new_col_names return df - - def plot_intensity_bar(self, taxon_name:str|None=None, sample_list:list|None = None, - func_name:str|None =None, peptide_seq=None, - width:int=1200, height:int=800, df= None, - title:str|None =None, rename_taxa:bool=False, - show_legend:bool=True, font_size:int=10, - rename_sample:bool=True, plot_mean:bool=False, - plot_percent:bool=False, sub_meta:str="None", - show_all_labels:tuple = (False, False), use_3d:bool=False - ): + def _get_modfied_df(self, df= None, + taxon_name:str|None=None, sample_list:list|None = None, + func_name:str|None =None, peptide_seq=None, + rename_taxa:bool=False, rename_sample:bool=True, + plot_mean:bool=False, sub_meta:str="None", plot_percent:bool=False): + """ + Get modified df for plotting + Returns: + - df: pd.DataFrame + - rename_sample: bool + """ if df is None: - df = self.tfa.GetMatrix.get_intensity_matrix(taxon_name=taxon_name, func_name=func_name, peptide_seq=peptide_seq, sample_list= sample_list) + df = self.tfa.GetMatrix.get_intensity_matrix(taxon_name=taxon_name, func_name=func_name, + peptide_seq=peptide_seq, sample_list= sample_list) if df.empty: raise ValueError('No data to plot') @@ -63,25 +66,69 @@ def plot_intensity_bar(self, taxon_name:str|None=None, sample_list:list|None = N df = self.tfa.BasicStats.get_stats_mean_df_by_group(df) rename_sample = False - - if use_3d: - if sub_meta == "None": - use_3d = False - print('sub_meta must be provided for 3D bar plot, use 2D bar plot instead') - + # rename taxa if rename_taxa: df = self.rename_taxa(df) #rename columns (sample name) if rename_sample and sub_meta == "None": df = self._add_group_name_to_sample(df) - - col_num = len(df) - + if plot_percent: # transform to percentage of each column df = df.div(df.sum(axis=0), axis=1) * 100 + return df, rename_sample + + + def plot_intensity_bar_sns(self, taxon_name:str|None=None, sample_list:list|None = None, + func_name:str|None =None, peptide_seq=None, + width:int=12, height:int=8, df= None, + title:str|None =None, rename_taxa:bool=False, + show_legend:bool=True, font_size:int=10, + rename_sample:bool=True, plot_mean:bool=False, + plot_percent:bool=False, sub_meta:str="None", **kwargs): + + df, _ = self._get_modfied_df(df, taxon_name, sample_list, func_name, peptide_seq, + rename_taxa, rename_sample, plot_mean, sub_meta, plot_percent) + colormap = GetDistinctColors().get_distinct_colors(len(df.index)) + fig, ax = plt.subplots(figsize=(width, height)) + df.T.plot(kind='bar', stacked=True, ax=ax, color=colormap) + + ax.set_title(title, fontsize=font_size) + ax.set_xlabel('Group' if plot_mean else 'Sample', fontsize=font_size) + ax.set_ylabel('Percentage' if plot_percent else 'Intensity', fontsize=font_size) + ax.tick_params(axis='both', which='major', labelsize=font_size) + if show_legend: + ax.legend(title='Taxa' if rename_taxa else 'Sample', title_fontsize=font_size, fontsize=font_size, loc='center left', bbox_to_anchor=(1, 0.5)) + else: + ax.get_legend().remove() + + plt.tight_layout() + plt.show() + + return fig + + + + def plot_intensity_bar_js(self, taxon_name:str|None=None, sample_list:list|None = None, + func_name:str|None =None, peptide_seq=None, + width:int=1200, height:int=800, df= None, + title:str|None =None, rename_taxa:bool=False, + show_legend:bool=True, font_size:int=10, + rename_sample:bool=True, plot_mean:bool=False, + plot_percent:bool=False, sub_meta:str="None", + show_all_labels:tuple = (False, False), use_3d:bool=False + ): + df, rename_sample = self._get_modfied_df(df, taxon_name, sample_list, func_name, peptide_seq, + rename_taxa, rename_sample, plot_mean, sub_meta, plot_percent) + + if use_3d: + if sub_meta == "None": + use_3d = False + print('sub_meta must be provided for 3D bar plot, use 2D bar plot instead') + + col_num = len(df) # Create title if title is None: diff --git a/metax/taxafunc_ploter/heatmap_plot.py b/metax/taxafunc_ploter/heatmap_plot.py index 5190664..0cdbd21 100644 --- a/metax/taxafunc_ploter/heatmap_plot.py +++ b/metax/taxafunc_ploter/heatmap_plot.py @@ -357,10 +357,17 @@ def plot_basic_heatmap(self, df, title = 'Heatmap',fig_size:tuple|None = None, ''' if plot_mean and sub_meta == 'None': # if sub_meta is not None, plot_mean is False - df = self.tfa.BasicStats.get_stats_mean_df_by_group(df) print('Plot the mean of the data, set rename_sample to False') rename_sample = False + df = self.tfa.BasicStats.get_stats_mean_df_by_group(df) + # remove all 0 rows + row_num = len(df) + df = df.loc[~(df==0).all(axis=1)] if row_cluster else df + # remove all 0 columns + col_num = len(df.columns) + df = df.loc[:, (df != 0).any(axis=0)] if col_cluster else df + print(f"Remove all 0 rows and columns after calculating the mean of the data:\n{row_num - len(df)} rows are removed\n{col_num - len(df.columns)} columns are removed") if len(df) < 2: row_cluster = False diff --git a/metax/taxafunc_ploter/network_plot.py b/metax/taxafunc_ploter/network_plot.py index beae9a2..f857861 100644 --- a/metax/taxafunc_ploter/network_plot.py +++ b/metax/taxafunc_ploter/network_plot.py @@ -130,20 +130,43 @@ def create_nodes_links( # create network_df for export to cytoscape network_df = self.tfa.BasicStats.get_stats_mean_df_by_group(df) - network_df['sum'] = network_df.sum(axis=1) network_df.reset_index(inplace=True) network_df.columns = ['taxa', 'function'] + network_df.columns.tolist()[2:] + taxa_dict = network_df.drop('function', axis=1).groupby('taxa').sum().to_dict() + func_dict = network_df.drop('taxa', axis=1).groupby('function').sum().to_dict() network_df['focus_taxa'] = network_df['taxa'].apply(lambda x: 'Y' if x in focus_list else 'N') network_df['focus_func'] = network_df['function'].apply(lambda x: 'Y' if x in focus_list else 'N') - # Done creating network_df + # cerate attributes_df + attributes_taxa_df = pd.DataFrame(network_df[['taxa']]) + attributes_taxa_df.drop_duplicates(inplace=True) + attributes_taxa_df['focus'] = attributes_taxa_df['taxa'].apply(lambda x: 'Y' if x in focus_list else 'N') + attributes_taxa_df.columns = ['node', 'focus'] + attributes_taxa_df['type'] = 'taxa' + # add the intensity columns to the attributes_df + for col in taxa_dict.keys(): + attributes_taxa_df[col] = attributes_taxa_df['node'].map(taxa_dict[col]) + + attributes_func_df = pd.DataFrame(network_df[['function']]) + attributes_func_df.drop_duplicates(inplace=True) + attributes_func_df['focus'] = attributes_func_df['function'].apply(lambda x: 'Y' if x in focus_list else 'N') + attributes_func_df.columns = ['node', 'focus'] + attributes_func_df['type'] = 'function' + # add the intensity columns to the attributes_df + for col in func_dict.keys(): + attributes_func_df[col] = attributes_func_df['node'].map(func_dict[col]) + + # concatenate the taxa and function attributes_df + attributes_df = pd.concat([attributes_taxa_df, attributes_func_df]) + attributes_df['mean'] = attributes_df.drop(['node', 'focus', 'type'], axis=1).mean(axis=1) + # Done creating network_df and attributes_df for export to cytoscape - df['sum'] = df.sum(axis=1) + df['mean'] = df.mean(axis=1) df.reset_index(inplace=True) colname = df.columns.tolist() colname[0] = 'taxa' colname[1] = 'function' df.columns = colname - df = df[['taxa', 'function', 'sum']] + df = df[['taxa', 'function', 'mean']] if plot_list_only: print("Plotting only the list provided in focus_list") @@ -165,8 +188,8 @@ def create_nodes_links( df = df.loc[df['taxa'].isin(focus_list) | df['function'].isin(focus_list)] print(f"New df shape: {df.shape}") - taxa_sum = df[df['taxa'] != ""].groupby('taxa')['sum'].sum().to_dict() - function_sum = df[df['function'] != ""].groupby('function')['sum'].sum().to_dict() + taxa_sum = df[df['taxa'] != ""].groupby('taxa')['mean'].sum().to_dict() + function_sum = df[df['function'] != ""].groupby('function')['mean'].sum().to_dict() sum_dict = {**taxa_sum, **function_sum} min_value = min(sum_dict.values()) @@ -217,7 +240,7 @@ def normalize(value): {"name": "Function", "itemStyle": {"normal": {"color": self.func_color}}}, ] - return nodes, links, categories, network_df + return nodes, links, categories, network_df, attributes_df def plot_tflink_network( self, @@ -244,6 +267,7 @@ def plot_tflink_network( Returns: - A Pyecharts Graph object that can be displayed in Jupyter notebooks or web pages. - A DataFrame containing nodes and links for export to Cytoscape. + - A DataFrame containing attributes of the nodes for export to Cytoscape. """ # preprocess focus_list @@ -260,13 +284,13 @@ def plot_tflink_network( new_list.extend((taxon, func)) else: print(f"Warning: {i} is not in taxa or function list") - nodes, links, categories, network_df = self.create_nodes_links(sample_list=sample_list, + nodes, links, categories, network_df, attributes_df = self.create_nodes_links(sample_list=sample_list, focus_list = new_list, plot_list_only = plot_list_only, list_only_no_link=list_only_no_link) else: focus_list = [] - nodes, links, categories, network_df = self.create_nodes_links(sample_list = sample_list) + nodes, links, categories, network_df, attributes_df = self.create_nodes_links(sample_list = sample_list) c = ( @@ -336,7 +360,7 @@ def plot_tflink_network( ) - return c , network_df + return c , network_df, attributes_df def plot_co_expression_network(self, df_type:str= 'taxa', corr_method:str = 'pearson', diff --git a/metax/utils/version.py b/metax/utils/version.py index ca0218e..f987022 100644 --- a/metax/utils/version.py +++ b/metax/utils/version.py @@ -1,2 +1,2 @@ -__version__ = '1.117.0' +__version__ = '1.117.1' API_version = '3' \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 6791e77..b89d208 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "MetaXTools" -version = "1.117.0" +version = "1.117.1" description = "MetaXTools is a novel tool for linking peptide sequences with taxonomic and functional information in Metaproteomics." readme = "README_PyPi.md" license = { text = "NorthOmics" }