Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneFactorClassificationModel.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneFactorClassificationModel.cs	(revision 14826)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneFactorClassificationModel.cs	(revision 14826)
@@ -0,0 +1,103 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab 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.
+ *
+ * HeuristicLab 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 HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using HeuristicLab.Common;
+using HeuristicLab.Core;
+using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+using HeuristicLab.Problems.DataAnalysis;
+
+namespace HeuristicLab.Algorithms.DataAnalysis {
+  [StorableClass]
+  [Item("OneFactor Classification Model", "A model that uses only one categorical feature (factor) to determine the class.")]
+  public sealed class OneFactorClassificationModel : ClassificationModel {
+    public override IEnumerable<string> VariablesUsedForPrediction {
+      get { return new[] { Variable }; }
+    }
+
+    [Storable]
+    private string variable;
+    public string Variable {
+      get { return variable; }
+    }
+
+    [Storable]
+    private string[] variableValues;
+    public string[] VariableValues {
+      get { return variableValues; }
+    }
+
+    [Storable]
+    private double[] classes;
+    public double[] Classes {
+      get { return classes; }
+    }
+
+    [Storable]
+    private double defaultClass;
+    public double DefaultClass {
+      get { return defaultClass; }
+    }
+
+    [StorableConstructor]
+    private OneFactorClassificationModel(bool deserializing) : base(deserializing) { }
+    private OneFactorClassificationModel(OneFactorClassificationModel original, Cloner cloner)
+      : base(original, cloner) {
+      this.variable = (string)original.variable;
+      this.variableValues = (string[])original.variableValues.Clone();
+      this.classes = (double[])original.classes.Clone();
+      this.defaultClass = original.defaultClass;
+    }
+    public override IDeepCloneable Clone(Cloner cloner) { return new OneFactorClassificationModel(this, cloner); }
+
+    public OneFactorClassificationModel(string targetVariable, string variable, string[] variableValues, double[] classes, double defaultClass = double.NaN)
+      : base(targetVariable) {
+      if (variableValues.Length != classes.Length) {
+        throw new ArgumentException("Number of variable values and classes has to be equal.");
+      }
+      this.name = ItemName;
+      this.description = ItemDescription;
+      this.variable = variable;
+      this.variableValues = variableValues;
+      this.classes = classes;
+      this.defaultClass = double.IsNaN(defaultClass) ? classes.First() : defaultClass;
+      Array.Sort(variableValues, classes);
+    }
+
+    public override IEnumerable<double> GetEstimatedClassValues(IDataset dataset, IEnumerable<int> rows) {
+      return dataset.GetStringValues(Variable, rows)
+        .Select(GetPredictedValueForInput);
+    }
+
+    private double GetPredictedValueForInput(string val) {
+      var matchingIdx = Array.BinarySearch(variableValues, val);
+      if (matchingIdx >= 0) return classes[matchingIdx];
+      else return DefaultClass;
+    }
+
+    public override IClassificationSolution CreateClassificationSolution(IClassificationProblemData problemData) {
+      return new OneFactorClassificationSolution(this, new ClassificationProblemData(problemData));
+    }
+
+  }
+}
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneFactorClassificationSolution.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneFactorClassificationSolution.cs	(revision 14826)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneFactorClassificationSolution.cs	(revision 14826)
@@ -0,0 +1,48 @@
+﻿#region License Information
+/* HeuristicLab
+ * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
+ *
+ * This file is part of HeuristicLab.
+ *
+ * HeuristicLab 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.
+ *
+ * HeuristicLab 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 HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
+ */
+#endregion
+
+using HeuristicLab.Common;
+using HeuristicLab.Core;
+using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
+using HeuristicLab.Problems.DataAnalysis;
+
+namespace HeuristicLab.Algorithms.DataAnalysis {
+  [StorableClass]
+  [Item(Name = "OneR Classification Solution", Description = "Represents a OneR classification solution which uses only a single factor for class prediction.")]
+  public sealed class OneFactorClassificationSolution : ClassificationSolution {
+    public new OneFactorClassificationModel Model {
+      get { return (OneFactorClassificationModel)base.Model; }
+      set { base.Model = value; }
+    }
+
+    [StorableConstructor]
+    private OneFactorClassificationSolution(bool deserializing) : base(deserializing) { }
+    private OneFactorClassificationSolution(OneFactorClassificationSolution original, Cloner cloner) : base(original, cloner) { }
+    public OneFactorClassificationSolution(OneFactorClassificationModel model, IClassificationProblemData problemData)
+      : base(model, problemData) {
+      RecalculateResults();
+    }
+
+    public override IDeepCloneable Clone(Cloner cloner) {
+      return new OneFactorClassificationSolution(this, cloner);
+    }
+  }
+}
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneR.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneR.cs	(revision 14523)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneR.cs	(revision 14826)
@@ -20,4 +20,5 @@
 #endregion
 
+using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -65,4 +66,27 @@
 
     public static IClassificationSolution CreateOneRSolution(IClassificationProblemData problemData, int minBucketSize = 6) {
+      var classValues = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TrainingIndices);
+      var model1 = FindBestDoubleVariableModel(problemData, minBucketSize);
+      var model2 = FindBestFactorModel(problemData);
+
+      if (model1 == null && model2 == null) throw new InvalidProgramException("Could not create OneR solution");
+      else if (model1 == null) return new OneFactorClassificationSolution(model2, (IClassificationProblemData)problemData.Clone());
+      else if (model2 == null) return new OneRClassificationSolution(model1, (IClassificationProblemData)problemData.Clone());
+      else {
+        var model1EstimatedValues = model1.GetEstimatedClassValues(problemData.Dataset, problemData.TrainingIndices);
+        var model1NumCorrect = classValues.Zip(model1EstimatedValues, (a, b) => a.IsAlmost(b)).Count(e => e);
+
+        var model2EstimatedValues = model2.GetEstimatedClassValues(problemData.Dataset, problemData.TrainingIndices);
+        var model2NumCorrect = classValues.Zip(model2EstimatedValues, (a, b) => a.IsAlmost(b)).Count(e => e);
+
+        if (model1NumCorrect > model2NumCorrect) {
+          return new OneRClassificationSolution(model1, (IClassificationProblemData)problemData.Clone());
+        } else {
+          return new OneFactorClassificationSolution(model2, (IClassificationProblemData)problemData.Clone());
+        }
+      }
+    }
+
+    private static OneRClassificationModel FindBestDoubleVariableModel(IClassificationProblemData problemData, int minBucketSize = 6) {
       var bestClassified = 0;
       List<Split> bestSplits = null;
@@ -71,9 +95,17 @@
       var classValues = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TrainingIndices);
 
-      foreach (var variable in problemData.AllowedInputVariables) {
+      var allowedInputVariables = problemData.AllowedInputVariables.Where(problemData.Dataset.VariableHasType<double>);
+
+      if (!allowedInputVariables.Any()) return null;
+
+      foreach (var variable in allowedInputVariables) {
         var inputValues = problemData.Dataset.GetDoubleValues(variable, problemData.TrainingIndices);
         var samples = inputValues.Zip(classValues, (i, v) => new Sample(i, v)).OrderBy(s => s.inputValue);
 
-        var missingValuesDistribution = samples.Where(s => double.IsNaN(s.inputValue)).GroupBy(s => s.classValue).ToDictionary(s => s.Key, s => s.Count()).MaxItems(s => s.Value).FirstOrDefault();
+        var missingValuesDistribution = samples
+          .Where(s => double.IsNaN(s.inputValue)).GroupBy(s => s.classValue)
+          .ToDictionary(s => s.Key, s => s.Count())
+          .MaxItems(s => s.Value)
+          .FirstOrDefault();
 
         //calculate class distributions for all distinct inputValues
@@ -120,5 +152,5 @@
           while (sample.inputValue >= splits[splitIndex].thresholdValue)
             splitIndex++;
-          correctClassified += sample.classValue == splits[splitIndex].classValue ? 1 : 0;
+          correctClassified += sample.classValue.IsAlmost(splits[splitIndex].classValue) ? 1 : 0;
         }
         correctClassified += missingValuesDistribution.Value;
@@ -134,5 +166,5 @@
       //remove neighboring splits with the same class value
       for (int i = 0; i < bestSplits.Count - 1; i++) {
-        if (bestSplits[i].classValue == bestSplits[i + 1].classValue) {
+        if (bestSplits[i].classValue.IsAlmost(bestSplits[i + 1].classValue)) {
           bestSplits.Remove(bestSplits[i]);
           i--;
@@ -140,8 +172,44 @@
       }
 
-      var model = new OneRClassificationModel(problemData.TargetVariable, bestVariable, bestSplits.Select(s => s.thresholdValue).ToArray(), bestSplits.Select(s => s.classValue).ToArray(), bestMissingValuesClass);
-      var solution = new OneRClassificationSolution(model, (IClassificationProblemData)problemData.Clone());
-
-      return solution;
+      var model = new OneRClassificationModel(problemData.TargetVariable, bestVariable,
+        bestSplits.Select(s => s.thresholdValue).ToArray(),
+        bestSplits.Select(s => s.classValue).ToArray(), bestMissingValuesClass);
+
+      return model;
+    }
+    private static OneFactorClassificationModel FindBestFactorModel(IClassificationProblemData problemData) {
+      var classValues = problemData.Dataset.GetDoubleValues(problemData.TargetVariable, problemData.TrainingIndices);
+      var defaultClass = FindMostFrequentClassValue(classValues);
+      // only select string variables
+      var allowedInputVariables = problemData.AllowedInputVariables.Where(problemData.Dataset.VariableHasType<string>);
+
+      if (!allowedInputVariables.Any()) return null;
+
+      OneFactorClassificationModel bestModel = null;
+      var bestModelNumCorrect = 0;
+
+      foreach (var variable in allowedInputVariables) {
+        var variableValues = problemData.Dataset.GetStringValues(variable, problemData.TrainingIndices);
+        var groupedClassValues = variableValues
+          .Zip(classValues, (v, c) => new KeyValuePair<string, double>(v, c))
+          .GroupBy(kvp => kvp.Key)
+          .ToDictionary(g => g.Key, g => FindMostFrequentClassValue(g.Select(kvp => kvp.Value)));
+
+        var model = new OneFactorClassificationModel(problemData.TargetVariable, variable,
+          groupedClassValues.Select(kvp => kvp.Key).ToArray(), groupedClassValues.Select(kvp => kvp.Value).ToArray(), defaultClass);
+
+        var modelEstimatedValues = model.GetEstimatedClassValues(problemData.Dataset, problemData.TrainingIndices);
+        var modelNumCorrect = classValues.Zip(modelEstimatedValues, (a, b) => a.IsAlmost(b)).Count(e => e);
+        if (modelNumCorrect > bestModelNumCorrect) {
+          bestModelNumCorrect = modelNumCorrect;
+          bestModel = model;
+        }
+      }
+
+      return bestModel;
+    }
+
+    private static double FindMostFrequentClassValue(IEnumerable<double> classValues) {
+      return classValues.GroupBy(c => c).OrderByDescending(g => g.Count()).Select(g => g.Key).First();
     }
 
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneRClassificationModel.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneRClassificationModel.cs	(revision 14523)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneRClassificationModel.cs	(revision 14826)
@@ -31,5 +31,5 @@
   [StorableClass]
   [Item("OneR Classification Model", "A model that uses intervals for one variable to determine the class.")]
-  public class OneRClassificationModel : ClassificationModel {
+  public sealed class OneRClassificationModel : ClassificationModel {
     public override IEnumerable<string> VariablesUsedForPrediction {
       get { return new[] { Variable }; }
@@ -37,5 +37,5 @@
 
     [Storable]
-    protected string variable;
+    private string variable;
     public string Variable {
       get { return variable; }
@@ -43,5 +43,5 @@
 
     [Storable]
-    protected double[] splits;
+    private double[] splits;
     public double[] Splits {
       get { return splits; }
@@ -49,5 +49,5 @@
 
     [Storable]
-    protected double[] classes;
+    private double[] classes;
     public double[] Classes {
       get { return classes; }
@@ -55,5 +55,5 @@
 
     [Storable]
-    protected double missingValuesClass;
+    private double missingValuesClass;
     public double MissingValuesClass {
       get { return missingValuesClass; }
@@ -61,10 +61,11 @@
 
     [StorableConstructor]
-    protected OneRClassificationModel(bool deserializing) : base(deserializing) { }
-    protected OneRClassificationModel(OneRClassificationModel original, Cloner cloner)
+    private OneRClassificationModel(bool deserializing) : base(deserializing) { }
+    private OneRClassificationModel(OneRClassificationModel original, Cloner cloner)
       : base(original, cloner) {
       this.variable = (string)original.variable;
       this.splits = (double[])original.splits.Clone();
       this.classes = (double[])original.classes.Clone();
+      this.missingValuesClass = original.missingValuesClass;
     }
     public override IDeepCloneable Clone(Cloner cloner) { return new OneRClassificationModel(this, cloner); }
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneRClassificationSolution.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneRClassificationSolution.cs	(revision 14523)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/BaselineClassifiers/OneRClassificationSolution.cs	(revision 14826)
@@ -28,5 +28,5 @@
   [StorableClass]
   [Item(Name = "OneR Classification Solution", Description = "Represents a OneR classification solution which uses only a single feature with potentially multiple thresholds for class prediction.")]
-  public class OneRClassificationSolution : ClassificationSolution {
+  public sealed class OneRClassificationSolution : ClassificationSolution {
     public new OneRClassificationModel Model {
       get { return (OneRClassificationModel)base.Model; }
@@ -35,6 +35,6 @@
 
     [StorableConstructor]
-    protected OneRClassificationSolution(bool deserializing) : base(deserializing) { }
-    protected OneRClassificationSolution(OneRClassificationSolution original, Cloner cloner) : base(original, cloner) { }
+    private OneRClassificationSolution(bool deserializing) : base(deserializing) { }
+    private OneRClassificationSolution(OneRClassificationSolution original, Cloner cloner) : base(original, cloner) { }
     public OneRClassificationSolution(OneRClassificationModel model, IClassificationProblemData problemData)
       : base(model, problemData) {
