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 14781)
+++ 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 14781)
+++ 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 14781)
+++ 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) {
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/GaussianProcess/GaussianProcessClassificationModelCreator.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/GaussianProcess/GaussianProcessClassificationModelCreator.cs	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/GaussianProcess/GaussianProcessClassificationModelCreator.cs	(revision 14826)
@@ -67,5 +67,7 @@
         HyperparameterGradientsParameter.ActualValue = new RealVector(model.HyperparameterGradients);
         return base.Apply();
-      } catch (ArgumentException) { } catch (alglib.alglibexception) { }
+      } catch (ArgumentException) {
+      } catch (alglib.alglibexception) {
+      }
       NegativeLogLikelihoodParameter.ActualValue = new DoubleValue(1E300);
       HyperparameterGradientsParameter.ActualValue = new RealVector(Hyperparameter.Count());
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/GradientBoostedTrees/GradientBoostedTreesAlgorithmStatic.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/GradientBoostedTrees/GradientBoostedTreesAlgorithmStatic.cs	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/GradientBoostedTrees/GradientBoostedTreesAlgorithmStatic.cs	(revision 14826)
@@ -148,4 +148,10 @@
     // for custom stepping & termination
     public static IGbmState CreateGbmState(IRegressionProblemData problemData, ILossFunction lossFunction, uint randSeed, int maxSize = 3, double r = 0.66, double m = 0.5, double nu = 0.01) {
+      // check input variables. Only double variables are allowed.
+      var invalidInputs =
+        problemData.AllowedInputVariables.Where(name => !problemData.Dataset.VariableHasType<double>(name));
+      if (invalidInputs.Any())
+        throw new NotSupportedException("Gradient tree boosting only supports real-valued variables. Unsupported inputs: " + string.Join(", ", invalidInputs));
+
       return new GbmState(problemData, lossFunction, randSeed, maxSize, r, m, nu);
     }
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/HeuristicLab.Algorithms.DataAnalysis-3.4.csproj
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/HeuristicLab.Algorithms.DataAnalysis-3.4.csproj	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/HeuristicLab.Algorithms.DataAnalysis-3.4.csproj	(revision 14826)
@@ -122,4 +122,6 @@
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="BaselineClassifiers\OneFactorClassificationModel.cs" />
+    <Compile Include="BaselineClassifiers\OneFactorClassificationSolution.cs" />
     <Compile Include="BaselineClassifiers\OneR.cs" />
     <Compile Include="BaselineClassifiers\OneRClassificationModel.cs" />
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/AlglibUtil.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/AlglibUtil.cs	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/AlglibUtil.cs	(revision 14826)
@@ -20,4 +20,5 @@
 #endregion
 
+using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -27,8 +28,12 @@
   public static class AlglibUtil {
     public static double[,] PrepareInputMatrix(IDataset dataset, IEnumerable<string> variables, IEnumerable<int> rows) {
-      List<string> variablesList = variables.ToList();
+      // check input variables. Only double variables are allowed.
+      var invalidInputs =
+        variables.Where(name => !dataset.VariableHasType<double>(name));
+      if (invalidInputs.Any())
+        throw new NotSupportedException("Unsupported inputs: " + string.Join(", ", invalidInputs));
+
       List<int> rowsList = rows.ToList();
-
-      double[,] matrix = new double[rowsList.Count, variablesList.Count];
+      double[,] matrix = new double[rowsList.Count, variables.Count()];
 
       int col = 0;
@@ -45,5 +50,12 @@
       return matrix;
     }
+
     public static double[,] PrepareAndScaleInputMatrix(IDataset dataset, IEnumerable<string> variables, IEnumerable<int> rows, Scaling scaling) {
+      // check input variables. Only double variables are allowed.
+      var invalidInputs =
+        variables.Where(name => !dataset.VariableHasType<double>(name));
+      if (invalidInputs.Any())
+        throw new NotSupportedException("Unsupported inputs: " + string.Join(", ", invalidInputs));
+
       List<string> variablesList = variables.ToList();
       List<int> rowsList = rows.ToList();
@@ -64,4 +76,57 @@
       return matrix;
     }
+
+    /// <summary>
+    /// Prepares a binary data matrix from a number of factors and specified factor values
+    /// </summary>
+    /// <param name="dataset">A dataset that contains the variable values</param>
+    /// <param name="factorVariables">An enumerable of categorical variables (factors). For each variable an enumerable of values must be specified.</param>
+    /// <param name="rows">An enumerable of row indices for the dataset</param>
+    /// <returns></returns>
+    /// <remarks>Factor variables (categorical variables) are split up into multiple binary variables one for each specified value.</remarks>
+    public static double[,] PrepareInputMatrix(
+      IDataset dataset,
+      IEnumerable<KeyValuePair<string, IEnumerable<string>>> factorVariables,
+      IEnumerable<int> rows) {
+      // check input variables. Only string variables are allowed.
+      var invalidInputs =
+        factorVariables.Select(kvp => kvp.Key).Where(name => !dataset.VariableHasType<string>(name));
+      if (invalidInputs.Any())
+        throw new NotSupportedException("Unsupported inputs: " + string.Join(", ", invalidInputs));
+
+      int numBinaryColumns = factorVariables.Sum(kvp => kvp.Value.Count());
+
+      List<int> rowsList = rows.ToList();
+      double[,] matrix = new double[rowsList.Count, numBinaryColumns];
+
+      int col = 0;
+      foreach (var kvp in factorVariables) {
+        var varName = kvp.Key;
+        var cats = kvp.Value;
+        if (!cats.Any()) continue;
+        foreach (var cat in cats) {
+          var values = dataset.GetStringValues(varName, rows);
+          int row = 0;
+          foreach (var value in values) {
+            matrix[row, col] = value == cat ? 1 : 0;
+            row++;
+          }
+          col++;
+        }
+      }
+      return matrix;
+    }
+
+    public static IEnumerable<KeyValuePair<string, IEnumerable<string>>> GetFactorVariableValues(IDataset ds, IEnumerable<string> factorVariables, IEnumerable<int> rows) {
+      return from factor in factorVariables
+             let distinctValues = ds.GetStringValues(factor, rows).Distinct().ToArray()
+             // 1 distinct value => skip (constant)
+             // 2 distinct values => only take one of the two values
+             // >=3 distinct values => create a binary value for each value
+             let reducedValues = distinctValues.Length <= 2
+               ? distinctValues.Take(distinctValues.Length - 1)
+               : distinctValues
+             select new KeyValuePair<string, IEnumerable<string>>(factor, reducedValues);
+    }
   }
 }
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/LinearDiscriminantAnalysis.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/LinearDiscriminantAnalysis.cs	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/LinearDiscriminantAnalysis.cs	(revision 14826)
@@ -37,5 +37,5 @@
   /// Linear discriminant analysis classification algorithm.
   /// </summary>
-  [Item("Linear Discriminant Analysis", "Linear discriminant analysis classification algorithm (wrapper for ALGLIB).")]
+  [Item("Linear Discriminant Analysis (LDA)", "Linear discriminant analysis classification algorithm (wrapper for ALGLIB).")]
   [Creatable(CreatableAttribute.Categories.DataAnalysisClassification, Priority = 100)]
   [StorableClass]
@@ -71,5 +71,13 @@
       IEnumerable<int> rows = problemData.TrainingIndices;
       int nClasses = problemData.ClassNames.Count();
-      double[,] inputMatrix = AlglibUtil.PrepareInputMatrix(dataset, allowedInputVariables.Concat(new string[] { targetVariable }), rows);
+      var doubleVariableNames = allowedInputVariables.Where(dataset.VariableHasType<double>).ToArray();
+      var factorVariableNames = allowedInputVariables.Where(dataset.VariableHasType<string>).ToArray();
+      double[,] inputMatrix = AlglibUtil.PrepareInputMatrix(dataset, doubleVariableNames.Concat(new string[] { targetVariable }), rows);
+
+      var factorVariables = AlglibUtil.GetFactorVariableValues(dataset, factorVariableNames, rows);
+      double[,] factorMatrix = AlglibUtil.PrepareInputMatrix(dataset, factorVariables, rows);
+
+      inputMatrix = factorMatrix.HorzCat(inputMatrix);
+
       if (inputMatrix.Cast<double>().Any(x => double.IsNaN(x) || double.IsInfinity(x)))
         throw new NotSupportedException("Linear discriminant analysis does not support NaN or infinity values in the input dataset.");
@@ -83,5 +91,5 @@
       int info;
       double[] w;
-      alglib.fisherlda(inputMatrix, inputMatrix.GetLength(0), allowedInputVariables.Count(), nClasses, out info, out w);
+      alglib.fisherlda(inputMatrix, inputMatrix.GetLength(0), inputMatrix.GetLength(1) - 1, nClasses, out info, out w);
       if (info < 1) throw new ArgumentException("Error in calculation of linear discriminant analysis solution");
 
@@ -93,5 +101,17 @@
 
       int col = 0;
-      foreach (string column in allowedInputVariables) {
+      foreach (var kvp in factorVariables) {
+        var varName = kvp.Key;
+        foreach (var cat in kvp.Value) {
+          BinaryFactorVariableTreeNode vNode =
+            (BinaryFactorVariableTreeNode)new HeuristicLab.Problems.DataAnalysis.Symbolic.BinaryFactorVariable().CreateTreeNode();
+          vNode.VariableName = varName;
+          vNode.VariableValue = cat;
+          vNode.Weight = w[col];
+          addition.AddSubtree(vNode);
+          col++;
+        }
+      }
+      foreach (string column in doubleVariableNames) {
         VariableTreeNode vNode = (VariableTreeNode)new HeuristicLab.Problems.DataAnalysis.Symbolic.Variable().CreateTreeNode();
         vNode.VariableName = column;
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/LinearRegression.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/LinearRegression.cs	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/LinearRegression.cs	(revision 14826)
@@ -74,5 +74,11 @@
       IEnumerable<string> allowedInputVariables = problemData.AllowedInputVariables;
       IEnumerable<int> rows = problemData.TrainingIndices;
-      double[,] inputMatrix = AlglibUtil.PrepareInputMatrix(dataset, allowedInputVariables.Concat(new string[] { targetVariable }), rows);
+      var doubleVariables = allowedInputVariables.Where(dataset.VariableHasType<double>);
+      var factorVariableNames = allowedInputVariables.Where(dataset.VariableHasType<string>);
+      var factorVariables = AlglibUtil.GetFactorVariableValues(dataset, factorVariableNames, rows);
+      double[,] binaryMatrix = AlglibUtil.PrepareInputMatrix(dataset, factorVariables, rows);
+      double[,] doubleVarMatrix = AlglibUtil.PrepareInputMatrix(dataset, doubleVariables.Concat(new string[] { targetVariable }), rows);
+      var inputMatrix = binaryMatrix.HorzCat(doubleVarMatrix);
+
       if (inputMatrix.Cast<double>().Any(x => double.IsNaN(x) || double.IsInfinity(x)))
         throw new NotSupportedException("Linear regression does not support NaN or infinity values in the input dataset.");
@@ -99,5 +105,17 @@
 
       int col = 0;
-      foreach (string column in allowedInputVariables) {
+      foreach (var kvp in factorVariables) {
+        var varName = kvp.Key;
+        foreach (var cat in kvp.Value) {
+          BinaryFactorVariableTreeNode vNode =
+            (BinaryFactorVariableTreeNode)new HeuristicLab.Problems.DataAnalysis.Symbolic.BinaryFactorVariable().CreateTreeNode();
+          vNode.VariableName = varName;
+          vNode.VariableValue = cat;
+          vNode.Weight = coefficients[col];
+          addition.AddSubtree(vNode);
+          col++;
+        }
+      }
+      foreach (string column in doubleVariables) {
         VariableTreeNode vNode = (VariableTreeNode)new HeuristicLab.Problems.DataAnalysis.Symbolic.Variable().CreateTreeNode();
         vNode.VariableName = column;
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/MultinomialLogitClassification.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/MultinomialLogitClassification.cs	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/MultinomialLogitClassification.cs	(revision 14826)
@@ -69,7 +69,13 @@
       var dataset = problemData.Dataset;
       string targetVariable = problemData.TargetVariable;
-      IEnumerable<string> allowedInputVariables = problemData.AllowedInputVariables;
+      var doubleVariableNames = problemData.AllowedInputVariables.Where(dataset.VariableHasType<double>);
+      var factorVariableNames = problemData.AllowedInputVariables.Where(dataset.VariableHasType<string>);
       IEnumerable<int> rows = problemData.TrainingIndices;
-      double[,] inputMatrix = AlglibUtil.PrepareInputMatrix(dataset, allowedInputVariables.Concat(new string[] { targetVariable }), rows);
+      double[,] inputMatrix = AlglibUtil.PrepareInputMatrix(dataset, doubleVariableNames.Concat(new string[] { targetVariable }), rows);
+
+      var factorVariableValues = AlglibUtil.GetFactorVariableValues(dataset, factorVariableNames, rows);
+      var factorMatrix = AlglibUtil.PrepareInputMatrix(dataset, factorVariableValues, rows);
+      inputMatrix = factorMatrix.HorzCat(inputMatrix);
+
       if (inputMatrix.Cast<double>().Any(x => double.IsNaN(x) || double.IsInfinity(x)))
         throw new NotSupportedException("Multinomial logit classification does not support NaN or infinity values in the input dataset.");
@@ -96,5 +102,5 @@
       relClassError = alglib.mnlrelclserror(lm, inputMatrix, nRows);
 
-      MultinomialLogitClassificationSolution solution = new MultinomialLogitClassificationSolution(new MultinomialLogitModel(lm, targetVariable, allowedInputVariables, classValues), (IClassificationProblemData)problemData.Clone());
+      MultinomialLogitClassificationSolution solution = new MultinomialLogitClassificationSolution(new MultinomialLogitModel(lm, targetVariable, doubleVariableNames, factorVariableValues, classValues), (IClassificationProblemData)problemData.Clone());
       return solution;
     }
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/MultinomialLogitClassificationSolution.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/MultinomialLogitClassificationSolution.cs	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/MultinomialLogitClassificationSolution.cs	(revision 14826)
@@ -43,5 +43,5 @@
       : base(original, cloner) {
     }
-    public MultinomialLogitClassificationSolution( MultinomialLogitModel logitModel,IClassificationProblemData problemData)
+    public MultinomialLogitClassificationSolution(MultinomialLogitModel logitModel, IClassificationProblemData problemData)
       : base(logitModel, problemData) {
     }
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/MultinomialLogitModel.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/MultinomialLogitModel.cs	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/Linear/MultinomialLogitModel.cs	(revision 14826)
@@ -56,4 +56,7 @@
     [Storable]
     private double[] classValues;
+    [Storable]
+    private List<KeyValuePair<string, IEnumerable<string>>> factorVariables;
+
     [StorableConstructor]
     private MultinomialLogitModel(bool deserializing)
@@ -68,12 +71,22 @@
       allowedInputVariables = (string[])original.allowedInputVariables.Clone();
       classValues = (double[])original.classValues.Clone();
+      this.factorVariables = original.factorVariables.Select(kvp => new KeyValuePair<string, IEnumerable<string>>(kvp.Key, new List<string>(kvp.Value))).ToList();
     }
-    public MultinomialLogitModel(alglib.logitmodel logitModel, string targetVariable, IEnumerable<string> allowedInputVariables, double[] classValues)
+    public MultinomialLogitModel(alglib.logitmodel logitModel, string targetVariable, IEnumerable<string> doubleInputVariables, IEnumerable<KeyValuePair<string, IEnumerable<string>>> factorVariables, double[] classValues)
       : base(targetVariable) {
       this.name = ItemName;
       this.description = ItemDescription;
       this.logitModel = logitModel;
-      this.allowedInputVariables = allowedInputVariables.ToArray();
+      this.allowedInputVariables = doubleInputVariables.ToArray();
+      this.factorVariables = factorVariables.Select(kvp => new KeyValuePair<string, IEnumerable<string>>(kvp.Key, new List<string>(kvp.Value))).ToList();
       this.classValues = (double[])classValues.Clone();
+    }
+
+    [StorableHook(HookType.AfterDeserialization)]
+    private void AfterDeserialization() {
+      // BackwardsCompatibility3.3
+      #region Backwards compatible code, remove with 3.4
+      factorVariables = new List<KeyValuePair<string, IEnumerable<string>>>();
+      #endregion
     }
 
@@ -83,5 +96,9 @@
 
     public override IEnumerable<double> GetEstimatedClassValues(IDataset dataset, IEnumerable<int> rows) {
+
       double[,] inputData = AlglibUtil.PrepareInputMatrix(dataset, allowedInputVariables, rows);
+      double[,] factorData = AlglibUtil.PrepareInputMatrix(dataset, factorVariables, rows);
+
+      inputData = factorData.HorzCat(inputData);
 
       int n = inputData.GetLength(0);
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/NearestNeighbour/NearestNeighbourModel.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/NearestNeighbour/NearestNeighbourModel.cs	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/NearestNeighbour/NearestNeighbourModel.cs	(revision 14826)
@@ -144,5 +144,5 @@
       if (inputMatrix.Cast<double>().Any(x => double.IsNaN(x) || double.IsInfinity(x)))
         throw new NotSupportedException(
-          "Nearest neighbour classification does not support NaN or infinity values in the input dataset.");
+          "Nearest neighbour model does not support NaN or infinity values in the input dataset.");
 
       this.kdTree = new alglib.nearestneighbor.kdtree();
Index: trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/NonlinearRegression/NonlinearRegression.cs
===================================================================
--- trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/NonlinearRegression/NonlinearRegression.cs	(revision 14781)
+++ trunk/sources/HeuristicLab.Algorithms.DataAnalysis/3.4/NonlinearRegression/NonlinearRegression.cs	(revision 14826)
@@ -21,4 +21,5 @@
 
 using System;
+using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
@@ -208,4 +209,43 @@
       var parser = new InfixExpressionParser();
       var tree = parser.Parse(modelStructure);
+      // parser handles double and string variables equally by creating a VariableTreeNode
+      // post-process to replace VariableTreeNodes by FactorVariableTreeNodes for all string variables
+      var factorSymbol = new FactorVariable();
+      factorSymbol.VariableNames =
+        problemData.AllowedInputVariables.Where(name => problemData.Dataset.VariableHasType<string>(name));
+      factorSymbol.AllVariableNames = factorSymbol.VariableNames;
+      factorSymbol.VariableValues =
+        factorSymbol.VariableNames.Select(name =>
+        new KeyValuePair<string, Dictionary<string, int>>(name,
+        problemData.Dataset.GetReadOnlyStringValues(name).Distinct()
+        .Select((n, i) => Tuple.Create(n, i))
+        .ToDictionary(tup => tup.Item1, tup => tup.Item2)));
+
+      foreach (var parent in tree.IterateNodesPrefix().ToArray()) {
+        for (int i = 0; i < parent.SubtreeCount; i++) {
+          var varChild = parent.GetSubtree(i) as VariableTreeNode;
+          var factorVarChild = parent.GetSubtree(i) as FactorVariableTreeNode;
+          if (varChild != null && factorSymbol.VariableNames.Contains(varChild.VariableName)) {
+            parent.RemoveSubtree(i);
+            var factorTreeNode = (FactorVariableTreeNode)factorSymbol.CreateTreeNode();
+            factorTreeNode.VariableName = varChild.VariableName;
+            factorTreeNode.Weights =
+              factorTreeNode.Symbol.GetVariableValues(factorTreeNode.VariableName).Select(_ => 1.0).ToArray();
+            // weight = 1.0 for each value
+            parent.InsertSubtree(i, factorTreeNode);
+          } else if (factorVarChild != null && factorSymbol.VariableNames.Contains(factorVarChild.VariableName)) {
+            if (factorSymbol.GetVariableValues(factorVarChild.VariableName).Count() != factorVarChild.Weights.Length)
+              throw new ArgumentException(
+                string.Format("Factor variable {0} needs exactly {1} weights",
+                factorVarChild.VariableName,
+                factorSymbol.GetVariableValues(factorVarChild.VariableName).Count()));
+            parent.RemoveSubtree(i);
+            var factorTreeNode = (FactorVariableTreeNode)factorSymbol.CreateTreeNode();
+            factorTreeNode.VariableName = factorVarChild.VariableName;
+            factorTreeNode.Weights = factorVarChild.Weights;
+            parent.InsertSubtree(i, factorTreeNode);
+          }
+        }
+      }
 
       if (!SymbolicRegressionConstantOptimizationEvaluator.CanOptimizeConstants(tree)) throw new ArgumentException("The optimizer does not support the specified model structure.");
