From 24871bfc8ee9fb1077bafbc0a36712143ad48386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hubert=20Soko=C5=82owski?= Date: Sat, 8 May 2021 15:11:08 +0200 Subject: [PATCH] create confusion matrix with actual accuracy --- src/components/ConfusionMatrix/Table.jsx | 94 +++++++++++++++++ src/components/ConfusionMatrix/index.jsx | 123 +++++++++++++++++++++++ src/components/Node/DataViewer/index.jsx | 13 +-- src/components/Tree.jsx | 17 +++- src/utils/predict.js | 23 ++--- 5 files changed, 244 insertions(+), 26 deletions(-) create mode 100644 src/components/ConfusionMatrix/Table.jsx create mode 100644 src/components/ConfusionMatrix/index.jsx diff --git a/src/components/ConfusionMatrix/Table.jsx b/src/components/ConfusionMatrix/Table.jsx new file mode 100644 index 0000000..795cec4 --- /dev/null +++ b/src/components/ConfusionMatrix/Table.jsx @@ -0,0 +1,94 @@ +import React, { useEffect, useState } from 'react'; +import { + Button, + Textarea, + Box, + ButtonGroup, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + ModalFooter, + useDisclosure, +} from '@chakra-ui/react'; +import { Table, Thead, Tbody, Tfoot, Tr, Th, Td, TableCaption } from '@chakra-ui/react'; +import { predict } from '../../utils/predict'; + +/** + * @param {Object} props + * @param {Array[]} props.confusionMatrix + * @param {string[]} props.headers + */ +function TableComponent({ confusionMatrix, headers }) { + return ( + + + + + + + + + {headers.map(x => ( + + ))} + + + + {confusionMatrix.map((x, idx) => { + console.log(x, idx); + return ( + + {idx === 0 ? ( + + ) : ( + <> + )} + + {x.map(y => ( + + ))} + + ); + })} + {/* + + + + */} + + {/* + + + + Accuracy: + {acc.toFixed(3)}% + + */} +
+ Actual +
{x}
+ Predict + {headers[idx]}{y}
+ Predict + millimetres (mm)25.4
+ ); +} + +export default TableComponent; diff --git a/src/components/ConfusionMatrix/index.jsx b/src/components/ConfusionMatrix/index.jsx new file mode 100644 index 0000000..1d3ba73 --- /dev/null +++ b/src/components/ConfusionMatrix/index.jsx @@ -0,0 +1,123 @@ +import React, { useEffect, useState } from 'react'; +import { + Button, + Textarea, + Box, + ButtonGroup, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalCloseButton, + ModalBody, + ModalFooter, + useDisclosure, +} from '@chakra-ui/react'; +import { predict } from '../../utils/predict'; +import TableComponent from './Table'; + +/** + * @param {Object} props + * @param {Object} props.tree + * @param {Function} props.onChange + * @param {Object[]} props.data + * @param {string[]} props.allClasses + * @param {string} props.categoryAttr + * @param {boolean} props.disabled + */ +function ConfusionMatrix({ tree, onChange, data, allClasses, categoryAttr, disabled }) { + const { isOpen, onOpen, onClose } = useDisclosure(); + const [confusionMatrix, setConfusionMatrix] = useState([]); + const [accuracy, setAccuracy] = useState(0); + + function handleAccuracy(confusionMatrix) { + if (confusionMatrix.length > 0) { + let allTargets = 0; + let goodTargets = 0; + for (let i = 0; i < confusionMatrix[0].length; i++) { + for (let j = 0; j < confusionMatrix[0].length; j++) { + if (i === j) { + goodTargets += confusionMatrix[i][j]; + } + allTargets += confusionMatrix[i][j]; + } + } + let acc = (goodTargets / allTargets) * 100; + console.log(acc); + return acc; + } + return 0; + } + + useEffect(() => { + //console.log(allClasses); + let CM = buildArray(allClasses.length); + // console.log(CM); + // console.log(categoryAttr); + // console.log(tree); + // console.log(data); + + if (!disabled && tree !== null) { + let prediction, clazz; + for (let index = 0; index < data.length; index++) { + prediction = predict(tree, data[index]); + clazz = data[index][categoryAttr]; + //console.log(prediction, clazz); + + if (prediction === clazz) { + CM[allClasses.indexOf(clazz)][allClasses.indexOf(clazz)]++; + } else { + CM[allClasses.indexOf(clazz)][allClasses.indexOf(prediction)]++; + } + } + setConfusionMatrix(CM); + let tmpAccuracy = handleAccuracy(CM); + setAccuracy(tmpAccuracy); + onChange(tmpAccuracy); + //console.log(CM); + } + }, [tree, disabled, allClasses, categoryAttr, data]); + + function buildArray(lenght) { + let arr = []; + for (var x = 0; x < lenght; x++) { + arr[x] = []; + for (var y = 0; y < lenght; y++) { + arr[x][y] = 0; + } + } + + return arr; + } + + return ( + <> + + + + + + Confusion Matrix + + + + + + + + + + + ); +} + +export default ConfusionMatrix; diff --git a/src/components/Node/DataViewer/index.jsx b/src/components/Node/DataViewer/index.jsx index d5cd530..95377a3 100644 --- a/src/components/Node/DataViewer/index.jsx +++ b/src/components/Node/DataViewer/index.jsx @@ -24,6 +24,7 @@ function DataViewer({ node, side, onChange, hide }) { const finalRef = React.useRef(); const scrollBehavior = 'inside'; const sideSubTitle = side ? 'Matched' : 'Not Matched'; + const nodeSize = node.nodeSet ? node.nodeSet.length : node.trainingSet2.length; return ( @@ -48,17 +49,9 @@ function DataViewer({ node, side, onChange, hide }) { diff --git a/src/components/Tree.jsx b/src/components/Tree.jsx index fa2b91e..3fd0459 100644 --- a/src/components/Tree.jsx +++ b/src/components/Tree.jsx @@ -17,6 +17,7 @@ import { useLoadingContext } from '../contexts/LoadingContext'; import { executeAlgorithm } from '../utils/algorithm-executor'; import TestSetFileReader from './TestSetFileReader'; import Predicter from './Predicter'; +import ConfusionMatrix from './ConfusionMatrix'; /** * @typedef {import('../utils/decision-tree.js').DecisionTreeBuilder} DecisionTreeBuilder @@ -68,7 +69,7 @@ const Tree = ({ options }) => { useEffect(() => { logTree(root); - setAccuracy(Math.random() * 10); + //setAccuracy(Math.random() * 10); }, [root]); const requestChildChange = newRoot => setRoot(newRoot); @@ -93,7 +94,7 @@ const Tree = ({ options }) => { color="#black" borderRadius="0.375rem" /> - + { - + */} +