diff --git a/poetry.lock b/poetry.lock
index 539e932..1c709bf 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -771,4 +771,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = ">=3.8,<3.9"
-content-hash = "a652dcb9e1a8a200480197c5618b7ddb4cb33fbf1a89278058eeda745ea7293f"
+content-hash = "ff683c2a3f778cd6ad946d6aa4b1f567514f36026e07fccffdb2cd7e86778e0a"
diff --git a/pyproject.toml b/pyproject.toml
index cfc236f..2292dac 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,9 +12,11 @@ readme = "README.md"
python = ">=3.8,<3.9"
pre-commit = "^3.4.0"
pyinstaller = "^6.0.0"
-pandas = "^2.0"
pyside2 = "^5.15.2.1"
-pylint = "^2"
+pandas = { version = "^2", markers = "python_version < '3.9'" }
+pylint = { version = "^2", markers = "python_version < '3.9'" }
+# scipy = { version = "^1", markers = "python_version < '3.9'" }
+# openpyxl = "^3.1.2"
[build-system]
diff --git a/statapp/__main__.py b/statapp/__main__.py
index 85a118a..5e760de 100644
--- a/statapp/__main__.py
+++ b/statapp/__main__.py
@@ -39,6 +39,5 @@ def main():
window.show()
return app.exec_()
-
if __name__ == "__main__":
sys.exit(main())
diff --git a/statapp/calculations.py b/statapp/calculations.py
index 871fafb..25381ea 100644
--- a/statapp/calculations.py
+++ b/statapp/calculations.py
@@ -17,6 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
+from dataclasses import dataclass
+
import numpy as np
import pandas as pd
@@ -24,6 +26,10 @@ DIRECT_LINK = 0
INDIRECT_LINK = 1
+def generateYValues(mean, std, count):
+ return np.random.normal(mean, std, size=(count, 1))
+
+
def generateXValues(mean, std, typeConnection, yColumn):
yMean = np.mean(yColumn)
values = []
@@ -38,7 +44,9 @@ def generateXValues(mean, std, typeConnection, yColumn):
else:
x = mean
values.append(x)
- return np.array(values)
+
+ res = np.array(values)
+ return res.reshape(len(res), 1)
def varianceAnalysis(data):
@@ -49,3 +57,38 @@ def varianceAnalysis(data):
def correlationAnalysis(data):
return pd.DataFrame(data).corr().to_numpy()
+
+@dataclass()
+class LinearPolynomResult:
+ paramsAndImportance: np.ndarray
+ residualVariance: np.float64
+
+
+def linearPolynom(inputData) -> LinearPolynomResult:
+ x = inputData[:, 1:]
+ y = inputData[:, 0]
+ data = pd.DataFrame(x)
+ data.insert(0, 'const', 1)
+ # ---
+ result = np.linalg.lstsq(data, y, rcond=None)
+ # Коэффициенты регрессии
+ params = result[0]
+ # Остатки
+ residues = result[1]
+
+ # Степень свободы
+ dof = len(data) - len(params)
+ mse = residues / dof
+ cov = mse * np.diagonal(np.linalg.inv(data.T @ data))
+ se = np.sqrt(cov)
+ tStatistics = params / se
+
+ # возможно стоит сделать через np.reshape + np.concatenate
+ out = pd.DataFrame()
+ out[0] = params
+ out[1] = tStatistics
+
+ return LinearPolynomResult(
+ out.to_numpy(),
+ np.float64(mse[0])
+ )
diff --git a/statapp/linear_polynom_window.py b/statapp/linear_polynom_window.py
new file mode 100644
index 0000000..b37bfd3
--- /dev/null
+++ b/statapp/linear_polynom_window.py
@@ -0,0 +1,42 @@
+#
+# Copyright (c) 2023 Maxim Slipenko, Eugene Lazurenko.
+#
+# This file is part of Statapp
+# (see https://github.com/shizand/statapp).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+from PySide2.QtWidgets import QDialog, QHeaderView
+
+from statapp.calculations import linearPolynom
+from statapp.models.linear_polynom_model import LinearPolynomModel
+from statapp.ui.ui_linear_polynom_window import Ui_LinearPolynomWindow
+from statapp.utils import addIcon
+
+
+class LinearPolynomWindow(QDialog):
+ def __init__(self, data):
+ super().__init__()
+ self.ui = Ui_LinearPolynomWindow()
+ self.ui.setupUi(self)
+ addIcon(self)
+
+ result = linearPolynom(data)
+
+ self.model = LinearPolynomModel(result.paramsAndImportance.round(2))
+ self.ui.tableView.setModel(self.model)
+ header = self.ui.tableView.horizontalHeader()
+ header.setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
+
+ self.ui.residualVarianceValueLabel.setText(str(result.residualVariance))
diff --git a/statapp/main_window.py b/statapp/main_window.py
index eb6172d..8f90f37 100644
--- a/statapp/main_window.py
+++ b/statapp/main_window.py
@@ -21,8 +21,9 @@ import numpy as np
from PySide2.QtCore import Slot
from PySide2.QtWidgets import QMainWindow, QMessageBox
-from statapp.calculations import generateXValues
+from statapp.calculations import generateXValues, generateYValues
from statapp.generate_factor_window import GenerateFactorWindow
+from statapp.linear_polynom_window import LinearPolynomWindow
from statapp.models.input_values_model import InputValuesModel
from statapp.generate_window import GenerateWindow
from statapp.about_window import AboutWindow
@@ -46,12 +47,48 @@ class MainWindow(QMainWindow):
self.ui.varianceAnalysisAction.setEnabled(False)
self.ui.correlationAnalisisAction.setEnabled(False)
+ self.mainActions = [
+ self.ui.varianceAnalysisAction,
+ self.ui.correlationAnalisisAction,
+ self.ui.linearPolynomAction,
+ ]
+
self.aboutWindow = None
self.isDataChanged = False
self.model = InputValuesModel()
self.fileModel = FileSLCModel()
self.ui.tableView.setModel(self.model)
+ self.model.layoutChanged.connect(self.updateActionsEnabled)
+ self.updateActionsEnabled()
+ #
+ # Для быстрой отладки
+ # n = 10
+ # y = generateYValues(100, 5, n)
+ # x1 = generateXValues(20, 2, 0, y)
+ # x2 = generateXValues(10, 1, 0, y)
+ # self.model.updateAllData(np.concatenate([y, x1, x2], axis=1))
+
+
+ def updateActionsEnabled(self):
+ data = self.model.getData()
+
+ # есть только отклик
+ if data.shape[1] == 1:
+ self.ui.generateXaction.setEnabled(True)
+ self.setEnabledMainActions(False)
+ # есть отклик и фактор(ы)
+ elif data.shape[1] > 1:
+ self.ui.generateXaction.setEnabled(True)
+ self.setEnabledMainActions(True)
+ else:
+ self.ui.generateXaction.setEnabled(False)
+ self.setEnabledMainActions(False)
+
+
+ def setEnabledMainActions(self, enabled):
+ for action in self.mainActions:
+ action.setEnabled(enabled)
@Slot()
def on_openfileaction_triggered(self):
@@ -85,18 +122,6 @@ class MainWindow(QMainWindow):
self.model.updateAllData(data)
self.isDataChanged = False
- if data.shape[1] == 1:
- self.ui.generateXaction.setEnabled(True)
- self.ui.varianceAnalysisAction.setEnabled(False)
- self.ui.correlationAnalisisAction.setEnabled(False)
- elif data.shape[1] > 1:
- self.ui.generateXaction.setEnabled(True)
- self.ui.varianceAnalysisAction.setEnabled(True)
- self.ui.correlationAnalisisAction.setEnabled(True)
- else:
- self.ui.generateXaction.setEnabled(False)
- self.ui.varianceAnalysisAction.setEnabled(False)
- self.ui.correlationAnalisisAction.setEnabled(False)
@Slot()
def on_savefileaction_triggered(self):
@@ -112,10 +137,9 @@ class MainWindow(QMainWindow):
gw = GenerateWindow()
if gw.exec():
- y = np.random.normal(gw.mat, gw.deviation, size=(gw.count, 1))
+ y = generateYValues(gw.mat, gw.deviation, gw.count)
self.model.updateAllData(y.round(2))
self.isDataChanged = True
- self.ui.generateXaction.setEnabled(True)
@Slot()
def on_generateXaction_triggered(self):
@@ -125,11 +149,8 @@ class MainWindow(QMainWindow):
data = self.model.getData()
y = self.model.getY()
xValues = generateXValues(gfw.mat, gfw.deviation, gfw.typeConnection, y)
- xValues = xValues.reshape(len(xValues), 1).round(2)
- data = np.concatenate((data, xValues), axis=1)
+ data = np.concatenate((data, xValues.round(2)), axis=1)
self.model.updateAllData(data)
- self.ui.varianceAnalysisAction.setEnabled(True)
- self.ui.correlationAnalisisAction.setEnabled(True)
self.isDataChanged = True
@Slot()
@@ -147,6 +168,11 @@ class MainWindow(QMainWindow):
dw = CorrelationAnalysisWindow(self.model.getData())
dw.exec()
+ @Slot()
+ def on_linearPolynomAction_triggered(self):
+ dw = LinearPolynomWindow(self.model.getData())
+ dw.exec()
+
def closeEvent(self, event):
if self.isDataChanged:
file = ''
diff --git a/statapp/models/linear_polynom_model.py b/statapp/models/linear_polynom_model.py
new file mode 100644
index 0000000..249ae16
--- /dev/null
+++ b/statapp/models/linear_polynom_model.py
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2023 Maxim Slipenko, Eugene Lazurenko.
+#
+# This file is part of Statapp
+# (see https://github.com/shizand/statapp).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+from PySide2.QtCore import QModelIndex
+
+from statapp.models.ro_table_model import ROTableModel
+
+
+class LinearPolynomModel(ROTableModel):
+ def getHorizontalHeader(self):
+ return ['Коэффициент регрессии', 'Коэффициент значимости']
+
+ def getVerticalHeader(self):
+ count = (self.rowCount(QModelIndex()))
+ return ['Свободный член'] + [f'X{i}' for i in range(1, count)]
diff --git a/statapp/ui/linear_polynom_window.ui b/statapp/ui/linear_polynom_window.ui
new file mode 100644
index 0000000..09e0768
--- /dev/null
+++ b/statapp/ui/linear_polynom_window.ui
@@ -0,0 +1,49 @@
+
+
+ LinearPolynomWindow
+
+
+
+ 0
+ 0
+ 630
+ 400
+
+
+
+ Линейный полином
+
+
+ -
+
+
-
+
+
+ -
+
+
+ 10
+
+
-
+
+
+ Остаточная дисперсия:
+
+
+
+ -
+
+
+ undefined
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/statapp/ui/main_window.ui b/statapp/ui/main_window.ui
index 37a6252..dd762e4 100644
--- a/statapp/ui/main_window.ui
+++ b/statapp/ui/main_window.ui
@@ -40,7 +40,7 @@
0
0
800
- 21
+ 27
diff --git a/statapp/ui/ui_linear_polynom_window.py b/statapp/ui/ui_linear_polynom_window.py
new file mode 100644
index 0000000..254cb4e
--- /dev/null
+++ b/statapp/ui/ui_linear_polynom_window.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2023 Maxim Slipenko, Eugene Lazurenko.
+#
+# This file is part of Statapp
+# (see https://github.com/shizand/statapp).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+
+
+################################################################################
+## Form generated from reading UI file 'linear_polynom_window.ui'
+##
+## Created by: Qt User Interface Compiler version 5.15.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide2.QtCore import *
+from PySide2.QtGui import *
+from PySide2.QtWidgets import *
+
+
+class Ui_LinearPolynomWindow(object):
+ def setupUi(self, LinearPolynomWindow):
+ if not LinearPolynomWindow.objectName():
+ LinearPolynomWindow.setObjectName(u"LinearPolynomWindow")
+ LinearPolynomWindow.resize(630, 400)
+ self.gridLayout_2 = QGridLayout(LinearPolynomWindow)
+ self.gridLayout_2.setObjectName(u"gridLayout_2")
+ self.gridLayout = QGridLayout()
+ self.gridLayout.setObjectName(u"gridLayout")
+ self.tableView = QTableView(LinearPolynomWindow)
+ self.tableView.setObjectName(u"tableView")
+
+ self.gridLayout.addWidget(self.tableView, 0, 0, 1, 1)
+
+ self.gridLayout_3 = QGridLayout()
+ self.gridLayout_3.setObjectName(u"gridLayout_3")
+ self.gridLayout_3.setContentsMargins(-1, 10, -1, -1)
+ self.residualVarianceLabel = QLabel(LinearPolynomWindow)
+ self.residualVarianceLabel.setObjectName(u"residualVarianceLabel")
+
+ self.gridLayout_3.addWidget(self.residualVarianceLabel, 0, 0, 1, 1)
+
+ self.residualVarianceValueLabel = QLabel(LinearPolynomWindow)
+ self.residualVarianceValueLabel.setObjectName(u"residualVarianceValueLabel")
+
+ self.gridLayout_3.addWidget(self.residualVarianceValueLabel, 0, 1, 1, 1)
+
+
+ self.gridLayout.addLayout(self.gridLayout_3, 1, 0, 1, 1)
+
+
+ self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)
+
+
+ self.retranslateUi(LinearPolynomWindow)
+
+ QMetaObject.connectSlotsByName(LinearPolynomWindow)
+ # setupUi
+
+ def retranslateUi(self, LinearPolynomWindow):
+ LinearPolynomWindow.setWindowTitle(QCoreApplication.translate("LinearPolynomWindow", u"\u041b\u0438\u043d\u0435\u0439\u043d\u044b\u0439 \u043f\u043e\u043b\u0438\u043d\u043e\u043c", None))
+ self.residualVarianceLabel.setText(QCoreApplication.translate("LinearPolynomWindow", u"\u041e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u0430\u044f \u0434\u0438\u0441\u043f\u0435\u0440\u0441\u0438\u044f:", None))
+ self.residualVarianceValueLabel.setText(QCoreApplication.translate("LinearPolynomWindow", u"undefined", None))
+ # retranslateUi
diff --git a/statapp/ui/ui_main_window.py b/statapp/ui/ui_main_window.py
index 89f3292..92106cf 100644
--- a/statapp/ui/ui_main_window.py
+++ b/statapp/ui/ui_main_window.py
@@ -54,6 +54,8 @@ class Ui_MainWindow(object):
self.varianceAnalysisAction.setObjectName(u"varianceAnalysisAction")
self.correlationAnalisisAction = QAction(MainWindow)
self.correlationAnalisisAction.setObjectName(u"correlationAnalisisAction")
+ self.linearPolynomAction = QAction(MainWindow)
+ self.linearPolynomAction.setObjectName(u"linearPolynomAction")
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
@@ -73,7 +75,7 @@ class Ui_MainWindow(object):
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(MainWindow)
self.menubar.setObjectName(u"menubar")
- self.menubar.setGeometry(QRect(0, 0, 800, 21))
+ self.menubar.setGeometry(QRect(0, 0, 800, 27))
self.filemenu = QMenu(self.menubar)
self.filemenu.setObjectName(u"filemenu")
self.generatemenu = QMenu(self.menubar)
@@ -101,6 +103,7 @@ class Ui_MainWindow(object):
self.generatemenu.addAction(self.generateXaction)
self.analyzemenu.addAction(self.varianceAnalysisAction)
self.analyzemenu.addAction(self.correlationAnalisisAction)
+ self.modelmenu.addAction(self.linearPolynomAction)
self.helpmenu.addAction(self.aboutmenuaction)
self.retranslateUi(MainWindow)
@@ -118,6 +121,7 @@ class Ui_MainWindow(object):
self.closefileaction.setText(QCoreApplication.translate("MainWindow", u"\u0417\u0430\u043a\u0440\u044b\u0442\u044c", None))
self.varianceAnalysisAction.setText(QCoreApplication.translate("MainWindow", u"\u0414\u0438\u0441\u043f\u0435\u0440\u0441\u0438\u043e\u043d\u043d\u044b\u0439 \u0430\u043d\u0430\u043b\u0438\u0437", None))
self.correlationAnalisisAction.setText(QCoreApplication.translate("MainWindow", u"\u041a\u043e\u0440\u0440\u0435\u043b\u044f\u0446\u0438\u043e\u043d\u043d\u044b\u0439 \u0430\u043d\u0430\u043b\u0438\u0437", None))
+ self.linearPolynomAction.setText(QCoreApplication.translate("MainWindow", u"\u041b\u0438\u043d\u0435\u0439\u043d\u044b\u0439 \u043f\u043e\u043b\u0438\u043d\u043e\u043c", None))
self.label.setText(QCoreApplication.translate("MainWindow", u"\u0421\u0422\u0410\u0422\u0418\u0421\u0422\u0418\u0427\u0415\u0421\u041a\u0418\u0415 \u0414\u0410\u041d\u041d\u042b\u0415", None))
self.filemenu.setTitle(QCoreApplication.translate("MainWindow", u"\u0424\u0430\u0439\u043b", None))
self.generatemenu.setTitle(QCoreApplication.translate("MainWindow", u"\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u0435\u0439", None))
diff --git a/statapp/utils.py b/statapp/utils.py
index c4f019f..9d1e83c 100644
--- a/statapp/utils.py
+++ b/statapp/utils.py
@@ -34,11 +34,13 @@ def resourcePath(relative):
bundleDir = os.path.dirname(os.path.abspath(__file__))
return os.path.join(bundleDir, relative)
+
def addIcon(windowOrDialog):
icon = QIcon()
icon.addFile(resourcePath("ui/images/logo.ico"), QSize(), QIcon.Normal, QIcon.Off)
windowOrDialog.setWindowIcon(icon)
+
def safeListGet(lst, idx, default):
try:
return lst[idx]