Bereken distributie van verzameling in Java

Het omzetten van een verzameling getallen (of objecten waarvan u de velden wilt inspecteren) in een verdeling van die getallen is een gebruikelijke statistische techniek en wordt in verschillende contexten gebruikt in rapportage- en gegevensgestuurde toepassingen.

Gegeven een verzameling:

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

U kunt hun verdeling inspecteren als een telling (frequentie van elk element) en de resultaten opslaan in een kaart:

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

Of je kan normaliseren de waarden gebaseerd op het totale aantal waarden โ€“ dus uitgedrukt in percentages:

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

Of druk deze percentages zelfs uit in a 0..100 formaat in plaats van een 0..1 formaat.

In deze handleiding bekijken we hoe u een verdeling van een verzameling kunt berekenen โ€“ zowel met behulp van primitieve typen als objecten waarvan u de velden mogelijk wilt rapporteren in uw toepassing.

Met de toevoeging van functionele programmeerondersteuning in Java, is het berekenen van distributies eenvoudiger dan ooit. We gaan werken met een verzameling getallen en een verzameling van Books:

public class Book {

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

   
}

Bereken distributie van collectie in Java

Laten we eerst eens kijken hoe je een verdeling voor primitieve typen kunt berekenen. Door met objecten te werken, kunt u eenvoudig aangepaste methoden uit uw domeinklassen aanroepen om meer flexibiliteit in de berekeningen te bieden.

Standaard geven we de percentages weer als een verdubbeling van 0.00 naar 100.00.

Primitieve soorten

Laten we een lijst met gehele getallen maken en hun verdeling afdrukken:

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

De verdeling wordt berekend met:

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

Deze methode accepteert een lijst en streamt deze. Tijdens het streamen zijn de waarden gegroepeerd op hun gehele waarde - en hun waarden zijn geteld gebruik Collectors.counting(), alvorens te worden verzameld in een Map waarbij de sleutels de invoerwaarden vertegenwoordigen en de dubbele getallen hun percentages in de verdeling vertegenwoordigen.

De belangrijkste methoden hier zijn collect() die accepteert twee verzamelaars. De sleutelverzamelaar verzamelt door simpelweg te groeperen op sleutelwaarden (invoerelementen). De waardeverzamelaar incasseert via de collectingAndThen() methode, die ons in staat stelt om tel de waarden en formatteer ze vervolgens in een ander formaat, zoals count * 100.00 / list.size() waarmee we de getelde elementen in percentages kunnen uitdrukken:

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

Sorteer distributie op waarde of sleutel

Wanneer u distributies maakt, wilt u meestal de waarden sorteren. Vaker wel dan niet, zal dit voorbij zijn sleutel. Java HashMaps garanderen niet dat de volgorde van inbrengen behouden blijft, dus we zullen een LinkedHashMap wat doet. Bovendien is het het gemakkelijkst om de kaart opnieuw te streamen en opnieuw te verzamelen nu deze veel kleiner en veel beter beheersbaar is.

De vorige bewerking kan snel meerdere duizenden records samenvouwen tot kleine kaarten, afhankelijk van het aantal sleutels waarmee u te maken hebt, dus opnieuw streamen is niet duur:

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

Objecten

Hoe kan dit voor objecten worden gedaan? Dezelfde logica is van toepassing! In plaats van een identificatiefunctie (Integer::intValue), gebruiken we in plaats daarvan het gewenste veld, zoals het gepubliceerde jaar voor onze boeken. Laten we een paar boeken maken, ze in een lijst opslaan en dan de verdelingen van de publicatiejaren berekenen:

Bekijk onze praktische, praktische gids voor het leren van Git, met best-practices, door de industrie geaccepteerde normen en bijgevoegd spiekbriefje. Stop met Googlen op Git-commando's en eigenlijk leren het!

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

Laten we de verdeling van de berekenen publishedYear veld:

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

Pas de .... aan "%.2f" om de precisie van de drijvende komma in te stellen. Dit resulteert in:

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

50% van de gegeven boeken (2/4) is gepubliceerd in 2011, 25% (1/4) is gepubliceerd in 2014 en 25% (1/4) in 2017. Wat als u dit resultaat anders wilt opmaken en wilt normaliseren het bereik binnen 0..1?

Bereken genormaliseerde (percentage) distributie van collectie in Java

Om de percentages van a te normaliseren 0.0...100.0 bereik tot een 0..1 bereik - we passen gewoon de collectingAndThen() bellen naar niet vermenigvuldig het aantal met 100.0 alvorens te delen door de grootte van de verzameling.

Eerder, de Long tellen geretourneerd door Collectors.counting() werd impliciet omgezet in een double (vermenigvuldiging met een dubbele waarde) โ€“ dus deze keer willen we expliciet de doubleValue() van de 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));
}

Pas de .... aan "%.4f" om de precisie van de drijvende komma in te stellen. Dit resulteert in:

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

Bereken het aantal elementen (frequentie) van de verzameling

Eindelijk - we kunnen het aantal elementen (frequentie van alle elementen) in de verzameling krijgen door simpelweg het aantal niet te delen door de grootte van de verzameling! Dit is een volledig niet-genormaliseerde telling:

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

Dit resulteert in:

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

Er zijn inderdaad twee boeken uit 2011 en รฉรฉn uit elk 2014 en 2017.

Conclusie

Het berekenen van distributies van gegevens is een veelvoorkomende taak in gegevensrijke toepassingen en vereist geen gebruik van externe bibliotheken of complexe code. Met functionele programmeerondersteuning maakte Java het werken met collecties een fluitje van een cent!

In dit korte concept hebben we gekeken hoe u frequentietellingen van alle elementen in een verzameling kunt berekenen, en hoe u distributiekaarten kunt berekenen die zijn genormaliseerd naar percentages tussen 0 en 1 net zoals 0 en 100 op Java.

Tijdstempel:

Meer van Stapelmisbruik