Commit f06b67c3 authored by Bruno López Trigo's avatar Bruno López Trigo

Solucionados bugs en interface gráfica e engadido soporte touch para árbore

parent 8c835958
......@@ -5,6 +5,7 @@ import brunolopez.expliclas.exceptions.FormatEx;
import brunolopez.expliclas.exceptions.NotAllowedEx;
import brunolopez.expliclas.exceptions.NotFoundEx;
import brunolopez.expliclas.models.Attribute;
import brunolopez.expliclas.models.CategoricAttribute;
import brunolopez.expliclas.models.Classification;
import brunolopez.expliclas.models.Consequent;
import brunolopez.expliclas.models.DatasetConfig;
......@@ -25,6 +26,7 @@ public interface ClassifierManager {
public Property getPropertyConfig(String token, String dataset, String attribute, int property, String lang) throws NotFoundEx, IOException;
public Property removePropertyConfig(String token, String dataset, String attribute, int property, String lang) throws NotFoundEx, IOException, NotAllowedEx, FormatEx;
public NumericAttribute updateAttributeConfig(String token, String dataset, String attribute, NumericAttribute attributeConfig, String lang) throws NotFoundEx, IOException, FormatEx;
public CategoricAttribute updateCategoricAttributeConfig(String token, String dataset, String attribute, CategoricAttribute attributeConfig, String lang) throws NotFoundEx, IOException, FormatEx;
public NumericAttribute updateAttributeConfig(String token, String dataset, String attribute, NumericProperty property, String lang) throws NotFoundEx, IOException, FormatEx;
public VisualNode buildTree(String token, String dataset, String algorithm) throws NotFoundEx, FormatEx, ConflictEx, IOException;
public VisualNode getTree(String token, String dataset, String algorithm, String lang) throws NotFoundEx, IOException;
......
......@@ -6,6 +6,8 @@ import brunolopez.expliclas.exceptions.FormatEx;
import brunolopez.expliclas.exceptions.NotAllowedEx;
import brunolopez.expliclas.exceptions.NotFoundEx;
import brunolopez.expliclas.models.Attribute;
import brunolopez.expliclas.models.CategoricAttribute;
import brunolopez.expliclas.models.CategoricProperty;
import brunolopez.expliclas.models.Classification;
import brunolopez.expliclas.models.NumericAttribute;
import brunolopez.expliclas.models.Consequent;
......@@ -373,6 +375,117 @@ public class ClassifierManagerImpl implements ClassifierManager {
return attributeConfig;
}
@Override
public CategoricAttribute updateCategoricAttributeConfig(String token, String dataset, String attribute, CategoricAttribute attributeConfig, String lang) throws NotFoundEx, IOException, FormatEx {
File configFileEn, configFileEs, configFileGl,
treeFileJ48 = null, treeFileREPTree = null, treeFileRandomTree = null,
logJ48 = null, logREPTree = null, logRandomTree = null;
DatasetConfig configEn, configEs, configGl;
CategoricAttribute attEn, attEs, attGl;
try {
treeFileJ48 = this.fmanager.getTree(token, dataset, "J48");
logJ48 = this.fmanager.getLog(token, dataset, "J48");
treeFileREPTree = this.fmanager.getTree(token, dataset, "REPTree");
logREPTree = this.fmanager.getLog(token, dataset, "REPTree");
treeFileRandomTree = this.fmanager.getTree(token, dataset, "RandomTree");
logRandomTree = this.fmanager.getLog(token, dataset, "RandomTree");
} catch (NotFoundEx ex) {
}
configFileEn = this.fmanager.getConfig(token, dataset, "en");
configFileEs = this.fmanager.getConfig(token, dataset, "es");
configFileGl = this.fmanager.getConfig(token, dataset, "gl");
configEn = this.mapper.readConfigJSON(configFileEn);
configEs = this.mapper.readConfigJSON(configFileEs);
configGl = this.mapper.readConfigJSON(configFileGl);
attEn = (CategoricAttribute) configEn.getAttributeById(attribute);
attEs = (CategoricAttribute) configEs.getAttributeById(attribute);
attGl = (CategoricAttribute) configGl.getAttributeById(attribute);
switch (lang) {
case "en":
if(attEn.getProperties().size() != attributeConfig.getProperties().size())
throw new FormatEx("Properties size doesn't match");
boolean found = false;
for(Property p: attEn.getProperties()){
for(Property prop: attributeConfig.getProperties()){
if(p.getName().equals(prop.getName())){
found = true;
((CategoricProperty) p).setValue(((CategoricProperty) prop).getValue());
}
}
if(!found){
throw new FormatEx("Property " + p.getName() + " not found");
}
found = false;
}
this.mapper.writeJSON(configEn, configFileEn);
break;
case "es":
if(attEs.getProperties().size() != attributeConfig.getProperties().size())
throw new FormatEx("Properties size doesn't match");
found = false;
for(Property p: attEs.getProperties()){
for(Property prop: attributeConfig.getProperties()){
if(p.getName().equals(prop.getName())){
found = true;
((CategoricProperty) p).setValue(((CategoricProperty) prop).getValue());
}
}
if(!found){
throw new FormatEx("Property " + p.getName() + " not found");
}
found = false;
}
this.mapper.writeJSON(configEs, configFileEs);
break;
case "gl":
if(attGl.getProperties().size() != attributeConfig.getProperties().size())
throw new FormatEx("Properties size doesn't match");
found = false;
for(Property p: attGl.getProperties()){
for(Property prop: attributeConfig.getProperties()){
if(p.getName().equals(prop.getName())){
found = true;
((CategoricProperty) p).setValue(((CategoricProperty) prop).getValue());
}
}
if(!found){
throw new FormatEx("Property " + p.getName() + " not found");
}
found = false;
}
this.mapper.writeJSON(configGl, configFileGl);
break;
default:
throw new FormatEx("Invalid language " + lang + " provided");
}
try {
configFileEn = this.fmanager.getConfig(token, dataset, "en");
if (logJ48 != null && treeFileJ48 != null) {
this.treeBuilder = new TreeBuilder(logJ48, configFileEn, treeFileJ48);
this.treeBuilder.buildTree(true);
}
if (logREPTree != null && treeFileREPTree != null) {
this.treeBuilder = new TreeBuilder(logREPTree, configFileEn, treeFileREPTree);
this.treeBuilder.buildTree(true);
}
if (logREPTree != null && treeFileREPTree != null) {
this.treeBuilder = new TreeBuilder(logRandomTree, configFileEn, treeFileRandomTree);
this.treeBuilder.buildTree(true);
}
} catch (ConflictEx ex) {
throw new IOException();
}
return attributeConfig;
}
@Override
public NumericAttribute updateAttributeConfig(String token, String dataset, String attribute, NumericProperty property, String lang) throws NotFoundEx, IOException, FormatEx {
......
......@@ -8,6 +8,7 @@ import brunolopez.expliclas.exceptions.NotAllowedEx;
import brunolopez.expliclas.exceptions.NotFoundEx;
import brunolopez.expliclas.filters.TokenNeeded;
import brunolopez.expliclas.models.Attribute;
import brunolopez.expliclas.models.CategoricAttribute;
import brunolopez.expliclas.models.Classification;
import brunolopez.expliclas.models.Consequent;
import brunolopez.expliclas.models.DatasetConfig;
......@@ -459,7 +460,7 @@ public class ClassifierService {
public Response updateClassifierConfigProperty(@Context HttpHeaders httpheaders,
@Parameter(description = "Dataset name")
@PathParam("dataset") String dataset,
@Parameter(description = "Attribute config")
@Parameter(description = "Attribute name")
@PathParam("attribute") String attribute,
@Parameter(description = "Attribute config")
NumericAttribute attributeConfig,
......@@ -484,6 +485,82 @@ public class ClassifierService {
}
@Operation(
summary = "Update attribute categoric config",
description = "Update categoric attribute config by modifying names",
responses = {
@ApiResponse(
responseCode = "200",
description = "Attribute config successfuly update",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = NumericAttribute.class))
),
@ApiResponse(
responseCode = "404",
description = "Config couldn't be found",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = SimpleMessage.class))
),
@ApiResponse(
responseCode = "500",
description = "Error updating config",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = SimpleMessage.class))
),
@ApiResponse(
responseCode = "400",
description = "Bad request format",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = SimpleMessage.class))
),
@ApiResponse(
responseCode = "401",
description = "Unhauthorized",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = SimpleMessage.class))
)
},
tags = "classifier"
)
@SecurityRequirement(name = "token")
@PUT
@Path("/config/{dataset}/catattribute/{attribute}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@TokenNeeded
public Response updateClassifierCategoricConfigProperty(@Context HttpHeaders httpheaders,
@Parameter(description = "Dataset name")
@PathParam("dataset") String dataset,
@Parameter(description = "Attribute name")
@PathParam("attribute") String attribute,
@Parameter(description = "Attribute config")
CategoricAttribute attributeConfig,
@Parameter(description = "Configuration language {en: english, es: spanish, gl: galician}")
@DefaultValue("en") @QueryParam("lang") String lang) {
String header = httpheaders.getHeaderString(HttpHeaders.AUTHORIZATION);
String token = header.substring("Bearer".length()).trim();
CategoricAttribute attConfig;
try {
attConfig = this.manager.updateCategoricAttributeConfig(token, dataset, attribute, attributeConfig, lang);
return Response.status(Response.Status.OK).entity(attConfig).build();
} catch (NotFoundEx ex) {
return Response.status(Response.Status.NOT_FOUND).entity(new SimpleMessage(ex.getMessage())).build();
} catch (IOException ex) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(new SimpleMessage("Error reading config")).build();
} catch (FormatEx ex) {
return Response.status(Response.Status.BAD_REQUEST).entity(new SimpleMessage(ex.getMessage())).build();
}
}
@Operation(
summary = "Add new property to numeric attribute",
description = "Add new property to numeric attribute at the end of properties list",
......
=== Run information ===
Scheme: trees.J48 -C 0.25 -M 2
Relation: bank
Instances: 300
Attributes: 9
age
sex
region
income
married
children
car
mortgage
pep
Test mode: 10-fold cross-validation
=== Classifier model (full training set) ===
J48 pruned tree
------------------
children = YES
| income <= 30099.3
| | car = YES: NO (50.0/15.0)
| | car = NO
| | | married = YES
| | | | income <= 13106.6: NO (9.0/2.0)
| | | | income > 13106.6
| | | | | mortgage = YES: YES (12.0/3.0)
| | | | | mortgage = NO
| | | | | | income <= 18923: YES (9.0/3.0)
| | | | | | income > 18923: NO (10.0/3.0)
| | | married = NO: NO (22.0/6.0)
| income > 30099.3: YES (59.0/7.0)
children = NO
| married = YES
| | mortgage = YES
| | | region = INNER_CITY
| | | | income <= 39547.8: YES (12.0/3.0)
| | | | income > 39547.8: NO (4.0)
| | | region = RURAL: NO (3.0/1.0)
| | | region = TOWN: NO (9.0/2.0)
| | | region = SUBURBAN: NO (4.0/1.0)
| | mortgage = NO: NO (57.0/9.0)
| married = NO
| | mortgage = YES
| | | age <= 39
| | | | age <= 28: NO (4.0)
| | | | age > 28: YES (5.0/1.0)
| | | age > 39: NO (11.0)
| | mortgage = NO: YES (20.0/1.0)
Number of Leaves : 17
Size of the tree : 31
=== Summary ===
Correctly Classified Instances 206 68.6667 %
Incorrectly Classified Instances 94 31.3333 %
Kappa statistic 0.3576
K&B Relative Info Score 7800.3086 %
K&B Information Score 77.6986 bits 0.259 bits/instance
Class complexity | order 0 298.6496 bits 0.9955 bits/instance
Class complexity | scheme 17410.281 bits 58.0343 bits/instance
Complexity improvement (Sf) -17111.6313 bits -57.0388 bits/instance
Mean absolute error 0.379
Root mean squared error 0.4816
Relative absolute error 76.2791 %
Root relative squared error 96.6145 %
Total Number of Instances 300
=== Confusion Matrix ===
a b <-- classified as
74 64 | a = YES
30 132 | b = NO
{
"dataset" : "example",
"attributes" : [ {
"type" : "numericAtt",
"id" : "age",
"name" : "age",
"properties" : [ {
"type" : "numericProp",
"name" : "Low",
"id" : 0,
"interval" : {
"left" : 18.0,
"right" : 34.33333333333333
}
}, {
"type" : "numericProp",
"name" : "Medium",
"id" : 1,
"interval" : {
"left" : 34.33333333333333,
"right" : 50.666666666666664
}
}, {
"type" : "numericProp",
"name" : "High",
"id" : 2,
"interval" : {
"left" : 50.666666666666664,
"right" : 67.0
}
} ],
"interval" : {
"left" : 18.0,
"right" : 67.0
},
"value" : 0.0
}, {
"type" : "categoricAtt",
"id" : "sex",
"name" : "sex",
"properties" : [ {
"type" : "categoricProp",
"name" : "MALE",
"value" : "MALE"
}, {
"type" : "categoricProp",
"name" : "FEMALE",
"value" : "FEMALE"
} ]
}, {
"type" : "categoricAtt",
"id" : "region",
"name" : "region",
"properties" : [ {
"type" : "categoricProp",
"name" : "INNER_CITY",
"value" : "INNER_CITY"
}, {
"type" : "categoricProp",
"name" : "RURAL",
"value" : "RURAL"
}, {
"type" : "categoricProp",
"name" : "TOWN",
"value" : "TOWN"
}, {
"type" : "categoricProp",
"name" : "SUBURBAN",
"value" : "SUBURBAN"
} ]
}, {
"type" : "numericAtt",
"id" : "income",
"name" : "income",
"properties" : [ {
"type" : "numericProp",
"name" : "Low",
"id" : 0,
"interval" : {
"left" : 5014.21,
"right" : 24386.173333333332
}
}, {
"type" : "numericProp",
"name" : "Medium",
"id" : 1,
"interval" : {
"left" : 24386.173333333332,
"right" : 43758.136666666665
}
}, {
"type" : "numericProp",
"name" : "High",
"id" : 2,
"interval" : {
"left" : 43758.136666666665,
"right" : 63130.1
}
} ],
"interval" : {
"left" : 5014.21,
"right" : 63130.1
},
"value" : 0.0
}, {
"type" : "categoricAtt",
"id" : "married",
"name" : "married",
"properties" : [ {
"type" : "categoricProp",
"name" : "YES",
"value" : "YES"
}, {
"type" : "categoricProp",
"name" : "NO",
"value" : "NO"
} ]
}, {
"type" : "categoricAtt",
"id" : "children",
"name" : "children",
"properties" : [ {
"type" : "categoricProp",
"name" : "YES",
"value" : "YES"
}, {
"type" : "categoricProp",
"name" : "NO",
"value" : "NO"
} ]
}, {
"type" : "categoricAtt",
"id" : "car",
"name" : "car",
"properties" : [ {
"type" : "categoricProp",
"name" : "YES",
"value" : "YES"
}, {
"type" : "categoricProp",
"name" : "NO",
"value" : "NO"
} ]
}, {
"type" : "categoricAtt",
"id" : "mortgage",
"name" : "mortgage",
"properties" : [ {
"type" : "categoricProp",
"name" : "YES",
"value" : "YES"
}, {
"type" : "categoricProp",
"name" : "NO",
"value" : "NO"
} ]
} ],
"consequents" : [ {
"matrixPosition" : 1,
"id" : "YES",
"name" : "YES",
"percentageNode" : 0.0
}, {
"matrixPosition" : 2,
"id" : "NO",
"name" : "NO",
"percentageNode" : 0.0
} ]
}
\ No newline at end of file
{
"dataset" : "example",
"attributes" : [ {
"type" : "numericAtt",
"id" : "age",
"name" : "age",
"properties" : [ {
"type" : "numericProp",
"name" : "Low",
"id" : 0,
"interval" : {
"left" : 18.0,