This commit is contained in:
Maxim Slipenko 2023-10-19 22:24:30 +03:00
parent 2bdd88ba43
commit 8303cc9e54
9 changed files with 277 additions and 21 deletions

View File

@ -1,6 +1,6 @@
# Copyright (c) 2023 Matthew Rocklin
# All rights reserved.
import numpy as np
# This source code is distributed under the terms of the BSD license,
# which allows you to use, modify, and distribute it
# as long as you comply with the license terms.
@ -12,23 +12,24 @@
# TODO: remove
# pylint: skip-file
from numpy import linalg, zeros, ones, hstack, asarray
from numpy import linalg, zeros, ones, hstack, asarray, diagonal
import itertools
def basis_vector(n, i):
def basisVector(n, i):
""" Return an array like [0, 0, ..., 1, ..., 0, 0]
>>> from multipolyfit.core import basis_vector
>>> from statapp._vendor.multipolyfit import basisVector
>>> basis_vector(3, 1)
array([0, 1, 0])
>>> basis_vector(5, 4)
>>> basisVector(5, 4)
array([0, 0, 0, 0, 1])
"""
x = zeros(n, dtype=int)
x[i] = 1
return x
def as_tall(x):
def asTall(x):
""" Turns a row vector into a column vector """
return x.reshape(x.shape + (1,))
@ -65,25 +66,38 @@ def multipolyfit(xs, y, deg, full=False, model_out=False, powers_out=False):
y = asarray(y).squeeze()
rows = y.shape[0]
xs = asarray(xs)
num_covariates = xs.shape[1]
numCovariates = xs.shape[1]
xs = hstack((ones((xs.shape[0], 1), dtype=xs.dtype) , xs))
generators = [basis_vector(num_covariates+1, i)
for i in range(num_covariates+1)]
generators = [basisVector(numCovariates + 1, i)
for i in range(numCovariates+1)]
# All combinations of degrees
powers = [sum(x) for x in itertools.combinations_with_replacement(generators, deg)]
# Raise data to specified degree pattern, stack in order
A = hstack(asarray([as_tall((xs**p).prod(1)) for p in powers]))
a = hstack(asarray([asTall((xs ** p).prod(1)) for p in powers]))
beta = linalg.lstsq(A, y, rcond=None)[0]
result = linalg.lstsq(a, y, rcond=None)
beta = result[0]
if model_out:
return mk_model(beta, powers)
if powers_out:
return beta, powers
if full:
residues = result[1]
dof = len(a) - len(beta)
mse = residues / dof
cov = mse * diagonal(linalg.inv(a.T @ a))
se = np.sqrt(cov)
tStatistics = beta / se
return result, powers, tStatistics, mse
return beta
def mk_model(beta, powers):
@ -109,8 +123,8 @@ def mk_sympy_function(beta, powers):
def get_terms(powers):
from sympy import symbols, Add, Mul, S
num_covariates = len(powers[0]) - 1
xs = (S.One,) + symbols('x0:%d' % num_covariates)
num_covariates = len(powers[0])
xs = (S.One,) + symbols('x1:%d' % num_covariates)
terms = [Mul(*[x ** deg for x, deg in zip(xs, power)]) for power in powers]
return terms

View File

@ -60,12 +60,17 @@ def correlationAnalysis(data):
return pd.DataFrame(data).corr().to_numpy()
@dataclass()
class LinearPolynomResult:
class RegressionResult:
"""
Attributes:
paramsAndImportance (np.ndarray): Параметры модели
residualVariance (np.float64): Остаточная дисперсия
"""
paramsAndImportance: np.ndarray
residualVariance: np.float64
def linearPolynom(inputData) -> LinearPolynomResult:
def linearPolynom(inputData) -> RegressionResult:
x = inputData[:, 1:]
y = inputData[:, 0]
data = pd.DataFrame(x)
@ -76,8 +81,6 @@ def linearPolynom(inputData) -> LinearPolynomResult:
params = result[0]
# Остатки
residues = result[1]
# Степень свободы
dof = len(data) - len(params)
mse = residues / dof
cov = mse * np.diagonal(np.linalg.inv(data.T @ data))
@ -89,18 +92,40 @@ def linearPolynom(inputData) -> LinearPolynomResult:
out[0] = params
out[1] = tStatistics
return LinearPolynomResult(
return RegressionResult(
out.to_numpy(),
np.float64(mse[0])
)
def squaredPolynom(inputData) -> LinearPolynomResult:
@dataclass()
class ExtendedRegressionResult:
"""
Attributes:
paramsAndImportance (np.ndarray): Параметры модели
residualVariance (np.float64): Остаточная дисперсия
"""
paramsAndImportance: np.ndarray
residualVariance: np.float64
powers: list
def squaredPolynom(inputData):
x = inputData[:, 1:]
y = inputData[:, 0]
data = pd.DataFrame(x)
betas, powers = multipolyfit(x, y, 2, powers_out=True)
result, powers, tStatistics, mse = multipolyfit(x, y, 2, full=True)
betas = result[0]
res = mk_sympy_function(betas, powers)
print(data)
print(res)
return powers
out = pd.DataFrame()
out[0] = betas
out[1] = tStatistics
return ExtendedRegressionResult(
out.to_numpy(),
np.float64(mse[0]),
powers
)

View File

@ -28,6 +28,7 @@ from statapp.models.input_values_model import InputValuesModel
from statapp.generate_window import GenerateWindow
from statapp.about_window import AboutWindow
from statapp.models.fileslc_model import FileSLCModel
from statapp.squared_polynom_window import SquaredPolynomWindow
from statapp.ui.ui_main_window import Ui_MainWindow
from statapp.utils import buildMessageBox, addIcon
from statapp.variance_analysis import VarianceAnalysisWindow
@ -173,6 +174,11 @@ class MainWindow(QMainWindow):
dw = LinearPolynomWindow(self.model.getData())
dw.exec()
@Slot()
def on_squaredPolynomAction_triggered(self):
dw = SquaredPolynomWindow(self.model.getData())
dw.exec()
def closeEvent(self, event):
if self.isDataChanged:
file = ''

View File

@ -0,0 +1,29 @@
#
# 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 statapp._vendor.multipolyfit import get_terms
from statapp.models.linear_polynom_model import LinearPolynomModel
class SquaredPolynomModel(LinearPolynomModel):
powers: list
def getVerticalHeader(self):
return [str(x) for x in get_terms(self.powers)]

View File

@ -0,0 +1,44 @@
#
# 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 squaredPolynom
from statapp.models.squared_polynom_model import SquaredPolynomModel
from statapp.ui.ui_squared_polynom_window import Ui_SquaredPolynomWindow
from statapp.utils import addIcon
class SquaredPolynomWindow(QDialog):
def __init__(self, data):
super().__init__()
self.ui = Ui_SquaredPolynomWindow()
self.ui.setupUi(self)
addIcon(self)
result = squaredPolynom(data)
# Не округляем, т.к. может получиться коэффициент = 0 и значимый (t-критерий)
self.model = SquaredPolynomModel(result.paramsAndImportance)
self.model.powers = result.powers
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

@ -70,6 +70,7 @@
<string>Моделирование</string>
</property>
<addaction name="linearPolynomAction"/>
<addaction name="squaredPolynomAction"/>
</widget>
<widget class="QMenu" name="helpmenu">
<property name="title">
@ -129,6 +130,11 @@
<string>Линейный полином</string>
</property>
</action>
<action name="squaredPolynomAction">
<property name="text">
<string>Квадратичный полином</string>
</property>
</action>
</widget>
<resources/>
<connections/>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SquaredPolynomWindow</class>
<widget class="QDialog" name="SquaredPolynomWindow">
<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

@ -56,6 +56,8 @@ class Ui_MainWindow(object):
self.correlationAnalisisAction.setObjectName(u"correlationAnalisisAction")
self.linearPolynomAction = QAction(MainWindow)
self.linearPolynomAction.setObjectName(u"linearPolynomAction")
self.squaredPolynomAction = QAction(MainWindow)
self.squaredPolynomAction.setObjectName(u"squaredPolynomAction")
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
@ -104,6 +106,7 @@ class Ui_MainWindow(object):
self.analyzemenu.addAction(self.varianceAnalysisAction)
self.analyzemenu.addAction(self.correlationAnalisisAction)
self.modelmenu.addAction(self.linearPolynomAction)
self.modelmenu.addAction(self.squaredPolynomAction)
self.helpmenu.addAction(self.aboutmenuaction)
self.retranslateUi(MainWindow)
@ -122,6 +125,7 @@ class Ui_MainWindow(object):
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.squaredPolynomAction.setText(QCoreApplication.translate("MainWindow", u"\u041a\u0432\u0430\u0434\u0440\u0430\u0442\u0438\u0447\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))

79
statapp/ui/ui_squared_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 'squared_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_SquaredPolynomWindow(object):
def setupUi(self, SquaredPolynomWindow):
if not SquaredPolynomWindow.objectName():
SquaredPolynomWindow.setObjectName(u"SquaredPolynomWindow")
SquaredPolynomWindow.resize(630, 400)
self.gridLayout_2 = QGridLayout(SquaredPolynomWindow)
self.gridLayout_2.setObjectName(u"gridLayout_2")
self.gridLayout = QGridLayout()
self.gridLayout.setObjectName(u"gridLayout")
self.tableView = QTableView(SquaredPolynomWindow)
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(SquaredPolynomWindow)
self.residualVarianceLabel.setObjectName(u"residualVarianceLabel")
self.gridLayout_3.addWidget(self.residualVarianceLabel, 0, 0, 1, 1)
self.residualVarianceValueLabel = QLabel(SquaredPolynomWindow)
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(SquaredPolynomWindow)
QMetaObject.connectSlotsByName(SquaredPolynomWindow)
# setupUi
def retranslateUi(self, SquaredPolynomWindow):
SquaredPolynomWindow.setWindowTitle(QCoreApplication.translate("SquaredPolynomWindow", u"\u041a\u0432\u0430\u0434\u0440\u0430\u0442\u0438\u0447\u043d\u044b\u0439 \u043f\u043e\u043b\u0438\u043d\u043e\u043c", None))
self.residualVarianceLabel.setText(QCoreApplication.translate("SquaredPolynomWindow", 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("SquaredPolynomWindow", u"undefined", None))
# retranslateUi