Calculer la distribution à partir de la collection en Java

Transformer une collection de nombres (ou d'objets dont les champs que vous souhaitez inspecter) en une distribution de ces nombres est une technique statistique courante, et est utilisée dans divers contextes dans les rapports et les applications basées sur les données.

Étant donné une collection :

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

Vous pouvez inspecter leur distribution sous forme de comptage (fréquence de chaque élément) et stocker les résultats dans une carte :

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

Ou tu peux normaliser les valeurs basées sur le nombre total de valeurs – les exprimant ainsi en pourcentages :

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

Ou même exprimer ces pourcentages dans un 0..100 format au lieu d'un 0..1 le format.

Dans ce guide, nous verrons comment vous pouvez calculer une distribution à partir d'une collection - à la fois en utilisant des types primitifs et des objets dont vous pourriez vouloir signaler les champs dans votre application.

Avec l'ajout de la prise en charge de la programmation fonctionnelle en Java, le calcul des distributions est plus facile que jamais. Nous allons travailler avec une collection de nombres et une collection de Books:

public class Book {

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

   
}

Calculer la distribution de la collection en Java

Voyons d'abord comment vous pouvez calculer une distribution pour les types primitifs. Travailler avec des objets vous permet simplement d'appeler des méthodes personnalisées à partir de vos classes de domaine pour offrir plus de flexibilité dans les calculs.

Par défaut, nous représenterons les pourcentages par un double à partir de 0.00 à 100.00.

Types primitifs

Créons une liste d'entiers et imprimons leur distribution :

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

La distribution est calculée avec :

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()))))));
}

Cette méthode accepte une liste et la diffuse. Pendant la diffusion, les valeurs sont regroupés par leur valeur entière – et leurs valeurs sont dénombré en utilisant Collectors.counting(), avant d'être collecté dans un Map où les clés représentent les valeurs d'entrée et les doubles représentent leurs pourcentages dans la distribution.

Les méthodes clés ici sont collect() qui accepte deux collectionneurs. Le collecteur de clés collecte en regroupant simplement les valeurs de clé (éléments d'entrée). Le collecteur de valeur collecte via le collectingAndThen() méthode qui nous permet de compter les valeurs puis formatez-les dans un autre format, tel que count * 100.00 / list.size() ce qui nous permet d'exprimer les éléments comptés en pourcentages :

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

Trier la distribution par valeur ou par clé

Lors de la création de distributions, vous souhaiterez généralement trier les valeurs. Le plus souvent, ce sera par key. Java HashMaps ne garantit pas de préserver l'ordre d'insertion, nous devrons donc utiliser un LinkedHashMap qui fait. De plus, il est plus facile de rediffuser la carte et de la récupérer maintenant qu'elle est beaucoup plus petite et beaucoup plus gérable.

L'opération précédente peut rapidement regrouper plusieurs milliers d'enregistrements dans de petites cartes, en fonction du nombre de clés avec lesquelles vous traitez, de sorte que la retransmission n'est pas coûteuse :

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));
}

Objets

Comment cela peut-il être fait pour les objets ? La même logique s'applique ! Au lieu d'une fonction d'identification (Integer::intValue), nous utiliserons le champ souhaité à la place, comme l'année de publication de nos livres. Créons quelques livres, stockons-les dans une liste, puis calculons les distributions des années de publication :

Consultez notre guide pratique et pratique pour apprendre Git, avec les meilleures pratiques, les normes acceptées par l'industrie et la feuille de triche incluse. Arrêtez de googler les commandes Git et en fait apprendre il!

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);

Calculons la distribution des publishedYear champ:

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));
}

Ajuste le "%.2f" pour définir la précision en virgule flottante. Cela se traduit par :

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

50% des livres donnés (2/4) ont été publiés en 2011, 25% (1/4) ont été publiés en 2014 et 25% (1/4) en 2017. Et si vous voulez formater ce résultat différemment, et normaliser la gamme dans 0..1?

Calculer la distribution normalisée (pourcentage) de la collection en Java

Pour normaliser les pourcentages d'un 0.0...100.0 portée à un 0..1 gamme - nous adapterons simplement la collectingAndThen() appeler pour ne sauraient multiplier le compte par 100.0 avant de diviser par la taille de la collection.

Auparavant, le Long nombre renvoyé par Collectors.counting() a été implicitement converti en un double (multiplication avec une valeur double) - donc cette fois, nous voudrons obtenir explicitement le doubleValue() des 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));
}

Ajuste le "%.4f" pour définir la précision en virgule flottante. Cela se traduit par :

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

Calculer le nombre d'éléments (fréquence) de la collection

Enfin, nous pouvons obtenir le nombre d'éléments (fréquence de tous les éléments) dans la collection en ne divisant tout simplement pas le nombre par la taille de la collection ! Il s'agit d'un décompte entièrement non normalisé :

   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));
}

Cela se traduit par:

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

En effet, il y a deux livres de 2011, et un de 2014 et 2017 chacun.

Conclusion

Le calcul des distributions de données est une tâche courante dans les applications riches en données et ne nécessite pas l'utilisation de bibliothèques externes ou de code complexe. Grâce à la prise en charge de la programmation fonctionnelle, Java a facilité le travail avec les collections !

Dans ce court brouillon, nous avons examiné comment calculer le nombre de fréquences de tous les éléments d'une collection, ainsi que comment calculer des cartes de distribution normalisées en pourcentages entre 0 ainsi que le 1 ainsi que 0 ainsi que le 100 en Java.

Horodatage:

Plus de Stackabuse