Przekształcenie zbioru liczb (lub obiektów, których pola chcesz sprawdzić) w rozkład tych liczb jest powszechną techniką statystyczną i jest wykorzystywana w różnych kontekstach w raportowaniu i aplikacjach opartych na danych.
Biorąc pod uwagę kolekcję:
1, 1, 2, 1, 2, 3, 1, 4, 5, 1, 3
Możesz sprawdzić ich rozkład jako liczebność (częstotliwość każdego elementu) i zapisać wyniki na mapie:
{
"1": 5,
"2": 2,
"3": 2,
"4": 1,
"5": 1
}
Lub możesz normalizować wartości na podstawie łącznej liczby wartości – wyrażając je w ten sposób w procentach:
{
"1": 0.45,
"2": 0.18,
"3": 0.18,
"4": 0.09,
"5": 0.09
}
Lub nawet wyrazić te wartości procentowe w 0..100
format zamiast a 0..1
Format.
W tym przewodniku przyjrzymy się, jak można obliczyć rozkład z kolekcji — zarówno przy użyciu typów pierwotnych, jak i obiektów, których pola możesz chcieć raportować w swojej aplikacji.
Dzięki dodaniu obsługi programowania funkcyjnego w Javie – obliczanie dystrybucji jest łatwiejsze niż kiedykolwiek. Będziemy pracować ze zbiorem liczb i zbiorem Book
s:
public class Book {
private String id;
private String name;
private String author;
private long pageNumber;
private long publishedYear;
}
Oblicz dystrybucję kolekcji w Javie
Przyjrzyjmy się najpierw, jak obliczyć rozkład dla typów pierwotnych. Praca z obiektami pozwala po prostu wywoływać niestandardowe metody z klas domeny, aby zapewnić większą elastyczność obliczeń.
Domyślnie będziemy reprezentować wartości procentowe jako podwójne z 0.00
do 100.00
.
Typy prymitywne
Stwórzmy listę liczb całkowitych i wypiszmy ich rozkład:
List integerList = List.of(1, 1, 2, 1, 2, 3, 1, 4, 5, 1, 3);
System.out.println(calculateIntegerDistribution(integerList));
Rozkład obliczany jest za pomocą:
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()))))));
}
Ta metoda akceptuje listę i przesyła ją strumieniowo. Podczas przesyłania strumieniowego wartości są pogrupowane według ich wartość całkowita – a ich wartości to liczone za pomocą Collectors.counting()
, przed zebraniem w Map
gdzie klucze reprezentują wartości wejściowe, a dublety reprezentują ich udziały procentowe w rozkładzie.
Kluczowe metody tutaj to collect()
który akceptuje dwa kolektory. Kolektor kluczy zbiera, po prostu grupując według wartości kluczy (elementów wejściowych). Kolekcjoner wartości zbiera za pośrednictwem collectingAndThen()
metoda, która pozwala nam policz wartości a następnie sformatuj je w innym formacie, na przykład count * 100.00 / list.size()
co pozwala wyrazić zliczane elementy w procentach:
{1=45.45, 2=18.18, 3=18.18, 4=9.09, 5=9.09}
Sortuj dystrybucję według wartości lub klucza
Tworząc rozkłady — zazwyczaj będziesz chciał posortować wartości. Częściej niż nie, będzie to klucz. Jawa HashMap
s nie gwarantuje zachowania kolejności wstawiania, więc będziemy musieli użyć LinkedHashMap
co robi. Ponadto najłatwiej jest ponownie przesłać mapę i ponownie ją zebrać, ponieważ jest znacznie mniejszy i łatwiejszy w zarządzaniu.
Poprzednia operacja może szybko zwinąć wiele tysięcy rekordów na małe mapy, w zależności od liczby kluczy, z którymi masz do czynienia, więc ponowne przesyłanie strumieniowe nie jest drogie:
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));
}
Obiekty
Jak można to zrobić dla przedmiotów? Obowiązuje ta sama logika! Zamiast funkcji identyfikacji (Integer::intValue
), zamiast tego użyjemy odpowiedniego pola, takiego jak rok wydania naszych książek. Stwórzmy kilka książek, przechowujmy je na liście, a następnie obliczmy rozkłady lat wydawniczych:
Zapoznaj się z naszym praktycznym, praktycznym przewodnikiem dotyczącym nauki Git, zawierającym najlepsze praktyki, standardy przyjęte w branży i dołączoną ściągawkę. Zatrzymaj polecenia Google Git, a właściwie uczyć się to!
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);
Obliczmy rozkład publishedYear
pole:
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));
}
Poprawić "%.2f"
aby ustawić dokładność zmiennoprzecinkową. To skutkuje:
{2011=50.0, 2014=25.0, 2017=25.0}
50% podanych książek (2/4) ukazało się w 2011 roku, 25% (1/4) ukazało się w 2014 roku, a 25% (1/4) w 2017 roku. zakres w 0..1
?
Oblicz znormalizowany (procentowy) rozkład kolekcji w Javie
Aby znormalizować wartości procentowe z a 0.0...100.0
zakres do 0..1
zakres – po prostu dostosujemy collectingAndThen()
Dzwonić do nie pomnóż liczbę przez 100.0
przed podzieleniem przez wielkość kolekcji.
Poprzednio Long
liczba zwrócona przez Collectors.counting()
został domyślnie przekonwertowany na double (mnożenie z wartością double) – więc tym razem będziemy chcieli jawnie uzyskać doubleValue()
ukończenia 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));
}
Poprawić "%.4f"
aby ustawić dokładność zmiennoprzecinkową. To skutkuje:
{2011=0.5, 2014=0.25, 2017=0.25}
Oblicz liczbę elementów (częstotliwość) kolekcji
Wreszcie – możemy uzyskać liczbę elementów (częstotliwość wszystkich elementów) w kolekcji, po prostu nie dzieląc liczby przez rozmiar kolekcji! Jest to w pełni nieznormalizowana liczba:
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));
}
To skutkuje:
{2011=2, 2014=1, 2017=1}
Rzeczywiście, istnieją dwie książki z 2011 roku i po jednej z 2014 i 2017 roku.
Wnioski
Obliczanie dystrybucji danych jest typowym zadaniem w aplikacjach bogatych w dane i nie wymaga użycia bibliotek zewnętrznych ani złożonego kodu. Dzięki funkcjonalnemu wsparciu programowania Java praca z kolekcjami stała się dziecinnie prosta!
W tym krótkim szkicu przyjrzeliśmy się, w jaki sposób można obliczyć liczbę częstotliwości wszystkich elementów w kolekcji, a także jak obliczać mapy rozkładu znormalizowane do wartości procentowych między 0
i 1
jak również 0
i 100
w Javie.