Beregn distribution fra samling i Java

Kildeknude: 1734738

At omdanne en samling af tal (eller objekter, hvis felter du gerne vil inspicere) til en fordeling af disse tal er en almindelig statistisk teknik og bruges i forskellige sammenhænge i rapportering og datadrevne applikationer.

Givet en samling:

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

Du kan inspicere deres fordeling som en optælling (hyppigheden af ​​hvert element) og gemme resultaterne på et kort:

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

Eller du kan normalisere værdierne baseret på det samlede antal værdier – således udtrykt i procenter:

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

Eller endda udtrykke disse procenter i en 0..100 format i stedet for a 0..1 format.

I denne guide tager vi et kig på, hvordan du kan beregne en fordeling fra en samling – både ved hjælp af primitive typer og objekter, hvis felter du måske vil rapportere i din ansøgning.

Med tilføjelsen af ​​funktionel programmeringsunderstøttelse i Java er det nemmere end nogensinde at beregne distributioner. Vi vil arbejde med en samling af tal og en samling af Books:

public class Book {

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

   
}

Beregn fordeling af samling i Java

Lad os først tage et kig på, hvordan du kan beregne en fordeling for primitive typer. At arbejde med objekter giver dig simpelthen mulighed for at kalde brugerdefinerede metoder fra dine domæneklasser for at give mere fleksibilitet i beregningerne.

Som standard vil vi repræsentere procenterne som en dobbelt fra 0.00 til 100.00.

Primitive typer

Lad os oprette en liste over heltal og udskrive deres fordeling:

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

Fordelingen er beregnet med:

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

Denne metode accepterer en liste og streamer den. Mens streamet er værdierne grupperet efter deres heltalsværdi – og deres værdier er tælles ved brug af Collectors.counting(), før de samles i en Map hvor tasterne repræsenterer inputværdierne og doublerne repræsenterer deres procenter i fordelingen.

De vigtigste metoder her er collect() som tager imod to samlere. Nøglesamleren indsamler ved blot at gruppere efter nøgleværdierne (inputelementer). Værdisamleren indsamler via collectingAndThen() metode, som giver os mulighed for tælle værdierne og formater dem derefter i et andet format, som f.eks count * 100.00 / list.size() som lader os udtrykke de talte elementer i procenter:

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

Sorter fordeling efter værdi eller nøgle

Når du opretter distributioner – vil du typisk sortere værdierne. Oftere end ikke, vil det være forbi nøgle. Java HashMaps garanterer ikke at bevare rækkefølgen af ​​indsættelse, så vi bliver nødt til at bruge en LinkedHashMap som gør. Derudover er det nemmest at genstreame kortet og samle det igen, nu hvor det er meget mindre og meget mere overskueligt.

Den tidligere operation kan hurtigt kollapse flere tusinde poster til små kort, afhængigt af antallet af nøgler, du har med at gøre, så genstreaming er ikke dyrt:

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

Objekter

Hvordan kan dette gøres for objekter? Den samme logik gælder! I stedet for en identifikationsfunktion (Integer::intValue), bruger vi i stedet det ønskede felt – såsom udgivelsesåret for vores bøger. Lad os oprette et par bøger, gemme dem på en liste og derefter beregne fordelingen af ​​udgivelsesårene:

Tjek vores praktiske, praktiske guide til at lære Git, med bedste praksis, brancheaccepterede standarder og inkluderet snydeark. Stop med at google Git-kommandoer og faktisk lærer det!

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

Lad os beregne fordelingen af publishedYear Mark:

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

Juster "%.2f" for at indstille floating point-præcisionen. Dette resulterer i:

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

50% af de givne bøger (2/4) blev udgivet i 2011, 25% (1/4) blev udgivet i 2014 og 25% (1/4) i 2017. Hvad hvis du vil formatere dette resultat anderledes, og normalisere rækkevidden i 0..1?

Beregn normaliseret (procent) fordeling af samling i Java

For at normalisere procenterne fra a 0.0...100.0 rækkevidde til a 0..1 rækkevidde – vi tilpasser simpelthen collectingAndThen() ring til ikke gange antallet med 100.0 før du dividerer med samlingens størrelse.

Tidligere har den Long antal returneret af Collectors.counting() blev implicit konverteret til en dobbelt (multiplikation med en dobbelt værdi) – så denne gang vil vi eksplicit få doubleValue() af 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));
}

Juster "%.4f" for at indstille floating point-præcisionen. Dette resulterer i:

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

Beregn elementantal (hyppighed) af indsamling

Endelig – vi kan få elementantallet (frekvensen af ​​alle elementer) i samlingen ved simpelthen ikke at dividere antallet med samlingens størrelse! Dette er et fuldstændigt ikke-normaliseret antal:

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

Dette resulterer i:

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

Der er faktisk to bøger fra 2011 og en fra 2014 og 2017 hver.

Konklusion

Beregning af distributioner af data er en almindelig opgave i datarige applikationer og kræver ikke brug af eksterne biblioteker eller kompleks kode. Med funktionel programmeringsunderstøttelse gjorde Java arbejdet med samlinger til en leg!

I dette korte udkast har vi taget et kig på, hvordan du kan beregne frekvenstællinger af alle elementer i en samling, samt hvordan du beregner fordelingskort normaliseret til procenter mellem 0 , 1 samt 0 , 100 i Java.

Tidsstempel:

Mere fra Stablemisbrug