Доброго времени суток всем читающим. Сначала изложу предысторию данного вопроса. Началось все с того что знакомый попросил меня по работе помочь ему сделать приложение (WinForm на C#), которая бы алгоритмом CART создавала бинарное дерево решений, исходя из статистических данных находящихся в таблице на форме. Суть в том, чтобы делить исходную таблицу на 2 части по вычисляемым критериям. Для того чтобы поделить исходную таблицу, мы должны найти среднее по каждому из столбцов:
Первый и последний столбцы в расчете не учитываются (в первом значение Год, в последнем значения 0 или 1). Данная функция вернет одномерный массив средних значений, обращение к которым будет идти согласно индексу.
Затем идет собственно поиск коэффициента Gini по каждому из столбцов, который покажет нам какой из столбцов брать для разделения нашей таблицы, среди них выбирается минимальное и соответственно индекс столбца с минимальным коэффициентом будет необходимым нам для разделения. Затем выбирается этот столбец и все значения больше среднего значения для данного столбца отправляются в одну таблицу, например 1.1, а все значения меньше среднего в таблицу 1.2. Ниже код. который отвечает за первое разделение исходной таблицы:
Сама таблица, напомню, получается из DataGridView. И вызывается все вышеизложенное таким образом:
Затем каждая промежуточная таблица проходит все те же этапы. Сигналом к завершению деления таблицы служит то, что в последнем столбце (Т) все значения будут либо все равны 1 либо все равны 0. На каждом следующем этапе не участвует в вычислениях один столбец, для которого на предыдущем шаге коэффициент Gini был минимальным. Таким образом строится бинарное дерево решений. Так вот, а теперь собственно сам вопрос: каким образом можно организовать хранение промежуточных таблиц, так чтобы все их обсчитать и что немаловажно не потерять. Предлагали вариант хранить их в трехмерном массиве, укладывая все промежуточные таблицы на позиции 1, 11, 12, 111, 112 и так далее, но такой вариант мы отвергли ввиду его «прожорливости» к памяти. Если кто понял хоть что либо подскажите как такое организовать.
//нахождение средних значений по столбцам public static double[] GetAverageValue(DataTable table) { #region Расчет средних значений по столбцам var sr = new double[14]; for (int j = 1; j < table.Columns.Count - 1; j++) { double sum = 0.0; for (int i = 0; i < table.Rows.Count; i++) { sum = sum + Convert.ToDouble(table.Rows[i][j]); } sr[j - 1] = sum / table.Rows.Count; } #endregion return sr; }
Первый и последний столбцы в расчете не учитываются (в первом значение Год, в последнем значения 0 или 1). Данная функция вернет одномерный массив средних значений, обращение к которым будет идти согласно индексу.
Затем идет собственно поиск коэффициента Gini по каждому из столбцов, который покажет нам какой из столбцов брать для разделения нашей таблицы, среди них выбирается минимальное и соответственно индекс столбца с минимальным коэффициентом будет необходимым нам для разделения. Затем выбирается этот столбец и все значения больше среднего значения для данного столбца отправляются в одну таблицу, например 1.1, а все значения меньше среднего в таблицу 1.2. Ниже код. который отвечает за первое разделение исходной таблицы:
//нахождение значений больше либо равных среднему в столбце таблицы public static int[] GetMoreAverageAndLessAverage(double average, double[] columns, int[] T) { int moreAverage = 0; int valueTMoreAverage = 0; int valueTInvertMoreAverage = 0; int lessAverage = 0; int valueTLessAverage = 0; int valueTLessInvertAverage = 0; var moreAverageValue = new int[6]; for (int i = 0; i < columns.Length; i++ ) { if (columns[i] >= average) { moreAverage = moreAverage + 1; if (T[i] == 1) { valueTMoreAverage = valueTMoreAverage + 1; } else { valueTInvertMoreAverage = valueTInvertMoreAverage + 1; } } else { lessAverage = lessAverage + 1; if (T[i] == 1) { valueTLessAverage = valueTLessAverage + 1; } else { valueTLessInvertAverage = valueTLessInvertAverage + 1; } } } moreAverageValue[0] = moreAverage; moreAverageValue[1] = valueTMoreAverage; moreAverageValue[2] = valueTInvertMoreAverage; moreAverageValue[3] = lessAverage; moreAverageValue[4] = valueTLessAverage; moreAverageValue[5] = valueTLessInvertAverage; return moreAverageValue; } //нахождение значения коэффициента Gini(T,param) public static double GetValueGini(int[] currentCollumnsAndT, int lengthCollumns) { var gini = 0.0; if (currentCollumnsAndT != null) { double gini1 = (Convert.ToDouble(currentCollumnsAndT.GetValue(0)) / lengthCollumns) * (1 - ((Convert.ToDouble(currentCollumnsAndT.GetValue(1)) / Convert.ToDouble(currentCollumnsAndT.GetValue(0))) * (Convert.ToDouble(currentCollumnsAndT.GetValue(1)) / Convert.ToDouble(currentCollumnsAndT.GetValue(0)))) - ((Convert.ToDouble(currentCollumnsAndT.GetValue(2)) / Convert.ToDouble(currentCollumnsAndT.GetValue(0))) * (Convert.ToDouble(currentCollumnsAndT.GetValue(2)) / Convert.ToDouble(currentCollumnsAndT.GetValue(0))))); double gini2 = (Convert.ToDouble(currentCollumnsAndT.GetValue(3)) / lengthCollumns) * (1 - ((Convert.ToDouble(currentCollumnsAndT.GetValue(4)) / Convert.ToDouble(currentCollumnsAndT.GetValue(3))) * (Convert.ToDouble(currentCollumnsAndT.GetValue(4)) / Convert.ToDouble(currentCollumnsAndT.GetValue(3)))) - ((Convert.ToDouble(currentCollumnsAndT.GetValue(5)) / Convert.ToDouble(currentCollumnsAndT.GetValue(3))) * (Convert.ToDouble(currentCollumnsAndT.GetValue(5)) / Convert.ToDouble(currentCollumnsAndT.GetValue(3))))); gini = gini1 + gini2; } return gini; } //расчет первичного разделения таблицы public static double[] GetValueGiniIsPrimaryDivide(DataTable table) { var currentCollumn = new double[table.Rows.Count]; var average = GetAverageValue(table); //выкинуть в класс формы чтобы не изменялось, передавать параметром в GetValueGiniIsPrimaryDivide (возможно передавать) var T = new int[table.Rows.Count]; var gini = new double[table.Columns.Count - 1]; for (int i = 0; i < table.Rows.Count; i++) { T[i] = Convert.ToInt16(table.Rows[i][table.Columns.Count - 1]); } for (int i = 1; i < table.Columns.Count - 1; i++) { for (int j = 0; j < table.Rows.Count; j++) { currentCollumn[j] = 0.0; currentCollumn[j] = Convert.ToDouble(table.Rows[j][i].ToString()); } var bufer = average[i - 1]; gini[i] = GetValueGini(GetMoreAverageAndLessAverage(bufer, currentCollumn, T), table.Rows.Count); } var min = Convert.ToDouble(gini.GetValue(0)); int indexMin = 0; for (int i = 0; i < gini.Length; i++) { if (Convert.ToDouble(gini.GetValue(i)) <= min) { min = Convert.ToDouble(gini.GetValue(i)); indexMin = i; } } var returnValue = new double[2]; returnValue[0] = min; returnValue[1] = indexMin; return returnValue; } }
Сама таблица, напомню, получается из DataGridView. И вызывается все вышеизложенное таким образом:
#region Создание из DataGrid DataTable var table = new DataTable(); for (int j = 0; j < dataGridView1.Columns.Count; j++) { table.Columns.Add(dataGridView1.Columns[j].Name); } for (int i = 0; i < dataGridView1.Rows.Count - 1; i++) { table.Rows.Add(); for (int j = 0; j < dataGridView1.Columns.Count; j++) { table.Rows[i][j] = dataGridView1[j, i].Value.ToString(); } } #endregion var giniIsMin = SeparationTable.GetValueGiniIsPrimaryDivide(table);
Затем каждая промежуточная таблица проходит все те же этапы. Сигналом к завершению деления таблицы служит то, что в последнем столбце (Т) все значения будут либо все равны 1 либо все равны 0. На каждом следующем этапе не участвует в вычислениях один столбец, для которого на предыдущем шаге коэффициент Gini был минимальным. Таким образом строится бинарное дерево решений. Так вот, а теперь собственно сам вопрос: каким образом можно организовать хранение промежуточных таблиц, так чтобы все их обсчитать и что немаловажно не потерять. Предлагали вариант хранить их в трехмерном массиве, укладывая все промежуточные таблицы на позиции 1, 11, 12, 111, 112 и так далее, но такой вариант мы отвергли ввиду его «прожорливости» к памяти. Если кто понял хоть что либо подскажите как такое организовать.
