create confusion matrix with actual accuracy

This commit is contained in:
Hubert Sokołowski
2021-05-08 15:11:08 +02:00
parent fa258a6b30
commit 24871bfc8e
5 changed files with 244 additions and 26 deletions

View File

@@ -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 (
<Table size="sm" overflowX="auto" d="block">
<Thead>
<Tr>
<Th colSpan={confusionMatrix.length + 2} textAlign="center">
Actual
</Th>
</Tr>
<Tr>
<Th></Th>
<Th></Th>
{headers.map(x => (
<Th>{x}</Th>
))}
</Tr>
</Thead>
<Tbody>
{confusionMatrix.map((x, idx) => {
console.log(x, idx);
return (
<Tr>
{idx === 0 ? (
<Th
rowSpan={confusionMatrix.length}
style={{
writingMode: 'vertical-rl',
textOrientation: 'mixed',
transform: 'rotate(-90deg)',
//padding: '2px',
}}
>
Predict
</Th>
) : (
<></>
)}
<Th>{headers[idx]}</Th>
{x.map(y => (
<Td>{y}</Td>
))}
</Tr>
);
})}
{/* <Tr>
<Td
rowSpan={confusionMatrix.length}
style={{
writingMode: 'vertical-rl',
textOrientation: 'revert',
}}
>
Predict
</Td>
<Td>millimetres (mm)</Td>
<Td>25.4</Td>
</Tr> */}
</Tbody>
{/* <Tfoot mt={2}>
<Tr fontSize={20}>
<Th></Th>
<Th></Th>
<Tr>Accuracy:</Tr>
<Tr>{acc.toFixed(3)}%</Tr>
</Tr>
</Tfoot> */}
</Table>
);
}
export default TableComponent;

View File

@@ -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 (
<>
<Button
bg={'#ddd'}
color="#black"
_hover={{ bg: '#aaa' }}
onClick={onOpen}
size="sm"
disabled={disabled}
>
Confusion Matrix
</Button>
<Modal onClose={onClose} size={'xl'} isOpen={isOpen} blockScrollOnMount={false}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Confusion Matrix</ModalHeader>
<ModalCloseButton />
<ModalBody p={5} display="flex" justifyContent="center">
<TableComponent headers={allClasses} confusionMatrix={confusionMatrix} />
</ModalBody>
<ModalFooter>
<Button>Accuracy: {accuracy.toFixed(3)}%</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
}
export default ConfusionMatrix;

View File

@@ -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 (
<Box>
@@ -48,17 +49,9 @@ function DataViewer({ node, side, onChange, hide }) {
<Tooltip hasArrow label="View data in node" bg="purple.600" placement="right">
<Button
mt={1}
// px={2}
// py={3}
// h={26}
colorScheme="facebook"
border={'1px solid #333'}
// bg={'#ddd'}
// color="black"
// _hover={{
// bg: '#333',
// color: 'white',
// }}
disabled={nodeSize ? false : true}
onClick={onOpen}
leftIcon={<CgDatabase />}
variant="outline"
@@ -66,7 +59,7 @@ function DataViewer({ node, side, onChange, hide }) {
fontSize="14px"
fontWeight="semibold"
>
{sideSubTitle} ({node.nodeSet ? node.nodeSet.length : node.trainingSet2.length})
{sideSubTitle} ({nodeSize})
</Button>
</Tooltip>
</ButtonGroup>

View File

@@ -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"
/>
<Input value={accuracy} readOnly textAlign="center" />
<Input value={accuracy.toFixed(3)} readOnly textAlign="center" />
<InputRightAddon
children="%"
fontWeight={700}
@@ -104,7 +105,7 @@ const Tree = ({ options }) => {
</InputGroup>
</Box>
<Box>
<Button
{/* <Button
bg={'#ddd'}
color="#black"
_hover={{ bg: '#aaa' }}
@@ -112,7 +113,15 @@ const Tree = ({ options }) => {
size="sm"
>
Confusion Matrix
</Button>
</Button> */}
<ConfusionMatrix
tree={root}
data={options.trainingSet}
allClasses={options.allClasses}
categoryAttr={options.categoryAttr}
onChange={setAccuracy}
disabled={isLoading}
/>
</Box>
<Box>
<Button

View File

@@ -1,19 +1,18 @@
export function predict(tree, item) {
var attr1, attr2, value, predicate, pivot, match;
// Traversing tree from the root to leaf
while (true) {
if (tree.category) {
if (tree?.category) {
// only leafs contains predicted category
return tree.category;
}
console.log(
tree.predicateName,
tree.weight,
tree.predicateName === '>=',
tree.predicateName === '==',
tree.predicateName === '<'
);
// console.log(
// tree.predicateName,
// tree.weight,
// tree.predicateName === '>=',
// tree.predicateName === '==',
// tree.predicateName === '<'
// );
if (tree.weight) {
attr1 = tree.attr2;
attr2 = tree.pivot;
@@ -23,7 +22,7 @@ export function predict(tree, item) {
predicate = predicates['w'];
match = predicate(value, pivot, tree.weight);
console.log('predict - waga', match);
// console.log('predict - waga', match);
}
if (tree.predicateName === '>=' || tree.predicateName === '==') {
attr1 = tree.attr2;
@@ -32,7 +31,7 @@ export function predict(tree, item) {
predicate = predicates[tree.predicateName];
match = predicate(value, pivot);
console.log('predict - c45', match);
// console.log('predict - c45', match);
}
if (tree.predicateName === '<') {
attr1 = tree.attr2;
@@ -43,7 +42,7 @@ export function predict(tree, item) {
predicate = predicates[tree.predicateName];
match = predicate(value, pivot);
console.log('predict - tsp', match);
// console.log('predict - tsp', match);
}
// move to one of subtrees