Java でコレクションから分布を計算する

数値のコレクション (または検査したいフィールドであるオブジェクト) をそれらの数値の分布に変換することは、一般的な統計手法であり、レポートやデータ駆動型アプリケーションのさまざまなコンテキストで採用されています。

与えられたコレクション:

1, 1, 2, 1, 2, 3, 1, 4, 5, 1, 3

それらの分布をカウント (各要素の頻度) として検査し、結果をマップに保存できます。

{
"1": 5,
"2": 2,
"3": 2,
"4": 1,
"5": 1
}

または、できます ノーマライズ 値の総数に基づく値 - したがって、パーセンテージで表します。

{
"1": 0.45,
"2": 0.18,
"3": 0.18,
"4": 0.09,
"5": 0.09
}

または、これらのパーセンテージを 0..100 の代わりにフォーマットする 0..1 形式でダウンロードすることができます。

このガイドでは、コレクションから分布を計算する方法を見ていきます。プリミティブ型と、アプリケーションでレポートするフィールドであるオブジェクトの両方を使用します。

Java での関数型プログラミングのサポートが追加されたことで、分布の計算がこれまでになく簡単になりました。 数値のコレクションと Books:

public class Book {

    private String id;
    private String name;
    private String author;
    private long pageNumber;
    private long publishedYear;

   
}

Java でコレクションの分布を計算する

まず、プリミティブ型の分布を計算する方法を見てみましょう。 オブジェクトを操作すると、ドメイン クラスからカスタム メソッドを呼び出して、計算の柔軟性を高めることができます。

デフォルトでは、パーセンテージを double から 0.00 〜へ 100.00.

プリミティブタイプ

整数のリストを作成し、それらの分布を出力しましょう:

List integerList = List.of(1, 1, 2, 1, 2, 3, 1, 4, 5, 1, 3);
System.out.println(calculateIntegerDistribution(integerList));

分布は次のように計算されます。

public static Map calculateIntegerDistribution(List list) {
    return list.stream()
            .collect(Collectors.groupingBy(Integer::intValue,
                    Collectors.collectingAndThen(Collectors.counting(),
                            count -> (Double.parseDouble(String.format("%.2f", count * 100.00 / list.size()))))));
}

このメソッドは、リストを受け取り、それをストリーミングします。 ストリーミング中、値は グループ化 それらの整数値 - そしてそれらの値は カウント Collectors.counting()、に収集される前に Map ここで、キーは入力値を表し、double は分布内のパーセンテージを表します。

ここでのキーメソッドは collect() 受け入れる 二人のコレクター. キーコレクターは、キー値 (入力要素) でグループ化するだけで収集します。 value-collector は、 collectingAndThen() メソッドにより、 値を数える 次に、次のような別の形式でフォーマットします count * 100.00 / list.size() これにより、カウントされた要素をパーセンテージで表現できます。

{1=45.45, 2=18.18, 3=18.18, 4=9.09, 5=9.09}

分布を値またはキーでソート

ディストリビューションを作成するときは、通常、値を並べ替える必要があります。 多くの場合、これは キー。 Java HashMaps 挿入の順序を維持することを保証するものではありません、したがって、使用する必要があります LinkedHashMap それはします。 さらに、サイズがはるかに小さくなり、管理しやすくなったため、マップを再ストリーミングして再収集するのが最も簡単です。

前の操作では、処理しているキーの数によっては、数千のレコードを小さなマップにすばやく折りたたむことができるため、再ストリーミングに費用はかかりません。

public static Map calculateIntegerDistribution(List list) {
    return list.stream()
            .collect(Collectors.groupingBy(Integer::intValue,
                    Collectors.collectingAndThen(Collectors.counting(),
                            count -> (Double.parseDouble(String.format("%.2f", count.doubleValue() / list.size()))))))
            
            
            .entrySet()
            .stream()
            .sorted(Map.Entry.comparingByKey())
            .collect(Collectors.toMap(e -> Integer.parseInt(e.getKey().toString()),
                    Map.Entry::getValue,
                    (a, b) -> {
                        throw new AssertionError();
                    },
                    LinkedHashMap::new));
}

オブジェクト

オブジェクトに対してこれをどのように行うことができますか? 同じロジックが適用されます。 識別機能の代わりに (Integer::intValue)、代わりに目的のフィールドを使用します (書籍の発行年など)。 いくつかの本を作成し、それらをリストに保存してから、出版年の分布を計算してみましょう:

ベストプラクティス、業界で認められた標準、および含まれているチートシートを含む、Gitを学習するための実践的で実用的なガイドを確認してください。 グーグルGitコマンドを停止し、実際に 学ぶ それ!

Book book1 = new Book("001", "Our Mathematical Universe", "Max Tegmark", 432, 2014);
Book book2 = new Book("002", "Life 3.0", "Max Tegmark", 280, 2017);
Book book3 = new Book("003", "Sapiens", "Yuval Noah Harari", 443, 2011);
Book book4 = new Book("004", "Steve Jobs", "Water Isaacson", 656, 2011);

List books = Arrays.asList(book1, book2, book3, book4);

の分布を計算してみましょう。 publishedYear フィールド:

public static Map calculateDistribution(List books) {
    return books.stream()
            .collect(Collectors.groupingBy(Book::getPublishedYear,
                    Collectors.collectingAndThen(Collectors.counting(),
                            count -> (Double.parseDouble(String.format("%.2f", count * 100.00 / books.size()))))))
            
            .entrySet()
            .stream()
            .sorted(Map.Entry.comparingByKey())
            .collect(Collectors.toMap(e -> Integer.parseInt(e.getKey().toString()),
                    Map.Entry::getValue,
                    (a, b) -> {
                        throw new AssertionError();
                    },
                    LinkedHashMap::new));
}

を調整する "%.2f" 浮動小数点の精度を設定します。 これにより、次の結果が得られます。

{2011=50.0, 2014=25.0, 2017=25.0}

与えられた本の 50% (2/4) は 2011 年に出版され、25% (1/4) は 2014 年に出版され、25% (1/4) は 2017 年に出版されました。の範囲 0..1?

Java でコレクションの正規化 (パーセント) 分布を計算する

パーセンテージを正規化するには 0.0...100.0 までの範囲 0..1 範囲 – 単純に適応させます collectingAndThen() に呼び出します カウントを掛ける 100.0 コレクションのサイズで割る前に。

以前は、 Long によって返されるカウント Collectors.counting() は暗黙的に double に変換されました (double 値を使用した乗算) – したがって、今回は明示的に取得する必要があります。 doubleValue() count:

    public static Map calculateDistributionNormalized(List books) {
        return books.stream()
            .collect(Collectors.groupingBy(Book::getPublishedYear,
                    Collectors.collectingAndThen(Collectors.counting(),
                            count -> (Double.parseDouble(String.format("%.4f", count.doubleValue() / books.size()))))))
            
            .entrySet()
            .stream()
            .sorted(comparing(e -> e.getKey()))
            .collect(Collectors.toMap(e -> Integer.parseInt(e.getKey().toString()),
                    Map.Entry::getValue,
                    (a, b) -> {
                        throw new AssertionError();
                    },
                    LinkedHashMap::new));
}

を調整する "%.4f" 浮動小数点の精度を設定します。 これにより、次の結果が得られます。

{2011=0.5, 2014=0.25, 2017=0.25}

コレクションの要素数 (頻度) を計算する

最後に、コレクションの要素数 (すべての要素の頻度) を取得するには、数をコレクションのサイズで割らないようにします。 これは完全に正規化されていないカウントです。

   public static Map calculateDistributionCount(List books) {
        return books
            .stream()
            .collect(Collectors.groupingBy(Book::getPublishedYear,
                    Collectors.collectingAndThen(Collectors.counting(),
                            count -> (Integer.parseInt(String.format("%s", count.intValue()))))))
            
            .entrySet()
            .stream()
            .sorted(Map.Entry.comparingByKey())
            .collect(Collectors.toMap(e -> Integer.parseInt(e.getKey().toString()),
                    Map.Entry::getValue,
                    (a, b) -> {
                        throw new AssertionError();
                    },
                    LinkedHashMap::new));
}

この結果:

{2011=2, 2014=1, 2017=1}

実際、2011 年から 2014 冊、2017 年と XNUMX 年からそれぞれ XNUMX 冊の本があります。

まとめ

データの分布を計算することは、データが豊富なアプリケーションでは一般的なタスクであり、外部ライブラリや複雑なコードを使用する必要はありません。 関数型プログラミングのサポートにより、Java はコレクションを簡単に操作できるようになりました。

この短いドラフトでは、コレクション内のすべての要素の頻度カウントを計算する方法と、要素間のパーセンテージに正規化された分布マップを計算する方法を調べました。 0 & 1 と同様 0 & 100 Javaで。

タイムスタンプ:

より多くの スタックアバス