feat: добавлен "Линейный полином" (#74)

This commit is contained in:
Maxim Slipenko 2023-10-05 21:28:11 +03:00 committed by GitHub
parent e653c3df29
commit 232de9f145
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 309 additions and 26 deletions

2
poetry.lock generated
View File

@ -771,4 +771,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.8,<3.9" python-versions = ">=3.8,<3.9"
content-hash = "a652dcb9e1a8a200480197c5618b7ddb4cb33fbf1a89278058eeda745ea7293f" content-hash = "ff683c2a3f778cd6ad946d6aa4b1f567514f36026e07fccffdb2cd7e86778e0a"

View File

@ -12,9 +12,11 @@ readme = "README.md"
python = ">=3.8,<3.9" python = ">=3.8,<3.9"
pre-commit = "^3.4.0" pre-commit = "^3.4.0"
pyinstaller = "^6.0.0" pyinstaller = "^6.0.0"
pandas = "^2.0"
pyside2 = "^5.15.2.1" 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] [build-system]

View File

@ -39,6 +39,5 @@ def main():
window.show() window.show()
return app.exec_() return app.exec_()
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

View File

@ -17,6 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from dataclasses import dataclass
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@ -24,6 +26,10 @@ DIRECT_LINK = 0
INDIRECT_LINK = 1 INDIRECT_LINK = 1
def generateYValues(mean, std, count):
return np.random.normal(mean, std, size=(count, 1))
def generateXValues(mean, std, typeConnection, yColumn): def generateXValues(mean, std, typeConnection, yColumn):
yMean = np.mean(yColumn) yMean = np.mean(yColumn)
values = [] values = []
@ -38,7 +44,9 @@ def generateXValues(mean, std, typeConnection, yColumn):
else: else:
x = mean x = mean
values.append(x) values.append(x)
return np.array(values)
res = np.array(values)
return res.reshape(len(res), 1)
def varianceAnalysis(data): def varianceAnalysis(data):
@ -49,3 +57,38 @@ def varianceAnalysis(data):
def correlationAnalysis(data): def correlationAnalysis(data):
return pd.DataFrame(data).corr().to_numpy() 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])
)

View File

@ -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 <http://www.gnu.org/licenses/>.
#
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))

View File

@ -21,8 +21,9 @@ import numpy as np
from PySide2.QtCore import Slot from PySide2.QtCore import Slot
from PySide2.QtWidgets import QMainWindow, QMessageBox 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.generate_factor_window import GenerateFactorWindow
from statapp.linear_polynom_window import LinearPolynomWindow
from statapp.models.input_values_model import InputValuesModel from statapp.models.input_values_model import InputValuesModel
from statapp.generate_window import GenerateWindow from statapp.generate_window import GenerateWindow
from statapp.about_window import AboutWindow from statapp.about_window import AboutWindow
@ -46,12 +47,48 @@ class MainWindow(QMainWindow):
self.ui.varianceAnalysisAction.setEnabled(False) self.ui.varianceAnalysisAction.setEnabled(False)
self.ui.correlationAnalisisAction.setEnabled(False) self.ui.correlationAnalisisAction.setEnabled(False)
self.mainActions = [
self.ui.varianceAnalysisAction,
self.ui.correlationAnalisisAction,
self.ui.linearPolynomAction,
]
self.aboutWindow = None self.aboutWindow = None
self.isDataChanged = False self.isDataChanged = False
self.model = InputValuesModel() self.model = InputValuesModel()
self.fileModel = FileSLCModel() self.fileModel = FileSLCModel()
self.ui.tableView.setModel(self.model) 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() @Slot()
def on_openfileaction_triggered(self): def on_openfileaction_triggered(self):
@ -85,18 +122,6 @@ class MainWindow(QMainWindow):
self.model.updateAllData(data) self.model.updateAllData(data)
self.isDataChanged = False 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() @Slot()
def on_savefileaction_triggered(self): def on_savefileaction_triggered(self):
@ -112,10 +137,9 @@ class MainWindow(QMainWindow):
gw = GenerateWindow() gw = GenerateWindow()
if gw.exec(): 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.model.updateAllData(y.round(2))
self.isDataChanged = True self.isDataChanged = True
self.ui.generateXaction.setEnabled(True)
@Slot() @Slot()
def on_generateXaction_triggered(self): def on_generateXaction_triggered(self):
@ -125,11 +149,8 @@ class MainWindow(QMainWindow):
data = self.model.getData() data = self.model.getData()
y = self.model.getY() y = self.model.getY()
xValues = generateXValues(gfw.mat, gfw.deviation, gfw.typeConnection, y) xValues = generateXValues(gfw.mat, gfw.deviation, gfw.typeConnection, y)
xValues = xValues.reshape(len(xValues), 1).round(2) data = np.concatenate((data, xValues.round(2)), axis=1)
data = np.concatenate((data, xValues), axis=1)
self.model.updateAllData(data) self.model.updateAllData(data)
self.ui.varianceAnalysisAction.setEnabled(True)
self.ui.correlationAnalisisAction.setEnabled(True)
self.isDataChanged = True self.isDataChanged = True
@Slot() @Slot()
@ -147,6 +168,11 @@ class MainWindow(QMainWindow):
dw = CorrelationAnalysisWindow(self.model.getData()) dw = CorrelationAnalysisWindow(self.model.getData())
dw.exec() dw.exec()
@Slot()
def on_linearPolynomAction_triggered(self):
dw = LinearPolynomWindow(self.model.getData())
dw.exec()
def closeEvent(self, event): def closeEvent(self, event):
if self.isDataChanged: if self.isDataChanged:
file = '' file = ''

View File

@ -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 <http://www.gnu.org/licenses/>.
#
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)]

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LinearPolynomWindow</class>
<widget class="QDialog" name="LinearPolynomWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>Линейный полином</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTableView" name="tableView"/>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="gridLayout_3">
<property name="topMargin">
<number>10</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="residualVarianceLabel">
<property name="text">
<string>Остаточная дисперсия:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="residualVarianceValueLabel">
<property name="text">
<string>undefined</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -40,7 +40,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>800</width>
<height>21</height> <height>27</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="filemenu"> <widget class="QMenu" name="filemenu">
@ -69,6 +69,7 @@
<property name="title"> <property name="title">
<string>Моделирование</string> <string>Моделирование</string>
</property> </property>
<addaction name="linearPolynomAction"/>
</widget> </widget>
<widget class="QMenu" name="helpmenu"> <widget class="QMenu" name="helpmenu">
<property name="title"> <property name="title">
@ -123,6 +124,11 @@
<string>Корреляционный анализ</string> <string>Корреляционный анализ</string>
</property> </property>
</action> </action>
<action name="linearPolynomAction">
<property name="text">
<string>Линейный полином</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

79
statapp/ui/ui_linear_polynom_window.py generated Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
## 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

View File

@ -54,6 +54,8 @@ class Ui_MainWindow(object):
self.varianceAnalysisAction.setObjectName(u"varianceAnalysisAction") self.varianceAnalysisAction.setObjectName(u"varianceAnalysisAction")
self.correlationAnalisisAction = QAction(MainWindow) self.correlationAnalisisAction = QAction(MainWindow)
self.correlationAnalisisAction.setObjectName(u"correlationAnalisisAction") self.correlationAnalisisAction.setObjectName(u"correlationAnalisisAction")
self.linearPolynomAction = QAction(MainWindow)
self.linearPolynomAction.setObjectName(u"linearPolynomAction")
self.centralwidget = QWidget(MainWindow) self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget") self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget) self.gridLayout = QGridLayout(self.centralwidget)
@ -73,7 +75,7 @@ class Ui_MainWindow(object):
MainWindow.setCentralWidget(self.centralwidget) MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(MainWindow) self.menubar = QMenuBar(MainWindow)
self.menubar.setObjectName(u"menubar") 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 = QMenu(self.menubar)
self.filemenu.setObjectName(u"filemenu") self.filemenu.setObjectName(u"filemenu")
self.generatemenu = QMenu(self.menubar) self.generatemenu = QMenu(self.menubar)
@ -101,6 +103,7 @@ class Ui_MainWindow(object):
self.generatemenu.addAction(self.generateXaction) self.generatemenu.addAction(self.generateXaction)
self.analyzemenu.addAction(self.varianceAnalysisAction) self.analyzemenu.addAction(self.varianceAnalysisAction)
self.analyzemenu.addAction(self.correlationAnalisisAction) self.analyzemenu.addAction(self.correlationAnalisisAction)
self.modelmenu.addAction(self.linearPolynomAction)
self.helpmenu.addAction(self.aboutmenuaction) self.helpmenu.addAction(self.aboutmenuaction)
self.retranslateUi(MainWindow) 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.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.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.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.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.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)) 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))

View File

@ -34,11 +34,13 @@ def resourcePath(relative):
bundleDir = os.path.dirname(os.path.abspath(__file__)) bundleDir = os.path.dirname(os.path.abspath(__file__))
return os.path.join(bundleDir, relative) return os.path.join(bundleDir, relative)
def addIcon(windowOrDialog): def addIcon(windowOrDialog):
icon = QIcon() icon = QIcon()
icon.addFile(resourcePath("ui/images/logo.ico"), QSize(), QIcon.Normal, QIcon.Off) icon.addFile(resourcePath("ui/images/logo.ico"), QSize(), QIcon.Normal, QIcon.Off)
windowOrDialog.setWindowIcon(icon) windowOrDialog.setWindowIcon(icon)
def safeListGet(lst, idx, default): def safeListGet(lst, idx, default):
try: try:
return lst[idx] return lst[idx]