前回は、定量データをスコア順に並び替えたのち、この順番でグループに割り振っていく方法を見た。今回は、定量データをスコア順に並び替えるところは同じだが、割り振り方を変更することにより、より平均的に分類できないかを見ていく。

前回と同じく、100人の学生を3つのクラスA、B、Cに分ける方法を考えてみる。

  1. まず、学生をスコア順にA、B、Cに一人ずつ割り振る。
  2. 次に、学生をA、B、Cのそれぞれに割り振った場合、どのクラスが最もスコアが良くなるかを判定して、最もスコアが良いクラスに割り振る。
  3. 仮に、Aに割り振ったとすると、次の学生はB、Cのどちらに割り振った方がクラスのスコアが良くなるのかを判定して、最もスコアが良いクラスに割り振る。
  4. 仮に、Bに割り振ったとすると、次の学生は自動的にCに割り振って、2へ繰り返す。

それでは、実際にC#で組んでみる。
以前作成した定量データを格納するItemクラスと分類された定量データを格納するGroupクラスは変更がないので、記載は省略した。
実際の分類は以下のクラスで行う。


public class SequentialSelectionClassification
{
    public IEnumerable Classify(IEnumerable source, IEnumerable valueIndices, int groupNumber)
    {
        //平均値
        var averageDictionary = new Dictionary();
        foreach (var valueIndex in valueIndices)
        {
            averageDictionary[valueIndex] = source.Average(x => x.Values[valueIndex]);
        } 

        //スコア計算
        var scoreDictionary = new Dictionary();
        foreach (var item in source)
        {
            var d = 0.0d;
            foreach (var valueIndex in valueIndices)
            {
                var v = (item.Values[valueIndex] - averageDictionary[valueIndex]);
                d += v * v;
            }
            scoreDictionary[item] = d;
        } 

        //スコアを昇順に並び替え
        var list = new List>(scoreDictionary);
        //昇順
        list.Sort((x, y) => x.Value.CompareTo(y.Value));
        // 降順
        //list.Sort((x, y) => -x.Value.CompareTo(y.Value));
        var items = list.Select(x => x.Key).ToArray(); 

        //グループ作成
        var groups = new List();
        for (var i = 0; i < groupNumber; i++)
        {
            groups.Add(new Group(valueIndices));
        } 

        //グループのインデックスと要素数のペア
        var groupCountDictionary = new Dictionary();
        for (var i = 0; i < groupNumber; i++)
        {
            groupCountDictionary[i] = 0;
        } 

        //グループ振り分け
        for (var i = 0; i < groupNumber; i++)
        {
            groups[i].Items.Add(items[i]);
            groupCountDictionary[i]++;
        }
        for (var i = groupNumber; i < items.Length; i++)
        {
            var item = items[i];
            var min = groupCountDictionary.Min(x => x.Value);
            var indices = groupCountDictionary.Where(x => x.Value == min).Select(x => x.Key); 

            var minIndex = -1;
            var minScore = double.MaxValue; 

            foreach (var index in indices)
            {
                var score = Score(averageDictionary, item, groups[index]);
                if (score < minScore)
                {
                    score = minScore;
                    minIndex = index;
                }
            } 

            groups[minIndex].Items.Add(item);
            groupCountDictionary[minIndex]++;
        } 

        return groups;
    } 

    private double Score(Dictionary averageDictionary, Item item, Group group)
    {
        var d = 0.0d;
        foreach (var kvp in averageDictionary)
        {
            var s = group.Items.Sum(x => x.Values[kvp.Key]) + item.Values[kvp.Key];
            s = (double)s / (group.Items.Count + 1) - kvp.Value;
            d += s * s;
        }
        return d;
    }
}

以上で、準備は完了だ。それでは、実際に分類してみる。定量データは乱数を用いてダミーデータを作成している。


public class Test
{
    public void Test2()
    {
        //ダミーデータ作成
        var items = new List();
        for (var i = 0; i < 100; i++)
        {
            var random = new Random(i);
            var values = new double[2];
            values[0] = random.Next(0, 100);
            values[1] = random.Next(0, 100);
            items.Add(new Item(values));
        }

        //分類
        var classification = new SequentialSelectionClassification();
        var groups = classification.Classify(items, new int[] { 0, 1 }, 3);

        //ダミーデータ表示
        System.Console.WriteLine("dummy-data");
        foreach (var item in items)
        {
            System.Console.WriteLine(item.Values[0] + "," + item.Values[1]);
        }

        //ダミーデータの平均値の表示
        System.Console.WriteLine("dummy-data-average");
        System.Console.WriteLine("0:" + items.Average(x => x.Values[0]));
        System.Console.WriteLine("1:" + items.Average(x => x.Values[1]));

        //結果表示
        foreach (var group in groups)
        {
            System.Console.WriteLine("group-item");
            foreach (var item in group.Items)
            {
                System.Console.WriteLine(item.Values[0] + "," + item.Values[1]);
            }
            System.Console.WriteLine("group-average");
            var dictionary = group.Average();
            foreach (var kvp in dictionary)
            {
                System.Console.WriteLine(kvp.Key + ":" + kvp.Value);
            }
        }
    }
}

この結果は以下のようになった。実際には、前回用いたダミーデータをファイル保存しておき、それを読み取っている。
【スコアを昇順で並び替えた場合】

全データ グループ1 グループ2 グループ3
インデックス0の平均値 51.12 52.81 44.78 55.61
インデックス1の平均値 50.66 45.96 50.06 55.79

【スコアを降順で並び替えた場合】

全データ グループ1 グループ2 グループ3
インデックス0の平均値 51.12 47.53 52.57 53.26
インデックス1の平均値 50.66 53.06 45.69 53.14

評価式を定義する。
(グループ1のインデックス0平均値-全データのインデックス0の平均値)^2
+(グループ2のインデックス0平均値-全データのインデックス0の平均値)^2
+(グループ3のインデックス0平均値-全データのインデックス0の平均値)^2
+(グループ1のインデックス1平均値-全データのインデックス1の平均値)^2
+(グループ2のインデックス1平均値-全データのインデックス1の平均値)^2
+(グループ3のインデックス1平均値-全データのインデックス1の平均値)^2

スコアを昇順で並び替えた場合:111.97
スコアを降順で並び替えた場合:56.18

以上から、スコアを降順に並べてから、グループスコアが最も小さくなるようにグループに振り分けていく方が、より平均的に分類できていることが分かる。

今までは、定量データをスコア順に取り出していた。
次回は、スコア順ではなく適切に取り出すことによって、より平均的に分類できないかを考えてみる。

平均的に分類する方法の考察(3)