Tính toán phân phối từ Bộ sưu tập trong Java

Nút nguồn: 1734738

Biến một tập hợp các số (hoặc đối tượng thuộc các trường bạn muốn kiểm tra) thành phân phối các số đó là một kỹ thuật thống kê phổ biến và được sử dụng trong nhiều ngữ cảnh khác nhau trong các ứng dụng báo cáo và dựa trên dữ liệu.

Đưa ra một bộ sưu tập:

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

Bạn có thể kiểm tra sự phân bố của chúng dưới dạng số lượng (tần suất của từng phần tử) và lưu trữ kết quả trong bản đồ:

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

Hoặc bạn có thể bình thường hóa các giá trị dựa trên tổng số giá trị - do đó biểu thị chúng theo tỷ lệ phần trăm:

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

Hoặc thậm chí thể hiện các tỷ lệ phần trăm này trong một 0..100 định dạng thay vì một 0..1 định dạng.

Trong hướng dẫn này, chúng ta sẽ xem xét cách bạn có thể tính toán phân bổ từ một bộ sưu tập – cả bằng cách sử dụng các kiểu nguyên thủy và đối tượng là các trường mà bạn có thể muốn báo cáo trong ứng dụng của mình.

Với việc bổ sung hỗ trợ lập trình chức năng trong Java – việc tính toán phân phối trở nên dễ dàng hơn bao giờ hết. Chúng ta sẽ làm việc với một tập hợp các số và một tập hợp các Books:

public class Book {

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

   
}

Tính toán phân phối bộ sưu tập trong Java

Trước tiên chúng ta hãy xem cách bạn có thể tính toán phân phối cho các kiểu nguyên thủy. Làm việc với các đối tượng chỉ cho phép bạn gọi các phương thức tùy chỉnh từ các lớp miền của mình để mang lại sự linh hoạt hơn trong tính toán.

Theo mặc định, chúng tôi sẽ biểu thị tỷ lệ phần trăm dưới dạng gấp đôi từ 0.00 đến 100.00.

Các loại nguyên thủy

Hãy tạo một danh sách các số nguyên và in phân phối của chúng:

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

Phân phối được tính với:

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

Phương thức này chấp nhận một danh sách và truyền trực tuyến nó. Trong khi được phát trực tiếp, các giá trị là được nhóm lại bởi giá trị nguyên của chúng – và giá trị của chúng là tính sử dụng Collectors.counting(), trước khi được thu thập vào một Map trong đó các khóa đại diện cho các giá trị đầu vào và các phím gấp đôi đại diện cho tỷ lệ phần trăm của chúng trong phân phối.

Các phương pháp chính ở đây là collect() cái nào chấp nhận hai nhà sưu tập. Bộ thu thập khóa thu thập bằng cách chỉ cần nhóm theo các giá trị khóa (phần tử đầu vào). Người thu thập giá trị thu thập thông qua collectingAndThen() phương pháp này cho phép chúng tôi đếm các giá trị và sau đó định dạng chúng ở định dạng khác, chẳng hạn như count * 100.00 / list.size() cho phép chúng tôi thể hiện các phần tử được đếm theo tỷ lệ phần trăm:

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

Sắp xếp Phân phối theo Giá trị hoặc Khóa

Khi tạo bản phân phối – thông thường bạn sẽ muốn sắp xếp các giá trị. Thường xuyên hơn không, điều này sẽ xảy ra chính. Java HashMaps không đảm bảo giữ nguyên thứ tự chèn, vì vậy chúng ta sẽ phải sử dụng một LinkedHashMap cái nào có. Ngoài ra, cách dễ dàng nhất là phát lại bản đồ và thu thập lại bản đồ ngay bây giờ vì nó có kích thước nhỏ hơn nhiều và dễ quản lý hơn nhiều.

Thao tác trước đó có thể nhanh chóng thu gọn hàng nghìn bản ghi thành các bản đồ nhỏ, tùy thuộc vào số lượng khóa bạn đang xử lý, do đó việc phát trực tuyến lại không tốn kém:

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

Đối tượng

Làm thế nào điều này có thể được thực hiện cho các đối tượng? Logic tương tự cũng được áp dụng! Thay vì một hàm xác định (Integer::intValue), thay vào đó, chúng tôi sẽ sử dụng trường mong muốn – chẳng hạn như năm xuất bản cho sách của chúng tôi. Hãy tạo một vài cuốn sách, lưu trữ chúng trong một danh sách và sau đó tính toán mức phân bổ của các năm xuất bản:

Xem hướng dẫn thực hành, thực tế của chúng tôi để học Git, với các phương pháp hay nhất, các tiêu chuẩn được ngành công nghiệp chấp nhận và bảng lừa đảo đi kèm. Dừng lệnh Googling Git và thực sự học nó!

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

Hãy tính phân bố của publishedYear cánh đồng:

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

Điều chỉnh "%.2f" để đặt độ chính xác của dấu chấm động. Kết quả này trong:

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

50% số sách nhất định (2/4) được xuất bản vào năm 2011, 25% (1/4) được xuất bản vào năm 2014 và 25% (1/4) vào năm 2017. Điều gì sẽ xảy ra nếu bạn muốn định dạng kết quả này khác và chuẩn hóa phạm vi trong 0..1?

Tính toán phân phối bộ sưu tập chuẩn hóa (phần trăm) trong Java

Để chuẩn hóa tỷ lệ phần trăm từ 0.0...100.0 phạm vi đến một 0..1 phạm vi – chúng tôi sẽ chỉ điều chỉnh collectingAndThen() gọi tới không nhân số lượng với 100.0 trước khi chia cho kích thước của bộ sưu tập.

Trước đây, Long đếm được trả lại bởi Collectors.counting() đã được chuyển ngầm thành một phép nhân đôi (phép nhân có giá trị gấp đôi) – vì vậy lần này, chúng ta sẽ muốn lấy một cách rõ ràng doubleValue() của 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));
}

Điều chỉnh "%.4f" để đặt độ chính xác của dấu chấm động. Kết quả này trong:

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

Tính số phần tử (tần suất) của tập hợp

Cuối cùng - chúng ta có thể lấy số lượng phần tử (tần số của tất cả các phần tử) trong bộ sưu tập bằng cách không chia số lượng cho kích thước của bộ sưu tập! Đây là số lượng hoàn toàn không được chuẩn hóa:

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

Kết quả này trong:

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

Thật vậy, có hai cuốn sách từ năm 2011, và một cuốn từ năm 2014 và 2017.

Kết luận

Tính toán phân bố dữ liệu là một nhiệm vụ phổ biến trong các ứng dụng giàu dữ liệu và không yêu cầu sử dụng thư viện bên ngoài hoặc mã phức tạp. Với sự hỗ trợ lập trình chức năng, Java khiến việc làm việc với các bộ sưu tập trở nên dễ dàng!

Trong bản nháp ngắn này, chúng ta đã xem xét cách bạn có thể tính toán số lượng tần suất của tất cả các phần tử trong một bộ sưu tập cũng như cách tính toán bản đồ phân phối được chuẩn hóa thành tỷ lệ phần trăm giữa 01 cũng như 0100 trong Java.

Dấu thời gian:

Thêm từ xếp chồng lên nhau